Поиск…
Вступление
Оператор цикла выполняет группу операторов несколько раз, пока не будет выполнено условие. В C ++ существует три типа примитивных циклов: for, while и do ... while.
Синтаксис
- в то время как (условие) оператор;
- do statement while ( выражение );
- for ( for-init-statement ; condition ; expression ) statement ;
- for ( for-range-declaration : for-range-initializer ) оператор ;
- перерыв ;
- Продолжить ;
замечания
algorithm
вызова обычно предпочтительнее для ручных петель.
Если вы хотите что-то, что уже делает алгоритм (или что-то очень похожее), вызов алгоритма более ясный, часто более эффективный и менее подверженный ошибкам.
Если вам нужен цикл, который делает что-то довольно простое (но для использования алгоритма требуется путаница путаницы связующих и адаптеров), просто напишите цикл.
Диапазон для
for
циклов можно использовать для итерации по элементам диапазона, основанного на итераторе, без использования числового индекса или прямого доступа к итераторам:
vector<float> v = {0.4f, 12.5f, 16.234f};
for(auto val: v)
{
std::cout << val << " ";
}
std::cout << std::endl;
Это будет выполнять итерацию по каждому элементу из v
, причем val
получает значение текущего элемента. Следующее утверждение:
for (for-range-declaration : for-range-initializer ) statement
эквивалентно:
{
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
}
}
Это изменение было введено для запланированной поддержки Ranges TS в C ++ 20.
В этом случае наш цикл эквивалентен:
{
auto&& __range = v;
auto __begin = v.begin(), __end = v.end();
for (; __begin != __end; ++__begin) {
auto val = *__begin;
std::cout << val << " ";
}
}
Обратите внимание, что auto val
объявляет тип значения, который будет копией значения, хранящегося в диапазоне (мы инициализируем его инициализацию с помощью итератора по мере продвижения). Если значения, хранящиеся в диапазоне, дороги для копирования, вы можете использовать const auto &val
. Вы также не обязаны использовать auto
; вы можете использовать соответствующее имя типа, если оно неявно конвертируется из типа значения диапазона.
Если вам нужен доступ к итератору, диапазон на основе не может помочь вам (не без каких-либо усилий, по крайней мере).
Если вы хотите ссылаться на него, вы можете сделать это:
vector<float> v = {0.4f, 12.5f, 16.234f};
for(float &val: v)
{
std::cout << val << " ";
}
Вы можете итерации по ссылке const
если у вас есть контейнер const
:
const vector<float> v = {0.4f, 12.5f, 16.234f};
for(const float &val: v)
{
std::cout << val << " ";
}
Можно использовать пересылку ссылок, когда итератор последовательности возвращает объект-прокси, и вам нужно работать с этим объектом const
способом. Примечание: это, скорее всего, путает читателей вашего кода.
vector<bool> v(10);
for(auto&& val: v)
{
val = true;
}
Типа «Диапазон» предоставляется на основе диапазона for
может быть одним из следующих:
Языковые массивы:
float arr[] = {0.4f, 12.5f, 16.234f}; for(auto val: arr) { std::cout << val << " "; }
Обратите внимание, что выделение динамического массива не учитывается:
float *arr = new float[3]{0.4f, 12.5f, 16.234f}; for(auto val: arr) //Compile error. { std::cout << val << " "; }
Любой тип, который имеет функции-члены
begin()
иend()
, возвращает итераторы к элементам типа. Стандартные библиотечные контейнеры подходят, но могут использоваться и пользовательские типы: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 << " "; } }
Любой тип, который имеет функции non-member
begin(type)
иend(type)
которые могут быть найдены через зависимый от аргументов поиск поtype
. Это полезно для создания типа диапазона без необходимости изменять тип класса: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 << " "; } }
Для цикла
Цикл for
выполняет инструкции в loop body
, а condition
цикла - true. Прежде, чем initialization statement
цикла выполняется ровно один раз. После каждого цикла выполняется исполняющая часть iteration execution
.
Цикл for
определяется следующим образом:
for (/*initialization statement*/; /*condition*/; /*iteration execution*/)
{
// body of the loop
}
Объяснение заявлений заполнителя:
-
initialization statement
: этот оператор запускается только один раз, в начале циклаfor
. Вы можете ввести объявление нескольких переменных одного типа, напримерint i = 0, a = 2, b = 3
. Эти переменные действительны только в области цикла. Переменные, определенные перед циклом с тем же именем, скрываются во время выполнения цикла. -
condition
: Этот оператор оценивается перед выполнением каждого цикла цикла и прерывает цикл, если он оценивает значениеfalse
. -
iteration execution
: этот оператор запускается после тела цикла, перед оценкой следующего условия , если циклfor
не прерван в теле (поbreak
,goto
,return
или исключаемому исключению). Вы можете ввести несколько операторов в частиiteration execution
, например,a++, b+=10, c=b+a
.
Грубый эквивалент for
цикла, переписан как в while
петли:
/*initialization*/
while (/*condition*/)
{
// body of the loop; using 'continue' will skip to increment part below
/*iteration execution*/
}
Наиболее распространенным случаем использования цикла for
является выполнение операторов определенное количество раз. Например, рассмотрим следующее:
for(int i = 0; i < 10; i++) {
std::cout << i << std::endl;
}
Правильный цикл также:
for(int a = 0, b = 10, c = 20; (a+b+c < 100); c--, b++, a+=c) {
std::cout << a << " " << b << " " << c << std::endl;
}
Пример скрытия объявленных переменных перед циклом:
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
Но если вы хотите использовать уже объявленную переменную и не скрывать ее, то опустите часть объявления:
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
Заметки:
- Операторы инициализации и приращения могут выполнять операции, не связанные с оператором условия, или вообще ничего - если вы этого хотите. Но по соображениям удобочитаемости лучше всего выполнять операции, непосредственно относящиеся к циклу.
- Переменная, объявленная в инструкции инициализации, видна только внутри области цикла
for
и освобождается после завершения цикла. - Не забывайте, что переменная, которая была объявлена в
initialization statement
может быть изменена во время цикла, а также переменная, отмеченная вcondition
.
Пример цикла, который рассчитывается от 0 до 10:
for (int counter = 0; counter <= 10; ++counter)
{
std::cout << counter << '\n';
}
// counter is not accessible here (had value 11 at the end)
Объяснение фрагментов кода:
-
int counter = 0
инициализируетcounter
переменных равным 0. (Эта переменная может использоваться только внутри циклаfor
.) -
counter <= 10
- это логическое условие, которое проверяет, равен лиcounter
меньше или равен 10. Если этоtrue
, цикл выполняется. Если онfalse
, цикл завершается. -
++counter
- это операция приращения, которая увеличивает значениеcounter
на 1 перед следующей проверкой состояния.
Если оставить все утверждения пустыми, вы можете создать бесконечный цикл:
// infinite loop
for (;;)
std::cout << "Never ending!\n";
В while
петля эквивалент выше:
// infinite loop
while (true)
std::cout << "Never ending!\n";
Тем не менее, бесконечный цикл все равно может быть оставлен при использовании операторов break
, goto
или return
или путем исключения исключения.
Следующий общий пример итерации по всем элементам из коллекции STL (например, vector
) без использования заголовка <algorithm>
:
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;
}
Пока цикл
В while
цикл выполняет операторы , пока данное условие не станет false
. Этот оператор управления используется, когда заранее неизвестно, сколько раз должен выполняться блок кода.
Например, чтобы напечатать все числа от 0 до 9, можно использовать следующий код:
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, первые 2 оператора могут быть объединены
while (int i = 0; i < 10)
//... The rest is the same
Чтобы создать бесконечный цикл, можно использовать следующую конструкцию:
while (true)
{
// Do something forever (however, you can exit the loop by calling 'break'
}
Существует еще один вариант циклов while
, а именно do...while
construct. Дополнительную информацию см. В примере цикла while-while .
Объявление переменных в условиях
В состоянии циклов for
и while
также разрешено объявлять объект. Этот объект будет рассматриваться как область действия до конца цикла и будет сохраняться через каждую итерацию цикла:
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.
Однако не разрешается делать то же самое с do...while
loop; вместо этого объявляйте переменную перед циклом и (необязательно) вставляйте как переменную, так и цикл в локальную область, если вы хотите, чтобы переменная выходила из области действия после завершения цикла:
//This doesn't compile
do {
s = do_something();
} while (short s > 0);
// Good
short s;
do {
s = do_something();
} while (s > 0);
Это связано с тем, что часть оператора цикла do...while
while (тело цикла) оценивается до того, как часть выражения ( while
) достигнута, и, таким образом, любое объявление в выражении не будет видимым во время первой итерации петля.
Цикл Do-while
Цикл Do-то время очень похоже на время цикла, за исключением того, что условие проверяется в конце каждого цикла, а не в начале. Поэтому цикл гарантированно выполняется хотя бы один раз.
Следующий код будет печатать 0
, поскольку условие будет оцениваться как false
в конце первой итерации:
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
Примечание: не забывайте точку с запятой в конце while(condition);
, который необходим в конструкции do-while .
В отличие от цикла do- while, следующее не будет печатать ничего, потому что условие принимает значение false
в начале первой итерации:
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
Примечание: А в то время как цикл может быть прерван без условия становится ложным, используя break
, goto
, или return
заявление.
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
Тривиальный цикл do- while также иногда используется для записи макросов, для которых требуется их собственная область (в этом случае конечная точка с запятой опускается из определения макроса и должна быть предоставлена пользователем):
#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);
Записи цикла Loop: Break and Continue
Операторы управления циклом используются для изменения потока выполнения из его обычной последовательности. Когда выполнение оставляет область, все автоматические объекты, созданные в этой области, уничтожаются. break
и continue
- это операторы управления контуром.
Инструкция break
прекращает цикл без какого-либо дальнейшего рассмотрения.
for (int i = 0; i < 10; i++)
{
if (i == 4)
break; // this will immediately exit our loop
std::cout << i << '\n';
}
Вышеприведенный код будет распечатан:
1
2
3
Оператор continue
не сразу выходит из цикла, а пропускает остальную часть тела цикла и переходит в начало цикла (включая проверку состояния).
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";
}
Вышеприведенный код будет распечатан:
1 is an odd number
3 is an odd number
5 is an odd number
Поскольку такие изменения потока управления иногда трудны для людей, которые легко понимают, break
и continue
использоваться экономно. Более простую реализацию, как правило, легче читать и понимать. Например, первый цикл for
с break
выше может быть переписан как:
for (int i = 0; i < 4; i++)
{
std::cout << i << '\n';
}
Второй пример с continue
может быть переписан как:
for (int i = 0; i < 6; i++)
{
if (i % 2 != 0) {
std::cout << i << " is an odd number\n";
}
}
Диапазон - для суб-диапазона
Используя циклы базы значений, вы можете перебрать часть под конкретного контейнера или другого диапазона, создав прокси-объект, который подходит для циклов, основанных на диапазонах.
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();
}
теперь мы можем сделать:
std::vector<int> v = {1,2,3,4};
for (auto i : except_first(v))
std::cout << i << '\n';
и распечатать
2
3
4
Имейте в виду, что промежуточные объекты, сгенерированные в части for(:range_expression)
цикла for
будут истекли к моменту начала цикла for
.