基类和派生类的赋值对象转换、派生类与基类成员的函数隐藏、派生类中的默认成员函数、继承与友元、继承与静态成员函数、菱形继承、菱形虚拟继承等的介绍
文章目录
- 前言
- 一、基类和派生类的赋值对象转换
- 二、派生类与基类成员的函数隐藏
- 三、派生类中的默认成员函数
- 四、继承与友元
- 五、 继承与静态成员函数
- 六、菱形继承
- 七、菱形虚拟继承
- 总结
前言
基类和派生类的赋值对象转换、派生类与基类成员的函数隐藏、派生类中的默认成员函数、继承与友元、继承与静态成员函数、菱形继承、菱形虚拟继承等的介绍
一、基类和派生类的赋值对象转换
派生类可以赋值给基类,因为会是使用切片、切割的方式将派生类中基类所拥有的成员赋值给基类
基类不可以直接赋值给派生类
#include <iostream>
#include <string>
using namespace std;class Person
{
public:string _name;string _sex;int _age;
};class Student : public Person
{
public:int _No;
};
int main()
{Student s;s._name = "zhangsan";s._sex = "nan";s._age = 18;s._No = 999;Person p;p = s;return 0;
}
二、派生类与基类成员的函数隐藏
当派生类中的成员函数与基类中的成员函数同名时,就会默认隐藏基类中的成员函数,只能访问到派生类的成员函数,若需要访问基类中的成员函数,需要加类的访问限定符。
#include <iostream>
using namespace std;class Person
{
public:void fun(){cout << "name:" << _name << endl;}protected:string _name = "peter";int _num = 100;
};class Student : public Person
{
public:void fun(int i){// 派生类找成员变量的顺序,默认从局部域开始找,若没有去自己的类中找,// 若没有再去基类中找,最后再去全局域中找,没找到报错//int _num = 0;cout << "num:" << _num << " i:" << i << endl;cout << "Person::num:" << Person::_num << endl;}protected:int _num = 999;
};
int main()
{Student s;s.fun(1);s.Person::fun();return 0;
}
三、派生类中的默认成员函数
-
派生类中的构造函数,会默认调用基类的默认构造函数,若基类中没有默认构造函数,则需要手动调用。
-
派生类中的拷贝构造,在调用基类的拷贝构造函数时,只要传入派生类对象即可,因为派生类会自动切片赋值给基类
-
派生类中的赋值运算符重载函数,与基类中的赋值运算符重载函数名相同,会自动隐藏,在调用基类中的赋值运算符重载时,要加类访问限定符调用基类的赋值运算符重载函数。
-
派生类中的析构函数,在析构函数调用结束时会自动调用基类中的析构函数,因为必须先析构派生类,再析构基类(防止析构完基类,但再次用到基类的成员)
// 派生类的默认成员函数
#include <iostream>
using namespace std;class Person
{
public:Person(const char* name):_name(name){cout << "Person()" << endl;}Person(const Person& p):_name(p._name){cout << "Person(const Person& p)" << endl;}Person& operator=(const Person& p){if (this != &p){_name = p._name;}return *this;}~Person(){cout << "~Person()" << endl;}
protected:string _name;
};class Student : public Person
{
public:// 派生类的构造函数会在初始化列表中先调用基类的默认构造函数,在对自己的成员变量初始化// 如果基类中没有默认构造函数,需要在派生类的初始化列表调用基类的构造函数// 派生类继承基类,基类成员的声明一定是在派生类中的,所以初始列表一般应先初始化基类成员,与声明保持一致Student(const char* name = "zhangsan", int num = 99):Person(name),_num(num){}Student(const Student& s):Person(s),_num(s._num){}Student& operator=(const Student& s){if (this != &s){// 直接写operator=会调用派生类的赋值运算符重载// 此处应该调用基类的赋值运算符重载,应该加类域Person::operator=(s);_num = s._num;}return *this;}~Student(){}
protected:int _num;
};int main()
{Student s1("knowledge", 100);Student s2(s1);Student s3;s3 = s2;return 0;
}
四、继承与友元
友元关系不能继承
五、 继承与静态成员函数
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例 。
所以,我们可以计算一个基类以及它的派生列共实例化了多少个对象。
因为每一个派生类构造时都会调用基类的默认构造,所以我们用静态成员变量在基类的默认构造中计数
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:Person() { ++_count; }
protected:string _name;
public:static int _count;
};
int Person::_count = 0;class Student : public Person
{
protected:int _num;
};class Teacher : public Person
{
protected:int _id;
};
int main()
{Person p;Student s1;Student s2;Teacher t;cout << p._count << endl; // 4return 0;
}
六、菱形继承
#include<iostream>
#include <string>
using namespace std;class Person
{
public:string _name;
};class Student : public Person
{
protected:int _num;
};class Teacher : public Person
{
protected:int _id;
};class Assistant : public Student, public Teacher
{
protected:int _age;
};int main()
{Assistant a;// 二义性的问题,_name指向不明确//a._name = "zhangsan";// 可以解决二义性的问题,但是无法解决数据冗余的问题a.Student::_name = "zhang";a.Teacher::_name = "Mr.zhang";return 0;
}
七、菱形虚拟继承
在腰部的类的继承方式前面加 virtual
虚拟继承会改变类对象模型的结构,比如: D会改变B和C的结构,B中除了存储_b外,还存除了,当前位置到A的偏移量。有了偏移量可以让每个类都有很好的访问方式
#include<iostream>
#include <string>
using namespace std;class A
{
public:int _a;
};class B : virtual public A
{
public:int _b;
};class C : virtual public A
{
public:int _c;
};class D : public B, public C
{
public:int _d;
};int main()
{D d;d._a = 2;d._b = 3;d._c = 4;d._d = 5;return 0;
}
总结
基类和派生类的赋值对象转换、派生类与基类成员的函数隐藏、派生类中的默认成员函数、继承与友元、继承与静态成员函数、菱形继承、菱形虚拟继承等的介绍