Структуры и Объединения
По определению 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() не пройдет }