С++ обзор языка



страница9/10
Дата22.06.2019
Размер1.54 Mb.
1   2   3   4   5   6   7   8   9   10

12.5.2. Статическая память
Пример:
table tb1(100);
void f(int i)

{

static table tb2(200);

};
int main()

{

f(200);

// . . .

};
Здесь конструктор table() будет вызываться дважды: для tb1 и tb2. Деструктор ~table() также будет вызываться дважды: для уничтожения tb1 и tb2 после выхода из main(). Конструкторы для глобальных статических объектов в файле выполняются в порядке объявления переменных; деструкторы вызываются в обратном порядке. Конструктор локального статического объекта вызывается, как только поток управления достигнет описания объекта.
Вызов конструкторов и деструкторов для статических объектов играет в С++ очень важную роль, так как это очень удобный способ обеспечить надлежащую инициализацию и очистку данных в библиотеках.
12.5.3. Свободная память
Рассмотрим:
main()

{

table p* = new table(100);

table q* = new table(200);

delete p;

delete p; // возможна ошибка при исполнении

};
Конструктор и деструктор вызываются дважды, при этом q не уничтожается, а p уничтожается дважды. Обычно, то что объект не уничтожается, не является ошибкой, а лишь бесполезной тратой памяти. Двойное уничтожение p будет, как правило, серьезной ошибкой (обычно это приводит к бесконечному циклу в подпрограмме управления свободной паматью).
12.5.4. Компонентные объекты класса
Рассмотрим:
class classdef {

table members;

int no_of_members;

// . . .

classdef(int size);

~classdef();

};
Цель, очевидно, состоит в том, что объект класса classdef должен содержать таблицу элементов длиною size, а проблема состоит в вызове конструктора table() с параметром size. Сделать это можно так:
classdef::classdef(int size) : members(size)

{

no_of_memebrs = size;

// . . .

};
Параметры для конструктора компонента (здесь это table::table()) помещаются в описание конструктора объемлющего класса (здесь это classdef::classdef()). При этом конструктор компонента вызывается перед телом основного конструктора согласно следующему синтаксису:
ctor-инициализатор:

: список-инициализаторов-компонентов


список-инициализаторов-компонентов:

инициализатор-компонента

инициализатор-компонента, список-инициализаторов-компонентов
инициализатор-компонента:

полное-имя-класса (список-выражений opt)



идентификатор (список-выражений opt)
Идентификатор в инициализаторе-компонента - это идентификатор инициализируемого компонента. Если тип этого компонента не класс, выражение в скобках поставляет инициализирующее значение для компонента, в противном случае вызывается конструктор соответствующего класса. Если в классе есть несколько компонентов, которым нужны списки параметров для конструкторов, для них вызываются соответствующие конструкторы. Пример:
class classdef {

table members;

table friends;

int no_of_members;

// . . .

classdef(int size);

~classdef();

};
classdef::classdef(int size) : members(size), friends(size), no_of_memebrs(size)

{ //

};
Оба конструктора members и friends вызываются с параметром size.
Если конструктору компонента не нужно параметров, соответствующий инициализатор-компонента может опускаться:
classdef::classdef(int size) : members(size)

{

no_of_memebrs = size;

// . . .

};
Поскольку table::table() был определен с параметром 15, размер таблицы friends будет равен 15.
Когда объект, содержащий другие объекты, уничтожается, первым исполняется его деструктор, а затем - деструкторы компонентов в порядке, обратном порядку их объявления. Рассмотрим альтернативу компонентным объектам класса: указатели на объекты, инициализируемые в конструкторе.
class classdef {

table* members;

table* friends;

int no_of_members;

// . . .

classdef(int size);

~classdef();

};
classdef::classdef(int size)

{

memebrs = new table(size);

friends = new table; // размер таблицы по умолчанию

no_of_memebrs = size;

// . . .

};
Так как таблицы созданы посредством new, они должны уничтожаться посредством delete:
classdef::~classdef()

{

// . . .

delete members;

delete friends;

};
12.5.5. Массивы объектов класса
Чтобы объявить массив объектов класса, имеющего конструктор, этот класс должен иметь конструктор по умолчанию, который может вызываться без списка параметров, так как нет способа задания параметра конструктора в объявлении массива. Например,
table tb1[10]
создает массив из 10 объектов типа table, каждый из которых инициализируется вызовом конструктора table::table(15).
Когда массив уничтожается, деструктор должен вызываться для каждого его элемента. Для автоматических массивов это делается неявно при выходе из блока. Для массивов в свободной памяти уничтожение должно быть указано явным образом. Пример:
void f(int sz)

{

table* t1 = new table;

table* t2 = new table[sz];

// . . .

delete t1;

delete[] t2;

};
12.5.6. Конструкторы и деструкторы производных классов
В конструкторе производного класса вызываются конструкторы его базовых классов с соответствующими параметрами. Пример:
class employee {

char* name;

int department;

// . . .

public:

employee(char* n, int d);

// . . .

};
class manager: public employee {

int level, group;

// . . .

public:

manager(char* n, int d, l, g);

// . . .

};
Параметры конструктора базового класса указываются в описании конструктора производного класса. В этом отношении базовый класс рассматривается как компонент производного класса:
manager :: manager((char* n, int d, l, g) : employee(n, d), level(l), group(g) {};
Объекты производного класса конструируются снизу вверх: сначала объекты базовых классов, затем компоненты, а потом сам производный класс; уничтожаются они в обратном порядке.

13. Совмещение имен
Если в некоторой области действия имеется несколько различных объявлений функций с одним именем, это имя называется совмещенным (overloaded). Когда употребляется такое имя, нужная функция выбирается путем сопоставления типов фактических и формальных параметров. Пример:
double abs(double);

int abs(int);
abs(1); // вызывается abs(int)

abs(1.0); // вызывается abs(double)
Функции, типы которых различаются только типом результата, не могут иметь совмещенного имени.
Тип, описанный посредством typedef, является лишь синонимом указанного типа и потому функции, типы которых различаются только "типами по typedef", не могут иметь совмещенного имени. Пример:
typedef int Int;
void f(int i) { /* . . . */ }

void f(Int i) { /* . . . */ } // ошибка: повторное описание f
13.1. Отождествление объявлений
Два объявления функции с одним и тем же именем относятся к одной функции, если они лежат в одной области действия и имеют идентичные типы параметров. Компонентная функция производного класса лежит в иной области действия, нежели одноименная функция базового класса. Пример:
class B {

public int f(int);

};
class D : public B {

public int f(char*);

};
Здесь D::f(char*) скорее закрывает доступ к B::f(int), чем использует совмещенное имя.
13.2. Отождествление параметров
При вызове функции с данным именем необходимо выбрать ту, для которой фактические параметры подходят наилучшим образом. Для этого часто приходится делать преобразования типов параметров.
При отождествлении фактического параметра не рассматриваются последовательности преобразований, содержащие более одного пользовательского преобразования, как и те, что могут быть сокращены удалением одного или нескольких преобразований. Такая последовательность называется последовательностью лучшего отождествления (best matching sequence). Например, int->float->double не последовательность лучшего отождествления, так как она содержит более короткую последовательность int->double
Следующие тривиальные преобразования над типом Т не влияют на то, какая из двух последовательностей преобразований лучше:
ИЗ В

T T&


T& T

T[] T*


T(параметры) T*(параметры)

T const T

T volatile T

T* const T*

T* volatile T*

T& const T&



T& volatile T&
Последовательности преобразований, различающиеся только порядком преобразований, не различаются. Используются следующие правила:
1. Точное отождествление: последовательности из нуля или более тривиальных преобразований лучше, чем любые другие последовательности. Из них те, которые не преобразуют Т* к const T*, T* к volatile T*, T& к const T& и T& к volatile T&, лучше. Примеры:
void f(char);

void f(int);

void f(unsigned);

void f(long);
void g()

{

f('c'); // вызов f(char)

f(1u); // вызов f(unsigned)

f(1); // вызов f(int)

f(1L); // вызов f(long)

}
Можно описывать функции, различающиеся лишь словом const в указателе или ссылке:
void f(char*);

void f(const char*);
void g(char* pc, const char* pcc)

{

f(pc); // вызов f(char*)

f(pcc); // вызов f(const char*)

}
2. Отождествление при помощи расширений: из последовательностей, не названных в [1], те, которые содержат лишь целочисленные расширения (4.1).
void f(int);

void f(double);
void g()

{

short aa = 1;

float ff = 1.0;

f(aa); // вызов f(int)

f(ff); // вызов f(double)

}
Без этого правила вызовы были бы неоднозначными, так как short может быть преобразовано и в int и в double, как и float может быть преобразовано и в int и в double.
3. Отождествление при помощи стандартных преобразований: из последовательностей, не названных в [2], те, которые содержат стандартные (4.1-4.8) и тривиальные преобразования, лучше других. Из них, если В - открытый прямой или косвенный производный от А, то преобразование B* в A* лучше, чем в void* или const void*; кроме того, если С - открытый прямой или косвенный производный от В, то преобразование С* в В* лучше, чем в A*, и преобразование С& в В& лучше, чем в A&.
Это правило гарантирует, что все неоднозначности между стандартными преобразованиями обнаруживаются и все стандартные преобразования считаются эквивалентными. Пример:
void f(char);

void f(float);
void g()

{

f(1); // неоднозначность: f(char) или f(float)

f(1L); // неоднозначность: f(char) или f(float)

}
4. Отождествление посредством преобразований, определенных пользователем: из последовательностей, не названных в [3], те, которые включают только пользовательские преобразования (12.3), стандартные преобразования (4) и тривиальные преобразования, лучше других.
5. Отождествление по многоточию: последовательности, содержащие отождествление с многоточием, хуже всех других.
13.3. Адрес функции с совмещенным именем
Употребление имени функции без параметров приводит к выбору той (единственной) среди функций с данным именем из текущей области действия, которая точно соответствует цели. Целью может быть:
инициализируемый объект (8.4);

левая часть присваивания (5.17);

формальный параметр функции (5.2.2);

тип возвращаемого функцией значения (8.2.5).


Примеры:
int f(double);

int f(int);

int (*pdf)(double) = &f;

int (*pfi)(int) = &f;


13.4. Совмещенные знаки операций
Большинство операций могут иметь совмещенные знаки операций.
имя-функции-операции:

operator операция
операция: одна из

new delete

+ - * / % ^ & | ~ ! = < > += -= *= /= %= ^= &= |= << >> <<= >>=

!= <= >= && || ++ -- , ->* -> () []
Следующие знаки операций не могут быть совмещены: . .* :: ?:
Функции-операции (operator functions) обычно вызываются не прямо, а для выполнения операций. К ним, однако, можно обратиться и явно, например:
complex z = a.operator+(b); // complex z = a+b;
Вызов функции
первичное-выражение (список-выражений opt)
рассматривается как бинарная операция с первичным-выражением в качестве первого операнда и возможным списком-выражений - в качестве второго. Таким образом, фраза х(пар1, пар2) понимается как вызов x.operator()(пар1, пар2).
Индексация
первичное-выражение [выражение]
также рассматривается как бинарная операция. Поэтому выражение x[y] интерпретируется как x.operator[](y).

14. Шаблоны
Шаблон класса (class template) определяет данные и операции потенциально неограниченного множества родственных типов. Шаблон функции (function template) определяет потенциально неограниченное множество родственных функций.
14.1. Шаблоны
объявление-шаблона:

template <список-параметров-шаблона> объявление

список-параметров-шаблона:

параметр-шаблона

список-параметров-шаблона, параметр-шаблона

параметр-шаблона:

типовый-параметр

объявление-параметра

типовый-параметр:



class идентификатор
Объявление в объявлении шаблона должно объявлять или описывать класс или функцию.
Типовый-параметр объявляет свой идентификатор в области действия описания шаблона как имя-типа.
Имена шаблонов подчиняются обычным правилам области действия и управления доступом.
Объявление-шаблона является объявлением и может быть только глобальным.
14.2. Шаблоны классов
Шаблон класса задает способ построения отдельных классов. Например, шаблоны класса vector можно объявить примерно так:
template class vector {

T* v;

int sz;

public

vector(int);

T& operator [](int);

T& elem(int i) {return v[i]; };

// . . .

};
Префикс template указывает, что объявляется шаблон с типовым параметром Т. Теперь посредством имени-класса-по-шаблону можно образовать конкретный класс:
имя-класса-по-шаблону:

имя-шаблона <список-факт-пар-шаблона>

список-факт-пар-шаблона:

фактический-параметр-шаблона

список-факт-пар-шаблона, фактический-параметр-шаблона

фактический-параметр-шаблона:

выражение

имя-типа
Имя-класса-по-шаблону является именем-класса (9). Типы фактических-параметров-шаблона в имени-класса-по-шаблону отождествляются с соответствующими (по порядку) типами из списка-параметров-шаблона и должны соответствовать им по количеству.


Другие фактические-параметры-шаблона должны быть константными-выражениями, адресами объектов или функций с внешним связыванием или статических компонентов класса (т.е. допускать статический контроль типов).
Пример:
vector v1(20);

vector v2(30);

typedef vector cvec; // заведение синонима

cvec v3(40);

Класс, сгенерированный из шаблона (например vector), называется шаблонным классом (template class).


14.3. Эквивалентность типов
Два имени-класса-по-шаблону обозначают один класс, если совпадают их шаблоны и их фактические параметры имеют совпадающие значения. Пример:
template class buffer;
buffer x;

buffer y;

buffer z;
объявляет x и y, в отличае от z, как переменные одного и того же типа, а
template

class list { /* . . . */};
list x1;

list x2;

list x3;

list x4;
объявляет x2 и x3 как переменные одного типа, отличного от типа переменных x1 и x4.
14.4. Шаблоны функций
Шаблон функций определяет потенциально неограниченное множество функций, генерируемых из шаблона. Семейство функций сортировки можно объявить, например, следующим образом:
template void sort(vector);
Параметры шаблона при генерации конкретной функции явным образом не задаются, а выводятся из типов аргументов, поэтому шаблонная функция рассматривается как множество функций с совмещенным именем. Пример:
viod f(vector& cv, vector& ci)

{

sort(cv); // вызывается sort(vector)

sort(ci); // вызывается sort(vector,int>)

}
Функция, сгенерированная из шаблона, называется шаблонной функцией (template function). Шаблонная функция может совмещать имя с другими шаблонными и обыкновенными функциями. Распознование функции в таком случае выполняется в три шага:
1. Поиск обычной функции, параметры которой в точности соответствуют требуемым; если таковая находится, обращение производится к ней.
2. Поиск шаблона функции, из которого может быть сгенерирована функция при точном соответствии параметров; если она найдена, она вызывается.
3. Поиск обычной функции, типы формальных параметров которой лучше всего соответствуют фактическим параметрам вызова; если она найдена, она вызывается.
Если отождествление не найдено, фиксируется ошибка. Пример:
template T max(T a, T b) {return a>b? a : b;};
void f(int a, int b, char c, char d)

{

int m1 = max(a, b); // max(int, int)

char m2 = max(c, d); // max(char, char)

int m3 = max(a, c); // ошибка: нельзя сгенерировать max(int, char)
Заметим, что при отождествлении с шаблоном не применяются даже тривиальные преобразования и потому в последнем примере вызов не сводится к вызову

max(a, int(c)). Однако добавление
int max(int, int);
приведет к разрешению третьего вызова, поскольку теперь вызов max(a, c) будет отождествлен с этой функцией после применения к параметру с стандартного преобразования char в int.
Если на каком-либо этапе будет найдено больше одного подходящего определения, также фиксируется ошибка.
Для генерации отдельных версий шаблона необходимо описание шаблона; для порождения обращений к этим отдельным версиям достаточно объявления шаблона.
Все параметры шаблона функции должны быть типовыми параметрами и каждый из них должен быть использован в типах параметров. Пример:
template T* create(); // ошибка
14.5. Объявления и описания
Использование имени-класса-по-шаблону составляет объявление шаблонного класса. Вызов шаблона функции или взятие его адреса является объявлением шаблонной функции.
Никаких операций, требующих описания шаблона класса, не может быть выполнено над шаблонным классом, пока компилятору не доступен сам шаблон класса. После этого отдельный шаблонный класс считается описанным непосредственно перед первым объявлением, его именующим.
14.6. Шаблоны компонентных функций
Компонентная функция шаблонного класса неявно оказывается шаблонной функцией, параметрами шаблона которой являются параметры шаблона класса. На пример,
template class vector {

T* v;

int sz;

public

vector(int);

T& operator[](int);

T& elem(int i) {return v[i]; };

// . . .

};
неявно объявляет три шаблона функции. Функция индексации могла бы быть описана следующим образом:
template T& vector::operator[](int i);

{

if (i < 0 || sz <= i) error("vector: выход за границу");

return v[i];

}
Параметр шаблона для vector::operator[]() определяется вектором, к которому применяется индексация. Пример:
vector v1(20);

vector v2(30);
v1[3] = 7; // vector::operator[]()

v2[3] = complex(7,8); // vector::operator[]()
14.7. Друзья
Функция-друг шаблона не становится неявно шаблонной функцией. Пример:
template class task {

// . . .

friend void next_time();

friend task* preemt(task*);

friend task* prmt(task*); // ошибка

};
Здесь next_time становится другом всех task классов, и каждый task имеет соответствующего типа функцию preemt() в качестве друга. Функции preemt() должны быть описаны как шаблон:
template task* preemt(task* t) { /* . . . */}
Объявление prmt(task*) ошибочно, потому что не существует типа task, а лишь отдельные шаблонные типы task(int), task(compleх) и т.д.
14.8. Статические компоненты и переменные
Каждый шаблонный класс или функция, сгенерированные по шаблону, обладают своими экземплярями всех статических переменных или компонентов. Пример:
template class X {

static T s;

// . . .

};
X aa;

X bb;
Здесь X имеет статический компонент s типа int, а X имеет статический компонент s типа char*. В том же духе:
template f(T* p)

{

static T s;

// . . .

};
void g(int a, char* b)

{

f(&a);

f(&b)

}
Здесь f(int*) имеет статическую локальную переменную s типа int, а f(char**) имеет статическую локальную переменную s типа char*.



Поделитесь с Вашими друзьями:
1   2   3   4   5   6   7   8   9   10


База данных защищена авторским правом ©vossta.ru 2019
обратиться к администрации

    Главная страница