Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
КРАТКИЙ ОБЗОР С.doc
Скачиваний:
1
Добавлен:
26.10.2018
Размер:
2.11 Mб
Скачать

5.4.6 Структуры и объединения

      По определению структура - это класс, все члены которого общие,       т.е. описание       struct s { ...       это просто краткая форма описания       class s { public: ...       Поименованное объединение определяется как структура, все члены       которой имеют один и тот же адрес ($$R.9.5). Если известно, что       в каждый момент времени используется значение только одного члена       структуры, то объявив ее объединением, можно сэкономить память.       Например, можно использовать объединение для хранения лексем       транслятора С:       union tok_val {       char* p; // строка       char v[8]; // идентификатор (не более 8 символов)       long i; // значения целых       double d; // значения чисел с плавающей точкой       };       Проблема с объединениями в том, что транслятор в общем случае       не знает, какой член используется в данный момент, и поэтому       контроль типа невозможен. Например:       void strange(int i)       {       tok_val x;       if (i)       x.p = "2";       else       x.d = 2;       sqrt(x.d); // ошибка, если i != 0       }       Кроме того, определенное таким образом объединение нельзя       инициализировать таким кажущимся вполне естественным способом:       tok_val val1 = 12; // ошибка: int присваивается tok_val       tok_val val2 = "12"; // ошибка: char* присваивается tok_val       Для правильной инициализации надо использовать конструкторы:       union tok_val {       char* p; // строка       char v[8]; // идентификатор (не более 8 символов)       long i; // значения целых       double d; // значения чисел с плавающей точкой       tok_val(const char*); // нужно выбирать между p и v       tok_val(int ii) { i = ii; }       tok_val(double dd) { d = dd; }       };       Эти описания позволяют разрешить с помощью типа членов неоднозначность       при перегрузке имени функции (см. $$4.6.6 и $$7.3). Например:       void f()       {       tok_val a = 10; // a.i = 10       tok_val b = 10.0; // b.d = 10.0       }       Если это невозможно (например, для типов char* и char[8] или int       и char и т.д.), то определить, какой член инициализируется, можно,       изучив инициализатор при выполнении программы, или введя       дополнительный параметр. Например:       tok_val::tok_val(const char* pp)       {       if (strlen(pp) <= 8)       strncpy(v,pp,8); // короткая строка       else       p = 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("короткая"); // присваивается v       tok_val t2("длинная строка"); // присваивается p       char s[8];       strncpy(s,t1.id(),8); // нормально       strncpy(s,t2.id(),8); // check() выдаст ошибку       }       Описав тип 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);