
Лекция3. Наследование_в_С++.ppt
- Количество слайдов: 38
Наследование в С++
Зачем нужно наследование Повторное использование кода – использование класса для создания объектов и в качестве базового для создания нового класса l Построение иерархии классов позволяет выполнить упорядочивание классов l При наследовании можно расширить характеристики класса, сузить их, изменить или уничтожить l
Доступ при наследовании Ключ доступа Спецификатор в базовом классе Доступ в производном классе private protected public Нет private protected public Нет protected private Нет public protected public По умолчанию наследование частное. Можно изменить (восстановить) уровень доступа некоторых членов базового класса. сlass Derived: Base{ public : Base: : m 1; // теперь он общий член класса Derived protected : Base: : m 2; };
Доступ при наследовании class Base { int m 3; protected: int m 2; public: int m 1; }; class Privateclass: private Base{ void test(){ m 1=1; m 2=2; m 3=3; //недоступен }}; class Derivedpri: public Privateclass{ void test(){ m 1=1; m 2=2; //недоступны m 3=3; }}; class Protectedclass: protected Base{ void test(){ m 1=1; m 2=2; m 3=3; //недоступен }}; class Derived. Pro: : public Protectedclass{ void test(){ m 1=1; m 2=2; m 3=3; // недоступен class Publicclass: public Base{ void test(){ m 1=1; m 2=2; m 3=3; // недоступен }}; class Derived. Pub: public Publicclass{ void test(){ m 1=1; m 2=2; m 3=3; // недоступен }}; void fn(){ Privateclass Obj 1; Obj 1. m 1=1; // недоступны Obj 1. m 2=2; Obj 1. m 3=3; Protectedclass Obj 2; Obj 2. m 1=1; Obj 2. m 2=2; //недоступны Obj 3. m 3=3; Publicclass Obj 3; Obj 3. m 1=1; //доступен Obj 3. m 2=2; Obj 3. m 3=3; //недоступны }
Простое наследование Не наследуются: Конструкторы; l Деструкторы; l Переопределенные операции new; l Переопределенные операции присваивания; l Отношения дружественности l
Простое наследование l Класс имеет один базовый класс First int a f a void set. A(int) Second s a b int b void set. B(int) Third int c void set. C(int) t a b c class First { int a; public: void set. A(int _a){ a = _a; } }; int a class Second: public First { int b; public: set. A(int) void set. B(int _b){ b = _b; } }; class Third: public Second { int c; public: void set. C(int _c){ c = _c; } }; int main(int argc, char* argv[]) { First f; Second s; Third t; cout<< sizeof ( f) << " " << sizeof( s) << " " << sizeof( t)<<'n'; //4 8 12 return 0; }
Порядок вызова конструкторов – от базового класса (First, Second, Third) class First { int a; public: First(int _a=0): a(_a){} void set. A(int _a){a = _a; } }; class Second: public First { int b; public: Second(int _a=0): First(_a){b = _b; } void set. B(int _b){b = _b; } }; class Third: public Second { int c; public: Third(int _a=0): Second(_a){c = _c; } void set. C(int _c){c = _c; } }; int main(int argc, char* argv[]) { First f; Second s; Third t; return 0; } First(int _a=0): a(_a){} void set. A(int _a){a = _a; } }; class Second: public First { int b; public: Second(int _a=0){b = _b; } void set. B(int _b){b = _b; } }; class Third: public Second { int c; public: Third(int _a=0){c = _c; } void set. C(int _c){c = _c; } }; int main(int argc, char* argv[]) { First f; Second s; Third t; return 0; }
Порядок вызова деструкторов – в обратном порядке class First { int a; public: ~Third l ~Second l ~First l First(int _a=0): a(_a){cout <<"Firstn"; } void set. A(int _a){a = _a; } ~First(){cout <<"~Firstn"; } }; class Second: public First { int b; public: Second(int _a=0){b = _a; cout <<"Secondn"; } void set. B(int _b){b = _b; } ~Second(){cout <<"~Secondn"; } }; class Third: public Second { int c; public: Third(int _a=0){c = _a; cout <<"Thirdn"; } void set. C(int _c){c = _c; } ~Third(){cout <<"~Thirdn"; } }; int main(int argc, char* argv[]) { First f; Second s; Third t; return 0; }
Правила наследования различных методов l l l Конструкторы не наследуются, поэтому производный класс должен иметь собственные конструкторы Если в конструкторе производного класса явный вызов конструктора базового класса отсутствует, то вызывается конструктор базового класса по умолчанию При многоуровневой иерархии вызов конструкторов начинается с самого верхнего уровня Если класс содержит члены другого класса, то сначала вызываются конструкторы членов класса, а потом конструктор класса Не наследуется операция присваивания. Ее надо явно определить в производном классе. Деструкторы не наследуются. Если деструктор в производном классе не определен, то он формируется по умолчанию и вызывает деструкторы базовых классов, причем в порядке, обратном вызову конструкторов.
Полиморфизм. Виртуальные функции Раннее или статическое связывание class Base { public : void print() {cout<<“nbase”; } }; class Dir : public Base { public: void print() {cout<<“ndir”; } }; void main() { Base B, *bp = &B; Dir D, *dp = &D; Base *p = &D; bp –>print(); // base dp –>print(); // dir p –>print(); // base } Позднее или динамическое связывание class Base { public: virtual void print(){cout<<“nbase”; } }; class Dir : public Base { public: void print(){cout<<“ndir”; } }; void main() { Base B, *bp = &B; Dir D, *dp = &D; Base *p = &D; bp –>print(); // base dp –>print(); // dir }
Полиморфизм. Виртуальные функции class Base { protected: int h, r; public: Base(int a, int b): h(a), r(b) {} virtual double f 1() { return 3, 14*r*r*h; } //V цилиндра void f 2() { cout<< f 1(); } }; class Derived: public Base { public: Derived(int a, int b): Base(a, b) {} double f 1() { return 3, 14*r*r*h*1/3; } // V конуса }; void main() { Base B(5, 2); B. f 2(); Derived D(5, 2); D. f 2(); }
Абстрактные классы Чистые виртуальные функции Абстрактным называется класс, в котором есть хотя бы одна чистая (пустая) виртуальная функция. Чистой виртуальной называется компонентная функция, которая имеет следующее определение: virtual тип имя_функции(список_формальных_параметров) = 0; Чистая виртуальная функция ничего не делает и недоступна для вызовов. Ее назначение – служить основой для подменяющих ее функций в производных классах. Абстрактный класс может использоваться только в качестве базового для производных классов. По сравнению с обычными классами абстрактные классы пользуются “ограниченными правам”. А именно: l невозможно создать объект абстрактного класса; l абстрактный класс нельзя употреблять для задания типа параметра функции или типа возвращаемого функцией значения; l абстрактный класс нельзя использовать при явном приведении типов; в то же время можно определить указатели и ссылки на абстрактный класс. l объект абстрактного класса не может быть формальным параметром функции, однако формальным параметром может быть указатель на абстрактный класс.
Абстрактные классы Чистые виртуальные функции class figura{ public: virtual double area()=0; }; class rectangle: public figura { double h, l; public: rectangle(double a, double b): h(a), l(b); double area() {return h*l; } }; class circle: public figura { double r; public: circle (double a): r(a); double area() {return 3. 14*r*r; } }; void main() { rectangle obj 1(10, 10); circle obj 2(100); cout<
class Person { // Абстрактный класс public: Person() { strcpy(name, "NONAME"); age=0; next=0; } Person(char*NAME, int AGE) { strcpy(name, NAME); age=AGE; next=0; } virtual~Person(){}; virtual void Show()=0; virtual void Input()=0; friend class List; //для доступа в классе List к next protected: char name[20]; //имя int age; //возраст Person* next; }; class Student: public Person{ //Класс- СТУДЕНТ public: Student() {grade=0; } Student(char* NAME, int AGE, float GRADE): Person(NAME, AGE) {grade=GRADE; } void Show() { cout<<"name="<
Однонаправленный список объектов связанных наследованием void main() { List list; Student* ps; Teacher* pt; ps=new Student("Иванов", 21, 50. 5); list. Insert(ps); pt=new Teacher("Котов", 34, 10); list. Insert(pt); ps=new Student(); ps->Input(); list. Insert(ps); pt=new Teacher(); pt->Input(); list. Insert(pt); list. Show(); }
Включение объектов Есть два варианта включения объекта типа X в класс A; 1)Объявить в классе А член типа Х; class A{ X x; //. . . }; 2)Объявить в классе А член типа X*. class A{ X* p; }; Включая другие объекты, мы создаем иерархию объектов. Она является альтернативой и дополнением к иерархии классов. class person { /// Персона char* name: public: person(char*); //. . . }; class school {//Школа person head; //директор public: school(char* name): head(name){} //. . . };
Включение объектов Если число включаемых объектов неизвестно, то создаем динамический список: class person{ char* name; person* next; . . . }; class school{ person* head; // указатель на директора школы person* begin; // указатель на начало // списка учеников public: shool(char* name): head(new person(name)), begin(NULL){} ~shool(); void add(person* ob); //. . . }; Второй способ заключается в использовании специального объекта-контейнера. Контейнерный класс предназначен для хранения объектов и представляет удобные простые и удобные способы доступа к ним. class school{ person* head; container pupil; . . . };
Включение и наследование 1). Пусть класс В есть производный класс от класса D. class B{. . . }; class D: public B{. . . }; Слово public в заголовке класса D говорит об открытом наследовании. Это означает что производный класс D является подтипом класса В, т. е. объект D является и объектом В. Такое наследование является отношением is-a или говорят, что D есть разновидность В. Иногда его называют также интерфейсным наследованием. Используя наследование мы строим иерархию классов. 2). Пусть теперь класс D имеет член класса В. class D{ public: B b; …}; Такое включение называют отношением has-a. Используя включение мы строим иерархию объектов. 3). Если мы хотим от наследования только повторное использование кода, то решением здесь является закрытое наследование. Закрытое наследование не носит характера отношения подтипов, или отношения is-a. Будем называть его отношением like-a (подобный) или наследованием реализации, в противоположность наследованию интерфейса. Закрытое (также как и защищенное) наследование не создает иерархии типов.
Множественное наследование Animal Tovar char * name float cost Animal(char *s) char* Name() Tovar(float c) float Cost() Zoomag char * address Zoomag(char *n, char *adr, float c) char Address() Zoomag obj(“Кот”, ”ул. Ленина”, 5. 7); cout << “Цена”<
Вызов конструкторов базовых классов class Zoomag : public Animal, public Tovar { Zoomag(char *n, char *adr, float c): Animal(n), Tovar(c) { address = adr; } };
Особенности множественного наследования class circl { // окружность int x, y, r; public: circl(int x 1, int y 1, int r 1){x = x 1; y = y 1; r = r 1; } void show() {} }; class square { // квадрат int x, y, l; public: square(int x 1, int y 1, int l 1){x = x 1; y = y 1; l = l 1; } void show() {} }; class circlsqrt : public circl, public square { public: circlsqrt(int x 1, int y 1, int r 1) : circl(x 1, y 1, r 1), square(x 1, y 1, 2*r 1) {. . . } void show() { circl : : show(); square : : show(); } }; class X{. . . f(); . . . }; class Y : public X{. . . }; class Z : public X{. . . }; class A : public Y, public Z{. . . }; Такое дублирование класса соответствует включению в производный объект нескольких объектов базового класса. Cуществуют два объекта класса Х в объекте класса А. Для устранения возможных неоднозначностей нужно обращаться к конкретному компоненту класса Х, используя полное имя Y : : X : : f() или Z : : X : : f()
Виртуальные базовые классы A class A B { int i, j; public: A(){i = j = 0; } A(int _i, int _j){i = _i; j = _j; } }; class B: public A { int i, j; public: B(){i = j = 0; } B(int _i, int _j){i = _i; j = _j; } }; class C: public A { int i, j; public: C(){i = j = 0; } C(int _i, int _j){i = _i; j = _j; } }; class D: public B, public C { public: D(int k): B(k, k), C(k, k){} } A C D A class A { int i, j; public: A(){i = j = 0; } A(int _i, int _j){i = _i; j = _j; } }; class B: public virtual A { int i, j; public: B(){i = j = 0; } B(int _i, int _j){i = _i; j = _j; } }; class C: public virtual A { int i, j; public: C(){i = j = 0; } C(int _i, int _j){i = _i; j = _j; } }; class D: public B, public C { public: D(int k): B(k, k), C(k, k){} } B C D
Порядок вызова деструкторов при использовании виртуальных базовых классов l Вначале вызывается конструктор виртуального базового класса, а затем конструкторы других классов в порядке объявления при наследовании l Вызов деструкторов – в обратном порядке
class A { int i, j; public: A(){i = j = 0; } A(int _i, int _j){i = _i; j = _j; } }; class B: public A { int i, j; public: B(){i = j = 0; } B(int _i, int _j){i = _i; j = _j; } }; class C: public A { int i, j; public: C(){i = j = 0; } C(int _i, int _j){i = _i; j = _j; } }; class D: public B, public C { public: D(int k): B(k, k), C(k, k){} }; int main(int argc, char* argv[]) { A a; B b; C c; D d(55); cout << sizeof(a)<<" "<
Разрешение видимости class A { public: int i, j; A(){i = j = 0; } A(int _i, int _j){i = _i; j = _j; } }; class B: public virtual A { public: int i, j; B(){i = j = 0; } B(int _i, int _j){i = _i; j = _j; } }; class C: public virtual A { public: int i, j; C(){i = j = 0; } C(int _i, int _j){i = _i; j = _j; } }; class D: public B, public C { public: D(int k): B(k, k), C(k, k){} } D d(55); //d. i d. A: : i = 77; d. B: : i = 77; d. C: : i = 77;
Преобразование типов в наследовании Преобразование к базовому типу делается по умолчанию l class A { int a; public: A(int _a=0){a=_a; } }; class B: public A { int b; public: B(int _b=0): A(_b){b=_b; } }; int main(int argc, char* argv[]) { A * pa = new B(55); B * pb = (B*)pa; pa = new A(55); pb = (B*)pa; //B * pb 1 = new A(10); return 0; }
Невиртуальные деструкторы Абстрактные классы Чистые виртуальные функции int main(int argc, char* argv[]){ class Figure { Figure * p[3]; int x, y; p[0] = new Circle (55); public: p[1] = new Rectangle (66, 99); Figure(int _x=0, int _y = 0): x(_x), y(_y){} p[2] = new Circle(77); virtual void draw()=0; for ( int i = 0; i < 3; i++ ) ~Figure(){cout << "~Figuren"; } p[i]->draw(); }; for ( int i = 0; i < 3; i++ ) class Circle: public Figure { delete p[i]; int rad; return 0; public: } Circle(int r, int _x=0, int _y = 0): rad(r){} void draw(){cout << "Circlen"; } ~Circle(){cout << "~Circlen"; } }; class Rectangle: public Figure { int width, height; public: Rectangle(int w, int h, int _x=0, int _y = 0): width(w), height(h){} void draw(){cout << "Rectanglen"; } ~Rectangle(){cout << "~Rectanglen"; } };
Осторожность int main(int argc, char* argv[]) { class Figure { Figure * p[3]; int x, y; p[0] = new Circle (55); public: p[1] = new Rectangle (66, 99); Figure(int _x=0, int _y = 0): x(_x), y(_y){} p[2] = new Circle(77); virtual void draw()=0; for ( int i = 0; i < 3; i++ ) virtual ~Figure(){ cout << "~Figuren"; } p[i]->draw(); }; for ( int i = 0; i < 3; i++ ) class Circle: public Figure { delete p[i]; int rad; return 0; public: } Circle(int r, int _x=0, int _y = 0): rad(r){} void draw(){cout << "Circlen"; } ~Circle(){cout << "~Circlen"; } }; class Rectangle: public Figure { int width, height; public: Rectangle(int w, int h, int _x=0, int _y = 0): width(w), height(h){} void draw(){cout << "Rectanglen"; } ~Rectangle(){cout << "~Rectanglen"; } };
int main(int argc, char* argv[]) { Figure ** p; *p = new Figure[3]; //ошибка – абстрактный класс p[0] = new Circle (55); p[1] = new Rectangle (66, 99); p[2] = new Circle(77); for ( int i = 0; i < 3; i++ ) p[i]->draw(); for ( i = 0; i < 3; i++ ) delete p[i]; return 0; } int main(int argc, char* argv[]) { Circle** p; *p = new Circle[3]; // нет соответствующего конструктора p[0] = new Circle (55); p[1] = new Rectangle (66, 99); // не связаны наследованием p[2] = new Circle(77); for ( int i = 0; i < 3; i++ ) p[i]->draw(); for ( i = 0; i < 3; i++ ) delete p[i]; return 0; }
Нельзя: l Делать виртуальными конструкторы l Делать виртуальными статические функции-члены класса
Локальные и вложенные классы Класс может быть объявлен внутри блока. Такой класс называется локальным. Это предполагает недоступность его компонентов вне области определения класса (вне блока). l Локальный класс не может иметь статических данных. l Внутри локального класса разрешено использовать из объемлющей его области только: l имена типов, статические (static) переменные, внешние (extern) переменные, внешние функции и элементы перечислений. l Запрещено - переменные автоматической памяти. l Компонентные функции могут быть только inline. Внутри класса разрешается определять типы, следовательно, один класс может быть описан внутри другого. Такой класс называется вложенным. Вложенный класс является локальным для класса, в котором он описан, и на него распространяются правила использования локального класса. Вложенный класс не имеет никакого особого права доступа к членам охватывающего класса, то есть он может обращаться к ним только через объект типа этого класса (так же как и охватывающий класс не имеет какихлибо особых прав доступа к вложенному классу).
Пример вложенного класса int i; class global{ static int n; public: int i; static float f; class intern{ void func(global& ob) { i=3; // Ошибка: используется имя нестатического данного // из охватывающего класса f=3. 5; // Правильно: f-статическая переменная : : i=3; // Правильно: i-внешняя (по отношению к классу) // переменная ob. i=3; // Правильно: обращение к членам охватывающего // класса через объект этого класса n=3; // Ошибка: обращение к private-члену охватывающего класса } } };
Вложенный класс class point{// точка protected: int x, y; public: point(int x 1=0, int y 1=0): x(x 1), y(y 1){} int& getx(){return x; } int& gety(){return y; } }; class rect{// прямоугольник class segment{// вложенный клас “отрезок” point a, b; //начало и конец отрезка public: segment(point a 1=point(0, 0), point b 1=point(0, 0)) { a. getx()=a 1. getx(); a. gety()=a 1. gety(); b. getx()=b 1. getx(); b. gety()=b 1. gety(); } point& beg(){return a; } point& end(){return b; } void Show() { //показать отрезок line(a. getx(), a. gety(), b. getx(), b. gety()); } }; //конец класса segment ab, bc, cd, da; //стороны прямоугольника public: rect(point c 1=point(0, 0), int d 1=0, int d 2=0) { point a, b, c, d; //координаты вершин a. getx()=c 1. getx(); a. gety()=c 1. gety(); b. getx()=c 1. getx()+d 1; b. gety()=c 1. gety(); c. getx()=c 1. getx()+d 1; c. gety()=c 1. gety()+d 2; d. getx()=c 1. getx(); d. gety()=c 1. gety()+d 2; ab. beg()=a; ab. end()=b; //граничные точки отр. bc. beg()=b; bc. end()=c; cd. beg()=c; cd. end()=d; da. beg()=d; da. end()=a; } void Show(){ ab. Show(); bc. Show(); cd. Show(); da. Show(); } };
Вложенный класс void main() { point p 1(120, 80); point p 2(250, 240); rect A(p 1, 80, 30); rect B(p 2, 100, 200); A. Show(); B. Show(); }
Отношение между классами l агрегация (Aggregation); l ассоциация (Association); l наследование (Inheritance);
Наследование
Агрегация Число объектов: "1. . n" - от единицы до бесконечности; "0" - ноль; "1" - один; "n" - фиксированное количество; "0. . 1" - ноль или один. // определение класса Fish public class Fish { private: Aquarium home; public: Fish() { } } // определение класса Aquarium public class Aquarium { private: Fish inhabitants[]; public: Aquarium() { } }
Ассоциация public class Programmer { private: Computer computers[]; public: Programmer() { } } public class Computer { private: Programmer programmers[]; public: Computer() { } }