原文链接:
https://blog.csdn.net/oyoung_2012/article/details/54601314
我们都知道C是一门面相过程的语言,在C的世界里是没有面相对象这个概念的,但是C语言为我们提供的神兵利器,仍旧可以让我们使用面相对象的思维方式
在C语言里,我们每做一个操作,都需要写一个函数,但是该函数都是过程化的,但是我们有两种神兵利器,一个叫指针,一个叫结构体
为什么这么说呢?
面相对象的最基本的功能就是对数据的封装,在C语言的世界里,我们有结构体这个法宝,同样可以将数据打包整整体,然后通过指针的方式,将结构体作为参数在函数中进行传递
举个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| struct Book { char name[32]; char author[32]; int total; int price; }; typedef struct Book Book;
void book_init(Book *book, const char *name, const char *author, int total, int price) { strcpy(book->name, name); strcpy(book->author, author); book->total = total; book->price = price; }
void book_update_price(Book *book, int newPrice) { book->price = price; }
|
以上的例子很简单,初始化一本书以及更新书价,在使用的时候,我们只需要像以下方式调用
1 2 3 4 5 6
| Book book; book_init(&book, "WPF 编程宝典", "Matthew MacDonald", 934, 128);
book_update_price(&book, 100);
|
以上的这些使用的都是面相过程的思维。
所谓面向过程编程,就好比“让某某去做某事”,而面向对象呢,就好比“某某去做某事”。从字面意思来看,面向过程貌似多了个“高级领导”,而面向对象显得更自由。
###我们再变一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| struct Book { char name[32]; char author[32]; int price; void (*init)(Book *book, const char *name, const char *author, int price); void (*update_price)(Book *book, int newPrice); }
void book_init(Book *book, const char *name, const char *author, int price) { strcpy(book->name, name); strcpy(book->author, author); book->total = total; book->price = price; }
void book_update_price(Book *book, int newPrice) { book->price = price; }
void book_create(Book *book) { book->init = book_init; book->update_price = book_update_price; }
|
然后你就可以像以下一样使用了
1 2 3 4 5 6
| Book book; book_create(&book);
book.init(&book, "WPF 编程宝典", "Matthew MacDonald", 128);
book.update_price(&book, 100);
|
使用这种方式来调用,看起来有了一种“书做了某某事”的面向对象的错觉
###C++正是借鉴了这一点,从而产生了Class(类)
当我们定义一个class的时候,我们自己定义的成员函数就使用了我们上面的思想,参考如下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Book { char name[32]; char author[32; int price; void init(const char *name, const char *author, int price) { strcpy(this->name, name); strcpy(this->author, author); this->price = price; } void updatePrice(int price) { this->price = price; } };
|
我们是用起来显得更简单
1 2 3 4
| Book book; book.init("WPF 编程宝典", "Matthew MacDonald", 128):
book.updatePrice(100);
|
看起来与我们使用C语言模仿的面向对象是不是很像?
接下来我们注意到一个关键字this, 指的是调用函数的某个对象,谁调用了,this指的就是谁,上面的book调用init和updatePrice的时候,this指的就是book这个对象。
我想你应该明白了,C++的面向对象,就是使用了我们的上述模拟面向对象的思维,然后C++自己将对象本身作为一个隐含的参数传递给了我们的成员函数,当然,这些并不包括C++面向对象中更强大的继承和多态。
到此我觉得你应该明白了C++成员函数与普通函数之间的区别以及其内部的原理。
再看下面代码
1 2 3 4 5 6 7 8 9
| class Student { public: Student(const char *name) { strcpy(this->name, name);} void makeTest() { std::cout<<"我在考试"<<std::endl;} void appear() { std::cout<<"My name is "<<name<<std::endl; private: char name[32]; };
|
我们在看如下调用
1 2 3 4
| Student *pStu;
pStu->makeTest(); pStu->appear();
|
很多人都会想到,pStu是一个未初始化的变量,以上①②两种调用都会导致崩溃。再仔细想想看,真的是这样吗?
比较细心的同学会发现,在某些编译器上运行,我们却可以看到①调用后的打印信息,而运行②以后,程序崩溃掉了,我想细心的你应该知道这是什么原因了。
很简单,我们在①处调用的makeTest()函数,翻译成C语言的形式应该是
1 2 3 4 5 6
| void makeTest(Student *this) { }
makeTest(pStu);
|
在代码里面,pStu虽然没有初始化,但是在上面的C语言版的makeTest中,我们并没有使用到this这个参数,所以即使这个参数指针是一个野指针或者空指针,都不会出现崩溃现象,但是对于②处的调用,使用的appear()函数中使用到了this, 也就是说,此时的this是一个未初始化的指针(我们称为野指针,不是空指针),这个指针指向什么地方我们并不知道,可能是没有权限操作的一块内存,也可能正好是空指针,也可能是已经分配的某个内存
我们再做一个小实验
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class Test { public: void test() { if(this != nullptr) { std::cout<<"我不是空的"<<std::endl; } else { std::cout<<"我是空的"<<std::endl; } } };
int main() { Test t; Test *p = nullptr; p->test(); p = &t; p->test(); return 0; }
|
你能猜出最终的打印信息吗?(