результат 0. Правый операнд преобразуется к типу левого операнда,
прежде выполнения присваивания.
Указатель типа T* const можно присваивать указателю типа T*, но
обратное присваивание считается незаконным ($$R.7.1.6). Объекты
типа const T или volatile T можно присваивать по адресу типа T или
volatile T (см. так же $$R.8.4).
Если левый операнд имеет тип указателя на член, правый операнд
должен быть типа указатель на член или выражением-константой,
дающим результат 0; перед присваиванием правый операнд преобразуется
к типу левого операнда.
Присваивание объектам класса X ($$R.9) задается функцией
X::operator=() ($$R.13.4.3). Если пользователь не определил
свою функцию X::operator=(), для присваивания используется
стандартный вариант ($$R.12.8). Отсюда следует, что объект класса,
который является прямым или непрямым производным от X, и
однозначно описан как производный в части public ($$R.4.6),
можно присвоить объекту X.
Указатель на член класса B можно присваивать указателю на член
того же типа класса D при условии, что D является прямым или
непрямым производным класса B, и однозначно описан как
производный в части public ($$R.10.1.1).
Присваивание объекту типа "ссылка на T" сводится к присваиванию
объекту типа T, который обозначается ссылкой.
Выполнение выражение вида E1 op= E2 эквивалентно выполнению
E1 = E1 op (E2), однако E1 вычисляется лишь один раз. В операциях
+= и -= левый операнд может быть указателем, в этом случае правый
(целочисленный) операнд преобразуется так, как объяснялось в $$R.5.7.
Все правые операнды и все левые операнды, не являющиеся ссылками,
должны быть арифметического типа.
Для объектов класса присваивание в общем случае не совпадает с
инициализацией ($$R.8.4, $$R.12.1, $$R.12.6, $$R.12.8).
Операции запятая выполняются слева направо.
выражение:
выражение-присваивания
выражение, выражение-присваивания
Пара выражений, разделенных запятой, вычисляется слева направо и
значение левого выражения уничтожается. Все побочные эффекты вычисления
левого выражения могут возникать до вычисления правого выражения.
Тип и значение результата совпадают с типом и значением правого
выражения. Результат является адресом, если таковым является
правое выражение.
В контекстах, где запятая имеет специальное значение, скажем
в списке фактических параметров функции ($$R.5.2.2) или в списке
инициализаторов ($$R.8.4), описанная здесь операция запятая
может появляться только в скобках, например, вызов функции
f(a, (t=3,t+2), c);
содержит три параметра, причем второй имеет значение 5.
В нескольких местах описания С++ требуются выражения, которые
дают в результате целочисленную константу, например: в задании границ
массива ($$R.8.2.4), в выражениях case ($$R.6.4.2),
для задания длины битового поля ($$R.9.6) и как инициализирующее
значение элемента перечисления ($$R.7.2).
выражение-константа:
выражение-условия
В конструкции выражение-константа могут участвовать: литералы
($$R.2.5), элементы перечисления, значения целочисленного типа со
спецификацией const, инициализированные выражением-константой
($$R.8.4) и выражения sizeof. Константы с плавающей точкой ($$R.2.5.3)
должны быть приведены к целочисленному типу. Допустимы только
преобразования типа к целочисленному типу. В частности не допустимы
функции, объекты классов, указатели и ссылки, если не считать их
использования в sizeof. Операция запятая и операция присваивания
не допустимы в выражении-константе.
Все операторы, за исключением оговоренных случаев, выполняются
один за другим.
оператор:
помеченный-оператор
оператор-выражение
составной-оператор
выбирающий-оператор
оператор-цикла
оператор-перехода
оператор-описания
Оператор можно снабдить меткой.
помеченный-оператор:
идентификатор : оператор
case выражение-константа : оператор
default : оператор
Использование идентификатора в качестве метки является ее определением.
Идентификатор метки может использоваться помимо этого только в качестве
указания перехода в операторе goto. Областью видимости метки является
функция, в которой она появилась. Метки нельзя повторно описывать
в пределах одной функции. Нельзя использовать метку в операторе goto
до ее определения. Метки имеют свое пространство именования и
они не вступают в коллизию с другими идентификаторами.
Метки в case или default могут встречаться только в операторе
переключателя.
Чаще всего операторами бывают выражения; в этом случае оператор
имеет такой вид:
оператор-выражение:
выражение opt ;
Обычно операторы-выражения являются присваиваниями или вызовами
функций. Все побочные эффекты выполнения оператора-выражения
происходят до выполнения следующего оператора. Оператор-выражение с
отсутствующим выражением называется пустым оператором. Он
может пригодиться, если необходимо поставить метку перед самым концом
составного оператора ({) или для задания пустого тела оператора
цикла while ($$R.6.5.1).
Для тех случаев, когда вместо одного оператора нужно использовать
несколько, предусмотрен составной оператор (иногда его называют
"блок").
составной-оператор:
{ список-операторов opt }
список-операторов:
оператор
список-операторов оператор
Отметим, что описание считается оператором ($$R.6.7).
Выбирающие операторы выбирают одну из нескольких структур управления.
выбирающий-оператор:
if ( выражение ) оператор
if ( выражение ) оператор else оператор
switch ( выражение ) оператор
Оператор в выбирающем-операторе не может быть описанием.
Выражение должно быть арифметического типа, или типа указателя, или
типа класс, для которого существует однозначное преобразование
в арифметический тип или тип указателя ($$R.12.3).
Вычисляется выражение, и если оно имеет отличный от нуля результат,
выполняется первый вложенный оператор. Если использована конструкция
else и выражение дает результат 0, выполняется второй вложенный
оператор. Неоднозначность в случае нескольких конструкциями else
разрешается путем отнесения else к последнему встретившемуся if,
для которого не было else.
Оператор переключателя вызывает передачу управления на один из
нескольких операторов в зависимости от значения выражения.
Выражение должно быть целочисленного типа или типа класса, для
которого существует однозначное преобразование к целочисленному
типу ($$R.12.3). Выполняются стандартные целочисленные преобразования.
Любой из операторов переключателя можно пометить одним или несколькими
префиксами, имеющими вид:
case выражение-константа :
Здесь выражение-константа ($$R.5.19) приводится к преобразованному
типу выражения переключателя. Никакие две константы из case одного
переключателя не должны иметь одинаковое значение.
В переключателе может быть только один префикс вида
default:
Операторы переключателя могут быть вложенными, тогда метки из
case или default относятся к самому первому переключателю,
объемлющему их.
При выполнении оператора переключателя вычисляется выражение,
и его значение сравнивается с каждой из констант вариантов (case).
Если одна из этих констант равна значению выражения, то управление
передается в оператор, идущий за этой константой. Если ни одна из
констант не совпала со значением выражения, но есть префикс
default, то управление передается на оператор с этим префиксом.
Если префикса default нет, и совпадения не было, то не выполняется
ни один из операторов переключателя.
Если операторы, выполняемые в результате выбора, не приводят
к каким-либо передачам управления, то программа продолжает выполняться
"по меткам case и default" беспрепятственно. Выход из переключателя
возможен с помощью оператора break (см. $$R.6.6.1).
Обычно оператор, с которым имеет дело переключатель, бывает
составным. Описания могут появиться в операторах переключателя.
Однако переход ниже описания, в котором была явная или неявная
инициализация, считается незаконным, если только описание не
находится во внутреннем блоке, который обходится (т.е. полностью
обходится при передаче управления, $$R.6.7). Отсюда следует,
что описание с явной или неявной инициализацией должно содержаться
во внутреннем блоке.
Эти операторы задают виды цикла.
оператор-цикла:
while ( выражение ) оператор
do оператор while (выражение)
for ( оператор-иниц выражение opt ; выражение opt ) оператор
оператор-иниц:
оператор-выражение
оператор-описание
Обратите внимание, что конструкция оператор-иниц кончается точкой с
запятой.
Оператор в операторе-цикла не должен быть описанием.
В операторе while вложенный оператор выполняется до тех пор,
пока значение выражения не станет равным нулю. Проверка происходит
перед каждым выполнением оператора.
Выражение должно быть арифметического типа, или типа указателя, или
типа класс, для которого существует однозначное преобразование в
арифметический тип или тип указателя ($$R.12.3).
В операторе do вложенный оператор выполняется до тех пор,
пока значение выражения не станет равным нулю. Проверка происходит
после каждого выполнения оператора.
Выражение должно быть арифметического типа, или типа указателя,
или типа класс, для которого существует однозначное преобразование
в арифметический тип или тип указателя ($$R.12.3).
Оператор for
for (оператор-иниц выражение-1 opt ; выражение-2 opt ) оператор
эквивалентен конструкции
оператор-иниц
while (выражение-1) {
оператор
выражение-2 ;
}
за исключением того факта, что оператор continue в операторе for
вызовет выполнение выражение-2 перед тем& как начать повторное
вычисление выражения-1. Таким образом, первый оператор задает
инициализацию для цикла, первое выражение производит проверку,
выполняемую перед каждым шагом цикла, так что цикл завершается, когда
выражение становится нулем, а второе выражение обычно задает
приращение, и оно добавляется после каждого шага цикла. Первое
выражение должно иметь арифметический тип, или тип указателя, или
тип класса, для которого существует однозначное преобразование
к арифметическому типу или типу указателя ($$R.12.3).
Могут быть опущены одно или оба выражения. Если отсутствует
выражение-1, то эквивалентный цикл с while имеет условие while (1).
Если оператор-иниц является описанием, область видимости имен,
описанных в нем, простирается до конца блока, закрывающего оператор
for.
Операторы перехода делают безусловную передачу управления.
оператор-перехода:
break ;
continue ;
return выражение opt ;
goto идентификатор ;
По выходе из области видимости (каким бы образом это не произошло)
вызываются деструкторы ($$R.12.4) для всех объектов классов,
построенных в этой области, которые еще не были уничтожены. Это
относится как к явно описанным объектам, так и ко временным объектам
($$R.12.2).
Оператор break может встретиться только в операторе цикла или
переключателе, он приводит к окончанию ближайшего из объемлющих
его операторов цикла или переключателей. Управление передается на
оператор, следующий непосредственно за заканчиваемым, если такой есть.
Оператор continue может встретиться только в операторе цикла и
приводит к передаче управления в заголовок ближайшего
из объемлющих операторов цикла, т.е. в конец цикла. Более точно
можно сказать, что в каждом из операторов:
while (foo) { do { for (;;) {
// ... // ... // ...
contin: ; contin: ; contin: ;
} } while (foo); }
оператор continue, не относящийся ко внешним операторам цикла,
эквивалентен оператору goto contin.
Возврат из функции в обратившуюся к ней функцию происходит с помощью
оператора return.
Оператор return без выражения можно использовать только в
функциях, которые не возвращают значение, т.е. в функциях,
возвращающих значение типа void, или в конструкторах ($$R.12.1)
и деструкторах ($$R.12.4). Оператор return с выражением можно
использовать только в функциях, которые возвращают значение. Значение
выражения передается в ту функцию,которая вызвала данную функцию. Если
нужно, значение преобразуется к типу функции, в которой выполняется
return, по тем же правилам как при инициализации. Это может привести
к вызову конструктора или копированию временных объектов ($$R.12.2).
Выход из функции по концу эквивалентен возврату без выдаваемого
значения, что является незаконным для функции, возвращающей
значение.
Оператор goto безусловно передает управление на оператор,
помеченный идентификатором. Идентификатор должен быть меткой
($$R.6.1), находящейся в текущей функции.
Оператор описания заводит в блоке новый идентификатор и имеет
вид:
оператор-описания:
описание
Если идентификатор, введенный с помощью описания, уже был ранее
описан во внешнем блоке, внешнее описание становится скрытым до
конца блока, после чего оно опять вступает в силу.
Все инициализации автоматических (auto) и регистровых (register)
переменных производятся каждый раз, когда выполняется
оператор-описание. Уничтожение локальных переменных, описанных в
блоке, происходит при выходе из блока ($$R.6.6). Уничтожение
автоматических переменных, определенных в цикле, происходит
на каждом шаге цикла. Например, переменная Index j создается и
уничтожается каждый раз в течение цикла по i:
for (int i = 0; i<100; i++)
for (Index j = 0; j<100; j++) {
// ...
}
Выход из цикла или из блока или переход, минуя инициализацию
автоматических переменных, приводит к уничтожению автоматических
переменных, описанных в точке, откуда происходит переход, но не
в точке, куда происходит переход.
Переход в блок возможен при условии, что он не приводит к
пропуску инициализации. Считается незаконным переход, обходящий
описание с явной или неявной инициализацией, кроме случаев, когда
оно находится во внутреннем блоке, который пропускается (т.е. в него
никогда не попадает управление) или переход происходит из той точки,
где уже была инициализация переменной. Например,
void f()
{
// ...
goto lx; //ошибка: переход, минуя инициализацию
// ...
ly:
X a = 1;
// ...
lx:
goto ly; // нормально, за переходом будет вызов
// деструктора для `a'
}
Автоматическая переменная, которая была создана при некотором
условии, уничтожается при выполнении этого условия, и не может
быть доступна вне проверки этого условия. Например,
if (i)
for (int j = 0; j<100; j++) {
// ...
}
if (j!=100) // ошибка: обращение вне условия
// ...
;
Инициализация локального объекта с классом памяти static ($$R.7.1.1)
производится прежде, чем управление пройдет через область его
описания. Если статическая переменная инициализируется выражением,
которое не является выражением-константой, то перед первым входом
в блок происходит стандартная инициализация нулем, приведенным
к нужному типу ($$R.8.4).
Деструктор для локального статического объекта будет вызываться
в том и только в том случае, если переменная была создана с помощью
конструктора. Деструктор должен вызываться сразу перед вызовом или
как составная часть вызова функций, заданных в atexit() ($$R.3.4).
Существует неоднозначность в грамматике языка, касающаяся
оператора-выражения и описания, а именно, оператор-выражение,
содержащий как самое левое подвыражение явное преобразование типа,
заданное в функциональном стиле ($$R.5.2.3), может быть не отличим от
описания, в котором первый описатель начинается со (. В таких случаях
оператор считается описанием.
Для разрешения неоднозначности следует исследовать весь оператор,
чтобы определить является он оператором-выражением или описанием.
Так устраняется неоднозначность во многих случаях. Например, пусть
T - имя-простого-типа ($$R.7.1.6), тогда имеем
T(a)->m = 7; // оператор-выражение
T(a)++; // оператор-выражение
T(a,5)<<c; // оператор-выражение
T(*e)(int); // описание
T(f)[]; // описание
T(g) = {1, 2 }; // описание
T(*d)(double(3)); // описание
Остальные случаи представляют описания. Например,
T(a); // описание
T(*b)(); // описание
T(c)=7; // описание
T(d),e,f=3; // описание
T(g)(h,2); // описание
Неоднозначность здесь чисто синтаксическая, т.е. на ее
разрешение не влияет тот факт, является ли имя именем-типа или нет.
Есть другой вид коллизии между оператором-выражением и описанием,
который разрешается требованием, чтобы описание функции в блоке
($$R.6.3) сопровождалось именем-типа, например:
void g()
{
int f(); // описание
int a; // описание
f(); // оператор-выражение
a; // оператор-выражение
}
Описания используются для интерпретации каждого из идентификаторов;
необязательно, чтобы они сопровождались выделением памяти,
сопоставляемой с идентификатором. Описания имеют вид
описания:
спецификации-описания opt список-описателей opt ;
описание-asm
определение-функции
спецификация-связи
Описатели в списке-описателей ($$R.8) содержат описываемые
идентификаторы. Конструкция спецификации-описания может отсутствовать
только в определении функций ($$R.8.3) или в описании функций.
Список-описателей может быть пустым, только при описании класса ($$R.9)
или перечисления ($$R.7.2), т.е. когда спецификация-описания есть
спецификация-класса или спецификация-перечисления.
Конструкция описание-asm объясняется в $$R.7.3, а спецификация-связи
в $$R.7.4. Описание происходит в определенной области видимости
($$R.3.2), правила области видимости приводятся в $$R.10.4.
В описании можно использовать следующие спецификации:
спецификация-описания:
спецификация-класса-памяти
спецификация-типа
спецификация-fct
спецификация-шаблона-типа
friend
typedef
спецификации-описания:
спецификации-описания opt спецификация-описания
Самая длинная последовательность конструкций спецификация-описания,
которая, возможно, является именем типа, образует в описании конструкцию
спецификации-описания. Последовательность должна быть согласованной,
что объясняется ниже. Например,
typedef char* Pc;
static Pc; // ошибка: нет имени
Здесь описание static Pc является незаконным, поскольку не указано
никакого имени статической переменной типа Pc. Чтобы иметь
переменную типа int с именем Pc, необходимо задать
спецификацию-типа int, чтобы показать, что (пере)определяется
имя Pc из typedef, а не просто Pc является одним из элементов
последовательности конструкций спецификация-описания, например,
void f(const Pc); // void f(char* const)
void g(const int Pc); // void g(const int)
Укажем, что поскольку signed, unsigned, long и short по
умолчанию трактуются как int, конструкция имя-typedef, которая
появляется после одной из перечисленных спецификаций типа,
должна задавать (пере)определяемое имя, например,
void h(unsigned Pc); // void h(unsigned int)
void k(unsigned int Pc); // void k(unsigned int)
Спецификации класса памяти могут быть такие:
спецификация-класса-памяти:
auto
register
static
extern
Спецификации auto и register могут применяться только для
имен объектов, которые описаны в блоке ($$R.6.3), или для формальных
параметров ($$R.8.3). Почти всегда спецификация auto избыточна и
используется не часто, так, auto используется, чтобы явно отделить
оператор-описание от оператора-выражения ($$R.6.2).
Описание register является описанием auto, которое подсказывает
транслятору, что описываемые переменные будут использоваться
достаточно интенсивно. Подсказка может быть проигнорирована, и во
многих реализациях она игнорируется в том случае, когда берется
адрес переменной.
Описание объекта считается определением, если только оно не
содержит спецификации extern и инициализации ($$R.3.1).
Определение приводит к выделению памяти соответствующего
размера и выполнению соответствующей инициализации ($$R.8.4).
Спецификации static и extern могут применяться только к именам
объектов или функций или к анонимным объединениям. Внутри блока
недопустимы описания функций со спецификацией static или
формальных параметров со спецификацией static или extern.
Статические члены класса описываются в $$R.9.4. Спецификация
extern недопустима для членов класса.
Имя со спецификацией static подлежит внутреннему связыванию.
Объекты, описанные как const, подлежат внутреннему связыванию,
если только они не были описаны с внешней связью. Имя со
спецификацией extern подлежит внешнему связыванию, если только ранее
оно не было описано с внутренней связью. Имя с файловой областью
видимости и без спецификации-класса-памяти подлежит внешнему
связыванию, если только ранее оно не было описано с внутренней
связью или со спецификацией const. В смысле связывания для функций,
не являющихся членами, спецификация inline эквивалентна static
($$R.3.3). Для одного имени все его спецификации, определяющие
связывание, должны быть согласованы. Например,
static char* f(); // f() имеет внутреннее связывание
char* f() // f() все еще внутреннее
{ /* ... */ }
char* g(); // g() имеет внешнее связывание
static char* g() // ошибка: противоречие в связывании
{ /* ... */ }
static int a; // `a' имеет внутреннее связывание
int a; // ошибка: второе определение
static int b; // `b' имеет внутреннее связывание
extern int b; // `b' все еще внутреннее
int c; // `c' имеет внешнее связывание
static int c; // ошибка: противоречие в связывании
extern int d; // `d' имеет внешнее связывание
static int d; // ошибка: противоречие в связывании
Имя неопределенного класса можно использовать в описании
extern. Однако, такое описание нельзя использовать прежде, чем
класс будет определен, например,
struct S;
extern S a;
extern S f();
extern void g(S);
void h()
{
g(a); // ошибка: S неопределено
f(); // ошибка: S неопределено
}
Некоторые спецификации можно использовать только в описании функций.
спецификация-fct:
inline
virtual
Спецификация inline подсказывает транслятору, что необходимо
произвести подстановку тела функции вместо обычной реализации
вызова функции. Подсказка может игнорироваться. В случае функций,
не являющихся членами, спецификация inline дополнительно устанавливает
для функции внутреннее связывание ($$R.3.3). Функция ($$R.5.2.2,
$$R.8.2.5), определенная в описании класса, имеет по умолчанию
спецификацию inline.
Функция-член со спецификацией inline должна иметь в точности
такое же определение в каждой единице трансляции, где она появляется.
Функцию-член не обязательно явно описывать со спецификацией
inline при описании класса, чтобы она трактовалась как подстановка.
Если спецификации inline не было, связывание будет внешним,
если только определение со спецификацией inline не появится перед
первым вызовом функции.
class X {
public:
int f();
inline int g(); // X::g() имеет внутреннее связывание
int h();
};
void k(X* p)
{
int i = p->f(); // теперь X::f() внешнее связывание
int j = p->g();
// ...
}
inline int X::f() // ошибка: вызов до определения
// как inline
{
// ...
}
inline int X::g()
{
// ...
}
inline int X::h() // теперь X::h() имеет внутреннее связывание
{
// ...
}
Спецификация virtual может использоваться только в описаниях
нестатических функций-членов при описании класса (см. $$R.10.2).
Описания со спецификацией typedef задают идентификаторы, которые
позднее могут использоваться для обозначения основных или
производных типов. Спецификация typedef недопустима в определении-функции
($$R.8.3).
имя-typedef:
идентификатор
В пределах области видимости ($$R.3.2) описания typedef любой
идентификатор, появляющийся в части любого из описателей,
становится синтаксически эквивалентным служебному слову и обозначает тип,
связанный с данным идентификатором, как описано в $$R.8. Таким образом,
имя-typedef является синонимом другого типа. В отличие от описания
класса ($$R.9.1) имя-typedef не добавляет нового типа. Например,
после описания
typedef int MILES, *KLICKSP;
конструкции
MILES distance;
extern KLICKSP metricp;
являются законными описаниями, тип distance есть int, а у metricp
тип "указатель на int".
С помощью typedef можно переопределить имя так, чтобы оно опять
обозначало тип, на который уже ссылалось, причем даже в той области
видимости, в которой тип был первоначально описан, например,
typedef struct s { /* ... */ } s;
typedef int I;
typedef int I;
typedef I I;
Безымянный класс, который определяется в typedef, получает в
качестве своего имени имя, использованное в typedef, например,
typedef struct { /* .... */ } S; // имя структуры стало S
С помощью описания typedef нельзя переопределить имя типа,
описанного в этой же области видимости, так, чтобы оно обозначало
другой тип, например,
class complex { /* ... */ };
typedef int complex; // ошибка: переопределение
Аналогично, нельзя описывать класс с именем типа, описанного
в этой же области видимости, так, чтобы он обозначал другой
тип, например,
typedef int complex;
class complex { /* ... */ }; // ошибка: переопределение
Имя-typedef, которое обозначает класс, является именем-класса
($$R.9.1). Синоним нельзя использовать после следующих префиксов:
class, struct и union, а также в именах конструкторов и
деструкторов в описании самого класса, например,
struct S {
S();
~S();
};
typedef struct S T;
S a = T(); // нормально
struct T* p; // ошибка
Спецификация шаблона типа используется для задания семейства типов
или функций (см. $$R.14).
Спецификация friend используется для задания доступа к членам класса
прежде выполнения присваивания.
Указатель типа T* const можно присваивать указателю типа T*, но
обратное присваивание считается незаконным ($$R.7.1.6). Объекты
типа const T или volatile T можно присваивать по адресу типа T или
volatile T (см. так же $$R.8.4).
Если левый операнд имеет тип указателя на член, правый операнд
должен быть типа указатель на член или выражением-константой,
дающим результат 0; перед присваиванием правый операнд преобразуется
к типу левого операнда.
Присваивание объектам класса X ($$R.9) задается функцией
X::operator=() ($$R.13.4.3). Если пользователь не определил
свою функцию X::operator=(), для присваивания используется
стандартный вариант ($$R.12.8). Отсюда следует, что объект класса,
который является прямым или непрямым производным от X, и
однозначно описан как производный в части public ($$R.4.6),
можно присвоить объекту X.
Указатель на член класса B можно присваивать указателю на член
того же типа класса D при условии, что D является прямым или
непрямым производным класса B, и однозначно описан как
производный в части public ($$R.10.1.1).
Присваивание объекту типа "ссылка на T" сводится к присваиванию
объекту типа T, который обозначается ссылкой.
Выполнение выражение вида E1 op= E2 эквивалентно выполнению
E1 = E1 op (E2), однако E1 вычисляется лишь один раз. В операциях
+= и -= левый операнд может быть указателем, в этом случае правый
(целочисленный) операнд преобразуется так, как объяснялось в $$R.5.7.
Все правые операнды и все левые операнды, не являющиеся ссылками,
должны быть арифметического типа.
Для объектов класса присваивание в общем случае не совпадает с
инициализацией ($$R.8.4, $$R.12.1, $$R.12.6, $$R.12.8).
Операции запятая выполняются слева направо.
выражение:
выражение-присваивания
выражение, выражение-присваивания
Пара выражений, разделенных запятой, вычисляется слева направо и
значение левого выражения уничтожается. Все побочные эффекты вычисления
левого выражения могут возникать до вычисления правого выражения.
Тип и значение результата совпадают с типом и значением правого
выражения. Результат является адресом, если таковым является
правое выражение.
В контекстах, где запятая имеет специальное значение, скажем
в списке фактических параметров функции ($$R.5.2.2) или в списке
инициализаторов ($$R.8.4), описанная здесь операция запятая
может появляться только в скобках, например, вызов функции
f(a, (t=3,t+2), c);
содержит три параметра, причем второй имеет значение 5.
В нескольких местах описания С++ требуются выражения, которые
дают в результате целочисленную константу, например: в задании границ
массива ($$R.8.2.4), в выражениях case ($$R.6.4.2),
для задания длины битового поля ($$R.9.6) и как инициализирующее
значение элемента перечисления ($$R.7.2).
выражение-константа:
выражение-условия
В конструкции выражение-константа могут участвовать: литералы
($$R.2.5), элементы перечисления, значения целочисленного типа со
спецификацией const, инициализированные выражением-константой
($$R.8.4) и выражения sizeof. Константы с плавающей точкой ($$R.2.5.3)
должны быть приведены к целочисленному типу. Допустимы только
преобразования типа к целочисленному типу. В частности не допустимы
функции, объекты классов, указатели и ссылки, если не считать их
использования в sizeof. Операция запятая и операция присваивания
не допустимы в выражении-константе.
Все операторы, за исключением оговоренных случаев, выполняются
один за другим.
оператор:
помеченный-оператор
оператор-выражение
составной-оператор
выбирающий-оператор
оператор-цикла
оператор-перехода
оператор-описания
Оператор можно снабдить меткой.
помеченный-оператор:
идентификатор : оператор
case выражение-константа : оператор
default : оператор
Использование идентификатора в качестве метки является ее определением.
Идентификатор метки может использоваться помимо этого только в качестве
указания перехода в операторе goto. Областью видимости метки является
функция, в которой она появилась. Метки нельзя повторно описывать
в пределах одной функции. Нельзя использовать метку в операторе goto
до ее определения. Метки имеют свое пространство именования и
они не вступают в коллизию с другими идентификаторами.
Метки в case или default могут встречаться только в операторе
переключателя.
Чаще всего операторами бывают выражения; в этом случае оператор
имеет такой вид:
оператор-выражение:
выражение opt ;
Обычно операторы-выражения являются присваиваниями или вызовами
функций. Все побочные эффекты выполнения оператора-выражения
происходят до выполнения следующего оператора. Оператор-выражение с
отсутствующим выражением называется пустым оператором. Он
может пригодиться, если необходимо поставить метку перед самым концом
составного оператора ({) или для задания пустого тела оператора
цикла while ($$R.6.5.1).
Для тех случаев, когда вместо одного оператора нужно использовать
несколько, предусмотрен составной оператор (иногда его называют
"блок").
составной-оператор:
{ список-операторов opt }
список-операторов:
оператор
список-операторов оператор
Отметим, что описание считается оператором ($$R.6.7).
Выбирающие операторы выбирают одну из нескольких структур управления.
выбирающий-оператор:
if ( выражение ) оператор
if ( выражение ) оператор else оператор
switch ( выражение ) оператор
Оператор в выбирающем-операторе не может быть описанием.
Выражение должно быть арифметического типа, или типа указателя, или
типа класс, для которого существует однозначное преобразование
в арифметический тип или тип указателя ($$R.12.3).
Вычисляется выражение, и если оно имеет отличный от нуля результат,
выполняется первый вложенный оператор. Если использована конструкция
else и выражение дает результат 0, выполняется второй вложенный
оператор. Неоднозначность в случае нескольких конструкциями else
разрешается путем отнесения else к последнему встретившемуся if,
для которого не было else.
Оператор переключателя вызывает передачу управления на один из
нескольких операторов в зависимости от значения выражения.
Выражение должно быть целочисленного типа или типа класса, для
которого существует однозначное преобразование к целочисленному
типу ($$R.12.3). Выполняются стандартные целочисленные преобразования.
Любой из операторов переключателя можно пометить одним или несколькими
префиксами, имеющими вид:
case выражение-константа :
Здесь выражение-константа ($$R.5.19) приводится к преобразованному
типу выражения переключателя. Никакие две константы из case одного
переключателя не должны иметь одинаковое значение.
В переключателе может быть только один префикс вида
default:
Операторы переключателя могут быть вложенными, тогда метки из
case или default относятся к самому первому переключателю,
объемлющему их.
При выполнении оператора переключателя вычисляется выражение,
и его значение сравнивается с каждой из констант вариантов (case).
Если одна из этих констант равна значению выражения, то управление
передается в оператор, идущий за этой константой. Если ни одна из
констант не совпала со значением выражения, но есть префикс
default, то управление передается на оператор с этим префиксом.
Если префикса default нет, и совпадения не было, то не выполняется
ни один из операторов переключателя.
Если операторы, выполняемые в результате выбора, не приводят
к каким-либо передачам управления, то программа продолжает выполняться
"по меткам case и default" беспрепятственно. Выход из переключателя
возможен с помощью оператора break (см. $$R.6.6.1).
Обычно оператор, с которым имеет дело переключатель, бывает
составным. Описания могут появиться в операторах переключателя.
Однако переход ниже описания, в котором была явная или неявная
инициализация, считается незаконным, если только описание не
находится во внутреннем блоке, который обходится (т.е. полностью
обходится при передаче управления, $$R.6.7). Отсюда следует,
что описание с явной или неявной инициализацией должно содержаться
во внутреннем блоке.
Эти операторы задают виды цикла.
оператор-цикла:
while ( выражение ) оператор
do оператор while (выражение)
for ( оператор-иниц выражение opt ; выражение opt ) оператор
оператор-иниц:
оператор-выражение
оператор-описание
Обратите внимание, что конструкция оператор-иниц кончается точкой с
запятой.
Оператор в операторе-цикла не должен быть описанием.
В операторе while вложенный оператор выполняется до тех пор,
пока значение выражения не станет равным нулю. Проверка происходит
перед каждым выполнением оператора.
Выражение должно быть арифметического типа, или типа указателя, или
типа класс, для которого существует однозначное преобразование в
арифметический тип или тип указателя ($$R.12.3).
В операторе do вложенный оператор выполняется до тех пор,
пока значение выражения не станет равным нулю. Проверка происходит
после каждого выполнения оператора.
Выражение должно быть арифметического типа, или типа указателя,
или типа класс, для которого существует однозначное преобразование
в арифметический тип или тип указателя ($$R.12.3).
Оператор for
for (оператор-иниц выражение-1 opt ; выражение-2 opt ) оператор
эквивалентен конструкции
оператор-иниц
while (выражение-1) {
оператор
выражение-2 ;
}
за исключением того факта, что оператор continue в операторе for
вызовет выполнение выражение-2 перед тем& как начать повторное
вычисление выражения-1. Таким образом, первый оператор задает
инициализацию для цикла, первое выражение производит проверку,
выполняемую перед каждым шагом цикла, так что цикл завершается, когда
выражение становится нулем, а второе выражение обычно задает
приращение, и оно добавляется после каждого шага цикла. Первое
выражение должно иметь арифметический тип, или тип указателя, или
тип класса, для которого существует однозначное преобразование
к арифметическому типу или типу указателя ($$R.12.3).
Могут быть опущены одно или оба выражения. Если отсутствует
выражение-1, то эквивалентный цикл с while имеет условие while (1).
Если оператор-иниц является описанием, область видимости имен,
описанных в нем, простирается до конца блока, закрывающего оператор
for.
Операторы перехода делают безусловную передачу управления.
оператор-перехода:
break ;
continue ;
return выражение opt ;
goto идентификатор ;
По выходе из области видимости (каким бы образом это не произошло)
вызываются деструкторы ($$R.12.4) для всех объектов классов,
построенных в этой области, которые еще не были уничтожены. Это
относится как к явно описанным объектам, так и ко временным объектам
($$R.12.2).
Оператор break может встретиться только в операторе цикла или
переключателе, он приводит к окончанию ближайшего из объемлющих
его операторов цикла или переключателей. Управление передается на
оператор, следующий непосредственно за заканчиваемым, если такой есть.
Оператор continue может встретиться только в операторе цикла и
приводит к передаче управления в заголовок ближайшего
из объемлющих операторов цикла, т.е. в конец цикла. Более точно
можно сказать, что в каждом из операторов:
while (foo) { do { for (;;) {
// ... // ... // ...
contin: ; contin: ; contin: ;
} } while (foo); }
оператор continue, не относящийся ко внешним операторам цикла,
эквивалентен оператору goto contin.
Возврат из функции в обратившуюся к ней функцию происходит с помощью
оператора return.
Оператор return без выражения можно использовать только в
функциях, которые не возвращают значение, т.е. в функциях,
возвращающих значение типа void, или в конструкторах ($$R.12.1)
и деструкторах ($$R.12.4). Оператор return с выражением можно
использовать только в функциях, которые возвращают значение. Значение
выражения передается в ту функцию,которая вызвала данную функцию. Если
нужно, значение преобразуется к типу функции, в которой выполняется
return, по тем же правилам как при инициализации. Это может привести
к вызову конструктора или копированию временных объектов ($$R.12.2).
Выход из функции по концу эквивалентен возврату без выдаваемого
значения, что является незаконным для функции, возвращающей
значение.
Оператор goto безусловно передает управление на оператор,
помеченный идентификатором. Идентификатор должен быть меткой
($$R.6.1), находящейся в текущей функции.
Оператор описания заводит в блоке новый идентификатор и имеет
вид:
оператор-описания:
описание
Если идентификатор, введенный с помощью описания, уже был ранее
описан во внешнем блоке, внешнее описание становится скрытым до
конца блока, после чего оно опять вступает в силу.
Все инициализации автоматических (auto) и регистровых (register)
переменных производятся каждый раз, когда выполняется
оператор-описание. Уничтожение локальных переменных, описанных в
блоке, происходит при выходе из блока ($$R.6.6). Уничтожение
автоматических переменных, определенных в цикле, происходит
на каждом шаге цикла. Например, переменная Index j создается и
уничтожается каждый раз в течение цикла по i:
for (int i = 0; i<100; i++)
for (Index j = 0; j<100; j++) {
// ...
}
Выход из цикла или из блока или переход, минуя инициализацию
автоматических переменных, приводит к уничтожению автоматических
переменных, описанных в точке, откуда происходит переход, но не
в точке, куда происходит переход.
Переход в блок возможен при условии, что он не приводит к
пропуску инициализации. Считается незаконным переход, обходящий
описание с явной или неявной инициализацией, кроме случаев, когда
оно находится во внутреннем блоке, который пропускается (т.е. в него
никогда не попадает управление) или переход происходит из той точки,
где уже была инициализация переменной. Например,
void f()
{
// ...
goto lx; //ошибка: переход, минуя инициализацию
// ...
ly:
X a = 1;
// ...
lx:
goto ly; // нормально, за переходом будет вызов
// деструктора для `a'
}
Автоматическая переменная, которая была создана при некотором
условии, уничтожается при выполнении этого условия, и не может
быть доступна вне проверки этого условия. Например,
if (i)
for (int j = 0; j<100; j++) {
// ...
}
if (j!=100) // ошибка: обращение вне условия
// ...
;
Инициализация локального объекта с классом памяти static ($$R.7.1.1)
производится прежде, чем управление пройдет через область его
описания. Если статическая переменная инициализируется выражением,
которое не является выражением-константой, то перед первым входом
в блок происходит стандартная инициализация нулем, приведенным
к нужному типу ($$R.8.4).
Деструктор для локального статического объекта будет вызываться
в том и только в том случае, если переменная была создана с помощью
конструктора. Деструктор должен вызываться сразу перед вызовом или
как составная часть вызова функций, заданных в atexit() ($$R.3.4).
Существует неоднозначность в грамматике языка, касающаяся
оператора-выражения и описания, а именно, оператор-выражение,
содержащий как самое левое подвыражение явное преобразование типа,
заданное в функциональном стиле ($$R.5.2.3), может быть не отличим от
описания, в котором первый описатель начинается со (. В таких случаях
оператор считается описанием.
Для разрешения неоднозначности следует исследовать весь оператор,
чтобы определить является он оператором-выражением или описанием.
Так устраняется неоднозначность во многих случаях. Например, пусть
T - имя-простого-типа ($$R.7.1.6), тогда имеем
T(a)->m = 7; // оператор-выражение
T(a)++; // оператор-выражение
T(a,5)<<c; // оператор-выражение
T(*e)(int); // описание
T(f)[]; // описание
T(g) = {1, 2 }; // описание
T(*d)(double(3)); // описание
Остальные случаи представляют описания. Например,
T(a); // описание
T(*b)(); // описание
T(c)=7; // описание
T(d),e,f=3; // описание
T(g)(h,2); // описание
Неоднозначность здесь чисто синтаксическая, т.е. на ее
разрешение не влияет тот факт, является ли имя именем-типа или нет.
Есть другой вид коллизии между оператором-выражением и описанием,
который разрешается требованием, чтобы описание функции в блоке
($$R.6.3) сопровождалось именем-типа, например:
void g()
{
int f(); // описание
int a; // описание
f(); // оператор-выражение
a; // оператор-выражение
}
Описания используются для интерпретации каждого из идентификаторов;
необязательно, чтобы они сопровождались выделением памяти,
сопоставляемой с идентификатором. Описания имеют вид
описания:
спецификации-описания opt список-описателей opt ;
описание-asm
определение-функции
спецификация-связи
Описатели в списке-описателей ($$R.8) содержат описываемые
идентификаторы. Конструкция спецификации-описания может отсутствовать
только в определении функций ($$R.8.3) или в описании функций.
Список-описателей может быть пустым, только при описании класса ($$R.9)
или перечисления ($$R.7.2), т.е. когда спецификация-описания есть
спецификация-класса или спецификация-перечисления.
Конструкция описание-asm объясняется в $$R.7.3, а спецификация-связи
в $$R.7.4. Описание происходит в определенной области видимости
($$R.3.2), правила области видимости приводятся в $$R.10.4.
В описании можно использовать следующие спецификации:
спецификация-описания:
спецификация-класса-памяти
спецификация-типа
спецификация-fct
спецификация-шаблона-типа
friend
typedef
спецификации-описания:
спецификации-описания opt спецификация-описания
Самая длинная последовательность конструкций спецификация-описания,
которая, возможно, является именем типа, образует в описании конструкцию
спецификации-описания. Последовательность должна быть согласованной,
что объясняется ниже. Например,
typedef char* Pc;
static Pc; // ошибка: нет имени
Здесь описание static Pc является незаконным, поскольку не указано
никакого имени статической переменной типа Pc. Чтобы иметь
переменную типа int с именем Pc, необходимо задать
спецификацию-типа int, чтобы показать, что (пере)определяется
имя Pc из typedef, а не просто Pc является одним из элементов
последовательности конструкций спецификация-описания, например,
void f(const Pc); // void f(char* const)
void g(const int Pc); // void g(const int)
Укажем, что поскольку signed, unsigned, long и short по
умолчанию трактуются как int, конструкция имя-typedef, которая
появляется после одной из перечисленных спецификаций типа,
должна задавать (пере)определяемое имя, например,
void h(unsigned Pc); // void h(unsigned int)
void k(unsigned int Pc); // void k(unsigned int)
Спецификации класса памяти могут быть такие:
спецификация-класса-памяти:
auto
register
static
extern
Спецификации auto и register могут применяться только для
имен объектов, которые описаны в блоке ($$R.6.3), или для формальных
параметров ($$R.8.3). Почти всегда спецификация auto избыточна и
используется не часто, так, auto используется, чтобы явно отделить
оператор-описание от оператора-выражения ($$R.6.2).
Описание register является описанием auto, которое подсказывает
транслятору, что описываемые переменные будут использоваться
достаточно интенсивно. Подсказка может быть проигнорирована, и во
многих реализациях она игнорируется в том случае, когда берется
адрес переменной.
Описание объекта считается определением, если только оно не
содержит спецификации extern и инициализации ($$R.3.1).
Определение приводит к выделению памяти соответствующего
размера и выполнению соответствующей инициализации ($$R.8.4).
Спецификации static и extern могут применяться только к именам
объектов или функций или к анонимным объединениям. Внутри блока
недопустимы описания функций со спецификацией static или
формальных параметров со спецификацией static или extern.
Статические члены класса описываются в $$R.9.4. Спецификация
extern недопустима для членов класса.
Имя со спецификацией static подлежит внутреннему связыванию.
Объекты, описанные как const, подлежат внутреннему связыванию,
если только они не были описаны с внешней связью. Имя со
спецификацией extern подлежит внешнему связыванию, если только ранее
оно не было описано с внутренней связью. Имя с файловой областью
видимости и без спецификации-класса-памяти подлежит внешнему
связыванию, если только ранее оно не было описано с внутренней
связью или со спецификацией const. В смысле связывания для функций,
не являющихся членами, спецификация inline эквивалентна static
($$R.3.3). Для одного имени все его спецификации, определяющие
связывание, должны быть согласованы. Например,
static char* f(); // f() имеет внутреннее связывание
char* f() // f() все еще внутреннее
{ /* ... */ }
char* g(); // g() имеет внешнее связывание
static char* g() // ошибка: противоречие в связывании
{ /* ... */ }
static int a; // `a' имеет внутреннее связывание
int a; // ошибка: второе определение
static int b; // `b' имеет внутреннее связывание
extern int b; // `b' все еще внутреннее
int c; // `c' имеет внешнее связывание
static int c; // ошибка: противоречие в связывании
extern int d; // `d' имеет внешнее связывание
static int d; // ошибка: противоречие в связывании
Имя неопределенного класса можно использовать в описании
extern. Однако, такое описание нельзя использовать прежде, чем
класс будет определен, например,
struct S;
extern S a;
extern S f();
extern void g(S);
void h()
{
g(a); // ошибка: S неопределено
f(); // ошибка: S неопределено
}
Некоторые спецификации можно использовать только в описании функций.
спецификация-fct:
inline
virtual
Спецификация inline подсказывает транслятору, что необходимо
произвести подстановку тела функции вместо обычной реализации
вызова функции. Подсказка может игнорироваться. В случае функций,
не являющихся членами, спецификация inline дополнительно устанавливает
для функции внутреннее связывание ($$R.3.3). Функция ($$R.5.2.2,
$$R.8.2.5), определенная в описании класса, имеет по умолчанию
спецификацию inline.
Функция-член со спецификацией inline должна иметь в точности
такое же определение в каждой единице трансляции, где она появляется.
Функцию-член не обязательно явно описывать со спецификацией
inline при описании класса, чтобы она трактовалась как подстановка.
Если спецификации inline не было, связывание будет внешним,
если только определение со спецификацией inline не появится перед
первым вызовом функции.
class X {
public:
int f();
inline int g(); // X::g() имеет внутреннее связывание
int h();
};
void k(X* p)
{
int i = p->f(); // теперь X::f() внешнее связывание
int j = p->g();
// ...
}
inline int X::f() // ошибка: вызов до определения
// как inline
{
// ...
}
inline int X::g()
{
// ...
}
inline int X::h() // теперь X::h() имеет внутреннее связывание
{
// ...
}
Спецификация virtual может использоваться только в описаниях
нестатических функций-членов при описании класса (см. $$R.10.2).
Описания со спецификацией typedef задают идентификаторы, которые
позднее могут использоваться для обозначения основных или
производных типов. Спецификация typedef недопустима в определении-функции
($$R.8.3).
имя-typedef:
идентификатор
В пределах области видимости ($$R.3.2) описания typedef любой
идентификатор, появляющийся в части любого из описателей,
становится синтаксически эквивалентным служебному слову и обозначает тип,
связанный с данным идентификатором, как описано в $$R.8. Таким образом,
имя-typedef является синонимом другого типа. В отличие от описания
класса ($$R.9.1) имя-typedef не добавляет нового типа. Например,
после описания
typedef int MILES, *KLICKSP;
конструкции
MILES distance;
extern KLICKSP metricp;
являются законными описаниями, тип distance есть int, а у metricp
тип "указатель на int".
С помощью typedef можно переопределить имя так, чтобы оно опять
обозначало тип, на который уже ссылалось, причем даже в той области
видимости, в которой тип был первоначально описан, например,
typedef struct s { /* ... */ } s;
typedef int I;
typedef int I;
typedef I I;
Безымянный класс, который определяется в typedef, получает в
качестве своего имени имя, использованное в typedef, например,
typedef struct { /* .... */ } S; // имя структуры стало S
С помощью описания typedef нельзя переопределить имя типа,
описанного в этой же области видимости, так, чтобы оно обозначало
другой тип, например,
class complex { /* ... */ };
typedef int complex; // ошибка: переопределение
Аналогично, нельзя описывать класс с именем типа, описанного
в этой же области видимости, так, чтобы он обозначал другой
тип, например,
typedef int complex;
class complex { /* ... */ }; // ошибка: переопределение
Имя-typedef, которое обозначает класс, является именем-класса
($$R.9.1). Синоним нельзя использовать после следующих префиксов:
class, struct и union, а также в именах конструкторов и
деструкторов в описании самого класса, например,
struct S {
S();
~S();
};
typedef struct S T;
S a = T(); // нормально
struct T* p; // ошибка
Спецификация шаблона типа используется для задания семейства типов
или функций (см. $$R.14).
Спецификация friend используется для задания доступа к членам класса