【C++ Primer】第十三章 类继承
一,类继承
定义:从已有的类派生出新的类,而派生类继承了原有类的特征,包括方法。
目标:提供可重用的代码
二,一个简单的基类
#include <iostream> #include <cstring> using namespace std; class student //基类 { private: char name[20]; int num; int age; public: student(const char *m_name,const int m_num,const int m_age); ~student(); virtual void display();//派生类有重载 故采用虚函数形式 }; class graduate:public student //派生类 注意写法 { private: char major[20]; int thesis; public: graduate(const char *m_major,const int m_thesis,student &st); ~graduate(); void display(); void setThesis(int i); }; student::student(const char *m_name,const int m_num,const int m_age) { strcpy(name,m_name); num=m_num; age=m_age; } student::~student() { cout<<"student is over"<<endl; } void student::display() { cout<<"name:"<<name<<"\nnum:"<<num<<"\nage"<<age<<endl; } graduate::graduate(const char* m_major, const int m_thesis, student& st):student(st) { //特别注意:由于派生类不能直接使用基类私有成员,所以必须使用基类构造函数 st.display(); strcpy(major,m_major); thesis=m_thesis; } graduate::~graduate() { cout<<"graduate is over"<<endl; } void graduate::display() { cout<<"major:"<<major<<"\nthesis:"<<thesis<<endl; } void graduate::setThesis(int i) { thesis=i; } int main() { student st("tianshuai",1,18); st.display(); cout<<"**************\n"; graduate gd("compuater",5,st); gd.display(); return 0; }输出:
name:tianshuai
num:1
age18
**************
name:tianshuai
num:1
age18
major:compuater
thesis:5
graduate is over
student is over
student is over
【简介】1)继承类 继承了基类的私有成员,但是不能通过继承类的对象直接访问私有成员
2)派生类不能直接访问基类私有成员,派生类构造函数必须使用基类构造函数
3)派生类构造函数特点:基类对象首先被创建 创建graduate gd(); student 对象先被创建
创建派生类对象时,先调用基类构造函数,再调用派生类构造函数
派生类对象过期时,先析构派生类,再析构基类
4)不能讲基类对象和地址赋给派生类
student st;
graduate &gd=st; // Not Allow
graduate *gd=st; // Not Allow
如果允许的话,则基类可以访问派生类成员,但访问基类没有的成员,对于基类对象来说是没有意义的
st.setThesis(int i) //因为基类没有该方法
三,继承----- is-a 关系
继承方式:共有继承、私有继承、保护继承。主要是限定派生类的子类对所继承的基类的访问权限。
1)公有继承:公有继承的特点是基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态,而基类的私有成员仍然是私有的,不能被这个派生类的子类所访问。
2)私有继承:私有继承的特点是基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问。
3)保护继承:保护继承的特点是基类的所有公有成员和保护成员都成为派生类的保护成员,并且只能被它的派生类成员函数或友元访问,基类的私有成员仍然是私有的。
共有继承建立一种is-a关系:派生类对象也是一个基类对象,可以对基类对象执行的任何操作,也可以对派生类对象执行
例如:Fruit 是水果类,有重量和热量。Banana是派生类,包含重量和热量外,还添加专门香蕉成员,这些成员通常不用于Fruit.
【注意】在C++语言中,一个派生类可以从一个基类派生,也可以从多个基类派生。从一个基类派生的继承称为单继承;从多个基类派生的继承称为多继承。
四,多态共有继承
两种实现机制:1>在派生类中重新定义基类方法。2>使用虚方法
不存在虚拟构造函数,但基类的虚拟析构函数是必要的。请看下例:
class ClxBase{ public: ClxBase(){cout << "Output from the con ClxBase!" << endl;}; virtual ~ClxBase(){cout << "Output from the destructor of class ClxBase!" << endl;}; virtual void DoSomething(){ cout << "Do something in class ClxBase!" << endl; }; }; class ClxDerived : public ClxBase{ public: ClxDerived() {cout << "Output from the con ClxDerived!" << endl;}; ~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; }; void DoSomething() { cout << "Do something in class ClxDerived!" << endl; }; }; void main() { ClxBase *pTest = new ClxDerived; //基类的指针 引用派生类对象 pTest->DoSomething(); //调用派生类的方法 delete pTest; }输出:Do something in class ClxDerived!
Output from the destructor of class ClxDerived!
这个很简单,非常好理解。
但是,如果把类ClxBase析构函数前的virtual去掉,那输出结果就是下面的样子了:
Do something in class ClxDerived!
也就是说,类ClxDerived的析构函数根本没有被调用!一般情况下类的析构函数里面都是释放内存资源,而析构函数不被调用的话就会造成内存泄漏。我想所有的C++程序员都知道这样的危险性。当然,如果在析构函数中做了其他工作的话,那你的所有努力也都是白费力气。
当然,并不是要把所有类的析构函数都写成虚函数。因为当类里面有虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间。所以,只有当一个类被用来作为基类的时候,才把析构函数写成虚函数。
五,静态联编和动态联编
联编就是将模块或者函数合并在一起生成可执行代码的处理过程,同时对每个模块或者函数调用分配内存地址,并且对外部访问也分配正确的内存地址,它是计算机程序彼此关联的过程。按照联编所进行的阶段不同,可分为两种不同的联编方法:静态联编和动态联编。
函数名联编:将源代码中函数调用解释为执行特定的函数代码块但是在C++中由于函数重载,编译器必须查看函数参数以及函数名才能确定使用哪个函数
静态联编:在编译阶段就将函数实现和函数调用关联起来,因此静态联编也叫早绑定,在编译阶段就必须了解所有的函数或模块执行所需要检测的信息,它对函数的选择是基于指向对象的指针(或者引用)的类型,C语言中,所有的联编都是静态联编
动态联编:在程序执行的时候才将函数实现和函数调用关联,因此也叫运行时绑定或者晚绑定,动态联编对函数的选择不是基于指针或者引用,而是基于对象类型,不同的对象类型将做出不同的编译结果。C++中一般情况下联编也是静态联编,但是一旦涉及到多态和虚拟函数就必须要使用动态联编了。
多态:字面的含义是具有多种形式或形态。C++多态有两种形式,动态多态和静态多态;动态多态是指一般的多态,是通过类继承和虚函数机制实现的多态;静态多态是通过模板来实现,因为这种多态是在编译时而非运行时,所以称为静态多态。
在基类我们会把想要多态的函数声明为虚函数,而虚函数的实现原理就使用了动态联编。
上面例子可以作为动态联编的例子!!
静态多态例子:使用模板
#include <stdio.h> #include <iostream> /** *Shape */ class CShape { public: CShape(){} virtual ~CShape(){} virtual void Draw() = 0; }; /** *Point */ class CPoint : public CShape { public: CPoint(){} ~CPoint(){} void Draw() { printf("Hello! I am Point!/n"); } }; /** *Line */ class CLine : public CShape { public: CLine(){} ~CLine(){} void Draw() { printf("Hello! I am Line!/n"); } }; template <class T> void DrawShape(T* t) { t->Draw(); } void main() { CShape* shape = new CPoint(); //shape->Draw();//draw point DrawShape<CPoint>((CPoint*)shape); delete shape; shape = new CLine(); //shape->Draw();//draw Line DrawShape<CLine>((CLine*)shape); delete shape; return ; }在程序编译main函数的时候,编译器就已经指定了DrawShape函数里面的Draw要调用那个实现了,这就是静态多态,在编译时就已经知道了要调用的函数。
1)为什么有两种类型联编,为什么 静态联编为默认
动态联编让您能够重新定义类方法,而静态联编则很差,但静态联编效率高。
如果派生类不重新定义基类任何方法,则不需要动态联编。仅仅将那些派生类需要重新定义的函数定义为虚拟的
2)虚函数工作原理
编译器给每个对象添加一个隐藏成员,其中保存一个该函数地址的指针,这种数组成为虚函数表。
调用虚函数时,查看存储在对象中虚拟函数表地址,然后转向相应函数地址表,如果使用类声明中定义的第一个虚函数,则程序将使用数组中的第一个函数地址,并执行具有该地址的函数。
3)注意事项
如果重新定义继承的方法,则应确保与原来的原型完全相同。
如果基类声明被重载了,则应在派生类中重新定义所有基类版本
六,抽象基类ABC(abstract base class)
说明:某几个子类的所公有的数据和方法抽象出来组成一个抽象基类,然后从抽象基类中派生出这几个子类,可以通过基类指针数组同时管理这几个子类。对于每个子类中的不同方法,可以将该方法在抽象基类中定义为纯虚函数的方式,同时在各子类中将该方法定义为虚函数。
#include <iostream> using namespace std; const double pi=3.14; class BaseEllipse //抽象基类 含有圆跟椭圆的公共成员 { private: double x; double y; public: BaseEllipse(double t_x=0,double t_y=0) { x=t_x; y=t_y; } virtual ~BaseEllipse(){} void Move(int nx,int ny) { x=nx; y=ny; cout<<"中心坐标为:"<<"("<<x<<","<<y<<")"<<endl; } virtual void Area()const=0;//纯虚函数结尾处为 “=0” 在类中可以只定义,不用实现 }; class Circle:public BaseEllipse //圆 { private: double r; public: Circle(double t_x=0,double t_y=0,double t_r=0); //半径和中心坐标 Circle(const BaseEllipse & ba,double t_r=0); void Area()const; }; class Ellipse:public BaseEllipse //椭圆 { private: double a; double b; public: Ellipse(double t_x=0,double t_y=0,double t_a=0,double t_b=0); Ellipse(const BaseEllipse & p,double t_a=0,double t_b=0); void Area()const; };/*圆*/ Circle::Circle(double t_x,double t_y,double t_r):BaseEllipse(t_x,t_y) { r=t_r; } Circle::Circle(const BaseEllipse & ba,double t_r):BaseEllipse(ba) { r=t_r; } void Circle::Area()const { cout<<pi*r*r<<endl; }/*椭圆*/ Ellipse::Ellipse(double t_x,double t_y,double t_a,double t_b):BaseEllipse(t_x,t_y) { a=t_a; b=t_b; } Ellipse::Ellipse(const BaseEllipse & ba,double t_a,double t_b):BaseEllipse(ba) { a=t_a; b=t_b; } void Ellipse::Area()const { cout<<0.5*a*b<<endl; } int main() { Circle c1(0,0,5); c1.Move(1,2); c1.Area(); Ellipse e1(0,0,7,8); e1.Move(3,4); e1.Area(); return 0; }