Как правильно перебирать вектор в C++?
Рассмотрим эти два фрагмента кода, этот отлично работает:
for (unsigned i=0; i < polygon.size(); i++) {
sum += polygon[i];
}
и этот:
for (int i=0; i < polygon.size(); i++) {
sum += polygon[i];
}
который генерирует warning: comparison between signed and unsigned integer expressions.
Я новичок в мире C++, поэтому переменная unsigned выглядит для меня немного пугающей, и я знаю, что переменные unsigned могут быть опасны, если используются неправильно, так что - это правильно?
Чтобы устранить предупреждение о подписанном / неподписанном, просто замените int на uint (unsigned int) в декларации i.
@AdamBruss .size() не относится к типу unsigned, также известному как unsigned int. Это типа std::size_t.
@underscore_d size_t - это псевдоним для unsigned.
@AdamBruss Нет. std::size_t - это typedef, определенный _ реализацией. См. Стандарт. std::size_t может быть эквивалентом unsigned в вашей текущей реализации, но это не имеет значения. Представление, что это так, может привести к непереносимому коду и неопределенному поведению.
@underscore_d В какой версии C++ беззнаковый не эквивалентен size_t?
@underscore_d Я был неправ, говоря, что unsigned эквивалентно size_t. size_t составляет 8 байтов при 64-битной сборке, как вы указали. Это верно и для Microsoft Visual C++. Но если size_t действительно различался в двух компиляторах, как вы предполагаете, у вас был бы непереносимый код, просто используя size_t.
@AdamBruss ... туше! Избитый, используя мою собственную логику, из-за чего моя тирада в моем последнем комментарии выглядела лицемерной :-) Да, поведение, определяемое реализацией, кажется гарантированным с такими определениями типов, независимо от того, используете ли вы их или игнорируете. Я бы сказал, что, возможно, лучше использовать их там, где это возможно, даже если выгода чисто теоретическая - но сравните это с другими потоками, где люди утверждают, что std::size_t в значительной степени бессмысленен, и все обычные циклы должны выполняться с int! Думаю, это не совсем практические темы, так как мало кто когда-либо будет использовать такие большие массивы.
@underscore_d Я полагаю, что size_t - необходимое зло, позволяющее, например, разрешить одну функцию size () вместо той, которая возвращает 4 байта без знака, а другая возвращает 8 байтов без знака. Я согласен насчет больших контейнеров.
@underscore_d Это не типа size_t, а типа decltype(polygon)::size_type.





Используйте size_t:
for (size_t i=0; i < polygon.size(); i++)
Цитата Википедия:
The stdlib.h and stddef.h header files define a datatype called
size_twhich is used to represent the size of an object. Library functions that take sizes expect them to be of typesize_t, and the sizeof operator evaluates tosize_t.The actual type of
size_tis platform-dependent; a common mistake is to assumesize_tis the same as unsigned int, which can lead to programming errors, particularly as 64-bit architectures become more prevalent.
size_t Хорошо для вектора, так как он должен хранить все объекты в массиве (сам объект тоже), но std :: list может содержать больше, чем size_t элементов!
size_t обычно достаточно для перечисления всех байтов в адресном пространстве процесса. Хотя я понимаю, что это может быть не так в некоторых экзотических архитектурах, я бы предпочел не беспокоиться об этом.
AFAIK рекомендуется #include <cstddef>, а не <stddef.h> или, что еще хуже, [c]stdlib целиком, и использовать std::size_t, а не неквалифицированную версию - и то же самое для любой другой ситуации, когда у вас есть выбор между <cheader> и <header.h>.
Вызов vector<T>::size() возвращает значение типа std::vector<T>::size_type, а не int, unsigned int или иное.
Также обычно итерация по контейнеру в C++ выполняется с использованием итераторы, как это.
std::vector<T>::iterator i = polygon.begin();
std::vector<T>::iterator end = polygon.end();
for(; i != end; i++){
sum += *i;
}
Где T - это тип данных, которые вы храните в векторе.
Или с использованием различных итерационных алгоритмов (std::transform, std::copy, std::fill, std::for_each и так далее).
Итераторы, как правило, являются хорошей идеей, хотя я сомневаюсь, что есть необходимость хранить «конец» в отдельной переменной, и все это можно сделать внутри оператора for (;;).
Я знаю, что begin () и end () - это амортизируемое постоянное время, но я обычно считаю, что это более читабельно, чем втискивать все в одну строку.
Вы можете разделить for на отдельные строки, чтобы улучшить читаемость. Объявление итераторов вне цикла означает, что вам нужно другое имя итератора для каждого цикла по контейнерам разных типов.
Я знаю обо всех различиях, и в основном это сводится к личным предпочтениям; Обычно так я и поступаю.
обычно рекомендуется сохранять результат end () в переменной, чтобы избежать повторного вызова функции. даже если это постоянное время, все равно будут накладные расходы на выполнение вызова. нет причин для инициализации вне цикла, и единственный результат этого - загромождение области.
@ user44511: так что вы предлагаете?
@pihentagy Я думаю, это было бы установить его в первом разделе цикла for. например. for (auto i = polygon.begin (), end = polygon.end (); i! = end; i ++)
любая операция, которая делает недействительными итераторы, также потенциально делает недействительным end ()
for (vector<int>::iterator it = polygon.begin(); it != polygon.end(); it++)
sum += *it;
Для вектора это нормально, но в целом лучше использовать ++ it, а не it ++, в случае, если сам итератор нетривиален.
Лично я привык использовать ++ i, но думаю, что большинство людей предпочитают стиль i ++ (фрагмент кода VS по умолчанию для "for" - это i ++). Просто мысль
@MehrdadAfshari Кого волнует, что делают «большинство людей»? «большинство людей» ошибаются во многих вещах. Пост-инкремент / декремент, когда значение pre никогда не используется, неверно и неэффективно, по крайней мере, теоретически - независимо от того, как часто оно вслепую используется во всех примерах кода sub-par. Вы не должны поощрять плохие практики только для того, чтобы сделать вещи более знакомыми для людей, которые еще не знают лучшего.
Немного истории:
Чтобы указать, является ли число отрицательным или нет, компьютер использует «знаковый» бит. int - это тип данных со знаком, что означает, что он может содержать положительные и отрицательные значения (от -2 до 2 миллиардов). Unsigned может хранить только положительные числа (и, поскольку он не тратит немного времени на метаданные, он может хранить больше: от 0 до примерно 4 миллиардов).
std::vector::size() возвращает unsigned, почему вектор может иметь отрицательную длину?
Предупреждение говорит вам, что правый операнд вашего оператора неравенства может содержать больше данных, чем левый.
По сути, если у вас есть вектор с более чем 2 миллиардами записей и вы используете целое число для индексации, вы столкнетесь с проблемами переполнения (int вернется к отрицательным 2 миллиардам).
Первый - это правильный тип и правильный в некотором строгом смысле слова. (Если задуматься, размер никогда не может быть меньше нуля.) Однако это предупреждение кажется мне одним из хороших кандидатов на игнорирование.
Я считаю, что это ужасный кандидат для игнорирования - его легко исправить, и время от времени возникают настоящие ошибки из-за ошибок при неправильном сравнении подписанных / неподписанных значений. Например, в этом случае, если размер больше INT_MAX, цикл никогда не завершается.
... или, может быть, он немедленно прекращается. Один из двух. Зависит от того, преобразовано ли значение со знаком в беззнаковое для сравнения или беззнаковое преобразовано в знаковое. Однако на 64-битной платформе с 32-битным int, как и в win64, int будет повышен до size_t, и цикл никогда не закончится.
@SteveJessop: Нельзя с уверенностью сказать, что цикл никогда не закончится. На итерации, когда i == INT_MAX, i++ вызывает неопределенное поведение. Здесь может случиться что угодно.
@BenVoigt: правда, и все же не дает оснований игнорировать предупреждение :-)
Для итерации в обратном направлении см. этот ответ.
Итерация вперед практически идентична. Просто измените итераторы / декремент свопа на инкремент. Вам следует предпочесть итераторы. Некоторые люди советуют использовать std::size_t в качестве типа индексной переменной. Однако это непереносимо. Всегда используйте size_type typedef контейнера (хотя вы можете обойтись только преобразованием в случае прямой итерации, на самом деле она может пойти не так, как в случае обратной итерации при использовании std::size_t, в случае, если std::size_t шире, чем то, что typedef size_type):
for(std::vector<T>::iterator it = v.begin(); it != v.end(); ++it) {
/* std::cout << *it; ... */
}
Важно всегда использовать форму приращения префикса для итераторов, определения которых вам неизвестны. Это гарантирует, что ваш код будет работать как можно более универсальным.
for(auto const& value: a) {
/* std::cout << value; ... */
for(std::vector<int>::size_type i = 0; i != v.size(); i++) {
/* std::cout << v[i]; ... */
}
for(element_type* it = a; it != (a + (sizeof a / sizeof *a)); it++) {
/* std::cout << *it; ... */
}
for(auto const& value: a) {
/* std::cout << value; ... */
for(std::size_t i = 0; i != (sizeof a / sizeof *a); i++) {
/* std::cout << a[i]; ... */
}
Прочтите в ответе на обратную итерацию, какую проблему может решить подход sizeof.
тип указателей размера: использование Difference_type может быть более переносимым. попробуйте iterator_traits <element_type *> :: difference_type. это полнейшая декларация, но она более портативна ...
Вильгельмтелл, для чего мне использовать разницу_тип? sizeof должен возвращать size_t :) я вас не понимаю. если бы мне пришлось вычитать указатели друг из друга, «разница_тип» была бы правильным выбором.
итерация по массивам с использованием техники, которую вы упомянули в этом посте, не будет работать, если итерация выполняется в функции в массиве, переданном этой функции. Потому что массив sizeof вернет только указатель sizeof.
Согласно этому руководству [1] использование счетчиков циклов без подписи - плохая идея. Поэтому, вероятно, следует сначала указать размер в подписанном int. [1] developer.download.nvidia.com/compute/cuda/3_1/toolkit/docs/…
@Nils Я согласен с тем, что использование счетчиков циклов без знака - плохая идея. но поскольку стандартная библиотека использует беззнаковые целочисленные типы для индекса и размера, я предпочитаю беззнаковые индексные типы для стандартной библиотеки. следовательно, другие библиотеки используют только подписанные типы, такие как Qt lib.
Обновление для C++ 11: цикл for на основе диапазона. for (auto p : polygon){sum += p;}
@ C.R. также обновление для C++ 11: используйте std::begin() и std::end(), чтобы сделать версию массива и векторной версии равной.
@StackedCrooked Около года назад я опубликовал ответ C++ 11, см. Ниже: stackoverflow.com/questions/409348/iteration-over-vector-in- c /…
если вам нужно использовать индекс цикла и вам лень набирать полный size_type контейнера, рассмотрите еще одну функцию C++ 11 decltype, как в for (decltype(vec.size()) i=0; i<vec.size(); ++i)
@ JohannesSchaub-litb Пример "Использование std :: vector / Использование диапазона C++ 11" мне кажется неправильным. Откуда взялось требование "const &"? Разве пример не должен быть: for(auto i : v) { /* std::cout << i; ... */ }?
Обычно я использую BOOST_FOREACH:
#include <boost/foreach.hpp>
BOOST_FOREACH( vector_type::value_type& value, v ) {
// do something with 'value'
}
Он работает с контейнерами STL, массивами, строками в стиле C и т. д.
Хороший ответ на другой вопрос (как мне перебрать вектор?), Но совершенно не то, что спрашивал OP (в чем смысл предупреждения о беззнаковой переменной?)
Ну, он спросил, как правильно перебирать вектор. Так что кажется достаточно актуальным. Предупреждение объясняет, почему он недоволен своим текущим решением.
В конкретном случае в вашем примере я бы использовал алгоритмы STL для этого.
#include <numeric>
sum = std::accumulate( polygon.begin(), polygon.end(), 0 );
Для более общего, но все же довольно простого случая я бы выбрал:
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>
using namespace boost::lambda;
std::for_each( polygon.begin(), polygon.end(), sum += _1 );
Что касается ответа Йоханнеса Шауба:
for(std::vector<T*>::iterator it = v.begin(); it != v.end(); ++it) {
...
}
Это может работать с некоторыми компиляторами, но не с gcc. Проблема здесь в том, является ли std :: vector :: iterator типом, переменной (членом) или функцией (методом). Мы получаем следующую ошибку с gcc:
In member function ‘void MyClass<T>::myMethod()’:
error: expected `;' before ‘it’
error: ‘it’ was not declared in this scope
In member function ‘void MyClass<T>::sort() [with T = MyClass]’:
instantiated from ‘void MyClass<T>::run() [with T = MyClass]’
instantiated from here
dependent-name ‘std::vector<T*,std::allocator<T*> >::iterator’ is parsed as a non-type, but instantiation yields a type
note: say ‘typename std::vector<T*,std::allocator<T*> >::iterator’ if a type is meant
Решение использует ключевое слово typename, как сказано:
typename std::vector<T*>::iterator it = v.begin();
for( ; it != v.end(); ++it) {
...
Следует уточнить, что это применимо только тогда, когда T является аргументом шаблона, и, следовательно, выражение std::vector<T*>::iterator является зависимым именем. Чтобы зависимое имя было проанализировано как тип, перед ним должно стоять ключевое слово typename, как показывает диагностика.
Прошло четыре года, и Google дал мне такой ответ. С стандартный C++ 11 (он же C++ 0x) есть новый приятный способ сделать это (ценой нарушения обратной совместимости): новое ключевое слово auto. Это избавляет вас от необходимости явно указывать тип используемого итератора (повторяя векторный тип снова), когда очевидно (для компилятора), какой тип использовать. Если v является вашим vector, вы можете сделать что-то вроде этого:
for ( auto i = v.begin(); i != v.end(); i++ ) {
std::cout << *i << std::endl;
}
C++ 11 идет еще дальше и дает вам специальный синтаксис для итерации по коллекциям, таким как векторы. Это устраняет необходимость писать всегда одно и то же:
for ( auto &i : v ) {
std::cout << i << std::endl;
}
Чтобы увидеть это в работающей программе, соберите файл auto.cpp:
#include <vector>
#include <iostream>
int main(void) {
std::vector<int> v = std::vector<int>();
v.push_back(17);
v.push_back(12);
v.push_back(23);
v.push_back(42);
for ( auto &i : v ) {
std::cout << i << std::endl;
}
return 0;
}
На момент написания этого, когда вы компилируете это с помощью g ++, вам обычно нужно настроить его для работы с новым стандартом, указав дополнительный флаг:
g++ -std=c++0x -o auto auto.cpp
Теперь вы можете запустить пример:
$ ./auto
17
12
23
42
Пожалуйста, обрати внимание, что инструкции по компиляции и запуску относятся к компилятору GNU C++ на Linux, программа должна быть независимой от платформы (и компилятора).
C++ 11 дает вам for (auto& val: vec)
@flexo Спасибо, я не знаю, как я мог это забыть. Думаю, недостаточно много C++. Не мог поверить, что есть что-то настолько практичное (на самом деле, думал, что это синтаксис JavaScript). Я изменил ответ, включив это.
Ваш ответ очень хороший. К сожалению, версия g ++ по умолчанию в различных наборах ОС не ниже 4.3, поэтому она не работает.
Вам нужно инициализировать вектор с помощью std::vector<int> v = std::vector<int>();, или вы могли бы просто использовать вместо него std::vector<int> v;?
@BillCheatham Ну, я просто попробовал это без инициализации, и он работал, так что кажется, что он работает без.
Спасибо, да, у меня это сработало, но я подумал, не повезло ли мне просто с памятью и не получил segfault. Я полагаю, у std::vector не должно быть тех же проблем, что и у массивов ...?
Мне кажется странным, что вы написали ответ, восхваляющий новые возможности C++ 11, но при этом инициализировали свой контейнер, например что, вместо использования std::initializer_list C++ 11, который существует именно для того, чтобы избежать такого шаблона. Просто сделайте это: std::vector<int> v{17, 12, 23, 42};. Это позволяет избежать ненужной конструкции по умолчанию из еще более ненужного временного и повторяющегося push_back() - и, кстати, мы должны по умолчанию использовать еще одну новую вещь C++ 11, emplace_back(), поскольку, по крайней мере, для более сложных объектов push может генерировать ненужные копирующие конструкции. .
Вам не нужен & для доступа только для чтения, и я думаю, что здесь лучше int, чем auto: я бы использовал: for (int i: v)
Я хотел бы знать, как "for (auto & val: vec)" работает внутренне, использует итератор внутри и функции begin (), end () класса vector?
Чтобы быть полным, синтаксис C++ 11 позволяет использовать только одну другую версию для итераторов (ссылка):
for(auto it=std::begin(polygon); it!=std::end(polygon); ++it) {
// do something with *it
}
Что также удобно для обратной итерации
for(auto it=std::end(polygon)-1; it!=std::begin(polygon)-1; --it) {
// do something with *it
}
Я бы использовал общие алгоритмы, такие как for_each, чтобы избежать поиска правильного типа итератора и лямбда-выражения, чтобы избежать дополнительных именованных функций / объектов.
Короткий "красивый" пример для вашего конкретного случая (при условии, что многоугольник является вектором целых чисел):
for_each(polygon.begin(), polygon.end(), [&sum](int i){ sum += i; });
проверено на: http://ideone.com/i6Ethd
Не забудьте про алгоритм включать: и, конечно же, вектор :)
У Microsoft есть хороший пример по этому поводу:
источник: http://msdn.microsoft.com/en-us/library/dd293608.aspx
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
int main()
{
// Create a vector object that contains 10 elements.
vector<int> v;
for (int i = 1; i < 10; ++i) {
v.push_back(i);
}
// Count the number of even numbers in the vector by
// using the for_each function and a lambda.
int evenCount = 0;
for_each(v.begin(), v.end(), [&evenCount] (int n) {
cout << n;
if (n % 2 == 0) {
cout << " is even " << endl;
++evenCount;
} else {
cout << " is odd " << endl;
}
});
// Print the count of even numbers to the console.
cout << "There are " << evenCount
<< " even numbers in the vector." << endl;
}
Стандартный заголовок <algorithm> предоставляет нам возможности для этого:
using std::begin; // allows argument-dependent lookup even
using std::end; // if the container type is unknown here
auto sum = std::accumulate(begin(polygon), end(polygon), 0);
Другие функции библиотеки алгоритмов выполняют общие задачи - убедитесь, что вы знаете, что доступно, если хотите сэкономить силы.
Непонятная, но важная деталь: если вы скажете «for (auto it)» следующим образом, вы получите копию объекта, а не фактический элемент:
struct Xs{int i} x;
x.i = 0;
vector <Xs> v;
v.push_back(x);
for(auto it : v)
it.i = 1; // doesn't change the element v[0]
Чтобы изменить элементы вектора, вам нужно определить итератор как ссылку:
for(auto &it : v)
Если ваш компилятор поддерживает это, вы можете использовать диапазон на основе для доступа к векторным элементам:
vector<float> vertices{ 1.0, 2.0, 3.0 };
for(float vertex: vertices){
std::cout << vertex << " ";
}
Отпечатки: 1 2 3. Обратите внимание, что вы не можете использовать эту технику для изменения элементов вектора.
Два сегмента кода работают одинаково. Однако маршрут unsigned int "правильный. Использование типов int без знака будет лучше работать с вектором в том экземпляре, который вы его использовали. Вызов функции-члена size () для вектора возвращает целочисленное значение без знака, поэтому вы хотите сравнить переменную "i" к значению собственного типа.
Кроме того, если вас все еще немного беспокоит, как "unsigned int" выглядит в вашем коде, попробуйте "uint". По сути, это сокращенная версия unsigned int, работающая точно так же. Вам также не нужно включать другие заголовки, чтобы использовать его.
Целое число без знака для size () не обязательно равно «unsigned int» в терминах C++, часто «целое число без знака» в этом случае представляет собой 64-битное целое число без знака, тогда как «unsigned int» обычно 32-битное.
Добавляя это, поскольку я не мог найти его ни в одном ответе: для итерации на основе индекса мы можем использовать decltype(vec_name.size()), который будет оценивать std::vector<T>::size_type
for(decltype(v.size()) i{ 0 }; i < v.size(); i++) {
/* std::cout << v[i]; ... */
}
Беззнаковый правильный, потому что polygon.size () имеет тип беззнаковый. Беззнаковый означает всегда положительный или 0. Вот и все, что это значит. Так что, если переменная всегда используется только для подсчета, то правильный выбор - беззнаковый.