继承是面向对象的三大特性之一
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;
}
虚继承:
- 继承指针,生成对应得偏移量表,指向唯一数据
总结:
- 菱形继承带来的主要问题是子类继承两份相同得数据,导致资源浪费以及毫无意义
- 利用虚继承可以解决菱形继承问题