一、继承(Inherit)
1.1、定义
继承是一种机制,在已有类的基础之上,重新封装一个新的类,这种方式叫做继承.
1> 父类中的成员变量和成员函数会被子类继承
2> 子类有父类中所有的方法和属性
3> 子类是特殊的父类
eg:
学生类:姓名,年龄,性别,学习
老师类:姓名,年龄,性别,授课,工资
以上写法代码冗余
Human:姓名,年龄,性别
学生类:继承Human类,学习
教师类:继承Human类,授课,工资
人 类 》基类 (父类)
/ \
学生类 教师类 》派生类 (子类)
1.2、继承的语法格式
class 子类类名:继承表{};
class 子类类名:继承属性 基类类名1,
继承属性 基类类名2,......{};
继承属性:
public
protected
private
1.3、子类继承基类中的成员属性的变化
行---->父类访问权限
列---->子类继承方式
| public(公有) | protected(受保护) | private(私有) | |
|---|---|---|---|
| public(公有的继承) | public(公有) | protected(受保护) | private(私有) |
| protected(受保护继承) | protected(受保护) | protected(受保护) | private(私有) |
| private(私有的继承) | private(私有) | private(私有) | private(私有) |
公有的继承:
子类对象会继承基类中的所有的属性和行为,
子类对象访问从基类中继承的公有的和保护的成员,如同访问自己的一样。
基类中私有的成员在子类中无法访问,子类继承了基类中私有的成员,
只是继承的私有的成员在子类中被隐藏了。
受保护继承:
私有的继承:
//参考案例: 04inher.cpp
#include <iostream>
using namespace std;
class Basic{
public:
Basic(int data):m_data1(data),m_data2(data),m_data3(data){}
Basic(void){}
void show(){
cout << "m_data3 = " << m_data3 << endl;
}
void print(){
cout << "m_data3 = " << m_data3 << endl;
}
public:
int m_data1;
protected:
int m_data2;
private:
int m_data3;
};
class Child:public Basic{
public:
Child(int data):Basic(data),m_data(data){}
void show()
{
// 基类公有的成员子类可以访问
cout << "m_data1 = " << m_data1 << endl;
// 基类保护的成员子类可以访问
cout << "m_data2 = " << m_data2 << endl;
// 基类私有的成员子类不可以访问
// cout << "m_data3 = " << m_data3 << endl;
Basic::show();
cout << "m_data = " << m_data << endl;
}
private:
int m_data;
};
int main(int argc, const char *argv[])
{
Child c(100);
c.show();
// 通过子类对象调用基类中在子类中隐藏的函数
c.Basic::show();
c.print();
return 0;
}
//结果:
m_data1 = 100
m_data2 = 100
m_data3 = 100
m_data = 100
m_data3 = 100
m_data3 = 100
练习题: 测试保护继承和私有继承
A -> B:public
A -> B:protected -> C:public B
A -> B:private -> C:public B
1.4、基类中的构造函数,子类是无法继承的
1> 但是可以通过初始化表的方式显示的调用基类中的构造函数,完成对基类中成员的初始化。
语法格式:
类名(形参表):基类类名(形参名),...,子类成员(形参名),...{}
2> 如果子类的构造函数没有显示的在初始化表中调用基类的构造函数,默认会调用基类的无参构造函数。
3> 如果子类想调用基类中的构造函数,必须在初始化表中显示的调用基类的构造函数。
4> 构造函数的调用顺序
基类的构造函数
子类的构造函数
5> 子类对象被创建出来的时候父类构造函数先于子类构造函数被调用
6> 类内封装的其它类的构造顺序和类内的声明一致
7> 调用方法可以隐式调用和显示调用
//参考案例: 05inher.cpp
#include <iostream>
using namespace std;
class Human{
public:
Human(void){
cout << "基类的无参构造函数" << endl;
}
Human(const string& name, int age):m_name(name),m_age(age){
cout << "基类有参构造函数" << endl;
}
void show(void){
cout << "姓名:" << m_name << endl;
cout << "年龄:" << m_age << endl;
}
~Human(void){
cout << "基类的析构函数" << endl;
}
private:
string m_name;
int m_age;
};
// 如果省略public继承属性,默认是公有的继承
class Student:public Human{
public:
Student(const string& name, int age, int score):Human(name,age),m_score(score){
cout << "子类的有参构造函数" << endl;
}
// 子类中的构造函数没有显示的调用基类的构造函数,
// 默认会调用基类的无参构造函数
Student(void){
cout << "子类的无参构造函数" << endl;
}
~Student(void){
cout << "子类的析构函数" << endl;
}
// 子类中隐藏基类中同名的成员,
void show(void){
Human::show();
cout << "成绩:" << m_score << endl;
}
private:
int m_score;
};
int main(int argc, const char *argv[])
{
Student stu1("zhoukai", 18, 99);
// 通过子类对象调用基类在子类中隐藏的成员
// stu1.Human::show();
// 调用的是子类中的show函数
stu1.show();
cout << sizeof(stu1) << endl;
Student stu2;
return 0;
}
//结果:
基类有参构造函数
子类的有参构造函数
姓名:zhoukai
年龄:18
成绩:99
40
基类的无参构造函数
子类的无参构造函数
子类的析构函数
基类的析构函数
子类的析构函数
基类的析构函数
1.5、基类的析构函数,子类也无法继承
1> 子类中的析构函数是否显示的定义了,都会调用基类中的析构函数完成对基类的成员的析构。
1. 如果说在全局区或者栈区,子类对象中析构函数的执行顺序和构造函数执行顺序相反
2. 如果在堆区,和delete的执行顺序有关
2> 析构函数的调用顺序
子类的析构函数
基类的析构函数
//参考案例: 05inher.cpp
//在上面
1.6、子类中隐藏基类中同名的成员
1> 子类和基类具有相同名字的成员,在子类中会被隐藏,
2> 子类中隐藏基类中的成员函数在子类中的成员函数和基类中的成员函数,及时具有相同的函数名,形参列表不同,也不可以构成重载的关系,原因是函数的作用域不同。
3> 如果想在子类隐藏的基类成员:(这些成员是保护或者公有)
基类类名::成员函数名(实参表);
基类类名::变量名;
如果通过子类对象访问在子类中隐藏的基类的公有的成员
对象名.基类类名::函数名(实参表);
对象名.基类类名::变量名;
参考案例: 06inher.cpp
#include <iostream>
using namespace std;
class Human{
public:
Human(void){
cout << "基类的无参构造函数" << endl;
}
Human(const string& name, int age):
m_name(name),m_age(age){
cout << "基类有参构造函数" << endl;
}
void show(int){
cout << "姓名:" << m_name << endl;
cout << "年龄:" << m_age << endl;
}
~Human(void){
cout << "基类的析构函数" << endl;
}
private:
string m_name;
int m_age;
};
// 如果省略public继承属性,默认是公有的继承
class Student:public Human{
public:
Student(const string& name, int age, int score):
Human(name,age),m_score(score){
cout << "子类的有参构造函数" << endl;
}
// 子类中的构造函数没有显示的调用基类的构造函数,
// 默认会调用基类的无参构造函数
Student(void){
cout << "子类的无参构造函数" << endl;
}
~Student(void){
cout << "子类的析构函数" << endl;
}
// 子类中隐藏基类中同名的成员,
void show(void){
Human::show(1);
cout << "成绩:" << m_score << endl;
}
private:
int m_score;
};
int main(int argc, const char *argv[])
{
Student stu1("zhoukai", 18, 99);
// 通过子类对象调用基类在子类中隐藏的成员
stu1.Human::show(1);
// 调用的是子类中的show函数
stu1.show();
cout << sizeof(stu1) << endl;
Student stu2;
return 0;
}
//结果:
基类有参构造函数
子类的有参构造函数
姓名:zhoukai
年龄:18
姓名:zhoukai
年龄:18
成绩:99
40
基类的无参构造函数
子类的无参构造函数
子类的析构函数
基类的析构函数
子类的析构函数
基类的析构函数
1.7、子类中的拷贝构造函数
1》子类如果没有显示的定义拷贝构造函数,编译器默认会提供一个缺省的拷贝构造函数,缺省的拷贝构造函数默认会调用基类中的拷贝构造函数,完成对基类中成员的初始化。
2》 子类中显示的定义了拷贝构造函数,编译器不在提供缺省的拷贝构造函数,此时必须通过初始化表的方式显示的调用基类中的拷贝构造函数,否则会调用基类中的无参构造函数,对基类中的成员进行无参初始化。
//参考案例:07inher.cpp
#include <iostream>
using namespace std;
class Shape{
public:
Shape(int x,int y):m_x(x),m_y(y){}
Shape(){
cout << "基类中的无参构造函数" << endl;
}
~Shape(void){}
Shape(const Shape& other):
m_x(other.m_x),m_y(other.m_y){
cout << "基类拷贝构造函数" << endl;
}
void show(){
cout << "坐标:" << m_x << ":" << m_y << endl;
}
private:
int m_x;
int m_y;
};
class Rect:public Shape{
public:
Rect(int x,int y,int w,int h):
m_w(w),m_h(h),Shape(x,y){}
~Rect(void){}
#if 0
// 拷贝构造函数没有显示调用基类的拷贝构造函数
// 默认调用基类无参构造函数,
// 完成对基类中的成员无参初始化
Rect(const Rect& other):m_w(other.m_w),m_h(other.m_h){}
#else
// Rect --> Shape : 隐士的完成类型转换
Rect(const Rect& other):
Shape(other),m_w(other.m_w),m_h(other.m_h){}
#endif
void show(){
Shape::show();
cout << "宽高:" << m_w << ":" << m_h << endl;
}
private:
int m_w;
int m_h;
};
int main(int argc, const char *argv[])
{
Rect r1(1,2,3,4);
Rect r2(r1); // 调用拷贝构造函数
r2.show();
return 0;
}
//结果:
基类拷贝构造函数
坐标:1:2
宽高:3:4
1.8、子类中的拷贝赋值函数
1》子类如果没有显示的定义拷贝赋值函数,编译器默认会提供一个缺省的拷贝赋值函数,缺省的拷贝赋值函数默认会调用基类中的拷贝赋值函数,完成对基类中成员的初始化。
2》 子类中显示的定义了拷贝赋值函数,编译器不在提供缺省的拷贝赋值函数,此时必须通过在拷贝赋值函数中显示的调用基类中的拷贝赋值函数,否则基类中的成员保持不变。
//参考案例:08inher.cpp
#include <iostream>
using namespace std;
class Shape{
public:
Shape(int x,int y):m_x(x),m_y(y){}
Shape(){
cout << "基类中的无参构造函数" << endl;
}
~Shape(void){}
Shape(const Shape& other):m_x(other.m_x),m_y(other.m_y){
cout << "基类拷贝构造函数" << endl;
}
Shape& operator=(const Shape& other){
cout << "基类拷贝赋值函数" << endl;
if(&other != this){
m_x = other.m_x;
m_y = other.m_y;
}
return *this;
}
void show(){
cout << "坐标:" << m_x << ":" << m_y << endl;
}
private:
int m_x;
int m_y;
};
class Rect:public Shape{
public:
Rect(int x,int y,int w,int h):
m_w(w),m_h(h),Shape(x,y){}
~Rect(void){}
#if 0
// 拷贝构造函数没有显示调用基类的拷贝构造函数
// 默认调用基类无参构造函数,
// 完成对基类中的成员无参初始化
Rect(const Rect& other):m_w(other.m_w),m_h(other.m_h){}
#else
// Rect --> Shape : 隐士的完成类型转换
Rect(const Rect& other):Shape(other),m_w(other.m_w),m_h(other.m_h){}
#endif
// 子类显示的定义拷贝赋值函数
#if 0
// 子类的拷贝赋值函数不显示的调用基类的拷贝赋值函数
Rect& operator=(const Rect& other){
cout << "子类的拷贝赋值函数" << endl;
if(&other != this){
m_w = other.m_w;
m_h = other.m_h;
}
return *this;
}
#else
// 子类的拷贝赋值函数显示的调用基类的拷贝赋值函数
Rect& operator=(const Rect& other){
cout << "子类的拷贝赋值函数" << endl;
if(&other != this){
// Rect --> Shape : 隐士类型转换
Shape::operator=(other);
m_w = other.m_w;
m_h = other.m_h;
}
return *this;
}
#endif
void show(){
Shape::show();
cout << "宽高:" << m_w << ":" << m_h << endl;
}
private:
int m_w;
int m_h;
};
int main(int argc, const char *argv[])
{
Rect r1(1,2,3,4);
Rect r2(5,6,7,8);
r1 = r2; // 调用拷贝赋值函数
r1.show();
return 0;
}
//结果:
子类的拷贝赋值函数
基类拷贝赋值函数
坐标:5:6
宽高:7:8
1.9、基类和子类之间的类型转换
1> 子类---》基类 : 向上造型 (常用,安全)
2> 基类---》子类 : 向下造型 (不常用,不安全)
重点内容:
运算符的重载
继承
向上造型
二、多重继承
A B
\ /
C
2.1、定义
一个子类继承多个基类,这种继承方式叫做多重继承
2.2、语法格式
class 子类类名:继承属性 基类类名1,继承属性 基类类名2,......{};
2.3、问题
1> 名字冲突的问题
在多个基类中具有相同名字的成员,通过子类对象去访问基类中的成员时,就会存在名字冲突的问题。
注意:不能和子类中的名字相同,否则会调用子类中的成员。
2> 可以通过"基类类名::"限定符的方式访问基类类中的成员。
3> 多重继承向上造型的问题:
将继承自多个基类的子类对象,隐式的转换成基类对象,编译器会根据每个基类在内存中 的排布不同自动的完成地址偏移,让每个基类指向跟自己类型匹配的地址空间。
#include <iostream>
using namespace std;
class A{
int m_a;
public:
A(int a):m_a(a){}
void show(){
cout << m_a << "-" << &m_a << endl;
}
};
class B{
float m_b;
public:
B(float b):m_b(b){}
void show(){
cout << m_b << "-" << &m_b <<endl;
}
};
//多重继承
class C:public B,public A{
string m_name;
public:
C(const string& name, int a, float b):m_name(name),A(a),B(b){}
void print()
{
cout << m_name << "-" << &m_name << endl;
A::show();
B::show();
}
};
int main(int argc, const char *argv[])
{
C c("sdfas", 12, 3.14);
c.print();
A a = c;
a.show();
B b = c;
b.show();
return 0;
}
//结果:
sdfas - 0x7fffa169abd8
12 - 0x7fffa169abd4
3.14 - 0x7fffa169abd0
12 - 0x7fffa169abcc
3.14 - 0x7fffa169abc8
三、虚继承《钻石继承》
3.1、定义
一个子类的多个基类来自于共同的祖先基类,这种继承方式需要使用虚继承的语法格式。
A
/ \
B C : 需要采用虚继承的语法格式
\ /
D
3.2、语法格式
关键字: virtual
class A{} // 虚基类
class B:virtual public A{} 虚继承
class C:virtual public A{} 虚继承
class D:public B, public C{}
3.3、多个公共基类问题
派生多个中间子类 (B,C) 的 公共基类(A) ,在继承自多个中间子类的 汇聚子类(D),如果 中间子类 (B,C)不采用虚继承的语法格式,会在 汇聚子类(D)实例化对象时, 公共基类(A)会存在多个实例对象。如果在一个 汇聚子类(D)对象中存在多个 公共基类(A)的对象时,根据不同的访问路径,访问不同的地址空间
A A // 公共基类
| |
B C // 中间子类
\ /
D // 汇聚子类
#include <iostream>
using namespace std;
class A{ // 公共基类
public:
A(int a):m_a(a){}
void show(){
cout << "A::m_a = " << m_a << endl;
cout << "&A::m_a = " << &m_a << endl;
}
private:
int m_a;
};
class B:public A{
public:
B(int a, int b):A(a),m_b(b){}
void show(){
A::show();
cout << "B::m_b = " << m_b << endl;
}
private:
int m_b;
};
class C:public A{
public:
C(int a, int c):A(a),m_c(c){}
void show(){
A::show();
cout << "C::m_c = " << m_c << endl;
}
private:
int m_c;
};
class D:public B, public C{
public:
D(int a, int b, int c,int d):B(a, b),C(a,c),m_d(d){}
void show(){
cout << "D -> B -> A -> m_a" << endl;
B::show();
cout << "D -> C -> A -> m_a" << endl;
C::show();
cout << "D::m_d = " << m_d << endl;
}
private:
int m_d;
};
int main(int argc, const char *argv[])
{
D d(1111,2222,3333,4444);
d.show();
return 0;
}
//结果:
D -> B -> A -> m_a
A::m_a = 1111
//&A::m_a = 0x7ffff0ebcf20
B::m_b = 2222
D -> C -> A -> m_a
A::m_a = 1111
//&A::m_a = 0x7ffff0ebcf28
C::m_c = 3333
D::m_d = 4444
// 公共基类的地址不一样,即 存在多个公共基类
3.4、虚继承
可以使用虚继承的语法格式解决以上问题。在继承链的最末端的汇聚子类负责构造虚基类对象。
虚基类的所有的子类(B,C,D)都必须在其构造函数的初始化表中显示的调用虚基类的构造函数。
关键字: virtual
class A{} // 虚基类
class B:virtual public A{} 虚继承
class C:virtual public A{} 虚继承
class D:public B, public C{}
#include <iostream>
using namespace std;
class A{ // 公共基类
public:
A(int a):m_a(a){}
void show(){
cout << "A::m_a = " << m_a << endl;
cout << "&A::m_a = " << &m_a << endl;
}
private:
int m_a;
};
// 使用 virtual 关键字进行虚继承 基类
class B:virtual public A{
public:
B(int a, int b):A(a),m_b(b){}
void show(){
A::show();
cout << "B::m_b = " << m_b << endl;
}
private:
int m_b;
};
// 使用 virtual 关键字进行虚继承 基类
class C:virtual public A{
public:
C(int a, int c):A(a),m_c(c){}
void show(){
A::show();
cout << "C::m_c = " << m_c << endl;
}
private:
int m_c;
};
class D:public B, public C{
public:
D(int a, int b, int c,int d):
A(a), B(a, b),C(a,c),m_d(d){}
void show(){
cout << "D -> B -> A -> m_a" << endl;
B::show();
cout << "D -> C -> A -> m_a" << endl;
C::show();
cout << "D::m_d = " << m_d << endl;
}
private:
int m_d;
};
int main(int argc, const char *argv[])
{
D d(1111,2222,3333,4444);
d.show();
return 0;
}
//结果:
D -> B -> A -> m_a
A::m_a = 1111
//&A::m_a = 0x7fff68ce27d0
B::m_b = 2222
D -> C -> A -> m_a
A::m_a = 1111
//&A::m_a = 0x7fff68ce27d0
C::m_c = 3333
D::m_d = 4444
// 公共基类的地址一样,即 只存在一个公共基类
四、多态
4.1、定义:
函数的多种表现形态,
通过基类对象调用子类中的成员函数,及子类中的成员函数完成对基类中的成员函数的覆盖,
这种形态叫做多态。
4.2、表现出多态特性的条件
1> 子类中的成员函数完成对基类中的成员函数的覆盖。
2> 通过基类对象调用子类中的成员函数。
4.3、成员函数覆盖的条件
1> 基类和子类中具有相同的函数原型
函数的名字相同,形参列表相同,返回值相同具有相同的常属性。
2> 基类中的成员函数使用virtual关键字修饰,
则在子类中具有和基类中相同的函数也将变成虚函数。
3> 构造函数,拷贝构造函数、全局函数、静态成员函数、友元函数不可以是虚函数。
析构函数可以是虚函数。
4.4、表现出多态的特性?
1> 满足覆盖的条件
2> 通过指向子类对象的基类指针
Shape *s1 = new Rect(1,2,3,4);
或者通过引用目标为子类对象的基类引用
Circle c1(5,6,7);
Shape &s2 = c1;
Shape s3 = c1; // 无法表现出多态的特性
#include <iostream>
using namespace std;
class Shape{
public:
Shape(int x, int y):m_x(x),m_y(y){}
virtual ~Shape(void){
cout << "Shape::~Shape()" << endl;
}
// 在基类的函数前加virtual关键字,则此函数将变成虚函数,在子类中和该函数具有相同原型的函数也将变成虚函数,
// 此时通过指向子类对象的基类指针调用此函数,将会调用到子类中对应的虚函数,
// 可以表现出这种现象的叫做多态.
// virtual关键字必须添加到基类中的函数前边,加载子类中的函数前边没有任何的影响
virtual void Painter(void){
cout << "坐标:" << m_x << ":" << m_y << endl;
}
private:
int m_x;
int m_y;
};
class Rect:public Shape{
public:
Rect(int x, int y, int w, int h):Shape(x,y),m_w(w),m_h(h){}
~Rect(void){
cout << "Rect::~Rect()" << endl;
}
void Painter(void){
cout << "绘制矩形" << endl;
Shape::Painter();
cout << "宽高:" << m_w << ":" << m_h << endl;
}
private:
int m_w;
int m_h;
};
class Circle:public Shape{
public:
Circle(int x,int y, int r):Shape(x,y),m_r(r){}
~Circle(){
cout << "Circle::~Circle()" << endl;
}
void Painter(void){
cout << "绘制圆" << endl;
Shape::Painter();
cout << "半径:" << m_r << endl;
}
private:
int m_r;
};
int main(int argc, const char *argv[])
{
// 指向子类对象的基类指针
Shape *S1 = new Rect(1,2,3,4);
S1->Painter(); // 调用基类的Painter
delete S1;
Shape *S2 = new Circle(5,6,7);
S2->Painter(); // 调用基类的Painter
delete S2;
Rect r1(11,22,33,44);
Shape &S3 = r1;
S3.Painter();
// 无法表现出多态的特性
// Shape S4 = r1;
// S4.Painter();
return 0;
}
//结果:
绘制矩形
坐标:1:2
宽高:3:4
Rect::~Rect()
Shape::~Shape()
绘制圆
坐标:5:6
半径:7
Circle::~Circle()
Shape::~Shape()
绘制矩形
坐标:11:22
宽高:33:44
Rect::~Rect()
Shape::~Shape()
4.5、虚析构函数
1> 通过指向子类对象的基类指针,当delete基类指针时,只会调用基类中的析构函数,而不调用子类中的析构函数,
2> 此时可以将基类中的析构函数声明为虚函数,
3> 此时子类中的析构函数也将变成虚函数,
4> 此时再delete基类指针时,调用的是子类中的析构函数对基类的析构函数的覆盖版本,
5> 在通过子类的析构函数调用基类中的析构函数,完成对基类中成员的资源的释放。
4.6、纯虚函数
语法格式:
virtual 返回类型 函数名 (形参表) = 0;
没有函数体的虚函数,叫做纯需函数。
4.7、虚类(抽象类)
类中包含纯虚函数的类叫做虚类。
4.8、纯虚类(纯抽象类)
类中只包含纯虚函数的类叫做纯虚类。
#include <iostream>
using namespace std;
// 虚类 纯虚类
class Shape{
public:
Shape(int x, int y):m_x(x),m_y(y){}
virtual ~Shape(void){
cout << "Shape::~Shape()" << endl;
}
// 纯虚函数
virtual void Painter(void) = 0;
protected:
int m_x;
int m_y;
};
class Rect:public Shape{
public:
Rect(int x, int y, int w, int h):Shape(x,y),m_w(w),m_h(h){}
~Rect(void){
cout << "Rect::~Rect()" << endl;
}
void Painter(void){
cout << "绘制矩形" << endl;
cout << "坐标:" << m_x << ":" << m_y << endl;
cout << "宽高:" << m_w << ":" << m_h << endl;
}
private:
int m_w;
int m_h;
};
class Circle:public Shape{
public:
Circle(int x,int y, int r):Shape(x,y),m_r(r){}
~Circle(){
cout << "Circle::~Circle()" << endl;
}
void Painter(void){
cout << "绘制圆" << endl;
cout << "坐标:" << m_x << ":" << m_y << endl;
cout << "半径:" << m_r << endl;
}
private:
int m_r;
};
int main(int argc, const char *argv[])
{
// 指向子类对象的基类指针
Shape *S1 = new Rect(1,2,3,4);
S1->Painter(); // 调用基类的Painter
delete S1;
Shape *S2 = new Circle(5,6,7);
S2->Painter(); // 调用基类的Painter
delete S2;
Rect r1(11,22,33,44);
Shape &S3 = r1;
S3.Painter();
// 无法表现出多态的特性
// Shape S4 = r1;
// S4.Painter();
return 0;
}
//结果:
绘制矩形
坐标:1:2
宽高:3:4
Rect::~Rect()
Shape::~Shape()
绘制圆
坐标:5:6
半径:7
Circle::~Circle()
Shape::~Shape()
绘制矩形
坐标:11:22
宽高:33:44
Rect::~Rect()
Shape::~Shape()
- 父类中使用virtual关键字修饰的方法,可以在子类中被重写,从而实现多态
4.2 虚函数表
- 虚函数表(virtual table),用来解决多态和覆盖的问题
- 虚函数表是在对象开始的位置
- 只有类中有虚函数,才会有一个指针,指向虚函数表
- 虚函数表中存放的是这个对象中的虚函数的地址
- 一个类中如果有虚函数(不论多少个),会多出来四字节,这个是指向虚函数表指针的字节大小
- 虚函数表中的虚函数顺序是根据类中虚函数的声明顺序来的
- 如果,重写了父类的虚函数,虚函数表中将只体现一个虚函数
4.3 重载、隐藏、重写的区别
-
重载
是发生在同一个作用域
函数名相同,形参列表不同
-
隐藏
发生在不同的作用域
函数名和形参列表完全相同的时候,不管有没有virtual关键字子类都会把父类中的同名函数隐藏
实际上函数指针指向的是两个函数,默认访问的是子类的同名函数,如果想要访问父类的同名函数需要加域访问运算符
-
重写
发生在不同的作用域
父类中必须有virtual关键字,子类才能重写父类的同名函数
4.4 面向对象的特点
封装 继承 多态
浙公网安备 33010602011771号