看个问题:
Point3d obj;
Point3d *ptr = &obj;
//下面这个调用会发生什么事?
obj.normalize();
ptr->normalize();
/*
Point3d normalize() const
{
register float mag = magnitude();
Point3d normal;
normal._x = _x / mag;
normal._y = _y / mag;
normal._z = _z / mag;
return normal;
}
*/
答案是不知道!C++支持三种成员函数类型:静态、非静态和虚函数。每个类型的调用方式都不相同,我们不确定normalize()和magnitude()两函数是否为虚函数,不过可以肯定它们不是静态的,因为它们能直接存取非静态数据而且还被声明为const的,这些是静态不能做到的。
成员的各种调用方式
冷知识:虚函数在20实际80中期被引入,且受到许多质疑;静态成员函数在80年代末被引入,是最后引入的函数类型
调用非静态成员函数
C++设计准则之一就是让调用非静态成员函数效率和调用普通函数一样高。看看下面的函数:
//成员函数
float Point3d::magnitude3d()
{
return sqrt(_x * _x +
_y * _y +
_z * _z);
}
//普通函数
float magnitude3d(const Point3d *this)
{
return sqrt(this->_x * this->_x +
this->_y * this->_y +
this->_z * this->_z);
}
肯定要有人说:这看起来调用成员函数应该比普通函数快啊?因为普通函数要间接的通过参数调用成员,而成员函数可以直接调用。事实上就算是成员函数,在内部也是被转化为调用普通函数的形式,转化过程如下:
- 改写函数的原型。插入一个额外的参数到成员函数中用来提供一个存取的通道,使得类对象可以调用这个函数,这个额外参数就是this指针:
//给成员函数插入一个this指针
float Point3d::magnitude3d(Point3d *const this)
- 对每个非静态成员的存取操作都变成由this指针来做
{
return sqrt(this->_x * this->_x +
this->_y * this->_y +
this->_z * this->_z);
}
- 将成员函数重新写成一个 外部函数,然后对函数名进行名称粉碎,避免二义性
extern magnitude_7Point3dFv(register Point3d *const this);
现在函数被转化完成,调用操作也必须转化:
obj.magnitude()变成了magnitude_7Point3dFv(&obj)
ptr->magnitude()变成了magnitude_7Point3dFv(ptr)
名称特殊处理(mangling)
如果派生类继承一个基类,然后两个类中都有一个同名的数据成员该怎么办?可以共存,就是通过name mangling的方式区分。
更难一点,成员函数怎么区分?成员函数可以被重载,要区分那就更难了,所以需要更广泛的mangling手法来提供独一无二的名称。为了让它们都独一无二的名称,会再加上它们的参数链表(从函数原型中参考得到)
调用虚成员函数
如果normalize是虚函数,那么下面这个调用会被在内部被转换:
ptr->normalize();
//转换如下
(*(ptr->vptr[1]))(ptr);
调用自己类中的vptr,然后vptr指向vtbl中1索引的那个函数,注意第二个ptr表示的是this指针。
然后调用magnitude,因为magnitude是在normalize中被调用的,而后者已经由虚拟机制决议妥当,所以明确调用Point3d实体会比较有效率,并因此压制由于虚拟机制而产生的不必要的重复调用操作:
//明确的调用可以压制虚拟机制
register float mag = Point3d::magnitude();
调用静态成员函数
//假设normalize是静态成员函数
obj.normalize();
ptr->normalize();
//这两个函数调用会被转为一般的非成员函数调用
//obj.normalize()转换如下
normalize_7Point3dSFv();
//ptr->normalize()转换如下
normalize_7Point3dSFv();
在C++引入静态成员函数之前会有以下这样怪异的写法:
((Point3d*)0)->object_count();
引入静态成员函数之前C++要求所有成员函数都必须由类的对象来调用,但实际上只有成员函数中存在数据成员的存取时我们才真正需要类对象。类对象是通过this指针给这种形式的函数使用,还是刚才讲的,如果函数中没有数据成员被存取,那也不需要this指针。这样一来存取静态数据成员时就会产生一些问题,比如如果不把静态数据成员设为public,那就必须提供一或多个成员函数来存取该成员,现在虽然可以不用通过对象存取该静态成员,但是存取该成员的函数还是在对象之中。
所以独立于对象之外的存取操作在某些时候就比较重要,程序员们希望支持不需要对象存在的情况,
静态成员函数应运而生,主要特性就是没有this指针、不能被声明为const或虚、不需要由对象调用。在程序方法上就是很奇特的把0强转为一个class指针,当作this
object_count((Point3d*)0);
静态成员函数会被提出于类声明之外,并经过名称粉碎,比如:
unsigned int Point3d::object_count()
{
return _object_count;
}
//会被转换为下面这样
unsigned int object_count_5Point3dSFv()
{
return _object_count_5Point3d;
}
//SFv表示它是个静态成员函数,拥有一个void的参数列表
如果取一个静态成员函数的地址,获得的是其在内存中的位置,也就是其地址。
虚函数
之前讲过一个模型,每个类都有一个vtbl,内含类中所有虚函数的地址;然后每个对象都有一个vptr,指向自己类的vtbl。
指向成员函数的指针
我们可以这样定义并初始化成员函数指针:
x的返回值 (Point::*coord)(x的参数列表) = &Point::x;
//然后这样调用该指针
(origin.*coord)(参数列表);
(ptr->*coord)(参数列表);
支持指向虚成员函数的指针
有以下代码:
float (Point::*pmf)() = &Point::z;
Point *ptr = new Point3d;
//下面两个调用一样吗?
ptr->z();
(ptr->*pmf)();
ptr->z()
可以调用Point3d的z(),(ptr->*pmf)()
可以吗?可以!那么是怎么实现的呢?
对一个非静态成员函数取地址可以得到该函数在内存中的地址,而对一个虚函数取地址,其地址在编译时期未知,但是可以得到虚函数在vtbl中的索引值。
class Point
{
public:
virtual ~Point();
float x();
float y();
virtual float z();
};
这里取~Point()的地址会得到1,取z()会得到2。通过pmf来调用z(),会被内部转化为一个编译时期的式子,形势如下:
//(ptr->*pmf)()
(*ptr->vptr[(int)pmf])(ptr);
但是对一个指向成员函数的指针评估求值会因为该值有两种意义而复杂化,比如这个pmf的定义:
float (Point::*pmf)();
//这个定义可以匹配两个函数,一个不虚的float x()