C++基础(17)——继承

目录

1.继承的概述

1.1概念

1.2继承的基本用法

 1.3继承的好处

1.4语法

2.继承方式

2.1继承语法

2.2继承方式

3.继承中的构造与析构顺序

4.继承同名成员处理方式

4.1继承同名成员属性处理方式

 4.2继承同名成员函数处理方式

 5.同名静态成员处理

 6.多继承语法

7.菱形继承案例

7.1菱形继承概念


1.继承的概述

1.1概念

继承是面向对象三大特性之一。我们发现在定义类的时候,下级别的成员除了拥有上一级别的的共性,还有自己的特性,这时候我们可以考虑使用继承的技术来减少重复的代码。

1.2继承的基本用法

例如我们看到很多的网站中,都有公共的头部、公共的底部、甚至公共的左侧列表,只有中心内容不同,接下来我们分别利用普通写法和继承的写法来实现网页中的内容,看一下继承存在的意义以及好处。

#include<iostream>
using namespace std;

class java {//java网站类
public:
	void header() {
		cout << "首页、公开课、登录、注册……(公共头部)" << endl;
	}
	void foter() {
		cout << "帮助中心、交流合作、站内地图……(公共底部)" << endl;
	}
	void left(){
		cout << "Java、python、C++……(公共分类列表)" << endl;
	}
	void content() {
		cout << "java学科视频" << endl;
	}
};

//Python页面
class Python {//java网站类
public:
	void header() {
		cout << "首页、公开课、登录、注册……(公共头部)" << endl;
	}
	void foter() {
		cout << "帮助中心、交流合作、站内地图……(公共底部)" << endl;
	}
	void left() {
		cout << "Java、python、C++……(公共分类列表)" << endl;
	}
	void content() {
		cout << "Python学科视频" << endl;
	}
};

//C++学习页面
class Cpp{//java网站类
public:
	void header() {
		cout << "首页、公开课、登录、注册……(公共头部)" << endl;
	}
	void foter() {
		cout << "帮助中心、交流合作、站内地图……(公共底部)" << endl;
	}
	void left() {
		cout << "Java、python、C++……(公共分类列表)" << endl;
	}
	void content() {
		cout << "C++学科视频" << endl;
	}
};

void test02() {
	java ja;
	cout << "java下载视频页面如下" << endl;
	ja.header();
	ja.foter();
	ja.left();
	ja.content();

	cout << "-----------------------" << endl;
	cout << "Python下载视频页面如下" << endl;
	Python py;
	py.header();
	py.foter();
	py.left();
	py.content();

	cout << "-----------------------" << endl;
	cout << "C++下载视频页面如下" << endl;
	Cpp cp;
	cp.header();
	cp.foter();
	cp.left();
	cp.content();
}

void main() {
	
	test02();
}

可以看到,代码没有问题,但是有太多的冗余代码,这样看起来太low。所以我们可以考虑使用继承的方式:

class BasePage {//公共的界面
public:
	void header() {
		cout << "首页、公开课、登录、注册……(公共头部)" << endl;
	}
	void foter() {
		cout << "帮助中心、交流合作、站内地图……(公共底部)" << endl;
	}
	void left() {
		cout << "Java、python、C++……(公共分类列表)" << endl;
	}
};

//Java页面
class Java :public BasePage {//继承公共页面
public:
	void content() {
		cout << "Java学科视频" << endl;
	}
};

//Python页面
class Python :public BasePage {//继承公共页面
public:
	void content() {
		cout << "Python学科视频" << endl;
	}
};

//C++页面
class Cpp :public BasePage {//继承公共页面
public:
	void content() {
		cout << "C++学科视频" << endl;
	}
};

void test02() {
	Java ja;
	cout << "java下载视频页面如下" << endl;
	ja.header();
	ja.foter();
	ja.left();
	ja.content();

	cout << "-----------------------" << endl;
	cout << "Python下载视频页面如下" << endl;
	Python py;
	py.header();
	py.foter();
	py.left();
	py.content();

	cout << "-----------------------" << endl;
	cout << "C++下载视频页面如下" << endl;
	Cpp cp;
	cp.header();
	cp.foter();
	cp.left();
	cp.content();
}

void main() {
	
	test02();
}

 

 1.3继承的好处

减少重复冗余的代码

1.4语法

class 子类:继承方式  父类 ,如下所示:

 子类:也成为派生类,父类也称为基类

2.继承方式

2.1继承语法

class 子类:继承方式  父类 

2.2继承方式

  • 公共继承
  • 保护继承
  • 私有继承

解析:

  • 子类不能继承父类的私有属性
  • 子类如果是公共继承,在父类中是公共的属性在子类中同样是,在父类中是保护继承的属性,在子类中也是如此
  • 如果子类是保护继承,父类中的公共属性,在子类中变为保护权限,父类保护,子类也是保护
  • 如果子类是私有继承父类中的公共权限和保护权限属性,在子类中都变为私有权限
#include<iostream>
using namespace std;

//公共继承
class Base1 {
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};

class Son :public Base1 {
public:
	void func() {
		m_A = 10;//没有报错说明父类中的公共类成员在子类中依然是公共权限
		m_B = 10;//没有报错说明父类中的保护类成员在子类中依然是保护权限
		//m_C = 10;报错了,说明父类中的私有属性,子类拿不到
	}
	int m_D;
};



void test02() {
	Son s1;
	s1.m_A = 100;//m_A是公共权限,类内可以访问,类外也可以访问
	//s1.m_B = 100;m_B是保护权限,类内可以访问,但是类外不可访问
	cout << "size of Son =" << sizeof(Son) << endl;
}

void main() {
	
	test02();
}

 

我们也可以利用开发人员命令提示工具查看对象模型(VS自带的Developer Command Prompt)

  • 查看需要运行的程序在哪个盘,如果在D盘就输入D:就跳转到D盘
  • 跳转文件的路径:cd  具体的路径
  • 查看对象模型输入:cl /d1 reportSingleClassLayout类名 “文件名” 

如下所示:

3.继承中的构造与析构顺序

子类继承父类后,当创建子类对象,也会调用父类的构造函数

问题:父类和子类的构造和析构顺序谁先谁后呢?

#include<iostream>
using namespace std;

//继承中的构造与析构的顺序
class Base1 {
public:
	Base1() {
		cout << "Base1的构造函数" << endl;
	}
	~Base1() {
		cout << "Base1的析构函数" << endl;
	}
};

class Son :public Base1 {
public:
	Son() {
		cout << "Son的构造函数" << endl;
	}
	~Son() {
		cout << "Son的析构函数" << endl;
	}
};

void test01() {
	//Base1 b;
	Son s;


}


void main() {
	
	test01();
}

可以看出,继承中的构造与析构顺序如下:

先构造父类,再构造子类,析构的顺序与构造的顺序相反

4.继承同名成员处理方式

问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或者父类中的同名的数据?

  • 访问子类同名成员  直接访问即可
  • 访问父类的同名成员需要加作用域

4.1继承同名成员属性处理方式

#include<iostream>
using namespace std;

//继承中的构造与析构的顺序
class Base1 {
public:
	Base1() {
		m_A = 100;
	}
	
	int m_A ;
};

class Son :public Base1 {
public:
	Son() {
		m_A = 200;
	}
	int m_A;
};

void test01() {
	Son s;
	cout << "Son m_A=" <<s. m_A << endl;

}

void main() {
	
	test01();
}

可以看出如果直接访问,m_A是200,说明如果出现同名直接访问是访问的自身的成员。

 如果需要拿到父类的成员:

只需要加一个父类的作用域即可访问到同名中父类的属性。 

 4.2继承同名成员函数处理方式

#include<iostream>
using namespace std;

//继承中的构造与析构的顺序
class Base1 {
public:
	Base1() {
		m_A = 100;
	}
	void func() {
		cout << "这是base中的函数调用" << endl;
	}
	int m_A ;
};

class Son :public Base1 {
public:
	Son() {
		m_A = 200;
	}
	void func() {
		cout << "这是son中的函数调用" << endl;
	}
	int m_A;
};

void test01() {
	Son s;
	cout << "Son m_A=" <<s. m_A << endl;
	cout << "Base m_A=" << s.Base1::m_A << endl;
}

void test02() {
	Son s;
	s.func();//当出现重名调用的还是子类的
	s.Base1::func();//调用父类的成员函数
}
void main() {
	
	test01();
	test02();
}

 5.同名静态成员处理

问题:继承同名的静态成员在子类对象上如何进行访问?

静态成员和非静态成员出现同名,处理方式一样。

  • 访问子类同名成员,直接访问即可
  • 访问父类同名成员,需要加作用域

静态成员的属性特点:

  • 编译阶段分配内存
  • 所有对象共享一份数据
  • 类内声明,类外要初始化
#include<iostream>
using namespace std;

class Base1 {
public:
	
	static int m_A ;//类内声明
};
int Base1::m_A = 100;//类外初始化

class Son :public Base1 {
public:

	static int m_A;
};
int Son::m_A = 200;

void test01() {
	Son s;
	cout << "Son m_A=" << s.m_A << endl;
}
void main() {
	
	test01();
}

可以看到直接访问是访问的子类的,要想访问父类需要加一个作用域

 

 访问可以通过对象访问,也可以通过类名访问,如下:

 

#include<iostream>
using namespace std;

class Base1 {
public:
	
	static int m_A ;//类内声明
};
int Base1::m_A = 100;//类外初始化

class Son :public Base1 {
public:

	static int m_A;
};
int Son::m_A = 200;

void test01() {
	//通过对象访问
	Son s;
	cout << "Son m_A=" << s.m_A << endl;
	cout << "Base m_A=" << s.Base1::m_A << endl;

	//通过类名访问
	cout << "通过类名访问:" << endl;
	cout << "Son 下 m_A=" << Son::m_A<<endl;
	cout << "Base 下 m_A=" << Son::Base1::m_A << endl;
}
void main() {
	
	test01();
}

继承静态的成员属性以及成员函数的完整代码如下:

#include<iostream>
using namespace std;

class Base1 {
public:
	static void func() {
		cout << "Base static void func()" << endl;
	}
	static int m_A ;//类内声明
};
int Base1::m_A = 100;//类外初始化

class Son :public Base1 {
public:
	static void func() {
		cout << "Son static void func()" << endl;
	}
	static int m_A;
};
int Son::m_A = 200;

void test01() {
	//通过对象访问
	Son s;
	cout << "Son m_A=" << s.m_A << endl;
	cout << "Base m_A=" << s.Base1::m_A << endl;

	//通过类名访问
	cout << "通过类名访问:" << endl;
	cout << "Son 下 m_A=" << Son::m_A<<endl;
	cout << "Base 下 m_A=" << Son::Base1::m_A << endl;
}

void test02() {
	//通过对象访问
	cout << "通过对象访问" << endl;
	Son s;
	s.func();
	s.Base1::func();

	//通过类访问
	cout << "通过类访问" << endl;
	Son::func();
	Son::Base1::func();
}
void main() {
	
	test01();
	cout << "***********************" << endl;
	test02();
}

 

 6.多继承语法

C++允许一个类继承多个类

语法:class 子类 :继承方式 父类1,继承方式 父类2 ……

注意:多继承可能会引发父类中有同名成员的出现,需要加作用域区分

C++在实际开发中不建议使用多继承

#include<iostream>
using namespace std;

class Base1 {
public:
	Base1() {
		m_A = 100;
	}
	int m_A;
};

class Base2 {
public:
	Base2() {
		m_B = 200;
	}
	int m_B;
};

//子类继承Base1和Base2
class Son :public Base1,public Base2{
public:
	Son() {
		m_C = 300;
		m_D = 400;
	}
	int m_C;
	int m_D;
};


void test01() {
	Son s;
	cout << "sizeof Son=" << sizeof(s) << endl;
}


void main() {
	
	test01();
	
}

我们可以看看类的结构,具体如何打开的见上一个博客。 

我们可以看出size是16,Son这个类,在Base1中继承了m_A在Base2中继承了m_B,自己还有m_C和m_D。

7.菱形继承案例

7.1菱形继承概念

  • 两个派生类继承同一个基类
  • 又有某个类同时继承两个派生类
  • 这种继承称为菱形继承,或者钻石继承

例如:羊和骆驼继承了动物这个类,草泥马这个动物又继承了羊和骆驼这个类,若动作有一个m_A这个属性,那么羊和骆驼也继承了动物类中的m_A,那么草泥马这个类就有两份m_A,而我们只需要一份即可,那怎么办?

#include<iostream>
using namespace std;

//动物类
class Animal {
	int m_Age;
};

//羊类
class Sheep:public Animal {

};

//骆驼类
class Camel:public Animal {

};

//羊驼类(草泥马类)
class SheepCamel :public Sheep, public Camel {

};

void test01() {
	SheepCamel sc;
	sc.m_Age = 10;
}


void main() {
	
	test01();
	
}

test01中直接对m_Age=10赋值会出现“SheepCamel m_Age不明确”的错误。所以:当菱形继承,两个父类拥有相同的数据,需要加以作用域区分

那么草泥马(羊驼)的m_Age到底是多少呢,我们只需要一份数据就可以了。我们打出报告可以看到:SheepCamel继承了两个类,一个是Sheep一个是Camel,有两个m_Age,但是我们只需要一个m_Age那么该如何解决呢?

 利用虚继承可以解决菱形继承的问题。即为,在继承之前加一个关键字virtual变为虚继承。

 

#include<iostream>
using namespace std;

//动物类
class Animal {
public:
	int m_Age;
};


//利用虚继承解决菱形继承问题,继承之前加上关键词virtual变为虚继承
//Animal类称为虚基类
//羊类
class Sheep:virtual public Animal {

};

//骆驼类
class Camel:virtual public Animal {

};

//羊驼类(草泥马类)
class SheepCamel :public Sheep, public Camel {

};

void test01() {
	SheepCamel sc;
	sc.Sheep::m_Age = 10;
	sc.Camel::m_Age = 28;
	//当菱形继承,两个父类拥有相同的数据,需要加以作用域区分
	cout << "sc.Sheep::m_Age = " << sc.Sheep::m_Age << endl;
	cout << "sc.Camel::m_Age=" << sc.Camel::m_Age << endl;
	cout << "SheepCamel m_Age=" << sc.m_Age << endl;
}


void main() {
	
	test01();
	
}

 经过修改后发现年龄都变成28了。我们再看看报告:

可以看到与没有加关键词virtual时的结构完全不一样了。可以看到SheepCamel的m_Age只有一份了,而从Sheep和Camel继承下来的是vbptr。vbptr(virtual base pointer)表示虚基类指针 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AI炮灰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值