Ir al contenido

Flujo de Control y Bucles

Domina todos los constructos de flujo de control de C++ incluyendo if/else, switch, todas las formas de bucle, range-based for, inicializadores de C++17 y structured bindings.

Flujo de Control en C++

Las sentencias de flujo de control determinan el orden en que tu programa se ejecuta. C++ proporciona un rico conjunto de constructos de flujo de control que van mucho mas alla de los basicos heredados de C. C++ moderno (C++17 y posteriores) agrega caracteristicas como if con inicializadores y constexpr if que hacen el flujo de control mas expresivo y seguro.

Un principio clave: minimiza el ambito de las variables. Cada variable debe declararse lo mas cerca posible de su primer uso, y en el ambito mas estrecho posible. Las caracteristicas de C++17 como if con inicializadores te ayudan a seguir este principio.

if/else y switch

Las sentencias if y switch son los mecanismos principales de ramificacion. C++17 agrega clausulas de inicializacion a ambos:

control_flow.cppc++23
#include <iostream>
#include <map>
#include <string>
#include <optional>

std::optional<int> find_value(const std::string& key) {
    std::map<std::string, int> data = {{"alpha", 1}, {"beta", 2}, {"gamma", 3}};
    if (auto it = data.find(key); it != data.end()) {  // C++17 if with initializer
        return it->second;
    }
    return std::nullopt;
}

int main() {
    // C++17: if with initializer — the variable's scope is limited to the if/else
    if (auto val = find_value("beta"); val.has_value()) {
        std::cout << "Found: " << *val << '\n';
    } else {
        std::cout << "Not found\n";
    }
    // 'val' is no longer in scope here — cleaner!

    // switch with C++17 initializer
    enum class Color { Red, Green, Blue };
    Color c = Color::Green;

    switch (auto name = "unknown"; c) {  // C++17 switch with initializer
        case Color::Red:   name = "red";   std::cout << name << '\n'; break;
        case Color::Green: name = "green"; std::cout << name << '\n'; break;
        case Color::Blue:  name = "blue";  std::cout << name << '\n'; break;
    }

    // [[fallthrough]] attribute (C++17) — document intentional fallthrough
    int priority = 2;
    switch (priority) {
        case 1:
            std::cout << "Critical: ";
            [[fallthrough]];       // Tells the compiler this is intentional
        case 2:
            std::cout << "Important: ";
            [[fallthrough]];
        case 3:
            std::cout << "Handle it\n";
            break;
        default:
            std::cout << "Unknown priority\n";
    }
}
Salida

Pulsa Ejecutar para compilar y ejecutar este ejemplo.

Bucles: for, while, do-while y range-based for

C++ ofrece multiples formas de bucle. El bucle range-based for (C++11) es preferido al iterar sobre contenedores porque elimina errores de off-by-one y es mas claro:

loops.cppc++23
#include <iostream>
#include <vector>
#include <string>
#include <map>

int main() {
    std::vector<std::string> names = {"Alice", "Bob", "Charlie"};

    // Traditional for loop — use when you need the index
    for (std::size_t i = 0; i < names.size(); ++i) {
        std::cout << i << ": " << names[i] << '\n';
    }

    // Range-based for loop (C++11) — preferred for iteration
    for (const auto& name : names) {        // const auto& avoids copying
        std::cout << name << '\n';
    }

    // Range-based for with structured bindings (C++17)
    std::map<std::string, int> scores = {{"Alice", 95}, {"Bob", 87}, {"Charlie", 92}};
    for (const auto& [name, score] : scores) {   // Destructure the pair
        std::cout << name << " scored " << score << '\n';
    }

    // C++20: range-based for with init-statement
    for (auto v = std::vector{1, 2, 3, 4, 5}; auto elem : v) {
        std::cout << elem << ' ';
    }
    std::cout << '\n';

    // while loop — use when the number of iterations is unknown
    int n = 1;
    while (n <= 1024) {
        std::cout << n << ' ';
        n *= 2;
    }
    std::cout << '\n';

    // do-while — body executes at least once
    int input;
    do {
        std::cout << "Enter a positive number: ";
        input = 42;  // Simulated input for example
    } while (input <= 0);
}
Salida

Pulsa Ejecutar para compilar y ejecutar este ejemplo.

Structured Bindings (C++17)

Los structured bindings (C++17) te permiten descomponer un objeto en sus partes constituyentes. Funcionan con arrays, structs con todos los miembros publicos, std::pair, std::tuple, y cualquier tipo que soporte el protocolo de structured bindings.

La sintaxis auto [a, b] = expr; crea referencias con nombre a los componentes de expr. Puedes agregar calificadores const y &: const auto& [a, b] = expr; crea referencias const sin copiar.

Los structured bindings son especialmente poderosos con la iteracion de std::map (descomponiendo std::pair), retornos de funciones que usan std::pair o std::tuple, y el valor de retorno del metodo insert en contenedores asociativos.

Buena práctica

Usa break para salir de un bucle anticipadamente cuando hayas encontrado lo que necesitas. Usa continue para omitir el resto de la iteracion actual y pasar a la siguiente. Ambos hacen los bucles mas faciles de entender cuando se usan juiciosamente.

Prefiere range-based for sobre for basado en indices cuando no necesites el indice. Elimina clases enteras de errores: errores de off-by-one, usar la variable incorrecta como indice, y advertencias de comparacion signed/unsigned.

Cuando necesites el indice, considera usar std::size_t como tipo de la variable del bucle para coincidir con el tipo de retorno de .size(), o usa std::ssize() (C++20) que retorna un tamano con signo.

Evita bucles profundamente anidados — si te encuentras anidando tres o mas bucles, extrae los bucles internos a funciones. Esto mejora la legibilidad y la capacidad de prueba.

  • Prefiere bucles range-based for sobre bucles basados en indices cuando el indice no es necesario
  • Usa const auto& en range-based for para evitar copias innecesarias
  • Usa structured bindings para descomponer pares y tuplas en variables de bucle
  • Extrae bucles profundamente anidados a funciones con nombre para mayor claridad

constexpr if (C++17): Ramificacion en Tiempo de Compilacion

if constexpr evalua la condicion en tiempo de compilacion y descarta la rama que no se toma. Esto es esencial en la programacion con plantillas donde diferentes ramas pueden no ser validas para todos los tipos:

constexpr_if.cppc++23
#include <iostream>
#include <type_traits>
#include <string>

template <typename T>
void describe(T value) {
    std::cout << "Value: " << value;

    if constexpr (std::is_integral_v<T>) {
        // This branch only compiles when T is an integer type
        std::cout << " (integer, " << (value % 2 == 0 ? "even" : "odd") << ")";
    } else if constexpr (std::is_floating_point_v<T>) {
        // This branch only compiles when T is a floating-point type
        std::cout << " (floating-point)";
    } else {
        std::cout << " (other type)";
    }
    std::cout << '\n';
}

int main() {
    describe(42);           // Value: 42 (integer, even)
    describe(3.14);         // Value: 3.14 (floating-point)
    describe("hello");      // Value: hello (other type)
}
Salida

Pulsa Ejecutar para compilar y ejecutar este ejemplo.

Puntos clave
  • El if/switch de C++17 con inicializadores limita el ambito de la variable a la sentencia — usalos para mantener el codigo limpio
  • Los bucles range-based for eliminan errores de off-by-one y problemas de comparacion signed/unsigned
  • Los structured bindings (C++17) descomponen pares, tuplas y structs en variables con nombre
  • Usa [[fallthrough]] para documentar el fallthrough intencional en los case de switch
  • if constexpr (C++17) evalua condiciones en tiempo de compilacion — la rama descartada no se compila
  • Minimiza el ambito de las variables: declara las variables lo mas cerca posible del primer uso

Quiz — Pon a prueba tus conocimientos

(15 XP)

1. Cual es la ventaja del `if` con inicializador de C++17: `if (auto x = f(); x > 0)`?

2. Que hace `for (const auto& [key, val] : my_map)`?

3. Que sucede con la rama descartada de una sentencia `if constexpr`?