查看原文
其他

c语言设计模式--补充面向对象基础

亚索老哥 embed linux share 2021-01-31


这一篇给大家补充一些学习设计模式的基础知识,比如设计模式的哲学思想,c语言如何实现面向对象特性等等。是进一步学习各种设计模式招式的内功。

设计模式感悟

中国数千年的历史文明,留给后人数之不尽的智慧。自从事编程工作以来,已经三年多有余,慢慢代码敲多了,竟然在代码中发现了一些先哲们伟大思想的蛛丝马迹。

道德经中有这么一段:“有物混成,先天地生。寂兮寥兮,独立而不改,周行而不殆,可以为天地母。吾不知其名,字之曰道,强为之名曰大。大曰逝,逝曰远,远曰反。”

每个人对道有不同的理解,在我看来,如老子说的一般,道可道,非常道。说不明白,解释不尽的才是道。有那么一点只可身教、不可言传的味道。它是各种自然界天然法则的本质抽象,至少在某一方面跟我们揭示了各种事物如何组织在一起以及进行沟通互动的原始规律。

设计模式就是这么一种道。它指导着开发者在编码过程中,各个对象怎么组织联系成为一个优雅健壮的整体,如何使对象相互间即独立又相互协作。一方面,保证了良好的可维护项和可拓展性,另一方面,共同稳定可靠地完成某种功能。

上面说了,设计模式是面向对象的道,那我们必须先了解对象是什么?它有什么特性?c语言如何实现面向对象?

对象的特性

几乎所有的书本资料都会告诉你,c语言是面向过程的语言,C++是一门面向对象的语言。而面向对象有三大特性:封装、继承、多态

封装

图片中的ATM相信小百姓都不陌生,那大家有没有想过为什么ATM要把money都存放到内部的保险柜中,只留一个出钞口可以获取到money?哈哈,这个问题有点弱智,其实就是为了保护money不被人非法获取,只有符合身份信息验证的人才能从里面提取money。

其实这个就是封装,封装隐藏了里面的所有money,你没有办法直接地知道里面money的细节,比如这些money是第几版的纸币?它的新旧程度怎么样?要获取这些细节就只能通过唯一的出钞口来获取。

继承

恩?这是...国民老公?对,虽然现在他名下资产被法院冻结,但遥想思聪当年,娱乐圈纪委、百分百胜率ADC、吃着热狗带领IG捧回当年S赛冠军,是何等风光。思聪C道出位离不开对其父亲--首富王健林的帮助。这其实就是一种"继承".

换在程序开发的话,你可以试想,如果你花了大力气开发了一套国家图书馆管理软件,那么在以后可能遇到其他省市级图书馆管理软件需要开发的时候,是不是希望把自己以前开发国家图书馆管理软件的代码也派上用场呢?

多态

我们可以看到图片中各种各样的球类,虽然它们都是圆滚滚的,也都富有弹性,但是它们的用途各不一样。这说明对象都拥有自己的一些行为特点,我们把对象的这些行为特点称为"多态"。

总结一下面向对象的特性

  • 封装,隐藏内部细节
  • 继承,复用现有代码
  • 多态,改写对象行为

c语言面向对象的艺术

先来看封装。

所谓封装,通俗地说,就是一个姑娘化了妆,只给你看她想让你看的那一面,至于里面是否刮了骨、垫了东西,不给你看。说到封装就得说隐藏,这是对兄弟概念;其实我理解隐藏是更深的封装,完全不给你看见,而封装可能是犹抱琵琶半遮面。封装在 C++ 语言中有 protected 、 private 关键字在语言层面上支持,而 C 语言中没有这些。C 有结构体( struct ),其实可以实现封装和隐藏。

struct A_private;
struct A {
int a;
...
void (*func)(struct A*);

struct A_private * priv;
};

上面的代码,我们只向声明结构体 struct A_private ,没人知道它里面具体是什么东西。假如 struct A 对应的头文件是 A.h ,那么把 A_private 的声明放在 A_p.h 中,A.c 文件包含 A_p.h ,那么在实现 struct A 的函数指针 func 时如何使用 struct A_private ,客户程序员根本无须知道。

这样做的好处是显而易见的,除了预定义好的接口,客户程序员完全不需要知道实现细节,即便实现经过重构完全重来,客户程序员也不需要关注,甚至相应的模块连重新编译都不要——因为 A.h 自始至终都没变过。

另外对于某些方法,仅仅是在对象内部使用,它们将采用static修辞把作用范围局限在一个文件的内部:

到现在为止,封装和隐藏就实现了,而且很彻底。

再看继承。

首先C语言中的void指针是非常强大的,不仅可以指向变量而且还可以泛指向任何数据,例如不同类型的变量,函数等。不同变量间的指针只需要强制转换类型就可实现不同类型数据的访问。例如如下代码:

struct parent_class
{
int a, b;
char *str;
};
struct child_class
{
struct parent_class p;
int a, b;
};
void func()
{
struct child_class obj, *obj_ptr;
struct parent_class *parent_ptr;
obj ptr = &obj;
/* 获得父指针 */
parent ptr = (struct parent*) &obj;
parent ptr->a = 1;
parent ptr->b = 5;
obj ptr->a = 10;
obj ptr->b = 100;
}

在上面代码中,注意subobject结构中第一个成员p,这种声明方式代表subobject类型的数据中开始的位置包含一个parent类型的变量。在函数func中obj是一个subobject对象,正向这个结构类型指示的,它前面的数据应该包含一个parent类型的数据。在第17行的强制类型赋值中parent ptr指向了obj变量的首地址,实际上也就是obj变量中的p对象。好了,现在parent ptr指向的是一个真真实实的parent类型的结构,那么可以按照parent的方式访问其中的成员,当然也包括可以使用和parent结构相关的函数来处理内部数据,因为一个正常的,正确的代码,它是不会越界访问parent结构体以外的数据的。经过这基本的结构体层层相套包含,对象简单的继存关系就体现出来了:父对象放于数据块的最前方,代码中可以通过强制类型转换来获得父对象指针。

最后来看多态。

这里只讲解多态最基本的用法,就是实现改写对象的行为。虚函数与重载涉及比较复杂的底层机制,笔者暂时还没有研究清楚。

多态,就是说用同一的接口代码处理不同的数据。比如说,下面的base_class结构就是一个通用的数据结构,我们也不清楚a是什么数据,vfunc是什么处理函数?但是,我们处理的时候只要调用base_class->vfunc(int a)就可以了。剩下来的事情我们不需要管,因为不同的接口会有不同的函数去处理,我们只要学会调用就可以了。

struct base_class
{
int a;
void (*vfunc)(int a);
}
void base_class_vfunc(struct base_class *self, int a)
{
assert(self != NULL);
assert(slef->vfunc != NULL);
self->vfunc(a);
}
struct child_class
{
struct base_class parent;
int b;
};
void child_class_init(struct child_class* self)
{
struct base_class* parent;
parent = (struct_base_class*) self;
assert(parent != NULL);
parent->vfunc = child_class_vfunc;
}
static void child_class_vfunc(struct child_class*self, int a)
{
self->b = a + 10;
}

学完这一节打好设计模式的基础了,欢迎继续查看其它c语言设计模式系列文章。


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存