Введение в язык Си++

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


По определению struct - это просто класс, все члены которого общие, то есть

struct s { ...

есть просто сокращенная запись

class s { public: ...

Структуры используются в тех случаях, когда скрытие данных неуместно.

Именованное объединение определяется как struct, в которой все члены имеют один и тот же адрес (см. #с.8.5.13). Если известно, что в каждый момент времени нужно только одно значение из структуры, то объединение может сэкономить пространство. Например, можно определить объединение для хранения лексических символов C компилятора:

union tok_val { char* p; // строка char v[8]; // идентификатор (максимум 8 char) 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 curr_val = 12; // ошибка: int присваивается tok_val'у

является недопустимым. Для того, чтобы это преодолеть, можно воспользоваться конструкторами:

union tok_val { char* p; // строка char v[8]; // идентификатор (максимум 8 char) long i; // целые значения double d; // значения с плавающей точкой



tok_val(char*); // должна выбрать между p и v tok_val(int ii) { i = ii; } tok_val() { d = dd; } };

Это позволяет справляться с теми ситуациями, когда типы членов могут быть разрешены по правилам для перегрузки имени функции (см. и #6.3.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(char* pp) { if (strlen(pp)

Таких ситуаций вообще-то лучше избегать.


Использование конструкторов не предохраняет от такого случайного неправильного употребления tok_val, когда сначала присваивается значение одного типа, а потом рассматривается как другой тип. Эта проблема решается встраиванием объединения в класс, который отслеживает, какого типа значение помещается:

class tok_val { char tag; union { char* p; char v[8]; long i; double d; }; int check(char t, char* s) { if (tag!=t) { error(s); return 0; } return 1; } public: tok_val(char* pp); tok_val(long ii) { i=ii; tag='I'; } tok_val(double dd) { d=dd; tag='D'; }

long ival() { check('I',"ival"); return i; } double fval() { check('D',"fval"); return d; } char* sval() { check('S',"sval"); return p; } char* id() { check('N',"id"); return v; } };

Конструктор, получающий строковый параметр, использует для копирования коротких строк strncpy(). strncpy() похожа на strcpy(), но получает третий параметр, который указывает, сколько символов должно копироваться:

tok_val::tok_val(char* pp) { if (strlen(pp)

Тип tok_val можно использовать так:

void f() { tok_val t1("short"); // короткая, присвоить v tok_val t2("long string"); // длинная строка, присвоить p char s[8]; strncpy(s,t1.id(),8); // ok strncpy(s,t2.id(),8); // проверка check() не пройдет }


Содержание раздела