Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
Постфиксные версии операторов ++ и -- используются в случае, когда в одном составном выражении необходимо использовать текущее значение переменной, а затем увеличить его.
В качестве примера используем постфиксный оператор инкремента для написания цикла, выводящего значения вектора до, но не включая, первого отрицательного значения.
auto pbeg = v.begin();
// отображать элементы до первого отрицательного значения
while (pbeg != v.end() && *beg >= 0)
cout << *pbeg++ << endl; // отобразить текущее значение и
// переместить указатель pbeg
Выражение *pbeg++ обычно малопонятно новичкам в языках С++ и С. Но поскольку эта схема весьма распространена, программисты С++ должны понимать такие выражения.
Приоритет постфиксного оператора инкремента выше, чем оператора обращения к значению, поэтому код *pbeg++ эквивалентен коду *(pbeg++). Часть pbeg++ осуществляет инкремент указателя pbeg и возвращает как результат копию предыдущего значения указателя pbeg. Таким образом, операндом оператора * будет неувеличенное значение указателя pbeg. Следовательно, оператор выводит элемент, на который первоначально указывал указатель pbeg, а затем осуществляет его инкремент.
Этот подход основан на том, что постфиксный оператор инкремента возвращает копию своего исходного, не увеличенного операнда. Если бы он возвратил увеличенное значение, то обращение к элементу вектора по такому увеличенному значению привело бы к плачевным результатам: первым оказался бы незаписанный элемент вектора. Хуже того, если бы у последовательности не было никаких отрицательных значений, то в конце произошла бы попытка обращения к значению несуществующего элемента за концом вектора.
Совет. Краткость может быть достоинствомТакие выражения, как *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.5Упражнение 4.17. Объясните различие между префиксным и постфиксным инкрементом.
Упражнение 4.18. Что будет, если цикл while из последнего пункта этого раздела, используемый для отображения элементов вектора, задействует префиксный оператор инкремента?
Упражнение 4.19. С учетом того, что ptr указывает на тип int, vec — вектор vector<int>, 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) обеспечивают доступ к члену. Оператор точка выбирает член из объекта типа класса; оператор стрелка определен так, что код ptr->mem эквивалентен коду (*ptr).mem.
string s1 = "a string", *p = &s1;
auto n = s1.size(); // вызов функции-члена size() строки s1
n = (*p).size(); // вызов функции-члена size() объекта, на который
// указывает указатель p
n = p->size(); // эквивалент (*p).size()
Поскольку приоритет обращения к значению ниже, чем оператора точка, часть обращения к значению следует заключить в скобки. Если пропустить круглые скобки, этот код поведет себя совсем по-иному:
// вызов функции-члена size() объекта, на который указывает указатель p
// затем обращение к значению результата!
*p.size(); // ошибка: p - указатель, он не имеет функции-члена size()
Этот код пытается вызвать функцию-член size() объекта p. Однако p — это указатель, у которого нет никаких членов; этот код не будет откомпилирован.
Оператор стрелка получает операнд в виде указателя и возвращает l-значение. Оператор точка возвращает l-значение, если объект, член которого выбирается, является l-значением; в противном случае результат — r-значение.
Упражнения раздела 4.6Упражнение 4.20. С учетом того, что iter имеет тип vector<string>::iterator, укажите, какие из следующих выражений допустимы, если таковые имеются. Объясните поведение допустимых выражений, и почему ошибочные не допустимы?
(a) *iter++; (b) (*iter)++; (с) *iter.empty()
(d) iter->empty(); (e) ++*iter; (f) iter++->empty();
4.7. Условный оператор
Условный оператор (оператор ?:) (conditional operator) позволяет внедрять простые конструкции if...else непосредственно в выражение. Условный оператор имеет следующий синтаксис:
условие ? выражение1 : выражение2;
где условие — это выражение, используемое в качестве условия, а выражение1 и выражение2 — это выражения одинаковых типов (или типов, допускающих преобразование в общий тип). Эти выражения выполняются в зависимости от условия. Если условие истинно, то выполняется выражение1; в противном случае выполняется выражение2. В качестве примера использования условного оператора рассмотрим код, определяющий, является ли оценка (grade) проходной (pass) или нет (fail):
string finalgrade = (grade < 60) ? "fail" : "pass";
Условие проверяет, не меньше ли оценка 60. Если это так, то результат выражения "fail"; в противном случае — результат "pass". Подобно операторам логического AND и OR (&& и ||), условный оператор гарантирует, что выполнено будет только одно из выражений, выражение1 или выражение2.
Результат условного оператора — l-значение, если оба выражения l-значения или если они допускают преобразование в общий тип l-значения. В противном случае результат — r-значение.
Вложенные условные операторыОдин условный оператор можно вложить в другой. Таким образом, условный оператор применяются как условие или как один или оба выражения другого условного оператора. В качестве примера используем пару вложенных условных операторов для трехступенчатой проверки оценки, чтобы выяснить, является ли она выше проходной, просто проходной или непроходной.