Б. Страуструп - Язык программирования С++ (1119446), страница 20
Текст из файла (страница 20)
Следует сказать, что в большинстве реализаций языка С++ нет контроля над границамимассивов. Описание массива не является самодостаточным, поскольку необязательно в нем будетхраниться число элементов массива. Понятие массива в С является, по сути, понятием языка низкогоуровня. Классы помогают развить его (см. $$1.4.3).2.3.8 СтруктурыМассив представляет собой совокупность элементов одного типа, а структура является совокупностьюэлементов произвольных (практически) типов. Например:struct address {char* name;long number;char* street;char* town;char* state[2];intzip;};////////////56имя "Jim Dandy"номер дома 61улица "South Street"город "New Providence"штат 'N' 'J'индекс 7974Бьерн Страуструп.Язык программирования С++Здесь определяется новый тип, называемый address, который задает почтовый адрес. Определение неявляется достаточно общим, чтобы учесть все случаи адресов, но оно вполне пригодно для примера.Обратите внимание на точку с запятой в конце определения: это один из немногих в С++ случаев, когдапосле фигурной скобки требуется точка с запятой, поэтому про нее часто забывают.Переменные типа address можно описывать точно так же, как и любые другие переменные,помощью операции .
(точка) можно обращаться к отдельным членам структуры. Например:а сaddress jd;jd.name = "Jim Dandy";jd.number = 61;Инициализировать переменные типа struct можно так же, как массивы. Например:address jd = {"Jim Dandy",61, "South Street","New Providence", {'N','J'}, 7974};Но лучше для этих целей использовать конструктор ($$5.2.4). Отметим, что jd.state нельзяинициализировать строкой "NJ". Ведь строки оканчиваются нулевым символом '\0', значит в строке "NJ"три символа, а это на один больше, чем помещается в jd.state. К структурным объектам частообращаются c помощью указателей, используя операцию ->.
Например:void print_addr(address* p){cout << p->name << '\n'<< p->number << ' ' << p->street << '\n'<< p->town << '\n'<< p->state[0] << p->state[1]<< ' ' << p->zip << '\n';}Объекты структурного типа могут быть присвоены, переданы как фактические параметры функций ивозвращены функциями в качестве результата.
Например:address current;address set_current(address next){address prev = current;current = next;return prev;}Другие допустимые операции, например, такие, как сравнение (== и !=), неопределены. Однакопользователь может сам определить эти операции (см. главу 7).Размер объекта структурного типа не обязательно равен сумме размеров всех его членов. Этопроисходит по той причине, что на многих машинах требуется размещать объекты определенных типов,только выравнивая их по некоторой зависящей от системы адресации границе (или просто потому, чторабота при таком выравнивании будет более эффективной ). Типичный пример - это выравниваниецелого по словной границе. В результате выравнивания могут появиться "дырки" в структуре.
Так, науже упоминавшейся машине автора sizeof(address) равно 24, а не 22, как можно было ожидать.Следует также упомянуть, что тип можно использовать сразу после его появления в описании, еще дотого, как будет завершено все описание. Например:struct link{link* previous;link* successor;};Однако новые объекты типа структуры нельзя описать до тех пор, пока неописание. Поэтому описание57появится ее полноеБьерн Страуструп.Язык программирования С++struct no_good {no_good member;};является ошибочным (транслятор не в состоянии установить размер no_good). Чтобы позволить двум(или более) структурным типам ссылаться друг на друга, можно просто описать имя одного из них какимя некоторого структурного типа. Например:struct list; // будет определено позднееstruct link {link* pre;link* suc;list* member_of;};struct list {link* head;};Если бы не было первого описания list, описание члена link привело бы к синтаксической ошибке.Можно также использовать имя структурного типа еще до того, как тип будет определен, если толькоэто использование не предполагает знания размера структуры.
Например:// 'S' - имя некоторого типаclass S;extern S a;S f();void g(S);Но приведенные описания можно использовать лишь после того, как тип S будет определен:void h(){S a;f();g(a);}// ошибка: S - неописано// ошибка: S - неописано// ошибка: S - неописано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;Это просто удобное средство сокращения записи.58Бьерн Страуструп.Язык программирования С++2.3.10 СсылкиСсылку можно рассматривать как еще одно имя объекта. В основном ссылки используются длязадания параметров и возвращаемых функциями значений , а также для перегрузки операций (см.$$7).Запись X& обозначает ссылку на X. Например:int i = 1;int& r = i;int x = r;r = 2;// r и i ссылаются на одно и то же целое// x = 1// i = 2;Ссылка должна быть инициализирована, т.е. должно быть нечто, что она может обозначать.
Следуетпомнить, что инициализация ссылки совершенно отличается от операции присваивания. Хотя можноуказывать операции над ссылкой, ни одна из них на саму ссылку не действует, например,int ii = 0;int& rr = ii;rr++;// ii увеличивается на 1Здесь операция ++ допустима, но rr++ не увеличивает саму ссылку rr; вместо этого ++ применяется кцелому, т.е.
к переменной ii. Следовательно, после инициализации значение ссылки не может бытьизменено: она всегда указывает на тот объект, к которому была привязана при ее инициализации.Чтобы получить указатель на объект, обозначаемый ссылкой rr, можно написать &rr. Очевиднойреализацией ссылки может служить постоянный указатель, который используется только длякосвенного обращения.
Тогда инициализация ссылки будет тривиальной, если в качествеинициализатора указан адрес (т.е. объект, адрес которого можно получить; см. $$R.3.7). Инициализатордля типа T должен быть адресом. Однако, инициализатор для &T может быть и не адресом, и даже нетипом T. В таких случаях делается следующее:[1] во-первых, если необходимо, применяется преобразование типа (см.$$R.8.4.3);[2] затем получившееся значение помещается во временную переменную;[3] наконец, адрес этой переменной используется в качестве инициализатора ссылки.Пусть имеются описания:double& dr = 1;const double& cdr = 1;// ошибка: нужен адрес// нормальноЭто интерпретируется так:double* cdrp;double temp;temp = double(1);cdrp = &temp;// ссылка, представленная как указательСсылки на переменные и ссылки на константы различаются по следующей причине: в первом случаесоздание временной переменной чревато ошибками, поскольку присваивание этой переменнойозначает присваивание временной переменной, которая могла к этому моменту исчезнуть.
Естественно,что во втором случае подобных проблем не существует. и ссылки на константы часто используются какпараметры функций (см.$$R.6.3). Ссылка может использоваться для функции, которая изменяетзначение своего параметра. Например:void incr(int& aa) { aa++; }void f(){int x = 1;incr(x);// x = 2}По определению передача параметров имеет ту же семантику, что и инициализация, поэтому привызове функции incr ее параметр aa становится другим именем для x.
Лучше, однако, избегатьизменяющих свои параметры функций, чтобы не запутывать программу. В большинстве случаевпредпочтительнее, чтобы функция возвращала результат явным образом, или чтобы использовался59Бьерн Страуструп.Язык программирования С++параметр типа указателя:int next(int p) { return p+1; }void inc(int* p) { (*p)++; }void g(){int x = 1;x = next(x);inc(&x);}// x = 2// x = 3Кроме перечисленного, с помощью ссылок можно определить функции, используемые как в правой, таки в левой частях присваивания.
Наиболее интересное применение это обычно находит приопределении нетривиальных пользовательских типов. В качестве примера определим простойассоциативный массив. Начнем с определения структурыpair:struct pair {char* name;int val;};// строка// целоеИдея заключается в том, что со строкой связывается некоторое целое значение. Нетрудно написатьфункцию поиска find(), которая работает со структурой данных, представляющей ассоциативныймассив. В нем для каждой отличной от других строки содержится структура pair (пара: строка и значение).
В данном примере - это просто массив. Чтобы сократить пример, используется предельно простой,хотя и неэффективный алгоритм:const int large = 1024;static pair vec[large+1];pair* find(const char* p)/*// работает со множеством пар "pair":// ищет p, если находит, возвращает его "pair",// в противном случае возвращает неиспользованную "pair"*/{for (int i=0; vec[i].name; i++)if (strcmp(p,vec[i].name)==0) return &vec[i];if (i == large) return &vec[large-1];return &vec[i];}Эту функцию использует функция value(), которая реализует массив целых, индексируемый строками(хотя привычнее строки индексировать целыми):int& value(const char* p){pair* res = find(p);if (res->name == 0) {// до сих пор строка не встречалась,// значит надо инициализироватьres->name = new char[strlen(p)+1];strcpy(res->name,p);res->val = 0;// начальное значение равно 0}return res->val;}Для заданного параметра (строки) value() находит объект, представляющий целое (а не простозначение соответствующего целого) и возвращает ссылку на него.