一.组合
1.定义:
组合就是一个类的对象具备了某一个属性,该属性的值是指向另一个类的对象
2.用处:
解决类与类之间代码冗余的问题
二.继承
1.定义
用一个数据类型来定义一个新的数据类型,定义的新类型(派生类或子类)既有原来数据(基类或父类)中的成员,也能添加自己的成员。
2.分类
1.单继承
定义的格式
class 派生类名:继承方式 基类名
{
派生类成员
};
2.多继承
定义的格式
class 派生类名:继承方式 基类名,继承方式 基类名
{
派生类成员;
};
在多继承中靠近派生类的基类属于先声明的,数据在派生类中先保存。
3.菱形继承
当一个子进程继承了多个父类的时候,多个父类最终继承了同一个类,这种继承称之为菱形继承
4.虚继承
class Furniture{……};
class Bed : virtual public Furniture{……}; // 使用虚继承
class Sofa : virtual public Furniture{……};// 使用虚继承
class sleepSofa : public Bed, public Sofa {……};
//Furniture类只需要构造一次
3.三种继承方式
public 公有继承
private 私有继承
protected 保护继承
访问方式 | 类里面 | 类外面 |
---|---|---|
public | 允许访问 | 允许访问 |
protected | 允许访问 | 不允许访问 |
private | 允许访问 | 不允许访问 |
继承方式 | 基类的public成员 | 基类的protected成员 | 基类的private成员 | 继承引起的访问控制关系变化概括 |
---|---|---|---|---|
public继承 | 仍为public成员 | 仍为protected成员 | 不可见 | 基类的非私有长远在子类的访问属性不变 |
protected继承 | 变为protected成员 | 变为protected成员 | 不可见 | 基类的非私有成员都为子类的保护成员 |
private继承 | 变为private成员 | 变为private成员 | 不可见 | 基类中的非私有成员都称为子类的私有成员 |
class默认私有继承,struct默认公有继承
4.友元函数的继承
1.友元函数和友元类
友元函数:可以访问指定类的私有和受保护的自定义成员,如果不是被指定的成员,则不能被访问。
友元类:友元关系是单向的,也不能被传递。
2.注意事项
1.友元函数可以访问类的私有成员,但不是类的成员函数
2.友元函数不能用const修饰
3.友元函数可以在类定义的任何地方声明,不受类访问限定符的限定
4.一个函数可以是多个类的友元函数
5.友元关系不能继承
5.静态成员的继承
静态成员可以继承,一个继承体系中static成员只有一个,无论有多少个派生类,都仅仅有一个static成员
三.多态
1.定义
指同一个事物的多种状态
2.用处
多态性:继承同一个类的多个子类中有相同的方法名,这时子类产生的对象就可以不用考虑具体的类型而直接调用该功能
3.静态多态性
1.函数重载和缺省参数
1.重载的函数必须有相同的函数名
2.重载的函数不可以拥有相同的参数
只有在 同一类定义中的同名成员函数才存在重载关系 ,主要特点是 函数的参数类型和数目有所不同 ,但 不能出现函数参数的个数和类型均相同 ,仅仅依靠返回值类型不同来区分的函数,这和普通函数的重载是完全一致的。另外,重载和成员函数是否是虚函数无关
3.默认参数只有卸载函数声明中即可
4.默认参数应该尽量靠近函数参数列表的最右边,以防二义性。
比如
double sum (float nNum2 = 10,float nNum1);
这样的函数声明,我们调用时:sum(15);程序就有可能无法匹配正确的函数而出现编译错误。
2.运算符重载
用法
返回值 operator 运算符(参数列表)
{
//code
}
3.宏多态
带变量的宏可以实现一种初级形式的静态多态
4.动态多态性
1.虚表和虚函数
#include<iostream>
using namespace std;
class animal
{
public:
void sleep(){
cout<<"animal sleep"<<endl;
}
virtual void breathe(){
cout<<"animal breathe"<<endl;
}
};
class fish:public animal
{
public:
void breathe(){
cout<<"fish bubble"<<endl;
}
};
int main()
{
fish fh;
animal *pAnimal=&fh;
pAnimal->breathe();
}
编译器为每个类的对象提供一个虚表指针,这个指针指向对象所属类的虚表。
虚表指针在构造函数中进行虚表的创建和虚标指针的初始化。
对于虚函数调用来说,每一个对象内部都有一个虚表指针,该虚表指针被初始化为本类的虚表。所以在程序中,不管你的对象类型如何转换,但该对象内部的虚表指针是固定的,所以呢,才能实现动态的对象函数调用,这就是C++多态性实现的原理。
总结(基类有虚函数):
1、每一个类都有虚表。
2、虚表可以继承。如果基类3个虚函数,那么基类的虚表中就有三项(虚函数地址),派生类也会有虚表,至少有三项,如果重写了相应的虚函数,那么虚表中的地址就会改变,指向自身的虚函数实现。如果派生类有自己的虚函数,那么虚表中就会添加该项。
3、派生类的虚表中虚函数地址的排列顺序和基类的虚表中虚函数地址排列顺序相同。
2.虚函数和纯虚函数
1.引入原因
虚函数:为了方便使用多态,在基类中定义虚函数
纯虚函数:为了实现多态性,自己不实现过程,让继承他的子类实现(抽象类即包含纯虚函数的类)
纯虚函数在基类只定义函数体,没有实现过程
virtual void Eat()=0;直接=0
2.区别
(1)虚函数中的函数是实现的,哪怕是空实现,它的作用是这个函数在子类里面可以被重载,运行时动态绑定实现动态
纯虚函数是个接口,是个函数声明,在基类中不实现,要等到子类中去实现
(2) 虚函数在子类里可以不重载,但是虚函数必须在子类里去实现。
四.模板
1.定义
template<typename T>.//在模板定义语法中关键字class与typename的作用完全一样
函数模板必须由编译器根据程序员的调用类型实例化为可执行的函数
2.类模板和模板类
1.类模板
一个类模板(类生成类)允许用户为类定义个一种模式,使得类中的某些数据成员、默认成员函数的参数,某些成员函数的返回值,能够取任意类型(包括系统预定义的和用户自定义的)。
一个类中的数据成员的数据类型不能确定,或者是某个成员函数的参数或返回值的类型不能确定,就必须将此类声明为模板,它的存在不是代表一个具体的、实际的类,而是代表一类类。
类模板的使用即为将一个类模板实例化为一个具体的类,格式:类名<实际的类型>
一个类定义中,只要有一个函数模板,则这个类是类模板;类模板的成员函数不全是是函数模板,当类模板实例化后,成员函数也随之实例化。
2.模板类
类模板实例化后的产物
五.覆盖
1.在派生类中覆盖基类中的同名函数,要求两个函数的参数个数、参数类型、返回类型都相同。
2.基类函数必须是虚函数
六.隐藏
1.定义
派生类中的函数屏蔽了基类中的同名函数
2.注意
1.2个函数名称相同,参数相同,但基类函数不是虚函数,父类函数被隐藏(和覆盖的区别在于基类函数是否是虚函数)。
2.2个函数名称相同,参数不同,无论基类函数是否是虚函数,基类函数都会被隐藏(和重载的区别在于两个函数不在同一类中)。
七.例题
1.有这样一个类: class Eye { public: void Look(void); }; 现在希望定义一个Head类,也想实现Look的功能,应该使用( )的方法,实现代码重用。
A 继承 B 组合 C 模板 D 多态
正确答案:B
2. 下面有关继承、多态、组合的描述,说法错误的是?
A. 封装,把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏
B. 继承可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展
C. 隐藏是指派生类中的函数把基类中相同名字的函数屏蔽掉了
D. 覆盖是指不同的函数使用相同的函数名,但是函数的参数个数或类型不同
正确答案: B D
3..下列关于模板的说法正确的是 ( )
A 模板的实参在任何时候都可以省略
B 类模板与模板类所指的是同一概念
C 类模板的参数必须是虚拟类型的
D 类模板中的成员函数不全是模板函数
正确答案: D