Б. Страуструп - Язык программирования С++ (1119446), страница 41
Текст из файла (страница 41)
Использование статических членов класса можетзаметно сократить потребность в глобальных переменных.Описывая член как статический, мы ограничиваем его область видимости и делаем его независимым ототдельных объектов его класса. Это свойство полезно как для функций-членов, так и для членов,представляющих данные:class task {// ...static task* task_chain;static void shedule(int);138Бьерн Страуструп.Язык программирования С++// ...};Но описание статического члена - это только описание, и где-то в программе должно бытьединственное определение для описываемого объекта или функции, например, такое:task* task::task_chain = 0;void task::shedule(int p) { /* ...
*/ }Естественно, что и частные члены могут определяться подобным образом.Отметим, что служебное слово static не нужно и даже нельзя использовать в определении статическогочлена класса. Если бы оно присутствовало, возникла бы неоднозначность: указывает ли оно на то, чточлен класса является статическим, или используется для описания глобального объекта или функции?Слово static одно из самых перегруженных служебных слов в С и С++. К статическому члену,представляющему данные, относятся оба основных его значения: "статически размещаемый" , т.е.противоположный объектам, размещаемым в стеке или свободной памяти, и "статический" в смысле сограниченной областью видимости, т.е. противоположный объектам, подлежащим внешнемусвязыванию.
К функциям-членам относится только второе значение static.5.4.5 Указатели на членыМожно брать адрес члена класса. Операция взятия адреса функции-члена часто оказывается полезной,поскольку цели и способы применения указателей на функции, о которых мы говорили в $$4.6.9, вравной степени относятся и к таким функциям. Указатель на член можно получить, применив операциювзятия адреса & к полностью уточненному имени члена класса, например, &class_name::member_name.Чтобы описать переменную типа "указатель на член класса X", надо использовать описатель вида X::*.Например:#include <iostream.h>struct cl{char* val;void print(int x) { cout << val << x << '\n'; }cl(char* v) { val = v; }};Указатель на член можно описать и использовать так:typedef void (cl::*PMFI)(int);int main(){cl z1("z1 ");cl z2("z2 ");cl* p = &z2;PMFI pf = &cl::print;z1.print(1);(z1.*pf)(2);z2.print(3);(p->*pf)(4);}Использование typedef для замены трудно воспринимаемого описателя в С достаточно типичныйслучай.
Операции .* и ->* настраивают указатель на конкретный объект, выдавая в результате функцию,которую можно вызывать. Приоритет операции () выше, чем у операций .* и ->*, поэтому нужны скобки.Во многих случаях виртуальные функции ($$6.2.5) успешно заменяют указатели на функции.5.4.6 Структуры и объединенияПо определению структура - это класс, все члены которого общие, т.е. описание139Бьерн Страуструп.Язык программирования С++struct s { ...это просто краткая форма описанияclass s { public: ...Поименованное объединение определяется как структура, все члены которой имеют один и тот жеадрес ($$R.9.5). Если известно, что в каждый момент времени используется значение только одногочлена структуры, то объявив ее объединением, можно сэкономить память.
Например, можноиспользовать объединение для хранения лексем транслятора С:union tok_val {char* p;char v[8];long i;double d;};////////строкаидентификатор (не более 8 символов)значения целыхзначения чисел с плавающей точкойПроблема с объединениями в том, что транслятор в общем случае не знает, какой член используется вданный момент, и поэтому контроль типа невозможен. Например:void strange(int i){tok_val x;if (i)x.p = "2";elsex.d = 2;sqrt(x.d);// ошибка, если i != 0}Кроме того, определенное таким образом объединение нельзя инициализировать таким кажущимсявполне естественным способом:// ошибка: int присваивается tok_val// ошибка: char* присваивается tok_valtok_val val1 = 12;tok_val val2 = "12";Для правильной инициализации надо использовать конструкторы:union tok_val {char* p;char v[8];long i;double d;tok_val(const char*);tok_val(int ii){ i = ii; }tok_val(double dd) { d = dd; }};//////////строкаидентификатор (не более 8 символов)значения целыхзначения чисел с плавающей точкойнужно выбирать между p и vЭти описания позволяют разрешить с помощью типа членов неоднозначность при перегрузке именифункции (см.
$$4.6.6 и $$7.3). Например:void f(){tok_val a = 10;tok_val b = 10.0;}// a.i = 10// b.d = 10.0Если это невозможно (например, для типов char* и char[8] или int и char и т.д.), то определить, какойчлен инициализируется, можно, изучив инициализатор при выполнении программы, или введядополнительный параметр. Например:tok_val::tok_val(const char* pp){if (strlen(pp) <= 8)140Бьерн Страуструп.strncpy(v,pp,8);elsep = pp;Язык программирования С++// короткая строка// длинная строка}Но лучше подобной неоднозначности избегать.Стандартная функция strncpy() подобно strcpy() копирует строки, но у нее есть дополнительныйпараметр, задающий максимальное число копируемых символов.То, что для инициализации объединения используются конструкторы, еще не гарантирует от случайныхошибок при работе с объединением, когда присваивается значение одного типа, а выбирается значениедругого типа.
Такую гарантию можно получить, если заключить объединение в класс, в котором будетотслеживаться тип заносимого значения :class tok_val {public:enum Tag { I, D, S, N };private:union {const char* p;char v[8];long i;double d;};Tag tag;void check(Tag t) { if (tag != t) error(); }public:Tag get_tag() { return tag; }tok_val(const char* pp);tok_val(long ii){ i = ii; tag = I; }tok_val(double dd) { d = dd; tag = D; }long& ival(){ check(I); return i;double& fval(){ check(D); return d;const char*& sval() { check(S); return p;char* id(){ check(N); return v;};tok_val::tok_val(const char* pp){if (strlen(pp) <= 8) {tag = N;strncpy(v,pp,8);}else {tag = S;p = pp;}}}}}}// короткая строка// длинная строка// записывается только указательИспользовать класс tok_val можно так:void f(){tok_val t1("короткая");tok_val t2("длинная строка");char s[8];strncpy(s,t1.id(),8);strncpy(s,t2.id(),8);}// присваивается v// присваивается p// нормально// check() выдаст ошибку141Бьерн Страуструп.Язык программирования С++Описав тип Tag и функцию get_tag() в общей части, мы гарантируем, что тип tok_val можноиспользовать как тип параметра.
Таким образом, появляется надежная в смысле типов альтернативаописанию параметров с эллипсисом. Вот, например, описание функции обработки ошибок, котораяможет иметь один, два, или три параметра с типами char*, int или double:extern tok_val no_arg;void error(const char* format,tok_val a1 = no_arg,tok_val a2 = no_arg,tok_val a3 = no_arg);5.5 Конструкторы и деструкторыЕсли у класса есть конструктор, он вызывается всякий раз при создании объекта этого класса.
Если укласса есть деструктор, он вызывается всякий раз, когда уничтожается объект этого класса. Объектможет создаваться как:[1]автоматический, который создается каждый раз, когда его описание встречается привыполнении программы, и уничтожается по выходе из блока, в котором он описан;[2]статический, который создается один раз при запуске программы и уничтожается при еезавершении;[3]объект в свободной памяти, который создается операцией new и уничтожается операциейdelete;[4]объект-член, который создается в процессе создания другого класса или при создании массива,элементом которого он является.Кроме этого объект может создаваться, если в выражении явно используется его конструктор ($$7.3)или как временный объект ($$R.12.2).
В обоих случаях такой объект не имеет имени. В следующихподразделах предполагается, что объекты относятся к классу с конструктором и деструктором. Вкачестве примера используется класс table из $$5.3.1.5.5.1 Локальные переменныеКонструктор локальной переменной вызывается каждый раз, когда при выполнении программывстречается ее описание. Деструктор локальной переменной вызывается всякий раз по выходе изблока, где она была описана. Деструкторы для локальных переменных вызываются в порядке,обратном вызову конструкторов при их создании:void f(int i){table aa;table bb;if (i>0) {table cc;// ...}// ...}Здесь aa и bb создаются (именно в таком порядке) при каждом вызове f(), а уничтожаются они привозврате из f() в обратном порядке - bb, затем aa. Если в текущем вызове f() i больше нуля, то ccсоздается после bb и уничтожается прежде него.Поскольку aa и bb - объекты класса table, присваивание aa=bb означает копирование по членам bb в aa(см.