《C++新经典对象模型》之第5章 函数语义学

《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>
#
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值