Каталог

Издание: C++. Справочник

Глава 3
Выражения

Выражение — это объединение литералов, имен, операторов и специальных символов, служащее для того, чтобы выразить или вычислить значение либо же достичь каких-либо побочных эффектов. В этой главе описываются правила записи и чтения выражений, в том числе приводятся сведения об именующих (lvalue) и значащих (rvalue) выражениях, о преобразовании типов, константных выражениях, а также о том, как в C++ выполняется вычисление выражений. В главе приведены детальные описания всех операторов и других форм выражений.

Описания синтаксиса, приведенные в этой главе, не формализованы. Точная BNF-грамматика дана в главе 12.

3.1. Именующие выражения и значащие выражения

Именующие и значащие выражения — краеугольный камень выражений в языке C++. Если не вдаваться в детали, то именующее выражение (lvalue) — это ссылка на объект, а значащее выражение (rvalue) — это некоторое значение. Разница между именующими и значащими выражениями играет важную роль в понимании того, как записывать и воспринимать выражения.

Именующее выражение — это выражение, результатом которого является ссылка на объект, например имя переменной, ссылка на элемент массива по индексу, разыменованный указатель или вызов функции, возвращающей ссылку. Именующее выражение всегда связано с некоторой областью памяти, адрес которой известен.

Значащее выражение — это выражение, не являющееся именующим. В качестве примера можно привести литералы, результаты большинства операторов и вызовы функций, не возвращающих ссылок. Значащее выражение может не быть привязано к какой-либо области памяти.

Строго говоря, имя функции — это именующее выражение, единственное применение которого заключается в вызове функции или взятии ее адреса. Обычно под именующим выражением понимают объектное именующее выражение, и эта книга следует этой традиции.

Язык C++ заимствовал эти термины из С, где только именующие выражения могли использоваться в левой части инструкции присваивания. Понятие же значащего выражения относится к выражениям, которые могут быть использованы только в правой части такой инструкции, например:

#define rvalue 42

 

int lvalue;

 

lvalue = rvalue;

В языке C++ эти простые правила больше не действуют, но названия остались прежними, поскольку они все же близки к истине. Наиболее значимое отступление от традиций C заключается в том, что именующее выражение в C++ может быть константным, и в этом случае ему нельзя ничего присвоить (язык C, также развивавшийся, теперь включает понятие константного именующего выражения).

При использовании встроенных операторов присваивания левым операндом должно быть именующее выражение. Встроенный оператор взятия адреса & также должен применяться к именующим выражениям, как и операторы инкремента ++ и декремента --. Во всех остальных операторах должны использоваться значащие выражения. Эти же правила уже не так строги по отношению к определенным пользователем операторам: так, любой объект, в том числе и значащее выражение, может быть использован для вызова функций-членов, в том числе и перегруженных версий операторов =, &, ++ и --.

Вот еще несколько правил, касающихся именующих и значащих выражений:

·         массив — это именующее выражение, а вот адрес — значащее;

·         результатами работы встроенных операторов индексации [], разыменования *, присваивания =, += и т. д., инкремента ++ и декремента -- являются именующие выражения. Все прочие встроенные операторы производят значащие выражения;

·         результатом приведения к ссылочному типу является именующее выражение; все прочие приведения имеют своим результатом значащие выражения;

·         вызов функции (в том числе и перегруженного оператора), которая возвращает ссылку, — именующее выражение; в противном случае это значащее выражение;

·         если необходимо, именующее выражение неявно преобразуется в значащее, но не наоборот.

В листинге 3.1 приведены примеры именующих и значащих выражений.

Листинг 3.1. Именующие и значащие выражения

class number {

 

public:

 

  number(int i = 0) : value(i) {}

 

  operator int(  ) const { return value; }

 

  number& operator=(const number& n);

 

private:

  int value;

 

};

 

 

 

number operator+(const number& x, const number& y);

 

 

 

int main(  )

 

{

 

  number a[10], b(42);

 

  number* p;

 

  a;           // именующее выражение

 

  a[0];        // именующее выражение

 

  &a[0];       // значащее выражение

 

  *a;          // именующее выражение

 

  p;           // именующее выражение

 

  *p;          // именующее выражение

 

  10;          // значащее выражение

 

  number(10);  // значащее выражение

 

  a[0] + b;    // значащее выражение

 

  b = a[0];    // именующее выражение

 

}

3.2. Преобразования типов

В арифметическом выражении операнды бинарных операторов должны иметь один и тот же тип. Если это условие не выполнено, то один из операндов должен быть преобразован так, чтобы соответствовать другому. При вызове функции типы аргументов должны соответствовать типам параметров; если это, опять же, не так, то аргументы преобразуются, чтобы их типы соответствовали.
В языке C++ есть операторы приведения, которые позволяют явно задавать преобразования типов, но можно дать компилятору возможность автоматически преобразовать тип. В этом разделе приведены правила автоматического преобразования типов.

3.2.1. Арифметические типы

Арифметическими называются базовые целочисленные типы и типы с плавающей точкой: bool, char, signed char, unsigned char, int, short, long, unsigned int, unsigned short, unsigned long, float, double и long double. Некоторые операторы применимы только к арифметическим типам, указателями, типам-перечислениям, классам или каким-либо комбинациям типов. Описание каждого оператора содержит сведения о том, какие типы могут использоваться с оператором.

3.2.2. Продвижение типов

Продвижение типов (promotion) — это автоматическое преобразование типов, применимое только к арифметическим типам, при котором значение «меньшего» типа преобразуется в значение «большего» типа, причем исходное значение не меняется. Этим продвижение отличается от прочих автоматических преобразований типов, при которых данные могут быть утеряны. В продвижении участвуют либо целочисленные значения, либо значения с плавающей точкой. Правила продвижения целочисленных типов таковы:

·          «Малые» целочисленные значения приводятся к типу int, если с его помощью можно представить все значения исходного типа; иначе значения будут приведены к типу unsigned int. «Малое» целочисленное значение — это битовое поле (см. главу 6), размер которого не превышает размер int, либо же значение, относящееся к одному из следующих типов: char, signed char, unsigned char, short int, unsigned short int.

·         Значащие выражения, относящиеся к типу wchar_t или к типу-перечислению (в том числе и к битовым полям, основанным на перечислениях), приводятся к первому из перечисленных далее типов, которым можно представить все значения исходного типа: int, unsigned int, long, unsigned long.

Значащее выражение типа bool может быть приведено к типу int; значение true превращается в 1, а значение false — в 0.

Существует единственное правило продвижения значений с плавающей точкой:

·         значащее выражение типа float может быть приведено к типу double.

3.2.3. Арифметические преобразования типов

Арифметические преобразования типов — это автоматические преобразования типов, которые компилятор применяет к операндам встроенных арифметиче­ских операторов и операторов сравнения. Результат арифметических операторов имеет тот же тип, что и операнды.

При выполнении арифметических преобразований типов компилятор обычно старается сохранять исходные значения, насколько это возможно, но это ему не всегда удается. Например, результатом вычисления выражения –1 / 1u не будет –1, поскольку тип –1 — int, который приводится к типу unsigned int, что делает значение выражения зависимым от реализации. На 32-битной платформе с дополнением до двойки результат будет равен 4294967295u.

Правила арифметического преобразования типов действуют в следующем порядке:

1.             Если тип одного из операндов — long double, то второй приводится к типу long double.

2.             Иначе, если тип одного из операндов — double, то второй приводится к типу double.

3.             Иначе, если тип одного из операндов — float, то второй приводится к типу float.

4.             Если предыдущие правила неприменимы, то производится продвижение интегральных типов (см. раздел 3.2.2).

5.             Если после продвижения интегральных типов тип одного их операндов — unsigned long, то второй приводится к типу unsigned long.

6.             Иначе, если тип одного их операндов — long, а второго — unsigned int, то преобразование производится следующим образом:

Ÿ         если все значения unsigned int умещаются в long int, то операнд тип unsig­ned int преобразуется к типу long int;

m иначе оба операнда преобразуются к типу unsigned long.

7.             Иначе, если тип одного из операндов — long, то второй приводится к типу long.

8.             Иначе, если тип одного из операндов — unsigned, то второй приводится к типу unsigned.

9.             Иначе оба операнда приводятся к типу int.

3.2.4. Неявные численные преобразования

Значение арифметического типа может быть неявно преобразовано в другое значение, даже если при таком преобразовании будет потеряна информация. Эти неявные преобразования могут иметь место при присваивании, инициализации, передаче аргументов и возврате значения функции, а также в аргументах шаблонов. Не путайте эти преобразования с ранее описанными арифметическими преобразованиями типов, которые могут иметь место при любой арифметиче­ской операции.

Базовое правило здесь заключается в том, что значения любого арифметиче­ского типа могут быть приведены к другому типу. Если целевой тип не может содержать преобразуемое значение, то поведение не определено. При присваивании числа с плавающей точкой целочисленной переменной число с плавающей точкой будет урезано за счет отбрасывания дробной части. При преобразовании целочисленного значения или значения типа-перечисления в значение с плавающей точкой в ситуации, когда целочисленное значение находится между двумя значениями с плавающей точкой (это значит, что целое число непредставимо в формате с плавающей точкой), реализация выбирает одно из двух соседних с ним значений.

Значение может быть преобразовано в объект некоторого класса, если у класса есть подходящий конструктор без спецификатора explicit. Объект некоторого класса может быть приведен к неклассовому типу, если у класса есть нужный оператор преобразования типа. При преобразовании объекта некоторого класса в объект другого класса компилятор опирается на конструкторы целевого класса и операторы приведения типа исходного класса.

3.2.5. Преобразования именующих выражений

Преобразования именующих выражений автоматически преобразуют именующее выражение в значащее выражение в тех случаях, где необходимо именно значащее выражение. Необходимость преобразования именующего выражения в значащее обычно очевидна, и эти преобразования перечислены здесь только ради соблюдения полноты изложения:

·         именующее выражение-массив может быть преобразовано в значащее выражение—указатель, указывающий на первый элемент этого массива;

·         именующее выражение-функция может быть преобразовано в значащее выражение—указатель на эту функцию;

·         любое другое именующее выражение может быть преобразовано в значащее выражение с тем же значением. Если тип не является классом, то cv-квалификаторы отбрасываются при преобразовании. Таким образом, именующее выражение типа const int преобразуется в значащее выражение типа int.

3.2.6. Преобразование к типу bool

Значения арифметических типов, типов-перечислений и типов-указателей могут быть преобразованы в значения типа bool, при этом нулевые указатели и арифметические значения превращаются в false, а все остальное — в true. Обычный способ обнаружения нулевого указателя выглядит так:

if (ptr)

  ... // Сделать что-то с *ptr.

else

  ... // Указатель - нулевой

 

if (!ptr)

 

     // другой способ обнаружения нулевого указателя

Эта идиома настолько распространена, что многие классы имеют операторы преобразования объекта к типу void *, ну а void * может быть преобразован в bool (для этого сгодился бы любой тип-указатель, но void * используется из-за того, что указатель не предназначен для разыменования, а только для приведения к типу bool). Оператор void * используется вместо прямого преобразования в bool для того, чтобы избежать неявного продвижения к целочисленному типу. Например, в классе basic_ios определен оператор void *, возвращающий нулевой указатель тогда, когда выставлен бит failbit класса iostream (детали описаны в главе 13, см. заголовочный файл <ios>).

3.2.7. Приведения типов

Приведение типа — это явное преобразование типа. В C++ есть несколько разных способов приведения выражения к другому типу. Эти способы отражают путь эволюции языка. Есть шесть видов выражений приведения:

·          ( тип ) выражение

·         тип ( выражение )

·         const_cast< тип >( выражение )

·         dynamic_cast< тип >( выражение )

·         reinterpret_cast< тип >( выражение )

·         static_cast< тип >( выражение )

Первая форма записи была унаследована от языка C; вторая была добавлена в первые годы существования C++. Она имеет то же значение, что и первая, но синтаксис ее записи несколько отличается. Оставшиеся четыре формы записи выражений приведения в настоящее время вытесняют первые две, а их использование более предпочтительно в силу того, что они имеют больший уровень специализации, что снижает шанс возникновения ошибки. Все выражения приведения типов детально описываются далее в этой же главе; первая форма описана в разделе 3.5.4, а остальные — в разделе 3.5.2. Остальная часть данного раздела посвящена надлежащим случаям использования каждой формы приведения.

Если вы хотите сделать выражение константным или, наоборот, убрать квалификатор const (например, перед передачей указателя унаследованной библиотеке, написанной не на C++ и не способной работать с константными объявлениями), то следует использовать const_cast<> (то же самое можно делать
и с квалификатором volatile, хотя это несколько менее распространено).

Если у вас есть указатель или ссылка на объект, имеющий своим типом некий базовый класс иерархии, а вам нужно получить указатель или ссылку, типом которых будет производный от него класс, то следует использовать dynamic_cast<>. При таком приведении типа, называемом динамическим, проводится проверка времени выполнения, определяющая допустимость подобного приведения. Динамическое приведение работает только с полиморфными классами, у которых есть хотя бы одна виртуальная функция.

Оператор static_cast<> обычно применяется при переводе из одного типа-перечисления в другой тип-перечисление, из арифметического типа в другой ариф­метический тип или при необходимости использования конкретного пре­обра­зования типа для объекта класса, у которого есть несколько операторов преобразования типов. Иногда в подобных случаях для краткости исполь­зуются более простые формы приведения типов. Так, например, выражение sqrt(flo­at(i)) воспринимается проще, чем sqrt(static_cast<float>(i)). Оператор static_cast<> можно использовать для выполнения приведения типов, обратного неявному преобразованию. Так, C++ автоматически переводит элементы перечислений в целочисленные типы; если нужно выполнить обратное преобразование, то следует использовать static_cast<>. Также static_cast<> можно использовать для приведения указателей к типу void * и наоборот.

Оператор reinterpret_cast<> предназначен для выполнения потенциально небез­опасного приведения типов, такого, как, например, преобразование одного ти­па-указателя в другой. Так же с его помощью можно перевести указатель в целое и наоборот. Например, пакет отладки может вести файлы журналов отладки. При журнализации указатели могут с помощью оператора reinterpret_cast<> преобразовываться в целые числа и записываться в определенном формате.

Если вы неправильно используете один из операторов приведения, имеющих вид шаблонов, то компилятор сообщит об ошибке. Например, если при использовании оператора static_cast<> попытаться отбросить «константность», то компилятор будет «ругаться», как и при попытке использования оператора static_ cast<> вместо reinterpret_cast<>. Сокращенные формы записи приведения типов не обладают такими возможностями по проверке ошибок, поскольку каждая из них обозначает сразу несколько вариантов приведения.

Если вы встретили приведение типа, то следует воспринимать его как преду­преждение о том, что происходит что-то необычное. Расширенные формы приведения типов несут больше информации о намерениях автора и помогают компилятору реализовать эти намерения.

3.3. Константные выражения

Константное выражение — это выражение, которое может быть вычислено во время компиляции программы. Константы интегральных типов и типов-перечислений необходимо использовать в различных ситуациях, например при задании размерности массивов, значений элементов перечислений и меток инструкций case. Нулевые указатели — это особый вид интегральных констант.

3.3.1. Интегральные константные выражения

Интегральные константные выражения — это выражения, которые могут быть вычислены на этапе компиляции и типом которых является интегральный тип или тип-перечисление. Ситуации, в которых необходимо использование интегральных константных выражений, — это задание границ массива, значений элемен­тов перечисления, меток инструкции case, размеров битовых полей, инициализаторов статических членов данных и значений-аргументов шаблона. Ком­пилятор должен иметь возможность вычислить константное выражение на этапе компиляции, поэтому можно использовать только литералы, элемен­ты перечислений, константные объекты с константными инициализаторами, интегральные или относящиеся к типу-перечислению параметры шаблона, sizeof-выражения и константные адреса. Константный адрес — это адрес статического объекта, являющегося одновременно и именующим выражением, или же адрес функции. Строковый литерал, представляющий собой статический массив символов, также является константным адресом.

Интегральный статический константный член данных может быть инициализирован в определении класса, если инициализатор является константным интегральным выражением или константным выражением типа-перечисления. После определения этот член может быть использован в качестве константного выражения везде в пределах определения класса, например:

template<typename T, size_t size>

 

class array {

public:

  static const size_t SIZE = size;

  ...

 

private:

  T data[SIZE];

};

В главе 6 о статических членах данных рассказывается подробнее.

3.3.2. Нулевые указатели

Константное выражение со значением 0 может использоваться как нулевой указатель-константа. Нулевой указатель-константа может быть преобразован в нулевой указатель-значение. Практически всегда под нулевым указателем понимается именно нулевой указатель-значение.

Битовая запись нулевого указателя-значения зависит от реализации. Многие реализации используют для этого последовательности нулевых битов, но некоторые отступают от этого правила. Таким образом, нулевой указатель-константа не служит для представления тех битов, которые составляют значение нулевого указателя, а предназначен для использования в качестве мнемонического обозначения, подобно тому, как запись = 0 применяется при объявлении чисто виртуальных функций (глава 6).

Когда переменной-указателю присваивается нулевой указатель-константа, компилятор преобразует его в нулевой указатель-значение, относящийся к соответствующему типу. Аналогично, компилятор при сравнении указателей следит за тем, что сравнение это имеет смысл. В частности, нулевой указатель-значение никогда не будет равен допустимому значению указателя, всегда будет равен любому другому нулевому указателю-значению или нулевому указателю-константе. Нулевой указатель-значение при преобразовании к типу bool дает в результате false. Указатель с пустым инициализатором инициализируется нулевым указателем-значением.

Макроопределение NULL, определенное в заголовочном файле <cstdlib> и в других заголовочных файлах (см. главу 13), при подстановке заменяется нулевым указателем-константой. Использование NULL вместо 0 может служить полезным напоминанием, особенно если природа типа данных скрыта typedef-определениями:

Token tok1 = 0;     // Это указатель или целое?

Token tok2 = NULL;  // tok2, судя по всему – указатель

Разыменование нулевого указателя ведет к неопределенному поведению.

3.4. Вычисление выражений

На самом основном своем уровне выполнение программы на C++ сводится к по­следовательному вычислению выражений, проводимому под управлением инструкций, где некоторые выражения могут иметь побочные эффекты. Любое выражение может иметь один или несколько побочных эффектов из приведенного ниже списка:

·         считывание значения volatile-объекта;

·         изменение объекта;

·         вызов функции стандартной библиотеки;

·         вызов любой другой функции, имеющей побочные эффекты.

3.4.1. Точки последовательности

В ходе выполнения программы есть такие моменты времени, называемые точками последовательности (sequence points), когда побочные эффекты от уже вычисленных выражений завершились, а побочные эффекты не вычисленных пока выражений еще не начались. Компилятор имеет полное право перегруппировывать выражения, стоящие между точками последовательности, так, как ему угодно, если это не меняет исходной семантики. Этим же термином обозначаются те места в исходном коде программы, где при ее выполнении и будут стоять точки последовательности. Обычно детали использования точек последовательности можно игнорировать, однако при работе с глобальными объектами или volatile-объектами важно знать, когда именно можно безбоязненно обращаться к ним — а это как раз и есть время после точки последовательно­сти. Помимо этого, любое выражение, изменяющее скалярный объект несколько раз или считывающее его значение после модификации в промежутке времени от одной точки последовательности до другой, при вычислении приведет к неопределенному поведению. Это правило часто бьет по несведущим программистам, которые используют операторы инкремента и декремента, например:

int i = 0;

 

i = ++i - ++i;             // Ошибка: неопределенное поведение

 

printf("%d,%d", ++i, ++i); // Ошибка: неопределенное поведение

 

i = 3, ++i, i++;           // OK: i == 5

Точки последовательности находятся на следующих позициях:

·         в конце каждого выражения, не являющегося подвыражением. Такое выражение может использоваться в инструкции-выражении, инициализаторе, в качестве условия в инструкции if и т. д.;

·         после вычисления всех аргументов функции, но перед ее вызовом;

·         при возврате из функции: после копирования возвращаемого функцией значения (если оно есть), но до вычисления любых других выражений вне функции;

·         после вычисления первого подвыражения в каждом из перечисленных ниже выражений в том случае, если использованные операторы не были перегружены:

·         expr1 && expr2

·         expr1 || expr2

·         expr1 ? expr2 : expr3

·         expr1 , expr2

3.4.2. Порядок вычисления

В целом порядок вычисления операндов выражения не определен, поэтому не следует писать программы, полагаясь на какой-либо определенный порядок вычисления операндов. Например, в выражении f( ) / g( ) первой может быть вызвана как функция f( ), так и функция g( ). Эта разница может иметь значение тогда, когда у функций есть побочные эффекты. В листинге 3.2 приведен пример ситуации, когда программа выводит 2, если первой вызывается функция g(), а если первой вызывается все-таки f( ), то программа выведет 1.

Листинг 3.2. Порядок вычисления операндов

#include <iostream>

#include <ostream>

 

int x = 1;

int f(  )

{

  x = 2;

  return x;

}

 

int g(  )

{

  return x;

}

 

int main(  )

{

  std::cout << f(  ) / g(  ) << '\n';

}

Вот еще один простой пример. Увеличение переменной i может произойти как до, так и после присваивания, поэтому i может быть равна и 2, и 3.

int i = 1;

i = i++ + 1; // Значение i не определено

При вызове функции все ее аргументы вычисляются до самого вызова. Как
и следовало ожидать, порядок вычисления аргументов при этом не определен.

3.4.3. Укороченное вычисление

При использовании логических операторов && и || выполняется укороченное вычисление. Сначала вычисляется левый операнд, и если значение выражения может быть определено уже на этом этапе, то правый операнд не вычисляется:

if (false && f(  )) ... // функция f(  ) не будет вызвана никогда.

 

if (true || f(  ))  ... // функция f(  ) не будет вызвана никогда.

Тем не менее при перегрузке логических операторов укороченное вычисление производиться не будет. Как и при вызове любой другой функции, все аргументы вычисляются перед ее вызовом. Поэтому следует избегать перегрузки операторов && и ||.

3.5. Правила записи выражений

В языке C++ присутствуют общепринятые унарные операторы, как, например, логическое отрицание !a, бинарные операторы — например, сложение a+b, и даже один тернарный оператор a?b:c. В отличие от многих других языков, доступ к элементам массива также производится при помощи оператора a[b], а вызов функции — это n-арный оператор (например, a(b,c,d)).

У каждого оператора есть приоритет. Операторы с более высоким приоритетом группируются так, что выполняются раньше операторов с низким приоритетом. Заметьте, что приоритет определяет порядок разбора выражения компилятором, а не действительный порядок вычислений. Например, в выражении
a(
) + b( ) * c( ) умножение имеет высший приоритет, но функция a( ) может быть вызвана первой.

Некоторые операторы группируются слева направо. Например, выражение x / y / z эквивалентно ( x / y ) / z. Другие операторы группируются справа налево, как в выражении x = y = z, которое эквивалентно x = (y = z). Порядок такой группировки называется ассоциативностью оператора.

При разборе выражений языка C++ следует знать приоритет и ассоциативность используемых операторов. Например, выражение *ptr++ читается как *(ptr++), так как приоритет оператора постфиксного инкремента выше, чем у оператора разыменования указателя.

В табл. 3.1 описывается синтаксис, приоритет и ассоциативность всех видов операторов. В последующих подразделах различные виды выражений описываются более детально; в каждом подразделе рассматривается группа операторов, имеющих одинаковый приоритет.

Таблица 3.1. Синтаксис и ассоциативность выражений1

Группа   Ассоциативность            Выражения

Базовые выражения Слева направо      литерал this ( выражение ) имя ::имя
(наивысший                            класс-или-пространство_имен::имя
приоритет)

Группа                                     Ассоциативность            Выражения

Постфиксные                           Слева направо                   указатель[ выражение ]
выражения                                                                           выражение ( выражение, ...)
                                                                                              тип( выражение, ...)
                                                                                              объект.член указатель->член
                                                                                              приведение < тип >( выражение )
                                                                                              typeid( выражение ) typeid( тип )
                                                                                              именующее-выражение ++
                                                                                              именующее-выражение -–

Унарные выражения               Справа налево                   ++ именующее-выражение --
                                                                                              именующее-выражение ~выражение
                                                                                              compl выражение ! выражение not
                                                                                              выражение + выражение - выражение *
                                                                                              указатель & именующее-выражение
                                                                                              sizeof выражение sizeof( тип ) new-
                                                                                              выражение delete-выражение

Выражения приведения          Справа налево                   ( тип ) выражение

Указатели на члены                 Слева направо                   объект.*выражение
                                                                                              указатель->*выражение

Мультипликативные               Слева направо                   выражение * выражение выражение /
выражения                                                                           выражение выражение % выражение

Аддитивные выражения         Слева направо                   выражение + выражение
                                                                                              выражение – выражение

Выражения сдвига                   Слева направо                   выражение << выражение
                                                                                              выражение >> выражение

Выражения сравнения            Слева направо                   выражение < выражение выражение >
                                                                                              выражение выражение <= выражение
                                                                                              выражение >= выражение

Выражения равенства             Слева направо                   выражение == выражение
                                                                                              выражение != выражение
                                                                                              выражение not_eq выражение

Побитовое AND                      Слева направо                   выражение & выражение выражение bitand выражение

Побитовое XOR                      Слева направо                   выражение ^ выражение
                                                                                              выражение xor выражение

Побитовое OR                         Слева направо                   выражение | выражение
                                                                                              выражение bitor выражение

Логическое AND                     Слева направо                   выражение && выражение
                                                                                              выражение and выражение

Логическое OR                        Слева направо                   выражение || выражение
                                                                                              выражение or выражение

Условное выражение              Справа налево                   выражение ? выражение : выражение

Выражения                               Справа налево                   именующее-выражение = выражение
присваивания                                                                       именующее-выражение op= выражение
                                                                                              throw выражение throw

Запятая (наименьший                   Слева направо выражение , выражение
приоритет)

3.5.1. Базовые выражения

Базовые выражения — это те кирпичики, из которых строятся более сложные выражения. В число базовых выражений входят выражение в скобках, литерал и имя (возможно, квалифицированное). Различные формы базовых выражений таковы:

·         литерал

Константное значение. Строковые литералы, будучи массивами c элементами типа const char или const wchar_t, являются именующими выражениями. Все прочие литералы — значащие выражения (см. главу 1).

·         this

Означает объект, применительно к которому вызвана нестатическая функция-член класса. Тип this — указатель на класс; его значение относится к значащим выражениям.

·          ( выражение )

Имеет тип и значение выражения в скобках.

·         неквалифицированное-имя

Именует сущность в соответствии с правилами поиска имен, описанными в главе 2. Результат вычисления выражения есть сама эта сущность, а тип выражения — тип этой сущности. Результат будет именующим выражением, если сущность — это объект, член данных класса или функция. Далее описаны различные виды неквалифицированных идентификаторов:

Ÿ         идентификатор

Именует объект, функцию, член класса, тип или пространство имен. Имя ищется по правилам поиска имен, описанным в главе 2. Типом будет тип сущности. Если сущность — объект, член данных класса или функция, то выражение будет именующим.

Ÿ         operator символ

Именует оператор. Более подробно операторы описаны в главе 5.

Ÿ         имя-шаблона < необязательные-аргументы-шаблона >

Именует экземпляр шаблона. Более подробно шаблоны описаны в главе 7.

Ÿ         operator тип

Именует оператор преобразования типа. Тип — это спецификатор типа, возможно, со знаками указателя в объявителе (в главе 2 более подробно рассказывается о спецификаторах типа и объявителях).

Ÿ         ~ имя-класса

Именует деструктор упомянутого класса.

·         квалифицированное-имя

Здесь оператор разрешения области видимости используется для того, чтобы квалифицировать идентификатор, оператор или деструктор. Квалифицируемое имя может находиться в глобальной области видимости или же в области видимости класса или пространства имен:

Ÿ         ::идентификатор

Именует глобальный идентификатор. Типом выражения будет тип, указанный при объявлении сущности. Если сущность — объект, член данных класса или функция, то выражение будет именующим, а в остальных случаях оно будет значащим.

Ÿ         operator символ

Именует глобальный оператор. Заметьте, что операторы преобразования типов должны быть функциями-членами класса, поэтому не существует глобальных операторов преобразования типов (см. главу 5).

Ÿ         вложенное-имя :: неквалифицированное-имя

вложенное-имя :: template неквалифицированное-имя

Именует сущность в области видимости класса или пространства имен. Вложенное-имя может быть именем класса или пространства имен либо иметь вид имя-класса-или-пространства-имен :: вложенное-имя или имя-класса :: template вложенное-имя. Используйте ключевое слово template при инстанцировании шаблонов. В главе 7 подробнее рассказывается о членах шаблонов.

Ÿ         ::вложенное-имя :: неквалифицированное-имя

::вложенное-имя :: template неквалифицированное-имя Именует сущность в области видимости класса или пространства имен. Первое (самое левый) имя класса или пространства имен будет искаться в глобальной области видимости.

В оставшейся части этой главы синтаксический элемент именное-выражение относится к квалифицированному или неквалифицированному имени в том виде, в котором оно было только что описано. В частности, именное-выражение  может использоваться в качестве правого операнда операторов . или -> в постфиксном выражении.

В листинге 3.3 приведены примеры базовых выражений.

Листинг 3.3. Базовые выражения

namespace ns {

 

  int x;

 

  class cls {

 

  public:

 

    cls(int);

 

    ~cls(  );

 

  };

}

продолжение È

Листинг 3.3 (продолжение)

 

int x;

 

 

 

3.14159         // Литерал

 

(2 + 3 * 4)     // Выражение в скобках

 

x               // Неквалифицированный идентификатор

 

ns::x           // Квалифицированный идентификатор

 

ns::cls::cls    // Квалифицированный конструктор

 

operator*       // Неквалифицированный оператор *

3.5.2. Постфиксные выражения

Постфиксные выражения — это выражения, использующие постфиксный синтаксис (оператор следует за операндом), за исключением выражений, которые просто имеют тот же приоритет. Постфиксные выражения:

·         указатель [ выражение ]

Возвращает элемент массива. Оператору взятия элемента по индексу в качестве левого операнда нужен указатель, поэтому массив неявно преобразуется в указатель. Правый операнд преобразуется в целое число, и значением выражения будет *((указатель) + (выражение)). Если индекс массива вышел за его границы, то поведение не определено. Результатом выражения будет именующее значение, тип которого — базовый тип указателя.

·         выражение (необязательный-список-выражений)

Вызывает функцию. Левым операндом оператора вызова функции может быть имя функции, выражение, результат вычисления которого — указатель на функцию, или же выражение, результат вычисления которого является объектом, для которого определен оператор вызова функции (имя оператора и имя функции в данном случае совпадают). Необязательный-список-выражений — это разделенный запятыми список выражений присваивания числом от нуля и более (см. раздел 3.5.17). Все выражения в этом списке вычисляются, а затем вызывается сама функция. Тип выражения — это тип значения, возвращаемого функцией. Если это ссылочный тип, то выражение — именующее; в противном случае выражение будет значащим. Если тип возвращаемого значения — void, то значения у выражения нет. В главе 5 о функциях рассказывается подробнее.

·         простой-спецификатор-типа (необязательный-список-выражений)

Производит преобразование типа или вызов конструктора типа. Простой-спецификатор-типа — это имя фундаментального типа или квалифицированное имя класса, перечисления или typedef-синонима. Результат выражения — экземпляр указанного типа, инициализированный следующим образом:

Ÿ         если список выражений пуст, то результат — значащее выражение, инициализированное конструктором по умолчанию или нулем (детали процесса инициализации описаны в главе 2);

Ÿ         если в списке выражений стоит одно лишь выражение, то это значение приводится к нужному типу так же, как и в выражении приведения (то есть в(тип) выражение). Если тип — ссылочный, то выражение — именующее; в противном случае выражение будет значащим;

Ÿ         если в списке выражений больше одного выражения, то тип должен быть классом, а выражения из списка служат аргументами подходящего конструктора при создании экземпляра класса, который и будет возвращен как значащее выражение.

·         объект.именное-выражение

Возвращает член объекта. Имя объекта может быть квалифицированным, с тем чтобы указывать имя в базовом классе (см. главу 2). Тип выражения в целом — это тип именного-выражения. Значение выражения зависит от того, является ли именное-выражение  членом данных, функцией-членом или элементом перечисления:

Ÿ         если именное-выражение — это статический член данных, то он и будет возвращен как значащее выражение;

Ÿ         если именное-выражение — нестатический член данных, то результат вычисления выражения будет именующим выражением, только если объект — именующее выражение. Если именное-выражение объявлено как mutable, то результат не будет константой, даже если объект — константа. В противном случае, для того чтобы результат был константой, хватит того, что константой является сам объект или именное-выражение. Аналогично, для того чтобы результат имел свойство volatile, достаточно, чтобы объект или именное-выражение были объявлены как volatile;

Ÿ         если именное-выражение — функция-член класса, то применяются обычные правила разрешения перегрузки (см. главу 5). Если функция-член — статическая, то выражение — именующее: можно получить адрес функции (оператор &) или вызвать ее;

Ÿ         если именное-выражение — нестатическая функция-член класса, то она долж­на быть использована для вызова функции, например obj.memfun(arg);

Ÿ         если именное-выражение — элемент перечисления, то выражение будет значащим.

·         указатель -> именное-выражение

Возвращает результат вычисления выражения (*(указатель)).именное-выражение.

·         именующее-выражение++

Увеличивает именующее-выражение и возвращает его значение в качестве значащего выражения перед увеличением именующего-выражения. Тип именующего-выражения должен быть арифметическим или типом-указателем. Новое значение именующего-выражения — именующее-выражение + 1.

Если именующее-выражение относится к типу bool, то новое значение — всегда true. На это поведение не следует полагаться.

·         именующее-выражение--

Уменьшает именующее-выражение и возвращает его значение в качестве значащего выражения перед уменьшением именующего-выражения. Тип именующего-выражения должен быть арифметическим или типом-указателем, но не может быть типом bool. Новое значение именующего-выражения именующее-выражение + 1.

·         const_cast< тип >( выражение )

Приводит выражение к типу. Если тип — ссылочный, то выражение, именующее; в противном случае оно будет значащим. Новый тип должен совпадать с типом выражения, но наборы cv-квалификаторов могут отличаться.

Приведение const_cast, снимающее квалификатор const, — это дурной тон. Тем не менее иногда необходимо снимать «константность», особенно при передаче указателей в устаревшие библиотеки.

В главе 6 описывается модификатор mutable, который позволяет модифицировать члены данных константного объекта.

·         dynamic_cast< тип >( выражение )

Приводит выражение, имеющее своим значением указатель или ссылку на базовый класс, к типу — классу-наследнику, при этом во время выполнения программы производится проверка корректности такого преобразования: класс, к которому относится выражение, должен быть типом или классом, производным от типа. Класс, к которому относится выражение, должен быть полиморфным, то есть должен иметь хотя бы одну виртуальную функцию. Базовый класс при этом может быть виртуальным. Оператор dynamic_cast<> не может использоваться для снятия cv-квалификаторов. Приведение работает следующим образом:

Ÿ         если тип — это void *, то результат приведения — указатель на объект наиболее глубокого типа в иерархии наследования, на который указывает выражение. В этом случае тип выражения даже не обязан быть полиморфным;

Ÿ         если выражение — указатель, то тип должен также быть типом-указателем. Если тип выражения не совпадает с типом (отличается от него и его производных классов), то результат приведения — нулевой указатель-значение. В противном случае будет возвращено значение выражения, приведенное к типу. Если выражение — нулевой указатель, то будет возвращен нулевой указатель;

Ÿ         если выражение — объект, а тип — ссылочный, то выражение приводится к типу. Если тип выражения несовместим с типом, то будет сгенерировано исключение класса bad_cast;

Ÿ         mтакже можно приводить выражения производных классов к базовым классам — это соответствует обычному неявному преобразованию. В этом случае тип выражения также не обязан быть полиморфным.

В листинге 3.4 приведены примеры использования оператора dynamic_cast<>.

Листинг 3.4. Использование оператора dynamic_cast<>

#include <iostream>

#include <ostream>

 

class base {

 

public:

 

  virtual ~base() {}

};

 

class derived : public base {};

 

class most_derived : public derived {};

 

class other : public base {};

 

int main()

{

  base* b = new derived;

  dynamic_cast<most_derived*>(b); // Нулевой указатель

 

  dynamic_cast<derived&>(*b);     // OK

 

  dynamic_cast<other*>(b);        // Нулевой указатель

 

  derived* d = new most_derived;

 

  b = d;

 

  b = dynamic_cast<base*>(d);    // OK, но dynamic_cast<>

                                 // вовсе не необходим

}

·         reinterpret_cast< тип >( выражение )

Приводит выражение  к типу. При использовании оператора reinterpret_cast<> конструкторы и функции преобразования не вызываются. Приведение к ссылочному типу делает выражение именующим; в противном случае выражение будет значащим. Допустимы только перечисленные далее преобразования:

Ÿ         указатель может быть преобразован в целое. Целое должно быть достаточно большим, чтобы иметь возможность содержать значение указателя. От реализации зависит то, какой именно целочисленный тип следует использовать, а также то, каким именно образом осуществляется преобразование указателя в целое;

Ÿ         целые числа и элементы перечислений могут быть преобразованы в указатели. Способ преобразования определяется реализацией. При условии, что целое число достаточно велико, результатом преобразования указателя в целое число и наоборот будет начальное значение указателя;

Ÿ         приведение целочисленной константы со значением 0 к типу-указателю всегда дает в результате нулевой указатель-значение. Приведение любого другого целочисленного выражения со значением 0 к типу-указателю даст результат, зависящий от реализации; в частности, он может быть нулевым указателем, а может и не быть;

Ÿ         указатель на функцию может быть преобразован в указатель на функцию другого типа. Вызов такой функции через указатель приведет к неопределенному поведению. Результатом обратного приведения полученного указателя к исходному типу будет начальное значение указателя;

Ÿ         указатель на объект может быть преобразован в указатель на объект другого типа. Использование объекта при помощи такого указателя ведет к неопределенному поведению. Результатом обратного приведения полученного указателя к исходному типу будет начальное значение указателя;

Ÿ         указатель на член класса может быть преобразован в указатель на другой член класса. Использование такого указателя на член класса ведет к неопределенному поведению, за тем исключением, что в результате приведения указателя на член данных или функцию-член класса к другому типу-указателю на член класса и обратно будет получено исходное значение указателя;

Ÿ         нулевой указатель (константа или значение) может быть преобразован в нулевой указатель упомянутого типа;

Ÿ         mразыменовываются). Это значит, что приведение reinterpret_cast<T&>(x) во всем подобно приведению reinterpret_cast<T*>(&x).

Существуют следующие ограничения по применению оператора rein­ter­pret_cast:

Ÿ         указатель на функцию нельзя преобразовать в указатель на объект, как и наоборот;

Ÿ         указатель на функцию-член класса нельзя преобразовать в указатель на член данных класса, как и наоборот;

Ÿ         указатель на член класса нельзя преобразовать в простой указатель, как и наоборот;

Ÿ         при приведении не должны отбрасываться cv-квалификаторы.

В обычных программах reinterpret_cast<> используется очень редко.

В листинге 3.5 приведены примеры использования reinterpret_cast<>. В первом примере float преобразуется в int для того, чтобы показать устройство типа float изнутри. Для того чтобы этот зависящий от конкретной реализации пример был работоспособен, необходимо, чтобы sizeof(float) не превышал sizeof(int). Второй пример — простое преобразование указателя на функцию в целое число, необходимо для его вывода в определенном формате. Для этого нужно, чтобы тип int имел размер, достаточный для хранения указателя на функцию.

Листинг 3.5. Использование оператора reinterpret_cast<>

#include <cassert>

#include <iomanip>

#include <iostream>

#include <ostream>

 

int foo()

{

  return 0;

}

 

int main()

{

  using namespace std;

 

  float pi = 3.1415926535897;

  int   ipi;

 

  // Вывод чисел в шестнадцатеричном формате

 

  cout << setfill('0') << showbase << hex << internal;

 

  // Выводим двоичное представление числа с плавающей точкой

 

  assert(sizeof(int) == sizeof(float));

 

  ipi = reinterpret_cast<int&>(pi);

 

  cout << "pi bits=" << setw(10) << ipi << '\n';

 

  // Выводим адрес функции foo().

 

  cout << "&foo=" << setw(10) << reinterpret_cast<int>(&foo) << '\n';

 

}

·         static_cast< тип >( выражение )

Приводит выражение к типу при помощи стандартных или определенных пользователем преобразований; приведение работает так, как если бы была объявлена временная переменная тип tmp(выражение), значение которой было бы использовано вместо выражения приведения. Выражение будет именующим, если тип — ссылочный; в противном случае выражение будет значащим. Оператор static_cast<> не может быть использован для снятия cv-квалификаторов. Допустимы следующие преобразования:

Ÿ         тип может быть типом void — в этом случае значение выражения игнорируется;

Ÿ         именующее выражение базового класса может быть преобразовано в ссылку на производный класс, если существует стандартное преобразование указателя на производный класс в базовый класс. Базовый класс не обязан быть виртуальным. Если выражение на самом деле не является объектом типа, производного от типа, то поведение не определенно (для обеспечения безопасности таких преобразований служит оператор dynamic_cast<>);

Ÿ         указатель на базовый класс может быть преобразован в указатель на производный класс так, как это было описано для случая со ссылками;

Ÿ         указатель на член производного класса может быть преобразован в указатель на член базового класса, если существует обратное стандартное преобразование. Базовый класс при этом должен содержать данный член класса;

Ÿ         можно обращать стандартные арифметические преобразования — так, long можно преобразовать в short, а целые числа можно приводить к типам-перечислениям;

Ÿ         перечисления можно преобразовывать в другие перечисления;

Ÿ         указатель на void может быть преобразован в указатель на любой объект. Приведение указателя к типу void * (с сохранением cv-квалификаторов) и обратно сохраняет его начальное значение.

В листинге 3.6 приведены примеры использования оператора static_cast<>.

Листинг 3.6. Использование static_cast<>

#include <iostream>

#include <ostream>

 

class base {};

 

class derived : public base {};

 

class other : public base {};

 

enum color   { red, black };

 

enum logical { no, yes, maybe };

 

int main()

{

  base* b = new derived;

 

  static_cast<derived&>(*b); // OK

 

  static_cast<other*>(b);    // Неопределенное поведение

 

  derived* d = new derived;

 

  b = d;

 

  b = static_cast<base*>(d); // OK, но безусловной необходимости нет

 

  color c = static_cast<color>(yes);

 

  int i = 65;

 

  std::cout << static_cast<char>(i);

}

·         typeid( выражение )

Возвращает сведения о типе выражения, не вычисляя при этом само выражение. Результат оператора typeid, содержащий сведения о типе выражения — это именующее выражение типа const std::type_info (или же определенный реализацией тип-наследник type_info). В главе 13, в разделе, посвященном заголовочному файлу <typeinfo>, приведены дополнительные сведения об этом типе.

Если выражение является именующим выражением полиморфного типа (то есть классом хотя бы с одной виртуальной функцией), то сведения о типе выражения относятся к классу наиболее глубокого уровня иерархии наследования. Если выражение — разыменование нулевого указателя, то будет сгенерировано исключение bad_typeid.

Если выражение  не является именующим или тип его не относится к полиморф­ным, то информация о типе относится к статическому типу выражения.

·         typeid( тип )

Возвращает информацию о типе так, как это описано ранее. В листинге 3.7 приведены некоторые примеры использования оператора typeid.

Листинг 3.7. Использование оператора typeid

#include <iostream>

#include <ostream>

#include <typeinfo>

 

class base {

 

public:

 

  virtual ~base(  ) {}

};

 

class derived : public base {};

 

enum color   { red, black };

 

// Вывод программы зависит от реализации, но при этом он должен

// отражать информацию, приведенную в комментариях

 

int main()

{

 

  base* b = new derived;

 

  std::cout << typeid(*b).name(  ) << '\n';      // Производный класс

 

  std::cout << typeid(base).name(  ) << '\n';    // Базовый класс

 

  derived* d = new derived;

 

  std::cout << typeid(*d).name(  ) << '\n';      // Производный класс

 

  std::cout << typeid(derived).name(  ) << '\n'; // Производный класс

продолжение È

Листинг 3.7 (продолжение)

  std::cout << typeid(red).name(  ) << '\n';     // Перечисление Color

 

  std::cout << typeid(color).name(  ) << '\n';   // Перечисление Color

 

}

3.5.3. Унарные выражения

В унарных выражениях используются унарные префиксные операторы:

·         ++ именующее-выражение

Увеличивает именующее-выражение, которое должно относиться к арифметическому типу или типу-указателю, после чего возвращает новое значение как именующее выражение. Выражение ++x эквивалентно выражению x += 1, если только x не принадлежит к типу bool — тогда выражение ++x эквивалентно x = true. Применять оператор инкремента к объектам типа bool не рекомендуется.

·         -- именующее-выражение

Уменьшает именующее-выражение, которое должно относиться к арифметиче­скому типу или типу-указателю, но не может относиться к типу bool, после чего возвращает новое значение как именующее выражение. Выражение --x эквивалентно выражению x -= 1.

·         *указатель

Разыменовывает указатель и возвращает именующее выражение — объект, на который указывает указатель. Если указатель имеет тип T*, то выражение будет иметь тип T (cv-квалификаторы при этом сохраняются).

·         &именующее-выражение

&квалифицированное-имя

Возвращает адрес именующего-выражения или квалифицированного-имени. Если именующее-выражение относится к типу T или же квалифицированное-имя — статический член T, то результатом будет адрес объекта — значащее выражение, типом которого будет указатель на T. Если квалифицированное-имя — нестатический член класса C, то тип результата — указатель на член класса C.

Заметьте, что указатель на член класса создается при помощи одного лишь оператора & и квалифицированного имени. Даже в области видимости класса выражение &неквалифицированное-имя будет не указателем на член класса, а обычным указателем на объект.

Адрес битового поля получить таким образом нельзя. Чтобы получить адрес перегруженной функции, контекст должен ясно указывать на то, адрес какой именно перегрузки нужен. В листинге 3.8 приведены примеры использования оператора &.

Листинг 3.8. Использование оператора &

class demo

{

public:

 

  int x;

 

  static int y;

 

  int get_x() { return x; }

};

 

int demo::y = 10;

 

int add(int a, int b) { return a + b; }

 

double add(double a, double b) { return a + b; }

 

int main()

{

  demo d;

 

  int demo::*p;

 

  int (demo::*func)();

 

  int *i;

 

  int local = 42;

 

  int *ptr = &local;

 

  p = &demo::x;

 

  i = &demo::y;

 

  func = &demo::get_x;

 

  d.*p = *ptr;

 

  *i = (d.*func)();

 

  int (*adder)(int, int);

 

  adder = &add;

 

  d.*p = adder(42, *i);

 

  return d.y;

}

·         l     +выражение

Возвращает выражение, тип которого должен при этом быть арифметическим типом, типом-перечислением или типом-указателем. При определении типа результата могут иметь место продвижения типа, и результирующее выражение при этом будет значащим.

·         -выражение

Возвращает выражение  с обратным знаком (тип выражения должен при этом быть арифметическим типом или типом-перечислением). При определении типа результата могут иметь место продвижения типа, и результирующее выражение при этом будет значащим. Если тип результата — беззнаковый, то результат равен 2nвыражение, где n — это количество битов типа результата.

·         ~ выражение

compl выражение

Возвращает побитно инвертированное выражение, тип которого должен при этом быть интегральным типом или типом-перечислением. При определении типа результата могут иметь место продвижения типа, и результирующее выражение при этом будет значащим. Каждый нулевой бит выражения преобразуется в единичный, и наоборот.

В случае неоднозначности вида ~C( ), где C — имя класса, запись ~C( ) трактуется как вызов оператора дополнения, а не как вызов деструктора. Если
у класса нет перегруженного оператора дополнения и он не может быть неявно приведен к интегральному типу или типу-перечислению, то такая запись ошибочна. Чтобы явно сослаться на деструктор, используйте ссылку на член класса this->~C( ) или квалифицированное имя C::~C().

·         !выражение

not выражение

Возвращает логическое отрицание выражения после приведения его к типу bool. Результат — значащее выражение типа bool. Если выражение равно true, то результат — false, и наоборот.

·         sizeof выражение

sizeof тип

Возвращает размер типа или типа выражения в байтах (выражение при этом не вычисляется). По определению sizeof(char) == 1. Узнать таким образом размер битового поля, функции или неполного типа нельзя. Размер ссылки — это размер того типа, на который она ссылается.

Оператор sizeof, будучи применен к классу или объекту класса, всегда возвращает ненулевое значение. Размер подобъекта базового класса в пределах объекта производного класса может быть нулевым, поэтому компилятор не тратит память зря. Пример такой ситуации приведен в листинге 3.9, где показано, что размер производного класса равен размеру базового класса. Результирующее выражение будет значимым, а типом его будет size_t
(см. раздел главы 13, посвященный <cstdlib>).

Листинг 3.9. Использование оператора sizeof

#include <iostream>

#include <ostream>

class base {};

 

class derived : public base {};

 

int main()

{

   // выводимые значения зависят от реализации, но многие реализации

   // выведут указанные в комментариях значения

 

  using namespace std;

 

  cout << sizeof(base)  << '\n';      // Выводит 1

 

  cout << sizeof(derived)  << '\n';   // Выводит 1

 

  base b[3];

 

  cout << sizeof(b) << '\n';          // Выводит 3

 

  derived d[5];

 

  cout << sizeof(d) << '\n';          // Выводит 5

}

·         new тип

new тип ( необязательный-список-выражений )

new ( список-выражений ) тип

new ( список-выражений ) тип ( необязательный-список-выражений )

Служит для выделения и инициализации динамического объекта или массива объектов. В выражениях с оператором new сначала вызывается функция выделения памяти (operator new), а затем в выделенной памяти конструируется сам объект. Для класса можно задать отдельную функцию выделения памяти, перегрузив operator new как функцию-член класса. Если этого не сделано, то будет вызываться глобальная функция operator new (описания стандартных функций выделения памяти приведены в разделе главы 13, посвященном заголовочному файлу <new>).

Выражение состоит из следующих частей:

Ÿ         new

::new

Перед ключевым словом new можно использовать оператор разрешения глобальной области видимости — тогда в качестве функции выделения памяти будет использована глобальная функция operator new.

Ÿ          ( список-выражений )

Список выражений в скобках называется разместителем. Это необязательная часть, но если она присутствует, то в ней должно быть по крайней мере одно выражение. Если разместитель указан, то выражения, составляющие его, без какой-либо дополнительной обработки передаются в качестве аргументов функции выделения памяти.

Ÿ         тип

Тип объекта, под который надо выделить память. Записывается он так (можно использовать необязательные скобки):

спецификаторы-типа операторы-указатели размерности

Сведения о спецификаторах типа приведены в главе 2. Операторы-указатели необязательны; это символы * или &, обозначающие указатели
и ссылки, соответственно. Размерности массива также необязательны. Все размерности массива, кроме первой, обязаны быть константными целочисленными выражениями, заключенными в квадратные скобки. Первая размерность может задаваться любым целочисленным выражением.

При определении типа компилятор считывает наибольшую возможную последовательность объявителей, даже если при этом происходит синтаксическая ошибка. Если в типе  присутствуют скобки (например, при задании указателя на функцию) или же вам необходимо обеспечить использование вполне конкретного типа, заключите его в круглые скобки. Так, при вычислении выражения (new int[n])[2] в памяти будет создан массив из n целых чисел, после чего будет взят его элемент с индексом 2. Если же не использовать скобки, то при вычислении выражения new int[n][2] будет создан динамический двумерный массив int.

Ÿ          ( необязательный-список-выражений )

Это необязательный инициализатор, который подчиняется общим правилам инициализации (см. главу 2).

Если список выражений состоит из одного выражения и создается только один объект, то выражение будет начальным значением объекта.

Если инициализатор состоит из нескольких выражений, то тип объекта должен быть типом-классом, а список выражений будет передан подходящему конструктору, который будет выбран по обычным правилам разрешения перегрузки функций (см. главу 5).

При создании массива инициализатор указывать нельзя. Если базовый тип массива относится к POD-типам, то его элементы останутся неинициализированными; в противном случае каждый элемент массива будет инициализирован конструктором по умолчанию. В главе 6 приведено сравнительное описание POD- и не-POD-типов.

Функция выделения памяти (operator new) всегда вызывается по крайней мере с одним аргументом — он имеет тип size_t и задает количество байтов, которые необходимо выделить. Если при этом используется разместитель, то аргументы разместителя используются в качестве дополнительных аргументов при вызове функции выделения памяти и передаются сразу после размера памяти. Если функция выделения памяти не может произвести выделение, то обычно она генерирует исключение std::bad_alloc. Тем не менее, если указать std::nothrow в качестве аргумента разместителя, стандартная функция выделения памяти в случае ошибки вернет нулевой указатель, а не будет генерировать исключение bad_alloc.

Выделение памяти под массив отличается от выделения памяти под скалярное значение. Функцией выделения памяти в этом случае будет ope­rator new[]. Запрашиваемый объем при этом — количество элементов массива, умноженное на размер одного элемента. Реализация вольна запрашивать при этом дополнительную память для собственных целей — обычно это делается для того, чтобы хранить количество элементов массива. Объем дополнительной памяти зависит от реализации. Даже если массив имеет нулевой размер, возвращаемый указатель будет ненулевым. Выделенная память выравнивается по наиболее жесткому требованию, применимому ко всем типам (если говорить точнее, то функция выделения памяти должна возвращать указатели, выравнивание которых подходит для любого типа, а new-выражение просто их использует). Таким образом можно, например, создать массив char и использовать его для хранения произвольного объекта. В стандартных контейнерах это часто используется; подробные сведения об алгоритмах, работающих с неинициализированной памятью, приведены в разделе главы 13, посвященном заголовочному файлу <memory>.

Если при инициализации объекта было сгенерировано исключение, то память освобождается путем вызова соответствующей функции освобождения памяти (соответствующей аналогичному delete-выражению). Если использовался разместитель, то функцией освобождения памяти будет (если он вообще существует) оператор delete с такими же аргументами разместителя; в противном случае функция освобождения памяти вызываться не будет.

Вот несколько примеров использования new:

int n = 10;                 // Заметьте, что n – не const

 

new int                     // Указатель на неинициализированный int

 

new int(  )                 // Указатель на int, инициализированный 0

 

new int[n]                  // n неинициализированных int'ов

 

new (int*)                  // Указатель на неинициализированный указатель на int

 

new (int (*[n])(  ))         // n указателей на функции

 

  typedef int (*int_func)(  );

 

new int_func[n];            // n указателей на функции

 

new (int*[n][4])            // Массив из nx4 указателей на int

 

new complex<int>(42)        // Указатель на объект типа complex

 

new complex<int>[5]         // Пять объектов типа complex, инициализируемых по умолчанию

·         delete указатель

delete[] указатель

Это выражение служит для уничтожения и освобождения динамического объекта или массива объектов и имеет значение типа void. Освобождение памяти выполняется функцией освобождения памяти, которая может быть задана для определенного класса путем перегрузки функции operator delete. В простом выражении с delete функция освобождения памяти сначала будет искаться в классе, если базовый тип указателя — класс, а затем, если там она не будет найдена, — в глобальной области видимости. Для того чтобы поиск функции производился только в глобальной области видимости, используйте оператор разрешения глобальной области видимости (стандартные функции освобождения памяти описаны в разделе главы 13, посвященном заголовочному файлу <new>).

Чтобы освободить память из-под массива, используйте оператор delete[]. Чтобы освободить память, занимаемую скалярным значением, используйте оператор delete. Если вы перепутаете операторы, результат работы программы не определен. Заметьте, что в общем случае компилятор не может помочь вам избежать ошибки, поскольку указатель на скаляр неотличим от указателя на массив (правда, есть библиотеки, которые более терпимы к этой ошибке, нежели другие).

Выражение указатель будет вычислено только один раз. Если его тип — указатель на объект класса, то при вычислении скалярной формы выражения сначала будет вызван деструктор объекта, а при вычислении delete[]-выражения деструктор будет вызван для каждого элемента массива. Затем значение указателя приводится к типу void * и передается функции освобождения памяти. Если статический тип1 выражения не совпадает с динамическим типом объекта, то у статического класса должен быть виртуальный деструктор, иначе поведение программы не определено (см. главу 6).

В delete-выражении можно использовать указатели на константные объекты и нулевые указатели-значения; в последнем случае функция освобождения памяти ничего не делает.

3.5.4. Выражение приведения

Выражение приведения служит для выполнения явного преобразования типов. Выражение приведения — это пережиток, оставшийся от языка C. В языке C++ рекомендуется использовать операторы приведения типов, описанные в разделе 3.5.2. Приведения в стиле C, однако, еще применяются в силу лаконичности их записи.

·         l     Приведение в стиле C преобразует выражение к типу, используя одно или несколько преобразований, записываемых в форме шаблонов. Если тип — ссылка, то выражение — именующее, в противном случае выражение будет значащим. Попытка применить преобразования типов производится в соответствии с приведенным ниже порядком. Применяется первое из синтаксически допустимых преобразований, даже если семантика его неверна.

Ÿ         const_cast< тип >( выражение )

Ÿ         static_cast< тип >( выражение )

Ÿ         const_cast< тип >( static_cast< тип1 >( выражение ))

Ÿ         reinterpret_cast< тип >( выражение )

const_cast< тип >( reinterpret_cast< тип1 >( выражение ))

Тип тип1 тот же, что и тип, но его cv-квалификаторы изменены так, чтобы подходить к типу выражения. Таким образом, приведение типа в стиле C может совмещать const_cast со static_cast или reinterpret_cast. Такое приведение также может использоваться для приведения к базовому классу, который был бы недоступен в другом случае (информация об уровнях доступа приведена в главе 6). Это значит, что объекты производного класса можно приводить к недоступному базовому классу, указатели на члены производного класса можно приводить к указателю на член недоступного базового класса или же приводить объекты недоступного базового класса к объектам доступного производного класса.

3.5.5. Операторы выбора члена класса

Левый операнд оператора выбора члена класса — объект или указатель на объект, а правый операнд — указатель на член класса, который таким образом связывается с объектом. Результатом операнда будет член данных или функция-член класса. Полученная таким образом функция-член класса может быть использована только для вызова функции. В листинге 3.8 приведены примеры использования указателей на члены класса. Операторы выбора члена записываются следующим образом:

·         объект .* выражение

Привязывает выражение к объекту. Выражение здесь — указатель на член некоторого класса C, а тип объекта — C или класс, производный от C. Это выражение будет именующим, если объект имеет именующее значение, а выражение указывает на член данных; в противном случае выражение будет значащим. Тип результата вычисления выражения определяется типом выражения. Если выражение — нулевой указатель на член класса, то поведение программы не определено.

·         указатель ->* выражение

Привязывает выражение к объекту, на который указывает указатель. Выражение здесь — указатель на член некоторого класса C, а тип объекта — C или класс, производный от C. Это выражение будет именующим, если выражение указывает на член данных. Тип результата вычисления выражения определяется типом выражения. Если указатель — нулевой или выражение — нулевой указатель на член класса, то поведение программы не определено.

Если выражение указывает на виртуальную функцию, то используются обычные правила. Это значит, что вызывается функция, относящаяся к типу *указателя или объекта, стоящему на наиболее глубоком уровне иерархии наследования. В главе 6 виртуальные функции описаны более подробно.

3.5.6. Мультипликативные выражения

Мультипликативные выражения используются для умножения, деления и взятия остатка от деления. Мультипликативные операторы применимы только
к арифметическим типам или типам-перечислениям; при этом применяются обычные преобразования типов, а выражение будет значащим. Если результат слишком велик, то поведение программы не определено (за исключением беззнаковых типов, для которых все операции выполняются по модулю максимального значения целого числа — см. главу 1). Многие реализации C++ игнорируют переполнение целых чисел. Мультипликативные операторы записываются так:

·         выражение1 * выражение2

Выполняет умножение.

·         выражение1 / выражение2

Выполняет сложение. Если деление — нуль, то поведение программы не определено.

·         выражение1 % выражение2

Возвращает остаток от деления выражения1  на выражение2. Операнды должны относиться к целочисленным типам или типам-перечислениям. Если выражение2  равно 0, то поведение программы не определено; в противном случае результат таков, что (a/b)*b + a%b == a. Если оба операнда неотрицательны, то и результат неотрицателен; в противном случае знак результата зависит от реализации.

3.5.7. Аддитивные выражения

Аддитивные выражения используются для сложения и вычитания. Аддитивные операторы применимы к операндам арифметических типов, типов-перечислений или к указателям. При этом используются обычные преобразования, а полученное выражение будет значащим. Если результат аддитивного выражения слишком велик, то поведение программы не определено (за исключением беззнаковых типов, для которых все операции выполняются по модулю максимального значения целого числа — см. главу 1). Многие реализации C++ игнорируют переполнение целых типов. Аддитивные операторы записываются следующим образом:

·         выражение1 + выражение2

Так записывается сложение. Если один из операндов — указатель, то другой должен относиться к целочисленному типу или типу-перечислению. Результатом такого сложения будет указатель на тот же массив, сдвинутый на N позиций (N может быть положительным, отрицательным или нулевым), где N —целочисленный операнд. Результирующий указатель должен находиться в границах массива или же указывать на элемент, следующий за последним элементом массива; в противном случае поведение программы не определено. Надо отметить, что указатель на любой объект можно рассматривать как одноэлементный массив.

·         выражение1 - выражение2

Так записывается вычитание. Если оба операнда имеют арифметические типы или типы-перечисления, то к ним применяются обычные преобразования продвижения, а результатом будет разность операндов.

Если оба операнда — указатели, то они должны указывать на элементы одного и того же массива или же на элемент, следующий за последним элементом этого массива. Результат такого вычитания будет иметь тип ptrdiff_t (он объявлен в заголовочном файле <cstdlib>) и будет равен разности индексов двух объектов в массиве.

Если левый операнд — указатель, а правый операнд имеет целочисленный тип или относится к типу-перечислению, то результат совпадает с результатом вычисления выражения выражение1 выражение2.

3.5.8. Выражения сдвига

Выражение сдвига служит для сдвига битов левого операнда на позицию, определяемую правым операндом. Операнды должны иметь целочисленный тип или тип-перечисление; оба операнда продвигаются к целочисленным типам. Тип результата — это тип левого операнда после продвижения.

Результат операции сдвига не определен, если правый операнд меньше нуля или имеет значение, превосходящее число битов в левом операнде.

Операторы сдвига записываются так:

·         выражение1 << выражение2

Оператор производит сдвиг значения выражения1 на выражение2 битов влево. Освобождаемые биты заполняются нулями. Если выражение1 — беззнаковое, то результат идентичен результату умножения выражения1 на 2 в степени выражение2 (по модулю размера целого числа; в главе 1 приведены более по­дробные сведения об арифметике над беззнаковыми целыми числами).

·         выражение1 >> выражение2

Оператор производит сдвиг значения выражения1 на выражение2 битов вправо. Если выражение1 — беззнаковое или положительное знаковое, то освобождаемые биты заполняются нулями. Результат идентичен результату деления выражения1 на 2 в степени выражение2 (по модулю размера целого числа). Если выражение1 — отрицательное знаковое, то результат определяется реализацией.

В шаблонах классов потокового ввода-вывода стандартной библиотеки операторы сдвига перегружены. Как и в случае с любой перегруженной функцией, синтаксис (приоритет и ассоциативность) остается прежним; изменяется только поведение программы во время выполнения. В главе 9 приведены более по­дробные сведения.

3.5.9. Выражения сравнения

Выражения сравнения служат для сравнения двух значений в соответствии с опре­деленным для них порядком. Операторы сравнения имеют приоритет выше, чем операторы равенства, поэтому следующие выражения эквивалентны:

a < b == c > d

(a < b) == (c > d)

Выражение сравнения — значащее выражение, результат которого будет иметь тип bool. Операнды должны относиться к арифметическим типам, типам-перечислениям или типам-указателям. В случае с арифметическими типами и типами-перечислениями применяются обычные преобразования, после которых значения и сравниваются.

При сравнении указателей операнды должны относиться к одному и тому же типу (после обычных преобразований и отбрасывания cv-квалификаторов), или же один из них должен быть указателем на void или константой — нулевым указателем. Если типы указателей совместимы, то они сравниваются следующим образом (здесь L — левый операнд, а R — правый):

·         если L и R указывают на одну и ту же функцию, объект или элемент, следующий за конечным элементом массива, или же оба являются нулевыми указателями, то они считаются равными друг другу. Это значит, что выражения L <= R и L >= R  истинны, а L < R и L > R  ложны;

·         если L и R указывают на разные объекты (не являющиеся членами общего объекта или элементами одного и того же массива) или функции, или же если только один из них является нулевым указателем, то результат работы операторов сравнения зависит от реализации;

·         если L и R  указывают на члены данных (являясь обычными указателями, а не указателями на члены класса) в рамках одного и того же объекта (например, члены одного и того же объекта, элементы массивов, являющихся членами данных, и так далее, в том числе и рекурсивно) и члены объекта при этом не разделены меткой спецификатора уровня доступа, а сам объект не является объединением, то L > R, если член, на который указывает L, объявлен раньше члена, на который указывает R, и наоборот. Если члены разделены меткой спецификатора уровня доступа, то результат сравнения зависит от реализации;

·         если L и R  указывают на члены данных (являясь обычными указателями, а не указателями на члены класса) одного и того же объединения, то они считаются равными;

·         если L и R  указывают на элементы одного и того же массива либо же элемент, следующий за последним элементом этого массива, то больше будет указатель на элемент с большим индексом.

В листинге 3.10 приведены примеры сравнения указателей.

Листинг 3.10. Сравнение указателей

#include <iostream>

#include <ostream>

 

struct Demo {

 

  int x;

  int y;

};

 

 

 

union U {

 

  int a;

  double b;

  char c[5];

  Demo d;

};

 

int main(  )

{

  Demo demo[10];

 

  std::cout << std::boolalpha;

 

  // Везде будет выведено "true".

 

  std::cout << (&demo[0]   < &demo[2])   << '\n';

 

  std::cout << (&demo[0]   == demo)      << '\n';

 

  std::cout << (&demo[10]  > &demo[9])   << '\n';

 

  std::cout << (&demo[0].x < &demo[0].y) << '\n';

 

  U u;

 

  std::cout << (&u.d == static_cast<void*>(u.c)) << '\n';

 

  std::cout << (&u.a == static_cast<void*>(&u.b)) << '\n';

}

Операторы сравнения имеют следующий синтаксис:

·         выражение1 < выражение2

Возвращает true, если выражение1 меньше выражения2.

·         выражение1 > выражение2

Возвращает true, если выражение1 больше выражения2.

·         выражение1 <= выражение2

Возвращает true, если выражение1 меньше или равно выражению2.

·         выражение1 >= выражение2

Возвращает true, если выражение1 больше или равно выражению2.

3.5.10. Выражения равенства

Выражения равенства служат для проверки двух значений на равенство. Приоритет операторов равенства ниже, чем у операторов сравнения, поэтому следующие выражения эквивалентны:

a < b == c > d

 

(a < b) == (c > d)

Выражение равенства — значащее, а его результат имеет тип bool. Операнды долж­ны иметь арифметический тип, тип-перечисление или тип-указатель. В случае с арифметическими типами и типами-перечислениями применяются обычные преобразования, после которых значения и сравниваются.

ВНИМАНИЕ

Надо отметить, что сравнение на равенство значений с плавающей точкой редко дает ожидаемый результат. Возможно, вам, придется подумать о применении нечеткого сравнения, которое принимает во внимание неточность чисел с плавающей точкой.

При сравнении указателей операнды должны иметь один и тот же тип (после обычных преобразований). Указатели считаются равными, если выполнено хотя бы одно из перечисленных ниже условий, и не равными, если ни одно из этих усло­вий не выполняется:

·         оба указателя – нулевые;

·         оба указателя указывают на один и тот же объект;

·         оба указателя указывают на один и тот же элемент, следующий за последним элементом массива;

·         оба указателя указывают на одну и ту же функцию;

·         оба указателя на члены класса указывают на один и тот же член объекта самого нижнего уровня иерархии наследования;

·         оба указателя на члены класса указывают на члены данных одного и того же объединения.

Операторы сравнения на равенство записываются следующим образом:

·         выражение == выражение

Возвращает true, если операнды равны.

·         выражение != выражение

выражение not_eq выражение

Возвращает false, если операнды равны.

3.5.11. Побитовое умножение

Выражение с побитовым умножением служит для выполнения логического умножения битов своих операндов. В выражении с побитовым умножением допустимы операнды только целочисленных типов, к которым были применены обычные преобразования. Оператор побитового умножения записывается так:

·         выражение & выражение

выражение bitand выражение

Выполняет побитовое умножение операндов. Любой бит результата будет единицей тогда и только тогда, когда оба соответствующих бита операндов — единицы; в противном случае соответствующий бит результата будет нулем.

3.5.12. Исключающее сложение

Выражение с исключающим сложением служит для выполнения исключающего сложения битов своих операндов. В выражении с исключающим сложением допустимы операнды только целочисленных типов, к которым были применены обычные преобразования. Оператор исключающего сложения записывается так:

·         выражение ^ выражение

выражение xor выражение

Выполняет поразрядное исключающее сложение операндов. Любой бит результата будет единицей тогда и только тогда, когда оба соответствующих бита операндов не равны; в противном случае соответствующий бит результата будет нулем.

3.5.13. Побитовое сложение

Выражения с побитовым сложением служат для выполнения логического сложения битов своих операндов. В выражении с побитовым сложением допустимы операнды только целочисленных типов, к которым были применены обычные преобразования. Оператор побитового сложения записывается так:

·         выражение | выражение

выражение bitor выражение

Выполняет поразрядное логическое сложение операндов. Любой бит результата будет нулем тогда и только тогда, когда оба соответствующих бита операндов равны нулю; в противном случае соответствующий бит результата будет единицей.

3.5.14. Логическое умножение

При логическом умножении операнды неявно приводятся к типу bool. Результат также имеет тип bool: true, если оба операнда равны true, и false в любом другом случае. Оператор логического умножения работает в соответствии с принципом укороченного вычисления, поэтому второй операнд вычисляется только тогда, когда первый равен true. Оператор логического умножения имеет следующий синтаксис:

·         выражение && выражение

выражение and выражение

Выполняет логическое умножение операндов.

3.5.15. Логическое сложение

При логическом сложении операнды неявно приводятся к типу bool. Результат также имеет тип bool: false, если оба операнда равны false, и true в любом другом случае. Оператор логического сложения работает в соответствии с принципом укороченного вычисления, поэтому второй операнд вычисляется только тогда, когда первый равен false. Оператор логического сложения имеет следующий синтаксис:

·         выражение || выражение

выражение or выражение

Выполняет логическое сложение операндов.

3.5.16. Условное выражение

Условное выражение похоже на инструкцию if, записываемую внутри выражения:

условие ? true-выражение : false-выражение

Первый операнд приводится к типу bool. Если его значение — true, то вычисляется только второй операнд; если же его значение — false, то вычисляется только третий операнд. Результат условного выражения — результат вычисления второго или третьего операнда в зависимости от того, какой именно из них был вычислен.

Тип выражения зависит от типов второго и третьего операнда. Если оба операнда — именующие выражения одного и того же типа, то результат тоже будет именующим выражением этого типа. Если один из операндов — именующее выражение типа T, а другой — именующее выражение, которое может быть неявно приведено к типу ссылка на T, то результат — именующее выражение типа T. Преобразование типов не должно быть неоднозначным — это значит, что недопустима ситуация, когда второй операнд может быть неявно приведен к типу третьего и, наоборот, третий операнд может быть приведен к типу второго. В противном случае выражение будет именующим, а его тип определяется следующим образом:

·         если оба операнда имеют один и тот же тип, то это и будет тип результата;

·         если один из операндов — выражение со throw, то результат будет иметь тип другого операнда;

·         если тип одного операнда (назовем его S) может быть неявно приведен к типу другого операнда T, то типом результата будет T. Если оба типа взаимно могут быть приведены друг к другу неявным образом, то это ошибка.

3.5.17. Выражения присваивания

Эти выражения служат для присваивания правого операнда левому операнду. Помимо обычного присваивания x = y, существует несколько других операторов присваивания, каждый из которых является сокращением для арифметической операции и присваивания. Левый операнд выражения присваивания должен быть именующим выражением, которое можно изменять. Результат также будет именующим выражением — это будет левый операнд, после того как присваивание завершится.

Операторы присваивания записываются следующим образом:

·         именующее-выражение = выражение

Присваивает именующему-выражению значение выражения. Если левый операнд имеет тип-класс, то вызывается подходящий оператор присваивания (детали см.
в главе 6). Если левый операнд не относится к типу-классу, а типы операндов различны, то правый операнд преобразуется к типу левого операнда.

·         именующее-выражение += выражение

именующее-выражение -= выражение

Оператор присваивания вида x op= y — это сокращение для записи x = x op y, за исключением того, что x вычисляется только один раз. Типом x должен быть арифметический тип или тип-указатель. Для оператора op используются обычные преобразования, а результат преобразуется к типу x.

·         именующее-выражение *= выражение

именующее-выражение /= выражение

именующее-выражение %= выражение

именующее-выражение <<= выражение

именующее-выражение >>= выражение

именующее-выражение &= выражение

именующее-выражение and_eq выражение

именующее-выражение ^= выражение

именующее-выражение xor_eq выражение

именующее-выражение |= выражение

именующее-выражение or_eq выражение

В данном случае оператор присваивания вида x op= y — это сокращение для записи x = x op y, за исключением того, что x вычисляется только один раз. Типом x должен быть арифметический тип (в отличие от аддитивных операторов присваивания, которые применимы к указателям, поскольку аддитивные операторы допускают использование операндов-указателей). Для оператора op используются обычные преобразования, а результат преобразуется к типу x.

·         throw выражение

throw

Генерирует исключение. Первая форма записи использует выражение в качестве генерируемого исключения. Вторая запись повторно генерирует текущее исключение. Если в программе используется вторая форма записи, а исключение на данный момент не сгенерировано, то оператор throw вызовет функцию terminate() (более подробно она описана в разделе главы 13, посвященном заголовочному файлу <exception>).

В качестве исключения можно использовать любое выражение, но обычно принято использовать объект класса exception или производного от него класса, в особенности от классов-наследников одного из стандартных классов исключений (см. раздел <stdexcept> главы 13).

Выражение со throw создает и инициализирует временный объект-исключение, передаваемый обработчику исключений, который может скопировать объект-исключение в свой блок catch. Когда обработчик завершается, объект уничтожается. Реализация может обойтись без лишнего копирования и инициализировать объект в блок catch непосредственно выражением. Даже если объект при этом никогда не будет скопирован, у класса должен быть конструктор копирования.

В разделе главы 4, посвященном инструкции try, приведено описание порядка обработки исключений, а также примеры использования выражений со throw. Также см. главу 5, где приведены сведения о throw-спецификациях в объявлениях функций.

3.5.18. Выражения с запятыми

Запятые в выражениях служат для сериализации вычисления выражений. Оператор-запятая вычисляет свой левый операнд, отбрасывает результат, а затем вычисляет правый операнд. Результат — значение правого операнда, а тип выражения — тип его правого операнда. Если правый операнд является именующим выражением, то и все выражение будет именующим; в противном случае выражение будет значащим. Обычно левый операнд вычисляется для получения побочного действия, такого, например, как присваивание или вызов функции.

Оператор-запятую можно перегрузить, при этом его «серийное» свойство будет утеряно. Будут вычислены оба операнда, а затем будет вызвана функция-оператор. Оператор-запятая редко нуждается в перегрузке.

Оператор-запятая записывается так:

·         выражение1, выражение2

Вычисляет сначала выражение1, потом выражение2, после чего возвращает значение выражения2. Чтобы использовать выражение с запятой в качестве аргумента функции или в другом контексте, где запятая имеет особое значение, заключите его в круглые скобки:

sin((angle = 0.0, angle + 1.0));

Книги
Компьютеры
Экономика
Психология
Популярная психология
Юридическая
Периодика
Медицина
Оздоровление
Образование
Воспитание
Домоводство
Гуманитарная
Книга
О книге
Содержание
Отрывок
    Каталог | Издательство | Отдел сбыта | Обратная связь | Заказ книг | uchebnik@piter.com

Авторские права охраняются.
Воспроизведение материалов или их частей в любом виде без письменного разрешения запрещено!
c 1997-2007, Издательский дом «Питер»