目录
一、继承
1. 继承的概念
2. 继承的定义
3. 类与类之间的关系
4. 继承的两类关系
二、继承方式的基本语法
总而言之,父类的私有内容,子类是访问不到的。
三、继承中的对象模型
父类中的私有属性被编译器隐藏,访问不到,但被继承下去了
用开发人员命令提示符查看对象模型
四、继承中的构造和析构的顺序
五、继承中同名成员的处理
六、继承同名静态成员 的处理方式
七、多继承语法
八、菱形继承
一、继承
1. 继承的概念
利用已有的数据类型来定义新的数据类型,通过继承机制,可以利用已有的数据类型来定义新的数据类型。所定义的新的数据类型不仅拥有新定义的成员,而且还同时拥有旧的成员。我们称已存在的用来派生新类的类为基类,又称为父类。由已存在的类派生出的新类称为派生类,又称为子类。
继承是面向对象三大特性之一。
2. 继承的定义
在C++语言中,一个派生类可以从一个基类派生,也可以从多个基类派生。从一个基类派生的继承称为单继承;从多个基类派生的继承称为多继承。
3. 类与类之间的关系
我们发现,定义这些类的时候,下级别的成员除了拥有上一级的共性,还有自己的特性。
这个时候我们就可以考虑利用继承得到技术,减少重复代码。
4. 继承的两类关系
具体化
类的层次通常反映了客观世界中某种真实的模型。在这种情况下,不难看出:基类是对若干个派生类的抽象,而派生类是基类的具体化。基类抽取了它的派生类的公共特征,而派生类通过增加行为将抽象类变为某种有用的类型。
延续化
先定义一个抽象基类,该基类中有些操作并未实现。然后定义非抽象的派生类,实现抽象基类中定义的操作。例如,虚函数就属此类情况。这时,派生类是抽象的基类的实现,即可看成是基类定义的延续。这也是派生类的一种常用方法。
派生类
在多继承时,一个派生类有多于一个的基类,这时派生类将是所有基类行为的组合。
派生类将其本身与基类区别开来的方法是添加数据成员和成员函数。因此,继承的机制将使得在创建新类时,只需说明新类与已有类的区别,从而大量原有的程序代码都可以复用,所以有人称类是“可复用的软件构件”。
总结:
继承的好处:可以减少重复的代码量
语法:
class 子类:继承方式 父类
class A : public B;
A 类称为子类 或者 派生类
B 类称为父类 或者 基类
派生类中的成员,包含两大部分:
一类是从基类继承过来的,一类是自己增加的成员
从基类继承过来的表现其共性,而新增的成员体现了其个性。
二、继承方式的基本语法
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 = 20; // 父类中的保护权限成员到子类依然是保护权限
// m_C = 30; // 父类中的私有权限成员到子类访问不到
}
};
void test01()
{
Son1 s1;
s1.m_A = 100; // 创建一个儿子类,访问父亲类中的公共权限是可以访问到的
// s1.m_B = 200; // 在Son类中,m_B是 保护权限 在类外是访问不到的
}
// 保护继承
class Base2 {
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
// 子类
class Son2 :protected Base2 {
public:
void func()
{
m_A = 90; // 父类中公共成员到子类中变为保护权限,在类内才能访问
m_B = 80; // 原本的保护权限的成员到子类中依然是保护权限
// m_C = 70; // 不管是哪种方式继承,父类中的私有属性的内容都访问不到
}
};
void test02()
{
Son2 s2;
// s2.m_A = 1000; // 报错,父类中公共成员,继承到子类的时候已经变为保护权限,在类外访问不到
// s2.m_B = 2000; // 报错,保护权限在类外访问不到
}
// 私有继承
class Base3 {
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
// 子类
class Son3 :private Base3 {
public:
void func()
{
m_A = 100; // 父类的公共成员到子类中变为 私有成员
m_B = 200; // 父类的保护成员到子类中变为 私有成员
//m_C = 300; // 不管是哪种方式继承,父类中的私有属性的内容都访问不到
}
};
class GrandSon3 :public Son3
{
public:
void func()
{
// m_A = 1000;// 到了Son3中, m_A变为私有属性,即使是儿子,也访问不到
// m_B = 2000; //与上一个相同,都是访问不到的
}
};
void test03()
{
Son3 s3;
// s3.m_A = 1000;
// s3.m_B = 2000; // 子类的成员都变为私有成员类外访问不到
}
int main()
{
test01();
test02();
test03();
return 0;
}
运行结果:
三、继承中的对象模型
??:从父类继承过来的成员,哪些属于子类对象中?
父类中所有非静态成员属性都会被子类继承下去
父类中的私有属性被编译器隐藏,访问不到,但被继承下去了
示例:
#include<iostream>
using namespace std;
// 继承中的对象模型
class Base
{
public:
int a;
protected:
int b;
private:
int c;
};
class Son : public Base
{
public:
int d;
};
int main()
{
// 父类中所有非静态成员属性都会被子类继承下去
// 父类的私有成员属性,是被编译器隐藏了,因此访问不到,但是确实被继承了
cout<<"儿子类的大小:"<<sizeof(Son)<<endl;// 16
return 0;
}
运行结果:
用开发人员命令提示符查看对象模型
进入文件所在盘符,cd跳转进入文件路径下,dir查看文件中的内容
输入:cl /d1 reportSingleClassLayout类名 cpp的文件(可以输入前几个字符后tab键自动填充)
四、继承中的构造和析构的顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数
问题:父类和子类的构造和析构的顺序是谁先谁后?
先构造父类再构造子类,先析构子类再析构父类(没有父类直接声明子类的时候,也会先构造父类)
示例:
#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;
}
运行结果:
五、继承中同名成员的处理
问题:当子类与父类出现同名成员,如何通过子类对象,访问到子类或父类中同名的数据?
- 访问子类同名成员,直接访问即可
- 访问父类的同名成员,需要加作用域
示例:
#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.m_A<<endl;
// 如果通过子类的对象 访问到父类的同名成员,需要加父类的作用域
cout<<"Base 下的 m_A = "<<s.Base::m_A<<endl;// 加父类的作用域
}
// 同名函数的处理方式
void test02()
{
Son s1;
s1.func(); // 直接调用 调用的是子类的函数
s1.Base::func(); // 加作用域 调用父类的函数调用
// s.func(100);
// s.Base::func(100);
// 如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名函数(重载的)
// 如果想要访问到父类中隐藏的同名成员函数,需要加作用域
}
int main()
{
test01();
test02();
return 0;
}
运行结果:
总结:
- 子类对象可以直接访问到子类中同名成员
- 子类对象加作用域可以访问到父类同名成员
- 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类同名函数
六、继承同名静态成员 的处理方式
问题:
继承中同名的静态成员在子类对象上如何进行访问?
- 访问子类同名成员,直接访问即可
- 访问父类的同名成员,需要加作用域
总结:
- 访问子类同名成员,直接访问即可
- 访问父类的同名成员,需要加作用域
- 子类出现和父类同名的静态成员函数,也会隐藏父类中所有同名成员函数
- 如果想要访问父类中被隐藏的同名成员,也需要加作用域
示例:
#include<iostream>
using namespace std;
// 继承中同名静态成员处理
class Base
{
public:
static int m_A; // 类内声明,类外初始化
static void func()
{
cout<<"Base - static 函数的调用 "<<endl;
}
};
int Base:: m_A = 100;
class Son : public Base
{
public:
static int m_A;
static void func()
{
cout<<"Son - static 函数的调用 "<<endl;
}
};
int Son:: m_A = 200;
// 同名静态成员属性
void test01()
{
// 1. 通过对象访问数据
cout<<"通过对象访问"<<endl;
Son s;
cout<<"Son 下的 m_A = "<<s.m_A<<endl;
cout<<"Base 下的 m_A = "<<s.Base::m_A<<endl<<endl;
// 2. 通过类名访问
cout<<"通过类名访问"<<endl;
cout<<" Son 下的 m_A = "<<Son::m_A<<endl;
// 第一个:: 代表通过类名方式访问 第二个::代表访问父类作用域下
cout<<"Base 下的 m_A = "<<Son::Base::m_A<<endl<<endl;
}
// 同名静态成员函数
void test02()
{
// 1. 通过对象来访问
cout<<"通过对象访问"<<endl;
Son s2;
s2.func();
s2.Base::func();
cout<<endl;
cout<<"通过类名访问"<<endl;
// 2. 通过类名访问
Son::func();
// 第一个:: 代表通过类名方式访问 第二个::代表访问父类作用域下
Son::Base::func();
// 如果子类出现和父类同名静态成员函数,也会隐藏父类中所有同名函数成员
// 如果想访问父类中被隐藏同名成员,需要加作用域
// Son::Base::func(100); 即重载了func为有参构造
}
int main()
{
test01();
test02();
return 0;
}
运行结果:
七、多继承语法
c++允许一个类继承多个类
语法:class 子类 : 继承方式 父类1,继承方式 父类2...
多继承可能会引发类中有同名成员出现,需要加作用域区分
总结:多继承中如果父类出现同名情况,子类使用的时候加作用域。
示例:
#include<iostream>
using namespace std;
// 多继承的语法
class Base1
{
public:
Base1()
{
m_A = 100;
}
int m_A;
};
class Base2
{
public:
Base2()
{
m_A = 200;
}
int m_A;
};
// 子类 需要继承Base1 和 Base2
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();
return 0;
}
运行结果:
通过命令提示符查看类的大小
八、菱形继承
概念:
两个派生类继承同一个基类
又有某个类同时继承这两个派生类
这种继承称为菱形继承,或者钻石继承
案例:
问题:
- 羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。
- 草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。
示例:
#include<iostream>
using namespace std;
// 模拟菱形继承
// 动物类
class Animal
{
public:
int m_age;
};
// 利用虚继承 解决菱形继承的问题
// 继承之前加关键字 virtual 变为虚继承
// Animal 变为虚基类
// 羊类
class sheep:virtual public Animal
{
};
// 驼类
class Tuo:virtual public Animal{};
// 羊驼类
class sheep_Tuo:public sheep,public Tuo{};
void test01()
{
sheep_Tuo st;
st.sheep::m_age = 18;
st.Tuo::m_age = 28;
// 当出现菱形继承的时候,有两个父类中有相同的数据,需要加作用域区分
cout<<"st.sheep::m_age = "<<st.sheep::m_age<<endl;
cout<<"st.Tuo::m_age = "<<st.Tuo::m_age<<endl;
// 虚继承导致数据只有一份,不存在数据来源不明确的情况
cout<<"st.m_age = "<<st.m_age<<endl; // 现在就可以访问到
// 产生虚基类指针 vbptr
// virtual base pointer 指向 vbtable
// 这份数据有一份就可以,但是菱形继承导致数据有两份,浪费资源
}
int main()
{
test01();
return 0;
}
运行结果:
命令提示符查看
vbptr 虚基类指针 ---指向 vbtable --虚基类表
v -- virtual
b -- base
ptr -- pointer