继承概览
面向对象的继承与派生
继承和派生:一个新类从现有的类继承 特征(属性和方法),从现有的类产生新类的过程称为派生
现有的用来派生新类的类成为基类或父类 ,派生出来的类成为派生类或子类
派生类可以作为基类继续派生新的类,从而形成复杂的类的层次结构
类继承的层次结构:下层具有上层的特征同时还加入独属于自己特生,即逐层细化具体 ,如此更符合人类认识世界的规律
派生类的定义
1 2 3 4 5 6 7 8 class 派生类名: 继承方式 基类名{ private : 成员声明列表 protected : 成员声明列表 public : 成员声明列表 };
派生类的示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Point //基类{ double x,y; public : Point(){ x=0 ; y=0 ; } Point( double a, double b) { x=a; y=b; } void setXY ( double a, double b) { x=a; y=b; } double getX () { return x; } double getY () { return y; } }; class Circle : public Point { double radius; public : Circle() { radius=0 ; } Circle( double a, double b,double r):Point(a,b){ radius=r; } void setR (double r) { radius=r; } double getR () { return radius; } }; int main () { Circle c (3 ,4 ,5 ) ; cout <<c.getX()<<endl ; cout <<c.getY()<<endl ; cout <<c.getR()<<endl ; return 0 ; }
公有派生类对象可以访问基类的公有成员就像访问自己的公有成员函数一样
派生类构造函数必须提供初始化从基类继承来的数据成员机制(初始化列表),否则将直接调用基类的无参构造函数来对初始化从基类继承的数据成员
派生类对基类的扩充
派生类继承了基类除构造函数、析构函数以外所有的数据成员和成员函数,实现了代码重构
派生类必然具有某些和基类不同的属性和行为,需要对基类进行部扩充和改
扩充:在派生类中增加新的成员函数和数据成员
改造:当继承而来的成员函数不能满足需求时可以进行覆盖操作。覆盖就是在派生类中定义与基类同名的函数从而实现对成员函数的改造。覆盖同理也可以针对数据成员进行改造,但是要慎用!
重载是同一个类中定义同名但是参数不同的函数,覆盖是不同类中同名的函数
继承方式
公有继承
基类的公有成员在派生类中仍然是公有成员
基类的保护成员在派生类中仍然是保护成员
基类的私有成员在派生类中不可访问
继承的基类成员
派生类内部直接访问
派生类外部类名访问
公有成员
✔
✔
保护成员
✔
✖
私有成员
✖
✖
自身添加的成员
派生类内部直接访问
派生类外部类名访问
公有成员
✔
✔
保护成员
✔
✖
私有成员
✔
✖
基类A访问权限
派生类B访问权限
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Point { double x; protected : double y; public : Point(double a, double b){ x = a; y = b; } void setX (double a) { x = a; } double getX () { return x; } }; class Circle : public Point{ double radius; public : Circle(double a, double b, double r) : Point(a, b), radius(r){} void setXYR (double a, double b, double r) { x = a; setX(a); y = b; radius = r; } };
保护继承
基类的公有成员和保护成员都变为派生类的保护成员
基类的私有成员在派生类中不可访问
一个其他继承方式没有的特点:多次保护继承派生出的类可以直接访问之前所有的父类成员
继承的基类成员
派生类内部直接访问
派生类外部类名访问
公有成员
✔
✖
保护成员
✔
✖
私有成员
✖
✖
自身添加的成员
派生类内部直接访问
派生类外部类名访问
公有成员
✔
✔
保护成员
✔
✖
私有成员
✔
✖
相当于在公有继承方式上又禁止了派生类外部通过类名访问原基类的公有成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 class Point { double x; protected : double y; public : Point(double a, double b){ x = a; y = b; } void setX (double a) { x = a; } int getX () { return x; } }; class Circle : protected Point{ double radius; public : Circle(double a, double b, double r) : Point(a, b){ radius = r; } void setXYR (double a, double b, double r) { x = a; setX(a); y = b; radius = r; } void setRadius (double r) { radius = r; } }; class Test : protected Circle{ int color; public : Test(double a, double b, double r, int c) : Circle(a, b, r){ color = c; } void setXYRC (double a, double b, double r, int c) { setX(a); y = b; setRadius(r); color = c; } };
私有继承
基类的公有成员和保护成员都成为派生类成员的私有成员
基类的私有成员在派生类中不可访问
继承的基类成员
派生类内部直接访问
派生类外部类名访问
公有成员
✔
✖
保护成员
✔
✖
私有成员
✖
✖
自身添加的成员
派生类内部直接访问
派生类外部类名访问
公有成员
✔
✔
保护成员
✔
✖
私有成员
✔
✖
虽然上述列表和保护成员的列表相同,但是继承得来的公有成员和保护成员变为了派生类的私有成员(权限更加小了),所以后面派生类再从当前派生类保护继承也不能访问当前派生类继承得来的公有成员和保护成员了
注意区分保护继承和私有继承:他们限制是后代派生类访问当前派生类继承得来的公有成员和保护成员的权限
基类A访问权限
派生类B和C访问权限
基类成员在派生类中权限对比
禁止继承
某些情况下如果不想让后序代码继承当前的类,可以借助 C++ 11 的 final
关键词修饰类,后序如果调用编译器报错
1 2 3 4 5 6 class Test final { ... } class Sub : public Test{ ... }
构造与析构
构造基类成员和自身成员
由于派生类继承了基类的所有成员,派生类的每一个对象都包含基类数据成员的值,在派生类构造函数中应该提供初始化列来初始化基类数据成员的机制(一般使用初始化列表)
非要在花括号内赋值?当然可以但是效率太低,不是真正意义上的初始化而是定义,初始化列表是真正的初始化,在创建之时进行
与容器类初始化对象成员类似,派生类也必须通过初始化列表初始化基类数据成员 。对本类的数据成员,可以用初始化列表,也可以放在构造函数体内赋值
构造函数的调用顺序
析构函数的调用顺序
类似栈的操作顺序,析构函数调用顺序恰好和构造函数的调用顺序相反
创建派生类对象时(A 派生 B)
调动派生类 B 的析构函数
调用 B 中对象成员(如果有的话)对应的析构函数
调用基类 A 析构函数
调用 A 中对象成员(如果有的话)对应的析构函数
A -> B -> C(A 派生 B,B 再派生 C)
调用间接派生类 C 的析构函数
调用直接派生类 B 的析构函数
调用基类 A 的析构函数
具体示例
在继承过程中,构造函数和析构函数不能被继承 ,在创建派生类对象时按照顺序由系统自动调用 基类和派生类的构造函数,而不能在派生类中显示调用基类构造函数
也不能在派生类中显示调用基类的析构函数
基类和派生类的定义
1 2 3 4 5 6 7 8 class Point { double x,y; public : Point(){ x=0 ; y=0 ; } Point(double a,double b) { x=a; y=b; } ... };
派生类构造函数定义
1 2 3 4 5 6 Circle::Circle(double a,double b,double aa,double bb,double r):Point(a,b),p(aa,bb){ radius=r; } Circle c (3 ,4 ,5 ,6 ,8 ) ;
多重继承
多继承概念
C++ 允许使用多个基类进行继承成为多继承。派生类继承所有基类的成员,定义多继承派生类语法与单继承语法类似,但是要指定所有要继承的基类以及每个基类的继承方式
1 2 3 4 5 6 7 8 class 派生类名: 继承方式1 基类名1 , 继承方式2 基类名2 , ...{ private : 成员声明列表 protected : 成员声明列表 public : 成员声明列表 };
在多继承派生类的构造函数中,要通过初始化列表的形式调用直接基类的构造函数
构造函数的执行顺序:先执行基类构造函数,再执行派生类构造函数;多个基类构造函数按照定义派生类时的顺序进行与初始化列表中顺序无关
使用多继承容易造成混乱尽量避免使用,只用了解
多继承的二义性
问题示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Base1 { public : void draw () {...} }; class Base2 { public : void draw () {...} }; class Derived : public Base1, public Base2{ ... }; int main () { Derived d; d.draw(); return 0 ; }
解决办法
问题可以解决但是不好,因为对于使用的客户不友好,他需要考虑函数所在的类并确定调用前缀
1 2 3 4 5 6 7 8 9 10 11 class Derived : public Base1, public Base2{ ... }; int main () { Derived d; d.Base1::draw(); d.Base2::draw(); return 0 ; }
派生类中提供接口,内部解决二义性方便客户使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Derived : public Base1, public Base2{ public : void draw () { if (...) Base1::draw(); else Base2::draw(); } }; int main () { Derived d; d. draw(); return 0 ; }
另一种多继承二义性
当一个派生类从多个基类继承但是这几个基类又同是另一个基类的直接派生类,这样继承而来的派生类同样会出现二义性问题
问题示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class A { public : double a; ... }; class B : public A{ ... }; class C : public A{ ... }; class D : public B, public C{ public : double getValue () { return a; } ... }; int main () { D d; cout << d.getValue(); return 0 ; }
解决办法
1 2 3 4 5 6 7 8 9 class D : public B, public C{ public : double getValue () { return A::a; return B::a; } ... }
这种办法看似能解决燃眉之急,但是本质上还是从不同的类继承了多个相同的成员,在实际中还是容易造成混乱。例如将上述改为下列修改操作
1 2 3 4 5 6 7 8 void D::setValue () { B::a=5 ; } int main () { D d; d.setValue(); return 0 ; }
这次修改从类 B 继承的 a ,下次修改从 A 继承的 a ?下下次呢?借助不同前缀修改成员最后查询的时候不是你想要的最终的 a。你能保证每次都用一个前缀?那么复杂的工程类代码不把眼看花?
上述二义性问题在于如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留该间接共同基类数据成员的多份同名成员
二义性解决:虚基类
将总的基类定义为成虚基类,可以确保该基类通过多条路径继承时派生类仅仅继承该基类一次,避免上述由于多路径继承造成的二义性
做法:借助虚函数将基类定义为虚基类
具体虚函数使用方法下文有详细讲解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class A { public : double a; }; class B : virtual public A {...};class C : virtual public A {...};class D : public B, public C {public : void setValue () { a=5 ; } ... }; int main () { D d; d.setValue(); return 0 ; }
C++ 规定,由虚基类沿不同路经进行继承时,要求最终的派生类载构造函数中通过初试化参数列表对虚基类成员直接进行初始化,而中间层次的类对虚基类成员的初始化被忽略,尽管他们提供了初始化列表,如此确保了虚基类构造函数只被调用一次
进行多继承时,同时有虚基类和非虚基类是虚基类构造函数优先执行
多态概览
多态指不同的对象接受到相同的消息时产生不同的行为(调用不同的方法)
相当于使用同一个函数名调用不同内容的函数,实现了“一个借口,多种方法”
在 C++ 中通过覆盖、成员函数重载、运算符重载、模版、虚函数等技术,使得基类和派生类中可以出现同名的成员函数。不同的成员函数被调用时表现出不同的行为,表现出很强的灵活性
成员函数覆盖
成员函数重载
运算符重载
虚函数
模板(后续讲解)
静多态性和动态多态性
成员函数的重载和覆盖
赋值兼容性
每一个派生类的对象都是基类的一个对象。赋值兼容规则是指在公有派生情况下 ,一个公有派生类的对象可以当作基类的对象使用,反之则禁止
派生类的对象可以赋值给基类对象
派生类的对象可以初始化基类的引用
指向基类的指针也可以指向派生类
通过基类对象名、指针只能使用基类继承的成员
没必要去死记硬背,根据理解就是允许派生类指向基类的操作(因为基类能做的派生类不能做),但是基类指向派生类的操作部分可以(如果派生类需要的但是基类给不了那就不行)
示例一
1 2 3 Circle c (2 ,3 ,4 ) ;Point p; p = c;
编译器允许用派生类赋值基类,因为基类有的成员派生类肯定有
示例二
1 2 3 Point p (2 ,3 ) ; Circle c; c = p;
编译器报错,派生类 c 并不能根据基类 p 确定所有的成员值
示例三
1 2 Circlr c (2 ,3 ,4 ) ; Point &rp = c;
编译器允许基类引用派生类,但是只能访问基类成员(部分数据和函数),因为其他的成员引用类型限制了它访问
示例四
1 2 Circle c (2 ,3 ,4 ) ;Point *pp=&c;
编译器允许基类指针指向派生类,但是只能访问基类成员(部分数据和函数),因为其他的成员指针类型限制了它访问
类型转换
static_cast
pp 和 ppc 的转换可以通过编译,但运行时会出现崩溃。static_cast
在继承体系中由基类这种转换为子类指针是不安全的
1 2 3 4 5 6 Circle c (2 ,3 ,4 ) ; Point p (2 ,3 ) ; Point *pp=&c; Circle *pc=static_cast <Circle *>(pp); Circle *ppc=static_cast <Circle *>(&p); int i=static_cast <int >(3.2 );
dynamic_cast
dynamic_cast
是一种运行时类型转换,可以转换指针或引用,用于继承体系中的类型转换。若转换失败,返回空指针或抛出异常(引用)
1 2 3 4 5 Circle c (2 ,3 ,4 ) ;Point p (2 ,3 ) ;Point *pp=&c; Circle *pc=dynamic_cast <Circle *>(pp); Circle *ppc=dynamic_cast <Circle *>(&p);
const_cast 和 reinterpret_cast
const_cast
去除常量特性的类型转换,reinterpret_cast
执行任意类型转换且不执行任何类型检查,安全性最差
1 2 3 4 5 6 double *p=new double (3.4 );char *pc=reinterpret_cast <char *>(p);char str[]{“hello”};const char * cps=str;char *ps=const_cast <char *>(cps);ps[1]='t;
覆盖技术
在派生类中定义与基类同名的成员函数后会出现覆盖现象,实现重新定义基类成员函数,对象类型不同调用的同名函数实现也不同
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 const double PI = 3.14159 ;class Point { double x, y; public : Point(double i, double j){ x = i; y = j; } double getArea () { return 0 ; } }; class Circle : public Point { double radius; public : Circle(double a,double b,double r) : Point(a, b){ radius = r; } double getArea () { return PI * radius * radius; } }; int main () { Point a (1.5 ,6.7 ) ; Circle c (1.5 ,6.7 ,2.5 ) ; cout << "area of a:" << a.getArea() << endl ; cout << "area of c:" << c.getArea() << endl ; Point *p = &c; cout << "area of c:" << p->getArea() << endl ; return 0 ; }
如果某些情况下需要该类访问被覆盖了的函数,可以借助前缀名实现,但是不好
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 class Point { double x, y; public : Point(){ x = 0 ;y = 0 ; } Point(double a, double b){ x = a;y = b; } void setVal (double a, double b) { x = a;y = b; } void show () { cout << x <<“,”<< y << endl ; } }; class Circle : public Point{ double radius; public : Circle(double a, double b, double r) : Point(a, b){ radius = r; } void setVal (double a, double b, double r) { Point::setVal(a, b); radius = r; } void show () { cout << radius <<"," ; Point::show(); } }; int main () { Circle c (3 , 4 , 5 ) ; c.show(); c.setVal(5 , 6 , 7 ); c.show(); c.Point::setVal(7 , 8 ); c.Point::show(); return 0 ; }
另外要注意的是同一个类中两个重载版本的函数覆盖并不会起到想象中函数重载的效果,而是直接最后一个函数覆盖之前所有版本的同名函数
总结成一句话:父子类之间的同名函数参数签名不同不会形成重载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 class Point { double x, y; public : Point(){ x = 0 ;y = 0 ; } Point(double a, double b){ x = a;y = b; } void setVal (double a, double b) { x = a;y = b; } void show () { cout << x <<"," << y << endl ; } }; class Circle : public Point{ double radius; public : Circle(double a, double b, double r) : Point(a, b){ radius = r; } void setVal (double a, double b, double r) { Point::setVal(a, b); radius = r; } void show () { cout << radius <<"," ; Point::show(); } }; int main () { Circle c (3 , 4 , 5 ) ; c.show(); c.setVal(5 , 6 , 7 ); c.show(); c.setVal(7 ,8 );c.setVal(7 ); c.Point::setVal(7 , 8 ); c.Point::show(); return 0 ; }
虚函数与运行时多态
知识补充:静态联编与动态联编
运行时多态必要性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 class Mammal { int age; double weight; public : Mammal(int a, double w) { age = a; weight = w; } void shout () { cout <<“I’m a mammal.\n”; } }; class Dog : public Mammal{ public : Dog(int a, double w) : Mammal(a, w) {} void shout () { cout <<“woo.\n”; } }; class Cat : public Mammal{ public : Cat(int a, double w) : Mammal(a, w) {} void shout () { cout <<“meow.\n”; } }; void shout (Mammal *p) { p->shout(); } int main () { Mammal m (3 , 5 ) ; Dog dog (4 , 6 ) ; Cat cat (4 , 7 ) ; shout(&m); shout(&dog); shout(&cat); return 0 ; }
很明显代码想做到基类指针根据指向对象的不同输出不同的结果。但是输出结果却都是 I'm a mammal.
因为定义的基类指针在编译后就成为了确定事实不会改变 ,调用自己的函数而不会动态的根据传入对象的结果调用不同的函数
勉强的一种解决办法就是通过对象分别调用各自的函数
1 2 3 4 5 6 7 8 9 10 int main () { Mammal m (3 ,5 ) ; Dog dog (4 ,6 ) ; Cat cat (4 ,7 ) ; m.shout(); dog.shout(); cat.shout(); return 0 ; }
但是当有上百种不同类型的对象的时候代码要写上百遍,真正解决的办法通过虚函数实现运行时的多态
虚函数
将想要实现运行动态的函数在基类中定义加上关键字 virtual
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Mammal { int age; double weight; public : Mammal(int a, double w){ age = a; weight = w; } virtual void shout () { cout <<"I’m a mammal.\n" ; } }; void shout (Mammal *p) { p->shout(); } int main () { Mammal m (3 , 5 ) ; Dog dog (4 , 6 ) ; Cat cat (4 , 7 ) ; shout(&m); shout(&dog); shout(&cat); return 0 ; }
如此在基类指针调用函数的时候会根据不同的对象类型调用相应的类内的函数,实现了一个借口多种方法的效果,这是静态联编无法实现的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void shout (Mammal *p) { p->shout(); } int main () { Mammal *p; if (...) p = new Dog(3 , 4 ); else p = new Cat(5 , 6 ); shout(p); delete p; return 0 ; }
虚函数说明
虚函数实现动态性关键在于使用基类指针 ,当用基类指针指向不同对象时,到底调用哪个版本的成员函数取决于所指向对象的类型。如果指向 Dog
类对象,则调用 Dog
的 shout()
,反之指向 Cat
类对象就会调用 Cat
类的 shout()
。如果指向 Mammal
类对象那么调用基类的 shout()
用虚函数实现的多态性是代码执行过程中的多态,大大增加了程序的灵活性
子类中覆盖的虚函数可以不加上 virtual
关键字,但是作为良好的习惯方便他人了解哪些是虚函数一般还是会加上 virtual
关键字
override
强制覆盖基类的方法
只有当基类定义虚函数时子类中覆盖该方法才能形成多态的效果,如果子类中定义方法与基类同名但是参数不同并不能形成多态,只是子类中增加了一个新的方法
为了防止忘记覆盖基类的虚函数,我们可以借助 C++ 11 的 override
关键字让编译器强制检查是否覆盖
1 2 3 4 5 class Sub : public Super{ public : virtual void method (int ) override ; };
如果基类中没有定义 virtual void method(int)
的方法那么编译器会报错
寻根求源:静态多态性
静态联编中通过借助不同类型对象的实例来调用函数可以实现不同的输出结果,只能根据对象类型确定调用哪个函数
寻根求源:虚函数
虚函数指针(引用)能实现动态联编的效果其实是给因为编译器给每个类隐含生成了一个虚函数的虚函数表,它为虚函数的指针(引用)指明了调用函数的入口
对象访问其实相当于静态联编,下文有讲原因
以下面代码为例:B 和 C 都是 A 的派生类,其中 show()
和 inc()
为虚函数,sub()
不是虚函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class A {public : int a; A(int x) { a = x; } virtual void show () { cout <<'a' << endl ; } virtual void inc () { a++; } void sub () { a--; } }; class B : public A{public : int b; B(int x, int y) : A(x) { b = y; } virtual void show () { cout <<'b' << endl ; } virtual void inc () { b++; } void sub () { b--; } }; class C : public A{public : int c; C(int x, int y) : A(x) { c = y; } virtual void show () { cout <<'c' << endl ; } virtual void inc () { c++; } void sub () { c--; } };
编译器在编译的时候给虚函数创建了一个虚函数表,这个表格里面存放的是类的虚函数的入口地址
1 2 3 4 5 6 7 int main () { A aa (3 ) ; B bb (4 , 5 ) ; C cc (6 , 7 ) ; return 0 ; }
调用对象的函数的时候虚函数和非虚函数都相当于静态联编(虚函数通过虚函数表确定调用入口)
1 2 3 4 5 6 7 8 9 int main () { A aa (3 ) ; A *p=&a; p->show(); p->inc(); p->sub(); return 0 ; }
基类指针指向派生类,虚函数通过派生类的虚函数表确定调用入口,非虚函数类调用指针类型的函数
1 2 3 4 5 6 7 8 9 int main () { B bb (4 ,5 ) ; A *p=&bb; p->show(); p->inc(); p->sub(); return 0 ; }
基类指针指向派生类,虚函数通过派生类的虚函数表确定调用入口,非虚函数类调用指针类型的函数
1 2 3 4 5 6 7 8 9 int main () { B bb (4 ,5 ) ; A *p=&bb; p->show(); p->inc(); p->sub(); return 0 ; }
虚析构函数
C++ 中规定某个类含有虚函数的时候,则应该将其析构函数设置为虚函数。否则容易出现内存泄漏的问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Shape { double x,y; public : virtual ~Shape() {...} virtual double getArea () {return 0 ; } }; const double PI = 3.1415926 ;class Circle : public Shape{ double radius; public : Circle(double x, double y, double z) : Shape(x, y) { radius = z; } virtual double getArea () { return PI * radius * radius; } virtual ~Circle() { ... } };
不采用虚析构函数
采用虚析构函数
纯虚函数
纯虚函数是一种特殊的虚函数,在基类中声明为虚函数,但不提供实现部分,而要求各派生类提供该虚函数的不同版本实现
纯虚函数只有声明没有实现!由于基类不提供定义,所以和虚函数不同,这个派生类不定义函数编译器是会报错的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 class Shape { double x, y; public : Shape(double a, double b){ x = a; y = b; } virtual ~Shape() {} virtual double getArea () = 0 ; }; const double PI = 3.1415926 ;class Circle : public Shape{ double radius; public : Circle(double x, double y, double z) : Shape(x, y) { radius = z; } virtual ~Circle() {} virtual double getArea () { return PI * radius * radius; } }; class Rectangle : public Shape{ double length, width; public : Rectangle(double x, double y, double z, double w) : Shape(x, y){ length = z; width = w; } virtual ~Rectangle() {} virtual double getArea () { return width * length; } }; double calArea (Shape &sh) { return sh.getArea(); } int main () { Circle c (3 , 4 , 5 ) ; cout <<"Circle area :" {<< calArea(c) << endl ; Rectangle r (3 , 4 , 5 , 6 ) ; cout <<"Rectangle area :" << calArea(r) << endl ; }
纯虚类
凡是含有纯虚函数的类称为抽象类。抽象类往往描述的是一般抽象概念,如形状类、动物类,其中的纯虚函数如 getArea()
没有实际意义,不能提供实现代码。要求派生类如 Circle
类提供自己版本的 getArea
实现
C++ 规定,不能在内存中创建抽象类对象,无论是定义抽象类对象、作为形参或返回值,还是动态创建抽象类对象都是非法的 。但可以定义一个抽象类指针(引用),并用该指针指向不同的派生类对象,以实现多态性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 double calArea (Shape *p) { return p->getArea(); } int main () { Circle c (3 ,4 ,5 ) ; cout <<calArea(&c)<<endl ; Rectangle r (3 ,4 ,5 ,6 ) ; cout <<calArea(&r)<<endl ; return 0 ; }