赞
踩
C++的方法和成员变量都是放在类里面的,通过类的实例化来访问其中的方法和成员,C++甚至还引入了命名空间,通过命名空间访问类,再实例化类。C语言也可以模拟C++的对类的封装,通过编码规范来实现,或者说,定了一套编码框架,按照这个框架来做,就可以实现封装。
先来看下面一段C代码。
/*mathOpr.c*/
#include<stdio.h>
int add(int data1, int data2)
{
return data1 + data2;
}
int sub(int data1, int data2)
{
return data1 - data2;
}
这段代码,定义了2个函数,add和sub,其他文件可以直接访问这2个函数,比如下面的代码。
/*main.c*/
#include <stdio.h>
#include "mathOpr.c"
int main(int argc, char *argv[])
{
int ret = add(1, 2);
printf("%d\n", ret);
ret = sub(4, 3);
printf("%d\n", ret);
return 0;
}
gcc -o main main.c
./main
我现在要把mathOpr.c封装,再加个mathOpr.h文件。
.h文件
/*mathOpr.h*/ #ifndef __MATH_OPR_H #define __MATH_OPR_H #include <stdio.h> /*封装函数,这2个函数也可以直接被调用*/ int add(int data1, int data2); int sub(int data1, int data2); /*定义封装函数结构体*/ typedef struct{ int (*add)(int data1, int data2); int (*sub)(int data1, int data2); } MathOprFunc; /*封装函数变量*/ static MathOprFunc m_mathOprFunc = { .add = add,/*指向上面的add函数*/ .sub = sub /*指向上面的sub函数*/ }; #endif
.c文件,在原来基础上,加上include上面的.h文件
/*mathOpr.c*/
#include<stdio.h>
int add(int data1, int data2)
{
return data1 + data2;
}
int sub(int data1, int data2)
{
return data1 - data2;
}
main.c文件,注意看,是#include “mathOpr.h”,而不是#include “mathOpr.c”
/*main.c*/
#include <stdio.h>
#include "mathOpr.h"
int main(int argc, char *argv[])
{
int ret;
ret = m_mathOprFunc.add(1, 2);/*通过封装变量m_mathOprFunc访问add函数*/
printf("%d\n", ret);
ret = m_mathOprFunc.sub(4, 3);/*通过封装变量m_mathOprFunc访问sub函数*/
printf("%d\n", ret);
return 0;
}
现在,不是直接编译成main可执行文件,而需要先把mathOpr.c编译成mathOpr.so,再把mathOpr.so作为main.c的编译文件。
#编译mathOpr.c
gcc -c mathOpr.c
#生成mathOpr.so
gcc -fpic -shared -o mathOpr.so mathOpr.o
#编译成main可执行文件
gcc -o main main.c mathOpr.so
#执行
./main
得到的结果和上面的一样。
为了实现封装,加了很多代码,而且编译过程也复杂了,感觉没必要进行封装了。但是,正如我当初学C语言,觉得C语言没啥用,1+1,我用计算器就可以算出来,没必要写那几行代码。当一个代码文件达到了数百行,甚至上千行,并且代码文件的函数有几十个,对函数和变量的封装就有必要了,有兴趣的可以用C++做同样的封装,代码量是差不多的。
一开始就提到,通过编码规范来实现封装,mathOpr.h里面的注释也提到,可以直接调用add和sub函数,万一其他代码文件也有类似功能的函数,命名也一样,那就会冲突了。所以,就通过命名规范(属于编码规范的内容)来区别,在所有的变量/结构体/函数前加上文件名称,在.h文件有体现,MathOprFunc和m_mathOprFunc就是,修改后的的.h文件和.c文件如下
/*mathOpr.h*/ #ifndef __MATH_OPR_H #define __MATH_OPR_H #include <stdio.h> /*封装函数,这2个函数也可以直接被调用*/ int mathOprAdd(int data1, int data2); int mathOprSub(int data1, int data2); /*定义封装函数*/ typedef struct{ int (*add)(int data1, int data2); int (*sub)(int data1, int data2); } MathOprFunc; /*封装函数变量,用add代替mathOprAdd,sub代替mathOprSub*/ MathOprFunc m_mathOprFunc = { .add = mathOprAdd, .sub = mathOprSub }; #endif
/*mathOpr.c*/
#include<stdio.h>
int mathOprAdd(int data1, int data2)
{
return data1 + data2;
}
int mathOprSub(int data1, int data2)
{
return data1 - data2;
}
main.c不需要改动,此时,通过访问m_mathOprFunc.add来访问mathOprAdd函数,这里看到封装的痕迹了吧。
这种封装,会不会损耗性能呢,封装前是直接访问函数,封装后是通过变量来访问函数指针,达到访问函数的目的。可以直接的说,性能损耗是肯定的,因为多做了动作,但是,可以通过在m_mathOprFunc变量前加static,再编译时加-O3级别优化,性能是没有损失的,编译器的优化可以做到直接用mathOprAdd来替换m_mathOprFunc.add。同时,相对函数实现功能的耗时来说,访问函数的耗时微不足道,除非像这里的很简单的功能,有兴趣的可以写代码测试一下。
上面说的是封装函数,接下来是封装变量。
代码可能会用到全局静态变量,仅限于当前代码使用,比如static int giTmp;下面3个代码文件是示例
.h文件
/*mathOpr.h*/ #include <stdio.h> /*定义封装变量结构体*/ typedef struct { int data1; int data2; } MathOprVar; /*初始化封装变量*/ static inline MathOprVar mathOprVarInit() { MathVar var = { .data1 = 10, .data2 = 20 }; return var; } /*封装函数,这2个函数也可以直接被调用*/ int mathOprAdd(MathOprVar *var); int mathOprSub(MathOprVar *var); /*定义封装函数*/ typedef struct { int (*add)(MathOprVar *var); int (*sub)(MathOprVar *var); } MathOprFunc; /*封装函数变量,用add代替mathOprAdd,sub代替mathOprSub*/ static MathOprFunc m_mathOprFunc = { .add = mathOprAdd, .sub = mathOprSub, };
.c文件
/*mathOpr.c*/
#include "mathOpr.h"
#include <stdio.h>
int mathOprAdd(MathVar *var)
{
return var->data1 + var->data2;
}
int mathOprSub(MathVar *var)
{
return var->data1 - var->data2;
}
main.c
#include "mathOpr.h"
#include <stdio.h>
int main(int argc, char *argv[])
{
MathOprVar var = mathOprVarInit();/*调用变量初始化函数*/
printf("%d %d\n", m_mathOprFunc.add(&var), m_mathOprFunc.sub(&var));
MathOprVar var2 = {12,23}; /*自己定义初始化的值*/
printf("%d %d\n", m_mathOprFunc.add(&var2), m_mathOprFunc.sub(&var2));
return 0;
}
从.h文件可以看到,多了结构体MathOprVar和函数mathOprVarInit。其中,结构体MathOprVar可以看作C++里面的类,而函数mathOprVarInit可以看作是构造函数,还可以加个mathOprRelease作为析构函数。
看main.c里面,可以用MathOprVar定义不同的变量,而且操作起来互不影响,因为,都是从参数传入的,并且操作的都是参数变量。由于每个函数都必须传入MathOprVar变量,这个变量当作不同函数交流的媒介,可以当作全局变量用。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。