继承的知识点结构:
继承是面向对象三大特性之一,例如动物类下可以有猫类、狗类。显然猫类、狗类都具有动物类的属性,于此同时,也就有自己独特的属性。这个时候,猫类、狗类就是继承于动物类,
采用继承的技术,可以减少重复的代码。
1. 继承的基本语法
语法:class 子类:继承方式 基类 继承示例: 一般网页都有很多部分相似的,我们可以将这些相似的部分抽象出来,作为一个基类。然后子类只需要继承基类,在基类的基础上添加自身其他的属性或者方法就可以实现基本的功能,同时减少了重复代码。
class BasePage
{
public:
void header()
{
cout << "网页公共头部信息" << endl;
}
void footer()
{
cout << "网页公共底部信息" << endl;
}
void left()
{
cout << "网页公共分类列表信息" << endl;
}
};
class Java :public BasePage
{
public:
void content()
{
cout << "Java网页信息" << endl;
}
};
class Python :public BasePage
{
public:
void content()
{
cout << "Python网页信息" << endl;
}
};
class CPP :public BasePage
{
public:
void content()
{
cout << "CPP网页信息" << endl;
}
};
void test01()
{
cout << "Java页面如下" << endl;
Java ja;
ja.header();
ja.footer();
ja.left();
ja.content();
cout << "Python页面如下" << endl;
Python py;
py.header();
py.footer();
py.left();
py.content();
cout << "CPP页面如下" << endl;
CPP cp;
cp.header();
cp.footer();
cp.left();
cp.content();
}
int main()
{
test01();
system("pause");
return 0;
}
2. 继承方式
继承方式一共有三种:
- 公共继承public
- 保护继承protected
- 私有继承private
注:保护访问权限和私有访问权限都是类内可访问,类外不能访问。但是两者的不同点会在继承中体现。
下面将说明发生继承后,子类访问权限的变化情况: 基类:
class A
{
public:
int a;
protected:
int b;
private:
int c;
};
class B :public A
{
public:
int a;
protected:
int b;
不可访问:
int c:
};
class B :protected A
{
protected:
int a;
int b;
不可访问:
int c:
};
class B :private A
{
private:
int a;
int b;
不可访问:
int c:
};
3. 继承的对象模型
问题:从父类继承过来的成员,哪些是属于子类对象的?
先来看一段代码:
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();
system("pause");
return 0;
}
输出的结果如下: 可以得到以下结论:
- 父类中所有非静态成员属性都会被子类继承下去
- 父类中私有成员属性是被编译器给隐藏了,因此访问不到,但是确实是被继承下去了
利用开发工具验证继承的对象模型:
- 打开Visual Studio的开发人员命令提示符
2.将路径定位到所要查看类对象模型所在文件夹的路径 3.输入代码:cl(l代表字母l) /d1(1代表数字1) reportSingleClassLayout类名 类所在的文件名,并得到结果
4. 继承中构造和析构顺序
示例:
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()
{
Son son;
}
int main()
{
test01();
system("pause");
return 0;
}
运行结果: 继承中的构造和析构顺序如下: 先构造父类,再构造子类,析构的顺序与构造的顺序相反
5. 继承中同名成员处理
问题:当子类与父类出现同名成员,如何通过子类对象访问到子类或者父类中同名的数据呢?
- 访问子类同名成员,直接访问即可
- 访问父类同名成员,需要加作用域
示例:
class Base
{
public:
Base()
{
m_A = 100;
}
int m_A;
};
class Son :public Base
{
public:
Son()
{
m_A = 200;
}
int m_A;
};
void test01()
{
Son son;
cout << "子类中的m_A = " << son.m_A << endl;
cout << "父类中的m_A = " << son.Base::m_A << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
运行结果: 注:同名成员函数的处理和同名成员属性的处理相同,要注意的是:如果子类中出现和父类同名的成员函数,子类的同名成员函数会隐藏掉父类中所有同名成员函数
6. 继承中同名静态成员处理方式
问题:继承中同名的静态成员在子类对象上如何进行访问?
静态成员和非静态成员出现同名时,处理方式一致
6.1 通过对象访问
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()
{
Son s;
cout << "子类中的静态成员m_A = " << s.m_A << endl;
cout << "父类中的静态成员m_A = " << s.Base::m_A << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
6.2 通过类名访问
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()
{
cout << "子类中的静态成员m_A = " << Son::m_A << endl;
cout << "父类中的静态成员m_A = " << Son::Base::m_A << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
7. 多继承语法
C++允许一个类继承多个类 语法:class 子类: 继承方式 父类1, 继承方式 父类2 ... 注:多继承可能会引发父类中有同名成员出现,需要加作用域区分,C++的实际开发中不建议用多继承的技术
7.1 多继承的对象模型
class Base1
{
public:
Base1()
{
m_A = 100;
}
int m_A;
};
class Base2
{
public:
Base2()
{
m_A = 200;
}
int m_A = 200;
};
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;
cout << "Base1中的m_A = " << s.Base1::m_A << endl;
cout << "Base2中的m_A = " << s.Base2::m_A << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
运行结果:
也可以利用开发者工具查看模型:
8. 菱形继承(钻石继承)
概念:两个派生类继承同一个基类,又有某个类同时继承着两个派生类
典型的菱形继承案例: 动物作为基类,羊类和驼类作为动物的派生类,羊驼类同时继承羊类和驼类
菱形继承问题:
- 羊继承了动物的数据,驼也同样继承了动物的数据,当羊驼使用数据时,就会产生二义性。
class Animal
{
public:
int m_Age;
};
class Sheep: public Animal
{
};
class Camel : public Animal
{
};
class SheepCamel: public Sheep, public Camel
{
};
void test01()
{
SheepCamel sc;
sc.m_Age = 18;
sc.Sheep::m_Age = 18;
sc.Camel::m_Age = 20;
}
int main()
{
test01();
system("pause");
return 0;
}
- 羊驼类实际上继承两份动物的数据,造成了内存的浪费(例如羊有年龄,驼也有年龄,羊驼都继承了羊类和驼类关于年龄的数据,但实际上羊驼的年龄只有一个,没有必要拥有两份关于年龄的数据)
可以参考多继承的对象模型:
采用虚基类解决菱形继承的问题: 继承之前,加上关键字virtual 变为虚继承
class Animal
{
public:
int m_Age;
};
class Sheep:virtual public Animal
{
};
class Camel :virtual public Animal
{
};
class SheepCamel: public Sheep, public Camel
{
};
void test01()
{
SheepCamel sc;
sc.Sheep::m_Age = 18;
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 << "sc.m_Age = " << sc.m_Age << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
运行结果:
说明:采用虚继承之后,菱形继承中羊驼类就只有一份年龄的数据了,解决了内存浪费的问题
虚继承的底层原理: 我们利用开发者工具调出羊驼类的对象模型: 和未采用虚继承技术的对象模型相比,年龄的数据变成只有一份了,但是多了vbptr vbptr 称为虚基类指针,指针指向的是vbtable ,也就是虚基类表,虚基类表中的内容其实就是一个偏移量
- 看
Sheep 中的虚基类指针指向的虚基类表中的偏移量为8 ,(0+8=8)就找到了年龄数据的地址 - 看
Camel 中的虚基类指针指向的虚基类表中的偏移量为4 ,(4+4=8)就找到了年龄数据的地址
|