sinanmu · 2020年05月29日

论面向对象和运算符重载思想

作者: @Ai酱

Attention: 1. 本文中的“面向对象”一词属于计算机用语范畴,非人文社科范畴。 2. 虽然本文用c++实现,但是思想在其他语言一样适用。

对于面向对象很多初学者非常不理解:“为啥要这么麻烦的定义类和各种访问限制?”。 这当然与老师的教法很有关系。 我一直认为:“不理解一个东西为何而产生,那么就不能说真的懂这个东西” 所以,在本文我想聊一聊,面向对象思想有何而来,以及运算符重载又是为了什么。

面向对象是什么?

经常听到一句话叫做:一切皆对象。 下面我们来实践下。

面向过程

假设有一天,老板让你实现存储一个平面坐标(x,y)。然后你会怎么做呢? 拍拍脑袋觉得好简单,不就两行代码吗?于是10秒钟解决。给如下代码给老板看。

// 存储坐标(1,1)
int x=1;
int y=1;

然后,老板说:“小明啊,我刚刚想了想,我们要存储100个点行不行?”。卧槽,怎么不早说。100个点我得复制粘贴100次啊,而且还得改变量名。 想想下面这种代码就头痛。

// 存储坐标(1,1)
int x=1;
int y=1;
// 存储坐标(1,1)
int x1=1;
int y1=1;
// 存储坐标(1,1)
int x2=1;
int y2=1;
...//省略n行代码,n>=50
// 存储坐标(1,1)
int x99=1;
int y99=1;

然后,你翻了翻书。发现结构体这个好东西。于是这这么存储100个坐标点。

struct Point{
    int x;
    int y;
}
Point a[100];

心想:“我简直是天才啊!”,5行代码解决。 然后,->-你老板看了后,想了想。不行啊,我想对坐标进行加法操作。两个坐标都不能相加要它有何用。 然后你就写了一个函数解决这个问题。

struct Point{
    int x;
    int y;
};
/**
* 返回a和b对应坐标值相加后的坐标
*/
Point add(Point a, Point b){
    Point result = Point();
    result.x =a.x+b.x;
    result.y =a.y+b.y;
    return result;
};
...
// 别人调用你代码这样写
Point c = add(a,b);

然后你兴高采烈的告诉老板,你写完了。老板说:“这么快写完了,不错不错”。

运算符重载

心中大舒一口气,想道:“这家伙终于没咋的了”。 然后,程序员B跟你说,你这不直观啊。整数可以a+b这么写,按道理坐标也应该要这么写啊。 你微笑道:“好的,我再改改”。脑海里想到四个词:“万马奔腾”。 然后,百度了下。发现咦用类好像还真可以这么写。然后复制粘贴,写成了下面这行代码。于是你顿悟,为啥要用运算符重载了,因为这样别人用你的代码更直观.

class Point{
public:
    int x;
    int y;
    Point(){
    x=0,y=0;
    }
    Point(int _x,int _y){
    x=_x,y=_y;
    }
    Point operator+(Point b){
        Point result = Point(x+b.x,y+b.y);
        return result;
    }

    // 哈哈,同事们这是我留给你们的新特性可以打印坐标
    print(){
        cout<<"(",x,",",y,")"<<endl;
    }
};
...
// 别人调用你代码可以这样写,确实变直观了
Point c = a + b;

继承

有一天,老板决定开新业务。说小明啊,我们想做3D的坐标。可以帮我实现下3维坐标存储和加法么? 你想了想,吸取上次的教训。这次一定得考虑怎么利用原有的代码。不然老是这么不停重写不是办法,头发都得掉光了。 然后去知乎搜下怎么重复利用代码,发现有人告诉你继承可以重复利用代码。 然后,你顿悟。原先是两个维度,我加个维度确实可以继承啊。我就只用写新增那个维度的代码了。 然后改成了这样。

class Point{
public:
    int x;
    int y;
    Point(){
    x=0,y=0;
    }
    Point(int _x,int _y){
    x=_x,y=_y;
    }
    Point operator+(Point b){
        Point result = Point(x+b.x,y+b.y);
        return result;
    }

    // 哈哈,同事们这是我留给你们的新特性可以打印坐标
    print(){
        cout<<"(",x,",",y,")"<<endl;
    }
};


// 以下是三维坐标类
class Point_3D : public Point{
    public:
    //哈哈,x,y在父类已经有了,子类直接继承相当于自己已经定义了x,y. 子类只用管新增的那个维度。
    int z;// 新增的一个维度

    Point_3D(){
        Point();// 重复利用父类的构造函数
        z = 0;
    }

    Point_3D(int _x, int _y, int _z){
        z = _z;
        Point(_x,_y);
    }

    // 加法也要改一改
      Point_3D operator+(Point_3D b){
      //利用父类的运算符重载实现x,y这两个维度的加法
        Point_3D result = *this + b;
      // 处理新增维度
        result.z = z+b;
        return result;
    }

    // 打印的也要改一改
    print(){
        cout<<"(",x,",",y,",",z,")"<<endl;
    }
};

多态

慢慢好像找到感觉了。好像写的有那么点艺术感。 有一天老板说,你给我用一个数组把二维坐标和三维坐标都存在一起。并且还有一次性将他们坐标都打印。 你仰天长啸:“哈哈,开森。终于懂了什么是多态了,多态原来这个用的啊”

Point a[3] = {Point(1,2), Point_3D(1,0,1),Point(0,1)};
// 小B,看我写的代码叼不叼,明明里面有不同类,我同样的代码就正确打印了
// 这就是多态,运行的时候父类会调用父类的print,子类会调用子类的print
for (int i=0;i < 3;i++)
    a[i].print();

总结

本文介绍了一些面向对象的思想的出发点,分别是:

运算符重载(为了让别人更直观的用你的代码)

继承(为了少写代码,保留更多的头发)

多态(为了一行代码父类和子类产生不同的效果)

如果你觉得这篇文章对你有用,你的赞是我分享的动力

推荐阅读:


欢迎关注我的知乎专栏适合初学者的机器学习神经网络理论到实践
1 阅读 328
推荐阅读
0 条评论
关注数
4
内容数
15
理工思维科普编程、读书、AI、机器人
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
Arm中国学堂公众号
关注Arm中国学堂
实时获取免费 Arm 教学资源信息
Arm中国招聘公众号
关注Arm中国招聘
实时获取 Arm 中国职位信息