🤖 AI文章摘要 qwen-turbo-latest
加载中...

多态概述

C++多态

函数重载

函数重载是只要同名函数的参数列表不同就表示不同的函数, 使用的时候通过传递的参数区分不同的函数.

  void test(int a , int b=10){}
void test(int a){}
test(10);//当只传一个10时, 两个函数都是合法的, 产生了二义性
- 常引用的函数和普通引用的函数的重载
void test(int &a){}
void test(const int &a){}
test(10);编译器是能区分这两个函数的
  
  • 重载原理: 相同的函数名通过不同的参数列表实现了复用, 其实编译器编译的时候偷偷换了其他名字, 用来区分不同的函数
    • 例如:void fun(){}换成 void _fun(){}, void fun(int a){}换成void _fun_int(int a){}
  • extern C关键字:基于函数重载的处理方式, 在源文件中定义的函数在编译后标识符会发生变化, 导致头文件的声明失效. C++引入extern "C" 修饰的函数声明或定义, 该关键字修饰的函数在编译期间不会更改标识符, 同时也不能进行重载. 值得注意的是, extern “C” 必须在全局范围内使用.
  extern "C" 单个函数声明或函数定义;
extern "C"{
	多个函数声明或函数定义;
}
----------------------------
extern "C"关键字一般使用宏操作来让编译器自行判断是否添加
#ifdef   __cplusplus
extern "C"{
#endif

/*函数声明或函数定义*/

#ifdef   __cplusplus
}
#endif
  
  • 注意事项: 当函数有默认值的时候可能会产生二义性

运算符重载

C++提供了符号重载, 拓展自定义数据类型的运算操作.

  语法格式: returnType operator@(参数){函数定义} //注: 其中的@表示运算符
- 写在全局作用域:
	a.  n元运算符有n个参数,至少一个自定义数据类型
	b.  使用方式:  XXX @ YYY 等价于调用了个函数   operator@(XXX, YYY)
	c.  所有算术运算符, 比较运算符, 建议全局定义
	d.  不能访问私有成员(添加友元函数解决)
  
- 写在类作用域:
	a.  n元运算符有n-1个参数, 数据类型不做要求 
	b. 使用方式:  对象 @ XXX 等价于调用了类方法 对象.operator@(XXX)
	c.  所有单目运算符, 赋值运算符, 建议类方法定义.  = , () , [ ] , - , *  必须定义为成员函数  
	d.  可以访问私有成员
  
  • 注意事项
    • 一元运算符,按照C语言要求的位置放就好了. 自增自减有前置和后置区分, 移步自增自减运算符重载
    • 不支持参数全为基本数据类型的符号重载, 不支持三目运算符重载
    • 不滥用符号重载, 涉及类操作时, 代码更好写,可读性更强时才用
    • 不要重载 &&和|| ,因为他们有短路特性, 重载之后会消失

虚函数重写

  虚函数是无形的, 形式意义上的函数. 只能在非构造函数声明前加上virtual关键字. 虚函数可以不定义而直接赋值为0, 这种虚函数称为纯虚函数, 拥有纯虚函数的类为抽象类, 不能实例化. 在继承关系中, 子类可以重写(覆盖)父类中的虚函数, 但要保证重写函数的函数声明完全一致.

  虚函数具有一个显著特点, 当基类指针或引用指向一个派生类对象, 且调用的是虚函数会选择调用派生类重写的虚函数. 在传统的静态地址绑定中, 函数地址会在编译阶段被确定下来, 无法根据具体的对象类型调用其特定的函数. 虚函数则实现了函数地址的运行期间动态绑定.

  编译器为每一个拥有或继承拥有虚函数的类准备了一个虚函数表vf_table, 它记录着这个类拥有或继承拥有的虚函数入口地址. 若派生类发生重写(覆盖), 会在自己的虚函数表中地址替换为重写虚函数的入口地址. 拥有或继承拥有虚函数的类, 其内存空间会存在一个虚函数表指针, 并在构造函数中将虚函数表指针指向自己的虚函数表. 当指针调用的是该类的虚函数时, 会走该对象的虚函数表指针访问虚函数表来获取具体函数地址并执行. 虚函数是通过虚函数表指针以及虚函数表实现的, 不像普通函数一样早绑定直接调用, 性能上肯定比普通函数要慢一点, 但是虚函数带来的扩展性对开发效率的提升是非常重要的.

  虚函数的使用场景: 在动态多态中, 基类指针或引用指向一个派生类对象, 且调用的是虚函数, 编译器会根据派生类的类型调用重写的虚函数. 基类的虚函数其实是不起作用的, 它可以根据具体的派生类对它进行重写实现扩展, 而且原来的代码也没有改动. 也可以发现当派生类没有重写虚函数时, 这个虚函数时毫无意义的, 还不如直接函数效率更高些. 开发秉承着"对修改关闭, 对扩展开放"的原则来提高开发的效率. 虚函数能通过重写进行扩展(功能更新), 而不需要对原来的 代码进行修改, 这样可以大大提高开发效率.

  • 注意事项
    • 虚函数重写时可不使用virtual关键字修饰 , 但为了代码可读性是建议加上的
    • 基类中的纯虚函数其派生类未全部重写的话, 派生类仍为抽象类, 不能实例化.
    • 含有虚函数的基类其析构函数应该声明为虚函数, 因为通过指向派生类对象的基类指针删除派生类对象时, 确保正确调用派生类的析构函数.
    • 虚析构函数其派生类不能被重写, 且必须由本类实现. 纯虚析构函数既不能重写也不能定义而产生矛盾, C++通过类外定义解决, 纯虚函数的作用在于将一个未确定是否使用动态多态的类声明为抽象类.
    • 含有纯虚析构函数的类, 纯虚析构不能被子类重写也必须要定义, 所以只能使用作用域运算符在全局范围内去定义
  class abstractClass
{
public:
	abstractClass(){}
	virtual void  virtual_funtion(void)
	{
		cout << "父类的虚函数" << endl;
	}
};
class concreteClass : public abstractClass
{
public:
	concreteClass(): abstractClass(){}
	virtual void virtual_funtion(void)
	{
		cout << "子类的虚函数" << endl;
	}
};

int main(void)
{
	abstractClass* a = new concreteClass;
	a->virtual_funtion();
	/*虚函数的手动调用过程. 如果你记忆力够强, 完全可以手动调用指定的虚函数.
	获取虚函数表指针int* vtptr = (int*)*(int*)a
	获取虚函数指针,i表示虚函数表第i个元素void(*vfptr)(void) = (void(*)(void))*(vtptr+i)
	通过虚函数调用 vfprt();
	*/
}
  

对象的转型

  • 静态类型转换: static_cast<Type>(xx)
    • 允许基本数据类型间强转
    • 允许指针向void*类型强转
    • 允许类的指针或引用向上或向下转型
  • 动态类型转换: dynamic_cast<Type>(xx) 用于基类或派生类指针或引用相互转型
    • 允许类的指针或引用向上转型, 允许含有虚函数的基类的指针向下转型, 成功转型返回相应地址, 否则返回NULL
  • 重新解释转换: const_cast<Type>(xx) 相当于强转