C++学习(13)—— 继承
继承是面向对象的三大特性之一
1.继承的基本语法
例如我们看到很多网站中,都有公共的头部,公共的底部,甚至公共的左侧列表,只有中心内容不同
接下来我们分别利用普通写法和继承的写法来实现网页中的内容,看一下继承存在的意义以及好处
- 继承的好处:减少重复代码
- 语法:
class 子类 : 继承方式 父类
- 子类也称为派生类,父类也称为基类
普通实现:
#include<iostream>
using namespace std;
//普通实习页面
class Java{
public:
void header(){
cout << "首页、公开课、登陆、注册...(公共头部)" << endl;
}
void footer(){
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left(){
cout << "Java、Python、C++...(公共分类列表)" <<endl;
}
void content(){
cout << "Java学科视频" << endl;
}
};
class Python{
public:
void header(){
cout << "首页、公开课、登陆、注册...(公共头部)" << endl;
}
void footer(){
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left(){
cout << "Java、Python、C++...(公共分类列表)" <<endl;
}
void content(){
cout << "Python学科视频" << endl;
}
};
void test01(){
cout << "Java下载视频页面如下:" << endl;
Java ja;
ja.header();
ja.footer();
ja.left();
ja.content();
}
void test02(){
cout << "Python下载视频页面如下:" << endl;
Python py;
py.header();
py.footer();
py.left();
py.content();
}
int main(){
test01();
cout << "------------------------------" << endl;
test02();
return 0;
}
继承实现:
#include<iostream>
using namespace std;
//公共页面
class BasePage{
public:
void header(){
cout << "首页、公开课、登陆、注册...(公共头部)" << endl;
}
void footer(){
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;
}
};
void test01(){
cout << "Java下载视频页面如下:" << endl;
Java ja;
ja.header();
ja.footer();
ja.left();
ja.content();
}
void test02(){
cout << "Python下载视频页面如下:" << endl;
Python py;
py.header();
py.footer();
py.left();
py.content();
}
int main(){
test01();
cout << "------------------------------" << endl;
test02();
return 0;
}
2.继承方式
语法:class 子类 : 继承方式 父类
继承方式一共有三种:
- 公共继承
- 保护继承
- 私有继承
#include<iostream>
using namespace std;
//公共继承
class Base1{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son1 : public Base1{
public:
void func(){
m_A = 10;
m_B = 10; //父类中保护成员,到子类中变为保护权限
//m_C = 10;
}
}
//保护继承
class Son2 : protected Base1{
public:
void func(){
m_A = 10; //父类中公共成员,到子类中变为保护权限
m_B = 10;
//m_C = 10;
}
}
//私有继承
class Son3 : protected Base1{
public:
void func(){
m_A = 10; //父类中公共成员,到子类中变为保护权限
m_B = 10;
//m_C = 10;
}
}
void test01(){
Son1 s1;
s1.m_A = 100;
//s1.m_B = 100;
//s1.m_C = 100;
}
void test02(){
Son2 s2;
s2.m_A = 100;
//s2.m_B = 100;
//s2.m_C = 100;
}
void test03(){
Son3 s3;
s3.m_A = 100;
//s3.m_B = 100;
//s3.m_C = 100;
}
int main(){
test01();
return 0;
}
3.继承中的对象模型
问题:从父类继承过来的成员,哪些属于子类对象中?
#include<iostream>
using namespace std;
class Base{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son : public Base{
public:
int m_D;
};
void test01(){
//父类中所有非静态成员属性都会被子类继承下去
//父类中私有成员属性,是被编译器给隐藏了,因此是访问不到的,但是确实被继承下去了
cout << "size of Son = " << sizeof(Son) << endl;
}
int main(){
test01();
return 0;
}
4.继承中构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类构造函数
问题:父类和子类的构造和析构顺序是谁先谁后呢?
#include<iostream>
using namespace std;
class Base{
public:
Base(){
cout << "Base构造函数" << endl;
}
~Base(){
cout << "Base析构函数" << endl;
}
};
class Son : public Base{
public:
Son(){
cout << "Son构造函数" << endl;
}
~Son(){
cout << "Son析构函数" << endl;
}
};
void test01(){
Base b;
Son s;
}
int main(){
test01();
return 0;
}
总结:继承中,先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反
5.继承同名成员处理方式
问题:当子类与父类出现同名成员,如何通过子类对象,访问到子类或父类中同名的数据呢?
- 访问子类同名成员 直接访问即可
- 访问父类同名成员 需要加作用域
#include<iostream>
using namespace std;
class Base{
public:
Base(){
m_A = 100;
}
void func(){
cout << "Base - func()调用 " << endl;
}
int m_A;
};
class Son : public Base{
public:
Son(){
m_A = 200;
}
void func(){
cout << "Son - func()调用 " << endl;
}
int m_A;
};
void test01(){
Son s;
cout << "m_A = " << s.Base::m_A << endl;
s.Base::func();
}
int main(){
test01();
return 0;
}
总结:
- 子类对象可以直接访问到子类中的同名成员
- 子类对象加作用域可以访问到父类同名成员
- 当子类与父类拥有同名成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数
6.继承同名静态成员处理方式
问题:继承中同名的静态成员在子类对象上如何进行访问?
静态成员和非静态成员出现同名,处理方式一致
- 访问子类同名成员 直接访问即可
- 访问父类同名成员 需要加作用域
#include<iostream>
using namespace std;
//继承中的同名静态成员处理方式
class Base{
public:
static int m_A;
};
int Base::m_A = 100;
class Son : public Base{
public:
static int m_A;
};
int Son::m_A = 200;
void test01(){
//1.通过对象访问数据
Son s;
cout << "通过对象访问数据" << endl;
cout << "Son下m_A = " << s.m_A << endl;
cout << "Base下m_A = " << s.Base::m_A << endl;
//2.通过类名访问数据
cout << "通过类名访问数据" << endl;
cout << "Son下m_A = " << Son::m_A << endl;
cout << "Base下m_A = " << Son::Base::m_A << endl;
}
int main(){
test01();
return 0;
}
7.多继承语法
C++允许一个类继承多个类
语法:class 子类 : 继承方式 父类1,继承方式 父类2...
多继承可能会引发父类中有同名成员出现,需要加作用域区分
C++实际开发中不建议使用多继承
8.菱形继承
菱形继承概念:
- 两个派生类继承同一个基类
- 又有某个类同时继承两个派生类
- 这种继承被称为菱形继承,或者钻石继承
典型的菱形继承案例:
动物
↓ ↘
羊 驼
↓ ↙
羊驼
菱形继承问题:
- 羊继承了动物的数据,驼同样继承了动物的数据,当羊驼使用数据时,就会产生二义性
- 羊驼继承自动物的数据继承了两份,其实我们清楚,这份数据我们只需要一份就可以
#include<iostream>
using namespace std;
//动物类
class Animal{
public:
int m_A;
};
//利用虚继承解决菱形继承的问题
//继承之前加上关键字virtual变成虚继承
//Animal类称为 虚基类
class Sheep : virtual public Animal{
};
class Tuo : virtual public Animal{
};
class SheepTuo : public Sheep, public Tuo{
};
void test01(){
SheepTuo st;
st.Sheep::m_A = 18;
st.Tuo::m_A = 28;
//当菱形继承时,两个父类拥有相同数据,需要加以作用域区分
cout << "st.Sheep::m_A = " << st.Sheep::m_A << endl;
cout << "st.Tuo::m_A = " << st.Tuo::m_A << endl;
cout << "st.m_A = " << st.m_A << endl;
//这份数据我们知道,只要有一份就可以,菱形继承导致数据有两份
}
int main(){
test01();
return 0;
}
虚继承:
- 继承指针,生成对应得偏移量表,指向唯一数据
总结:
- 菱形继承带来的主要问题是子类继承两份相同得数据,导致资源浪费以及毫无意义
- 利用虚继承可以解决菱形继承问题
Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.