类的一个特征就是数据与方法封装在一起,在编译器越来越智能的今天,大多数程序员已经习惯了数据与方法“本来”就是一个“整体”的概念。事实是不是这样呢?在面向对象语言出现之前的程序世界又是什么样的呢?让我们先回到C的世界中,看看那里的数据与方法(C中称为函数)是什么样子的。
在C中通常定义一个结构体用来把一些数据组合在一块儿,然后定义一个函数处理这个结构体。例如:
/* 版本1:*/
{
int data1;
char data2[10];
}SampleData;
/*函数定义,打印SampleData结构的数据*/
{
SampleData data;
data.data1 = 12;
strcpy(data.data2,"123456789");
printf("data1=%d, data2=%s ", data.data1, data.data2);
}
可以看到数据与对数据的操作是分离的,只要把数据所在的文件包含进来,就可以任意定义一个函数来进行操作。
接着再看看用C++ 把数据与方法封装在一起的代码形式。
// C++ 版本
{
public:
SampleData(){};
~SampleData(){};
void ProcessData()
{
data1 = 12;
strcpy(data2,"123456789");
cout<<"data1="<<data1<<endl;
cout<<"data2="<<data2<<endl;
}
private:
int data1;
char data2[10];
};
C++ 可以把数据和对数据的操作整合到一个class 中。能否在C的 struct 中做到跟C++的 class 一样,把ProcessData() 与 数据变量整合在一起呢?模仿上面的类定义,我们可能会尝试定义一个这样的数据结构:
/* 版本2:*/
{
int data1;
char data2[10];
void ProcessData(); //声明操作
}SampleData;
而ProcessData() 函数的实现依然跟【版本1】一样。编译,链接。真好,没有出现任何的错误。是否这样就可以了呢?噢,忘记了一个重要的步骤,还没有在任何地方使用过这个SampleData结构体的ProcessData 操作呢。OK, 没有关系,在主函数声明调用一下吧。大概像下面这样:
{
SampleData data;
data.ProcessData();
}
再编译,通过。链接,出现错误:unresolved external symbol "public: void __thiscall _SampleData::ProcessData(void)" (?ProcessData@_SampleData@@QAEXXZ)。编译器找不到结构体中声明的ProcessData 函数的具体实现,看来现在这样调用data.ProcessData() 是不行的。编译器并没有我们想象的那样自动地把 ProcessData 的声明与它的实现对应起来。那么怎样做才能达到预先的目的呢?
如果能把ProcessData()函数的地址赋给 SampleData 结构里的操作声明,那么一切问题都解决了。看来【版本2】也需要改进一下。
/* 版本3:*/
{
int data1;
char data2[10];
void (*ProcessData)(); //注意,已经改为ProcessData的函数指针
}SampleData;
主函数也得改一下,在使用data.ProcessData() 之前,把函数ProcessData的地址赋给结构体里的函数指针变量。结果如下:
{
SampleData data;
data.ProcessData = ProcessData; //注意,初始化函数指针变量
data.ProcessData();
}
再编译、链接,终于通过。
到这里你已经认为这样成功的实现了上面定义的SampleData的C++ 版本。不过不要高兴的太早,做个测试看看。
{
SampleData data;
data.data1 = 22; // step 1
strcpy(data.data2 , "987654321"); // step 2
data.ProcessData = ProcessData; //step 3
data.ProcessData(); //step 4
}
按预先的设想,执行完step 4 后应该打印出这样的结果:data1=22, data2=987654321
而实际上打印出的是:data1=12, data2=123456789 。原因很明显,ProcessData 函数里面有个 SampleData的局部变量。也就是说,结构体里的方法data.ProcessData() 打印出的内容并不是结构体本身的变量,而是这个局部变量的内容。
那怎样才能打印出结构体内变量的内容呢?C++里编译器为类的方法加上了 this 指针,所以我们不用去担心这个问题。而在C的世界里,编译器并没有这样做。那么就需要显示的增加一个结构指针,代替C++中类似this指针的功能。接下来再改变一下SampleData结构体的定义:
/* 版本4:*/
{
int data1;
char data2[10];
void (*ProcessData)(struct _SampleData * thisSample);//增加了一个本身的指针参数
}SampleData;
函数的实现也需要修改,它看起来像下面这个样子:
{
printf("data1=%d, data2=%s ", thisSample->data1, thisSample->data2);
}
接着在主函数的调用方式也要修改:
看看运行结果,OK,这才是我们所想要的功能。而 thisSample这个指向自己的指针就是C++里this指针的实现形式,只不过在C里要自己实现,而在C++是编译器自动为我们加上了,看到这里你也应该已经理解C++的this指针的概念了吧。
看来在机器的世界里数据与对数据的操作是分开的,只是由于在编译器的支持下,才给程序员展现出了一个完美的对象。