Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
c++book1.doc
Скачиваний:
0
Добавлен:
01.05.2025
Размер:
2 Мб
Скачать
☆

5.4.5 Указатели на Члены

Можно брать адрес члена класса. Получение адреса функции члена часто бывает полезно, поскольку те цели и причины, ко­торые приводились в #4.6.9 относительно указателей на функ­ции, в равной степени применимы и к функциям членам. Однако, на настоящее время в языке имеется дефект: невозможно описать выражением тип указателя, который получается в результате этой операции. Поэтому в текущей реализации приходится жуль­ничать, используя трюки. Что касается примера, который приво­дится ниже, то не гарантируется, что он будет работать. Ис­пользуемый трюк надо локализовать, чтобы программу можно было преобразовать с использованием соответствующей языковой конс­трукции, когда появится такая возможность. Этот трюк исполь­зует тот факт, что в текущей реализации this реализуется как первый (скрытый) параметр функции члена*:

--------------------

* Более поздние версии С++ поддерживают понятие указа­тель на член: cl::* означает "указатель на член класса cl". Например:

typedef void (cl::*PROC)(int);

PROC pf1 = &cl::print; // приведение к типу ненужно

PROC pf2 = &cl::print;

Для вызовов через указатель на функцию член используются операции . и ->. Например:

(z1.*pf1)(2);

((&z2)->*pf2)(4);

(прим. автора)

#include <stream.h>

struct cl

(*

char* val;

void print(int x) (* cout << val << x << "\n"; *);

cl(char* v) (* val = v; *)

*);

// ``фальшивый'' тип для функций членов:

typedef void (*PROC)(void*, int);

main()

(*

cl z1("z1 ");

cl z2("z2 ");

PROC pf1 = PROC(&z1.print);

PROC pf2 = PROC(&z2.print); z1.print(1); (*pf1)(&z1,2); z2.print(3); (*pf2)(&z2,4);

*)

Во многих случаях можно воспользоваться виртуальными функциями (см. Главу 7) там, где иначе пришлось бы использо­вать указатели на функции.

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

По определению 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; *)

*);

Это позволяет справляться с теми ситуациями, когда типы членов могут быть разрешены по правилам для перегрузки имени функции (см. #4.6.7 и #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) <= 8)

strncpy(v,pp,8); // короткая строка

else

p = 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) <= 8) (* // короткая строка

tag = 'N'

strncpy(v,pp,8); // скопировать 8 символов

*)

else (* // длинная строка

tag = 'S';

p = 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() не пройдет

*)

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]