Programación · n.º 10 entre los lenguajes más usados

Aprende C++ desde cero

Máximo rendimiento: videojuegos, navegadores, sistemas y programas exigentes.

  • 15 lecciones
  • 19 ejercicios interactivos
  • 21 conceptos clave
  • 24,3 % lo usa*

Qué es C++ y para qué sirve

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

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.

Ejemplo práctico
// main.cpp
#include <iostream>

int main() {
    std::cout << "¡Hola, mundo!\n";
    std::cout << 2 + 3 << '\n';   // 5
    return 0;
}

// g++ -std=c++20 main.cpp -o main
// ./main

Ejercicio

Completa para escribir en la consola.

std:: << "Hola\n";
Ver la solución

Respuesta: cout. std::cout es la salida estándar; con << le envías lo que quieres mostrar.

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).

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.

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++) {

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.

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

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.

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.

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.

Ejemplo práctico
#include <iostream>

class Cuenta {
public:
    explicit Cuenta(double inicial) : saldo_(inicial) {}
    void ingresar(double cantidad) { saldo_ += cantidad; }
    double saldo() const { return saldo_; }
private:
    double saldo_;
};

int main() {
    Cuenta c(100);
    c.ingresar(50);
    std::cout << c.saldo() << '\n';   // 150
}

Ejercicio

¿Desde dónde se puede usar un miembro private?

Ver la solución

Respuesta: Solo desde dentro de la propia clase. private oculta el miembro: solo los métodos de la clase pueden usarlo.

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.

Ejemplo práctico
#include <algorithm>
#include <iostream>
#include <map>
#include <string>
#include <vector>

int main() {
    std::vector<int> v = {5, 2, 9, 1};
    std::sort(v.begin(), v.end());                 // 1 2 5 9

    std::map<std::string, int> stock = {{"teclado", 10}, {"raton", 0}};
    stock["monitor"] = 3;
    for (const auto& [producto, unidades] : stock)
        std::cout << producto << ": " << unidades << '\n';
}

Ejercicio

Completa para ordenar el vector v.

std::(v.begin(), v.end());
Ver la solución

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.

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

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.

Ejemplo práctico
#include <iostream>
#include <memory>

struct Imagen {
    Imagen()  { std::cout << "Cargada\n"; }
    ~Imagen() { std::cout << "Liberada\n"; }
};

int main() {
    {
        auto img = std::make_unique<Imagen>();   // Cargada
        // … usar img …
    }                                            // Liberada (automático)
    std::cout << "Fin\n";
}

Ejercicio

¿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.

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.

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.

Ejemplo práctico
#include <iostream>
#include <memory>
#include <vector>

struct Figura {
    virtual ~Figura() = default;
    virtual double area() const = 0;   // método abstracto
};
struct Cuadrado : Figura {
    double lado;
    explicit Cuadrado(double l) : lado(l) {}
    double area() const override { return lado * lado; }
};

int main() {
    std::vector<std::unique_ptr<Figura>> figuras;
    figuras.push_back(std::make_unique<Cuadrado>(3));
    for (const auto& f : figuras) std::cout << f->area() << '\n'; // 9
}

Ejercicio

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.

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.

Ejemplo práctico
#include <iostream>
#include <string>

int main() {
    std::string correo = "ana.garcia@ejemplo.es";

    auto arroba = correo.find('@');
    if (arroba != std::string::npos) {
        std::string usuario = correo.substr(0, arroba);
        std::string dominio = correo.substr(arroba + 1);
        std::cout << usuario << " | " << dominio << "\n";  // ana.garcia | ejemplo.es
    }

    int edad = std::stoi("41");
    std::string texto = "Edad: " + std::to_string(edad + 1);
    std::cout << texto << " (" << texto.size() << " caracteres)\n";  // Edad: 42 (8 caracteres)
}

Ejercicio

¿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.

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.

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.

Ejemplo práctico
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>

struct Producto {
    std::string nombre;
    double precio;
};

int main() {
    std::vector<Producto> productos{{"Ratón", 12.5}, {"Teclado", 35}, {"Cable", 6}};

    std::sort(productos.begin(), productos.end(),
              [](const Producto& a, const Producto& b) { return a.precio < b.precio; });
    for (const auto& p : productos) std::cout << p.nombre << " ";  // Cable Ratón Teclado
    std::cout << "\n";

    double limite = 10;
    auto caros = std::count_if(productos.begin(), productos.end(),
                               [limite](const Producto& p) { return p.precio > limite; });
    std::cout << caros << " caros\n";  // 2 caros
}

Escribe el código

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.

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.

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.

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.

Fuentes consultadas

* 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.