一、继承的概念和语法
1.继承由于我们的现实世界中,对象之间存在共性和个性,超集和子集之间的关键.
这个时候就可以使用继承.
2.class 子类:继承方式1 基类1,继承方式2 基类2,...{}
3.继承方式:
公有继承:public
保护继承:protected
私有继承:private
二、公有继承的特征
1.子类对象任何时候都可以被当做其基类类型的对象.
class Human{...};
class Student:public Human{...};
Student student(...);
Human* phuman = &student;
Human& rhuman = student;
编译器认为访问范围缩小是安全的.
2.基类类型的指针或引用不能隐式转换为子类类型.即是说不能把一个基类看成是一个子类类型.
class Human{}
class Student:public Human{...};
Human human(...);
Student* pstudent = static_cast<Student*>(&human);
Student& rstudent = static_cast<Student&>(human);
->编译器认为访问范围扩大是危险的.
基类指针或引用的实际目标,究竟是不是子类对象,由程序员自己判断.
3. a.在子类中可以直接访问基类的多有公有和保护成员,就如同它们是在子类中声明的一样.
b.基类的私有成员在子类中虽然存在却不可见,故无法直接访问.
c.尽管基类的公有和保护成员在子类中直接可见,但是可以在子类中重新定义这些名字,子类
中的名字会隐藏所有基类中同名定义.
d.如果需要在子类中或通过子类访问一个在基类中定义却为子类所隐藏的名字,可以借助作用域限定符::实现
4.派生类的对象可以访问基类的公有成员;派生类的成员函数可以访问基类的公有成员和保护成员.
三、继承方式和访问控制
public:公有成员 基类,子类,外部以及友元都可以访问.
protected:保护成员 基类和子类以及友元可以访问,外部不能访问.
private: 私有成员 基类和友元可以访问,子类和外部不能访问.
基类中的公有,保护和私有的成员,在其公有,保护和私有子类中的访问控制属性,会因继承的方式不同而有差异.
基类中的 在公有的子类中中变成 在保护子类中变成 在私有子类中变成
公有成员 公有成员 保护成员 私有成员
保护成员 保护成员 保护成员 私有成员
私有成员 私有成员 私有成员 私有成员
总结一点就是公开继承不该改变访问控制属性,而私有继承和私有成员无论怎么弄其成员都是私有的
只是保护继承的时候会把公有的成员编程保护的.
四、子类的构造和析构
1.子类构造函数隐式调用基类构造函数
如果子类构造函数没有显示指明其基类部分的构造方式,那么编译器会选择其基类的缺省构造函数,构造该子类
对象中的基类子对象
2.子类构造函数显示调用基类构造函数
子类的构造函数可以在初始化表中显示指明基类部分的构造方式,即通过其基类的特定构造函数,构造该子类对象
中的基类子对象.
3.子类对象的构造过程
构造基类子对象---->构造成员变量----->执行构造代码
4.阻断继承
子类的构造函数无论如何都会调用基类的构造函数,构造子类对象中的基类子对象.
如果把基类的构造函数定义为私有,那么该类的子类就永远无法被实例化成功.
C++可以使用这种方法阻断一个类被扩展.
子类实例化的时候无论怎么样都要构造基类对象,如果把基类对象私有化,就可以阻断它的继承.
#include <iostream> using namespace std; class Base { public: static Base* createInstance(void) { return new Base; } private: //限制子类对象的创建. Base(void) { } }; class Derived:public Base { }; int main(void) { Base* pb = Base::createInstance(); //... delete pb; return 0; }
5.子类的析构函数隐式调用基类析构函数
a.子类的析构函数在执行完其中的析构代码,并析构完所有的成员变量以后,会自动调用其基类的析构函数,
析构该子类对象中的基类子对象.
b.基类的析构函数不会调用子类的析构函数.通过基类指针析构子类对象,实际上被析构的仅仅是子类对象
中的基类子对象.子类的扩展部分将失去被析构的机会,极有可能形成内存泄漏.
c.子类对象的析构过程
执行析构代码->析构成员变量->析构基类子对象.
6.子类的拷贝构造与赋值运算符.
a.子类没有定义拷贝构造函数
编译器为子类提供缺省拷贝构造函数,会自动调用其基类的拷贝构造函数,构造该子类对象中的基类子对象.
b.子类定义了拷贝构造函数,但并没有显示指明其基类部分的构造方式
编译器会选择其基类的缺省构造函数,构造该子类对象中的基类子对象.
c.子类定义了拷贝构造函数,同时显示指明了其基类部分以拷贝方式构造.
子类对象中的基类部分和扩展部分一起被复制.
#include <iostream> using namespace std; class Base { public: Base(void):m_i(0) { cout << "缺省Base::Base():" << this << endl; } Base(int i):m_i(i) { cout << "有参Base::Base(int):" << this << endl; } ~Base(void) { cout << "析构Base::~Base():" << this << endl; } int m_i; }; class Member { public: Member(void):m_i(0) { cout << "无参Member::Member():"<< this << endl; } Member(int i):m_i(i) { cout << "有参Member::Member(int):" << this << endl; } ~Member(void) { cout << "析构Member::~Member():" << this << endl; } int m_i; }; class Derived:public Base { public: Derived(void) { cout << "无参Derived::Derived():" << this << endl; } Derived(int i,int j):Base(12),m_member(j) { cout << "有参Dervied::Derived(int,int):" << this << endl; } ~Derived(void) { cout << "析构Derived::~Derived():" << this << endl; } Member m_member; }; int main(void) { //Derived d1; //缺省构造会调用基类的缺省构造 Derived d2(10,20);//这个会显示的调用基类的构造. cout << d2.m_i << endl; cout << d2.m_member.m_i << endl; return 0; }
分析打印的结果是:
有参Base::Base(int):0xbf926338
有参Member::Member(int):0xbf92633c
有参Derived::Derived(int,int):0xbf926338
12
20
析构Derived::~Derived():0xbf926338
析构Member::~Member():0xbf92633c
析构Base::~Base():0xbf926338
从这里可以看出子类的构造过程
首先是基类对象->然后是成员构造->最够才是自己的构造代码
析构过程:
首先是自己的析构->然后是成员析构->最后才是基类的析构.
还有这里的子类的基类部分是子类的首地址.
#include <iostream> using namespace std; class Base { public: Base(void):m_i(0){} Base(int i):m_i(i){} Base(const Base& that):m_i(that.m_i){} int m_i; }; class Derived:public Base { public: Derived(void):m_i(0){} Derived(int i,int j):Base(i),m_i(j){} Derived(const Derived& that):m_i(that.m_i),Base(that){} int m_i; }; int main(void) { Derived d1(100,200); Derived d2 = d1; cout << d1.m_i << endl; cout << d1.Base::m_i << endl; cout << d2.m_i << endl; cout << d2.Base::m_i << endl; return 0; }
在子类中要么显示的指明基类的构造方式,要么就不写.因为缺省的拷贝构造会调用基类中的拷贝构造.
而如果写了拷贝构造而又没有显示的指明基类的构造方式,它会调用基类缺省的构造函数.
7.