c语言 私有方法,C语言陷阱与技巧第28节,模拟“面向对象”编程,怎样定义私有成员...

上一节讨论了结合指针和结构体语法,C语言也能实现“面向对象”编程。由此可以看出C语言是一门极其灵活的语言,简洁的语法即可实现复杂的程序。

a30783ec14604b240e45bbceb79554d4.png

C语言“对象”的成员变量

不过,在面向对象编程中,对象不仅仅有成员函数,也应该有成员变量。成员变量允许每一个对象都有独立存放数据的能力,各个对象的数据互不干扰。

int val = 0;struct cfun{void (*modify)();void (*print)();};void modify(){ val ++;}void myprint(){printf("val = %d\n", val);}struct cfun f1 = {modify, myprint};struct cfun f2 = {modify, myprint};f1.modify();f1.print();f2.print();在上面这段C语言代码中,为了让“类”cfun 的各个成员函数都能访问变量 val,将 val 定义为全局变量了。但是 val 在内存中只有一份,所以就算是对象 f1 调用 modify() 修改了 val 的值,f2.print() 打印的 val 也会被修改。

当然有些设计期望的结果就是如此。

如果不希望出现这种结果,似乎只能定义两个 val,或者将 val 定义成数组:

int val1 = 0, val2 = 0;//或者int val[2] = {0};

14af76d2232904277ea9446b614b8032.png

然后再修改使用到这些全局变量的C语言代码。但是这么做至少有三个不好的地方:

C语言程序常常不能事先知道该“类”究竟会被实例化成多少个对象,若是对象比较多,超过了 val 的数目,就会导致程序崩溃。这种方式使用起来也不方便,程序员还需再设计出一套映射规则,用于说明不同对象与各个全局变量的对应关系。全局变量的作用域非常大,使用时必须小心的处理(这可能包括防止误调用,防止数据不同步等)。可能有些读者看到这里,就会感叹“C语言果然不适合面向对象编程!”。

解决问题

其实要解决上述不足,只需要把变量加入“类”描述结构体就可以了,请看下面这段C语言代码:

struct cfun{void (*modify)();void (*print)();int val;};void modify(struct cfun *f){ f->val ++;}void myprint(struct cfun *f){printf("val = %d\n", f->val);}struct cfun f1 = {modify, myprint, 0};struct cfun f2 = {modify, myprint, 0};f1.modify(&f1);f1.print(&f1); // 输出 val = 1f2.print(&f2); // 输出 val = 0将 val 加入结构体 cfun,之后每实例化一个对象,就会自动为该对象分配一个 val 变量,各个对象的 val 变量是彼此独立的,互不影响。所以,f1.print() 之后会输出 “val = 1”,而 f2.print() 之后会输出“val = 0”。

a5ba33129981e27acbf0fd02958f2f10.png

现在唯一有些不足的是,在调用成员函数时,需要将对象指针传递进去,这主要是因为C语言没有原生的“对象”语法。当然,也有办法省去这一过程,只需再设计一套额外的处理机制就可以了(这一点以后有机会再说)。

不过,再设计一套额外的处理机制,显然会消耗额外的资源(如内存、cpu等),这与C语言程序的“使用最小的资源,最高效率的办事”精神相违背。而且,省去传递结构体指针的操作,并不会为C语言程序带来质的改变,所以一般不会实现这套机制。

事实上,Linux 内核源码中使用的“对象”也并未使用额外的处理避免传递对象指针。

7893ae8239d3b76b8fdd9709a1ba1b7b.png

C语言对象的“私有成员变量”

直接在类结构体中加入变量作为该类的成员变量是方便的,但是这种成员变量显然是 public 的,该类实例化的任意对象都能随意访问该变量。当然,如果本来就是如此设计的,这么做没有什么问题。

不过有时候,我们只希望某个成员变量只供类内部使用,也即希望该成员变量是 private 的,该怎么办呢?当然,最简单的办法就是写下文档告诉调用者不要随意访问该成员,但是这种方法不具备强制性,很多C语言程序员使用的 IDE 甚至会自动联想补全出该成员变量,一不小心,很容易就出现直接访问本来希望是 private 的成员变量。

74d4e18cdc6319c9b0569c508f19c589.png

其实,我们可以将类的私有(private)成员变量再做一次封装,在类定义中只保留一个指针用于索引各个成员变量即可。请看下面这段C语言代码:

struct cfun{void (*modify)();void (*print)();void *private_data;};// 不对外开放struct PRIVATE{char c;int val;//...};上述C语言代码将“类”cfun 的私有成员变量封装成一个结构体,并且在 cfun 的定义中只保留一个 void * 指针作为入口,解析私有成员变量的结构体 struct PRIVATE 不对外开放,这样一来,只有在 cfun 内部才能解析出具体的私有成员变量。

外部调用者即使能够访问 private_data,也不能轻易的解析出具体的数据,这样就避免了外部调用者通过对象指针随意访问 cfun 的私有成员变量了。

对于 cfun 本身,结构体 struct PRIVATE 是可见的,因此访问 c 和 val 等私有成员变量是方便的,下面是一个示例,请看相关C语言代码:

void modify(struct cfun *f){ ((struct PRIVATE *)(f->private_data))->val ++;}void myprint(struct cfun *f){printf("val = %d\n", ((struct PRIVATE *)(f->private_data))->val);}

bad35bd4d295b64fd72b93ca29b0973d.png

如果觉得 ((struct PRIVATE * )(f->private_data))->val 这样访问 val 太过繁琐,可以使用定义宏的小技巧,这一点我们已经比较熟悉了,例如:

#define PD(pcfun) \ ((struct PRIVATE *)((pcfun)->private_data))这样一来,再写C语言代码就简洁了:

void modify(struct cfun *f){ PD(f)->val ++;}void myprint(struct cfun *f){printf("val = %d\n", PD(f)->val);}小结

在使用C语言结构体和指针语法模拟面向对象编程时,也是允许定义结构体的成员变量的,本文讨论了两种方式:借助于全局变量,或者直接在结构体中添加变量。比较推荐后者,不过直接在结构体中添加的变量是 public 的,各个实例对象都能直接访问。如果希望定义类内部使用的 private 变量,可以借助C语言的结构体和指针语法再封装一层。

1f8429d807bb39f1b710a2598f3ea8ac.png

当然,本文所讨论的内容只属于抛砖引玉,更多技巧和方法这里不可能一一涉及。相信读者在实际C语言项目开发中,必定能够发现更好的编程风格。

aebd6f3909bd15bdf50ee0eeb3546e67.png

欢迎在评论区一起讨论,质疑。文章都是手打原创,每天最浅显的介绍C语言、linux等嵌入式开发,喜欢我的文章就关注一波吧,可以看到最新更新和之前的文章哦。

举报/反馈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值