Бьерн Страуструп. Язык программирования С++ Второе дополненное издание



Pdf көрінісі
бет109/256
Дата11.07.2022
өлшемі2,87 Mb.
#37591
1   ...   105   106   107   108   109   110   111   112   ...   256
5.4.5 Указатели на члены 
Можно брать адрес члена класса. Операция взятия адреса функции-члена часто оказывается полезной
поскольку цели и способы применения указателей на функции, о которых мы говорили в $$4.6.9, в 
равной степени относятся и к таким функциям. Указатель на член можно получить, применив операцию 
взятия адреса & к полностью уточненному имени члена класса, например, &class_name::member_name. 
Чтобы описать переменную типа "указатель на член класса X", надо использовать описатель вида X::*. 
Например: 
#include  
struct cl 

char* 
val; 
void print(int x) { cout << val << x << '\n'; } 
cl(char* v) { val = v; } 
}; 
Указатель на член можно описать и использовать так: 
typedef void (cl::*PMFI)(int); 
int main() 

cl z1("z1 "); 
cl z2("z2 "); 
cl* p = &z2; 
PMFI pf = &cl::print; 
z1.print(1); 
(z1.*pf)(2); 
z2.print(3); 
(p->*pf)(4); 

Использование typedef для замены трудно воспринимаемого описателя в С достаточно типичный 
случай. Операции .* и ->* настраивают указатель на конкретный объект, выдавая в результате функцию, 
которую можно вызывать. Приоритет операции () выше, чем у операций .* и ->*, поэтому нужны скобки. 
Во многих случаях виртуальные функции ($$6.2.5) успешно заменяют указатели на функции. 
5.4.6 Структуры и объединения 
По определению структура - это класс, все члены которого общие, т.е. описание 


Бьерн Страуструп.
Язык программирования С++ 
 
140 
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) 


Бьерн Страуструп.
Язык программирования С++ 
 
141 
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; 


pp; 
// записывается только указатель 


Использовать класс tok_val можно так: 
void f() 

tok_val 
t
1("короткая"); 
// присваивается v 
tok_val t2("длинная строка"); 
// присваивается p 
char 
s[8]; 
strncpy(s,t1.id(),8); 
// 
нормально 
strncpy(s,t2.id(),8); 
// 
check() 
выдаст ошибку 



Бьерн Страуструп.
Язык программирования С++ 
 
142 
Описав тип 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); 


Достарыңызбен бөлісу:
1   ...   105   106   107   108   109   110   111   112   ...   256




©emirsaba.org 2024
әкімшілігінің қараңыз

    Басты бет