Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Срауструп CPP.doc
Скачиваний:
4
Добавлен:
05.11.2018
Размер:
4.95 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);