Buscar..


Introducción

Una instrucción de bucle ejecuta un grupo de instrucciones repetidamente hasta que se cumple una condición. Hay 3 tipos de bucles primitivos en C ++: para, while y do ... while.

Sintaxis

  • sentencia while ( condición );
  • hacer enunciado while ( expresión );
  • para ( para-init-sentencia ; condición ; expresión ) sentencia ;
  • para (para-gama-declaración: Con fines de gama-inicializador) Declaración;
  • rotura ;
  • continuar

Observaciones

algorithm llamadas de algorithm son generalmente preferibles a los bucles escritos a mano.

Si desea algo que ya hace un algoritmo (o algo muy similar), la llamada al algoritmo es más clara, a menudo más eficiente y menos propensa a errores.

Si necesita un bucle que haga algo bastante simple (pero requeriría una confusa maraña de carpetas y adaptadores si estuviera usando un algoritmo), simplemente escriba el bucle.

Basado en rango para

C ++ 11

for bucles pueden utilizarse para recorrer en iteración los elementos de un rango basado en iteradores, sin usar un índice numérico o acceder directamente a los iteradores:

vector<float> v = {0.4f, 12.5f, 16.234f};

for(auto val: v)
{
    std::cout << val << " ";
}

std::cout << std::endl;

Esto iterará sobre cada elemento en v , con val obteniendo el valor del elemento actual. La siguiente declaración:

for (for-range-declaration : for-range-initializer ) statement

es equivalente a:

{
    auto&& __range = for-range-initializer;
    auto __begin = begin-expr, __end = end-expr;
    for (; __begin != __end; ++__begin) {
        for-range-declaration = *__begin;
        statement
    }
}
C ++ 17
{
    auto&& __range = for-range-initializer;
    auto __begin = begin-expr;
    auto __end = end-expr; // end is allowed to be a different type than begin in C++17
    for (; __begin != __end; ++__begin) {
        for-range-declaration = *__begin;
        statement
    }
}

Este cambio se introdujo para el soporte planificado de Ranges TS en C ++ 20.

En este caso, nuestro bucle es equivalente a:

{
    auto&& __range = v;
    auto __begin = v.begin(), __end = v.end();
    for (; __begin != __end; ++__begin) {
        auto val = *__begin;
        std::cout << val << " ";
    }
}

Tenga en cuenta que auto val declara un tipo de valor, que será una copia de un valor almacenado en el rango (lo estamos inicializando desde el iterador). Si los valores almacenados en el rango son costosos de copiar, es posible que desee utilizar const auto &val . Tampoco está obligado a utilizar auto ; puede usar un nombre de tipo apropiado, siempre que sea implícitamente convertible del tipo de valor del rango.

Si necesita acceso al iterador, basado en rango no puede ayudarlo (no sin esfuerzo, al menos).

Si desea referenciarlo, puede hacerlo:

vector<float> v = {0.4f, 12.5f, 16.234f};

for(float &val: v)
{
    std::cout << val << " ";
}

Podría iterar en la referencia const si tiene un contenedor const :

const vector<float> v = {0.4f, 12.5f, 16.234f};

for(const float &val: v)
{
    std::cout << val << " ";
}

Se utilizarían referencias de reenvío cuando el iterador de secuencia devuelva un objeto proxy y usted necesite operar en ese objeto de forma no const . Nota: lo más probable es que confunda a los lectores de su código.

vector<bool> v(10);

for(auto&& val: v)
{
    val = true;
}

El tipo de "gama" proporcionada al intervalo basado- for puede ser uno de los siguientes:

  • Matrices de idiomas:

    float arr[] = {0.4f, 12.5f, 16.234f};
    
    for(auto val: arr)
    {
        std::cout << val << " ";
    }
    

    Tenga en cuenta que la asignación de una matriz dinámica no cuenta:

    float *arr = new float[3]{0.4f, 12.5f, 16.234f};
    
    for(auto val: arr) //Compile error.
    {
        std::cout << val << " ";
    }
    
  • Cualquier tipo que tenga funciones miembro begin() y end() , que devuelve los iteradores a los elementos del tipo. Los contenedores de la biblioteca estándar califican, pero los tipos definidos por el usuario también se pueden usar:

    struct Rng
    {
        float arr[3];
    
        // pointers are iterators
        const float* begin() const {return &arr[0];}
        const float* end() const   {return &arr[3];}
        float* begin() {return &arr[0];}
        float* end()   {return &arr[3];}
    };
    
    int main()
    {
        Rng rng = {{0.4f, 12.5f, 16.234f}};
    
        for(auto val: rng)
        {
            std::cout << val << " ";
        }
    }
    
  • Cualquier tipo que tenga funciones de begin(type) y end(type) que no sean miembros que se pueden encontrar mediante búsqueda dependiente de argumento, según el type . Esto es útil para crear un tipo de rango sin tener que modificar el tipo de clase en sí:

    namespace Mine
    {
        struct Rng {float arr[3];};
    
        // pointers are iterators
        const float* begin(const Rng &rng) {return &rng.arr[0];}
        const float* end(const Rng &rng) {return &rng.arr[3];}
        float* begin(Rng &rng) {return &rng.arr[0];}
        float* end(Rng &rng) {return &rng.arr[3];}
    }
    
    int main()
    {
        Mine::Rng rng = {{0.4f, 12.5f, 16.234f}};
    
        for(auto val: rng)
        {
            std::cout << val << " ";
        }
    }
    

En bucle

Un bucle for ejecuta instrucciones en el loop body del loop body , mientras que la condition del bucle es verdadera. Antes de que la initialization statement bucle se ejecute exactamente una vez. Después de cada ciclo, se ejecuta la parte de iteration execution .

Un bucle for se define de la siguiente manera:

for (/*initialization statement*/; /*condition*/; /*iteration execution*/)
{
    // body of the loop
}

Explicación de las declaraciones del marcador de posición:

  • initialization statement : esta sentencia se ejecuta solo una vez, al comienzo del bucle for . Puede ingresar una declaración de múltiples variables de un tipo, como int i = 0, a = 2, b = 3 . Estas variables solo son válidas en el ámbito del bucle. Las variables definidas antes del bucle con el mismo nombre se ocultan durante la ejecución del bucle.
  • condition : esta declaración se evalúa antes de cada ejecución del cuerpo del bucle y cancela el bucle si se evalúa como false .
  • iteration execution : esta instrucción se ejecuta después del cuerpo del bucle, antes de la siguiente evaluación de la condición , a menos que el bucle for sea ​​abortado en el cuerpo (por break , goto , return o una excepción lanzada). Puede ingresar varias declaraciones en la parte de iteration execution , como a++, b+=10, c=b+a .

El equivalente aproximado de un for de bucle, reescrito como un while bucle es:

/*initialization*/
while (/*condition*/)
{
    // body of the loop; using 'continue' will skip to increment part below
    /*iteration execution*/
}

El caso más común para usar un bucle for es ejecutar sentencias un número específico de veces. Por ejemplo, considere lo siguiente:

for(int i = 0; i < 10; i++) {
    std::cout << i << std::endl;
}

Un bucle válido también es:

for(int a = 0, b = 10, c = 20; (a+b+c < 100); c--, b++, a+=c) {
    std::cout << a << " " << b << " " << c << std::endl; 
}

Un ejemplo de ocultar variables declaradas antes de un bucle es:

int i = 99; //i = 99
for(int i = 0; i < 10; i++) { //we declare a new variable i
    //some operations, the value of i ranges from 0 to 9 during loop execution
}
//after the loop is executed, we can access i with value of 99

Pero si desea utilizar la variable ya declarada y no ocultarla, omita la parte de declaración:

int i = 99; //i = 99
for(i = 0; i < 10; i++) { //we are using already declared variable i
    //some operations, the value of i ranges from 0 to 9 during loop execution
}
//after the loop is executed, we can access i with value of 10

Notas:

  • Las instrucciones de inicialización e incremento pueden realizar operaciones no relacionadas con la declaración de condición, o nada en absoluto, si así lo desea. Pero por razones de legibilidad, es una buena práctica realizar solo operaciones directamente relevantes para el bucle.
  • Una variable declarada en la declaración de inicialización es visible solo dentro del alcance del bucle for y se libera al finalizar el bucle.
  • No olvide que la variable que se declaró en la initialization statement se puede modificar durante el bucle, así como la variable verificada en la condition .

Ejemplo de un bucle que cuenta de 0 a 10:

for (int counter = 0; counter <= 10; ++counter)
{
    std::cout << counter << '\n';
}
// counter is not accessible here (had value 11 at the end)

Explicación de los fragmentos de código:

  • int counter = 0 inicializa la variable counter a 0. (Esta variable solo se puede usar dentro del bucle for ).
  • counter <= 10 es una condición booleana que verifica si el counter es menor o igual a 10. Si es true , el bucle se ejecuta. Si es false , el bucle termina.
  • ++counter es una operación de incremento que incrementa el valor de counter en 1 antes de la siguiente verificación de condición.

Al dejar todas las declaraciones en blanco, puede crear un bucle infinito:

// infinite loop
for (;;)
    std::cout << "Never ending!\n";

El while de bucle equivalente de la anterior es:

// infinite loop
while (true)
    std::cout << "Never ending!\n";

Sin embargo, aún se puede dejar un bucle infinito utilizando las instrucciones break , goto o return o lanzando una excepción.

El siguiente ejemplo común de iteración sobre todos los elementos de una colección STL (por ejemplo, un vector ) sin usar el encabezado <algorithm> es:

std::vector<std::string> names = {"Albert Einstein", "Stephen Hawking", "Michael Ellis"};
for(std::vector<std::string>::iterator it = names.begin(); it != names.end(); ++it) {
    std::cout << *it << std::endl;
}

Mientras bucle

Un while de bucle ejecuta las instrucciones varias veces hasta que la condición dada se evalúa como false . Esta declaración de control se usa cuando no se sabe, de antemano, cuántas veces se debe ejecutar un bloque de código.

Por ejemplo, para imprimir todos los números del 0 al 9, se puede usar el siguiente código:

int i = 0;
while (i < 10)
{
    std::cout << i << " ";
    ++i; // Increment counter
}
std::cout << std::endl; // End of line; "0 1 2 3 4 5 6 7 8 9" is printed to the console
C ++ 17

Tenga en cuenta que desde C ++ 17, las 2 primeras declaraciones pueden combinarse

while (int i = 0; i < 10)
//... The rest is the same

Para crear un bucle infinito, se puede usar la siguiente construcción:

while (true)
{
    // Do something forever (however, you can exit the loop by calling 'break'
}

Hay otra variante de while bucles, a saber, do...while construct. Vea el ejemplo del bucle do-while para más información.

Declaración de variables en condiciones.

En la condición de la for y while bucles, también se permitió declarar un objeto. Se considerará que este objeto está en el alcance hasta el final del bucle, y persistirá en cada iteración del bucle:

for (int i = 0; i < 5; ++i) {
    do_something(i);
}
// i is no longer in scope.

for (auto& a : some_container) {
    a.do_something();
}
// a is no longer in scope.

while(std::shared_ptr<Object> p = get_object()) {
   p->do_something();
}
// p is no longer in scope.

Sin embargo, no está permitido hacer lo mismo con un bucle do...while while; en su lugar, declare la variable antes del bucle, y (opcionalmente) encierre tanto la variable como el bucle dentro de un ámbito local si desea que la variable salga del alcance después de que finalice el bucle:

//This doesn't compile
do {
    s = do_something();
} while (short s > 0);

// Good
short s;
do {
    s = do_something();
} while (s > 0);

Esto se debe a que la parte de instrucción de un bucle do...while while (el cuerpo del bucle) se evalúa antes de llegar a la parte de expresión ( while ), y por lo tanto, cualquier declaración en la expresión no será visible durante la primera iteración de lazo.

Bucle Do-while

Un bucle do-while es muy similar a un bucle while, excepto que la condición se comprueba al final de cada ciclo, no al principio. Por lo tanto, se garantiza que el bucle se ejecuta al menos una vez.

El siguiente código imprimirá 0 , ya que la condición se evaluará como false al final de la primera iteración:

int i =0;
do
{
    std::cout << i;
    ++i; // Increment counter
}
while (i < 0);
std::cout << std::endl; // End of line; 0 is printed to the console

Nota: No olvide el punto y coma al final de while(condition); , que se necesita en la construcción do-while .

En contraste con el bucle do- while, lo siguiente no imprimirá nada, porque la condición se evalúa como false al comienzo de la primera iteración:

int i =0;
while (i < 0)
{
    std::cout << i;
    ++i; // Increment counter
}    
std::cout << std::endl; // End of line; nothing is printed to the console

Nota: Un bucle while se puede salir sin la condición de convertirse en falsa utilizando una break , goto , o return comunicado.

int i = 0;
do
{
    std::cout << i;
    ++i; // Increment counter
    if (i > 5) 
    {
        break;
    }
}
while (true);
std::cout << std::endl; // End of line; 0 1 2 3 4 5 is printed to the console

Un bucle do-while trivial también es ocasionalmente usado para escribir macros que requieren su propio ámbito (en cuyo caso el punto y coma final se omite de la definición de la macro y requiere ser proporcionado por el usuario):

#define BAD_MACRO(x) f1(x); f2(x); f3(x);

// Only the call to f1 is protected by the condition here
if (cond) BAD_MACRO(var);

#define GOOD_MACRO(x) do { f1(x); f2(x); f3(x); } while(0)

// All calls are protected here
if (cond) GOOD_MACRO(var);

Declaraciones de control de bucle: romper y continuar

Las instrucciones de control de bucle se utilizan para cambiar el flujo de ejecución desde su secuencia normal. Cuando la ejecución deja un ámbito, se destruyen todos los objetos automáticos que se crearon en ese ámbito. El break y continue son instrucciones de control de bucle.

La break comunicado termina un bucle sin ninguna consideración adicional.

for (int i = 0; i < 10; i++)
{
    if (i == 4)
        break; // this will immediately exit our loop
    std::cout << i << '\n';
}

El código de arriba se imprimirá:

1
2
3

La declaración de continue no sale inmediatamente del bucle, sino que se salta el resto del cuerpo del bucle y va a la parte superior del bucle (incluida la verificación de la condición).

for (int i = 0; i < 6; i++)
{
    if (i % 2 == 0) // evaluates to true if i is even
        continue; // this will immediately go back to the start of the loop
    /* the next line will only be reached if the above "continue" statement 
       does not execute  */
    std::cout << i << " is an odd number\n";
}

El código de arriba se imprimirá:

1 is an odd number
3 is an odd number
5 is an odd number

Debido a que tales cambios en el flujo de control a veces son difíciles de entender para los humanos, los break y los continue se usan con moderación. Una implementación más sencilla suele ser más fácil de leer y entender. Por ejemplo, el primer bucle for con la break anterior podría reescribirse como:

for (int i = 0; i < 4; i++)
{
    std::cout << i << '\n';
}

El segundo ejemplo con continue podría reescribirse como:

for (int i = 0; i < 6; i++)
{
    if (i % 2 != 0) {
        std::cout << i << " is an odd number\n";
    }
}

Rango-para sobre un sub-rango

Usando bucles de rango de base, puede recorrer una subparte de un contenedor u otro rango dado generando un objeto proxy que califica para bucles basados ​​en rango.

template<class Iterator, class Sentinel=Iterator>
struct range_t {
  Iterator b;
  Sentinel e;
  Iterator begin() const { return b; }
  Sentinel end() const { return e; }
  bool empty() const { return begin()==end(); }
  range_t without_front( std::size_t count=1 ) const {
    if (std::is_same< std::random_access_iterator_tag, typename std::iterator_traits<Iterator>::iterator_category >{} ) {
      count = (std::min)(std::size_t(std::distance(b,e)), count);
    }
    return {std::next(b, count), e};
  }
  range_t without_back( std::size_t count=1 ) const {
    if (std::is_same< std::random_access_iterator_tag, typename std::iterator_traits<Iterator>::iterator_category >{} ) {
      count = (std::min)(std::size_t(std::distance(b,e)), count);
    }
    return {b, std::prev(e, count)};
  }
};

template<class Iterator, class Sentinel>
range_t<Iterator, Sentinel> range( Iterator b, Sentinal e ) {
  return {b,e};
}
template<class Iterable>
auto range( Iterable& r ) {
  using std::begin; using std::end;
  return range(begin(r),end(r));
}

template<class C>
auto except_first( C& c ) {
  auto r = range(c);
  if (r.empty()) return r;
  return r.without_front();
}

Ahora podemos hacer:

std::vector<int> v = {1,2,3,4};

for (auto i : except_first(v))
  std::cout << i << '\n';

e imprimir

2
3
4

Tenga en cuenta que los objetos intermedios generados en la parte for(:range_expression) del bucle for habrán caducado antes for comience el bucle for .



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow