Лекции по Объектно-ориентированному программированию

Вид материалаЛекции

Содержание


Основное достоинство ООП
В основу ООП положены 4 главных принципа
Cоздание приложений в интегрированной среде разработчика.
Уровень абстракции.
То есть то, как сделано снаружи ” Бутч.
То есть то, как сделано изнутри ” Бутч.
Элементы класса
Элементы функции.
Public – поля класса, функции доступны для любых элементов программы, в которой есть представитель класса. Private
Инициализация полей данных.
Присваивание полей производится при …
Конструктор от обычной функции отличается
Для явного вызова конструктора используются две формы
Доступ к полям и методам класса.
Ссылки как псевдоним переменных.
Операторы NEW и DELETE.
Перегруженные функции.
Конструктор копирования.
Статические компоненты класса.
2. Указатель на функции (методы класса).
...
Полное содержание
Подобный материал:
Лекции по Объектно-ориентированному программированию.


  1. Существующие парадигмы программирования.
  2. Основные принципы ООП.
  3. Объекты и классы.
  4. Абстракции и иерархия.



1. Языки высокого уровня (Algol 68, Fortran, PL/1 и т.д.) облегчили трудоемкую работу по созданию машинного кода, который стал делать компилятор. Программы стали короче и понятнее.

Потом задачи усложнились, и программы снова стали слишком громоздкими. Программы стали разбивать на процедуры или функции, которые решают свои задачи. Написать, откомпилировать и отладить маленькую функцию можно легко и быстро, а потом собрать все функции воедино.

Такое программирование стало процедурным программированием и стало парадигмой.

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

Появились новые языки.

Удачная структура данных может облегчить их обработку. Некоторые удобно представить в виде массива, а другие в виде стэка или дерева. Рост сложности и размеров программ потребовал развития структурирования данных и появления новых типов данных, которые могут определяться программистом.

Идея объединения данных и всех процедур их обработки в единый модуль – парадигма модульного программирования.

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

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

Так возникло ООП.

Это современная парадигма программирования.

Все программы состоят из двух частей (описание и сама программа). Любая программа может быть концептуально организована либо вокруг её кода “кодовое воздействие на данные”, либо вокруг данных “управляемый данными доступ к коду”.

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

ООП – это методология программирования, основанная на представлении программы в виде совокупности объектов, каждый из которых является экземпляром определенного класса; а классы образуют иерархию наследования.

1).ООП использует в качестве базовых элементов объекты, а не алгоритмы.

2).Каждый объект будет экземпляром какого-либо определенного класса.

3).Классы реализованы (организованы) иерархически.

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

Недостаткиснижение быстродействия из-за более сложной организации программы.

В основу ООП положены 4 главных принципа:

- абстрагирование;

- инкапсуляция;

- наследование;

- полиморфизм.


Программа будет состоять из трех файлов

|| Товар (название, код, единица измерения, продажная стоимость единицы).


1-ый файл: “file.h” - заголовочный файл.

2-ой файл: “file.сpp” - файл реализации.

3-ий файл: “demofile.cpp” - демонстрационный файл

здесь main


iostream

class имя класса

{

}; поля данных класса.


Конструктор” выделяет память, “деструктор” – наоборот. Конструктор никогда не возвращает значение.


{ private :

public :


Cоздание приложений в интегрированной среде разработчика.


Абстрагирование – это метод решения сложных задач. Описывая поведение сложного объекта, мы выделяем только те стороны, которые нас интересуют с точки зрения решаемых задач. Т.е. строим его приближенную модель. Модель не может описать реальный объект полностью. Мы выделяем только те характеристики, которые важны для задачи. Нам надо абстрагироваться от несущественных деталей объекта.


Уровень абстракции.


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

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

Абстрагирование – это взгляд на объект ни как он есть на самом деле, а

с точки зрения наблюдателя и интересующих его характеристик данного объекта.

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

СТЭК – гора тарелок, растет вверх и уменьшается сверху.


class CStack {private :

int size; // максимальный размер

int top_el; // индекс верхнего элемента

int *stack; // динамич. массив элементов

public:

cstack (int); // конструктор

~ cstack ( ); // деструктор

};


class CPerson {private :

char name [ 25 ]; // Ф.И.О. ( элементы

short int age; // возраст данных

сhar sex; // пол или

public : поля)

CPerson ( );

void *Member_of ( ); (элементы функций

void AddSpisok (void*); или

void RemoveFromSpisok (void*); методы)

};


Тип данных

CStack stack [10 ];

CPerson *person = new CPerson;

// Тип типа СPerson – class, описанный программистом //

void *firma = person -> Member_of ( );


Инкапсуляция – ограничение прав доступа. Объекты в задаче сохраняют конкретные данные, тип которых определяется полями класса. Каждый объект в задаче играет роль, определяет его “поведение”. То, что может делать каждый объект (кроме сохранения значений своих полей) задается элементами функции. Особенностью класса является инкапсуляция одной конструкции, как данных, так и методов функций, которые обрабатывают эти данные, контролируемым образом. Это защита данных и операций от неконтролируемого доступа.

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

Элементы public определяют интерфейс класса с другими частями программы и другими классами.

Абстрагирование и инкапсуляция дополняют друг друга. Абстрагирование направлено на наблюдение за объектом, а инкапсуляция занимается внутренним устройством объекта – это сокрытие некоторых элементов абстракции (которые не затрагивают существенных характеристик объекта как целого).

Надо выделить две части в описании абстракции. Первая – это интерфейс (взаимодействие). Это основные характеристики состояния, поведения объекта. Он описывает внешнее поведение объекта типа class. Вообще описывает абстракцию поведения всех объектов данного класса. В этой части собрано все, что касается взаимодействия этого объекта с любыми другими объектами.

То есть то, как сделано снаружи ” Бутч.

Вторая – реализация, представляет собой недоступные извне элементы реализации абстракции (внутренняя организация абстракции и механизмы реализации её поведения). Скрывает все детали.

То есть то, как сделано изнутри ” Бутч.

Наследование классов – это основа ООП, позволяет увеличить масштабируемость программ, реализовать повторное использование кода. Программа, которая использует объекты базового класса может быть расширена без изменения старого кода путем построения производных классов от базового. Использование новых объектов в производных классах. Это достигается соглашением о наследовании: объекты производного класса содержат все элементы базового класса (поля и методы), как если бы эти элементы были описаны в самом производном классе.

Отпадает необходимость многократного переписывания одних и тех же определений базового класса, появляется возможность пользоваться ими, как они есть. В базовом классе надо определить какие права доступа мы предоставляем производным класса


private

public

protected


class CBase { protected :

int a;

public :

void Draw ( );

};

class CProizv : public CBase {

int b;

public :

void Draw ( );

};


// (class CBase – базовый класс; class CProizv – производный класс.)


Производный класс может наследовать базовый как private, как public и как protected. Это влияет на возможность будущего расширения иерархии классов и интерпретации целей самого наследования.

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

Может понадобиться программа, которой требуется три типа СТЭКОВ (для хранения целых, вещественных чисел, для хранения символов).

Алгоритм, который реализует все стэки, будет один и тот же, хотя хранимые даны различны.

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

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


Классы.

class CMyClass

(тип) (название)


# include

class CMyClass

{ public :

int num;

int umnoz ( ); // прототип функции, которую умножили на 2.

void set_num (int ); // прототип функции, кот. устанавл. значение num.

void show ( ) { cout<
};

int CMyClass :: umnoz ( ) {return num*2;}

void CMyClass :: set_num (int n )

{num = n;}

int main ( )

{ CMyClass a,b,c; // экземпляр типа класса CMyClass a

a .set_num (5 );

cout << a . umnoz ( ) << “ \ n ” ;

a .show ( ); // печать значения

( или cout << a .num ; )

return Ø ;

}


Элементы класса – это данные, которые инкапсулируют состояние объектов, и функции, которые представляют собой код, реализующий поведение объектов.

Элементы данных аналогичны структурам в языке СИ. Они не могут быть объявлены как auto, extern или register. Они могут быть перечислениями и объектами ранее объявленных классов. Они не могут быть представителями самого класса. Элемент данных может быть указателем или ссылкой на сам класс.

Элементы функции.

Функция – элемент класса, которая объявляется внутри класса. Определение функции может находиться внутри класса (встроенная функция). Компилятор будет генерировать её встроенное расширение на месте вызова.

Если определение функции максимально вне определения класса, то к её имени добавляется префикс, состоящий из имени класса и операции расширения области видимости “ :: ” .


Квалификаторы доступа.


Лабораторная работа.


3. Использование конструкторов и деструкторов.

4. Использование дружественных функций.

5. Перегрузка операторов.


class student

{ private :

char name [25 ];

int age;

float dohod;

public :

student (char *, int, float);

student (char *);

student (char *,int);

}

student :: student (char *nn, int AA, float BB) // формальные параметры

{

student (name, nn);

age = aa;

dohod = BB;

}

friend void shapka (void); // заголовочный файл

}; // конец определения класса

void shapka ( )

{cout << “ “ \ n ” ;

cout << “Студенты группы ЗС \ n “ ;

cout << “... ... .... \ n “ ;


Cпецификатор доступа задается с помощью


Cпецификатор

Описание

PRIVATE

Данная часть класса закрыта. Только сам класс имеет к ней доступ.

PUBLIC

Данная часть класса открыта. Все имеют доступ.

PROTECTED

Данная часть класса открыта только для потомков класса.


По умолчанию – PRIVATE.


Public – поля класса, функции доступны для любых элементов программы, в которой есть представитель класса.

Private – cпецификатор с наиболее ограниченным доступом. Поля данных и функции-методы класса будут доступны только методам своего класса.

Protected – поля данных и функции доступны для функций-методов класса, порожденных от этого класса.


Инициализация полей данных.

Для того, чтобы предусмотреть инициализацию переменных в программе, надо написать специальную функцию – метод класса (access).


set_num (int n) поле_data = переменная;

{num = n ;}


Присваивание полей производится при …

Конструкторы (деструкторы).

Недостатком вышеизложенного метода является отсутствие инициализации объекта (автоматической). Для каждого вновь созданного объекта надо вызывать функцию set либо явным образом присваивать значение данного объекта. Для инициализации объектов класса в его определении можно явным образом включить специальную компонентную функцию – конструктор.


имя_класса (список формальных параметров) {тело конструктора}

/ * прототип */ имя_класса (список формальных параметров); / тело ниже


При создании новых типов данных:

CMyClass (int n = Ø ) – конструктор с параметрами

{num = n;}

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


Конструктор от обычной функции отличается:
  1. Конструктор никогда не возвращает значение, даже void.
  2. Указатель на конструктор не может быть определен, то есть нельзя получить адрес конструктора.
  3. Конструкторы не наследуются.
  4. Конструкторы не могут быть описаны с ключевыми словами: virtual, static, const, mutable, volatile.

Конструктор всегда создается для любого класса. Если не определен явно, то вызывается автоматически. По умолчанию создается конструктор без параметров и конструктор копирования. КОНСТРУКТОР может получать любое число параметров. Если конструктор описан явно, то конструктор по умолчанию не создается. В качестве параметра конструктора, но может быть ссылка на него.

Для явного вызова конструктора используются две формы:

1) имя_класса имя_объекта (фактические параметры);

2) имя_класса (фактические параметры);

CMyClass ob1(4);

CMyClass ob1 = CMyClass (4); // вызов объекта без имени

Пример создания объектов


CMyClass ob1, ob2, ob3 [3 ];

Деструктор: ~ имя_класса ( ) {} помещается в определении класса

Смотреть программа

class CMyClass {

public : то же самое

CMyClass ( ) { }

~ CMyClass ( ) { }

};

или

class ---- // ---- {

public : то же самое

CMyClass ( ) ;

~ CMyClass ( ) ;

};

CMyClass :: CMyClass ( ) { cout << “ Это конструктор”;}

CMyClass :: ~ CMyClass ( ) { cout << “ Это деструктор”;}

CMyClass ob1, ob2;

Доступ к полям и методам класса.

Доступ к элементам.

Так как функции – элементы класса, находящиеся в области действия класса, то они могут обращаться к элементам данных просто по имени.

Обычные функции или функции – элементы других классов могут обращаться к элементам, существующего представителя класса с помощью операций ( · или → ).

cout << ob . num;


CMyClass a;

cout << a . num;


Доступ к функциям – элементам класса (методам).

Функции - элементы определенного класса (по аналогии с элементами – полями данных) могут вызываться объектами, представителями своего класса просто по имени.


ob . show ( ) или cout << a . show ( );

int mas [ 3 ], *ptr mas;

ptr mas = & mas [Ø];

ptr mas ++;

(ptr mas = ptr mas+1)


CMyClass *pob, ob1;

pob1 = & ob1;

pob1 → show ( ); ob }


Вызов метода нашего класса через указатель.


Ссылки как псевдоним переменных.

# include

main ( )

{ int iVar = 888; // инициализация переменных

int *iPtr = iVar; // инициализация указат.

int & iRef = iVar;

int *iRPtr = & iRef;


cout <
cout << *iPtr;

cout << iRef;

cout << iPtr; // адрес первой переменной

cout << iRPtr;} // адрес ссылки


После инициализации имя ссылки может использоваться так же, как имя, ассоциированной с ней переменной.

!!! Ссылку после инициализации изменить нельзя !!!

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


Операторы NEW и DELETE.


new – для выделения памяти.

delete – для освобождения памяти.

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


char* PChar*,string;

int *PInt;

double *PDouble;

Pchar = new char;

Pint = new int;

Pdouble = new double;

*PChar = ‘ a’;

*PInt = 10;

*PDouble = 2.3+E2; потом cout << ... ...

delete PChar; // Освобождаем память

---- // ----

---- // ----

string = new char [25]; // массив из 25 элементов

delete [ ]string;


Перегруженные функции.


возвращаемое_значение имя_функции (список параметров);

это прототип функции (main)

main ( )

{

/ * вызов */ имя_функции (аргументы);

}

возвращаемое_значение имя_функции (---- // ----)

main ( )

{int a,b,c, max_el (int, int);

cin >> a >> b;

c = max_el (a, b);

cout << c;

}

int max_el (int aa, int bb)

{ if (aa > bb) return aa;

else return bb;

}

float max_el (float aa, float bb)

{if (aa > bb) return aa;

else return bb; }

template // символьное имя – универсальный тип данных

T max_el (Ta,Tb)

{ if (a >b) return a;

else return b;

}



Class TObject {protected : float x;

float y;

char *name;

public : TObject ( )

{name = new char[sizeof (“No name”)]; // конструктор по умолчанию

strcopy (name, “No name”);

x =0;

y =0; }

TObject (float x1, float y1, char*name1) // пример перегрузки компьютера

{name = new char[strlen (name1)+1]; // второй конструктор

strcopy (name, name1);

x =x1;

y =y1;}

float & coord_x ( ) {return x;}

float & coord_y ( ) {return y;}

void show_name ( )

{cout << ”название объекта” <
void show_coord ( )

{cout << ”размер х = ”<< x <
cout << ”размер y = ”<< y <
~ TObject {delete name;} – деструктор

}; // конец определения класса.

main ( )

{ TObject ob; 1;

TObject automat (15, 30, “UZI”);

obj1.show_name ( ); // No name

obj1.show_coord ( ); // размер х=0, у=0

automat.show_name ( ); // UZI


В определении класса мы поместили 2 конструктора: первый – по умолчанию, которое позволяет инициализировать поля объекта и выделить под него динамическую память оператор new при вызове конструктора без аргументов. А второй конструктор с инициализацией и выделением динамической памяти по аргументам.

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


TObject (float x1,float y1,char*name)

{... ...}

TObject (---- // ---- // ----// ----) : x (x1), y (y1)

{name = new char [strlen (name1) +1 ];

strcpy (name, name 1);}


Конструктор копирования.


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


Сlass TObject {---- // ----

---- // ----

public : TObject (TObject & );

---- // ----

}; либо


Сlass TObject {... ... ...

... ... ...

public : TObject (const TObject & (тип));

... ... ...

};


Class Tdefine {private : int TT;

public :

Tdefine (int r =1) {TT = r ;}

Tdefine (const Tdefine &NewObject)

{TT =NewObject . TT;}

void show_all ( )

{cout << ”значение ТТ = ” << TT <
~ Tdefine ( ) { }

}; // конец определения класса

main ( )

{Tdefine copia1;

Tdefine copia2 (3 );

Tdefine copia3 = copia2;

copia1. show_all ( ); // 1

copia2. ------ // ------ // 3

copia3. ------ // ------ // 3


Выделенная строка означает не только обычное копирование. Знак “ = ” между объектами абстрактного типа означает предписание компилятору использовать конструктор копирования. Во многих компиляторах часто происходит автоматическая генерация этого конструктора, если он не был описан программистом.


Деструкторы – это функции – члены класса, которые обеспечивают действия, обратные действиям конструктора.

Как правило, деструктор содержит оператор освобождения динамической памяти

~ TObject ( )

{delete name;}

Иначе память будет считаться распределенной, и к ней нельзя будет обращаться.


Статические компоненты класса.


Есть возможность доступа всех созданных объектов конкретного класса к одной переменной (члену данному), содержимое которой хранится в одном месте. Для этого объявляют переменной:


static тип имя

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


тип имя_класса :: имя_переменной = нач.значение;


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


class Stat

{public : static int flag;

Stat ( ) {flag ++;}

int Ret_flg {return flag;}

};

main ( )

{stat S1, S2;

cout << ”количество вызовов конструктора : ”<
stat S3;

cout << ”количество вызовов ... : ”<
};

  1. Указатель this.
  2. Указатель на методы класса.


1. Каждый новый экземпляр класса получает свою собственную копию данных.

Функция – метод определяет, к какому объекту относится конкретный компонент класса.


# include

# include

class Stroka

{char *ss;

public : stroka (char*text) {ss =text;}

void write_ss ( ) {cout <<”string : “<
};

void main ( )

{stroka s1 (“Текст1”);

stroka s2 (“Текст2”);

s1.write_ss ( ); // Текст 1

s2.write_ss ( ); } // Текст 2


Для решения этой проблемы вводится неявный указатель this на объект класса. Функция write_ss ( ) в качестве неявного аргумента получает адрес одного из объектов s1 и s2.

Этот указатель (this) определен в каждой нестатической функции класса.

Надо проинициализировать:


имя_класса *this = адрес_объекта или

имя_класса *сonst this = адрес_объекта


Изменим наш пример.


public : ------ // ------ // ------

void write_ss ( ) {cout <<”string : “<
void write_ss ( ) {cout <<”string : “< ss <

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

Снимаем неоднозначность.


# include ------ // ------

# include ------ // ------

class Stroka

{char *name;

char *ss;

public : stroka (char *ss, char *text)

{name =ss; // неоднозначность присваивания полю name

ss = text; // кому присв. значение ?

void wr_ss ( )

{cout <<”string eto”<
cout <<”string eto : ”<
};

main ( )

{stroka s1 (“Первая строка”, ”text 1”);

stroka s2 (“Вторая строка”, ”text 2”);

s1.wr_ss ( );

s2.wr_ss ( );

}


*

# include

# include

class stroka

{char *name;

char *ss;

public : stroka (char *ss, char *text)

this → name = ss; // полю данных name присваивается значение конструктора stroka.

this → ss = text; // полю данных ss ------ // ------------ // ------------ // ------------ // ------------ // ------.

Можно использовать операцию расширения области видимости (::)

stroka :: name = ss;

stroka :: ss = text;


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

2. Указатель на функции (методы класса).

Так как класс – новый тип данных, то для классов и для объектов типа “класс” может быть также определена операция получения адреса. То есть в программе мы можем использовать указатели на компоненты класса.


# include ------ // ------

------ // ------// ------

class UK {public : int u1, u2;

UK (int value 1 =Ø, int value 2 = Ø)

{u1 = value 1;

u2 = value 2;

void print ( )

{cout << ”znachenie polya data = “ <
cout << “u1 = “ << u1 <
cout << “u2 = “ << u2 <
}; // конец определения класса.

void set (UK& obj, int value)

{int UK :: * pos = &UK :: u2;

obj. *pos = value;} // pos- переменная, которая указывает на поле данных класса u2 типа int.

void main ( )

{UK ob1(1Ø); // присваиваем значение только u1, u2 =0 (cм. конструктор)

ob1. print ( );

set (ob1, 3Ø);

ob1. print ( );

}


Пример.

# include ------ // ------

------ // ------// ------

class UK {------// ------

------// -----// ------ }

/ * void set ( ) */

void main ( )

{UK ob2 (1Ø, 3ØØ);

void (UK1 :: *func) ( ) = & UK :: print ( ); // объявление указателя на функцию-метод класса.

тип (*имя_указателя) ( ) = & имя_функции //

(ob2. *func) ( ); // косвенный вызов print


Указатели на методы класса отличаются от указателей на обычные функции только явным присутствием в их объявлении имени класса, за которым следует операция расширения области видимости ( :: ). В остальном отличий нет.


Дружественные функции.


В СИ++ есть функции, которые могут дружить с классами. Эта “дружба” позволяет получить доступ заранее определенным функциям к любому полю данных класса, независимо от спецификатора доступа (private, protected и т.д.). Общее название таких функций – дружественные. Их используют, когда надо использовать поля данных разных классов с входом механизма членов функций. С помощью этих функций можно расширить количество способов взаимодействия с классами. Дружить с классом можно только с его разрешения.

Для этого в теле класса перед объявлением функции надо поставить слово friend.


class { ------ // ------ // ------

friend тип_возвр_значения имя_функции (список параметров);

------ // ------ // ------

}; // конец определения

тип_возвр_значения имя_функции (список параметров)

{ /* тело дружеств. функции */ }


# include

class FR

{private : int vol 1, vol 2;

friend void drag (FR*, int); // функция friend - не метод класса.

тип тип

public : FR (int par1 =Ø, int par2 =Ø)

{vol 1 = par1;

vol 2 = par2;}

void show ( );

void set (int);

};

void FR :: show ( )

{cout << ” \ n Значение поля данных = “<
cout << “vol 1 = “<
} // различный способ доступа к полям класса

void FR :: set (int s)

{vol 2 = s;}

void drag (FR*par, int s) // дружественная функция

{par → vol 2 = s;}

void main ( )

{FR ob (1Ø);

ob. show ( );

drag (& ob, 1111);

ob. show ( );

ob. set (1112);

ob. show ( );

}


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

К полям данных FR надо использовать явный указатель на объект (для доступа дружественных функций). В член функции вместо строки мы используем vol 2 = s → vol 2 = s.

Одна и та же функция может быть объявлена другом в нескольких классах.


class Y; // неполное объявление класса, для использования class Y в качестве параметра, до его описания (Y еще не определен).

class X {friend void fr_all (X,Y); // где X и Y – типы параметров функции.

… … … ... … … };

class Y {friend void fr_all (X,Y);

------ // ------ // ------ };

void fr_all (X par1, Y par2)

{ /* тело функции fr_all */ }


Перегрузка операций.


Механизм основан на возможности применения стандартных операций к операндам, тип которых не является стандартным, кроме :


( · ), ( .* ), ( :: ), ( ?: ), ( # ), ( ## ), ( = ), ( [ ] ), ( ( ) ), ( → ).


Нельзя использовать “свои ” символы.

тип_возвр_значения operator знак_операции (параметры) // operator – вместо имени

{ ... ... ... ... ... ...

операторы … … …}

Матр operator + ( Матр А, Матр В);

# include

# include

class Circle

{float x, y;

int r;

public : Circle (float xx =1, float yy =1, int rad =1);

x (xx), y (yy), r (rad) { }

void print ( )

{cout <<”окружность с центром в т : “<
cout <<”x = ”<< x << ”y =” << y <
cout << ”radius = “<< r <
}

Circle operator + (Circle &);

int get_r ( ) {return r;} // член – функция get для доступа к полю r

int set_r ( int v ) {r = v;} // член – функция set для записи поля r

};

Circle :: Circle operator + (Circle & c)

{Circle vr;

vr. r = r + c.r; // vr. r = this → r + c.r

vr. x = x ; // vr. r = this →x

vr. y = y ; // vr. r = this →y

return vr;}

Circle operator_(Circle&C1, Circle&C2)

// перегрузка с помощью глобальной функции операции перегрузки “_”

abs ( )

{Circle c;

c. set_r (abc ( c1. get_r ( )_c2. get_r ( ) );

return c;

}

void main ( )

{Circle c1 (5, 5, 10 );

Circle c2 (3, 2, 7 );

Circle c;

c = c1. operator + c2

c. print ( );

c = operator_(с1, c2);

c. print ( );

}


Наследование.


Наследование дает возможность повторно использовать код. Технически оно реализуется через создание производных классов (классов – потомков), которые производятся от базовых классов, т.е. классов, лежащих во главе иерархии.

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

Виды наследования: простое и множественное.

Простое - каждый класс имеет только один родительский класс ближайшего уровня.

Множественное – класс – потомок создается с использованием нескольких базовых классов – родителей.


1) Фигура




квадрат треугольник круг


2) Человек Рыба



женщина


русалка


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


Производные классы. Доступ к базовым классам.


Синтаксис объявления производного класса:

1) class базовый класс { // определение базового класса

}

class имя (производного класса) : спецификатор доступа

имя базового класса

{ // определение производного класса

}


2) class баз. кл.1 { // определение };

class баз. кл.2 { // определение };

------ // ------ // ------ // ------ // ------

class баз. кл.N { // определение };

class произв.кл.: спец. доступа имя баз. кл.1,

спец. доступа имя баз. кл.2, … , спец. доступа имя баз. кл.N

{ // тело класса };


Возможны следующие варианты доступа:


Спецификатор доступа Спецификатор доступа при Режим доступа к

к эл-ам баз. класса наследовании эл-ту в произв. кл.


public public public

protected protected

private недоступен!!!

public protected

protected protected protected

private protected

public private

protected private private

private недоступен!!!


Смотреть Лабораторную работу № 3.


class Student {private :

сhar *name, int age, float dohod;

public : // методы

}

class Stipendia_Studenta: public Student

{private: float razmer,

int kol_month

public: // методы производного класса };


При наследовании каждый класс определяет права доступа к своим элементам и элементам, унаследованным от родителя, а также возможность доступа к ним для следующих в иерархии классов.

Базовый класс скрывает свои элементы с помощью private от всех производных классов. То есть объекты производных классов могут использовать их только через доступные им методы базового класса.

Элементы protected базового класса будут доступны в базовом классе.

Элементы public – везде.


Конструкторы и деструкторы в иерархии классов.


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


Student :: Student (char*n, int a, float d)

{strcpy (name, n);

age = a;

dohod = d;}

Stipendia_Studenta :: Stip_Stud (char*n, int a, float d, float r_s, int m) :

Student (name, age, dohod ) // конструктор произв. класса содержит параметры базового класса.


{ kol_month = m;

razmer_stip = r_s;}


В списке инициализации полей (после : ) в конструкторе производного класса указывается имя базового, его конструктору передаются необходимые параметры.

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


Student :: ~ Student ( )

{cout << “ \ n Деструктор базового класса”; }


Виртуальные функции.


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


# include

class base // базовый класс

{public : void print ( )

{cout<< ” \ n Это класс base”;}

};

class proizv : public base

{public : void print ( )

{cout<< ” \ n Это класс proizv”;}

};

void mane ( )

{base B, *bp = &B; // тип указ. base

proizv D, *dp = &D; // тип указ. proizv

base *p = &D; // тип указателя base

bp → print ( ); // base

dp → print ( ); // proizv

p → print ( ); // base

}


В последнем случае вызывается функция print ( ) базового класса, хотя указатель был р был настроен на объект производного класса D. Выбор нужной функции выполняется на этапе компиляции программы и определяется типом указателя, а не его значением.

Переопределенная функция вызывается в соответствии с классом указателя. Это раннее или статическое связывание.

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


virtual тип_возвр_знач-я имя_функции (параметры)

сlass base {public : virtual void print ( )

{------ // ------ // ------ } } ; и т.д. см. предыдущую.

Результатом

// base

// proizv

// priz


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

Вызов функции p→print ( ) – позднее связывание , то есть ссылка на функцию разрешается во время выполнения программы.

Абстрактные классы.


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


Сontrol


ButtonBase ListControl




Button Check Radio List Combo

Box Button Box Box


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


virtual тип_возвр_значения имя_ф (параметры) = Ø;

Напр.: virtual void print ( ) = Ø;


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

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


Shape









area ( )


# ifndef SHAPE_H

# define SHAPE_H // Опред. абстрактн. кл.

class shape {protected : double param; // Т.к. поле param должно быть доступно в производном классе, поэтому мы используем protected – спец. доступа //

public : shape (double); // конструктор базового класса

virtual double area ( ) = Ø; // чисто виртуальная функция

}; // конец определения базового класса.


class circle : public shape { double radius;

public: circle (double);

double area ( ); }


class triangle : public shape { double hight, dlina;

public: triangle (double, double);

double area ( ); }


class rectangle : public shape

{ double

void print_area (shape*ptr) {cout<<”Пл = ”<
→area ( )<
main ( )

{circle c (5);

triangle t (4,32);

rectangle r (2,7);

print_area (& c); // площадь круга

print_area (& t); // площадь треугольника


Функция print_area одинаково вызывает разные функции area, которые являются методами разных классов в зависимости от объекта, на который будет указывать параметр print_area.

Это означает, что компилятор не может однозначно определить ссылку на функцию во время компиляции метода area, а компоновщик, соответственно разрешить эту ссылку. Поэтому ссылка не разрешена до выполнения программы. Окончательное связывание с нужным методом производит система исполнения. Это – позднее связывание.


Множественное наследование.


class произв_кл : квалификатор_доступа Баз_класс1;

квал_доступа Баз_класс 2,…


Например:


Баз.кл.А Баз.кл.В

Поле а Поле b

Методы Методы




Произв.кл.С

Поле с

Методы


// Файл заголовочн. abc.h

# ifndef abc_h

# define abc_h

// 1 класс

class A {protected: int a;

public: A (int);

virtual void Show ( );}; // конец определения класса

// 2 класс

сlass B {protected: int b;

public: B (int);

virtual void Show ( );};

class C: public A, public B {

int c;

public: c (int, int, int);

void show ( ); };

// Файл реализации abc.cpp.

# include “abc.h”

# include


A :: A (int n) : a(n) { }

B :: B (int n) : b(n) { }

C :: C (int k, int l, int m)

A(k), B(l), C(m) { }

void A :: show ( ) {cout<<”a = ”<
void B :: show ( ) {cout<<”b = ”<
void C :: show ( ) {cout<<”a = ”<

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


// Демо-файл demo_abc.cpp

# include abc.h

void main ( )

{A *a = new A (1); // объекты базового класса

a → show ( );

delete a;

B *b = new B (2);

b → show ( );

delete b;

a = new C (3,4,5); // доступ к объектам произв.кл.

// производится с помощью указателя базового класса.

a → show ( );

delete a;


b = new C (6,7,8);

b → show ( );

delete b; }


Библиотека потоков языка СИ++ представляет собой набор классов для управления ввода/вывода. В сам язык СИ++ средства ввода/вывода не входят.

iostream.h


Потоки ввода / вывода.


Библиотека потоков обеспечивает механизмами для извлечения данных из потоков и для помещения данных в поток.

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


В зависимости от реализации направления передачи данных потоки делят на 3 группы:

1). Входные (из которых читается информация);

2) Выходные (в которые вводятся данные);

3) Двунаправленные.


ios – базовый потоковый класс;

fstream – класс двунаправленных файловых потоков


ios

класс

двунаправленных

потоков

istream ostream

классы классы

входных iostream выход.

файлов. файлов. поток. ifstream ofstream потоков

fstream


iostream.h – Для ios, istream, ostream.

fstream .h – Для ifstream, ofstream, fstream.


ios – базовый потоковый класс, который содержит все общие сведения, то есть данные, которые относятся к состояниям потоков и методы, которые позволяют менять эти свойства.

Стандартные объекты – потоки.


сin – объект стандартного класса istream, который связан со стандартным входным потоком (клавиатура).


сout - объект класса ostream, который связан со стандартным выходным потоком (монитор).


Операции помещения и извлечения.


Как видно из иерархии для ввода и вывода есть 2 основных класса: istream, ostream.

Класс ostream используют для вывода операции левого сдвига (<< ) , перегрузив её. Если эта операция применяется к объектам потока, то её называют операцией помещения в поток.


cout<< “ Hello ! ”;


Класс istream перегружает для реализации ввода оператор правого сдвига, который называется операцией извлечения из потока.


cin>> name;