того же вектора (хотя в языке нет способа удостовериться, что это так). Когда из одного указателя вычитается другой, результатом является число элементов вектора между этими указателями (целое число). Можно добавлять целое к указателю или вычитать целое из указателя; в обоих случаях результатом будет значение типа указателя. Если это значение не указывает на элемент того же вектора, на который указывал исходный указатель, то результат использования этого значения неопределён. Например:
int v1[10]; int v2[10];
int i = amp;v1[5]– amp;v1[3]; // 2 i = amp;v1[5]– amp;v2[3]; // результат неопределен
int* p = v2+2; // p == amp;v2[2] p = v2-2; // p неопределено
2.3.8 Структуры
Вектор есть совокупность элементов одного типа, struct является совокупностью элементов (практически) произвольных типов. Например:
struct address (* // почтовый адрес char* name; // имя «Jim Dandy» long number; // номер дома 61 char* street; // улица «South Street» char* town; // город «New Providence» char* state[2]; // штат 'N' 'J' int zip; // индекс 7974 *)
определяет новый тип, названный address (почтовый адрес), состоящий из пунктов, требующихся для того, чтобы послать кому-нибудь корреспонденцию (вообще говоря, address не
является достаточным для работы с полным почтовым адресом, но в качестве примера достаточен). Обратите внимание на точку с запятой в конце; это одно из очень немногих мест в С++, где необходимо ставить точку с запятой после фигурной скобки, поэтому люди склонны забывать об этом.
Переменные типа address могут описываться точно также, как другие переменные, а доступ к отдельным членам получается с помощью операции . (точка). Например:
address jd; jd.name = «Jim Dandy»; jd.number = 61;
Запись, которая использовалась для инициализации векторов, можно применять и к переменным структурных типов. Например:
address jd = (* «Jim Dandy», 61, «South Street», «New Providence», (*'N','J'*), 7974 *);
Однако обычно лучше использовать конструктор (#5.2.4). Заметьте, что нельзя было бы инициализировать jd.state строкой «NJ». Строки оканчиваются символом ' ', поэтому в «NJ» три символа, то есть на один больше, чем влезет в jd.state.
К структурным объектам часто обращаются посредством указателей используя операцию -». Например:
void print_addr(address* p) (* cout «„ p-“name „„ „ “ „„ p-“number „„ ' ' „„ p-“street „« « “ «« p-“town «« « “ «« chr(p-“state[0]) «« chr(p-“state[1]) «« ' ' «« p-“zip «« « “; *)
Объекты типа структура можно присваивать, передавать как параметры функции и возвращать из функции в качестве результата. Например:
address current;
address set_current(address next) (* address prev = current; current = next; return prev; *)
Остальные осмысленные операции, такие как сравнение (== и !=) не определены. Однако пользователь может определить эти операции, см. Главу 6. Размер объекта структурного типа нельзя вычислить просто как сумму его членов. Причина этого состоит в том, что многие машины требуют, чтобы объекты определенных типов выравнивались в памяти только по некоторым зависящим от архитектуры границам (типичный пример: целое должно быть выровнено по границе слова), или просто гораздо более эффективно обрабатывают такие объекты, если они выровнены в машине. Это приводит к «дырам» в структуре. Например, (на моей машине) sizeof(address) равен 24, а не 22, как можно было ожидать.
Заметьте, что имя типа становится доступным сразу после того, как оно встретилось, а не только после того, как полностью просмотрено все описание. Например:
struct link(* link* previous; link* successor; *)
Новые объекты структурного типа не могут быть описываться, пока все описание не просмотрено, поэтому
struct no_good (* no_good member; *);
является ошибочным (компилятор не может установить размер no_good). Чтобы дать возможность двум (или более) структурным типам ссылаться друг на друга, можно просто описать имя как имя структурного типа. Например:
struct list; // должна быть определена позднее
struct link (* link* pre; link* suc; link* member_of; *);
struct list (* link* head; *)
Без первого описания list описание link вызвало бы к синтаксическую ошибку.
2.3.9 Эквивалентность типов
Два структурных типа являются различными даже когда они имеют одни и те же члены. Например:
struct s1 (* int a; *); struct s2 (* int a; *);
есть два разных типа, поэтому
s1 x; s2 y = x; // ошибка: несоответствие типов
Структурные типы отличны также от основных типов, поэтому
s1 x; int i = x; // ошибка: несоответствие типов
Однако существует механизм для описания нового имени для типа без введения нового типа. Описание с префиксом typedef описывает не новую переменную данного типа, а новое имя этого типа. Например:
typedef char* Pchar; Pchar p1, p2; char* p3 = p1;
Это может служить удобной сокращенной записью.
2.3.10 Ссылки
Ссылка является другим именем объекта. Главное применение ссылок состоит в спецификации операций для типов, определяемых пользователем; они обсуждаются в Главе 6. Они могут также быть полезны в качестве параметров функции. Запись x amp; означает ссылка на x. Например:
int i = 1; int amp; r = i; // r и i теперь ссылаются на один int int x = r // x = 1 r = 2; // i = 2;
Ссылка должна быть инициализирована (должно быть что-то, для чего она является именем). Заметьте, что инициализация ссылки есть нечто совершенно отличное от присваивания ей.
Вопреки ожиданиям, ни одна операция на ссылку не действует. Например:
int ii = 0; int amp; rr = ii; rr++; // ii увеличивается на 1
допустимо, но rr++ не увеличивает ссылку; вместо этого + + применяется к int, которым оказывается ii. Следовательно, после инициализации значение ссылки не может быть изменено; она всегда ссылается на объект, который ей было дано обозначать (денотировать) при инициализации. Чтобы получить указатель на объект, денотируемый ссылкой rr, можно написать amp;rr.
Очевидным способом реализации ссылки является константный указатель, который разыменовывается при каждом использовании. Это делает инициализацию ссылки тривиальной, когда инициализатор является lvalue (объектом, адрес которого вы можете взять, см. #с.5). Однако инициализатор для amp;T не обязательно должен быть lvalue, и даже не должен быть типа T. В таких случаях:
1. Во-первых, если необходимо, применяется преобразование типа (#с.6.6-8, #с.8.5.6),
2. Затем полученное значение помещается во временную переменную и
3. Наконец, ее адрес используется в качестве значения инициализатора.
Рассмотрим описание
double amp; dr = 1;
Это интерпретируется так:
double* drp; // ссылка, представленная как указатель double temp; temp = double(1); drp = amp;temp;
int x = 1; void incr(int amp; aa) (* aa++; *) incr(x) // x = 2
По определению семантика передачи параметра та же, что семантика инициализации, поэтому параметр aa функции incr становится другим именем для x. Однако, чтобы сделать программу читаемой, в большинстве случаев лучше всего избегать функций, которые изменяют значение своих параметров. Часто
предпочтительно явно возвращать значение из функции или требовать в качестве параметра