文章目录
前言
多态实际上是为了开发的便利设计的,可以理解为一个函数根据传入参数的不同有不同的实现->一个函数有多种状态,及多态。简单来说就是一个接口多种实现。
- 一个类内 函数同名参数不同->函数重载
- 命名倾轧:对函数名结合参数重命名 C中不支持,C++支持
- 父子类:一般来说 函数重写就是虚函数重写 为的是实现多态调用
- 1.函数同名参数相同
- 虚函数重写->多态
- 基类有virtual且无static->同名覆盖 即函数重写/覆盖
- 可通过::访问被隐藏的父类同名成员
- 有virtual则为虚函数
- 纯虚函数就是虚函数=0
- 纯虚函数并不实现具体函数,此时子类的同名成员函数强制实现
- 非虚函数重写->重定义的一种形式
- 无virtual->函数重定义/隐藏
- 虚函数重写->多态
- 2.函数同名参数不同->函数重定义/隐藏
- 1.函数同名参数相同
一、清晰几个概念
1.继承
- 继承是面向对象中代码复用的重要手段
- 语法形式如下
class 派生类名:基类名表
{
数据成员和成员函数声明
};
//基类名表构成
访问控制 基类名1,,访问控制 基类名2,...,基类名n
//访问控制表示派生类对基类的继承方式,使用关键字:
public 公有继承0父类成员在子类中不变
private 私有继承-父类成员在子类中变为private成员
protected 保护继承-父类的public->protected,protected和private不变
- 示例:
//描述继承关系 表示C的类继承基类A和B
class C: public A,B
2.基类与派生类
- 被继承的类叫基类(或父类),继承后的类叫派生类(或子类)
- 上面的示例中,C为子类/派生类,A和B为父类/基类
- 派生类不能直接使用基类的私有成员
- 基类是特殊的父类
3.同名覆盖
- 父子间的同名成员冲突->同名覆盖
- 子类可以使用作用域分辨符
::
访问父类中的同名成员 - 子类的函数将隐藏父类的同名函数
- 函数重写是同名覆盖的一种特殊情况
二、多态的分类
1.静多态
在编译阶段确定,通过函数重载实现
1.1函数重载
函数同名但不同参,同一作用域中(即同一个类中)
- 返回值类型可以不同
void func(int a){cout <<"a = "<<a<<endl;}
void func(double a){cout <<"a = "<<a<<endl;}
void func(int a,char b){cout <<"a = "<<a<<"b = "<<b<<endl;}
- 原理:编译过程把同名函数进行命名倾轧,例如
//定义:
void func(int a ){}
// 编译过程:函数名->函数名_参数
void func_i(int a){}
- 故而在C中是不支持函数重载的,在C语言中,C编译器仅对函数名简单的重命名
- 编译过程就进行了命名倾轧,即对函数名结合参数重命名
1.2一些概念:命名倾轧、重载二义性
1.2.1命名倾轧
通过上面你可以了解到,命名倾轧即编译过程对函数名重命名。
C++的定义和使用需要对应统不倾轧和不倾轧,默认倾轧的
- 不能定义倾轧使用不倾轧,这就引申另外一个问题,兼容C时要设置不倾轧
extern "C"
原因:C库是在链接时加入(此时是不倾轧),而在C头文件中声明时默认使用倾轧,故是要将头文件的声明设置为不倾轧,以此与库中的相互对应。很多C的标准头文件都有extern "C"
1.2.2重载二义性-理解就行了
一句话就是编译器不知道调用哪个重载函数,即候选函数不唯一->二义性,不过编译器会提示的,不用担心~
来一些例子,出现了二义性
- 隐式转换
如double类型的可以隐式转换为float或者in
void func(int a) { cout << "a = " << a << endl; }
void func(float a) { cout << "a = " << a << endl; }
int main(void) {
//double向属int和float都可以转换,编译器不知道该调用那个
func(-4.4);
}
- 默认参数
class Box {
public:
void func(int a, int b = 0) {}
void func(int a) {}
};
int main()
{
Box box;
box.func(1);//存在默认参数,编译器不知道找哪个
return 0;
}
2.动多态
在运行阶段确定,通过函数重写实现
2.1函数重写
函数同名同参,不同作用域中(即基类与派生类中)
- 在2个类之间,其中基类有
virtual
关键字,派生类中可有可无,不能有static - 父类和子类发生重新的函数完全相同:返回类型+函数名+参数列表
- 会在运行时再去确定对象的类型以及正确的调用函数
2.2一些概念:虚函数、纯虚函数、重定义/隐藏
2.1.1.虚函数
- 成员函数加上关键字
virtual
就是虚函数 - 存在关键字
virtual
时,编译器使用迟绑定- 没有virtual关键字时,C++编译器在编译时就确定了哪个函数被调用,这叫做早期绑定
- 存在virtual关键字是,编译器使用迟绑定,会在运行时再去确定对象的类型以及正确的调用函数
- 释义:在某基类中声明为
virtual
并在一个或多个派生类中被重新定义的成员函数 - 用法格式为:
virtual
函数返回类型 函数名(参数表) {函数体}; - 调用:通过指向派生类的基类指针或引用,访问派生类中同名覆盖成员函数
- 定义与声明示例如下
class A
{
public:
virtual void print(){cout<<"This is A"<<endl;}
};
class B : public A
{
public:
void print(){cout<<"This is B"<<endl;}
};
- 调用示例
#include<iostream>
using namespace std;
int main()
{
//调用指针函数
A a = new A();
B b =new B();
b->print();//打印This is B
delete a;
a= NULL;
delete b;
b = NULL;
return 0;
}
2.1.2.纯虚函数
- 纯虚函数就是虚函数
=0
- 是一种特殊的虚函数
- 声明为纯虚函数则在基类中不能对虚函数给出有意义的实现,而把它的实现留给该基类的派生类去做
- 派生类中不需要实现该成员函数时 不要将基类的同名函数定义为纯虚函数,因为纯虚函数并不实现…
- 纯虚函数在的基类称为抽象类。
class A
{
public://纯虚函数并不实现具体函数
virtual void print() = 0;
};
class B : public A
{
public://此时派生类的同名成员函数强制实现
void print(){cout<<"This is B"<<endl;}
};
2.1.3.重定义/隐藏
- 只能出现在继承结构中
- 非虚函数中同名函数会进行重定义即同名覆盖
三、总结
- 函数重载
- 同一作用域中,同名函数参数不同
- 函数重写/覆盖
- 不同作用域中,同名函数参数相同+
virtual
- 不同作用域中,同名函数参数相同+
- 函数重定义/隐藏
- 不同作用域中,同名函数参数相同+没有
virtual
- 不同作用域中,同名函数参数相同+没有
实际上平时说的多态都是动多态,用虚函数实现,即用父类的指针引用子类的同名函数(函数重写)以实现统一接口,多次调用。
纯虚函数在虚函数的基础上,不实现父类的同名成员函数,仅实现子类的函数,主要是强制统一,这个根据实际判断需不需要纯虚函数