C++ 继承

何为继承

面向对象编程三大特性之一,是类设计层次的复用,允许程序员在保持原有类特性的基础上进行扩展,增加功能,从而产生新的类

示例:

class Person// 基类/父类
{
public:
 void Print()
 {
 cout << "name:" << _name << endl;
 cout << "age:" << _age << endl;
 }
protected:
 string _name = "peter"; // 姓名
 int _age = 18;  // 年龄
};

//派生类/子类
class Student : public Person
{
protected:
 int _stuid; // 学号
};

//派生类/子类
class Teacher : public Person
{
protected:
 int _jobid; // 工号
};
int main()
{
 Student s;
 Teacher t;
 s.Print();//可以直接用从基类继承下来的函数
 t.Print();
 return 0;
}

如何继承

继承格式:

class Student : public Person
      派生类     继承方式 基类
//继承方式其实是可以不写的,class默认private继承,struct似乎默认public继承

继承基类成员访问方式的变化:

在这里插入图片描述

注:

  1. 基类private成员在派生类中无论以什么方式继承都是不可见的:被继承,但语法规定不能访问
  2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为 protected 。可以看出保护成员限定符是因继承才出现的
  3. 在实际运用中 一般使用都是public继承 ,几乎很少使用 protetced/private 继承
  4. 使用关键字class时默认的继承方式是 private,使用struct时默认的继承方式是 public,不过最好显示的写出继承方式

基类和派生类对象赋值转换

我们讨论的是 public 继承的情况下

double d = 1.1;
int a = d;//这期间 d 先隐式类型转换生成一个临时变量,再赋值给 a
class Person
{
protected:
    string _name; // 姓名
    string _sex;  // 性别
    int _age; // 年龄
};
class Student : public Person
{
public:
    int _No; // 学号
};
//…………………………………………
Student s ;
Person p = s;
//Student对象向 Public对象赋值,是天然支持的,不用隐式类型转换,不生成临时变量
//就像是把派生类中父类那部分切来赋值过去一样
Person& rp = s;
Person* pp = &s

在这里插入图片描述

基类一般不能给派生类赋值

继承中的作用域

● 在继承体系中基类和派生类都有独立的作用域
● 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义(但可以使用 基类::基类成员 显示访问)
● 关于成员函数,只需要函数名相同就构成隐藏(不构成重载:不在同一个作用域里)
建议别定义同名成员

示例:

class Person
{
protected:
    int _id = 1;
};
class Student : public Person
{
public:
    void Print()
    {
        cout << _id << endl;
    }
protected:
    int _id = 9;
};
int main() {
    Student().Print();//输出9
}

派生类的默认成员函数

● 构造函数:
派生类没有主动实现则调用父类的构造函数,主动实现时 父类继承而来的成员也要调用父类的构造函数

class Person
{
public:
    Person(const char* name = "peter")
        : _name(name)
    {
        cout << "Person()" << endl;
    }
protected:
    string _name; // 姓名
};
class Student : public Person
{
public:
    Student(const char* name, int num)
        : Person(name)//父类继承而来的成员只能调用父类的构造函数
        , _num(num)
    {
        cout << "Student()" << endl;
    }
protected:
    int _num; //学号
};
int main() {
    Student s("hazb",10);
    return 0;
}

主动实现派生类的构造函数时,即使你不主动调用父类的构造函数,它也一定会调用的:
在这里插入图片描述

● 拷贝构造:
同理

//父类:
Person(const Person& p)
	:_name(p._name)
{
		cout << "Person(const Person& p)" << endl;
}
//子类:
Student(const Student& s)
	:Person(s)//派生类天然支持直接给基类赋值
	, _num(s._num)
{
	cout << "Student(const Student& s)" << endl;
}

● 赋值重载:
同理,只是要注意函数构成隐藏的处理

//父类
Person& operator=(const Person& p)
{
    cout << "Person operator=(const Person& p)" << endl;
    if (this != &p)
        _name = p._name;

    return *this;
}
//子类
Student& operator=(const Student& s)
{
	cout << "Student& operator= (const Student& s)" << endl;
	if (this != &s)
	{
		Person::operator=(s);//函数构成隐藏
		_num = s._num;
	}
	return *this;
}

● 析构函数:
同理,但要注意子类不用我们去显示调用父类的析构

//父类
~Person()
{
	cout << "~Person()" << endl;
}
//子类
 ~Student()
 {
 	但因为多态的一些原因,析构函数名会被处理成 destructor,构成隐藏,于是我们这么写:
 	//Person::Person();
 	然后却发现多调用了一次父类的析构函数,也就是说上面这句是多余的(悲)
 	//●析构函数不需要我们显示的去调用父类的析构函数,子类析构函数完成时会自动调用父类的析构函数(这么做就可以保证先构造的父后析构 —— 虽然好像没什么用……因为这又不是两个对象,而是一个对象中的两部分,但主打一个和通常情况一致)
 	cout<<"~Student()" <<endl;
 }

继承与友元

友元不能继承

继承与静态成员

静态成员属于整个类(包括派生类),即派生类不会继承,但可以访问

问题:实现一个不能被继承的类

 //来自老师的解决方法(我稍微加了点东西)
class A
{
public:
	static A CreateObj()
	{
		return A();
	}
	A(const A& a) {
		_a = a._a;
	}
	void Print() {
		cout << _a << endl;
	}
private:
	int _a = 1;
	A()
	{}
};

class B : public A
{};

int main()
{
	A a = A::CreateObj();
	a.Print();
	return 0;
}

复杂的菱形继承及菱形虚拟继承

起初,C++的创始人只考虑到了继承的两种情况:

单继承:
在这里插入图片描述
多继承:
在这里插入图片描述

却没考虑到两者组合到一起的菱形继承:

在这里插入图片描述
菱形继承会导致数据冗余和二义性的问题,对此,后来补充的虚拟继承解决了这一问题:

class Person
{
public:
	string _name; // 姓名
};
class Student : virtual public Person//注意 virtual 添加的位置
{
protected:
	int _num; //学号
};
class Teacher : virtual public Person
{
protected:
	int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse; // 主修课程
};
int main()
{
	Assistant a;
	a._name = "peter";
	cout << a._name;
	return 0;
}

如果不加虚继承:
二义性:Assistant 类继承了两个类的 _name,直接访问不知道你访问的是哪个(想访问必须指定::)
冗余性:你这个人作为助手(Assistant)真的需要从学生类和老师类里继承两份_name下来吗?(一个名字显然不需要存两份)

实例分析:

class A
{
public:
	int _a;
};

//class B : public A
class B : virtual public A
{
public:
	int _b;
};

//class C : public A
class C : virtual public A
{
public:
	int _c;
};

class D : public B, public C
{
public:
	int _d;
};

int main()
{
	D d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;

	B b = d;
	return 0;
}

虚继承:
在这里插入图片描述

这里空间上B在C前面,因为先继承的先声明

以上指向的几个表被称为“虚基表”,通过其中存储的偏移量在D中寻找A这个基类

没有虚继承:

在这里插入图片描述

虚继承借助记录的偏移量找到相应位置从而使得访问的是同一份数据

● 在这个示例中,使用虚继承后用到的空间反而变大了,对此的解释:

  1. 指针指向的两块空间我们可以忽略不计,因为这是共用的空间,即创建的所有D对象都会指向这两块空间
  2. 当 _a 大于 8 字节时,就能看到节省空间了
  3. 即便在当前示例中不节省空间,但它解决了二义性的问题,d.B::_a 与 d.C::_a 表示的都是同一个变量

● 可以看到虚继承使得父类和子类的结构都发生了变化,父类的结构也跟着变化有什么好处?

B*ptrd = &d;
B*ptrb = &b;
//这么做使得这两个指针在访问时的操作是一样的

● B* 与 C*
在这里插入图片描述

多继承指针偏移

一道和上面的知识点联系感觉不太紧密的问题:
在这里插入图片描述
问:输出结果是什么?
class A class B class C class D
原因:
● 从我们的角度看,应当输出三次class A(经过三次A的初始化),但经编译器处理后,其实只会经过一次A的初始化
● 初始化的顺序和初始化列表中出现的顺序无关,和声明的顺序相关,而谁先被继承谁就先被声明

总结:建议不要使用多继承,一定不要用菱形继承,快跑!

继承与组合

在这里插入图片描述

  • 25
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值