Проектирования. Диаграммы классов. Отношения классов: ассоциация, наследование,




Операция delete

Операция delete уничтожает объект, созданный с помощью new.

Результат имеет тип void. Операндом delete должен быть указатель, который возвращает new. Эффект применения операции delete к указателю, который не получен в результате операции new без задания параметры-new, считается неопределенным и обычно приводит к опасным

последствиям. Однако гарантируется, что удаление по указателю с нулевым значением безопасно.

Результат попытки доступа к удаленному объекту неопределен, а удаление объекта может изменить его значение. Более того, если выражение, задающее объект, является изменяемым адресом, его значение после удаления неопределено.

Нельзя удалять указатель на константу.

Операция delete вызывает деструктор (если он есть) для объекта, на который настроен ее операнд.

Для освобождения памяти, отведенной под указываемый объект,

операция delete вызывает функцию operator delete.

Для объектов, не имеющих тип класс (в том числе и для массивов

классов), используется глобальная функция::operator delete().

Для объекта типа класс T вызывается функция T::operator delete(), если она есть (используя обычные правила просмотра при поиске членов класса и производных от него классов), в

противном случае вызывается глобальная функция::operator delete().

Обращение::delete гарантирует, что будет вызываться глобальная функция::operator delete(), даже если существует T::operator delete().

Для удаления массивов используется обращение вида

delete [ ] выражение-приведения

Здесь выражение должно указывать на массив. Если есть деструкторы, они будут вызываться для удаления указанных объектов.

Результат удаления массива с помощью простого обращения delete неопределен, так же как и удаление одиночного объекта с помощью delete [].

Операция:: Существует четыре области видимости: локальная, функция, файл и класс.

Локальная: Имя, описанное в блоке, является локальным в этом блоке и может использоваться только в нем и в блоках, содержащихся в этом блоке и появляющихся после момента описания.

Имена формальных параметров рассматриваются, как если бы они были описаны в самом объемлющем блоке этой функции.

Функция: Метки можно использовать повсюду в функции, в которой они описаны. Только метки имеют область видимости, совпадающую с функцией.

Файл: Имя описанное вне всех блоков и классов имеет область видимости файл и может быть использовано в единице трансляции, в которой оно появляется после момента описания. Имена,

описанные с файловой областью видимости, называются глобальными.

Класс: Имя члена класса является локальным в своем классе и оно может быть использовано только в функции-члене этого класса, или после операции., применяемой к объекту данного

класса или объекту производного класса, или после операции ->, применяемой к указателю на объект данного класса или на объект производного класса, или после

операции разрешения::, примененной к имени данного или производного класса. Имя, введенное с помощью операции friend, принадлежит той же области определенности, что и класс, содержащий описание friend. Класс, впервые описанный в операторе return или в типе параметра, принадлежит к глобальной области видимости. Специальные соглашения действуют на имена, введенные при описании параметров функции и в описаниях friend.

Имя может быть скрыто явным описанием того же имени в объемлющем блоке или классе. Скрытое имя члена класса все-таки можно использовать, если оно предваряется именем класса, к которому применена операция::. Скрытое имя объекта, функции, типа или элемента перечисления с файловой областью видимости можно использовать, если оно предваряется унарной операцией::.


 

№9_Объявление функций. Аргументы по умолчанию

Функция - это именованная часть программы, к которой можно обращаться из других частей программы столько раз, сколько потребуется. Рассмотрим программу, печатающую степени числа 2:

extern float pow(float, int); //pow() определена в другом месте

main()

{ for (int i=0; i<10; i++) cout << pow(2,i) << "\n"; }

Первая строка функции - описание, указывающее, что pow - функция, получающая параметры типа float и int и возвращающая float. Описание функции используется для того, чтобы сделать определенными обращения к функции в других местах.

При вызове тип каждого параметра функции сопоставляется с ожидаемым типом точно так же, как если бы инициализировалась переменная описанного типа. Это гарантирует надлежащую проверку и преобразование типов. Например, обращение pow(12.3,"abcd") вызовет недовольство компилятора, поскольку "abcd" является строкой, а не int. При вызове pow(2,i) компилятор преобразует 2 к типу float, как того требует функция. Функция pow может быть определена например так:

float pow(float x, int n)

{

if (n < 0) error("извините, отрицательный показатель для pow()");

switch (n) {

case 0: return 1;

case 1: return x;

default: return x*pow(x,n-1);

}

}

Первая часть определения функции задает имя функции, тип возвращаемого ею значения (если таковое имеется) и типы и имена ее параметров (если они есть). Значение возвращается из функции с помощью оператора return.

Разные функции обычно имеют разные имена, но функциям, выполняющим сходные действия над объектами различных типов, иногда лучше дать возможность иметь одинаковые имена. Если типы их параметров различны, то компилятор всегда может различить их и выбрать для вызова нужную функцию. Может, например, иметься одна функция возведения в степень для целых переменных и другая для переменных с плавающей точкой:

overload pow;

int pow(int, int);

double pow(double, double);

//...

x=pow(2,10);

y=pow(2.0,10.0);

Описание

overload pow;

сообщает компилятору, что использование имени pow более чем для одной функции является умышленным.

Если функция не возвращает значения, то ее следует описать как void:

void swap(int* p, int* q) // поменять местами

{

int t = *p;

*p = *q;

*q = t;

}

Значения формальных параметров по умолчанию.

Спецификация формальных параметров - это либо пусто, либо void, либо список спецификаций отдельных параметров, в конце которого может быть поставлено многоточие. Спецификация каждого параметра в определении функции имеет вид:

тип имя_параметра

тип имя_параметра = умалчиваемое_значение

Как следует из формата, для параметра может быть задано (а может отсутствовать) умалчиваемое значение. Это значение используется в том случае, если при обращении к функции соответствующий параметр опущен. При задании начальных (умалчиваемых) значений должно соблюдаться следующее соглашение. Если параметр имеет умалчиваемое значение, то все параметры, специфицированные справа от него, также должны иметь начальные значения.

void func(double a, char c='*', int i=2);

func(10.6); // правильно

func(12.6,'+'); // правильно

func(12.6,'-',3); // правильно

func(12.6,,3); // неправильно

 

 

№10_Перегрузка функций

Цель перегрузки функций состоит в том, чтобы функция с одним именем по-разному выполнялась и возвращала разные значения при обращении к ней с разными по типам и количеству фактическими параметрами. Например, может потребоваться функция, возвращающая максимальное из значений элементов одномерного массива, передаваемого ей в качестве параметра. Массивы, использованные как фактические параметры, могут содержать элементы разных типов, но пользователь функции не должен беспокоиться о типе результата. Функция всегда должна возвращать значение того же типа, что и тип массива - фактического параметра.

Для обеспечения перегрузки функций необходимо для каждого имени определить, сколько разных функций связано с ним, т.е. сколько вариантов сигнатур допустимы при обращении к ним. Предположим, что функция выбора максимального значения элемента из массива должна работать для массивов типа int, long, float, double. В этом случае придется написать четыре разных варианта функции с одним и тем же именем.

Распознавание перегруженных функций при вызове выполняется по их сигнатурам. Перегруженные функции поэтому должны иметь одинаковые имена, но спецификации их параметров должны различаться по количеству и (или) по типам, и (или) по расположению.

При использовании перегруженных функций нужно с осторожностью задавать начальные значения их параметров.

 

 

#12_С++ как СИ с классами. Определение класса. Конструкторы и деструкторы

Класс - это тип структуры, расширенный таким образом, что позволяет включать в описание не только элементы данных, но и функции.

Структуру классического Си можно рассматривать, как предшественницу класса. Объединяя программный код с данными, структура может служить элементарной формой класса.

Рассмотрим реализацию понятия даты с использованием struct для того, чтобы определить представление даты date и множества функций для работы с переменными этого типа:

struct date { int month, day, year; };

// дата: месяц, день, год }

date today;

void set_date(date*, int, int, int);

void next_date(date*);

void print_date(date*);

//...

Функции-члены и данные-члены. Никакой явной связи между функциями и типом данных в этом примере нет. Такую связь можно установить, описав функции как члены структуры. Эти функции могут действовать на данные, содержащие в самой структуре. По умолчанию при объявлении структуры ее данные и функции являются общими, то есть у объектов типа структура нет ни инкапсуляции, ни защиты данных:

struct date {

int month, day, year;

void set(int, int, int);

void get(int*, int*, int*);

void next();

void print();

};

Функции, описанные таким образом, называются функциями - членами и могут вызываться только для специальной переменной соответствующего типа с использованием стандартного синтаксиса для доступа к данным - членам структуры.

Поскольку разные структуры могут иметь функции члены с одинаковыми именами, при определении функции члена необходимо указывать имя структуры, связывая их с помощью оператора видимости::

void date::next()

{

if (++day > 28) {

// делает сложную часть работы

}

}

В функции члене имена членов могут использоваться без явной ссылки на объект. В этом случае имя относится к члену того объекта, для которого функция была вызванаИнтерфейсы и реализация. Описание date в предыдущем примере дает множество функций для работы с date, но не указывает, что эти функции должны быть единственными для доступа к объектам типа date. Это ограничение можно наложить используя вместо struct class:

class date {

int month, day, year;

public:

void set(int, int, int);

void get(int*, int*, int*);

void next();

void print();

};

Метка public делит тело класса на две части. Имена в первой, закрытой части (private), могут использоваться только функциями членами. Вторая, открытая часть, составляет интерфейс к объекту класса. Обе эти части составляют реализацию объекта

 

 

№14_Дружественные функции

Иногда желательно дать функциям не членам возможность доступа к закрытой части класса. У класса есть механизм предоставления права доступа к своей закрытой части функциям не членам. Просто в описание класса помещается описание функции, перед которым стоит ключевое слово friend. Например, если имеется

class Vec; // Vec - имя класса

class vector {

friend Vec operator+(Vec, Vec);

//...

};

То вы можете написать

Vec operator+(Vec a, Vec b)

{

int s = a.size();

if (s!= b.size()) error("плохой размер вектора для +");

Vec& sum = *new Vec(s);

int* sp = sum.v;

int* ap = a.v;

int* bp = b.v;

while (s--) *sp++ = *ap++ + *bp++;

return sum;

}

Одним из особенно полезных аспектов механизма friend является то, что функция может быть другом двух и более классов. Чтобы увидеть это, рассмотрим определение vector и matrix, а затем определение функции умножения (см. #?.8.8 <ref8.htm>).

Теперь, наконец, можно обсудить, в каких случаях для доступа к закрытой части определяемого пользователем типа использовать члены, а в каких - друзей. Некоторые операции должны быть членами: конструкторы, деструкторы и виртуальные функции (см. следующую главу), но обычно это зависит от выбора. Рассмотрим простой класс X:

class X {

//...

X(int);

int m();

friend int f(X&);

};

Внешне не видно никаких причин делать f(X&) другом дополнительно к члену X::m() (или наоборот), чтобы реализовать действия над классом X. Однако член X::m() можно вызывать только для "настоящего объекта", в то время как друг f() может вызываться для объекта, созданного с помощью неявного преобразования типа. Например:

void g()

{

1.m(); // ошибка

f(1); // f(x(1));

}

Поэтому операция, изменяющее состояние объекта, должно быть членом, а не другом. Для определяемых пользователем типов операции, требующие в случае фундаментальных типов операнд lvalue (=, *=, ++ и т.д.), наиболее естественно определяются как члены. И наоборот, если нужно иметь неявное преобразование для всех операндов операции, то реализующая ее функция должна быть другом, а не членом. Это часто имеет место для функций, которые реализуют операции, не требующие при применении к фундаментальным типам lvalue в качестве операндов (+, -, || и т.д.).

Если никакие преобразования типа не определены, то оказывается, что нет никаких существенных оснований в пользу члена, если есть друг, который получает ссылочный параметр, и наоборот. В некоторых случаях программист может предпочитать один синтаксис вызова другому. Например, оказывается, что большинство предпочитает для обращения матрицы m запись m.inv(). Конечно, если inv() действительно обращает матрицу m, а не просто возвращает новую матрицу, обратную m, ей следует быть другом.

При прочих равных условиях выбирайте, чтобы функция была членом: никто не знает, вдруг когда-нибудь кто-то определит операцию преобразования. Невозможно предсказать, потребуют ли будущие изменения изменить статус объекта. Синтаксис вызова функции члена ясно указывает пользователю, что объект можно изменить; ссылочный параметр является далеко не столь очевидным. Кроме того, выражения в члене могут быть заметно короче выражений в друге. В функции друге надо использовать явный параметр, тогда как в члене можно использовать неявный this. Если только не применяется перегрузка, имена членов обычно короче имен друзей.

 

№15_Сходства и различия классов, структур и объединений. Совместимость СИ и С++

По определению struct - это просто класс, все члены которого общие, то есть struct s {...

есть просто сокращенная запись class s { public:...

Структуры используются в тех случаях, когда скрытие данных неуместно.

Именованное объединение определяется как struct, в которой все члены имеют один и тот же адрес. Если известно, что в каждый момент времени нужно только одно значение из структуры, то объединение может сэкономить пространство. Например, можно определить объединение для хранения лексических символов 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; } };

Это позволяет справляться с теми ситуациями, когда типы членов могут быть разрешены по правилам для перегрузки имени. Например:

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

}

 


№16_Классы и объекты. Определение класса и области видимости. Опережающее объявление класса

Класс - тип данных, определенный пользователем, и представляющий из себя набор данных разных типов и набор операций для манипулирования этими данными.

Класс - это тип структуры, расширенный таким образом, что позволяет включать в описание не только элементы данных, но и функции.

Существует четыре области видимости: локальная, функция, файл и класс.

Локальная: Имя, описанное в блоке, является локальным в этом блоке и может использоваться только в нем и в блоках, содержащихся в этом блоке и появляющихся после момента описания.

Имена формальных параметров рассматриваются, как если бы они были описаны в самом объемлющем блоке этой функции.

Функция: Метки можно использовать повсюду в функции, в которой они описаны. Только метки имеют область видимости, совпадающую с функцией.

Файл: Имя описанное вне всех блоков и классов имеет область видимости файл и может быть использовано в единице трансляции, в которой оно появляется после момента описания. Имена,

описанные с файловой областью видимости, называются глобальными.

Класс: Имя члена класса является локальным в своем классе и оно может быть использовано только в функции-члене этого класса, или после операции., применяемой к объекту данного

класса или объекту производного класса, или после операции ->, применяемой к указателю на объект данного класса или на объект производного класса, или после операции разрешения::, примененной к имени данного или производного класса. Имя, введенное с помощью операции friend, принадлежит той же области определенности, что и класс, содержащий описание friend. Класс, впервые описанный в операторе return или в типе параметра, принадлежит к глобальной области видимости. Специальные соглашения действуют на имена, введенные при описании

параметров функции (и в описаниях friend.

Имя может быть скрыто явным описанием того же имени в объемлющем блоке или классе. Скрытое имя члена класса все-таки можно использовать, если оно предваряется именем класса, к которому применена операция::. Скрытое имя объекта, функции, типа или элемента перечисления с файловой областью видимости можно использовать, если оно предваряется унарной операцией::. В дополнении к этому, имя класса может быть скрыто именем объекта, функции или элемента перечисления, имеющего ту же область видимости. Если класс и объект, или функция, или элемент перечисления описаны (в любом порядке) с одинаковым именем в одной области видимости, то имя класса становится скрытым. Имя класса, скрытое в локальной области видимости или в области видимости класса именем объекта, функции или элемента перечисления, все-таки можно использовать, если предварить его подходящей спецификацией class, struct или union. Аналогично, скрытое имя элемента перечисления можно использовать, если предварить его спецификацией типа enum. В $$R.10.4 приводится сводка правил области видимости.

Моментом описания имени считается момент завершения описателя имени, предшествующей части инициализации (если она есть).

Например: int x = 12; { int x = x; }

Здесь второе x инициализируется своим собственным (неопределенным) значением.

Моментом описания элемента перечисления считается момент сразу

после появления его идентификатора, например: enum { x = x };

Здесь элемент перечисления x опять инициализируется своим собственным

(неопределенным) значением.

Опережающее объявление class Point2D; // Это имя будет определено в проге далее

Использование опережающего объявления возможно только при определении ссылок и указателей на объект класса, т.е. в тех случаях, когда не важна внутренняя структура класса.


№17_Использование спецификатора класса памяти static. Статические компоненты класса

Класс памяти определяет время жизни объектов и их нахождение в памяти.

Статические данные определяются при запуске и существуют до конца исполнения.

Автоматические объекты создаются в стеке в момент входа в блок и завершают свое существование при выходе изблока.

Динамические объекты создаются и уничтожаются по требованию програмера.

Использование static при объявлении глобальной переменной скрывают ее от других модулей. А если использовать static в блоке, то при выходе из блока данное остается в памяти.

Каждый объект класса имеет свою копию членов-данных класса. Иногда необходимо чтобы все объекты одного класса имели доступ к одной переменной, а не к копии. Например, счетчик объектов. Для того, чтобы элемент-данное класса разделилось в одном для всех классов, его необходимо объявить со спецификатором static. Если он объявлен, то он будет создан в момент запуска проги в одном экземпляре для всех объектов класса.

Т.о. статическое данное класса можно рассматривать как глобальную переменную класса, но с областью видимости класса.

class Point2D

{

double x,y;

static int count;

public:

Point2d(double _x, double _y)

{

x=_x; y=_y;

count++;

}

~Point2D() {count--}

};

Для обеспечения к закрытым элементам класса (статическим), должны быть предусмотрены открытые статические член-функции.

Т.к. статические данные находятся вне любого объекта, то естественно, что объект статической ф-ции не получает в качестве параметра указатель this и поэтому может работать только статическими членами-данными класса.

static

int GetCount() {return count;}

cout << Point2D::GetCount() << "\n";

Создание и иниц. происходит в начале запуска проги в порядке поступления их определений на вход компоновщика. Однако, не следует проектировать прогу таким образом, чтобы работа их зависела от порядка создания статич. объектов.

 

№18_ Константные объекты и константные методы

имя_типа * const this

const имя_типа *const this

Если метод не встраиваемый, то слово const должно быть в объявлении и определении. В связи с этим, могут существовать два одноименных метода, отличающихся только наличием или отсутствием атрибута const. Для const-объектов будет вызываться только константный метод. При существовании одного лишь конст. метода он будет вызываться для любых объектов, т.к. преобразование не конст. объекта к конст. - безопасно.

Иногда требуются исключения из правил доступа, когда некоторой функции или

классу требуется разрешить доступ к личной части объекта класса. Это

согласуется с тем принципом, что сам класс определяет права доступа к

своим объектам "со стороны". К средствам контроля доступа относятся объявления

функций-членов константными (const). В этом случае они не имеют права изменять

значение текущего объекта, с которым вызываются. Заголовок такой функции при

этом имеет вид

void dat::put() const

{... }

Аналогично можно определить константные объекты:

const class a{...} value;

 

 

№19_Неявный указатель this

Каждый не статический метод класса содержит в качестве данного указатель "this".

В функции - члене на данные - члены объекта, для которого она была вызвана, можно ссылаться непосредственно. Например:

class x {

int m;

public:

int readm() { return m; }

};

x aa;

x bb;

void f()

{

int a = aa.readm();

int b = bb.readm();

//...

}

В первом вызове члена readm() m относится к aa.m, а во втором - к bb.m.

Указатель на объект, для которого вызвана функция член, является скрытым параметром функции. На этот неявный параметр можно ссылаться явно как на this. В каждой функции класса x указатель this неявно описан как

x* this;

и инициализирован так, что он указывает на объект, для которого была вызвана функция член. this не может быть описан явно, так как это ключевое слово. Класс x можно эквивалентным образом описать так:

class x {

int m;

public:

int readm() { return this->m; }

};

При ссылке на члены использование this излишне. Главным образом this используется при написании функций членов, которые манипулируют непосредственно указателями

 


№20_Указатели на член класса

Указатель на данные:

int a;

int *pa;

pa=&a; *pa=11;

Указ. на ф-ции:

void Func(void);

void (*pf)(void);

pf=Func;

(*pf)(); // или pf();

Рассмотрим созданый нами ранее класс Point2D.

class Point2D {

public:

double x,y;

Point2D(double _x, double _y);

double mod();

};

Можно получить адрес объекта и работать с ним, а также адрес данного класса, если данное есть public.

Point2D a(3.0, 6.1);

double *pd;

pd=&a.x;

*pd=11;

pd=&a.y;

*pd=15;

double (*pf)();

pf=a.mod; // ошибка!!!

Ошибка возникает из-за того, что сигнатуры у методов класса и глоб. ф-ций отлич. наличием неявного указателя this. Это допустимо только для статических методов класса. В связи с этим в С++ было введено спец. средство член-данные и методы класса.

double Point2D::*pd;

double (Point2D::*pf)();

pd=&Point2D::x;

pf=&Point2D::mod;

pd и pf не связаны с конкретными объектамит, а значит это не настоящие адреса, а что-то похожее на смещение от начала объекта. Можно считать, что у каждого объекта есть свое пространство адресов, а указ. на члены класса, не что иное, как адреса пространства.

Сущ. в С++ две новых операции (.*) и (->*).

Point2D a(2,4); *pa =&a;

Point2D b(3,8); *pb =&b;

 

a.*pd=10; // a.x=10

pa->*pd=15; // pa->x=15

cout << (a.*pf)(); // a.mod()

cout << (pa->*pf)(); // pa->mod()

 


№21_Локальные и вложенные классы

Локальные классы

Класс можно описать в определении функции, такой класс называется локальным. Имя локального класса считается локальным в объемлющей области видимости, а областью видимости локального класса является объемлющая область видимости. В описаниях локального класса из объемлющей области видимости можно использовать только имена типов, статических переменных, внешних переменных и функций, а также элементы перечисления. Приведем пример:

int x;

void f()

{

static int s;

int x;

extern int g();

struct local {

int h() { return x; } // ошибка: `x' автоматическая

int j() { return s; } // нормально

int k() { return::x; } // нормально

int l() { return g(); } // нормально

}

}

Объемлющая функция не имеет особых прав доступа к членам локального класса, она подчиняется обычным правилам доступа. Функцию-член локального класса следует определять в определении этого класса. Локальный класс не может иметь статических членов, представляющих данные.

Вложенные классы

Класс можно описать в описании другого класса. Такой класс называют

вложенным. Имя вложенного класса локально по отношению к

объемлющему классу. Вложенный класс находится в области видимости

объемлющего класса. Если не считать явного использования указателей,

ссылок или имен объектов, то в описаниях вложенного класса допустимы

только имена типов, статических членов и элементов перечисления

из объемлющего класса.

int x;

int y;

class enclose {

public:

int x;

static int s;

class inner {

void f(int i)

{

x = i; // ошибка: присваивание enclose::x

s = i; // нормально: присваивание enclose::s

::x = i; // нормально: присваивание глобальному x

y = i; // нормально: присваивание глобальному y

}

void g(enclose* p, int i)

{

p->x = i; // нормально: присваивание enclose::x

}

};

};

inner* p = 0; // ошибка: `inner' вне области видимости

Функции-члены вложенного класса не имеют особых прав доступа к членам

объемлющего класса, они подчиняются обычным правилам доступа.

Аналогично, функции-члены объемлющего класса не имеют особых прав

доступа к членам вложенного класса и подчиняются обычным правилам

доступа.

 

Подобно функции-члену дружественная функция, определенная в данном

классе, находится в области видимости этого класса. Она подчиняется

тем же правилам связывания имен, что и функции-члены (они указаны выше),

и не имеет так же как они особых прав доступа к членам объемлющего класса и к локальным переменным функций этого класса.

№22_Специальный вид методов класса - конструкторы и деструкторы. Некоторые особенности конструкторов и деструкторов

Конструкторы и инициализация. Использование для обеспечения инициализации объекта класса функций вроде set_date() (установить дату) неэлегантно и чревато ошибками. Поскольку нигде не утверждается, что объект должен быть инициализирован, то программист может забыть это сделать, или (что приводит, как правило, к столь же разрушительным последствиям) сделать это дважды. Есть более хороший подход: дать возможность программисту описать функцию, явно предназначенную для инициализации объектов. Поскольку такая функция конструирует значения данного типа, она называется конструктором. Конструктор распознается по тому, что имеет то же имя, что и сам класс. Например:

class date {

date(int, int, int);

};

Когда класс имеет конструктор, все объекты этого класса будут инициализироваться. Если для конструктора нужны параметры, они должны даваться:

date today = date(23,6,1983);

date xmas(25,12,0); // сокращенная форма (xmas - рождество)

date my_burthday; // недопустимо, опущена инициализация

Часто бывает хорошо обеспечить несколько способов инициализации объекта класса. Это можно сделать, задав несколько конструкторов. Например:

class date {

int month, day, year;

public:

//...

date(int, int, int); // день месяц год

date(char*); // дата в строковом представлении

date(int); // день, месяц и год сегодняшние

date(); // дата по умолчанию: сегодня

};

Конструкторы подчиняются тем же правилам относительно типов параметров, что и перегруженные функции. Если конструкторы существенно различаются по типам своих параметров, то компилятор при каждом использовании может выбрать правильный:

 

Конструктор без параметров (по умолчанию). Размножение конструкторов в примере с date типично. При разработке класса всегда есть соблазн обеспечить "все", поскольку кажется проще обеспечить какое-нибудь средство просто на случай, что оно кому-то понадобится или потому, что оно изящно выглядит, чем решить, что же нужно на самом деле. Последнее требует больших размышлений, но обычно приводит к программам, которые меньше по размеру и более понятны. Один из способов сократить число родственных функций - использовать параметры по умолчанию. В случае date для каждого параметра можно задать значение по умолчанию, интерпретируемое как "по умолчанию принимать: today" (сегодня).

class date {

int month, day, year;

public:

//...

date(int d =0, int m =0, int y =0);

date(char*); // дата в строковом представлении

};

date::date(int d, int m, int y)

{

day = d? d: today.day;

month = m? m: today.month;

year = y? y: today.year;

// проверка, что дата допустимая

//..
№23_Классы(конструкторы), содержащие объекты других классов

Эл-ты - данные некотор. класса могут быть объектами других классов, сод. свои конструкторы и деструкторы.

В ОО методологии известно такое понятие HASA relationship.

Иниц. эл-ов-объектов осуществляется через список инициализации (constructor initializer).

Часть заголовка констр. начин. двоеточием, называется списком иниц. Он представляет собой перечисление имен объектов членов класса со списком аргументов в скобках. Отдельные члены должны быть проиниц. следующем образом:

имя_члена (список арг.).

Список аргументов должен соответствовать какому-нибудь из конструкторов класса к которому он принадлежит. Именно этот конструктор и будет вызываться для для таких членов-объектов класса.


В этом примере класс D создает два объекта классов A и B. Для иниц. a и b используется список иниц. При использов. классов агрегатов следует учесть, что конструкторы членов-объектов класса вызыв. раньше, чем для класса владельца.

Порядок вызовов конструкторов членов-класса определяется не списком иниц., порядком объявления переменных класса.

В нашем случае D с двумя переменными первым будет вызывать конструктор объекта a.

Если конструктору члена-класса аргум. не нужны, то этот объект можно не указ. в списке иниц. Конструктор будет вызван по умолчанию

Аргументы объектов в списке иниц. могут быть сколь угодно сложными.

Список иниц. членов явл. единственным способом иниц. константных объектов членов-класса, ссылок, объектов с защищенными данными.

 

 


№24_Создание объектов с различным временем жизни

Время жизни объектов определ. классом памяти.

Статические объекты создаются и иниц. на момент запуска проги в сегменте данных и сущ. до ее остановки, не меняя своего месторасположения в памяти. Констр. таких объектов назыв. цельными. Констр. для статич. объектов могут вызыв. до main(). Дестр. вызываются по оканчании проги. в порядке обратном вызове констр.

Автоматические объекты создаются и иниц. в стеке в момент входа в блок и заверш ают свое существов. при выходе из блока. Констр. для автомат. объектов вызывается неявно при каждом определенном входе в блок или в ф-цию. Деструкт. для каждого автоматич. объекта также вызываются неявно, при выходе из блока или ф-ции, но в порядке обратном вызову констр.

В стеке могут создаваться временные объекты. Порядок вызова таких ф-ций определ. порядком вычислений. Дестр. для них вызыв. по оканч. операц., в котроой потреблялись временные объекты.

Динамические данные создаются в куче. Нужно создавать с помощью new и delete. Опер. new кроме выдел. памяти осуществляет вызов соответствующего конструк, обеспечивая необходимую иниц. объектов. delete кроме освобождения памяти вызывает дестр., который может коректно завершить работу с объектом.

Point2D p1=new Poinr2D(5.5,10.1);

................................

delete p1;

 


№25_Массивы объектов класса

Допускается создание и инициализация статич. и автоматич. массивов из объектов класса. Синтаксически инициализация напоминает синтаксис для стандартных типов данных.

Point2D P1[3]={Point2D(2,4),Point2D(3,3),Point2D(4,6)};

 

Point2D P2[3]={Point2D(5),Point2D(4),Point2D(3)};

 

Point2D P3[3]={5,4,3};

 

Point2D P4[3]={Point2D(4),5,Point2D(3,1)};

 

На первый взгляд кажеться, что для каждого элемента массива создается временный элемент типа Point2D, а потом массив, иниц. этим объектами, с помощью конструктура копий. На самом деле лишних действий нет. Вызыв. лишь конструкторы для областей памяти, соответств. эл-ов массива.

Если у класса конструктор операции new [], можно создать массив в динам. области памяти. Иниц. такого массива не допустима.

Point2D *pArray= new Point2D[20];

...........

delete [] pArray;

Операция new гарантирует вызов конструктора для каждого объекта массива.

 

 


 

№26_Особенности копирования объектов

class A {

char *p;

unsigned size;

public:

A(unsigned s, const char *_s)

{

p=new char[size=s];

strcpy(p,_s);

}

~A() { delete [] p; }

 

const A &operator=(const A&);

A(const A&);

};

 

v



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2016-08-07 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: