straustrup2 (852740), страница 20
Текст из файла (страница 20)
Однакопользователь может сам определить эти операции (см. главу 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() находит объект, представляющий целое (а не простозначение соответствующего целого) и возвращает ссылку на него.
Эти функции можно использовать,например, так:const int MAX = 256;// больше длины самого длинного слова60Бьерн Страуструп.Язык программирования С++main()// подсчитывает частоту слов во входном потоке{char buf[MAX];while (cin>>buf) value(buf)++;for (int i=0; vec[i].name; i++)cout << vec[i].name << ": " << vec [i].val<< '\n';}В цикле while из стандартного входного потока cin читается по одному слову и записывается в буферbuf (см. глава 10), при этом каждый раз значение счетчика, связанного со считываемой строкой,увеличивается. Счетчик отыскивается в ассоциативном массиве vec с помощью функции find(). В циклеfor печатается получившаяся таблица различных слов из cin вместе с их частотой.
Имея входной потокaa bb bb aa aa bb aa aaпрограмма выдает:aa: 5bb: 3С помощью шаблонного класса и перегруженной операции [] ($$8.8) достаточно просто довести массивиз этого примера до настоящего ассоциативного массива.2.4 ЛИТЕРАЛЫВ С++ можно задавать значения всех основных типов: символьные константы, целые константы иконстанты с плавающей точкой. Кроме того, нуль (0) можно использовать как значение указателяпроизвольного типа, а символьные строки являются константами типа char[]. Есть возможностьопределить символические константы. Символическая константа - это имя, значение которого в егообласти видимости изменять нельзя. В С++ символические константы можно задать тремя способами:(1) добавив служебное слово const в определении, можно связать с именем любое значениепроизвольного типа; (2) множество целых констант можно определить как перечисление; (3) константойявляется имя массива или функции.2.4.1 Целые константыЦелые константы могут появляться в четырех обличьях: десятичные, восьмеричные,шестнадцатеричные и символьные константы.
Десятичные константы используются чаще всего ивыглядят естественно:0123497612345678901234567890Десятичная константа имеет тип int, если она умещается в память, отводимую для int, в противномслучае ее тип long. Транслятор должен предупреждать о константах, величина которых превышаетвыбранный формат представления чисел. Константа, начинающаяся с нуля, за которым следует x (0x),является шестнадцатеричным числом (с основанием 16), а константа, которая начинающаяся с нуля, закоторым следует цифра, является восьмеричным числом (с основанием 8). Приведем примерывосьмеричных констант:0020770123Их десятичные эквиваленты равны соответственно: 0, 2, 63, 83.
В шестнадцатеричной записи этиконстанты выглядят так:0x00x20x3f0x53Буквы a, b, c, d, e и f или эквивалентные им заглавные буквы используются для представления чисел10, 11, 12, 13, 14 и 15, соответственно. Восьмеричная и шестнадцатеричная формы записи наиболееподходят для задания набора разрядов, а использование их для обычных чисел может датьнеожиданный эффект. Например, на машине, в которой int представляется как 16-разрядное число вдополнительном коде, 0xffff есть отрицательное десятичное число -1. Если бы для представленияцелого использовалось большее число разрядов, то это было бы числом 65535.61Бьерн Страуструп.Язык программирования С++Окончание U может использоваться для явного задания констант типа unsigned.