【C++语法地图】类的继承

继承是面向对象的三大特性之一

类与类之间可以通过继承建立特殊的关系:

哈士奇 <--  |                        |  --> 加菲猫

萨摩耶 <-- 狗 <-- 动物 --> 猫 --> 布偶猫

 牧羊犬<--  |                         |  --> 英短    

被继承的类称为子类,子类拥有父类的全部特性,并且还能再拓展自己的特性。子类中的成员分为两部分继承的部分体现了子类与父类的共性,而自己的新增成员体现了子类的个性。当两个类有很多重复内容,此时使用继承可以提高代码复用率,减少重复代码

继承方式

继承方式有public、protected、private三种:

public          将父类的三种不同权限类型的成员原封不动的搬运进子类,不改变权限类型

protected    将父类除了private的类型全部转换成protected搬运进子类

private        将父类除了private的类型全部转换成private搬运进子类

权限特性:

公共权限的数据类内类外都可以访问

保护权限的数据类内可以访问,类外无法访问,子类可以访问,类对象无法访问

私有权限的数据类内可以访问,类外无法访问,子类也无法访问,类对象无法访问

Caution类自身的所有成员都可以通过自己的对象进行访问,其他类的对象只能访问本类的公有成员,无法访问父类中保护成员和私有成员,继承的权限仅仅局限于类与类之间的访问,不关对象的事

1、单继承语法

class 类名继承方法 父类名...... };

Sample:

class Animal{//父类Anminal 
public:   
  	int age ,sex;
};
class Cat : public Anmial{//Cat类以public方式继承Animal类的成员 
public:   
  	int strength; 
};

2、多继承语法

class 类名 继承方法 父类名继承方法 父类名......... };

Sample:

class Animal{//父类Animal
public:
	int age;
}; 
class Dragon{//父类Dragon 
public:
	int age1;
}; 
class Cat:public Animal, protected Dragon{
//子类Cat同时继承父类Animal和Dragon的成员 
public: 
	int strength;
};

Caution多继承容易引发父类中出现同名成员,所以实际开发中不建议使用多继承语法

同名成员的处理方式

继承中难免会遇到父类和子类中都有同名的成员,此时想要区分想要访问的成员有一定规则,默认情况下直接访问被调用的是子类的同名成员

  • 子类对象访问子类同名成员,直接访问

  • 子类对象访问父类同名成员,加作用域

  • 子类父类中拥有同名的函数成员,子类会隐藏父类中同名函数成员,此时需作用域来访问

Sample1:

#include<iostream>
using namespace std;
class Animal{
public:
	Animal(int age=100):age(age){}
	int age;//同名数据成员
};
class Cat :public Animal{//Cat作为Anmial的子类
public:
	Cat(int age=200):age(age){}
	int age;//同名数据成员
};
int main() {
	Cat c1;
	cout << "cat age = " << c1.age << endl;//默认指向子类的同名成员
	cout << "animal age = "<<c1.Animal::age << endl;//加作用域来访问父类成员
}

可以看到,通过继承的方式,哪怕父类没有实例化的对象,也可以通过子类来访问父类的成员

Sample2:

#include<iostream>
using namespace std;
class Animal {
public:
	void func() {//同名函数成员
		cout << "Animal-func()被调用" << endl;
	}
	void func(int a) {//父类重载的成员函数
		cout << "Animal-func(int a)被调用" << endl;
	}
};
class Cat :public Animal {//Cat作为Anmial的子类
public:
	void func() {//同名函数成员
		cout << "Cat-func()被调用" << endl;
	}
};
int main() {
	Cat c1;

	c1.func();
	c1.Animal::func();
	c1.Animal::func(1);
}

上述代码中,父类中出现函数重载。访问思路可以先从选择子类或父类开始,没加作用域访问子类函数成员,加了作用域访问父类函数成员;再去访问父类中函数时,调用时加入参数访问有参函数,没加参数访问无参函数

Caution:类中的同名静态成员数据和静态成员函数处理方式与上述方法相同,但是多了一种访问方式,可以用类名直接访问静态成员,不需要创建对象

菱形继承与虚基类

继承过程中有可能会出现一种特殊情况——菱形继承

这种继承结构发生于一个同时继承了多个父类的子类,那些父类又正好有一个共同的父类,此时这种继承关系如同一个菱形,被称为菱形继承

二义性

发生菱形继承时,相当于子类继承了父类的父类两次。比如说现有三个类,分别为猫类、龙类、动物类,龙类和动物类同时继承了动物类的成员,此时有一个龙猫类同时继承了龙类与猫类的成员,龙猫类对象使用数据时就会出现二义性

|——>——猫类——>——|

动物类                              龙猫类

|——>——龙类——>——|

Sample:

class Animal {//动物类
public:
	int age;
};
class Cat:public Animal{};//猫类
class Dragon:public Animal{};//龙类};
class DragonCat :public Cat, public Dragon {};//龙猫类
int main() {
	DragonCat p1;
	p1.Dragon::age = 18;
	p1.Cat::age = 28;
}

龙猫类的数据相当于继承了两次动物类,发生了资源不必要的重复使用,事实上那份数据事实上只需要一份就可以了,但现在DradonCat类中有两份age,并且因为二义性,此时访问p1.age会报错

解决二义性的方法——Virtual关键字

利用虚继承,可以解决菱形继承的问题,此时被继承的最大基类称为虚基类

在继承前加上virtual关键字进行虚继承

class Animal {//虚基类
public:
	int age;
};
class Cat:virtual public Animal{};//猫类虚继承
class Dragon:virtual public Animal{};//龙类虚继承
class DragonCat :public Cat, public Dragon {};//龙猫
int main() {
	DragonCat p1;
	p1.Dragon::age = 10;
	p1.Cat::age = 20;
	cout << p1.age <<  endl;
}

从程序输出结果来看,此时p1.age为20,而若将Dragon::和Cat::那两行赋值代码调换顺序,输出结果会变成10,说明age变量只有一个,覆写后显示最后一次赋值的结果。很好的解决了资源重复和二义性的问题

底层逻辑:

发生虚继承后的龙猫类中age只有一份,并且多了两份指针变量——vbptr

v——virtual    b——base    p——pointer    vbptr称为虚基类指针

vbtable称为虚基类表格

每一个虚基类指针指向一个虚基类表格,虚基类表格通过计算内存地址差值作为偏移量,来对指针进行偏移从而找到age的变量所在地址,通过这种方式使得age变量成为唯一确定的变量

illustration :封面  by 紺屋鴉江

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值