汉扬编程 C语言入门 C语言模拟实现设计模式(之原型模式)

C语言模拟实现设计模式(之原型模式)

原型模式本质上说就是对当前数据进行复制。就像变戏法一样,一个鸽子变成了两个鸽子,两个鸽子变成了三个鸽子,就这么一直变下去。在变的过程中,我们不需要考虑具体的数据类型。为什么呢?因为不同的数据有自己的复制类型,而且每个复制函数都是虚函数。

用C++怎么编写呢,那就是先写一个基类,再编写一个子类。就是这么简单。

class data{public: data () {} virtual ~data() {} virtual class data* copy() = 0;}; class data_A : public data{public: data_A() {} ~data_A() {} class data* copy() { return new data_A(); }}; class data_B : public data{public: data_B() {} ~data_B() {} class data* copy() { return new data_B(); } }; 那怎么使用呢?其实只要一个通用的调用接口就可以了。

class data* clone(class data* pData){ return pData->copy();} 就这么简单的一个技巧,对C来说,当然也不是什么难事。typedef struct _DATA{ struct _DATA* (*copy) (struct _DATA* pData);}DATA; 假设也有这么一个类型data_A,DATA data_A = {data_copy_A}; 既然上面用到了这个函数,所以我们也要定义啊。struct _DATA* data_copy_A(struct _DATA* pData){ DATA* pResult = (DATA*)malloc(sizeof(DATA)); assert(NULL != pResult); memmove(pResult, pData, sizeof(DATA)); return pResult;}; 使用上呢,当然也不含糊。struct _DATA* clone(struct _DATA* pData){ return pData->copy(pData);};

c语言与设计模式(0)——基础

写在前面

据上次写博客,已经好久好久了,期间一段时间,努力奋进,发粪涂墙,终于从技术菜鸟已然变成了技术老菜鸟。此次回归博客主要是想熟悉下人类的语言,因为每天面对着电脑,与人交流的能力在弱化,再加上单身没人要,回家就是刷手机,感觉整个人都废了。正值想(瞎)重(优)构(化) 公司代码之时,看了一些关于设计模式的文章,故此作为观后感记下来加深记忆,加强表达力,万一有人看,运气好没准有人还会评论批判下,这样也能看到自己写的错误,如此甚好!言归正传,本章先了解下基本概念以及如何用非人类语言(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语言可能天生就不太适合做面向对象的事情,但是有了一颗面向对象的思想后,也在一些地方有发光点啊。所以呀,我们要有一颗为之奋斗的心,不要等到高考之后才后悔当初没努力,不要等到女孩被猪拱了才后悔当初没认真追。问心无愧,人生才没有遗憾!

参考链接:

://blog.csdn.net/penzo/article/details/6001193

://blog.csdn.net/onlyshi/article/details/81672279

://blog.csdn.net/lovelion

本文来自网络,不代表汉扬编程立场,转载请注明出处:http://www.hyzlch.com/cjia/6712.html

C语言 技能提升 系列文章(八)字符输入/输出

设计模式只是一把锤子,不要拿着到处去敲

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

返回顶部