Многие говорят что умеют, на деле умеют не многие
Рекомендуемая литература:
- C++ Programming Language, 3rd edition, Bjarne Stroustrup
- Effective STL, Scott Meyers
- Мысль о BOOST_SCOPE_EXIT и лямбдах (осторожно - в конце есть кусок кода об который можно сломать мозг)
- Веревка достаточной длины, чтобы ... выстрелить себе в ногу, Ален Голуб
- Рекомендуемый к прохождению тест - Тест C++ - Средний уровень
К чему эпиграф?
К тому что многие C++ программисты говорят что умеют пользоваться контейнерами из библиотеки STL. На деле же приходится сталкиваться и с итераторами объявленными вне for и с дополнительным итераторами при удалении элемента из листа. На самом деле есть простые правила которым нужно следовать при обходе стандартных контейнеров:
- Не обходите вектор путём перебора его элементов по индексам, вы запросто можете потерять один из добавленных элементов. Используйте итераторы.
- Если не собираетесь менять значения элементов или же удалять их - используйте const_iterator. Так вы оставите себе и другим напоминание о том что контейнер нельзя менять.
- Никогда не сравнивайте итераторы при помощи операторов "больше" (
>) или "меньше" (<), проверяйте их только на равенство (==) и неравенство (!=), стандарт не гарантирует что итератор указывающий на элемент последовательности обязательно должен быть "меньше" итератора элемента который находится в последовательности после данного. - Если разницы нет - предпочитайте префиксный инкремент/декремент постфиксному (это касается не только стандартных контейнеров и итераторов). Префиксные операторы возвращают ссылку на элемент, а постфиксные - копию объекта, причём копирование объекта может занимать порой значительное время.
- Если не собираетесь менять количество элементов - объявляйте итераторы начала и конца контейнера в первой секции цикла
for. Не надо этими итераторами засорять внешнее пространство имён. Например можно делать так:for(std::list<int>::const_iterator iter=lst.begin(), end=lst.end(); iter != end; ++iter) - Если собираетесь менять количество элементов - получайте итератор конца контейнера каждый раз когда проверяется условие продолжения цикла.
- При удалении элементов последовательности (sequences) возвращают итератор элемента следующего за удалённым (аналогично для всех контейнеров операция вставки одиночного элемента возвращает итератор вставленного элемента):
for(std::list<int>::iterator iter=lst.begin(); iter != lst.end(); ) { if(check_condition(*iter)) { iter = lst.erase(iter); } else { ++iter; } } - По возможности, старайтесь использовать стандартные алгоритмы - это поможет избежать изобретения собственных велосипедов (у которых могут запросто оказаться квадратные колёса). Например, код выше запросто можно заменить этим (feel the difference):
lst.remove_if(check_condition); - Дополнение к пункту выше - вам не обязательно объявлять функции, которые вы собираетесь передавать в стандартные алгоритмы, в глобальной области видимости. Можно воспользоваться Boost.Lambda или же статьёй приведённой в списке рекомендуемой литературы:
#include <boost/preprocessor/cat.hpp> #define LOCAL(R, P) \ typedef struct { typedef R(*F) P; static R body P { #define LOCAL_END(name) \ } } BOOST_PP_CAT(local_func_,__LINE__); \ const BOOST_PP_CAT(local_func_,__LINE__)::F name = BOOST_PP_CAT(local_func_,__LINE__)::body ... LOCAL(bool, (int i)) return i & 1; LOCAL_END(is_odd); lst.remove_if(is_odd); - Если вы обходите массив по индексам, индексы начинаются с нуля (самый распространённый случай) и порядок обхода неважен - обходите не от нулевого элемента к последнему, а от последнего к нулевому (сравнение числа с нулём требует меньше времени чем с какой-либо другой константой):
int a[10]; for(unsigned i = 10; i--; ) ...
Я понимаю, эти правила звучат ультимативно, но из своего опыта могу сказать что если вы будете этим правилам следовать, то хуже ваш c++ код точно не станет.
Статья получилась очень короткая, но кто понял о чём правила выше - тот возьмёт на вооружение, кто уже использовал - даст почитать другим.
----
Дмитрий analizer Потапов, апрель 2009
Дело не только в добавленных элементах. Важно то, что код с проходом контейнера через итераторы не нужно будет переписывать, если вектор поменяют к примеру на список.
Совет 5:
Такую длинную контрукцию лучше в одну строчку не впихивать, лучше использовать компромисный, более читаемый вариант:
typedef std::list<int>::const_iterator ci;
for(ci iter=lst.begin(), end=lst.end(); iter != end; ++iter)
Если же используется компилятор, поддерживающий новый стандарт С++, то можно записать в одну строчк:
for(auto iter=lst.cbegin(), end=lst.cend(); iter != end; ++iter)
Совет 10:
Тут палка о двух концах. Может мы получим выигрышь в сравнении чисел с нулём, а может выигрыш окажется мизерным (это зависит в первую очередь архитектуры процессора).
А вот пенальти за непоследовательный доступ к памяти получим абсолютно точно. Как правило процессор при обращении к памяти делает небольшой префетч, и загружает в кэш нужную ячейку памяти + сколько-то ячеек после неё. Так что если мы будем обращат
2. вопрос стиля
3. согласен