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

我们都知道C是一门面相过程的语言,在C的世界里是没有面相对象这个概念的,但是C语言为我们提供的神兵利器,仍旧可以让我们使用面相对象的思维方式
在C语言里,我们每做一个操作,都需要写一个函数,但是该函数都是过程化的,但是我们有两种神兵利器,一个叫指针,一个叫结构体
为什么这么说呢?
面相对象的最基本的功能就是对数据的封装,在C语言的世界里,我们有结构体这个法宝,同样可以将数据打包整整体,然后通过指针的方式,将结构体作为参数在函数中进行传递
举个例子

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;
}

以上的例子很简单,初始化一本书以及更新书价,在使用的时候,我们只需要像以下方式调用

Book book;
book_init(&book, "WPF 编程宝典", "Matthew MacDonald", 934, 128);

//...

book_update_price(&book, 100); //降价

以上的这些使用的都是面相过程的思维。
所谓面向过程编程,就好比“让某某去做某事”,而面向对象呢,就好比“某某去做某事”。从字面意思来看,面向过程貌似多了个“高级领导”,而面向对象显得更自由。

我们再变一下:

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;
}

然后你就可以像以下一样使用了

Book book;
book_create(&book);

book.init(&book, "WPF 编程宝典", "Matthew MacDonald", 128);
//...
book.update_price(&book, 100);

使用这种方式来调用,看起来有了一种“书做了某某事”的面向对象的错觉

C++正是借鉴了这一点,从而产生了Class(类)

当我们定义一个class的时候,我们自己定义的成员函数就使用了我们上面的思想,参考如下代码

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;
    }
};

我们是用起来显得更简单

Book book;
book.init("WPF 编程宝典", "Matthew MacDonald", 128):
//...
book.updatePrice(100);

看起来与我们使用C语言模仿的面向对象是不是很像?
接下来我们注意到一个关键字this, 指的是调用函数的某个对象,谁调用了,this指的就是谁,上面的book调用init和updatePrice的时候,this指的就是book这个对象。

我想你应该明白了,C++的面向对象,就是使用了我们的上述模拟面向对象的思维,然后C++自己将对象本身作为一个隐含的参数传递给了我们的成员函数,当然,这些并不包括C++面向对象中更强大的继承和多态。
到此我觉得你应该明白了C++成员函数与普通函数之间的区别以及其内部的原理。
再看下面代码

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];
};

我们在看如下调用

Student *pStu;

pStu->makeTest(); // ①
pStu->appear(); // ②

很多人都会想到,pStu是一个未初始化的变量,以上①②两种调用都会导致崩溃。再仔细想想看,真的是这样吗?
比较细心的同学会发现,在某些编译器上运行,我们却可以看到①调用后的打印信息,而运行②以后,程序崩溃掉了,我想细心的你应该知道这是什么原因了。
很简单,我们在①处调用的makeTest()函数,翻译成C语言的形式应该是

void makeTest(Student *this)
{
    //打印“我在考试”
}

makeTest(pStu);

在代码里面,pStu虽然没有初始化,但是在上面的C语言版的makeTest中,我们并没有使用到this这个参数,所以即使这个参数指针是一个野指针或者空指针,都不会出现崩溃现象,但是对于②处的调用,使用的appear()函数中使用到了this, 也就是说,此时的this是一个未初始化的指针(我们称为野指针,不是空指针),这个指针指向什么地方我们并不知道,可能是没有权限操作的一块内存,也可能正好是空指针,也可能是已经分配的某个内存

我们再做一个小实验

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;
}

你能猜出最终的打印信息吗?(��是不是跟我一样呢)
这里写图片描述
这里写图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值