Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Конспект лекций ООПро.doc
Скачиваний:
43
Добавлен:
13.04.2015
Размер:
431.62 Кб
Скачать

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

Ключевое слово friend(друг) служит спецификатором, уточняющим свойства функции. Оно дает функции-не-члену доступ к скрытым членам класса и предоставляет способ для обхода ограничений сокрытия данных вC++. Однако должна быть веская причина для обхода этих ограничений, поскольку они важны для надежного программирования.

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

class Matrix;

class Vector{

float v[4];

//...

friend Vector operator*(const Matrix&,const Vector&);

};

class Matrix{

Vector v[4];

//...

friend Vector operator*(const Matrix&,const Vector&);

};

Vector operator*(const Matrix& m,const Vector& v)

{

Vector r;

for(int i=0;i<4;i++){

r.v[i]=0;

for(int j=0;j<4;j++)r.v[i]+=m.v[i].v[j]*v.v[j];

}

return r;

}

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

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

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

Производный класс

Класс можно сделать производным от существующего с использованием следующей формы:

class имя_класса: (public|protected|private)opt имя_базового_класса

{

объявления членов

};

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

Ключевые слова public,protectedиprivateиспользуются для указания того, насколько члены базового класса будут доступны для производного. Ключевое словоprotected – промежуточная форма доступа междуpublicиprivate.

Рассмотрим пример:

<сначала – только члены данных>

<потом – функции-члены>

<потом – конструкторы>

//inheritance.h

class employee

{

public:

employee (char *name, double salary);

void print()const;

private:

char nm[30];

double slry;

};

enum language {ASSEMBLER,BASIC,PASCAL,C,CPP,SMALLTALK};

class programmer1

{

public:

programmer1(char *name, double salary, language lang);

void print()const;

private:

char nm[30];

double slry;

language lng;

};

class programmer2:public employee

{

public:

programmer2(char *name, double salary, language lang);

void print()const;

private:

language lng;

};

//inheritance.cpp

#include "inheritance.h"

employee::employee(char *name, double salary)

:slry(salary)

{

strcpy(nm,name);

}

void employee::print()const

{

cout<<endl<<"Name: "<<nm<<", Salary: "<<slry<<"$";

}

programmer1::programmer1(char *name, double salary, language lang)

:slry(salary),lng(lang)

{

strcpy(nm,name);

}

void programmer1::print()const

{

cout<<endl<<"Name: "<<nm<<", Salary: "<<slry<<"$, Language:";

switch(lng){

case C:cout<<"C";break;

case CPP:cout<<"C++";break;

default:cout<<"Another language";break;

}

}

programmer2::programmer2(char *name, double salary, language lang)

:employee(name,salary),lng(lang)

{}

void programmer2::print()const

{

employee::print();//печатьинформацииоработнике

// должен использоваться оператор::, потому что

// print() была замещена в programmer2

// ошибка:

// cout<<endl<<"Name: "<<nm<<", Salary: "<<slry<<"$, Language:";

// нельзя обратиться к закрытым членам базового класса

cout<<", Language:";//печать информации, относящейся

switch(lng){ // только к программисту

case C:cout<<"C";break;

case CPP:cout<<"C++";break;

default:cout<<"Another language";break;

}

}

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

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

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

employee e1("John",80),e2("Ben",90);

v.print();p.print();

programmer1 p1("Ron",200,BASIC);

p1.print();

programmer2 p2("Michael",500,C);

p2.print();

Как видите, использование классов programmer1 (без наследования) иprogrammer2 (с наследованием) здесь не отличается. Однако наследование имеет преимущество. Определение классаprogrammer2 как производного отemployeeделает его подтипомemployee. Это значит, что объектомprogrammer2 можно пользоваться везде, где допустимemployee. Например:

print_in_report(const employee& e){ e.print(); }

print_in_report(e1);

//print_in_report(p1); //error

print_in_report(p2); //OK

Копирование и срезка

employee arr_emp[3]={e1,p2,e2};

mas[0]=v;

// mas[0]=p1;//error

for(int i=0;i<3;++i)mas[i].print();

programmer2 p;

employee r=p;//срезка

Копируется только employee-частьprogrammer2. Этот эффект называютсрезкой. Часто он является нежелательным. Одной из причин использования указателей и ссылок на объекты в иерархии является желание избежать срезки. Например:

employee *rptr=&r;//адрес работника

rptr=&p;//адрес программиста. Срезка отсутствует

Производные классы (Derived* можно использовать какBase*)

Функции-члены (замещение, обращение к ф-членам базового кл.)

Конструкторы и деструкторы (инициализация членов базового класса через конструктор базового класса)

UML

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

Виды диаграмм:

  1. Диаграммы прецедентов

  2. Диаграммы видов деятельности

  3. Диаграммы взаимодействий (подвид – диаграмма последовательностей)

  4. Диаграммы классов

  5. Диаграммы состояний

  6. Диаграммы развертывания

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

Для чего используется язык UML

Прежде всего UML – язык общения с самим собой, членами команды и клиентами.

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

Диаграмма классов

Описывает классы и отражает отношения, существующие между ними.

Класс обозначается прямоугольником, содержащим до трех элементов:

  • Имя класса

  • Имена методов (функций) класса

  • Имена данных-членов класса

Модификаторы доступа обозначаются знаками + public, #protected, -private.

Возможны следующие типы отношений между классами.

  • Отношение “is-a– в этом случае один класс является подвидом другого класса

  • Отношение “has-a – когда один объект одного класса «содержит» объект другого класса в качестве элемента данных.

Кардинальность (или кратность) элементов отношения показывают количество включаемых объектов.

Иерархия классов

Базовый класс (employee) может иметь несколько производных классов (teacher,manager, …):

class teacher: public employee{…};

class manager: public employee{…};

Производный класс (teacher), в свою очередь, сам может быть базовым классом (дляenglish_teacher):

class english_teacher: public teacher{…};

Такой набор связанных классов называется иерархией классов. Иерархия имеет вид дерева (если не использовать множественное наследование).

Преимущества использования производных классов:

  • Код используется повторно. Тип programmer2 использует существующий, хорошо проверенный код изemployee;

  • Иерархия отражает взаимоотношения, свойственные проблемной области;

  • Различные полиморфные механизмы позволяют клиентскому коду рассматривать programmer2 в качестве подтипаemployee, что упростит клиентский код.

Преобразование типов

Указатель на производный класс может быть преобразован в указатель на базовый класс.

У нас есть объект programmer2:

employee r("Vasya",80),*pr;

pr=&r;

pr->print();//employee::print

programmer2 p("Alex",300,CPP), *pp;

pr=pp=&p;//преобразование

pp->print();//programmer2::print

pr->print();//employee::print

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

Поля типа

Для того, чтобы через указатель на базовый класс вызывать подходящие функции (programmer2::print или employee::print), мы должны решить, какому типу на самом деле принадлежит объект, на который указывает указатель?

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

class employee{

public:

enum Rab_type{R,P};

Rab_type type;

employee():type(R){}

void print()const;

};

class programmer:public employee{

public:

programmer(){type=P;}

void print()const;

};

void employee::print()const{

cout<<"employee::print"<<endl;

}

void programmer::print()const{

cout<<"programmer::print"<<endl;

}

void print(employee* pr){

switch(pr->type){

case employee::R:

pr->print();

break;

case employee::P:

const programmer* p=static_cast<const programmer*>(pr);

p->print();

break;

}

}

Добавление нового производного от employeeкласса подразумевает внесение изменений во все ключевые функции системы:

  1. в базовом классе employeeв перечислимый тип добавляется новая константа;

  2. пишется производный класс (аналогично programmer);

  3. в функцию ::print добавляется новая ветвь.