写在前面
据上次写博客,已经好久好久了,期间一段时间,努力奋进,发粪涂墙,终于从技术菜鸟已然变成了技术老菜鸟。此次回归博客主要是想熟悉下人类的语言,因为每天面对着电脑,与人交流的能力在弱化,再加上单身没人要,回家就是刷手机,感觉整个人都废了。正值想(瞎)重(优)构(化) 公司代码之时,看了一些关于设计模式的文章,故此作为观后感记下来加深记忆,加强表达力,万一有人看,运气好没准有人还会评论批判下,这样也能看到自己写的错误,如此甚好!言归正传,本章先了解下基本概念以及如何用非人类语言(c语言)来表达。
其实很多大佬都已经在代码中无声无息地用过了,所以文章可能会显得很肤浅。作为菜鸟,我就是想系统的梳理一遍,望大佬扫过错误之处多多指正。
封装
封装在我的理解其实就是开闭原则的直接体现,即,暴露对外的接口,隐藏私有的数据,那我们可以这么来做:
/*st_dev.h*/
struct st_dev_private; //前向声明
struct st_dev {
...
void (* dev_func)(struct st_dev *dev); //实现放在st_dev.c中,并且让st_dev.c包含 st_dev_p.h
struct st_dev_private *private; //定义放在在另一个头文件里实现 eg:st_dev_p.h
// void *private; //印象中在源码的驱动部分也见过这种,等得空看源码的时候补上
};
/* 构造函数的声明,定义放在相应的.c文件中,可以通过参数传递给私有结构体来进行初始化,也可以直接在构造函数的函数体内直接初始化*/
struct st_dev *create_st_dev(....);
/*st_dev_p.h*/
struct st_dev_private {
int x;
int y;
};
/*st_dev.c*/
#include "st_dev.h"
#include "st_dev_p.h"
void dev_function(struct st_dev *dev)
{
//operate private
printf("%x\n", dev->private->x);
}
struct st_dev *create_st_dev(...)
{
struct st_dev *dev = malloc(sizeof(struct st_dev));
dev->dev_func = dev_function;
dev->private = malloc(sizeof(struct st_dev_private));
dev->private->x = ...;
...
return dev;
}
继承
继承是一种代码复用的手段,把公用的代码提取出来作为父类,子类继承父类去实现变化的部分。
c语言实现继承说简单也简单,说难也难。对于多继承,和多层继承,用c语言处理起来还是比较麻烦的,故自认为c更适合单层单继承。方法就是派生类将基类作为第一个成员即可。eg:
struct derived {
struct base super; // 必须为实体而不是指向实体的指针
...
};
这样,就可以通过强制转换比较安全的访问了。印象中Android的HAL的HW_module_t 和 HW_device_t 各自的父类都是这么搞的。
继承体现的原则是针对接口编程,这里的接口不是狭义的java的interface,而是广义的超类(包括interface)。
多态
多态简单的概括为“一个接口,多种方法”。目的是接口重用
说到多态就不得不抄了一下三个概念:
Overload(重载):在C++程序中,可以将语义、功能相似的几个函数用同一个名字表示,但参数或返回值不同(包括类型、顺序不同),即函数重载。
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。
Override:是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual 关键字。
Overwrite:是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。
当时用了5个月c++的我被猎头妹妹推去面试,被问及此问题,虽然我大概知道这些,但我并不太会表达出来(菜是原罪),在此粘上,警醒自己。
不过很多时候一提到多态都说的是override,不知道是不是这样的,毕竟没怎么系统学过c艹。如果说法有误烦请指正。c语言中实现多态的方法是 ,1. 定义一个 有一组(>0)函数指针 的 结构体作为函数虚表;2. 基类包含该虚表结构体或结构体指针;3. 派生类包含基类。eg:
/*shape.h*/
struct shape_vtbl {
void (* draw)(struct shape *shp);
int (*area)(struct shape *shp)
};
struct shape {
struct shape_vtbl *vtbl;
int x, y;
};
extern void shape_draw_ops(struct shape *shp);
extern int shape_area_ops(struct shape *shp);
/*rectangle.h*/
#include "shape.h"
struct rectangle {
struct shape super;
int width, height;
};
/*shape.c*/
#include "shape.h"
static void shape_draw(struct shape *shp){}
static int shape_area(struct shape *shp){}
static shape_vtbl sh_vtbl = {
.draw = shape_draw,
.area = shape_area
};
struct shape *create_shape(int x, int y)
{
struct shape *sp = malloc(sizeof(struct shape));
sp->vtbl = &sh_vtbl;
sp->x = x;
sp->y = y;
}
/* shape_draw_ops和shape_area_ops 可以防止shape.h里作为内联函数或宏,这样可以提高效率。当然会有额外的代码空间的开销,成年人不要什么都想要*/
void shape_draw_ops(struct shape *shp)
{
shp->vtbl->draw(shp);
}
int shape_area_ops(struct shape *shp)
{
shp->vtble->area(shp);
}
/*rectangle.c*/
#include "rectangle.h"
static void rectangle_draw(struct shape *shp)
{
...
}
static int rectangle_area(stuct shape *shp)
{
...
}
static rectangle_vtbl rt_vtbl = {
.draw = rectangle_draw,
.area = rectangle_area
};
struct shape *create_rectangle(int x, int y, int width, int height)
{
struct rectangle *rt = malloc(sizeof(struct shape));
rt->vtbl = &rt_vtbl;
rt->x = x;
rt->y = y;
}
/****circle****/
//假如我们还有个circle类,那和rectangle的实现一样
/**************/
/****client.c**/
#include "rectangle.h"
#include "circle.h"
int main()
{
shape *shp = create_rectangle(1, 2, 3, 4);
shape *shp2 = create_circle(5, 6, 7, 8);
shape_draw_ops(shp);
shape_draw_ops(shp2);
return 0;
}
接口(注:本节除了开头半句话都是抄的,我觉得观点挺好,其实就是上面描述的述继承一节的子集):
我在看《head first》的时候不是很明白接口和抽象类的区别,因此请教朋友、查阅资料,终于在刘伟大神的文章中有了比较简明的答案:接口强制派生类必须实现基类(接口)定义的契约,而抽象类则允许实现继承从而导致派生类可以不实现基类(接口)定义的契约。通常这不是问题,但在有一些特定的情况,看起来不那么合适。比如上面的代码定义一个shape基类,其中定义一个draw()方法,给个什么都不做的默认实现(通常是空函数体),这实际没有任何意义。
在我们编程中,实际上多数时候也不需要那么多的继承层次,一个接口作为基类,一个实现类继承接口类,这基本就足够了。eg:
struct base_interface {
void (*func_1)(struct base_interface *pbi);
vopi (*func_2)(struct base_interface *pbi);
};
struct derived {
struct base_interface bi;
int x;
char ch;
};
如上代码所述,derived结构体通过包含base_interface类型的成员bi来达到继承的效果;而base_interface无法实例化,我们没有提供相应的构造函数,也没有提供与func_1, func_2等函数指针对于的实现,即便有人malloc了一个base_interface,也无法使用。
derived类可以提供一个构造函数create_derived,同事在实现文件中提供func_1, func_2的实现并将函数地址付给bi的成员,从而完成derived类的装配实,实现base_interface定义的契约。
void derived_func_1(struct base_interface *pbi)
{
struct derived *pd = (struct derived *)pbi;
pd->x *= 2;
}
/* void derived_func_2(struct base_interface *pbi)*/
struct derived *create_derived(...)
{
strruct derived *d = malloc(sizeof(struct derived));
d->func_1 = derived_func_1;
d->func_2 = derived_func_2;
d->x = 0;
d->ch = 'c';
}
我们可以这么使用base_interface接口:
void do_something(struct base_interface *pbi)
{
pbi->func_1(pbi);
}
int main()
{
struct derived *d = create_derived();
do_something((struct base_interface *)d);
return 0;
}
总结
世间万物,皆有定数。不是所有的人都能面面俱到,比如我这种比较笨的人只能靠努力。同样c语言可能天生就不太适合做面向对象的事情,但是有了一颗面向对象的思想后,也在一些地方有发光点啊。所以呀,我们要有一颗为之奋斗的心,不要等到高考之后才后悔当初没努力,不要等到女孩被猪拱了才后悔当初没认真追。问心无愧,人生才没有遗憾!
参考链接:
https://blog.youkuaiyun.com/penzo/article/details/6001193
https://blog.youkuaiyun.com/onlyshi/article/details/81672279
https://blog.youkuaiyun.com/lovelion