-
继承的概念
通过一种机制表达类型之间共性和特性方式,利用已有的数据类型定义的新的数据类型,这种机制就是继承。
子类(派生类)继承父类(基类) -
继承的语法
class 子类:继承方式 基类1, 继承方式 基类2... ...{ ... };
继承方式:
1)公有继承public
2)保护继承 protected
3)私有继承 private -
公有继承的特性(public)
1)子类对象会继承基类的属性和行为,通过子类对象可以访问到基类的成员,如同是基类对象在访问它们一样。
注:子类对象中包含的基类部分被称为“基类子对象”
2)向上造型(upcast)
将子类类型的指针或引用转换为基类类型的指针或引用:这种操作性缩小的类型转换,在编译器看来是安全的,可以直接隐式转换。
本质: 改变指针的值来改变访问时的起始地址(这个起始地址一定是在派生类对象多跨地址范围之内),同时通过其他机制,控制该指针的访问长度,即访问范围。
3)向下造型(downcast)
将基类类型的指针或引用转换为子类类型的指针或引用:这种操作性放大的类型转换,在编译器看来是危险的,不能直接隐式转换,但是可以显示强转(推荐使用static_cast)
#include <iostream> using namespace std; class Human{//人类(基类) public: Human(const string& name, int age):m_name(name), m_age(age){} void eat(const string& food){ cout << "我在吃" << food << endl; } void sleep(int hour){ cout << "我睡了" << hour << "小时" << endl; } protected: //保护成员:可以在类的内部和子类中访问 string m_name; int m_age; }; //学生类,人类派生的子类 class Student:public Human{ public: Student(const string& name, int age, int no) :Human(name, age), m_no(no){}//Human(..):显式指明基类部分的初始化方式 void who(void){ cout << "我叫" << m_name << ",今年" << m_age << "岁,学号是" << m_no << endl; } void learn(const string& course){ cout << "我在学" << course << endl; } private: int m_no; }; //教师类,人类派生的子类 class Teacher:public Human{ public: Teacher(const string& name, int age, int sal) :Human(name, age), m_sal(sal){} void who(void){ cout << "我叫" << m_name << ",今年" << m_age << "岁了,工资为" << m_sal << endl; } void teach(const string& course){ cout << "我在讲" << course << endl; } private: int m_sal; }; int main(void){ Student s("关羽", 30, 10011); s.who(); s.eat("面包"); s.sleep(8); s.learn("孙武兵法"); Teacher t("孙悟空", 35, 30000); t.who(); t.eat("桃子"); t.sleep(6); t.teach("unix系统编程"); //Student* ---> Human*:向上造型 //这里是隐式转换 Human* ph = &s; ph->sleep(8); ph->eat("面包"); //ph->who(); //error //Human* ---> Student*:向下造型(合理),安全 Student* ps = static_cast<Student*>(ph); ps->who(); //Human* ---> Student*:向下造型(不合理),不安全 Human h("赵云", 25); Student* ps2 = static_cast<Student*>(&h); ps2->who(); return 0; }
4)子类继承基类的成员
在子类中,可以直接访问基类中的公有和保护成员,就如同它们是子类自己的成员一样。
基类的私有成员,子类也可以继承,但是会受到访问控制属性的影响,无法直接使用。如果需要在子类中访问到基类中的私有成员,可以让基类提供公有的或保护的成员函数来间接访问。
5)子类隐藏基类的成员
如果子类和基类中有同名的成员函数,因为作用域不同,不能构成重载关系,而是一种隐藏关系,通过子类对象将优先访问子类自己的成员,这时如果还需要访问基类中被隐藏的成员,可以借助作用于限定操作符 类名:: 显示指明
注:如果形成隐藏关系的成员函数参数有所区别,也可以通过using声明,将基类中的成员函数引入到子类作用域中,让它们形成重载,通过函数重载解决
下边代码解注18行或者24行其中一行,注掉23行即可不报错误。
#include <iostream> using namespace std; class Base{ public: void func(void){ cout << "基类的func" << endl; } }; class Derived:public Base{ public: void func(int i){ cout << "子类的func(int)" << endl; } //将基类中的func引入到当前子类的作用域,让其和子类中的func形成重载 //using Base::func; }; int main(void){ Derived d; d.func(); //error //d.Base::func();//加上作用域声明或者在子类中使用using 声明 d.func(123); return 0; }
-
访问控制属性和继承关系
1)访问控制属性:影响访问该类成员的位置访问控制限定符 访问控制属性 内部访问 子类访问 外部访问 友元访问 public 公有成员 ok ok ok ok protected 保护成员 ok ok no ok private 私有成员 ok no no ok 2)继承方式:影响通过子类访问基类中的成员的可访问性
注:向上造型语法特性在保护和私有继承中不再适用基类中的 在公有继承的子类中 在保护继承的子类中 在私有继承的子类中 公有成员 公有成员 保护成员 私有成员 保护成员 保护成员 保护成员 私有成员 私有成员 私有成员 私有成员 私有成员 #include <iostream> using namespace std; class Base{ public: Base(void):m_public(100){} public: int m_public; }; class Derived:private Base{}; int main(void){ Derived d; //Base* pb = &d;//向上造型error //Base* pb = static_cast<Base*>(&d);//error Base* pb = (Base*)&d;//ok return 0; }
小结:继承方式可以这样理解,如果派生类继承方式为公有继承,那么,基类中的各成员,如果是public的,到了派生类还是public,是protected的,到了派生类还是protected,private也是这样;如果派生类的继承方式是protected,那么基类中的public成员到派生类中会变成protected,基类中的protected和private成员到派生类中,是什么还是什么,不会变化;如果继承方式为private,基类中的成员,到了派生类,都会变成private修饰,这样可以防止基类的成员无限向子类蔓延,派生类中变成了private,派生类的派生类就不能直接访问这些成员了。
#include <iostream> using namespace std; class A{ public: A(void):m_public(100), m_protected(200), m_private(300){} public: int m_public; protected: int m_protected; private: int m_private; }; class B:public A{//公有继承的子类 //该类中可以访问 m_public m_protected }; class C:protected A{//保护继承的子类 //该类中可以访问 m_public m_protected }; class D:private A{//私有继承的子类 //该类中可以访问 m_public m_protected }; class X:public B{ public: void func(void){ m_public = 123;//ok m_protected = 123;//ok //m_private = 123;//no } }; class Y:public C{ public: void func(void){ m_public = 123;//ok m_protected = 123;//ok //m_private = 123;//no } }; class Z:public D{ public: void func(void){ //m_public = 123;//no //m_protected = 123;//no //m_private = 123;//no } }; int main(void){ B b; b.m_public = 123; //b.m_protected = 456; //b.m_private = 789; C c; //c.m_public = 123; //c.m_protected = 456; //c.m_private = 789; D d; //d.m_public = 123; //d.m_protected = 456; //d.m_private = 789; d.print(); return 0; }
-
子类的构造函数
1)如果子类的构造函数没有显式指明基类子对象的初始化,那么编译器将会自动调用基类的无参构造函数来初始化基类子对象。
2)如果希望以有参的方式初始化基类子对象,则必须使用初始化列表显式指明基类子对象的初始化方式。
3)子类对象创建过程
–》分配内存
–》构造基类子对象(按继承表从左到右顺序)
–》构造成员子对象(按声明自上而下的顺序)
–》执行子类的构造函数代码 -
子类析构函数
1)子类的析构函数,无论自定义的还是缺省提供的,都会自动调用基类的析构函数,完成基类子对象的销毁。
2)子类对象的销毁过程
–》执行子类的析构函数代码
–》析构成员子对象(按声明逆序)
–》析构基类子对象(按声明逆序)
–》释放内存
3)基类的析构函数不能调用子类的析构函数,所以delete一个指向子类对象的基类指针,实际被执行的仅是基类的析构函数,子类的析构函数执行不到,有内存泄露的风险
class A{};
class B:public A{};
A* pa = new B;
delete pa;
解决:虚析构函数#include <iostream> using namespace std; class Member{ public: Member(void):m_j(0){ cout << "Member(void)" << endl; } Member(int j):m_j(j){ cout << "Member(int)" << endl; } ~Member(void){ cout << "~Member(void)" << endl; } int m_j; }; class Base{ public: Base(void):m_i(0){ cout << "Base(void)" << endl; } Base(int i):m_i(i){ cout << "Base(int)" << endl; } ~Base(void){ cout << "~Base(void)" << endl; } int m_i; }; class Derived:public Base{ public: Derived(void){ cout << "Derived(void)" << endl; } //Base(i):指明基类子对象的初始化方式 //m_mem(i):指明成员子对象的初始化方式 Derived(int i):Base(i),m_mem(i){ cout << "Derived(int)" << endl; } ~Derived(void){ cout << "~Derived(void)" << endl; } Member m_mem;//成员子对象 }; int main(void){ Derived d; cout << d.m_i << ',' << d.m_mem.m_j << endl; Derived d2(123); cout << d2.m_i << ','<< d2.m_mem.m_j << endl; /* Base* pb = new Derived; delete pb;//内存泄露风险 */ return 0; }
执行结果
-
子类的拷贝构造和拷贝赋值
1)子类的拷贝构造
–》如果子类没有自己定义拷贝构造函数,那么编译器会为子类提供缺省的拷贝构造函数,该函数会自动调用基类的拷贝构造函数,完成基类子对象的拷贝初始化。
–》如果子类自己定义拷贝构造函数,那么编译器就不再为子类提供缺省的拷贝构造函数,这时需要使用初始化列表,显式指明基类子对象也要以拷贝的方式进行初始化。
2)拷贝赋值
–》如果子类没有定义拷贝赋值操作符函数,那么编译器会为子类提供缺省拷贝赋值函数,该函数会自动调用基类的拷贝赋值函数,完成基类子对象的赋值。
–》如果子类定义了自己的拷贝赋值函数,那么编译器不再为子类提供缺省拷贝赋值函数,这时需要显式调用基类的拷贝赋值函数,完成基类子对象的赋值。#include <iostream> using namespace std; class Base{ public: Base(int i=0):m_i(i){} Base(const Base& that):m_i(that.m_i){ cout << "基类的拷贝构造" << endl; } Base& operator=(const Base& that){ cout << "基类的拷贝赋值" << endl; if(&that != this){ m_i = that.m_i; } return *this; } int m_i; }; class Derived:public Base{ public: Derived(int i=0,int j=0):Base(i),m_j(j){} //Base(that):指明基类子对象以拷贝方式初始化 Derived(const Derived& that) :m_j(that.m_j),Base(that){} Derived& operator=(const Derived& that){ if(&that != this){ //显式调用基类的拷贝赋值函数,完成基类 //子对象的复制操作. //怎么写都可以this->Base::operator=(that);//that这里实际上是向上造型 //((Base*)this)->operator=(that);//用Base指针调用它自己的operator=,that这里实际上是向上造型 Base::operator=(that);//相当于用this,即派生对象,显式地调用基类operator=方法,Base::不可省略不写 m_j = that.m_j; } return *this; } int m_j; }; int main(void){ Derived d1(123,321); cout << d1.m_i << "," << d1.m_j << endl; Derived d2(d1);//拷贝构造 cout << d2.m_i << "," << d2.m_j << endl; Derived d3; d3 = d1;//拷贝赋值,d3.operator=(d1) cout << d3.m_i << "," << d3.m_j << endl; return 0; }