正文
大家好,我是bug菌!
最近收了几次稿费,不要太意外~
今天主要跟大家分享一种隐藏结构体成员的方法,很多地方也叫“不完全类型”,所以这里bug菌以更加通俗易懂的方式跟大家介绍下,并且谈一谈相关的一些问题。
1
引出话题
首先我们来看下面一个最简单的例子:
参考代码:
1/************filename: App.h*************/
2#ifndef __APP_H__
3#define __APP_H__
4
5
6typedef struct _tag_StObj stObj;
7struct _tag_StObj
8{
9 int member1;
10 int member2;
11};
12
13//interface
14
15int sAdd(stObj *pObj);
16
17#endif
18
19/************filename: App.c*************/
20#include "App.h"
21
22/**********************************
23 * Function : sAdd
24 * Note :加法函数,也是接口函数
25 * Author: bug菌
26 **********************************/
27int sAdd(stObj *pObj)
28{
29 return (pObj->member1 + pObj->member2);
30}
31
32/************filename: main.c*************/
33#include <stdio.h>
34#include "./App/App.h"
35
36int main(void)
37{
38 stObj Obj;
39 Obj.member1 = 1;
40 Obj.member2 = 2;
41
42 printf("result = %d\n",sAdd(&Obj));
43
44 return 0;
45}
以上是三个文件中的内容,程序编译通过,输出结果为3。
在main函数中均可以通过结构体定义变量,并且直接访问其结构体内部的成员,而很多人觉得结构体作为一个对象不应该把其内部数据全部暴露出来供开放访问,非常不利于内部实现细节的封装和对象数据的安全性。
那有什么办法不允许外部访问结构体成员呢?
2
不完全类型
"不完全类型"看起来很深奥的名字,主要还是翻译问题吧,从字面上来说就是不那么完整的类型,我们知道像常规的char,int,float类型,要作为一个类型,那么平台肯定为他们提供了所占据的内存大小和处理方式,而不完全类型几乎没有在定义的时候给出,比如没有指定长度的数组array[],他也是一种不完全类型,虽然表示的是数组,可是你不知道它到底有多大,这样编译器就不能够为其分配内存而定义报错。
下面修改下之前的程序,把结构体定义放到对应的app.c文件,而app.h中留下一个啥也不含的同名结构体"空壳"。
1/************filename: App.h*************/
2#ifndef __APP_H__
3#define __APP_H__
4
5
6typedef struct _tag_StObj stObj;
7/*struct _tag_StObj
8{
9 int member1;
10 int member2;
11};*/
12
13//interface
14
15int sAdd(stObj *pObj);
16
17#endif
18
19/************filename: App.c*************/
20#include "App.h"
21
22struct _tag_StObj
23{
24 int member1;
25 int member2;
26};
27/**********************************
28 * Function : sAdd
29 * Note :加法函数,也是接口函数
30 * Author: bug菌
31 **********************************/
32int sAdd(stObj *pObj)
33{
34 return (pObj->member1 + pObj->member2);
35}
36
37/************filename: main.c*************/
38#include <stdio.h>
39#include "./App/App.h"
40
41int main(void)
42{
43 stObj Obj;
44 Obj.member1 = 1;
45 Obj.member2 = 2;
46
47 printf("result = %d\n",sAdd(&Obj));
48
49 return 0;
50}
编译结果:
此时编译器会报一个error,表示不知道该结构体到底是多大,如果你要是问App.c文件里面不是定义了结构体成员吗?怎么还会报错?你需要看一下bug菌的往期精彩,C程序的编译都是以源文件为单元展开的。
3
求助指针
把前面的main.c改改看能不能编译通过:
1/************filename: main.c*************/
2
3#include <stdio.h>
4#include "./App/App.h"
5
6int main(void)
7{
8 stObj *Obj;
9 //Obj.member1 = 1;
10 //Obj.member2 = 2;
11
12 printf("result = %d\n",sAdd(Obj));
13
14 return 0;
15}
然而此时编译通过:
当然上面程序语法没问题,运行却是有问题的,定义了一个野指针,一旦运行基本上都会奔溃。
并且不能通过指针直接访问结构体成员,因为这是一个不知道成员的结构体"空壳",同样sizeof也检测不了大小。
那问题来了,为什么用结构体定义变量不行,而定义成指针却可以呢?
其实这个问题与bug菌之前谈到的可以定义成void*指针变量,却不能定义为void变量是相同的道理,因为指针的大小一般平台和编译器确定下来就基本确定下来了,它不依赖于所指向的对象类型,同样void也是一个不完全类型。
4
隐藏结构体成员
现在遵循两个原则:
1、不能直接用不完全类型定义变量,可以定义指针:
2、不能够访问其结构体内部成员,因为根本不知道。
参考代码:
1/************filename: App.h*************/
2#ifndef __APP_H__
3#define __APP_H__
4
5
6typedef struct _tag_StObj stObj;
7
8//interface
9stObj * sCreate(int member1,int member2);
10int sAdd(stObj *pObj);
11
12
13#endif
14
15/************filename: App.c*************/
16#include "App.h"
17
18struct _tag_StObj
19{
20 int member1;
21 int member2;
22};
23
24/**********************************
25 * Function : sCreate
26 * Note :创建函数,也是接口函数
27 * Author: bug菌
28 **********************************/
29stObj * sCreate(int member1,int member2)
30{
31 static stObj staticObj;
32
33 staticObj.member1 = member1;
34 staticObj.member2 = member2;
35
36 return &staticObj;
37}
38
39
40/**********************************
41 * Function : sAdd
42 * Note :加法函数,也是接口函数
43 * Author: bug菌
44 **********************************/
45int sAdd(stObj *pObj)
46{
47 return (pObj->member1 + pObj->member2);
48}
49
50/************filename: main.c*************/
51
52#include <stdio.h>
53#include "./App/App.h"
54
55int main(void)
56{
57 stObj *Obj;
58
59 Obj = sCreate(3,5); //内部构造结构体
60
61 printf("result = %d\n",sAdd(Obj)); //调用相应的接口
62
63 return 0;
64}
编译成功,运行OK,结果如下:
那么不完全类型隐藏结构体成员的目的基本上就达到了,以后外部也是无法通过结构体变量直接访问成员了,只能对象自身在相应的.c文件中定义对应的接口,然后声明在对应的interface中供外部使用。
5
but
那么我们回过头来想想这样的不完全类型究竟做了啥?
1)不允许外部访问数据细节,因为这个类型不完整,编译器把握不住~
2)全程通过指针传递,本质上和void*差别不大,但是他可以进行类型的检查,这样代码的数据意义更加的明确。
当我们使用不完全结构体类型,结构体所有的成员都变成了私有,即这一种封装私有数据的方式,且均需要通过相应的接口函数访问,确实一种好的面向对象的封装方式。
但是完全私有的封装还是比较麻烦,还是要做到"公私分明",函数调用也需要一定的开销,就看工程师们怎么去平衡"性价比"了~
最后
今天的内容暂时就到这里了,好好消化,觉得有所收获,记得点个赞哦~~
推荐专辑 点击蓝色字体即可跳转
☞ MCU进阶专辑
☞ “bug说”专辑
☞ 专辑|手撕C语言
☞ 专辑|经验分享