C++ se compila a código máquina y da un control muy fino sobre la memoria, por eso se usa donde el rendimiento importa: motores de videojuegos, navegadores, programas de edición, bases de datos o sistemas embebidos. Es más exigente que otros lenguajes, pero sus herramientas modernas (C++11 en adelante) lo hacen mucho más seguro.
Videojuegos y motores gráficos
Programas de alto rendimiento
Sistemas y dispositivos
Para empezar: Instala un compilador (GCC, Clang o Visual Studio con C++) y compila cada ejemplo con g++ -std=c++20 main.cpp -o main.
Nivel 1 Básico
1
Lección 1 · Básico
Tu primer programa
Un programa en C++ empieza en la función main. #include <iostream> trae la parte de la biblioteca estándar que permite escribir en la consola con std::cout y el operador <<.
El compilador traduce el archivo .cpp a un ejecutable. Si cambias el código, hay que volver a compilar.
Respuesta:cout. std::cout es la salida estándar; con << le envías lo que quieres mostrar.
2
Lección 2 · Básico
Variables y tipos
Cada variable tiene un tipo fijo: int, double, bool, char o std::string para texto (con #include <string>). Con auto, el compilador deduce el tipo del valor.
Inicializa siempre las variables: en C++ una variable local sin valor inicial contiene basura.
Ejemplo práctico
#include <iostream>
#include <string>
int main() {
int unidades = 3;
double precio = 19.99;
std::string producto = "Auriculares";
auto total = unidades * precio; // double
std::cout << producto << ": " << total << '\n';
}
Ejercicio
¿Qué hace auto en auto total = unidades * precio;?
Ver la solución
Respuesta: El compilador deduce el tipo del valor. auto deduce el tipo en tiempo de compilación; la variable sigue teniendo un tipo fijo (aquí, double).
3
Lección 3 · Básico
Condiciones y bucles
if y else deciden qué se ejecuta; for repite con un contador y while mientras se cumpla una condición. Los bloques van entre llaves.
Las comparaciones usan ==, !=, <, >, <= y >=, y se combinan con && (y) y || (o).
Ejemplo práctico
#include <iostream>
int main() {
for (int i = 1; i <= 5; ++i) {
if (i % 2 == 0) {
std::cout << i << " es par\n";
} else {
std::cout << i << " es impar\n";
}
}
}
Ejercicio
¿Qué calcula i % 2?
Ver la solución
Respuesta: El resto de dividir i entre 2. % es el resto de la división: vale 0 si el número es par.
Encuentra los errores
Este programa debería sumar los números del 1 al 10 y mostrar 55. Tiene 2 errores: uno da un resultado impredecible y otro se deja un número fuera.
Los errores
Línea 4: En C++ una variable local sin inicializar contiene un valor cualquiera (lo que hubiera en esa memoria), así que la suma empieza desde un número imprevisible. Corrección: int suma = 0;
Línea 5: Con i < 10 el bucle termina en 9 y el 10 no se suma; para incluirlo hace falta <=. Corrección: for (int i = 1; i <= 10; i++) {
Inicializa siempre las variables al declararlas. Los errores de «uno de más o de menos» en los bucles son de los más frecuentes: revisa si el límite debe incluirse.
Ver la solución
Línea 4: En C++ una variable local sin inicializar contiene un valor cualquiera (lo que hubiera en esa memoria), así que la suma empieza desde un número imprevisible. Corrección: int suma = 0;
Línea 5: Con i < 10 el bucle termina en 9 y el 10 no se suma; para incluirlo hace falta <=. Corrección: for (int i = 1; i <= 10; i++) {
4
Lección 4 · Básico
std::vector: listas que crecen
std::vector es la lista dinámica de la biblioteca estándar: crece al añadir elementos con push_back y size() dice cuántos tiene.
El bucle for basado en rango recorre todos los elementos sin manejar posiciones.
Ejemplo práctico
#include <iostream>
#include <vector>
int main() {
std::vector<int> notas = {7, 8, 9};
notas.push_back(10);
int suma = 0;
for (int n : notas) suma += n;
std::cout << "Media: " << static_cast<double>(suma) / notas.size() << '\n'; // 8.5
}
Ejercicio
Completa para añadir un 6 al final del vector.
notas.(6);
Ver la solución
Respuesta:push_back. push_back añade un elemento al final del vector.
Escribe el código
Escribe int contarMayores(const std::vector<int>& numeros, int limite): devuelve cuántos números del vector son mayores que limite. Recórrelo con un for de rango.
Tab escribe una sangría; para salir del editor con el teclado, pulsa Esc y luego Tab. Ctrl + Intro corrige.
Una solución posible
int contarMayores(const std::vector<int>& numeros, int limite) {
int cuenta = 0;
for (int n : numeros) {
if (n > limite) {
cuenta++;
}
}
return cuenta;
}
const std::vector<int>& pasa el vector por referencia sin copiarlo y sin permitir cambiarlo. Con la biblioteca estándar sería std::count_if.
Ver la solución
int contarMayores(const std::vector<int>& numeros, int limite) {
int cuenta = 0;
for (int n : numeros) {
if (n > limite) {
cuenta++;
}
}
return cuenta;
}
const std::vector<int>& pasa el vector por referencia sin copiarlo y sin permitir cambiarlo. Con la biblioteca estándar sería std::count_if.
Nivel 2 Intermedio
5
Lección 5 · Intermedio
Funciones y referencias
Una función declara el tipo que devuelve, su nombre y sus parámetros. Por defecto los argumentos se copian; con una referencia (&) la función trabaja con la variable original.
Para objetos grandes que no vas a modificar, pásalos como const&: no se copian y tampoco se pueden cambiar.
Ejemplo práctico
#include <iostream>
#include <string>
void duplicar(int& n) { n *= 2; } // modifica el original
std::string saludo(const std::string& nombre) { // sin copia ni cambios
return "Hola, " + nombre;
}
int main() {
int x = 5;
duplicar(x);
std::cout << x << '\n'; // 10
std::cout << saludo("Ana") << '\n';
}
Ejercicio
¿Qué ocurre con void duplicar(int& n) al llamar duplicar(x)?
Ver la solución
Respuesta: x cambia, porque se pasa por referencia. Con & el parámetro es otro nombre para la misma variable, así que el cambio se ve fuera.
Escribe el código
Escribe void intercambiar(int& a, int& b): intercambia los valores de las dos variables que recibe. Hazlo con una variable auxiliar, sin std::swap.
Tab escribe una sangría; para salir del editor con el teclado, pulsa Esc y luego Tab. Ctrl + Intro corrige.
Una solución posible
void intercambiar(int& a, int& b) {
int aux = a;
a = b;
b = aux;
}
Sin la variable auxiliar, al hacer a = b se perdería el valor original de a. El & hace que la función trabaje con las variables originales y no con copias.
Ver la solución
void intercambiar(int& a, int& b) {
int aux = a;
a = b;
b = aux;
}
Sin la variable auxiliar, al hacer a = b se perdería el valor original de a. El & hace que la función trabaje con las variables originales y no con copias.
6
Lección 6 · Intermedio
Clases
Una clase agrupa datos y funciones. Lo que está en private solo se puede usar desde dentro; lo que está en public, desde cualquier parte.
El constructor prepara el objeto al crearlo; con la lista de inicialización (: saldo_(inicial)) das valor a los atributos.
Respuesta: Solo desde dentro de la propia clase. private oculta el miembro: solo los métodos de la clase pueden usarlo.
7
Lección 7 · Intermedio
La biblioteca estándar: algoritmos y map
La biblioteca estándar trae contenedores como std::map (pares clave–valor ordenados por clave) y algoritmos listos para usar en <algorithm>, como std::sort o std::count_if.
Antes de escribir tu propio bucle para ordenar o buscar, mira si ya existe un algoritmo: será correcto y eficiente.
Respuesta:sort. std::sort ordena el rango entre los dos iteradores que le pases.
Encuentra los errores
Este programa debería mostrar 1 (solo hay peras) y después 13 (las peras más 10). Tiene 2 errores.
Los errores
Línea 7:stock["kiwis"] crea la clave «kiwis» con valor 0 si no existe, así que el mapa pasa a tener 2 elementos. Para consultar sin crear, usa contains (C++20) o find. Corrección: if (stock.contains("kiwis") && stock.at("kiwis") > 0) std::cout << "Hay kiwis\n";
Línea 9:auto [fruta, n] copia cada elemento: se suma 10 a la copia y el mapa no cambia. Hace falta una referencia: auto&. Corrección: for (auto& [fruta, n] : stock) n += 10;
El operador [] de std::map es cómodo para escribir, pero al leer una clave que no existe la crea en silencio.
Ver la solución
Línea 7:stock["kiwis"] crea la clave «kiwis» con valor 0 si no existe, así que el mapa pasa a tener 2 elementos. Para consultar sin crear, usa contains (C++20) o find. Corrección: if (stock.contains("kiwis") && stock.at("kiwis") > 0) std::cout << "Hay kiwis\n";
Línea 9:auto [fruta, n] copia cada elemento: se suma 10 a la copia y el mapa no cambia. Hace falta una referencia: auto&. Corrección: for (auto& [fruta, n] : stock) n += 10;
Nivel 3 Avanzado
8
Lección 8 · Avanzado
Memoria y punteros inteligentes
Un puntero guarda la dirección de memoria de otro dato. Reservar memoria con new obliga a liberarla con delete, y olvidarlo provoca fugas o fallos graves.
En C++ moderno se usan punteros inteligentes: std::unique_ptr libera la memoria solo cuando deja de usarse (RAII). Así no hace falta escribir delete.
¿Qué ventaja tiene std::unique_ptr frente a new y delete?
Ver la solución
Respuesta: Libera la memoria automáticamente. Cuando el unique_ptr sale de su ámbito, su destructor libera la memoria: no puedes olvidarte de hacerlo.
9
Lección 9 · Avanzado
Plantillas
Una plantilla (template) es código genérico: escribes la función una vez y el compilador genera una versión para cada tipo con el que la uses.
Toda la biblioteca estándar está hecha con plantillas: std::vector<int> y std::vector<std::string> salen del mismo código.
Ejemplo práctico
#include <iostream>
#include <string>
template <typename T>
T mayor(T a, T b) {
return a > b ? a : b;
}
int main() {
std::cout << mayor(3, 7) << '\n'; // 7
std::cout << mayor(std::string("pera"), std::string("kiwi")) << '\n'; // pera
}
Ejercicio
¿Qué hace el compilador con template <typename T>?
Ver la solución
Respuesta: Crea una versión de la función para cada tipo usado. Cada vez que usas la plantilla con un tipo nuevo, el compilador genera el código para ese tipo.
10
Lección 10 · Avanzado
Herencia y polimorfismo
Una clase puede heredar de otra y redefinir sus métodos virtual con override. Así, una lista de punteros a la clase base puede contener objetos distintos y cada uno responde a su manera.
Una clase base con métodos virtuales debe tener un destructor virtual para que los objetos se destruyan bien.
Completa la palabra que indica que el método redefine uno de la clase base.
double area() const { return lado * lado; }
Ver la solución
Respuesta:override. override pide al compilador que compruebe que de verdad estás redefiniendo un método virtual.
11
Lección 11 · Avanzado
std::string a fondo
std::string guarda un texto y gestiona su memoria solo. find busca un trozo y devuelve su posición (o std::string::npos si no está), substr(posición, longitud) extrae una parte y + o append añaden texto.
Para convertir entre números y texto: std::to_string(42) da "42" y std::stoi("42") da 42. stoi lanza una excepción si el texto no empieza por un número.
¿Qué devuelve correo.find('#') si el texto no contiene #?
Ver la solución
Respuesta:std::string::npos. find devuelve std::string::npos (un valor especial muy grande) cuando no encuentra nada; por eso se compara con él antes de usar la posición.
12
Lección 12 · Avanzado
Excepciones
Cuando una función no puede hacer su trabajo (un dato fuera de rango, un archivo que no se abre), puede lanzar una excepción con throw. El error sube por las funciones que la llamaron hasta que algún try … catch lo captura.
La biblioteca estándar trae tipos de error como std::invalid_argument o std::runtime_error, que guardan un mensaje legible con what(). Captura las excepciones por referencia constante: catch (const std::exception& e).
Ejemplo práctico
#include <iostream>
#include <stdexcept>
double dividir(double a, double b) {
if (b == 0) {
throw std::invalid_argument("No se puede dividir entre 0");
}
return a / b;
}
int main() {
try {
std::cout << dividir(10, 4) << "\n"; // 2.5
std::cout << dividir(1, 0) << "\n"; // lanza la excepción
} catch (const std::exception& e) {
std::cout << "Error: " << e.what() << "\n"; // Error: No se puede dividir entre 0
}
}
Ejercicio
Completa para capturar cualquier excepción de la biblioteca estándar.
catch (const std::& e) {
Ver la solución
Respuesta:exception. std::exception es la clase base de los errores de la biblioteca estándar; capturándola por referencia constante atrapas todos sus tipos.
13
Lección 13 · Avanzado
Lambdas y algoritmos
Una lambda es una función sin nombre que se escribe justo donde se usa: [](int a, int b) { return a > b; }. Los algoritmos de <algorithm>, como std::sort o std::count_if, aceptan una lambda para decidir cómo ordenar o qué contar.
Entre los corchetes se indica qué variables de fuera puede usar la lambda: [limite] la copia y [&total] trabaja con la original.
Completa la llamada a std::sort con una lambda que ordene el vector nombres (de std::string) de la Z a la A, es decir, en orden descendente.
Tab escribe una sangría; para salir del editor con el teclado, pulsa Esc y luego Tab. Ctrl + Intro corrige.
Una solución posible
std::vector<std::string> nombres{"Luis", "Ana", "Marta"};
std::sort(nombres.begin(), nombres.end(),
[](const std::string& a, const std::string& b) { return a > b; });
std::sort llama a la lambda para saber si a va antes que b. Con a > b, los mayores (en orden alfabético) van primero.
Ver la solución
std::vector<std::string> nombres{"Luis", "Ana", "Marta"};
std::sort(nombres.begin(), nombres.end(),
[](const std::string& a, const std::string& b) { return a > b; });
std::sort llama a la lambda para saber si a va antes que b. Con a > b, los mayores (en orden alfabético) van primero.
14
Lección 14 · Avanzado
std::optional: un valor que puede faltar
Una función que busca algo no siempre lo encuentra. Devolver -1 o un puntero nulo obliga a recordar ese «valor especial»; std::optional<T> (desde C++17) deja claro en el propio tipo que puede no haber resultado.
Se comprueba como un booleano (if (resultado)), se lee con *resultado y value_or(valor) da uno por defecto. std::nullopt significa «no hay valor».
Ejemplo práctico
#include <iostream>
#include <optional>
#include <string>
#include <vector>
std::optional<int> posicionDe(const std::vector<std::string>& lista, const std::string& buscado) {
for (int i = 0; i < static_cast<int>(lista.size()); i++) {
if (lista[i] == buscado) return i;
}
return std::nullopt;
}
int main() {
std::vector<std::string> colores{"rojo", "verde", "azul"};
if (auto pos = posicionDe(colores, "verde")) {
std::cout << "verde está en la posición " << *pos << "\n"; // 1
}
std::cout << posicionDe(colores, "lila").value_or(-1) << "\n"; // -1
}
Ejercicio
¿Qué devuelve posicionDe(colores, "lila").value_or(-1) si "lila" no está?
Ver la solución
Respuesta: -1. El optional está vacío (std::nullopt), así que value_or devuelve el valor por defecto que le pasas. .value() sí lanzaría una excepción.
15
Lección 15 · Avanzado
Leer y escribir archivos con fstream
std::ofstream escribe en un archivo y std::ifstream lo lee, con los mismos << y >> que usas con la consola. Comprueba siempre que el archivo se ha abierto (if (!archivo)) antes de usarlo.
std::getline(archivo, linea) lee una línea completa cada vez, perfecto en un while. Los archivos se cierran solos al salir del bloque donde se crearon.
Ejemplo práctico
#include <fstream>
#include <iostream>
#include <string>
int main() {
{
std::ofstream salida("notas.txt");
salida << "Ana 7.5\nLuis 9\nMarta 6\n";
} // aquí se cierra y se guarda
std::ifstream entrada("notas.txt");
if (!entrada) {
std::cerr << "No se pudo abrir notas.txt\n";
return 1;
}
std::string nombre;
double nota, suma = 0;
int cuantas = 0;
while (entrada >> nombre >> nota) {
suma += nota;
cuantas++;
}
std::cout << "Media: " << suma / cuantas << "\n"; // Media: 7.5
}
Ejercicio
Completa para abrir un archivo y leerlo.
std:: entrada("datos.txt");
Ver la solución
Respuesta:ifstream. ifstream (input file stream) lee archivos; ofstream (output) los escribe.
¿Has terminado? Sigue practicando
Repasa los conceptos en el diccionario, practica con estas herramientas o prueba otro lenguaje: las ideas que has aprendido (variables, condiciones, funciones, objetos…) te servirán en cualquiera.
* Porcentaje de personas que dijeron haber usado C++ en la Stack Overflow Developer Survey 2026: Technology (pregunta sobre lenguajes de programación, de scripts y de marcado; todas las respuestas). El progreso de las lecciones se guarda solo en este navegador.
¿Nos dejas medir las visitas?
Con tu permiso usamos Google Analytics, que guarda cookies, para saber cuántas personas visitan la web y qué páginas les resultan útiles. No usamos cookies de publicidad y tus archivos nunca salen de tu navegador. Puedes cambiar de opinión cuando quieras en «Configurar cookies», al pie de la página. Más información.