目录
利用已有的数据类型来定义新的数据类型,通过继承机制,可以利用已有的数据类型来定义新的数据类型。所定义的新的数据类型不仅拥有新定义的成员,而且还同时拥有旧的成员。我们称已存在的用来派生新类的类为基类,又称为父类。由已存在的类派生出的新类称为派生类,又称为子类。
继承是面向对象三大特性之一。
在C++语言中,一个派生类可以从一个基类派生,也可以从多个基类派生。从一个基类派生的继承称为单继承;从多个基类派生的继承称为多继承。
我们发现,定义这些类的时候,下级别的成员除了拥有上一级的共性,还有自己的特性。
这个时候我们就可以考虑利用继承得到技术,减少重复代码。
具体化
类的层次通常反映了客观世界中某种真实的模型。在这种情况下,不难看出:基类是对若干个派生类的抽象,而派生类是基类的具体化。基类抽取了它的派生类的公共特征,而派生类通过增加行为将抽象类变为某种有用的类型。
延续化
先定义一个抽象基类,该基类中有些操作并未实现。然后定义非抽象的派生类,实现抽象基类中定义的操作。例如,虚函数就属此类情况。这时,派生类是抽象的基类的实现,即可看成是基类定义的延续。这也是派生类的一种常用方法。
派生类
在多继承时,一个派生类有多于一个的基类,这时派生类将是所有基类行为的组合。
派生类将其本身与基类区别开来的方法是添加数据成员和成员函数。因此,继承的机制将使得在创建新类时,只需说明新类与已有类的区别,从而大量原有的程序代码都可以复用,所以有人称类是“可复用的软件构件”。
总结:
继承的好处:可以减少重复的代码量
语法:
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
?