面向对象
面向过程
就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
面向对象
就是把现实中的事物都抽象为“对象”。每个对象是唯一的,且都可以拥有它的属性与行为。我们就可以通过调用这些对象的方法、属性去解决问题。
举例
单说名词没有感觉,我们来个例子,假设我们在做一个游戏,我们可以控制的角色有猫咪、鱼、鸟。
其中猫咪需要4条腿走路,鱼只能在水里游,鸟需要在天上飞。这时,用户按下前进键,那么不同的角色就需要调用不同的前进函数。
角色 | 函数 |
---|---|
猫咪 | run() |
鱼 | swimming() |
鸟 | fly() |
按照最直觉的想法,我们写一段代码
// 按键按下
void btn_press(unsigned char btn)
{
// 按下了前进键
if(btn == BTN_GO)
{
swich(roleNow)
{
case cat:
run();
break;
case fish:
swimming();
break;
case bird:
fly();
break;
}
}
}
这就是一段面向过程的代码,按下按键之后,先去判断当前选择的角色,然后根据不同的角色调用不同的前进函数。
看着好像没什么问题,也确实没什么问题,但如果角色多大100个,swich后面跟着100多个case,代码繁杂且难以维护。
下面我们试着用面向对象的方法去解决这个问题。
面向对象的解法
首先我们需要对猫咪、鱼、鸟进行抽象,我们发现这三个角色他们各自都有一个前进的函数,那么我们是否可以抽象出一个对象。
typedef struct Role
{
void (*go)();
};
这里我们定义了一个结构体,其中包含了一个函数指针,指针名称叫做go()
,它没有具体的实现,但却是每一个角色都有的。
然后我们将角色实例化,也就是定义几个角色
void main()
{
struct Role cat;
struct Role fish;
struct Role bird;
cat.go = run;
fish.go = swimming;
bird.go = fly;
}
最后,上述问题就会变得非常简单
// 按键按下
void btn_press(unsigned char btn)
{
// 按下了前进键
if(btn == BTN_GO)
{
roleNow.go();
}
}
优点
1、整个代码会变得非常简洁清爽
2、btn_press
内部的代码将无需再次维护,无论添加多少个角色,这里都不需要动了
3、代码维护将只需要在fly()\swimming()\run()
这些函数中进行,而无需改动外部代码,减少bug的发生。
代码
#include <stdio.h>
#define BTN_GO 1
struct Role* roleNow;
typedef struct Role
{
void (*go)();
};
void run()
{
printf("cat go");
}
void swimming()
{
printf("fish go");
}
void fly()
{
printf("bird go");
}
// 按键按下
void btn_press(unsigned char btn)
{
// 按下了前进键
if (btn == BTN_GO)
{
roleNow->go();
}
}
int main()
{
struct Role cat;
struct Role fish;
struct Role bird;
cat.go = run;
fish.go = swimming;
bird.go = fly;
roleNow = &bird;// 当前选中角色
btn_press(BTN_GO);
}
继承与派生
继承和派生其实都是面向对象编程的语言(如c++,c#,java等)中的名词。
保持已有类的特性而构成新类的过程称为继承
在已有类的基础上新增自己的特性而产生新类的过程称为派生
被继承的已有类成为基类(父类)base class
派生出的新类称为派生类(子类)derived class
起因
我们深入讨论上节面向对象中的问题,显然,不同角色有自己专属的属性。比如鸟有飞行高度,鱼有下潜深度,猫咪有跳跃高度。如果还想要用上一节面向对象的方式,那么role
这个结构体中就需要把所有的属性都包含进来。
typedef struct Role
{
void (*go)();
int flyHeight;// 飞行高度
int deep;// 下潜深度
int jumpHeight; // 跳跃高度
};
但是很显然,deep
属性对于鸟和猫咪是不需要的,如果每个角色都把这些属性包含进来,就会造成大量的浪费。
为了解决这个问题,就产生了继承和派生的概念,我们对猫咪、鸟、鱼分布进行定义
typedef struct Cat
{
struct Role this;
int jumpHeight; // 跳跃高度
};
typedef struct Fish
{
struct Role this;
int deep;// 下潜深度
};
typedef struct Bird
{
struct Role this;
int flyHeight;// 飞行高度
};
struct Cat,struct Fish,struct Bird
都包含了struct role
,我们就可以看作Role
被继承,派生出了Cat
。
技巧
我们解决了浪费的问题,但是还有一个问题,那就是roleNow
应该定义成什么类型?
虽然Cat\Fish\Bird
都有Role
这个属性,但是roleNow
需要能够代表任意一个角色啊,我们总不能在选择Cat
的时候把它定义成Cat
类型,选择Fish
类型的时候把它定义成Fish
类型吧。
这里我们就要用到一个C语言的特性了,那就是指针!
众所周知,指针指向一个地址,而非一个具体的内容,那么我们只需要将roleNow
定义成Role
类型的指针就好了。
我们以Cat
类型为例,他在内存中的分别如下
Cat |
---|
struct Role this |
int jumpHeight |
假设我们定义的struct Cat cat
变量在0x00的地址上,由于struct Role this
在struct Cat
结构体的第一位置上,所以struct Role this
的首地址也会是0x00。换而言之,指向0x00的这个指针既能指向cat
这个变量又能指向cat.this
这个内部变量。
基于此,只要我们在定义Cat\Fish\Bird
时,都把struct Role this
放在结构体的第一个位置,那么只需要定义一个struct Role*
的指针就能指向任何一个基于struct Role
派生出来的结构体。
代码
#include <stdio.h>
#define BTN_GO 1
struct Role* roleNow;
typedef struct Role
{
void (*go)();
};
typedef struct Cat
{
struct Role this;
int jumpHeight; // 跳跃高度
};
typedef struct Fish
{
struct Role this;
int deep;// 下潜深度
};
typedef struct Bird
{
struct Role this;
int flyHeight;// 飞行高度
};
void run()
{
printf("cat go");
}
void swimming()
{
printf("fish go");
}
void fly()
{
printf("bird go");
}
// 按键按下
void btn_press(unsigned char btn)
{
// 按下了前进键
if (btn == BTN_GO)
{
roleNow->go();
}
}
int main()
{
struct Cat cat;
struct Fish fish;
struct Bird bird;
cat.this.go = run;
fish.this.go = swimming;
bird.this.go = fly;
roleNow = &bird.this;// 当前选中角色
btn_press(BTN_GO);
}
注意
本文说的概念都是C语言在面向对象时的应用,不代表其自己真正的含义,如需探索,请自行搜索。