《鸿哥发现》:如何解决函数形参关联的全局变量过多而导致形参过多的问题。
本帖最后由 jianhong_wu 于 2016-10-24 08:45 编辑★坚鸿-深圳:
大家好,我是鸿哥,本群特别节目《鸿哥发现》现在开播。。。
这一次的主题是:如何解决函数形参关联的全局变量过多而导致形参过多的问题。
第一回
函数的形参有什么用?函数的形参主要是起到标识的作用。标识,也就是给我们程序员阅读方便的作用。比如:
一个加法函数:
unsigned char add(unsinged char a,unsigned char b)
{
return (a+b);
}
这里的a和b就是函数形参,它起到作用就是告诉人们,你把两个加数分别代入a和b,然后返回的就是你要的加法运算结果。所以说,这里的形参就是起到入口标识的作用。关键词是:标识,而不是入口。因为函数的入口不是唯一的,而是无穷条路径。为什么这么说?我们把上面的例子改一下,改成全局变量,如下:
一个加法函数:
unsinged char a;//加数
unsigned char b; //加数
unsigned char c;//和
void add(void)
{
c=a+b;
}
看到没,尽管鸿哥用两个void(空的)关键词把原来加法函数的入口(形参)和出口(return返回)都堵得死死的,但是,全局变量是无法阻挡的,它进入一个函数的内部不受任何限制,也就是说,我们做项目的时候,所有的函数的形参和返回都搞成void,然后所有的信息传递和数据传递全部都改用全局变量,这样也是可以把项目做完成的。刚才讲了这么多,就是为了说明,函数的形参是用来做标识的。这是第一回合。
第二回
接着进入第二回合,如果真的把所有的函数都搞成形参是void的函数,全部靠全局变量来传递信息,最大的问题是函数多了之后,阅读非常不方面,你每看到一个被调用的函数,你不能马上猜出来它大概跟哪些全局变量的数据发生了关联,你必须一个一个的去查该函数的源代码定义的部分,所以,C语言的设计者,给了函数非常丰富的形参入口,最理想的函数就是:你把凡是与此函数相关的全局变量都是经过形参的入口才进入到函数内部,也就是说,养成这个习惯,把形参的入口看作是函数的唯一合法入口(尽管不是唯一不是必须),这样只要看函数的形参就知道这个函数跟哪些全局变量有关,这个函数的输入输出就非常清晰了可读性就非常强,比如,我以前的按键函数,
unsigned char Gu8KeySec=0;//触发的按键
void KeyScan(void)//按键扫描
{
static unsigned char Su8KeyLock=0;
static unsigned int Su16KeyCnt=0;
if(1==key_sr) //IO口
{
Su8KeyLock=0;
Su16KeyCnt=0;
}
else if(0==Su8KeyLock)
{
Su16KeyCnt++;
if(Su16KeyCnt>CONST_30ms)
{
Su8KeyLock=1;
Gu8KeySec=1;//触发1号按键
}
}
}
void KeyService(void)
{
if(0==Gu8KeySec)
{
return;
}
switch(Gu8KeySec)
{
case 1: //1号键的处理
Gu8KeySec=0;
break;
}
}
其实void KeyScan(void)和void KeyService(void)的关联就是通过隐藏的Gu8KeySec这个全局变量关联起来的,现在,把它们这两个函数改成规规矩矩的从形参的入口进来,如下:
unsigned char Gu8KeySec=0;//触发的按键
voidKeyScan(unsigned char *pu8KeySec)//按键扫描
{
static unsigned char Su8KeyLock=0;
static unsigned int Su16KeyCnt=0;
if(1==key_sr) //IO口
{
Su8KeyLock=0;
Su16KeyCnt=0;
}
else if(0==Su8KeyLock)
{
Su16KeyCnt++;
if(Su16KeyCnt>CONST_30ms)
{
Su8KeyLock=1;
*pu8KeySec=1;//触发1号按键
}
}
}
void KeyService(unsigned char *pu8KeySec)
{
if(0==(*pu8KeySec))
{
return;
}
switch(*pu8KeySec)
{
case 1: //1号键的处理
*pu8KeySec=0;
break;
}
}
void Interrupt()//某定时中断函数
{
KeyScan(&Gu8KeySec);//按键扫描
}
void main()
{
while(1)
{
KeyService(&Gu8KeySec);
}
}
这是第二回合,主要讲了,用例子进一步讲了函数入口所起的标识作用。
第三回
刚才是讲完了第二回合,主要讲了,用例子进一步讲了函数入口所起的标识作用。现在接着讲第三回合,也就是本期节目最闪光的一部分,刚才那两个回合就是为第三回合作铺垫的,曾经困扰我数年的问题,也即将在第三回合里解密,读懂了第三回合,会让你的技术直接又上一个层次。
函数的入口确实很爽,很清晰,但是,实际项目中,一个函数可能要跟很多全局变量发生关系,那么,这个函数的形参就会变得非常多,函数的形参入口多了,这个函数的门面就感觉非常难看,还不如直接用原来那种全局变量强行进入的方法呢.比如,刚才讲到的按键服务函数,如果多增加了几个要处理的全局变量:
unsigned char a,b,c,d,e;
void KeyService(void)
{
if(0==Gu8KeySec)
{
return;
}
switch(Gu8KeySec) //第1个全局变量
{
case 1: //1号键的处理
a++; //第2个全局变量
b--; //第3个全局变量
c=c+2; //第4个全局变量
d=d+5; //第5个全局变量
e=e+6; //第6个全局变量
Gu8KeySec=0;
break;
}
}
这个函数内部要操作6个全局变量,你也不可能整6个形参一一对应吧,那如果万一是15个呢?所以遇到瓶颈了吧。我解决这个矛盾的方法是:
struct StructAbcde //做成一个结构体数据类型
{
unsigned char a;
unsigned char b;
unsigned char c;
unsigned char d;
unsigned char e;
};
struct StructAbcde GtData; //把5个变量用结构体打成一包
void KeyService(unsigned char *pu8KeySec,struct StructAbcde *ptData)
{
if(0==(*pu8KeySec))
{
return;
}
switch(*pu8KeySec) //第1个全局变量
{
case 1: //1号键的处理
ptData->a++;//第2个全局变量
ptData->b--;//第3个全局变量
ptData->c=ptData->c+2;//第4个全局变量
ptData->d=ptData->d+5;//第5个全局变量
ptData->e=ptData->e+6; //第6个全局变量
*pu8KeySec=0;
break;
}
}
void main() //主函数
{
while(1)
{
KeyService(&Gu8KeySec,&GtData);//2个形参就搞掂
}
}
秘密就是:把N个相关的全局变变量通过结构体打成一包数据,进入函数入口的时候,就以“包”为单位进入,这样就可以让函数的形参看起来非常少,这种方法,就是stm32的库函数一直在用的办法,只是它不告诉你它为什么要这么用,而是被我发现了它最重要的好处就是简化入口的通道数量。
你想想,stm32那么多寄存器,如果没有这种结构体的来支持,它的入口可能几十个形参,那不是要很麻烦?所以,今后鸿哥决定这么玩全局变量,同时,也解决了函数入口的问题。这样,函数与全局变量之间,它们的脉络再也不用隐藏起来,而是可以很清晰的表达清楚,你说,这种功夫是不是绝对上了一层。
只要看函数的形参就知道这个函数跟哪些全局变量有关,高!
页:
[1]