《C++新经典对象模型》之第5章 函数语义学
5.1 普通成员函数调用方式
成员函数与全局函数一样,有独立的内存地址,编译时就确定好了。
编译器内部会将对成员函数的调用转换成一种对全局函数的调用。
struct MYACLS
{
int m_i;
void myfunc(int abc)
{
m_i += abc;
}
};
//编译器视角
void myfunc(MYACLS *const this, int abc)
{
this->m_i += abc;
}
编译器会对成员函数名进行适当的处理。
05.01.cpp
#include <iostream>
#include <time.h>
#include <cstdio>
using namespace std;
struct MYACLS
{
int m_i;
void myfunc(int abc)
{
m_i += abc;
}
};
// 全局函数gmyfunc
void gmyfunc(MYACLS *ptmp, int abc)
{
ptmp->m_i += abc;
}
int main()
{
{
MYACLS myacls;
myacls.myfunc(18); // 调用成员函数
gmyfunc(&myacls, 18); // 调用全局函数
printf("MYACLS::myfunc地址=%p\n", &MYACLS::myfunc);
}
cout << "Over!\n";
return 0;
}
5.2 虚成员函数与静态成员函数调用方式
5.2.1 虚成员函数调用方式
struct MYACLS
{
virtual void myvirfunc()
{
printf("myvirfunc()被调用,this = %p\n", this);
// myvirfunc2();//通过虚函数表
MYACLS::myvirfunc2();//直接调用,压制住了虚拟机制
}
virtual void myvirfunc2()
{
printf("myvirfunc2()被调用,this = %p\n", this);
}
};
MYACLS myacls;
myacls.myvirfunc();//普通成员函数调用,非多态
MYACLS* pmyacls = new MYACLS;
pmyacls->myvirfunc();//虚函数表指针找到虚函数表,然后找到虚函数地址,调用
//等价于(*pmyacls->vptr[0])(pmyacls);
delete pmyacls;
5.2.2 静态成员函数调用方式
类名,对象名或者对象指针来调用静态成员函数,都一样,转换为普通成员函数调用,且不会插入this形参。
struct MYACLS
{
int m_i;
void myfunc(int abc)
{
// m_i += abc;
mystfunc(); // 这绝对没有问题
}
virtual void myvirfunc()
{
printf("myvirfunc()被调用,this = %p\n", this);
// myvirfunc2();
MYACLS::myvirfunc2();
}
virtual void myvirfunc2()
{
printf("myvirfunc2()被调用,this = %p\n", this);
}
static void mystfunc()
{
printf("mystfunc()被调用\n");
}
};
MYACLS myacls;
myacls.mystfunc();
MYACLS *pmyacls = new MYACLS;
pmyacls->mystfunc();
((MYACLS *)0)->mystfunc(); // 这样调用静态成员函数没问题
((MYACLS *)0)->myfunc(12);//未使用12可以这样调用
printf("MYACLS::mystfunc()地址 = %p\n", MYACLS::mystfunc);
静态成员函数特性:
(1)无this指针。
(2)无法存取类中普通的非静态成员变量。
(3)末尾不能增加const,也不能设置virtual。
(4)可用类对象来调用。
(5)等同于全局函数,可作为回调函数。
05.02.cpp
#include <iostream>
#include <time.h>
#include <cstdio>
using namespace std;
struct MYACLS
{
public:
int m_i;
void myfunc(int abc)
{
// m_i += abc;
mystfunc(); // 这绝对没有问题
}
virtual void myvirfunc()
{
printf("myvirfunc()被调用,this = %p\n", this);
// myvirfunc2();
MYACLS::myvirfunc2();
}
virtual void myvirfunc2()
{
printf("myvirfunc2()被调用,this = %p\n", this);
}
static void mystfunc()
{
printf("mystfunc()被调用\n");
}
};
// 全局函数gmyfunc
void gmyfunc(MYACLS *ptmp, int abc)
{
ptmp->m_i += abc;
}
int main()
{
{
MYACLS myacls;
myacls.myvirfunc();
MYACLS *pmyacls = new MYACLS;
pmyacls->myvirfunc();
delete pmyacls;
}
{
MYACLS myacls;
myacls.mystfunc();
MYACLS *pmyacls = new MYACLS;
pmyacls->mystfunc();
((MYACLS *)nullptr)->mystfunc(); // 这样调用静态成员函数没问题
((MYACLS *)0)->myfunc(12);
printf("MYACLS::mystfunc()地址 = %p\n", MYACLS::mystfunc);
}
cout << "Over!\n";
return 0;
}
5.3 虚函数地址问题的vcall引入
虚函数在内存中也有固定地址,编译时已确定。
class MYACLS
{
public:
virtual void myvirfunc1()
{
cout << "虚函数MYACLS::myvirfunc1()执行了" << endl;
}
virtual void myvirfunc2()
{
cout << "虚函数MYACLS::myvirfunc2()执行了" << endl;
}
};
//msvc编译器通过一系列的vcall函数(唯一对应虚函数),跳转到真正的虚函数去。
//输出vacll函数地址,不是真正的虚函数地址
printf("MYACLS::myvirfunc()地址 = %p\n", &MYACLS::myvirfunc1);
printf("MYACLS::myvirfunc()地址 = %p\n", &MYACLS::myvirfunc2);
cout << sizeof(MYACLS) << endl; // 8字节
vcall thunk,一段代码或者一段函数,两个作用:
(1)调整this指针。
(2)跳到真正的虚函数去。
05.03.cpp
#include <iostream>
#include <time.h>
#include <cstdio>
using namespace std;
class MYACLS
{
public:
virtual void myvirfunc1()
{
cout << "虚函数MYACLS::myvirfunc1()执行了" << endl;
}
virtual void myvirfunc2()
{
cout << "虚函数MYACLS::myvirfunc2()执行了" << endl;
}
};
int main()
{
{
printf("MYACLS::myvirfunc()地址 = %p\n", &MYACLS::myvirfunc1);
printf("MYACLS::myvirfunc()地址 = %p\n", &MYACLS::myvirfunc2);
cout << sizeof(MYACLS) << endl; // 8字节
MYACLS *pmyobj = new MYACLS();
pmyobj->myvirfunc1();
pmyobj->myvirfunc2();
delete pmyobj;
}
cout << "Over!\n";
return 0;
}
5.4 静动态类型、绑定、坑点与多态体现深谈
5.4.1 静态类型和动态类型
静态类型:对象定义时的类型,编译期间就确定了。
Base base; //静态类型Base
Derive derive; //静态类型Derive
Base *pbase; //无论指向,定义时已确定,静态类型Base*
Base *pbase2 = new Derive();//静态类型Base*
Base *pbase3 = new Derive2();//静态类型Base*
动态类型:对象目前指向的类型(运行时决定)。只有指针或引用才有动态类型,通常值父类的指针或引用。
- base和derive无动态类型,非指针非引用。
- pbase无动态类型,没有指向对象。
- pbase2动态类型Derive。
- pbase3动态类型Derive2.
动态类型执行过程中可改变。
pbase = pbase2; //pbase动态类型Derive
pbase = pbase3; //pbase动态类型Derive2
5.4.2 静态绑定和动态绑定
静态绑定:绑定的是静态类型,所对应的函数或属性依赖于对象的静态类型,发生在编译期。
动态绑定:绑定的是动态类型,所对应的函数或属性依赖于对象的动态类型,发生在运行期。
(1)普通成员函数是静态绑定,而虚函数是动态绑定。
(2)缺省函数一般是静态绑定。
5.4.3 继承的非虚函数坑
class Base
{
public:
void myfunc()
{
cout << "Base::myfunc()" << endl;
}
};
class Derive : public Base
{
public:
void myfunc()
{
cout << "Derive::myfunc()" << endl;
}
};
Derive derive;
Derive *pderive = &derive;
pderive->myfunc(); // Derive::myfunc()
Base *pbase = &derive;
pbase->myfunc(); // Base::myfunc(),这里调用的居然是父类的myfunc,是个陷阱,写程序时一定要注意
普通成员函数是静态绑定,具体执行哪个myfunc成员函数取决于调用者的静态类型。
不应该在子类中重新定义一个继承来的非虚成员函数。
5.4.4 虚函数的动态绑定
class Base
{
public:
virtual void myvirfunc()
{
cout << "Base::myvirfunc()" << endl;
}
};
class Derive : public Base
{
public:
virtual void myvirfunc()
{
cout << "Derive::myvirfunc()" << endl;
}
};
Base base;
Derive derive;
Derive *pderive = &derive;
pderive->myvirfunc(); // Derive::myvirfunc()
Base *pbase = &derive;
pbase->myvirfunc(); // Derive::myvirfunc()
pbase = &base;
pbase->myvirfunc(); // Base::myvirfunc()
虚函数是动态绑定,myvirfunc具体执行取决于调用者的动态类型。
5.4.5 重新定义虚函数的缺省参数坑
class Base
{
public:
virtual void myvirfunc(int value = 1)
{
cout << "Base::myvirfunc(), value=" << value << endl;
}
};
class Derive : public Base
{
public:
virtual void myvirfunc(int value = 2)
{
cout << "Derive::myvirfunc(), value=" << value << endl;
}
};
Base base;
Derive derive;
Derive *pderive = &derive;
pderive->myvirfunc(); // Derive::myvirfunc(), value=2
Base *pbase = &derive;
pbase->myvirfunc(); // Derive::myvirfunc(), value=1
pbase = &base;
pbase->myvirfunc(); // Base::myvirfunc(), value=1
缺省参数是静态绑定,考虑执行期间效率问题,好实现。
不要在子类中重新定义虚函数缺省参数的值。
5.4.6 C++中的多态性
多态必须存在虚函数且调用虚函数。
(1)代码实现上
调用虚函数时,通过查询虚函数表找到虚函数入口地址,然后去执行的路线,就是多态。无论是否继承,有无派生。
class A
{
public:
virtual void myvirfunc(){
}
};
A *pa=new A();
pa->myvirfunc();//多态
A a;
a.myvirfunc();//非多态
A *ya=&a;
ya->myvirfunc();//多态
(2)表现形式上(最终也通过代码体现)
- 有父类和子类(继承关系),父类必须含有虚函数,子类重写父类虚函数。
- 父类指针指向子类对象或者父类引用绑定(指向)子类对象。
- 父类指针或引用调用子类重写的虚函数。
Derive derive;
Base *pbase = &derive;
pbase->myvirfunc(); // Derive::myvirfunc()
Base *pbase2 = new Derive();
pbase2->myvirfunc(); // Derive::myvirfunc()
delete pbase2;
Derive derive2;
Base &yinbase = derive2;
yinbase.myvirfunc(); // Derive::myvirfunc()
05.04.cpp
#include <iostream>
#include <time.h>
#