LEC_cpp4 (1119519)
Текст из файла
5 лекция - C++ (4).
Друзья класса.
Друг класса – это функция, не являющаяся членом этого класса, но имеющая доступ к private и protected членам класса.
Своих друзей класс объявляет сам в зоне описания класса с помощью служебного слова friend (т.е. механизм защиты не взламывается).
Функция-друг может быть описана внутри класса.
Если функций, имена которых совпадают с объявленной к.-л. классом функцией-другом, несколько, то другом считается только та, у которой в точности совпадает прототип.
Другом класса может быть:
- обычная функция: friend void f (...);
- функция-член другого класса: friend void Y::f (..);
- весь класс (каждая его функция-член является другом): friend class Y;
Рассмотрим пример:
class X {
int a;
friend void fff (X*, int); // здесь нет this !
public:
void mmm (int);
};
void fff (X* p, int i) {
p -> a = i;
}
void X::mmm (int i) {
a = i;
}
void f () {
X obj;
fff (&obj, 10);
obj.mmm (10);
}
Плюсы дружбы:
1. эффективность реализации, т.к. можно обходить ограничения доступа, предназначенные для обычных пользователей.
2. функция-друг нескольких классов позволяет упростить интерфейс этих классов;
3. функция-друг допускает преобразование параметра-объекта, а функция-член нет.
Дружба не обладает ни наследуемостью, ни транзитивностью.
Пример:
class A {
friend class B;
int a;
};
class B {
friend class C;
};
class C {
void f (A* p) {p -> a++} // ошибка, нет доступа к закр. членам класса А
};
class D: public B {
void f (A* p) {p -> a++} //ошибка, D не друг А
};
Перегрузка операций.
Для перегрузки встроенных операций С++ используется ключевое слово operator.
Перегружать операцию можно с помощью функции-члена, функции-друга либо обычной функции (что менее эффективно).
Нельзя перегружать: ‘.’ , ‘::’ , ‘?:’ , ‘.*’ , sizeof, и typeid !!!
//.* - выбор члена класса через указатель на член (obj.*p).
// typeid (expr) – оператор определения типа во время выполнения, будет рассмотрен позднее.
Пример1:
class complex {
double re, im;
public:
complex (double r = 0, double i = 0) {
re = r;
im = i;
}
complex operator+ (const complex & a) {
complex temp (re + a.re, im + a.im);
return temp;
}
... // (***) operator double () {return re;}
};
int main () {
complex x (1, 2), y (5, 8), z;
double t = 7.5;
z = x + y; // O.K. – x.operator+ (y);
z = z + t; // O.K. – z.operator+ (complex (t)); если есть (***), то
// неоднозначность: '+' - double или перегруженный
z = t + x; //Er.! – т.к. первый операнд по умолчанию – типа complex.
}
Пример2: перегрузим + с помощью функции друга:
class complex {
double re, im;
public:
complex (double r = 0, double i = 0) {
re = r;
im = i;
}
friend complex operator+ (const complex & a, const complex & b);
...
};
complex operator+ (const complex & a, const complex & b) {
complex temp (a.re + b.re, a.im + b.im);
return temp;
}
int main () {
complex x (1, 2), y (5, 8), z;
double t = 7.5;
z = x + y; // O.K. – x.operator+ (y);
z = z + t; // O.K. – z.operator+ (complex (t));
z = t + x; //O.K. – complex (t).operator+ (x);
}
Но если есть обе функции, то в первых двух операторах присваивания будет сгенерирована ошибка из-за неоднозначности выбора функции.
Пример3: перегрузка операций может производиться не только для операндов пользовательского типа, например, определим операцию умножения complex на double:
class complex {
...
public:
friend complex operator* (const complex & a, double b);
...
};
complex operator* (const complex & a, double b) {
complex temp (a.re * b, a.im * b);
return temp;
}
int main () {
complex x (1, 2), z;
double t = 7.5;
z = x * t; // O.K. – x.operator* (t);
z = t * x; // Er.! т.к. нет функции преобразования x --> double, но если бы
была, была бы неоднозначность: * - из double или из complex
}
В таких случаях обычно определяют еще одного друга с профилем:
complex operator* (double b, const complex & a);
Замечания:
- n-местные операции перегружаются функцией-членом с (n-1) параметром,
либо функцией-другом или обычной функцией с n параметрами;
- в любом случае сохраняется приоритет, ассоциативность и местность
операций;
- операции = , [] , () и -> можно перегрузить только нестатическими
методами класса, что гарантирует, что первым операндом будет сам объект,
к которому операция применяется
- особенности перегрузки операций ++ и --:
префиксная ++:
complex & operator ++ (); т.е. ++x; ~ x.operator++ ();
complex operator ++ () {
re = re+1;
im = im + 1;
return *this;
}
постфиксная ++:
complex operator ++ (int); т.е. x++; ~ x.operator++ (0);
complex operator ++ (int) {
complex c = *this;
re = re+1;
im = im + 1;
return c;
}
Пример перегрузки оператора «()» и оператора вывода «<<».
class Matrix {
double M [3][3];
public:
Matrix ();
double & operator () (int i, int j) {
return M [i][j];
}
friend ostream & operator << (ostream & s, const Matrix & a) {
for (int i = 0; i < 3 ; i ++) {
for (int j = 0; j < 3; j ++) s << a (i, j) << ' ';
s << endl;
}
return s;
}
};
Совместное использование функций (перегрузка - overloading).
О перегрузке можно говорить только для функций из одной зоны описания!
Алгоритм поиска и выбора функции:
-
выбираются только те перегруженные (одноименные) функции, для которых фактические параметры соответствуют формальным по количеству и типу (приводятся с помощью каких-либо преобразований).
-
для каждого параметра функции (отдельно и по очереди) строится множество функций, оптимально отождествляемых по этому параметру (best matching).
-
находится пересечение этих множеств:
- если это ровно одна функция – она и является искомой,
- если множество пусто или содержит более одной функции, генерируется сообщение об ошибке.
Пример1:
class X {public: X(int);...};
class Y {<нет конструктора с параметром типа int>...};
void f (X, int); // 1 пар.- ‘+’ 2 пар.-‘+’
void f (X, double); // - “ - ‘+’ - “ - ‘-‘
void f (Y, double); // отбрасывается на 1-м шаге
void g () {... f (1,1); ...} //которая из функций f будет вызвана?
Т.к. в пересечении множеств осталась одна функция f (X, int) – см. комментарий выше - вызов разрешим.
Пример2:
struct X {X(int);...};
void f (X, int); // 1 пар.- ‘-’ 2 пар.-‘+’
void f (int, X); // - “ - ‘+’ - “ - ‘-‘
void g () {... f (1,1); ...} //которая из функций f будет вызвана?
Т.к. пересечение множеств пусто – см. комментарий выше - вызов неразрешим.
Пример3:
void f (char);
void f (double);
void g () {... f (1); ...} // ?
Не всегда просто выполнить шаг 1 алгоритма, поэтому стандартом языка С++ закреплены правила сопоставления формальных и фактических параметров при выборе одной из перегруженных функций.
Правила для шага 1 алгоритма выбора перегруженной функции
а) точное отождествление;
б) отождествление при помощи расширений;
в) отождествление с помощью стандартных преобразований;
г) отождествление с помощью преобразований, определенных пользователем;
д) отождествление по ... .
Пункт а).
- точное совпадение,
- совпадение с точностью до typedef,
- тривиальные преобразования:
T[] <--> T*,
T <--> T&,
T --> const T, // в одну сторону!
T(...) <--> (T*)(...) .
Пример:
void f (float); void g () {... f (1.0); // f (double)
void f (double); f (1.0F); // f (float)
void f (int); f (1); // f (int);
...
}
Пункт б).
- целочисленные расширения:
char, short (signed и unsigned), enum, bool --> int ( или unsigned int, если не все значения могут быть представлены типом int – например, тип unsigned short не всегда помещается в int);
-
float --> double; double --> long double;
Пример:
void f (int); void g () {
short aa = 1; // short приводится к int
float ff = 1.0;
void f (double); f (ff); // f (double)
f (aa); // f (int)
}
неоднозначности нет, хотя short -> int & double, float -> int & double
Пункт в).
- все оставшиеся стандартные целочисленные и вещественные преобразования, которые могут выполняться неявно;
- преобразования указателей:
0 --> любой указатель,
любой указатель – void*,
derived* --> base* для однозначного доступного базового класса;
Пример:
void f (char); void g () { ... f (0); // неоднозначность, т.к. преобр.
void f (double); // int --> char и int --> double
// равноправны
}
Пункт г).
- с помощью конструкторов преобразования;
- с помощью функций преобразования – функций-членов класса с прототипом:
operator <тип> (); // преобразует объект класса к типу <тип>;
Пример:
struct S {
S (long); // long --> S
operator int (); // S --> int
...
};
void f (long); void f (char*);
void g (S); void g (char*);
void h (const S&); void h (char*);
void ex (S &a) {
f (a); // O.K. f ( (long) ( a.operator int()) ); т.е. f (long) - на шаге г).
g (1); // O.K. g ( S ( (long) 1) ); т.е. g (S) - на шаге г).
g (0); // O.K. g ( (char*) 0); т.е. g (char*) – но! на шаге в).
h (1); // O.K. h ( S ( (long) 1) ); т.е. h (const S&) – на шаге г).
}
Замечание 1.
Пользовательские преобразования применяются неявно только в том случае, если они однозначны!
Пример:
class Boolean {
int b;
public:
Boolean operator+ (Boolean);
Boolean (int i) { b = i != 0;}
operator int () {return b;}
...
};
void g () {
Boolean b (1), c (0); // O.K.
int k;
c = b+1; // Er.! т.к. может интерпретироваться двояко:
// b.operator int () +1 – целочисленный ‘+’ или
// b.operator+ (Boolean (1)) – Boolean ‘+’
k = b+1; // Er.! -- “ --
}
Замечание 2.
Допускается не более одного пользовательского преобразования для обработки одного вызова для одного параметра!
Пример:
class X { ... public: operator int (); ... };
class Y { ... public: operator X (); ... };
void f () {
Y a; int b;
b = a; // Er.! т.к. требуется a.operator X ().operator int ()
...
}
Но! явно можно делать любые преобразования, явное преобразование сильнее неявного.
Пункт д).
- отождествление по ...
Пример1:
class Real {
public:
Real (double);
...
};
void f (int, Real);
void f (int, ...); // можно и без ‘,’
void g () {
f (1,1); // O.K. f (int, Real);
f (1, “Anna”); // O.K. f (int, ...);
}
Пример2: Многоточие может приводить к неоднозначности:
void f (int);
void f (int ...);
void g () {...
f (1); // Er.! т.к. отождествление по первому параметру дает
// обе функции.
}
8
Характеристики
Тип файла документ
Документы такого типа открываются такими программами, как Microsoft Office Word на компьютерах Windows, Apple Pages на компьютерах Mac, Open Office - бесплатная альтернатива на различных платформах, в том числе Linux. Наиболее простым и современным решением будут Google документы, так как открываются онлайн без скачивания прямо в браузере на любой платформе. Существуют российские качественные аналоги, например от Яндекса.
Будьте внимательны на мобильных устройствах, так как там используются упрощённый функционал даже в официальном приложении от Microsoft, поэтому для просмотра скачивайте PDF-версию. А если нужно редактировать файл, то используйте оригинальный файл.
Файлы такого типа обычно разбиты на страницы, а текст может быть форматированным (жирный, курсив, выбор шрифта, таблицы и т.п.), а также в него можно добавлять изображения. Формат идеально подходит для рефератов, докладов и РПЗ курсовых проектов, которые необходимо распечатать. Кстати перед печатью также сохраняйте файл в PDF, так как принтер может начудить со шрифтами.