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
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
}
}
{
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()
yend()
, 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)
yend(type)
que no sean miembros que se pueden encontrar mediante búsqueda dependiente de argumento, según eltype
. 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 buclefor
. Puede ingresar una declaración de múltiples variables de un tipo, comoint 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 comofalse
. -
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 buclefor
sea abortado en el cuerpo (porbreak
,goto
,return
o una excepción lanzada). Puede ingresar varias declaraciones en la parte deiteration execution
, comoa++, 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 lacondition
.
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 variablecounter
a 0. (Esta variable solo se puede usar dentro del buclefor
). -
counter <= 10
es una condición booleana que verifica si elcounter
es menor o igual a 10. Si estrue
, el bucle se ejecuta. Si esfalse
, el bucle termina. -
++counter
es una operación de incremento que incrementa el valor decounter
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
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
.