C++中,空指针是不能够使用的,然而空对象指针有时候却能够调用成员函数。
先来看一下以下程序:
#include<iostream>
#include<string.h>
using namespace std;
class A
{
public:
static void f1(){ cout<<"f1"<<endl; }
void f2(){ cout<<"f2"<<endl; }
void f3(){ cout<<num<<endl; }
virtual void f4() {cout<<"f4"<<endl; }
public:
int num;
};
int main(int argc,char* argv[])
{
A* pa = NULL;
pa->f1(); //正常
pa->f2(); //正常
pa->f3(); //错误,提示段错误
pa->f4(); //错误,提示段错误
return 0;
}
空指针对f1和f2的调用正常,对f3和f4的调用会出错,为什么呢?
要理解这个的话,成员函数其实可以认为是一个普通的函数,比如
class A{
public:
void func(int x) { cout<<"hello, A. x="<<x<<endl; }
};
在编译器看来,大概就长这个样子吧:
void A_func(A* this, int x) { cout<<"hello, A. x="<<x<<endl; }
你平时使用成员函数的时候,大概就是这样的:
A a;
a.func(2);
其实在编译器看来,是这个样子的:
A_func(&a, 2);
如果这么说的话,也许你就理解了,为什么对象是NULL的时候还可以调成员函数:
A *pa = NULL;
pa->func(2);
//在编译器看来就好像是 A_func(pa, 2);且pa==NULL
((A*)NULL)->func(2);
//在编译器看来就好像是 A_func( ((A*)NULL), 2);
上面的例子中func函数里并没有使用成员变量。考虑有成员变量并且在成员函数里使用的情况,就会不一样了:
class A{
private:
int y;
public:
void func(int x) { y = x; }
};
注意此时y是成员变量,编译器会自动给它加上this->,也就是:
void A_func(A* this, int x) { this->y = x; }
此时正常的情况就不用说了,说说用NULL对象指针调用成员函
数的情况:
A *pa = NULL;
pa->func(2);
//在编译器看来就好像是 A_func(pa, 2);且pa==NULL
((A*)NULL)->func(2);
//在编译器看来就好像是 A_func( ((A*)NULL), 2);
//好吧我承认这段代码跟上面的一毛一样啦!
此时程序会崩溃!为什么?因为this指针是NULL,而你访问了它的y变量!
从上面的分析及程序测试可以看出:
1)NULL对象指针可以调用成员函数
2)通过对象调用成员函数,对象的指针会被传入函数中,指针名称为this
3)NULL对象指针调用成员函数时,只要不访问此对象的成员变量,则程序正常运行
4)NULL对象指针调用成员函数时,一旦访问此对象的成员变量,则程序崩溃
经过上边的实验在来看一下文章开始的例子,分析一下原因:
类的成员函数并不与具体对象绑定,所有的对象共用同一份成员函数体,当程序被编译后,成员函数的地址即已确定,这份共有的成员函数体之所以能够把不同对象的数据区分开来,靠的是隐式传递给成员函数的this指针,成员函数中对成员变量的访问都是转化成"this->数据成员"的方式。因此,从这一角度说,成员函数与普通函数一样,只是多了一个隐式参数,即指向对象的this指针。而类的静态成员函数只能访问静态成员变量,不能访问非静态成员变量,所以静态成员函数不需要指向对象的this指针作为隐式参数。
有了上面的分析,就可以解释为什么空对象指针对f1, f2的调用成功,对f3的调用不成功。
f1是静态成员函数,不需要this指针,所以即使pa是空指针,也不影响对f1正常调用。
f2虽然需要传递隐式指针,但是函数体中并没有使用到这个隐式指针,也就是说没有通过这个隐式指针去使用非静态的成员变量,所以pa为空也不影响对f2的正常调用。
f3就不一样了,因为函数中使用到了非静态的成员变量,对num的调用被转化成this->num,也就是pa->num,而pa是空指针,因此pa->num非法,对f3的调用出错。
f4中并没有使用非静态成员变量,为什么调用也会出错呢,原因在于f4是虚函数,有虚函数的类会有一个成员变量,即虚表指针,当调用虚函数时,会使用虚表指针,对虚表指针的使用也是通过隐式指针使用的,因此对f4的调用也就会出错了。