- class Person
- {
- public:
-
- protected:
- string _name="li";
- int _age=1;
- };
- class Student :public Person
- {
- public:
- void print()
- {
- cout << _age;
- }
- protected:
- int _stuid=2;
- };
子类student公有继承基类Person,子类,继承Person所有成员变量,相当于copy一份,成员函数在公有,但子类可以访问。
父类的public | 父类的protected | 父类的private | |
public | 子类public | 子类的protected | 子类private |
protect | 子类protected | 子类protected | 子类private |
private | 不可见 | 不可见 | 不可见 |
不可见指的是类外都不能访问,private相比,至少类中可以访问。
实际上子类的访问=min(父类的访问限定符,继承方式)
protected 用于存放可以私有且子类可以访问。
不加继承方式,class默认私有继承,struct默认公有继承。
父类不能直接赋值子类(向下转换),子类可以给父类(向上转换),这是一种赋值兼容,把子类中父类的部分切割赋值给父类,而不是强制类型转换
- student=person;//报错
- person=student;
子类和父类同名成员变量,成员函数,编辑器优先选择子类,通过父类域可以选择父类的变量,函数,编译器优先顺序:作用域>类域>全局域。
- class Student:public Person
- ........
- void Print()
- {
- cout<< num;
- cout<< Person::num;
- }
*注意不是重载关系,因为重载要在同一个作用域,而子类和父类分别为两个类域。
- //基类
- class Person
- {
- public:
- Person(const char* name = "peter")
- : _name(name)
- {
- cout << "Person()" << endl;
- }
- Person(const Person& p)
- : _name(p._name)
- {
- cout << "Person(const Person& p)" << endl;
- }
- Person& operator=(const Person& p)
- {
- cout << "Person operator=(const Person& p)"<< endl;
- if (this != &p)
- {
- _name = p._name;
- }
- return *this;
- }
- ~Person()
- {
- cout << "~Person()" << endl;
- }
- protected:
- string _name;
- };
- //子类
- class Student :public Person
- {
- public:
- Student()
- :_id(0)
- {
-
- }
- protected:
- int _id;
- };
子类Student继承了父类Person,继承相当于copy了一份父类的成员变量,因此我们理解子类中有_name和_id两个变量。但不能在初始化列表中初始化_name,但可以在函数内部中。
初始化列表相当于在定义时初始化,可以理解为编译器在基类的定义找不到父类中的_name声明。
实际上继承的父类的成员变量,在初始化列表,默认调用父类构造函数来初始化。
- // 子类构造函数
- Student()
- :_id(0)
- ,Person("xiaoli")
- {
- _name="xixiaong"
- }
注意Person的初始化优先于,_id的初始化
理解成父类成员变量的声明先于子类成员变量。
- Student(const Student& s)
- :Person(s)
- ,_id(s._id)
- {
-
- }
用父类的拷贝构造,子类作为参数,拷贝构造父类的成员变量,我们此前所说的向上转换,子类给父类是指出的,再拷贝构造基类自身成员变量。
如果不显示调用拷贝构造,会调用默认构造。
派生类的赋值重载,除了子类向上构造,还有注意operator=()重定义的问题。
- Student& operator=(const Student& s)
- {
- if (this != &s)
- {
- Person::operator=(s);
- _id = s._id;
- }
- return *this;
- }
析构函数名被特殊处理destructor,在子类析构调用后,会自动调用父类析构函数。来保证先子后父
- ~Student()
- {
-
- }
先子后父的原因:初始化列表时,先调用父类构造函数,所以结束时后调用父类析构函数。子类可能用到父类的函数,成员变量,如果父类被析构了,那么就会报错。
- class Person
- ......
- protected:
- string _name;
- static int _count;
- };
- int Person::_count = 0;
Assiatant类继承了Student和Teacher两个类
- class Assistant :public Student, public Teacher
- {
- protected:
- string _majorCourse;
- };
菱形继承存在的问题,student可能和teacher同时从Person那里继承了age,使得assistant类中有两个年龄造成数据冗余和二义性
- // Student和Teacheer类虚继承了Person
- class Student :virtual public Person
- {
- protected:
- int _num;
- };
- class Teacher :virtual public Person
- {
- protected:
- int _id;
- };
- class A
- {
- public:
- int _a;
- };
- class B : public A
- {
- public: int _b;
- };
- class C : public A
- {
- public: int _c;
- };
- class D : public C, public B
- {
- public: int _d;
- };
由图可得_a有两个分别在B,C中。并且类A,B,C的变量依次排列
_a 的位置存放着一串地址
对应地址的下个位置的数值为20,12,恰好就是B,C到A的偏移量。
本质上保存的是_a的相对位置,那么为什么不直接保存A的地址,或者偏移量而是要保存指向一块空间的地址?如果存在多个D对象,因为相对位置是固定的,所以可以直接复用。d2 和d1的绝对地址是不一样的,但是相对位置是一样的。
右边的表又叫为虚基表,专门保存偏移量。
总结:把它单独划分出来便于复用。
虚继承后B,C,D 都报错相似的结构,有虚基表
当ptr无论指向B 还是C 都可以通过偏移量,找到A