Главная страница

Язык программирования C Пятое издание


Скачать 1.85 Mb.
НазваниеЯзык программирования C Пятое издание
Дата15.07.2019
Размер1.85 Mb.
Формат файлаpdf
Имя файла620354-www.libfox.ru.pdf
ТипДокументы
#84130
страница12 из 54
1   ...   8   9   10   11   12   13   14   15   ...   54

Напишите и выполните соответствующую программу. Совпадает ли вывод с ожиданиями?
Если нет, то объясните почему. int x[10]; int *p = x; cout << sizeof(x)/sizeof(*x) << endl; cout << sizeof(p)/sizeof(*p) << endl;
Упражнение 4.30. Используя таблицу из раздела 4.12, расставьте скобки в следующих выражениях так, чтобы продемонстрировать порядок его обработки:
(a) sizeof x + y (b) sizeof p->mem[i]
(с) sizeof а < b (d) sizeof f()
4.10. Оператор запятая
Оператор запятая (,) (comma operator) получает два операнда, обрабатываемых слева направо. Подобно операторам логического AND и OR, а также условному оператору,
оператор запятая гарантирует порядок обработки своих операндов.
Левое выражение обрабатывается, а его результат отбрасывается. Результат выражения запятая — это значение правого выражения. Результат является l-значением, если правый операнд — l-значение.
Оператор запятая нередко используется в цикле for: vector<int>::size_type cnt = ivec.size();
// присвоить значения элементам size...1 вектора ivec for (vector<int>::size_type ix = 0; ix != ivec.size(); ++ix, --cnt) ivec[ix] = cnt;
Page 204/1103

Здесь выражения в заголовке цикла for увеличивают значение итератора ix и уменьшают значение целочисленной переменной cnt. Значения итератора ix и переменной cnt изменяются при каждой итерации цикла. Пока проверка итератора ix проходит успешно,
следующему элементу присваивается текущее значение переменной cnt. Упражнения раздела 4.10
Упражнение 4.31. Программа этого раздела использовала префиксные операторы инкремента и декремента. Объясните, почему были использованы префиксные, а не постфиксные версии? Что следует изменить для использования постфиксных версий?
Перепишите программу с использованием постфиксных операторов.
Упражнение 4.32. Объясните следующий цикл: constexpr int size = 5; int ia[size] = {1,2,3,4,5}; for (int *ptr = ia, ix = 0; ix != size && ptr != ia+size; ++ix, ++ptr) { /* ... */ }
Упражнение 4.33. Используя таблицу раздела 4.12, объясните, что делает следующее выражение: someValue ? ++x, ++y : --x, --y
4.11. Преобразование типов
В языке С++ некоторые типы взаимосвязаны. Когда два типа взаимосвязаны, объект или значение одного типа можно использовать там, где ожидается операнд связанного типа. Два типа считаются связанными, если между ними возможно преобразование (conversion).
Для примера рассмотрим следующее выражение, инициализирующее переменную ival значением 6: int ival = 3.541 + 3; // компилятор может предупредить о потере точности
Операндами сложения являются значения двух разных типов: 3.541 имеет тип double а 3 —
int. Вместо попытки суммирования двух значений разных типов язык С++ определяет набор преобразований, позволяющих преобразовать операнды в общий тип. Эти преобразования выполняются автоматически без вмешательства программиста, а иногда и без его ведома.
Поэтому они и называются неявным преобразованием (implicit conversion).
Неявные преобразования между арифметическими типами определены так, чтобы по возможности сохранять точность. Как правило, если у выражения есть и целочисленное значение, и значение с плавающей запятой, целое число преобразуется в число с плавающей точкой. В данном случае значение 3 преобразуется в тип double, осуществляется сложение чисел с плавающей запятой, и возвращается результат типа double.
Page 205/1103

Затем происходит инициализация. При инициализации доминирует тип инициализируемого объекта. Поэтому инициализатор преобразуется в его тип. В данном случае результат сложения типа double преобразуется в тип int и используется для инициализации переменной ival. Преобразование типа double в тип int усекает значение типа double, отбрасывая десятичную часть. В данном случае выражение присваивает переменной ival значение 6.
Когда происходят неявные преобразования
Компилятор автоматически преобразует операнды при следующих обстоятельствах.
• В большинстве выражений значения целочисленных типов, меньших, чем int, сначала преобразуются в соответствующий больший целочисленный тип.
• В условиях нелогические выражения преобразуются в тип bool.
• При инициализации инициализатор преобразуется в тип переменной; при присвоении правый операнд преобразуется в тип левого.
• В арифметических выражениях и выражениях отношения с операндами смешанных типов происходит преобразование в общий тип.
• Преобразования происходят также при вызове функций, как будет продемонстрировано в главе 6.
4.11.1. Арифметические преобразования
Арифметические преобразования (arithmetic conversion), впервые представленные в разделе
2.1.2, преобразуют один арифметический тип в другой. Иерархию преобразований типов определяют правила, согласно которым операнды операторов преобразуются в самый большой общий тип. Например, если один операнд имеет тип long double, то второй операнд преобразуется тоже в тип long double независимо от своего типа. Короче говоря, в выражениях, где используются целочисленные значения и значения с плавающей точкой,
целочисленное значение преобразуется в соответствующий тип с плавающей точкой.Целочисленные преобразования
Целочисленное преобразование (integral promotion) преобразовывает значения малых целочисленных типов в большие. Типы bool, char, signed char, unsigned char, short и unsigned short преобразуются в int, если значение соответствует ему, а в противном случае оно преобразуется в тип unsigned int. Как уже неоднократно упоминалось, значение false типа bool преобразуется в 0, a true в 1.
Большие символьные типы (wchar_t, char16_t и char32_t) преобразуются в наименьший целочисленный тип int, unsigned int, long, unsigned long, long long или unsigned long long,
которому соответствуют все возможные значения этого символьного типа. Операнды беззнакового типа
Если операнды оператора имеют разные типы, они обычно преобразуются в общий тип. Если любой из операндов имеет беззнаковый тип, то тип, в который преобразуются операнды,
зависит от относительных размеров целочисленных типов на машине.
Как обычно, сначала осуществляются целочисленные преобразования. Если полученные в результате типы совпадают, то никаких дальнейших преобразований не нужно. Если оба
(возможно преобразованных) операнда имеют одинаковый знак, то операнд с меньшим типом преобразуется в больший тип.
Page 206/1103

При разных знаках, если тип беззнакового операнда больший, чем у знакового операнда,
знаковый операнд преобразуется в беззнаковый. Например, при операторах типа unsigned int и int, int преобразуется в unsigned int. Следует заметить, что если значение типа int отрицательное, результат преобразуется так, как описано в разделе 2.1.2.
Остается случай, когда тип знакового операнда больше, чем беззнакового. В данном случае результат зависит от конкретной машины. Если все значения беззнакового типа соответствуют большему типу, то операнд беззнакового типа преобразуется в знаковый. Если значения не соответствуют, то знаковый операнд преобразуется в беззнаковый. Например,
если операнды имеют типы long и unsigned int и размер у них одинаковый, операнд типа long будет преобразован в unsigned int. Если тип long окажется больше, то unsigned int будет преобразован в long. Концепция арифметических преобразований
Арифметические преобразования проще всего изучить на примерах. bool flag; char cval; short sval; unsigned short usval; int ival; unsigned int uival; long lval; unsigned long ulval; float fval; double dval;
3.14159L + 'a'; //
'a' преобразуется в int, а затем int в long double dval + ival; // ival преобразуется в double dval + fval; // fval преобразуется в double ival = dval; // dval преобразуется в int (с усечением) flag = dval; // если dval - 0, flag - false, в противном случае - true cval + fval; // cval преобразуется в int, затем int во float sval + cval; // sval и cval преобразуется в int cval + lval; // cval преобразуется в long ival + ulval; //
Page 207/1103
ival преобразуется в unsigned long usval + ival; // преобразование зависит от соотношения
// размеров типов unsigned short и int uival + lval; // преобразование зависит от соотношения
// размеров типов unsigned int и long
В первом выражении суммы символьная константа 'a' имеет тип char, являющийся числовым
(см. раздел 2.1.1). Какое именно это значение, зависит от используемого машиной набора символов. На машине авторов, где установлен набор символов ASCII, символу 'a'
соответствует число 97. При добавлении символа 'a' к значению типа long double значение типа char преобразуется в тип int, а затем в тип long double. Это преобразованное значение добавляется к литералу. Интересны также два последних случая, где происходит преобразование беззнаковых значений. Тип результата этих выражений зависит от конкретной машины. Упражнения раздела 4.11.1
Упражнение 4.34. С учетом определений переменных данного раздела объясните, какие преобразования имеют место в следующих выражениях:
(a) if (fval) (b) dval = fval + ival; (c) dval + ival * cval;
Помните, что возможно придется учитывать порядок операторов.
Упражнение 4.35. С учетом определений char cval; int ival; unsigned int ui; float fval; double dval; укажите неявные преобразования типов, если таковые вообще имеются.
(a) cval = 'a' + 3; (b) fval = ui - ival * 1.0;
(с) dval = ui * fval; (d) cval = ival + fval + dval;
4.11.2. Другие неявные преобразования
Кроме арифметических, существует еще несколько видов неявных преобразований, включая следующие.
Преобразование массива в указатель . В большинстве выражений, когда используется массив, он автоматически преобразуется в указатель на свой первый элемент. int ia[10]; //
Page 208/1103
массив из десяти целых чисел int* ip = ia; // ia преобразуется в указатель на первый элемент
Это преобразование не происходит при использовании массива в выражении decltype или в качестве операнда операторов обращения к адресу (&), sizeof или typeid (который рассматривается в разделе 19.2.2). Преобразование не происходит также при инициализации ссылки на массив (см. раздел 3.5.1). Подобное преобразование указателя происходит при использовании в выражении типа функции, как будет описано в разделе 6.7.
Преобразование указателя . Существует несколько других преобразований указателя:
постоянное целочисленное значение 0 и литерал nullptr могут быть преобразованы в указатель на любой тип; указатель на любой неконстантный тип может быть преобразован в void*, а указатель на любой тип может быть преобразован в const void*. Как будет продемонстрировано в разделе 15.2.2, существуют дополнительные преобразования указателя, относящиеся к типам, связанным наследованием.
Преобразование в тип bool . Существует автоматическое преобразование арифметических типов и типов указателя в тип bool. Если указатель или арифметическое значение — нуль,
преобразование возвращает значение false; любое другое значение возвращает true: char *cp = get_string(); if (cp) /* ... */ // true, если cp не нулевой указатель while (*cp) /* ... */ // true, если *cp не нулевой символ
Преобразование в константу. Указатель на неконстантный тип можно преобразовать в указатель на соответствующий константный тип, то же относится и к ссылкам. Таким образом,
если Т — тип, то указатель или ссылку на тип T можно преобразовать в указатель или ссылку на const Т (см. разделы 2.4.1 и 2.4.2). int i; const int &j = i; // преобразовать в ссылку на const int const int *p = &i; // преобразовать неконстантный адрес в константный int &r = j, *q = p; // ошибка: преобразование константы в не константу
// недопустимо
Обратное преобразование (устранение спецификатора const нижнего уровня) невозможно.
Преобразование, определенное типами класса . Тип класса может сам определять преобразования, которые компилятор применит автоматически. Компилятор применяет
Page 209/1103
только одно преобразование типа класса за раз. В разделе 7.5.4 приведен пример, когда необходимо несколько преобразований, и он не работает.
В программах ранее уже использовались преобразования типов класса, когда символьная строка в стиле С использовалась там, где ожидался библиотечный тип string (см. раздел
3.5.5), а также при чтении из потока istream в условии. string s, t = "a value"; // с имвольный строковый литерал преобразован
// в тип string while (cin >> s) // условие while преобразует cin в bool
Условие (cin >> s) читает поток cin и возвращает его же как результат. Условия ожидают значение типа bool, но оно проверяет значение типа istream. Библиотека IO определяет преобразование из типа istream в bool. Это преобразование используется автоматически,
чтобы преобразовать поток cin в тип bool. Результирующее значение типа bool зависит от состояния потока. Если последнее чтение успешно, то преобразование возвращает значение true. Если последняя попытка потерпела неудачу, то преобразование возвращает значение false.
4.11.3. Явные преобразования
Иногда необходимо явно преобразовать объект в другой тип. Например, в следующем коде может понадобиться использование деления с плавающей точкой: int i, j; double slope = i/j;
Для этого необходим способ явного преобразования переменных i и/или j в тип double. Для явного преобразования используется приведение (cast) типов.
Хотя приведение время от времени необходимо, оно довольно опасно. Именованные операторы приведения
Именованный оператор приведения имеет следующую форму: имя_приведения < тип >( выражение ); где
Page 210/1103
тип — это результирующий тип преобразования, а выражение — приводимое значение. Если тип — ссылка, то результат l-значение.
Имя_приведения может быть одним из следующих: static_cast, dynamic_cast, const_cast и reinterpret_cast. Приведение dynamic_cast, обеспечивающее идентификацию типов времени выполнения, рассматривается в разделе 19.2.
Имя_приведения определяет, какое преобразование осуществляется.Оператор static_cast
Любое стандартное преобразование типов, кроме задействующего спецификатор const нижнего уровня, можно затребовать, используя оператор static_cast. Например, приведя тип одного из операндов к типу double, можно заставить выражение использовать деление с плавающей точкой:
// приведение для вынужденного деления с плавающей точкой double slope = static_cast<double>(j) / i;
Оператор static_cast зачастую полезен при присвоении значения большего арифметического типа переменной меньшего. Приведение сообщает и читателю программы, и компилятору,
что мы знаем и не беспокоимся о возможной потере точности. При присвоении большего арифметического типа меньшему компиляторы зачастую выдают предупреждение. При явном приведении предупреждающее сообщение не выдается.
Оператор static_cast полезен также при выполнении преобразований, которые компилятор не выполняет автоматически. Например, его можно использовать для получения значения указателя, сохраняемого в указателе void* (см. раздел 2.3.2): void* p = &d; // ok: адрес любого неконстантного объекта может
// храниться в указателе void*
// ok: преобразование void* назад в исходный тип указателя double *dp = static_cast<double*>(p);
После сохранения адреса в указателе типа void* можно впоследствии использовать оператор static_cast и привести указатель к его исходному типу, что позволит сохранить значение указателя. Таким образом, результат приведения будет равен первоначальному значению адреса. Однако следует быть абсолютно уверенным в том, что тип, к которому приводится указатель, является фактическим типом этого указателя; при несоответствии типов результат непредсказуем. Оператор const_cast
Оператор const_cast изменяет только спецификатор const нижнего уровня своего операнда
(см. раздел 2.4.3): const char *pc;
Page 211/1103
char *p = const_cast<char*>(pc); // ok: однако запись при помощи p
// указателя непредсказуема
Принято говорить, что приведение, преобразующее константный объект в неконстантный,
"сбрасывает const". При сбросе константности объекта компилятор больше не будет препятствовать записи в этот объект. Если объект первоначально не был константным,
использование приведения для доступа на запись вполне допустимо. Но применение оператора const_cast для записи в первоначально константный объект непредсказуемо.
Только оператор const_cast позволяет изменить константность выражения. Попытка изменить константность выражения при помощи любого другого именованного оператора приведения закончится ошибкой компиляции. Аналогично нельзя использовать оператор const_cast для изменения типа выражения: const char *cp;
// ошибка: static_cast не может сбросить const char *q = static_cast<char*>(cp); static_cast<string>(cp); // ok: преобразует строковый литерал в строку const_cast<string>(cp); // ошибка: const_cast изменяет только
// константность
Оператор const_cast особенно полезен в контексте перегруженных функций,
рассматриваемых в разделе 6.4. Оператор reinterpret_cast
Оператор reinterpret_cast осуществляет низкоуровневую интерпретацию битовой схемы своих операндов. Рассмотрим, например, следующее приведение: int *ip; char *pc = reinterpret_cast<char*>(ip);
Никогда не следует забывать, что фактическим объектом, на который указывает указатель pc,
является целое число, а не символ. Любое использование указателя pc, подразумевающее,
что это обычный символьный указатель, вероятно, потерпит неудачу во время выполнения.
Например, следующий код, вероятней всего, приведет к непредвиденному поведению во время выполнения: string str(pc);
Использование указателя pc для инициализации объекта типа string — хороший пример
Page 212/1103
небезопасности оператора reinterpret_cast. Проблема в том, что при изменении типа компилятор не выдаст никаких предупреждений или сообщений об ошибке. При инициализации указателя pc адресом типа int компилятор не выдаст ни предупреждения, ни сообщения об ошибке, поскольку явно указано, что это и нужно. Однако любое последующее применение указателя pc подразумевает, что он содержит адрес значения типа char*.
Компилятор не способен выяснить, что фактически это указатель на тип int. Таким образом,
инициализация строки str при помощи указателя pc вполне правомерна, хотя в данном случае абсолютно бессмысленна, если не хуже! Отследить причину такой проблемы иногда чрезвычайно трудно, особенно если приведение указателя ip к pc происходит в одном файле,
а использование указателя pc для инициализации объекта класса string — в другом.
Оператор reinterpret_cast жестко зависит от конкретной машины. Чтобы безопасно использовать оператор reinterpret_cast, следует хорошо понимать, как именно реализованы используемые типы, а также то, как компилятор осуществляет приведение. Приведение типов в старом стиле
В ранних версиях языка С++ явное приведение имело одну из следующих двух форм: тип ( выражение ); // форма записи приведения в стиле функции
( тип ) выражение ; // форма записи приведения в стиле языка С
В зависимости от используемых типов, приведение старого стиля срабатывает аналогично операторам const_cast, static_cast или reinterpret_cast. В случаях, где используются операторы static_cast или const_cast, приведение типов в старом стиле позволяет осуществить аналогичное преобразование, что и соответствующий именованный оператор приведения. Но если ни один из подходов не допустим, то приведение старого стиля срабатывает аналогично оператору reinterpret_cast. Например, используя форму записи старого стиля, можно получить тот же результат, что и с использованием reinterpret_cast. char *pc = (char*) ip; // ip указатель на тип int Совет. Избегайте приведения типов
Приведение нарушает обычный порядок контроля соответствия типов (см. раздел 2.2),
поэтому авторы настоятельно рекомендуют избегать приведения типов. Это особенно справедливо для оператора reinterpret_cast. Такие приведения всегда опасны. Операторы const_cast могут быть весьма полезны в контексте перегруженных функций,
рассматриваемых в разделе 6.4. Использование оператора const_cast зачастую свидетельствует о плохом проекте. Другие операторы приведения, static_cast и dynamic_cast,
должны быть необходимы нечасто. При каждом применении приведения имеет смысл хорошо подумать, а нельзя ли получить тот же результат другим способом. Если приведение все же неизбежно, имеет смысл принять меры, позволяющие снизить вероятность возникновения ошибки, т.е. ограничить область видимости, в которой используется приведенное значение, а также хорошо документировать все подобные случаи.
Приведения старого стиля менее очевидны, чем именованные операторы приведения.
Page 213/1103

Поскольку их легко упустить из виду, обнаружить ошибку становится еще трудней.
Упражнения раздела 4.11.3
Упражнение 4.36. С учетом того, что i имеет тип int, a d — double, напишите выражение i *= d так, чтобы осуществлялось целочисленное умножение, а не с плавающей запятой.
Упражнение 4.37. Перепишите каждое из следующих приведений старого стиля так, чтобы использовался именованный оператор приведения. int i; double d; const string *ps; char *pc; void *pv;
(a) pv = (void*)ps; (b) i = int(*pc);
(c) pv = &d; (d) pc = (char*)pv;
Упражнение 4.38. Объясните следующее выражение: double slope = static_cast<double>(j/i);
4.12. Таблица приоритетов операторов
Таблица 4.4. Приоритет операторов Порядок и оператор Действие Применение Раздел Л ::
Глобальная область видимости :: имя 7.4.1 Л :: Область видимости класса класс :: имя 3.2.2 Л :: Область видимости пространства имен пространствоимен :: имя 3.1 Л . Обращение к члену класса объект . член 1.5.2 Л -> Обращение к члену класса pointer -> член 3.4.1 Л [] Индексирование выражение [ выражение ] 3.5.2 Л () Вызов функции имя ( список_выражений ) 1.5.2 Л () Конструкция type тип ( список_выражений ) 4.11.3 П ++ Постфиксный инкремент l-значение ++ 4.5 П -- Постфиксный декремент
Page 214/1103
l-значение -- 4.5 П typeid Идентификатор типа typeid( тип ) 19.2.2 П typeid Идентификатор типа времени выполнения typeid( выражение ) 19.2.2 П Явное приведение Преобразование типов cast_имя< тип >( выражение ) 4.11.3 П ++ Префиксный инкремент ++ l-значение 4.5 П -- Префиксный декремент -- l-значение 4.5 П

Побитовое NOT выражение 4.8 П ! Логическое NOT ! выражение 4.3 П - Унарный минус - выражение 4.2 П + Унарный плюс + выражение 4.2 П * Обращение к значению * выражение 2.3.2 П & Обращение к адресу & l-значение 2.3.2 П () Преобразование типов ( тип ) выражение 4.11.3 П sizeof Размер объекта sizeof выражение 4.9 П sizeof Размер типа sizeof( тип ) 4.9 П sizeof... Размер пакета параметров sizeof...( имя ) 16.4 П new Создание объекта new тип 12.1.2 П new[] Создание массива new тип [ размер ] 12.1.2 П delete Освобождение объекта delete выражение 12.1.2 П delete[]
Освобождение массива delete[] выражение 12.1.2 П noexcept Способность к передаче noexcept( выражение ) 18.1.4 Л ->* Указатель на член класса указатель ->* указатель_на_член 19.4.1 Л .* Указатель на член класса объект .* указатель_на_член 19.4.1 Л * Умножение выражение * выражение 4.2 Л / Деление выражение /
Page 215/1103
выражение 4.2 Л % Деление по модулю (остаток) выражение % выражение 4.2 Л + Сумма выражение + выражение 4.2 Л - Разница выражение - выражение 4.2 Л << Побитовый сдвиг влево выражение << выражение 4.8 Л >> Побитовый сдвиг вправо выражение >> выражение 4.8 Л < Меньше выражение < выражение 4.3 Л <= Меньше или равно выражение <= выражение 4.3 Л > Больше выражение > выражение 4.3 Л >= Больше или равно выражение >= выражение 4.3 Л == Равенство выражение == выражение 4.3 Л != Неравенство выражение != выражение 4.3 Л & Побитовый AND выражение & выражение 4.8 Л ^ Побитовый XOR выражение ^ выражение 4.8 Л | Побитовый OR выражение | выражение 4.8 Л && Логический AND выражение && выражение 4.3 Л || Логический OR выражение || выражение 4.3 П ?: Условный оператор выражение ?
Page 216/1103
выражение : выражение 4.7 П = Присвоение l-значение = выражение 4.4 П *=, /=, %=, Составные операторы присвоения l-значение += выражение , и т.д. 4.4 П +=, -=, 4.4 П <<=, >>=, 4.4 П &=, |=, ^= 4.4 П throw
Передача исключения throw выражение 4.6.1 Л , Запятая выражение, выражение 4.10
Резюме
Язык С++ предоставляет богатый набор операторов и определяет их назначение, когда они относятся к значениям встроенных типов. Кроме того, язык поддерживает перегрузку операторов, позволяющую самостоятельно определять назначение операторов для типов класса. Определение операторов для собственных типов рассматривается в главе 14.
Чтобы разобраться в составных выражениях (содержащих несколько операторов),
необходимо выяснить приоритет и порядок обработки операндов. Каждый оператор имеет приоритет и порядок. Приоритет определяет группировку операторов в составном выражении,
а порядок определяет группировку операторов с одинаковым уровнем приоритета.
Для большинства операторов порядок выполнения операндов не определен, компилятор выбирает сам, какой операнд обработать сначала — левый или правый. Зачастую порядок вычисления результатов операндов никак не влияет на результат выражения. Но если оба операнда обращаются к одному объекту, причем один из них изменяет объект, то порядок выполнения становится весьма важен, а связанные с ним серьезные ошибки обнаружить крайне сложно.
И наконец, компилятор зачастую сам преобразует тип операндов в другой связанный тип.
Например, малые целочисленные типы преобразуются в больший целочисленный тип каждого выражения. Преобразования существуют и для встроенных типов, и для классов.
Преобразования могут быть также осуществлены явно, при помощи приведения.
Термины
L-значение (l-value). Выражение, возвращающее объект или функцию. Неконстантное l-значение обозначает объект, который может быть левым операндом оператора присвоения.
R-значение (r-value). Выражение, возвращающее значение, но не ассоциированную с ним область, если таковое значение вообще имеется.
Арифметическое преобразование (arithmetic conversion). Преобразование одного арифметического типа в другой. В контексте парных арифметических операторов
Page 217/1103
арифметические преобразования, как правило, сохраняют точность, преобразуя значения меньшего типа в значения большего (например, меньший целочисленный тип char или short преобразуется в int).
Выражение (expression). Самый низкий уровень вычислений в программе на языке С++. Как правило, выражения состоят из одного или нескольких операторов. Каждое выражение возвращает результат. Выражения могут использоваться в качестве операндов, что позволяет создавать составные выражения, которым для вычисления собственного результата нужны результаты других выражений, являющихся его операндами.
Вычисление по сокращенной схеме (short-circuit evaluation). Термин, описывающий способ выполнения операторов логического AND и OR. Если первого операнда этих операторов достаточно для определения общего результата, то остальные операнды не рассматриваются и не вычисляются.
Неявное преобразование (implicit conversion). Преобразование, которое осуществляется компилятором автоматически. Такое преобразование осуществляется в случае, когда оператор получает значение, тип которого отличается от необходимого. Компилятор автоматически преобразует операнд в необходимый тип, если соответствующее преобразование определено.
Операнд (operand). Значение, с которым работает выражение. У каждого оператора есть один или несколько операндов
Оператор --. Оператор декремента. Имеет две формы, префиксную и постфиксную.
Префиксный оператор декремента возвращает l-значение. Он вычитает единицу из значения операнда и возвращает полученное значение. Постфиксный оператор декремента возвращает r-значение. Он вычитает единицу из значения операнда, но возвращает исходное, неизмененное значение. Примечание: итераторы имеют оператор -- даже если у них нет оператора -.
Оператор !. Оператор логического NOT. Возвращает инверсное значение своего операнда типа bool. Результат true, если операнд false, и наоборот.
Оператор &. Побитовый оператор AND. Создает новое целочисленное значение, в котором каждая битовая позиция имеет значение 1, если оба операнда в этой позиции имеют значение 1. В противном случае бит получает значение 0.
Оператор &&. Оператор логического AND. Возвращает значение true, если оба операнда истинны. Правый операнд обрабатывается, только если левый операнд истинен.
Оператор ,. Оператор запятая. Бинарный оператор, обрабатывающийся слева направо.
Результатом оператора запятая является значение справа. Результат является l-значением,
только если его операнд — l-значение.
Оператор ?:. Условный оператор. Сокращенная форма конструкции if...else следующего вида:
условие ? выражение1 : выражение2 . Если условие истинно (значение true) выполняется
Page 218/1103
выражение1 , в противном случае — выражение2 . Тип выражений должен совпадать или допускать преобразование в общий тип.
Выполняется только одно из выражений.
Оператор ^. Побитовый оператор XOR. Создает новое целочисленное значение, в котором каждая битовая позиция имеет значение 1, если любой (но не оба) из операндов содержит значение 1 в этой битовой позиции. В противном случае бит получает значение 0.
Оператор |. Побитовый оператор OR. Создает новое целочисленное значение, в котором каждая битовая позиция имеет значение 1, если любой из операндов содержит значение 1 в этой битовой позиции. В противном случае бит получает значение 0.
Оператор ||. Оператор логического OR. Возвращает значение true, если любой из операндов истинен. Правый операнд обрабатывается, только если левый операнд ложен.
Оператор . Побитовый оператор NOT. Инвертирует биты своего операнда.
Оператор ++. Оператор инкремента. Оператор инкремента имеет две формы, префиксную и постфиксную. Префиксный оператор инкремента возвращает l-значение. Он добавляет единицу к значению операнда и возвращает полученное значение. Постфиксный оператор инкремента возвращает r-значение. Он добавляет единицу к значению операнда, но возвращает исходное, неизмененное значение. Примечание: итераторы имеют оператор ++,
даже если у них нет оператора +.
Оператор <<. Оператор сдвига влево. Сдвигает биты левого операнда влево. Количество позиций, на которое осуществляется сдвиг, задает правый операнд. Правый операнд должен быть нулем или положительным значением, ни в коем случае не превосходящим количества битов в левом операнде. Левый операнд должен быть беззнаковым; если левый операнд будет иметь знаковый тип, то сдвиг бита знака приведет к непредсказуемому результату.
Оператор >>. Оператор сдвига вправо. Аналогичен оператору сдвига влево, за исключением направления перемещения битов. Правый операнд должен быть нулем или положительным значением, ни в коем случае не превосходящим количества битов в левом операнде. Левый операнд должен быть беззнаковым; если левый операнд будет иметь знаковый тип, то сдвиг бита знака приведет к непредсказуемому результату.
Оператор const_cast. Применяется при преобразовании объекта со спецификатором const нижнего уровня в соответствующий неконстантный тип, и наоборот.
Оператор dynamic_cast. Используется в комбинации с наследованием и идентификацией типов во время выполнения.
См . раздел 19.2.
Оператор reinterpret_cast. Интерпретирует содержимое операнда как другой тип. Очень опасен и жестко зависит от машины.
Оператор sizeof. Возвращает размер в байтах объекта, указанного по имени типа, или типа переданного выражения.
Оператор static_cast. Запрос на явное преобразование типов, которое компилятор осуществил бы неявно. Зачастую используется для переопределения неявного преобразования, которое в противном случае выполнил бы компилятор.
Page 219/1103

Оператор (operator). Символ, который определяет действие, выполняемое выражением. В
языке определен целый набор операторов, которые применяются для значений встроенных типов. В языке определен также приоритет и порядок выполнения для каждого оператора, а также задано количество операндов для каждого из них. Операторы могут быть перегружены и применены к объектам классов.
Парный оператор (binary operator). Операторы, в которых используются два операнда.
Перегруженный оператор (overloaded operator). Версия оператора, определенного для использования с объектом класса. Определение перегруженных версий операторов рассматривается в главе 14.
Порядок (associativity). Определяет последовательность выполнения операторов одинакового приоритета. Операторы могут иметь правосторонний (справа налево) или левосторонний
(слева направо) порядок выполнения.
Порядок вычисления (order of evaluation). Порядок, если он есть, определяет последовательность вычисления операндов оператора. В большинстве случаев компилятор
С++ самостоятельно определяет порядок вычисления операндов. Однако, прежде чем выполнится сам оператор, всегда вычисляются его операнды. Только операторы
&&, ||, ?: и , определяют порядок выполнения своих операндов.
Преобразование (conversion). Процесс, в ходе которого значение одного типа преобразуется в значение другого типа. Преобразования между встроенными типами заложены в самом языке. Для классов также возможны преобразования типов.
Преобразование (promotion).
См . целочисленное преобразование.
Приведение (cast). Явное преобразование типов.
Приоритет (precedence). Определяет порядок выполнения операторов в выражении.
Операторы с более высоким приоритетом выполняются прежде операторов с более низким приоритетом.
Результат (result). Значение или объект, полученный при вычислении выражения.
Составное выражение (compound expression). Выражение, состоящее из нескольких операторов.
Унарный оператор (unary operator). Оператор, использующий один операнд.
Целочисленное преобразование (integral promotion). Подмножество стандартных преобразований, при которых меньший целочисленный тип приводится к ближайшему большему типу. Операнды меньших целочисленных типов (например, short, char и т.д.)
преобразуются всегда, даже если такие преобразования, казалось бы, необязательны.
Глава 5
Операторы
Подобно большинству языков, язык С++ предоставляет операторы для условного
Page 220/1103
выполнения кода, циклы, позволяющие многократно выполнять те же фрагменты кода, и операторы перехода, прерывающие поток выполнения. В данной главе операторы,
поддерживаемые языком С++, рассматриваются более подробно.
Операторы (statement) выполняются последовательно. За исключением самых простых программ последовательного выполнения недостаточно. Поэтому язык С++ определяет также набор операторов управления потоком (flow of control), обеспечивающих более сложные пути выполнения кода.
5.1. Простые операторы
Большинство операторов в языке С++ заканчиваются точкой с запятой. Выражение типа ival +
5 становится оператором выражения (expression statement), завершающимся точкой с запятой. Операторы выражения составляют вычисляемую часть выражения. ival + 5; // оператор выражения (хоть и бесполезный) cout << ival; // оператор выражения
Первое выражение бесполезно: результат вычисляется, но не присваивается, а следовательно, никак не используется. Как правило, выражения содержат операторы,
результат вычисления которых влияет на состояние программы. К таким операторам относятся присвоение, инкремент, ввод и вывод. Пустые операторы
Самая простая форма оператора — это пустой (empty), или нулевой, оператор (null statement). Он представляет собой одиночный символ точки с запятой
(;).
; // пустой оператор
Пустой оператор используется в случае, когда синтаксис языка требует наличия оператора, а логика программы — нет. Как правило, это происходит в случае, когда вся работа цикла осуществляется в его условии. Например, можно организовать ввод, игнорируя все прочитанные данные, пока не встретится определенное значение:
// читать, пока не встретится конец файла или значение,
// равное содержимому переменной sought
Page 221/1103
while (cin >> s && s != sought)
; // пустой оператор
В условии значение считывается со стандартного устройства ввода, и объект cin неявно проверяется на успешность чтения. Если чтение прошло успешно, во второй части условия проверяется, не равно ли полученное значение содержимому переменной sought. Если искомое значение найдено, цикл while завершается, в противном случае его условие проверяется снова, начиная с чтения следующего значения из объекта cin.
Случаи применения пустого оператора следует комментировать, чтобы любой, кто читает код, мог сразу понять, что оператор пропущен преднамеренно. Остерегайтесь пропущенных и лишних точек с запятой
Поскольку пустой оператор является вполне допустимым, он может располагаться везде, где ожидается оператор. Поэтому лишний символ точки с запятой, который может показаться явно недопустимым, на самом деле является не более, чем пустым оператором.
Приведенный ниже фрагмент кода содержит два оператора: оператор выражения и пустой оператор. ival = v1 + v2;; // ok: вторая точка с запятой - это лишний
// пустой оператор
Хотя ненужный пустой оператор зачастую безопасен, дополнительная точка с запятой после условия цикла while или оператора if может решительно изменить поведение кода. Например,
следующий цикл будет выполняться бесконечно:
// катастрофа: лишняя точка с запятой превратила тело цикла
// в пустой оператор while (iter != svec.end()) ; // тело цикла while пусто!
++iter; // инкремент не является частью цикла
Несмотря на отступ, выражение с оператором инкремента не является частью цикла. Тело цикла — это пустой оператор, обозначенный символом точки с запятой непосредственно после условия.
Лишний пустой оператор не всегда безопасен. Составные операторы (блоки)
Составной оператор (compound statement), обычно называемый
Page 222/1103
блоком (block), представляет собой последовательность операторов, заключенных в фигурные скобки. Блок операторов обладает собственной областью видимости (см. раздел
2.2.4). Объявленные в блоке имена доступны только в данном блоке и блоках, вложенных в него. Как обычно, имя видимо только с того момента, когда оно определено, и до конца блока включительно.
Составные операторы применяются в случае, когда язык требует одного оператора, а логика программы нескольких. Например, тело цикла while или for составляет один оператор. Но в теле цикла зачастую необходимо выполнить несколько операторов. Заключив необходимые операторы в фигурные скобки, можно получить блок, рассматриваемый как единый оператор.
Для примера вернемся к циклу while из кода в разделе 1.4.1. while (val <= 10) { sum += val; // присвоить sum сумму val и sum
++val; // добавить 1 к val
}
Логика программы нуждалась в двух операторах, но цикл while способен содержать только один оператор. Заключив эти операторы в фигурные скобки, получаем один (составной)
оператор.
Блок не завершают точкой с запятой.
Как и в случае с пустым оператором, вполне можно создать пустой блок. Для этого используется пара фигурных скобок без операторов: while (cin >> s && s != sought)
{ } // пустой блок Упражнения раздела 5.1

1   ...   8   9   10   11   12   13   14   15   ...   54


написать администратору сайта