Форматы некоторых производных типов и их интерпретация
При описании объектов программы в C++ может указываться произвольное количество атрибутов (конечных, таких как char, int, short int, long int, unsigned char, float, double и т.д.; и промежуточных: * – указатель, [ ] – массив, ( ) – функция), причем промежуточные атрибуты должны следовать за конечным атрибутом.
!!! Недопустимы некоторые последовательности промежуточных атрибутов. Например, массив не может состоять из функций, а функция не может возвращать массив или функцию.
При интерпретации списка атрибутов квадратные и круглые скобки (справа от идентификатора) имеют приоритет перед звездочкой (слева от идентификатора). Квадратные и круглые скобки имеют один и тот же приоритет и рассматриваются слева направо. Конечный атрибут принимается во внимание на последнем этапе, когда все промежуточные атрибуты проинтерпретированы. Для изменения порядка интерпретации используются круглые скобки.
type имя[];
– массив объектов типа type.
Например:
double M[10]; //массив из10 объектов типа double (M[0], M[1], …M[9])
int m[5]; //массив из 5 объектов типа int
type1 имя(type2);
– функция, принимающая аргумент типа type2 и возвращающая значение типа type1.
Например:
int main(); //функцияmain, не требующая аргументов и
//возвращающая значение типа int.
void f1(double); //функция f1, требующая аргумент типа double
//и ничего не возвращающая.
type1 *имя(type2);
– функция, принимающая аргумент типа type2 и возвращающая указатель на объект типаtype1.
Например:
int *fp(float); //функцияfp, принимающая аргумент типа float и
// возвращающая указатель на объект типаint.
char *fun( ) //функцияfun, не требующая аргументов и
//возвращающая указатель на символ
type *имя;
– указатель на объекты типаtype.
Например:
int *рtr; // указатель рtr на объекты типа int.
type **имя;
– указатель на указатель на объекты типаtype.
Например:
int **ptr; указатель ptr на указатель на объекты типа int
type *имя[];
– массив указателей на объекты типа type.
Например:
int *mрtr[5]; // массив mрtr из 5 указателей на объекты типа int.
type (*имя)[];
– указатель на массив объектов типа type.
Например:
int (*рtr)[5]; // указатель рtr на массив из 5 объектов типа int.
double (*pm)[10]; //указатель pm на массив из 10 объектов типа double
type1 *(*имя) (type2);
– указатель на функцию, принимающую параметры типаtype2и возвращающую указатель наобъект типаtype1 .
Например:
int (*pf) (); //указатель pf на функцию без параметров,
//возвращающую объект типаint
double *(*pfp)(int); //указатель pfp на функцию, принимающую параметры
// типаint и возвращающую указатель на объект типаdouble
char (*fun)( ) //указатель fun на функцию без параметров,
//возвращающую символ
type1 (&имя) (type2);
– ссылка на функцию, принимающую параметры типаtype2и возвращающую объект типаtype1 .
struct имя {
type1 имя1;
type2 имя2;
};
– определение структурного типа имяс двумя компонентами (полями данных имя1иимя2),которыеимеют типыtype1иtype2.
union имя {
type1 имя1;
type2 имя2;
};
– определение типа-объединения имяс двумя компонентами (полями данных имя1иимя2),которыеимеют типыtype1иtype2.
class имя {
type1 имя1;
type2 имя2;
type3 имя3(type4);
};
– определение класса имяс тремя компонентами (полями данных имя1иимя2и функциейимя3).
//определение указателя pf на функцию, не требующую
// аргументов и возвращающую указатель
// на массив из 20 объектов типа int
int* (*(*pf) ()) [20]; //определение указателя pf на функцию, не требующую
//аргументов и возвращающую указатель на массив
// из 20 указателей на int
Для интерпретации списка атрибутов существует правило «справа-налево», которое состоит в следующем:
1. определяется идентификатор;
2. просматривается список атрибутов справа от идентификатора для нахождения промежуточного атрибута;
3. просматривается список атрибутов слева от идентификатора в поисках других промежуточных атрибутов (слева от идентификатора не может находиться промежуточный атрибут, отличающийся от указателя, т.е. *);
4. повторяются действия пп.2 и 3 до тех пор, пока не будет проинтерпретирован весь список промежуточных атрибутов;
5. в последнюю очередь интерпретируется конечный атрибут.
Рассмотрим пример использования правила «справа-налево»:
int *(*(*per)())[20];
1. Круглые скобки указывают порядок интерпретации. Во внутренних скобках заключена конструкция *per, т.е. per – указатель.
2. Просматриваем список атрибутов, начиная с найденной конструкции, слева направо для интерпретации следующего промежуточного атрибута. Следующий атрибут – (), т.е. функция.
3. Просматриваем список налево от идентификаторов. Следующий промежуточный атрибут – *, т.е. указатель.
4. Продолжаем просмотр в правую сторону. Следующий промежуточный атрибут – [20], т.е. массив из 20 элементов.
5. Двигаемся налево. Находим следующий промежуточный атрибут – *, т.е. указатель.
6. Двигаемся направо, обнаруживаем, что список промежуточных атрибутов завершен, поэтому присоединяем конечный атрибут. Здесь конечный атрибут – int, т.е. целый тип.
7. Итак, суммируя интерпретации предыдущих пунктов, получаем per – указатель на функцию, возвращающую указатель на массив из 20 элементов, каждый из которых есть указатель на целое.
Часто бывает необходимо решить обратную задачу, т.е. по словесной интерпретации записать список атрибутов для переменной. При этом условимся различать промежуточные атрибуты правые (( ) – функция, [ ] – массив) и левые (*–указатель).
При этом полезно пользоваться следующим правилом:
1. Выбирается идентификатор и записывается посредине строки.
2. Выписываются все атрибуты из словесного описания, берется первый атрибут.
3. Если атрибут является левым, т.е. указателем, он записывается слева от идентификатора.
4. Если атрибут является правым, т.е. массивом или функцией, то:
а) если в соответствии с п.3 слева был прописан указатель, вся конструкция с указателем заключается в круглые скобки и справа записывается правый атрибут;
b) если слева нет указателя, справа записывается нужный правый атрибут.
5. Повторяются действия пп.3 и 4 для всех промежуточных атрибутов из словесного описания.
6. Добавляются конечный атрибут слева и точка с запятой в конце описания.
Рассмотрим в качестве примера описание на С++ массива указателей на функции, возвращающие указатель на целое. Выпишем все атрибуты: массив, указатель, функция, указатель, целый.
Выберем идентификатор, например ab. Первый атрибут из списка – правый – массив: ab[ ]. Следующий атрибут из списка – левый – указатель: *ab[ ], заключаем всю конструкцию в скобки и приписываем справа правый атрибут – функцию: (*ab[ ])( ). Следующий атрибут из списка – указатель: *( *ab[ ] )( ). Все промежуточные атрибуты из списка закончились. Остался лишь конечный атрибут – целый: int *(*ab [ ] ) ( );