Этот подход основан на том, что постфиксный оператор инкремента возвращает копию своего исходного, не увеличенного операнда. Если бы он возвратил увеличенное значение, то обращение к элементу вектора по такому увеличенному значению привело бы к плачевным результатам: первым оказался бы незаписанный элемент вектора. Хуже того, если бы у последовательности не было никаких отрицательных значений, то в конце произошла бы попытка обращения к значению несуществующего элемента за концом вектора.
Такие выражения, как *iter++
, могут быть не очевидны, однако они весьма популярны. Следующая форма записи проще и менее подвержена ошибкам:
cout << *iter++ << endl;
чем ее более подробный эквивалент:
cout << *iter << endl;
++iter;
Поэтому примеры подобного кода имеет смысл внимательно изучать, чтобы они стали совершенно понятны. В большинстве программ С++ используются краткие формы выражений, а не их более подробные эквиваленты. Поэтому программистам С++ придется привыкать к ним. Кроме того, научившись работать с краткими формами, можно заметить, что они существенно менее подвержены ошибкам.
Большинство операторов не гарантирует последовательности обработки операндов (см. раздел 4.1.3). Отсутствие гарантированного порядка зачастую не имеет значения. Это действительно имеет значение в случае, когда выражение одного операнда изменяет значение, используемое выражением другого. Поскольку операторы инкремента и декремента изменяют свои операнды, очень просто неправильно использовать эти операторы в составных выражениях.
Для иллюстрации проблемы перепишем цикл из раздела 3.4.1, который преобразует в верхний регистр символы первого введенного слова:
for (auto it = s.begin(); it != s.end() && !isspace(*it) ; ++it)
it = toupper(*it); //
Этот пример использует цикл for
, позволяющий отделить оператор обращения к значению beg
от оператора его приращения. Замена цикла for
, казалось бы, эквивалентным циклом while
дает неопределенные результаты:
//
while (beg != s.end() && !isspace(*beg))
beg = toupper(*beg++); //
Проблема пересмотренной версии в том, что левый и правый операнды оператора =
используют значение, на которое указывает beg
,
*beg = toupper(*beg); //
*(beg + 1) = toupper(*beg); //
Или любым другим способом.
Упражнение 4.17. Объясните различие между префиксным и постфиксным инкрементом.
Упражнение 4.18. Что будет, если цикл while
из последнего пункта этого раздела, используемый для отображения элементов вектора, задействует префиксный оператор инкремента?
Упражнение 4.19. С учетом того, что ptr
указывает на тип int
, vec
— вектор vector
, a ival
имеет тип int
, объясните поведение каждого из следующих выражений. Есть ли среди них неправильные? Почему? Как их исправить?
(a) ptr != 0 && *ptr++ (b) ival++ && ival
(с) vec[ival++] <= vec[ival]
4.6. Операторы доступа к членам
Операторы точка (.
) (dot operator) (см. раздел 1.5.2) и стрелка (->
) (arrow operator) (см. раздел 3.4.1) обеспечивают доступ к члену. Оператор точка выбирает член из объекта типа класса; оператор стрелка определен так, что код
эквивалентен коду (*
.
string s1 = "a string", *p = &s1
auto n = s1.size(); //
n = (*p).size(); //