文章目录
联编
联编,binding,的任务是:确定程序调用每一个函数时具体去执行哪一个代码块。
联编有两种类型,各有千秋各有用途。
大部分联编工作在编译时进行,比如C就是通过把函数名作为地址,直接找到目标代码块的。C++由于有函数重载,不能只靠函数名,还要看参数类型和数目,即特征标,但是编译器一般会做名称修饰,所以也在编译期间就可以确定到目标代码位置。
但有时候也需要在运行时进行确定代码块位置,比如上一篇文章的多态特性展示,遍历一个基类指针数组的指针,调用虚函数ViewAcct,当指针指向的对象是基类对象时则程序调用Brass::ViewAcct()方法,指针指向的对象是派生类对象时,就调用BrassPlus::ViewAcct()方法。于是,必须在运行过程中才能确定要为该调用执行哪一块代码。
静态联编static binding or 早期联编early binding
当使用对象的指针或者引用去调用非虚成员函数时,使用静态联编,因为是根据指针和引用的类型来判断调用的方法是哪一个,因此编译时就可以确定。
静态联编的效率更高,因为不需要设置一些跟踪指针用于跟踪动态变量或者对象,不会增加额外的处理开销。
所以静态联编是C++的默认选择。
动态联编dynamic binding or 晚期联编late binding
当使用对象的指针或者引用去调用虚成员函数时,使用动态联编,因为不是根据指针和引用的类型来判断调用的方法是哪一个,而是要根据指向的对象的类型,对象的类型在运行时才可以确定,因为有可能会有隐式类型准换,因此编译时不可以确定。
- 如果类不会被用作基类,那就不会用到动态联编;
- 如果类被用作基类,但是派生类并没有重写基类的任何方法,那么也不需要用到动态联编。
- 所以动态联编只有在需要用时才用。而虚函数也只是在程序需要时才使用。比如不应该把在派生类中不被重写的方法声明为虚函数,只把要重写的定义为虚函数。但是,毕竟写基类的时候不能预知会不会被重写,所以说类的设计经常需要反反复复,不是一次成型。
把基类的方法设置为虚方法,就会启动动态联编
用对象的指针和引用去调用方法(虚成员方法)
虚函数,或者虚方法,是虚成员函数的简称。即虚函数一定是成员函数,友元函数等不能被定义为虚函数。
虚函数为什么得此名呢?那是因为虚函数真的很“虚空”,“虚无”,基类的虚函数定义可以在派生类中重写,所以基类的虚函数的定义就“虚无”了, “隐匿”了,本来有那个定义,但是在重写了该函数的派生类看来又是没有那些定义的,只有自己刚写的新定义,所以就似有还无,似无还有,虚无缥缈,故得此名。(此处推测纯属个人开脑洞,反正能帮助理解且说的通就是了)
先复习C++对类型一致性的严格要求(类型转换)
最近一直在说,C++不允许把一种类型的地址赋给另一个类型的指针,也不允许把一个类型的引用指向另一个类型
double x;
long * pl = &x;//报错
int & = x;//报错
向上强制转换upcasting 和 向下强制转换downcasting
- 向上强制转换upcasting (隐式)
但是,继承第一篇文章说过了,遇到基类和派生类时可以有个单向的例外:即基类的指针和引用可以指向派生类对象。
BrassPlus bp;
Brass * p = &bp;
Brass & r = bp;
这种把派生类对象的指针和引用转换为基类的指针和引用的过程实际上是一种隐式类型转换,且是向上强制类型转换,upcasting,这使得公有继承无需进行强制类型转换了。
示例 用指针或引用调用虚成员方法
//main.cpp
#include <iostream>
#include "Brass.h"
#include "BrassPlus.h"
const int NUM = 2;
void eatline();
void fr(Brass &);
void fp(Brass *);
void fv(Brass);
int main()
{
using std::cout;
using std::cin;
Brass ross("Ross Galler", 123786, 5600.0);//基类对象
BrassPlus mona("Mona White", 467838, 4500.0);//派生类对象
//按引用传递
fr(ross);//Brass 给Brass &
fr(mona);//BrassPlus 给Brass &,隐式向上强制转换
//按指针传递
std::cout << '\n';
fp(&ross);//Brass *给Brass *
fp(&mona);//BrassPlus *给Brass *,隐式向上强制转换
//按值传递
std::cout << '\n';
fv(ross);//Brass给Brass,调用复制构造函数Brass(const Brass &)
std::cout << '\n';
fv(mona);//BrassPlus给Brass,相当于只是把mona中的Brass类对象传进去了,,调用复制构造函数Brass(const Brass &)
std::cout << '\n';
return 0;//析构ross和mona对象,后者会先调用~BrassPlus,然后在其中调用~Brass
}
void eatline()
{
while (std::cin.get() != '\n')
;
}
void fr(Brass & r){
r.ViewAcct();}
void fp(Brass * p){
p->ViewAcct();}
void fv(Brass v){
v.ViewAcct();}
当Brass::ViewAcct()不是虚方法,三种传递方式都是只调用基类方法Brass::ViewAcct()
//按引用传递
fr(ross);//Brass::ViewAcct()
fr(mona);//Brass::ViewAcct()
//按指针传递
fp(&ross);//Brass::ViewAcct()
fp(&mona);//Brass::ViewAcct()
//按值传递
fv(ross);//Brass::ViewAcct()
fv(mona);//Brass::ViewAcct()
Client: Ross Galler
Account number: 123786
Balance: $5600.00
Client: Mona White
Account number: 467838
Balance: $4500.00
Client: Ross Galler
Account number: 123786
Balance: $5600.00
Client: Mona White
Account number: 467838
Balance: $4500.00
Client: Ross Galler
Account number: 123786
Balance: $5600.00
In ~Brass()
Client: Mona White
Account number: 467838
Balance: $4500.00
In ~Brass()
In ~BrassPlus()
In ~Brass()
In ~Brass()
当Brass::ViewAcct()是虚方法,可以看到按指针和按引用都使用了BrassPlus::ViewAcct(),但是按值传递仍然使用了Brass::ViewAcct()
//按引用传递
fr(ross);//Brass::ViewAcct()
fr(mona);//BrassPlus::ViewAcct()
//按指针传递
fp(&ross);//Brass::ViewAcct()
fp(&mona);//BrassPlus::ViewAcct()
//按值传递
fv(ross);//Brass::ViewAcct()
fv(mona)