mp; from) { // копирование по членам X } R.13.4.4 Вызов функции Вызов функции есть конструкция вида: первичное-выражение ( список-выражений opt ) Она считается бинарной операцией, в которой первичное-выражение представляет первый операнд, а список-выражений (возможно пустой), - второй операнд. Именем, задающим функцию, служит operator(), и вызов x(arg1,arg2,arg3) для объекта класса x интерпретируется как x.operator()(arg1,arg2,arg3). Функция operator() должна быть нестатической функцией-членом класса x. R.13.4.5 Индексация Индексация, определяемая как: первичное-выражение [ выражение ] считается бинарной операцией. Выражение с индексацией x[y] для объекта класса x интерпретируется как x.operator[](y). Функция operator[] должна быть нестатической функцией-членом класса x. R.13.4.6 Доступ к члену класса Доступ к члену класса определяется с помощью операции ->: первичное-выражение -> первичное-выражение Он считается унарной операцией. Для объекта класса x выражение x->m интерпретируется как (x.operator->())->m. Отсюда следует, что функция operator->() должна возвращать или указатель на класс, или ссылку на класс, или объект класса, для которого определена функция operator->(). Она должна быть нестатической функцией-членом класса. R.13.4.7 Инкремент и декремент Функция с именем operator++ и с одним параметром задает для объектов некоторого класса операцию префиксного инкремента ++. Функция с именем operator++ и с двумя параметрами задает для объектов некоторого класса операцию постфиксного инкремента ++. Для постфиксной операции ++ второй параметр должен быть типа int, и, когда в выражении встречается операция постфиксного инкремента, функция operator++ вызывается со вторым параметром, равным нулю. Приведем пример: class X { public: X operator++(); // префиксная ++a X operator++(int) // постфиксная a++ }; void f(X a) { ++a; // a.operator++(); a++; // a.operator++(0); a.operator++(); // явный вызов: действует как ++a; a.operator++(0); // явный вызов: действует как a++; } Префиксные и постфиксные операции декремента -- определяются аналогичным образом. R.14 ШАБЛОНЫ ТИПА R.14.1 Шаблоны типа Шаблон типа определяет целое семейство типов или функций. описание-шаблона-типа: template < список-параметров-шаблона-типа> описание список-параметров-шаблона-типа: параметр-шаблона-типа список-параметров-шаблона-типа , параметр-шаблона-типа параметр-шаблона-типа: параметр-типа описание-параметра параметр-типа: class идентификатор Конструкция описание в описании-шаблона-типа должна содержать описание или определение функции или класса. В конструкции параметр-типа идентификатор определяется как имя-типа в области видимости описания шаблона типа. Имена шаблонов типа подчиняются обычным правилам для областей видимости и контроля доступа. Конструкция описание-шаблона-типа считается описанием. Она может присутствовать в программе только как глобальное описание. R.14.2 Шаблоны типов для классов Шаблон типа для класса определяет как будут строиться классы, подобно тому, как описание класса определяет как будут строиться объекты этого класса. Шаблон типа для класса vector можно описать следующим образом: template<class T> class vector { T* v; int sz; public: vector(int); T& operator[](int); T& elem(int i) { return v[i] } // ... }; Префикс template<class T> показывает, что описывается шаблон типа, и что в этом описании используется имя-типа T, иными словами, vector - это параметризованный тип с параметром T. Класс можно задать с помощью конструкции имя-шаблонного-класса: имя-шаблонного-класса: имя-шаблона-типа < список-парам-шаблона-типа > список-парам-шаблона-типа: парам-шаблона-типа список-парам-шаблона-типа , парам-шаблона-типа парам-шаблона: выражение имя-типа Конструкция имя-шаблонного-класса является именем-класса ($$R.9). Класс, который порождается шаблоном типа для класса, называется шаблонным классом и эквивалентен обычному классу, определенному со специальным именем - именем-шаблонного-класса, см. $$R.14.5. Если в конструкции имя-шаблонного-класса имя-шаблона-типа не определено, то она обозначает неопределенный класс. Имя шаблонного класса должно быть уникальным в программе и в своей области видимости оно не может обозначать другой шаблон типа, класс, функцию, объект, значение или тип. Типы, указанные в списке-парам-шаблона-типа из имени-шаблонного-класса, должны соответствовать типам, заданным в списке-параметров-шаблона-типа из шаблона-типа. (Можно сказать, что первые являются фактическими параметрами шаблона типа, а вторые - формальными.) Кроме типов в списке-парам-шаблона-типа могут быть: выражения-константы, адреса объектов или функций, подлежащих внешнему связыванию, статические члены классов. Для параметров, не являющихся типами, требуется точное соответствие ($$R.13.2). Приведем примеры использования классов шаблона типа vector: vector<int> v1(20); vector<complex> v2(30); typedef vector<complex> cvec; // cvec становится синонимом // vector<complex> cvec v3(40); // v2 и v3 одного типа v1[3] = 7; v2[3] = v3.elem(4) = complex(7,8); Здесь vector<int> и vector<complex> являются шаблонными классами, и их определения берутся по умолчанию из шаблона типа vector. Поскольку шаблонное-имя-класса является именем-класса, то оно может использоваться там, где допустимо имя-класса, например: class vector<Shape*> vector<Window>* current_window; class svector : public vector<Shape*> { /* ... */ }; Определение функции-члена шаблонного класса дано в $$R.14.6. R.14.3 Эквивалентность типов Две конструкции шаблонное-имя-класса обозначают один и тот же класс, если в них совпадают имена шаблонов типа и значения указанных параметров. Например, в следующих описаниях x и y одного типа, который отличен от типа z: template<class E, int size> class buffer; buffer<char, 2*512> x; buffer<char,1024> y; buffer<char,512> z; Ниже приведены описания, в которых одинаковый тип имеют x2 и x3. Он отличается от типов x1 и x4: template<class T, void(*err_fct)()> class list { /* ... */ }; list<int,&error_handler1> x1; list<int,&error_handler2> x2; list<int,&error_handler2> x3; list<char,&error_handler2> x4; R.14.4 Шаблоны типа для функций Шаблон типа для функции определяет как будет строиться функция. Например, семейство функций sort можно описать следующим образом: template<class T> void sort(vector<T>); Шаблон типа для функции порождает неограниченное множество перегруженных функций. Функция, порождаемая шаблоном типа для функций, называется шаблонной функцией. Она эквивалентна функции, в описании которой указан тип, соответствующий шаблону, см. $$R.14.5. При вызове шаблонной функции параметры шаблона типа не задаются явно, вместо этого применяется правило разрешения неопределенности перегруженных функций. Рассмотрим пример: vector<complex> cv(100); vector<int> ci(200); void f(vector<complex>& cv, vector<int>& ci) { sort(cv); // вызывается sort(vector<complex>) sort(ci); // вызывается sort(vector<int>) } Шаблонная функция может быть перегружена как обычными, так и шаблонными функциями с тем же именем. Для разрешения неопределенности шаблонных и обычных функций с одним и тем же именем надо последовательно пройти три шага: [1] Попытаться найти точно сопоставимую вызову ($$R.13.2) функцию, и если она найдена, вызвать ее. [2] Попытаться найти шаблон типа для функций, по которому можно создать точно сопоставимую с рассматриваемым вызовом функцию. Если удалось найти, то вызвать функцию. [3] Попытаться применить обычное правило разрешения неопределенности перегруженных функций ($$R.13.2). Если с его помощью функция найдена, вызвать ее. Если не найдено сопоставимой функции, вызов является ошибочным. Если уже на первом шаге найдено более одного кандидата, сопоставимого с данной функцией, то вызов также считается неоднозначным и ошибочным. Успешное выполнение шага [2] приведет к созданию некоторой шаблонной функции с параметрами ($$R.14.5), типы которых точно сопоставятся с типами параметров, указанных в вызове. В этом случае недопустимо расхождение даже за счет тривиальных преобразований ($$R.13.2). Такие же действия применяются для сопоставления типов указателей на функции ($$R.13.3). Рассмотрим пример: template<class T> T max(T a, T b) { return a>b?a:b; }; void f(int a, int b, char c, char d) { int m1 = max(a,b); // max(int a, int b) char m2 = max(c,d); // max(char c, char b) int m3 = max(a,c); // ошибка: нельзя создать max(int,char) } Добавив к этому примеру описание int max(int,int); можно разрешить неопределенность для третьего вызова, поскольку теперь задана функция, которая после стандартного преобразования char в int, может сопоставиться с вызовом max(a,c). Определение шаблона типа для функции используется для создания различных вариантов шаблона типа. Для вызова определенного варианта достаточно лишь описания шаблона типа. Каждый параметр-шаблона-типа, который приведен в списке-параметров-шаблона-типа должен обязательно использоваться при задании типов параметров в шаблоне типа для функции. template<class T> T* create(); //ошибка template<class T> void f() { // ошибка T a; // ... } Все параметры-шаблона-типа, приведенные в шаблоне типа для функции, должны быть параметрами-типа. R.14.5 Описания и определения Для каждого имени шаблона типа в программе должно существовать только одно определение. Описаний может быть несколько. Определение используется для создания специальных шаблонных классов и шаблонных функций, которые будут соответствовать шаблону типа. Конструкция имя-шаблонного-класса вводит описание шаблонного класса. Вызов шаблонной функции или взятие ее адреса вводит описание шаблонной функции. Для вызова или взятия адреса шаблонной функции в языке существует особое соглашение: имя шаблонной функции используется точно так же как имя обычной функции. Описание функции с таким же именем, как у шаблонной функции, и с сопоставимыми типами параметров, вводит описание специальной шаблонной функции. Если для выполнения некоторых операций требуется определение специального шаблонного класса или специальной шаблонной функции, и если такого определения в программе нет, то оно будет создано. Определение обычной (нешаблонной) функции с типом, который точно сопоставляется с типом из описания шаблонной функции, считается определением специальной шаблонной функции. Рассмотрим пример: template<class T> void sort(vector<T>& v) { /* ... */ } void sort(vector<char*>& v) { /* ... */ } Здесь определение функции sort будет использоваться для той функции из семейства sort, которая сопоставляется при вызове с типом параметра vector<char*>. Для других типов vector будет создаваться соответствующее им определение функции по шаблону типа. Можно определить класс, который задает шаблонный класс, например: template<class T> class stream { /* ... */ }; class stream<char> { /* ... */ }; Здесь описание класса будет использоваться в качестве определения потока символов (stream<char>). Другие потоки будут управляться с помощью шаблонных функций, создаваемых по шаблону типа для функций. Пока не появится описание шаблона типа для класса, никакие операции, которым требуется определенный класс, не могут быть произведены над шаблонным классом. После этого специальный шаблонный класс будет считаться определенным, причем сразу же перед первым глобальным описанием, использующим его имя. R.14.6 Функции-члены шаблонов типа Функция-член шаблонного класса считается неявной шаблонной функцией, а параметры шаблона типа для ее класса - ее шаблонными параметрами. Приведем пример, в котором описаны три шаблона типа для функции: template<class T> class vector { T* v; int sz; public: vector(int); T& operator[](int); T& elem(int i) { return v[i]; } // ... }; Функцию, выполняющую индексацию, можно определить следующим образом: template<class T> T& vector<T>::operator[](int i) { if (i<0 || sz>=i) error("vector: range error"); return v[i]; } Шаблонный параметр для vector<T>::operator[]() будет задаваться тем типом vector, к которому применяется операция индексации. vector<int> v1(20); vector<complex> v2(30); v1[3] = 7; // vector<int>::operator[]() v2[3] = complex(7,8); // vector<complex>::operator[]() R.14.7 Друзья Функция-друг для шаблона типа не является неявной шаблонной функцией, например: template<class T> class task { // ... friend void next_time(); friend task<T>* preempt(task<T>*); friend task* prmt(task*); // ошибка // ... }; Здесь функция next_time() становится другом всех классов task, а каждый класс task имеет в качестве друга функцию preempt() c соответствующими типами параметров. Функцию preempt() можно определить как шаблон типа. template<class T> task<T>* preempt(task<T>* t) { /* ... */ } Описание функции prmt() является ошибочным, поскольку типа task не существует, а есть только специальные шаблонные типы task<int>, task<record>, и т.д. R.14.8 Статические члены и переменные Для каждого шаблонного класса или функции, создаваемых по шаблону типа, образуется своя копия статических переменных или членов. Рассмотрим пример: template<class T> class X { static T s; // ... }; X<int> aa; X<char*> bb; Здесь в классе X<int> есть статический член типа int, а в классе X<char> есть статический член типа char*. Аналогично, в приведенном ниже примере, функция f(int*) имеет статический член s типа int, а функция f(char**) имеет статический член типа char**: template<class T> f(T* p) { static T s; // ... } void g(int a, char* b) { f(&a); f(&b); } R.15 Обработка особых ситуаций R.15.1 Обработка особых ситуаций При обработке особых ситуаций в ходе выполнения программы информация и управление передаются из некоторой точки обработчику особых ситуаций. Обработчик находится в цепочке выполненных вызовов функций. Управление обработчику передается с помощью выражения-запуска, которое может быть только в проверяемом-блоке обработчика или в функции, вызванной из проверяемого-блока. проверяемый-блок: try составной-оператор список-обработчиков список-обработчиков: обработчик список-обработчиков opt обработчик: catch ( описание-особой-ситуации ) составной-оператор описание-особой-ситуации: список-спецификаций-типа описатель список-спецификаций-типа абстрактный-описатель список-спецификаций-типа ... выражение-запуска: throw выражение opt Конструкция проверяемый-блок является оператором ($$R.6), а выражение-запуска - унарным выражением типа void ($$R.5). Иногда выражение-запуска называют "точкой запуска", а про функцию, в которой встретилось выражение-запуска, говорят, что она "запускает особую ситуацию. Часть программы, которой передается управление из точки запуска называется обработчиком. R.15.2 Запуск особой ситуации При запуске особой ситуации управление передается обработчику. Запуск сопровождается передачей объект, тип которого определяет, какой обработчик должен перехватить особую ситуацию. Так, выражение throw "Help!"; может быть перехвачено некоторым обработчиком с типом char*: try { // ... } catch(const char* p) { // здесь обрабатывается особая ситуация в символьных строках } а особая ситуация Overflow (переполнение): class Overflow { // ... public: Overflow(char,double,double); }; void f(double x) { // ... throw Overflow('+',x,3.45e107); } может быть перехвачена обработчиком try { // ... f(1.2); // ... } catch(Overflow& oo) { // здесь обработка особой ситуации типа Overflow } При запуске особой ситуации управление передается ближайшему обработчику соответствующего типа. "Ближайший" - это обработчик, проверяемый-блок которого последним получил управление и оно еще не было передано оттуда. Что такое "соответствующий" тип определяется в $$R.15.4. При выполнении выражения-запуска создается временный объект статического типа, который служит операндом в команде throw, Этот объект используется для инициализации переменной, соответствующего типа, описанной в обработчике. Если не считать ограничений на сопоставление типов (см. $$R.15.4) и использование временной переменной, то операнд throw аналогичен параметру функции при вызове ($$R.5.2.2) или операнду в операторе return. Если можно было бы, не меняя смысла программы за счет отказа от вызовов конструкторов и деструкторов для временного объекта ($$R.12.1), обойтись совсем без временного объекта, то особую ситуацию можно было бы непосредственно инициализировать в обработчике параметром выражения запуска. Если в выражении-запуска операнд не задан, то происходит перезапуск обработки особой ситуации. Такое выражение-запуска может появится только в самом обработчике или в функции, которая непосредственно или опосредованно вызывается из него. Например, фрагмент программы, который выполняется при обработке особой ситуации, если нельзя еще полностью провести эту обработку, может выглядеть так: try { // ... } catch (...) { // перехват всех особых ситуаций // (частичная) обработка особых ситуаций throw; // передача остальных особых ситуаций другому обработчику } R.15.3 Конструкторы и деструкторы Когда управление передается из точки запуска особой ситуации обработчику, то вызываются деструкторы для всех автоматических объектов, построенных с момента входа в проверяемый-блок. Если объект не был построен полностью, то деструкторы вызываются только для полностью построенных вложенных в него объектов. Кроме того, если особая ситуация запускается в конструкторе при построении элемента автоматического массива, то уничтожаться будут только уже построенные элементы этого массива. Процесс вызова деструкторов для уничтожения автоматических объектов, построенных в ходе выполнения программы от начала проверяемого-блока до выражения-запуска, называется "раскручиванием стека". R.15.4 Обработка особой ситуации Обработчик типа T, const T, T& или const& сопоставим с выражением-запуска, имеющим операнд типа E, если: [1] T и E являются одним типом; [2] T является доступным ($$R.4.6) базовым классом E в точке запуска; [3] T является типом указателя, а E является таким типом указателя, который можно в точке запуска преобразовать в T с помощью стандартных преобразований указателя ($$R.4.6). Рассмотрим пример: class Matherr { /* ... */ virtual vf(); }; class Overflow : public Matherr { /* ... */ }; class Underflow : public Matherr { /* ... */ }; class Zerodivide : public Matherr { /* ... */ }; void f() { try { g(); } catch (Overflow oo) { // ... } catch (Matherr mm) { // ... } } Здесь обработчик Overflow будет перехватывать ситуации типа Overflow, а обработчик Matherr будет перехватывать ситуации типа Matherr и всех типов, являющихся общими производными от Matherr, включая Underflow и Zerodivide. Обработчики в проверяемом-блоке подбираются для данной особой ситуации в порядке их описания. Считается ошибкой , если обработчик для базового класса помещен перед обработчиком для производного класса, поскольку при таком расположении управление никогда не попадет к обработчику для производного класса. Эллипсис ... в описании-особой-ситуации действует так же как, и в описании параметров функции, он сопоставим с любой особой ситуацией. Если задан эллипсис, то использующий его обработчик должен идти последним в проверяемом-блоке. Если в проверяемом-блоке не произошло сопоставления ни с одним из обработчиков, поиск соответствующего обработчика продолжается в динамически объемлющем проверяемом-блоке. Если во всей программе не произошло сопоставления ни с одним обработчиком, вызывается функция terminate() ($$R.15.7). Особая ситуация считается обработанной после входа в тело обработчика. В этот момент завершится "раскручивание стека". R.15.5 Спецификации особых ситуаций Возникновение и перехватывание особой ситуации влияет на взаимодействие функций. Список особых ситуаций, которые прямо или косвенно может запустить данная функция, можно задать как часть ее описания. Конструкция спецификация-особой-ситуации предшествует описателю функции. спецификация-особой-ситуации: throw ( список-типов opt ) список-типов: имя-типа список-типов , имя-типа Приведем пример: void f() throw (X,Y) { // ... } Если функция попробует запустить неуказанную в списке ситуацию, управление передается функции unexpected(), см. $$R.15.8. Реализация языка не должна запрещать выражение только потому, что при его вычислении возможен запуск особой ситуации, не указанной в спецификации-особой ситуации описания функции. Обработка непредвиденных особых ситуаций происходит в динамике. Функция, в которой отсутствует спецификация-особой-ситуации, может запустить любую особую ситуацию. Функция с пустой спецификацией-особых-ситуаций (throw()) не должна запускать никаких особых ситуаций. Если функция может запустить особую ситуацию класса X, то она может запустить особую ситуацию любого класса, являющегося общим производным классом от X. Конструкция спецификация-особой-ситуации не относится к типу функции. R.15.6 Специальные функции Механизм управления особыми ситуациями использует для реакции на ошибки при самой обработке особых ситуаций функции: terminate() и unexpected(). R.15.6.1 Функция terminate() Иногда от предусмотренной обработки особых ситуаций приходится переходить к более грубым приемам, например: - когда механизм управления особыми ситуациями не смог найти обработчик для запущенной особой ситуации; - когда механизм управления особыми ситуациями столкнулся с нарушенной структурой стека; - когда деструктор, вызванный в процессе раскрутки стека при запуске особой ситуации, сам пытается завершить выполнение программы, запустив особую ситуацию. В этих случаях вызывается функция void terminate(); Она в свою очередь вызывает функцию, которая была указана как параметр при последнем обращении к set_terminate(): typedef void(*PFV)(); PFV set_terminate(PFV); Функция, которая была задана в предыдущем вызове set_terminate(), будет возвращаемым значением текущего вызова. Это помогает пользователю реализовать алгоритм восстановления стека. По умолчанию функция terminate() вызывает abort(). Выбор с помощью terminate() такой функции, которая вместо действительного завершения программы, пытается вернуться в вызвавшую программу, является ошибкой. R.15.6.2 Функция unexpected() Если функция, имеющая спецификацию-особой-ситуации, запускает неуказанную особую ситуацию, то вызывается функция void unexpected(); Она в свою очередь вызывает функцию, которая была задана как параметр при последнем обращении к set_unexpected(): typedef void(*PFV)(); PFV set_unexpected(PFV); Функция, которая была задана в предыдущем вызове set_unexpected(), будет возвращаемым значением текущего вызова. Это помогает пользователю реализовать алгоритм восстановления стека. По умолчанию unexpected() вызывает terminate(). Поскольку по умолчанию terminate() вызывает abort(), результатом будет непосредственное и точное обнаружение ошибки. R.15.7 Особые ситуации и правила доступа Для формального параметра операции catch действуют такие же правила доступа, как и для формального параметра функции, в которой задана операция catch. При запуске особой ситуации можно указывать такой объект, который можно копировать и уничтожать в области видимости функции, где задана операция throw. R.16 Препроцессорная обработка Реализация языка С++ включает препроцессор с возможностями макроподстановки, условной трансляции и включения указанных файлов. Для передачи заданий препроцессору служат строки, начинающиеся с символа # (перед ним могут идти пробелы и символы горизонтальной табуляции). Такие строки называются командами, и их синтаксис определяется независимо от остального языка. Команды могут находиться в любом месте программы, и их действие продолжается (независимо от правил областей видимости С++) до конца данной единицы трансляции ($$R.2). Команду препроцессора, как и любую строку, можно продолжить на следующей строке входного текста, поместив символ обратной дробной черты непосредственно перед символом конца продолжаемой строки. Препроцессор до того, как входная строка будет разбита на лексемы, удаляет символы обратной дробной черты и конца строки. Символ обратной дробной черты не должен быть последним символом входного файла. К лексемам препроцессора относятся: лексемы самого языка ($$R.2.1), имя файла, которое используется в команде #include и вообще любой символ, отличный от обобщенного пробела и несовпадающий ни с какой из лексем препроцессора. R.16.1 Фазы препроцессорной обработки По определению существует несколько фаз препроцессорной обработки. В конкретной реализации фазы могут сливаться, но результат все равно должен быть таким, как будто были выполнены все фазы. Перечислим их. При необходимости символы, зависящие от системы символы, обозначающие конец строки, заменяются на стандартный символ конца строки. Аналогичной замене подлежат все зависящие от системы символы. Определенные последовательности символов (триграфы) заменяются на эквивалентный им отдельный символ ($$R.16.2). Удаляются все такие пары символов: обратная дробная черта, следующий за ней символ конца строки. В результате будут слиты строки входного текста, из которых была удалена эта пара. Входной текст разбивается на лексемы препроцессора и последовательность обобщенных пробелов. Каждый комментарий заменяется на один пробел. Входной текст не должен кончаться посреди лексемы или комментария. Выполняются команды препроцессора, и производятся макроподстановки ($$R.16.3, $$R.16.4, $$R.16.5, $$R.16.6, $$R.16.7 и $$R.16.8). В символьных константах и строках литералов комбинации специальных символов заменяются на свои эквиваленты ($$R.2.5.2). Сливаются соседние строки литералов. Результат препроцессорной обработки подвергается синтаксическому и семантическому анализу, транслируется, а затем связывается с необходимыми библиотеками и другими программами. R.16.2 Триграфные последовательности Прежде чем начнется какая-либо иная препроцессорная обработка, каждое вхождение триграфной последовательности заменяется на один символ в соответствии с приведенной ниже таблицей. ??= # ??( [ ??/ \ ??) [ ??' ^ ??! | Например, строка ??=define arraycheck(a,b) a??(b??) ??!??! b??(a??) преобразуется в #define arraycheck(a,b) a[b] || b[a] R.16.3 Макроопределение и макроподстановка Команда вида #define идентификатор строка-лексем называется макроопределением. Она указывает препроцессору, что надо произвести замену всех последующих вхождений идентификатора на заданную последовательность лексем, называемую строкой замены. Обобщенные пробелы, окружающие эту последовательность лексем, отбрасываются. Например, при определении #define SIDE 8 описание char chessboard[side][side]; после макроподстановки примет вид char chessboard[8][8]; Определенный таким способом идентификатор можно переопределить с помощью другой команды #define, но при условии, что строки замены в обоих определениях совпадают. Все символы обобщенного пробела, разделяющие лексемы, считаются идентичными. Команда вида идентификатор ( идентификатор, ... ,идентификатор) строка-лексем называется макроопределением с параметрами или "функциональным" макроопределением. В нем недопустимы пробелы между первым идентификатором и символом (. Определенный таким способом идентификатор можно переопределить с помощью другого функционального макроопределения, но при условии, что во втором определении то же число и те же наименования параметров, что и в первом, а обе строки замены совпадают. Все символы обобщенного пробела, разделяющие лексемы, считаются идентичными. Последующие вхождения идентификатора, определенного в функциональном макроопределении, если за ним следуют символ (, последовательность лексем, разделенных запятыми, и символ ), заменяются на строку лексем из макроопределения. Обобщенные пробелы, окружающие строку замены, отбрасываются. Каждое вхождение идентификатора, из списка параметров макроопределения, заменяется на последовательность лексем, представляющую соответствующий фактический параметр в макровызове. Фактическими параметрами являются строки лексем, разделенные запятыми. Запятая, взятая в кавычки, или находящаяся в символьной константе или во вложенных круглых скобках, не разделяет параметров. Число фактических параметров макровызова должно совпадать с числом параметров макроопределения. После идентификации параметров для функционального макроопределения происходит подстановка фактических параметров. После выполнения подстановок в параметре (если они были) этот параметр в строке замены замещается фактическим параметром из макровызова ($$R.16.3.3); исключения составляют случаи, когда параметру предшествует лексема # ($$R.16.3.1), или с ним соседствует лексема ## ($$R.16.3.2). Приведем пример. Пусть есть макроопределения #define index_mask 0XFF00 #define extract(word,mask) word & mask Тогда макровызов index = extract(packed_data,index_mask); после подстановки примет вид index = packed_data & 0XFF00; Для обоих видов макроопределений строка замены проверяется на наличие других макроопределений ($$R.16.3.3). R.16.3.1 Операция # Если непосредственно перед параметром в строке замены идет лексема #, то при подстановке параметр и операция # будут заменены на строку литералов , содержащую имя соответствующего параметра макровызова. В символьной константе или строке литералов, входящих в параметр, перед каждым вхождением \ или " вставляется символ \. Например, если есть макроопределения #define path(logid,cmd) "/usr/" #logid "/bin/" #cmd то макровызов char* mytool=path(joe,readmail); приведет к такому результату: char* mytool="/usr/" "joe" "/bin/" "readmail"; После конкатенации соседних строк ($$R.16.1) получим: char* mytool="/usr/joe/bin/readmail"; R.16.3.2 Операция ## Если в строке замены между двумя лексемами, одна из которых представляет параметр макроопределения, появляется операция ##, то сама операция ## и окружающие ее обобщенные пробелы удаляются. Таким образом, результат операции ## состоит в конкатенации. Пусть есть макроопределение, #define inherit(basenum) public Pubbase ## basenum, \ private Privbase ## basenum тогда макровызов class D : inherit(1) { }; приведет к такому результату: class D : public Pubbase1, Privbase1 { }; Макроопределение, которое в строке замены соседствует с ##, не подлежит подстановке, однако, результат конкатенации может использоваться для подстановки. Приведем пример. Пусть есть определения: #define concat(a) a ## ball #define base B #define baseball sport Тогда макровызов concat(base) даст в результате sport а вовсе не Bball R.16.3.3 Повторный просмотр и дальнейшие подстановки После того, как в строке замены произошла подстановка всех параметров макровызова, получившаяся строка просматривается повторно для обнаружения дополнительных макроопределений. Если в процессе повторных просмотров строки замены найдено имя макроопределения, то подстановка все же не происходит. Рекурсивную подстановку нельзя выполнить как команду препроцессора, хотя она кажется для него естественной командой. R.16.3.4 Область видимости макроимен и конструкция #undef После появления макроопределения идентификатор из него считается определенным и остается в текущей области видимости (независимо от правил областей видимости в С++) до конца единицы трансляции или пока его определение не будет отменено с помощью команды #undef. Команда #undef имеет вид: #undef идентификатор Она заставляет препроцессор "забыть" макроопределение с этим идентификатором. Если указанный идентификатор не является определенным в данный момент макроименем, то команда #undef игнорируется. R.16.4 Включение файлов Управляющая строка вида: #include <имяфайла> приводит к замене данной строки на содержимое файла с указанным именем. Поиск указанного файла проходит в определенной последовательности частей архива системы и определяется реализацией. Аналогично, управляющая строка вида: #include "имяфайла" приводит к замене данной строки на содержимое файла с указанным именем. Поиск этого файла начинается в особых (системных) частях архива, указанных в начале последовательности поиска. Если там он не найден, то поиск файла идет по всей последовательности, как если бы управляющая строка имела вид: #include <имяфайла> В имени файла, ограниченном символами < и > нельзя использовать символы конца строки или >. Если в таком имени появится один из символов ', \, или ", а также последовательность символов /* или //, то результат считается неопределенным. В имени файла, ограниченном парой символов " нельзя использовать символы конца строки или ", хотя символ > допустим. Если в таком имени появится символ ' или \ или последовательность /* или //, то результат считается неопределенным. Если команда #include строка-лексем имеет вид, соответствующий ни первой, ни второй управляющей строке, то лексемы препроцессора, заданные в этой команде обрабатываются как обычный текст. В результате должна получиться команда, вид которой соответствует одному из приведенных. Она и будет выполнена как положено. Команда #include может быть в файле, который сам появился в результате выполнения другой команды #include. Реализация может накладывать ограничение на глубину вложенности команды #include во входных файлах программы, которые приходится читать для выполнения первоначальной команды #include в одном из входных файлов. R.16.5 Условная трансляция С помощью препроцессора можно организовать условную трансляцию программы. Синтаксически это задается следующим образом: условное: часть-if части-elif opt часть-else opt строка-endif часть-if: строка-if текст строка-if: # if выражение-константа # ifdef идентификатор # ifndef идентификатор части-elif: строка-elif текст части-elif строка-elif текст строка-elif: # elif выражение-константа часть-else: строка-else текст строка-else: # else строка-endif: # endif Константные выражения в #if и #elif (если эти части есть) вычисляются в порядке их задания в тексте до тех пор, пока одно из них не окажется отличным от нуля. Операторы С++, следующие за строкой, в которой выражение оказалось равным нулю, не транслируются. Команды препроцессора, идущие за этой строкой игнорируются. После того, как найдена команда с ненулевым значением выражения, текст всех последующих частей #elif и #else (т.е. операторы С++ и команды препроцессора) игнорируется. Текст, относящийся к первой команде с ненулевым значением выражения подлежит обычной препроцессорной обработке и трансляции. Если значения всех выражений, указанных в #if и #elif, оказались равными нулю, тогда обычной обработке подлежит текст, относящийся к #else. В выражении-константе, которое встретилось в #if или #elif можно использовать унарную операцию defined, причем в двух вариантах: defined идентификатор или defined (идентификатор) Если эта операция применяется к идентификатору, который был определен с помощью команды #define, и если это определение не было отменено командой #undef, то результат равен 1, иначе результат равен 0. Сам идентификатор defined нельзя переопределить, нельзя и отменить его определение. После применения операций defined происходит раскрытие всех всех макроопределений, имеющихся в константном выражении см. $$R.16.3. В результате должно получиться целочисленное выражение-константа, отличающееся от определения в $$R.5.19 тем, что типы int и unsigned int рассматриваются как long и unsigned long соответственно, а кроме того в этом выражении не должно быть операций приведения, sizeof или элемента перечисления. Управляющая строка #ifdef идентификатор эквивалентна строке #if defined идентификатор а управляющая строка #ifndef идентификатор эквивалентна строке #if !defined идентификатор Конструкции, задающие условную трансляцию, могут быть вложенными, но реализация может накладывать ограничение на глубину вложенности этих конструкций. R.16.6 Управление строками Для удобства написания программ, порождающих текст на С++, введена управляющая строка вида: #line константа "имяфайла" opt Она задает значение предопределенному макроимени __LINE__ ($$R.16.10), которое используется в диагностических сообщениях или при символической отладке; а именно: номер следующей строки входного текста считается равным заданной константе, которая должна быть десятичным целым числом. Если задано "имяфайла", то значение макроимени __FILE__ ($$R.16.10) становится равным имени указанного файла. Если оно не задано, __FILE__ не меняет своего значения. Макроопределения в этой управляющей строке раскрываются до выполнения самой команды. R.16.7 Команда error Строка вида: #error строка-лексем заставляет реализацию выдать диагностическое сообщение, состоящее из заданной последовательности лексем препроцессора. R.16.8 Команда pragma Строка вида: #pragma строка-лексем заставляет реализацию вести себя некоторым определенным образом при условии что реализация "понимает" эту строку. Любая нераспознанная строка #pragma игнорируется. R.16.9 Пустая директива Команда препроцессора вида # не оказывает никакого действия. R.16.10 Предопределенные макроимена В процессе трансляции определенную информацию содержат следующие предопределенные макроимена. __LINE__ десятичная константа, содержащая номер текущей строки текста программы на С++. __FILE__ строка литералов, представляющая имя транслируемого входного файла. __DAT