Ітерація над std :: vector: непідписаний проти підписаної змінної індексу


470

Який правильний спосіб ітерації над вектором в 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змінні можуть бути небезпечними, якщо їх не використовувати правильно, так - чи правильно це?


10
Непідписаний правильний, тому що polygon.size () має тип без підпису. Непідписаний означає позитив завжди або 0. Це все, що це означає. Отже, якщо використання змінної завжди лише для підрахунків, то правильним вибором є беззнаковий.
Адам Брусс

3
@AdamBruss .size()не належить до типу unsignedака unsigned int. Це типу std::size_t.
підкреслюй_26

1
@underscore_d size_t - псевдонім для непідписаного.
Адам Брусс

2
@AdamBruss No. std::size_t- тип, визначений імплементацією. Див. Стандарт. std::size_tможе бути еквівалентним unsignedу вашій поточній реалізації, але це не актуально. Прикидаючи це, це може призвести до непереносного коду та не визначеної поведінки.
підкреслюй_

2
@LF ... звичайно, що, мабуть, std::size_tна практиці. Ви думаєте, що ми висвітлювали все ще в цьому бурхливому потоці коментарів за 6 років?
підкреслюй_d

Відповіді:


817

Для повторення назад дивіться цю відповідь .

Ітерація вперед майже однакова. Просто змініть ітератори / поміняйте декремент на приріст. Вам слід віддати перевагу ітераторам. Деякі люди говорять вам використовувати std::size_tяк тип змінної індексу. Однак це не є портативним. Завжди використовуйте size_typetypedef контейнера (Хоча ви можете піти лише з конверсією у випадку прямого повторення, він може насправді помилитися у випадку зворотного ітерації у випадку використання std::size_t, якщо std::size_tширший, ніж типdef size_type) :


Використання std :: vector

Використання ітераторів

for(std::vector<T>::iterator it = v.begin(); it != v.end(); ++it) {
    /* std::cout << *it; ... */
}

Важливо, завжди використовуйте форму приросту префікса для ітераторів, визначення яких ви не знаєте. Це забезпечить ваш код працює максимально універсальним.

Використання діапазону C ++ 11

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; ... */
}

Використання діапазону C ++ 11

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може підходити підхід.


розмір типу покажчиків: використання різниці_типу може бути більш портативним. спробуйте iterator_traits <element_type *> :: razlika_type. це одна заява декларації, але вона є більш портативною ...
wilhelmtell

wilhelmtell, для чого я повинен використовувати різницю_типу? sizeof визначено, щоб повернути size_t :) Я вас не розумію. якби я віднімав покажчики один від одного, правильним вибором був би різновид_тип.
Йоханнес Шауб - запалений

ітерація над масивами з використанням техніки, яку ви згадали у цій публікації, не буде працювати, якщо ітерація виконується у функції масиву, переданого цій функції. Оскільки масив sizeof поверне лише покажчик sizeof.
замовчуванням

1
@Nils я погоджуюся, що використання непідписаних лічильників циклу - це погана ідея. але оскільки стандартна бібліотека використовує непідписані цілі типи для індексу та розміру, я віддаю перевагу неподписаним типам індексу для стандартної бібліотеки. отже, інші бібліотеки використовують лише підписані типи, наприклад, Qt lib.
Йоханнес Шауб - ліб

32
Оновлення для C ++ 11: діапазон на основі циклу. for (auto p : polygon){sum += p;}
Сіюань Рен

170

Минуло чотири роки, 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 , програма повинна бути платформою (і компілятором) незалежною.


7
C ++ 11 даєfor (auto& val: vec)
Flexo

@flexo Спасибі, я не знаю, як я могла це забути. Думаю, що недостатньо C ++. Не можу повірити, що є щось таке практичне (я вважав, що це синтаксис JavaScript). Я змінив відповідь, щоб включити її.
kratenko

Ваша відповідь дуже приємна. Заборонено, що версія за замовчуванням g ++ у різних ОС для девкітів знаходиться під 4.3, а це не працює.
Ratata Tata

Чи потрібно вам ініціалізувати вектор std::vector<int> v = std::vector<int>();, або ви могли просто std::vector<int> v;замість цього використати ?
Білл Чітхем

@BillCheatham Ну - я просто спробував це без ініціалізації, і він спрацював, тому, здається, він працює і без цього.
kratenko

44

У конкретному випадку у вашому прикладі я б використовував алгоритми 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 );

38

Щодо відповіді Йоганнеса Шауба:

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) {
...

2
Ви повинні уточнити, що це застосовується лише тоді, коли Tце аргумент шаблону, і, таким чином, вираз std::vector<T*>::iteratorє залежною назвою. Для того, щоб залежне ім'я було проаналізовано як тип, його потрібно попередньо встановити за допомогою typenameключового слова, як вказує діагностика.
Відновіть Моніку

17

Виклик vector<T>::size()повертає значення типу std::vector<T>::size_type, а не int, непідписаний 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 (;;).
Саулій Жемайтаїт

1
Я знаю, що початок () і кінець () амортизуються постійним часом, але я, як правило, вважаю це більш зрозумілим, ніж збивати все в один рядок.
Джаспер Беккерс

3
Ви можете розділити на на окремі рядки для поліпшення читабельності. Оголошення ітераторів поза циклом означає, що вам потрібно інше ім’я ітератора для кожного циклу над контейнерами різних типів.
Джей Конрод

Я усвідомлюю всі відмінності, і те, що в основному зводиться до цього, - це особисті переваги; це взагалі, як я закінчую робити справи.
Джаспер Беккерс

2
@pihentagy Я думаю, це було б встановити його в першому розділі for-loop. напр. for (auto i = polygon.begin (), end = polygon.end (); i! = end; i ++)
Jasper Bekkers

11

Використання size_t:

for (size_t i=0; i < polygon.size(); i++)

Цитуючи Вікіпедію :

Файли заголовків stdlib.h та stddef.h визначають тип даних, size_tякий називається, який використовується для представлення розміру об'єкта. Функції бібліотеки, які приймають розміри, очікують, що вони будуть size_tтиповими, і розмір оператора оцінює size_t.

Фактичний тип size_tплатформи залежить; Поширена помилка полягає в тому, що вважати, що size_tце те саме, що непідписаний int, що може призвести до помилок програмування, особливо, коли 64-бітні архітектури стають більш поширеними.


size_t ОК для вектора, оскільки він повинен зберігати всі об’єкти в масиві (сам об’єкт теж), але std :: список може містити більше елементів size_t!
MSalters

1
size_t зазвичай достатній для перерахування всіх байтів у адресному просторі процесу. Хоча я бачу, як це може бути не в деяких екзотичних архітектурах, я краще не переживаю про це.

AFAIK рекомендується #include <cstddef>радше, <stddef.h>або, що ще гірше, використовувати цілісність [c]stdlibта використовувати, std::size_tа не некваліфіковану версію - і те саме для будь-якої іншої ситуації, коли у вас є вибір між <cheader>і <header.h>.
підкреслюй_26

7

Трохи історії:

Щоб представити, чи є число негативним чи ні, комп'ютер використовуйте біт знака. intце підписаний тип даних, що означає, що він може містити позитивні та негативні значення (приблизно від -2 млрд. до 2 млрд.). Unsignedможе зберігати лише позитивні цифри (а оскільки він не витрачає трохи на метадані, він може зберігати більше: 0 до приблизно 4 мільярдів).

std::vector::size()return an unsigned, бо як вектор може мати негативну довжину?

Попередження говорить про те, що правий операнд вашої заяви про нерівність може містити більше даних, ніж лівий.

По суті, якщо у вас вектор з більш ніж 2 мільярдами записів і ви використовуєте ціле число, щоб індексувати, ви зіткнетесь із проблемами переповнення (int повернеться до мінус 2 мільярдів).


6

Зазвичай я використовую BOOST_FOREACH:

#include <boost/foreach.hpp>

BOOST_FOREACH( vector_type::value_type& value, v ) {
    // do something with 'value'
}

Він працює на контейнерах STL, масивах, рядках у стилі C тощо.


2
Хороша відповідь на якесь інше запитання (як я мушу повторювати вектор?), Але зовсім не зовсім те, що запитувала ОП (у чому сенс попередження про безпідписану змінну?)
abelenky

3
Ну, він запитав, який правильний спосіб ітерації над вектором. Так здається досить актуальним. Попередження лише тому, що він не задоволений своїм теперішнім рішенням.
jalf

5

Щоб бути повноцінним, синтаксис C ++ 11 включає лише одну іншу версію для ітераторів ( ref ):

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
}

5

В С ++ 11

Я б використовував загальні алгоритми, як, 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;
}

4
for (vector<int>::iterator it = polygon.begin(); it != polygon.end(); it++)
    sum += *it; 

2
Для вектора це добре, але загалом краще використовувати ++, а не ++, на випадок, якщо сам ітератор нетривіальний.
Стів Джессоп

Особисто я звик використовувати ++ i, але думаю, що більшість людей віддають перевагу стилю i ++ (фрагмент коду VS за замовчуванням для "для" - це i ++). Просто думка
Мехрдад Афшарі

@MehrdadAfshari Кого хвилює, що роблять "більшість людей"? "більшість людей" помиляються з приводу багатьох речей. Post-inc / decrement, коли попереднє значення ніколи не використовується, є невірним та неефективним, принаймні теоретично - незалежно від того, як часто воно наосліп використовується скрізь у підроздільному прикладі коду. Не слід заохочувати погану практику, щоб зробити речі більш звичними для людей, які ще не знають краще.
підкреслюй_26

2

Перший - це тип правильний і правильний у якомусь суворому сенсі. (Якщо ви задумаєтесь, розмір ніколи не може бути меншим за нуль.) Це попередження вражає мене як одного з хороших кандидатів на те, що вони ігноруються.


2
Я думаю, що це грізний кандидат, який потрібно ігнорувати - це легко виправити, і час від часу виникають справжні помилки через помилки порівняння підписаних / непідписаних значень. Наприклад, у цьому випадку, якщо розмір більше INT_MAX, цикл ніколи не припиняється.
Стів Джессоп

... або, можливо, воно припиняється негайно. Одне з двох. Залежить, чи буде підписане значення перетворене в непідписане для порівняння, чи неподписане перетворене в підписане. На 64-бітній платформі з 32-бітовим int, хоча, як і win64, int буде перетворений на size_t, і цикл ніколи не закінчується.
Стів Джессоп

@SteveJessop: Ви не можете сказати з певністю, що цикл ніколи не закінчується. Про ітерацію коли i == INT_MAX, то i++викликає невизначене поведінку. У цей момент може статися все, що завгодно.
Бен Войгт

@BenVoigt: правда, і досі не дає підстав ігнорувати попередження :-)
Стів Джессоп

2

Поміркуйте, чи потрібно взагалі повторювати

<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);

Інші функції бібліотеки алгоритмів виконують загальні завдання - переконайтеся, що ви знаєте, що є, якщо ви хочете заощадити зусилля.


1

Неясна, але важлива деталь: якщо ви скажете "для (авто це)" так, ви отримаєте копію об'єкта, а не власне елемента:

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)

1

Якщо ваш компілятор підтримує його, ви можете використовувати діапазон, заснований на доступ до векторних елементів:

vector<float> vertices{ 1.0, 2.0, 3.0 };

for(float vertex: vertices){
    std::cout << vertex << " ";
}

Принти: 1 2 3. Зауважте, ви не можете використовувати цю техніку для зміни елементів вектора.


0

Два сегменти коду працюють однаково. Однак непідписаний int "route є правильним. Використання непідписаних int типів буде краще працювати з вектором у тому випадку, коли ви його використовували. Виклик функції члена () у векторі повертає непідписане ціле значення, тому ви хочете порівнювати змінну "i" до значення власного типу.

Крім того, якщо вам все ще непокоїться, як виглядає "непідписаний int" у вашому коді, спробуйте "uint". Це, в основному, скорочена версія "unsigned int", і вона працює точно так само. Для його використання також не потрібно включати інші заголовки.


Непідписане ціле число для розміру () не обов'язково дорівнює "непідписаному int" у термінах C ++, часто "непідписане ціле число" в цьому випадку - це 64-бітове безпідписане ціле число, тоді як "непідписаний цілий" зазвичай 32 біт.
Medran

0

Додавши це так, як я не міг знайти його згаданого в жодній відповіді: для ітерації на основі індексу ми можемо використовувати те, для decltype(vec_name.size())чого можна було б оцінитиstd::vector<T>::size_type

Приклад

for(decltype(v.size()) i{ 0 }; i < v.size(); i++) {
    /* std::cout << v[i]; ... */
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.