类和对象-继承
18.0 前言
继承是面向对象三大特性之一。
有些类与类之间存在特殊的关系,例如下图中:
我们发现,定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的特性。
这个时候我们就可以考虑利用继承的技术,减少重复代码。
18.1 继承的基本语法
例如我们看到很多网站中,都有公共的头部,公共的底部,甚季公共的左侧列表,只有中心内容不同。
接下来我们分别利用普通写法和继承的写法来实现网页中的内容,看下继承存在的意义以及好处。
语法:
class 子类:继承方式 父类
- 继承的好处:减少重发代码
- 子类也称为派生类
- 父类也成为基类
示例:
#include<iostream>
using namespace std;
//普通方法实现网络页面
//Java
class Java
{
public:
Java()
{
this->header();
this->footer();
this->left();
this->content();
}
void header()
{
cout << "首页、公开课、登录、注册、...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++、...(公共分类列表)" << endl;
}
void content()
{
cout << "Java学科视频" << endl;
}
};
void test1_01()
{
cout << "Java下载视频页面如下:" << endl;
Java ja;
}
//Python
class Python
{
public:
Python()
{
this->header();
this->footer();
this->left();
}
void header()
{
cout << "首页、公开课、登录、注册、...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++、...(公共分类列表)" << endl;
}
void content()
{
cout << "Python学科视频" << endl;
}
};
void test1_02()
{
cout << "Python下载视频页面如下:" << endl;
Python py;
}
//继承方法实现网络页面
//C++
class BasePage
{
public:
BasePage()
{
this->header();
this->footer();
this->left();
}
void header()
{
cout << "首页、公开课、登录、注册、...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++、...(公共分类列表)" << endl;
}
};
//继承的好处:减少重发代码
//语法:class 子类:继承方式 父类
//子类也称为派生类
//父类也成为基类
class Cpp:public BasePage
{
public:
Cpp()
{
this->content();
}
void content()
{
cout << "C++学科视频" << endl;
}
};
void test1_03()
{
cout << "C++下载视频页面如下:" << endl;
Cpp cpp;
}
int main()
{
test1_01();
cout << "----------------" << endl;
test1_02();
cout << "----------------" << endl;
test1_03();
system("pause");
return 0;
}
派生类中的成员,包含两大部分:
一类是从基类继承过来的,一类是自己增加的成员。
从基类继承过来的表现其共性,而新增的成员表现了其个性。
18.2 继承方式
继承方式一共有三种:
- 公共继承
- 保护继承
- 私有继承
示例:
#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; //父类中的私有权限成员,子类无法访问
}
};
void test2_01()
{
Son1 s1;
s1.m_A = 100;
//s1.m_B = 100; //保护权限,访问不到
}
//保护继承
class Son2 :protected Base1
{
public:
void func()
{
m_A = 100; //父类中的公共权限成员,到子类中变成了保护权限成员
m_B = 100; //父类中的保护权限成员,到子类中依旧是保护权限成员
//m_C = 100;//父类中的私有权限成员,子类无法访问
}
};
void test2_02()
{
Son2 s2;
//s2.m_A = 100; //保护权限,访问不到
//s2.m_B = 100; //保护权限,访问不到
}
//私有继承
class Son3 :private Base1
{
public:
void func()
{
m_A = 100; //父类中的公共权限成员,到子类中变成了私有权限成员
m_B = 100; //父类中的保护权限成员,到子类中变成了私有权限成员
//m_C = 100;//父类中的私有权限成员,子类无法访问
}
};
//可以通过再创建一个类来继承此类查看成员是否为私有权限
class GrandSon3 :public Son3
{
public:
void func()
{
//m_A = 100;//父类中的私有权限成员,子类无法访问
//m_B = 100;//父类中的私有权限成员,子类无法访问
}
};
int main()
{
system("pause");
return 0;
}
18.3 继承中的对象模型
18.3.1 子类对父类成员全部继承
问题:从父类继承过来的成员,哪些属于子类对象中?
答案:父类中的所有非静态成员属性都会被子类继承下去。
#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 test3_01()
{
//父类中的所有非静态成员属性都会被子类继承下去
cout << "size of Son = " << sizeof(Son) << endl;
}
int main()
{
test3_01();
system("pause");
return 0;
}
- 利用开发人员命令提示工具查看对象模型
2. 跳转盘符 E:(此cpp所在的盘符)
3. 跳转文件路径 cd 此cpp所在的文件夹路径
4. 查看命令cl [filename].cpp /d1reportSingleClassLayoutXXX 其中XXX为类名。
如图所示:
可以看到:父类中的所有非静态成员属性都会被子类继承下去。
18.3.2 父类指针指向子类对象
C++支持父类和子类的类型转换,父类的指针可以直接指向子类对象。如果使用父类指针指向子类对象,那么子类对象将变为父类对象,会失去原有的成员,并拥有父类的成员,相当于类型转换。
示例:
#include<iostream>
using namespace std;
//动物类
class Animal
{
public:
void speak()
{
cout << "动物在说话" << endl;
}
};
class Cat :public Animal
{
public:
void speak()
{
cout << "小猫在说话" << endl;
m_A = 100;
}
};
//执行说话的函数
void doSpeak(Animal& animal) //Animal& animal = cat; 传入的是子类,用父类接收,此时此对象转换为了父类
{
cout << (typeid(animal).name()) << endl;
animal.speak();
//cout << animal.m_A << endl; 变成了父类对象,没有了子类的成员。
}
void test3_02()
{
Cat cat;
Animal an;
doSpeak(cat);
}
int main()
{
test3_02();
system("pause");
return 0;
}
18.4 继承中构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数。
问题:父类和子类的构造和析构顺序谁先谁后?
答案:父类先构造,然后子类构造,析构顺序与构造顺序相反(栈:先进后出)。
示例:
#include<iostream>
using namespace std;
class Base4
{
public:
Base4()
{
cout << "Base4构造函数" << endl;
}
~Base4()
{
cout << "Base4析构函数" << endl;
}
};
class Son :public Base4
{
public:
Son()
{
cout << "Son构造函数" << endl;
}
~Son()
{
cout << "Son析构函数" << endl;
}
};
void test4_01()
{
Son son;
}
int main()
{
test4_01();
system("pause");
return 0;
}
18.5 继承同名成员处理方式
问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢?
- 访问子类同名成员,直接访问即可。语法:
对象.成员
- 访问父类同名成员,需要加作用域。语法:
对象.父类::成员
这里说一下:当一个子类继承父类后,这个子类已经有了父类的所有成员变量和成员函数。
成员变量:
- 如果子类中没有和父类名字相同的成员变量,那么此时子类和父类共享一个成员变量,此时在类外访问这个成员时的语法为:
对象.成员变量
或者对象.父类::成员变量
。 - 为了区分子类和父类中同名的成员变量,在子类中,想要访问父类的成员变量时,使用语法:
父类::成员变量
或者this->父类::成员变量
。在类外访问子类的成员变量:对象.成员变量
;访问父类的成员变量:对象.父类::成员变量
。
成员函数:
- 如果子类中没有和父类名字相同的成员函数,那么此时子类和父类共享一个成员函数,使用
对象.成员函数
或者对象.父类::成员函数
。 - 在子类中的同名成员函数会将父类中的所有同名成员函数全部隐藏(所有重载的同名函数都会被隐藏),想要调用同名的父类函数必须使用加作用域的语法。
- 总之,在子类中,想要调用父类的成员函数时,使用语法:
父类::成员函数
或者this->父类::成员函数
。在类外调用子类的成员函数:对象.成员函数
;调用父类的成员函数:对象.父类::成员函数
。
示例:
#include<iostream>
using namespace std;
class Base5
{
public:
Base5()
{
m_A = 100;
}
void func()
{
cout << "Base5-func()调用" << endl;
}
void func(int a)
{
cout << "Son5_01-func(int a)调用" << endl;
}
int m_A;
};
class Son5_01 :public Base5
{
public:
Son5_01()
{
m_A = 300;
}
//int m_A; //如果不创建m_A的成员变量,那么构造函数将直接对父类中的m_A进行赋值.
void func()
{
cout << "Son5_01-func()调用" << endl;
}
};
class Son5_02 :public Base5
{
public:
Son5_02()
{
//Base5::m_A = 50; //如果需要改变父类的成员变量,指明作用域即可.
m_A = 200;
}
void funcc()
{
Base5::func(); //在子类中调用父类函数
}
int m_A; //创建m_A的成员变量后,则是对自己的成员m_A进行赋值.
};
void test5_01()
{
Son5_01 son;
cout << "Son5_01下: m_A = " << son.m_A << endl;
cout << "Base5下: m_A = " << son.Base5::m_A << endl;
}
void test5_02()
{
Son5_02 son;
cout << "Son5_02下: m_A = " << son.m_A << endl;
cout << "Base5下: m_A = " << son.Base5::m_A << endl;
}
void test5_03()
{
cout << "在类外中调用子类和父类函数" << endl;
Son5_01 son;
son.func();
//son.func(100); 父类中的同名成员函数都被隐藏了.
son.Base5::func();
son.Base5::func(100);
}
void test5_04()
{
cout << "在子类中调用父类函数" << endl;
Son5_02 son;
son.funcc();
}
int main()
{
test5_01();
test5_02();
test5_03();
test5_04();
system("pause");
return 0;
}
18.6 继承同名静态成员处理方式
问题:继承中同名的静态成员在子类对象上如何进行访问?
静态成员和非静态成员出现同名,处理方式一致。
- 访问子类同名成员,直接访问即可。语法:
对象.成员
- 访问父类同名成员,需要加作用域。语法:
对象.父类::成员
注:
当通过类名访问父类成员时,语法为:子类::父类::成员
。
示例:
#include<iostream>
using namespace std;
class Base6
{
public:
static int m_A; //类内定义
static void func()
{
cout << "Base6-static void func()" << endl;
}
};
int Base6::m_A=100;//类外初始化
class Son6 :public Base6
{
public:
static int m_A;
static void func()
{
cout << "Son6-static void func()" << endl;
}
};
int Son6::m_A = 200;
void test6_01()
{
//通过对象访问
cout << "通过对象访问" << endl;
Son6 s;
cout << "Son6下的m_A为:" << s.m_A << endl;
cout << "Base6下的m_A为:" << s.Base6::m_A << endl;
//通过类名访问
cout << "通过类名访问" << endl;
cout << "Son6下的m_A为:" << Son6::m_A << endl;
cout << "Base6下的m_A为:" << Son6::Base6::m_A << endl;//第一个::代表通过类名访问,第二个::代表作用域
}
void test6_02()
{
//通过对象访问
cout << "通过对象访问" << endl;
Son6 s;
s.func();
s.Base6::func();
//通过类名访问
cout << "通过类名访问" << endl;
Son6::func();
Son6::Base6::func();
}
int main()
{
test6_01();
test6_02();
system("pause");
return 0;
}
18.7 多继承语法
C++允许一个类继承多个类
语法:
class 子类 : 继承方式 父类1 , 继承方式 父类2 ...{
};
多继承可能会引发父类中有同名成员出现,需要加作用域区分。
C++实际开发中不建议使用多继承。
#include<iostream>
using namespace std;
class Base7_01
{
public:
Base7_01()
{
m_A = 100;
}
int m_A;
};
class Base7_02
{
public:
Base7_02()
{
m_A = 200;
}
int m_A;
};
class Son7 :public Base7_01, public Base7_02
{
public:
Son7()
{
m_C = 300;
m_D = 400;
}
int m_C;
int m_D;
};
void test7_01()
{
Son7 s;
cout << "sizeof s = " << sizeof(s) << endl; //4*4
cout << "Base7_01的m_A = " << s.Base7_01::m_A << endl;
cout << "Base7_02的m_A = " << s.Base7_02::m_A << endl;
}
int main()
{
test7_01();
system("pause");
return 0;
}
18.8 菱形继承(钻石继承)
菱形继承概念:
- 两个派生类继承同一个基类,
- 又有某个类同时继承这两个派生类,
- 这种继承被成为菱形继承,或者钻石继承。
典型的菱形继承案例:
菱形继承问题:
- 羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性(使用作用域解决)。
- 草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以(虚继承解决)。
虚继承:继承前加virtual
关键字后,变为虚继承。
语法:
class 子类 : virtual 权限 父类{
};
示例:
#include<iostream>
using namespace std;
//动物
class Animal
{
public:
int m_Age;
};
//利用虚继承 解决菱形继承的问题
// //继承之前加上关键字 virtual 变为虚继承
// Animal类称为 虚基类
//羊
class Sheep :virtual public Animal
{
};
//骆驼
class Tuo :virtual public Animal
{
};
//羊驼
class SheepTuo :public Sheep, public Tuo
{
};
void test8_01()
{
SheepTuo st;
//st.m_Age = 18; 不明确
st.Sheep::m_Age = 18;
st.Tuo::m_Age = 28;
st.m_Age = 100;
//当菱形继承,两个父类拥有相同数据,需要加以作用域区分
cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;
cout << "sizeof(st) = " << sizeof(st) << endl;
}
int main()
{
test8_01();
system("pause");
return 0;
}
多个子类使用虚继承方式继承父类时,此时公共的父类称为:虚基类。
此时这些存在继承关系的类中只存在一个成员变量,所以访问的时候无论使用对象.成员
还是对象.父类::成员
都是一个成员变量。
虚继承是在子类中储存一个虚基类指针——vbptr(virtual base pointer),这个指针指向一个虚基类表——vbtable(virtual base table)。在虚基类表中为每个子类储存了一个偏移量,每个子类在访问成员变量时,通过这个虚基类指针访问虚基类表中的偏移量将会访问到虚基类中唯一的成员变量。