单片机工程经验 - 面向对象编程及结构体的派生与继承

面向对象

面向过程

就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。

面向对象

就是把现实中的事物都抽象为“对象”。每个对象是唯一的,且都可以拥有它的属性与行为。我们就可以通过调用这些对象的方法、属性去解决问题。

举例

单说名词没有感觉,我们来个例子,假设我们在做一个游戏,我们可以控制的角色有猫咪、鱼、鸟。
其中猫咪需要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 thisstruct 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语言在面向对象时的应用,不代表其自己真正的含义,如需探索,请自行搜索。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蓝忧云枫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值