从C到C++看面相对象(深入了解C++的成员函数)

原文链接:
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) //C++11中引入的nullptr, 表示空指针,感兴趣的可以自己去多多了解C++11
{
std::cout<<"我不是空的"<<std::endl;
} else {
std::cout<<"我是空的"<<std::endl;
}
}
};

int main()
{
Test t;
Test *p = nullptr; //设置初始值,防止使用未初始化指针
p->test();

p = &t;
p->test();
return 0;
}

你能猜出最终的打印信息吗?(

感谢您对本站的支持.