引言:
编写高效简洁的C语言代码,是许多软件工程师追求的目标。本文就工作中的一些体会和经验做相关的阐述,不对的地方请各位指教。
第1招:以空间换时间
计算机程序中最大的矛盾是空间和时间的矛盾,那么,从这个角度出发逆向思维来考虑程序的效率问题,我们就有了解决问题的第1招——以空间换时间。
例如:字符串的赋值。
方法A,通常的办法:
#define LEN 32
char string1 [LEN];
memset (string1,0,LEN);
strcpy (string1,“This is a example!!”);
方法B:
const char string2[LEN] =“This is a example!”;
char * cp;
cp = string2 ;
(使用的时候可以直接用指针来操作。)
从上面的例子可以看出,A和B的效率是不能比的。在同样的存储空间下,B直接使用指针就可以操作了,而A需要调用两个字符函数才能完成。B的缺点在于灵活性没有A好。在需要频繁更改一个字符串内容的时候,A具有更好的灵活性;如果采用方法B,则需要预存许多字符串,虽然占用了大量的内存,但是获得了程序执行的高效率。
如果系统的实时性要求很高,内存还有一些,那我推荐你使用该招数。
该招数的变招——使用宏函数而不是函数。举例如下:
方法C:
#define bwMCDR2_ADDRESS 4
#define bsMCDR2_ADDRESS 17
int BIT_MASK(int __bf)
{
return ((1U << (bw ## __bf)) - 1) << (bs ## __bf);
}
void SET_BITS(int __dst, int __bf, int __val)
{
__dst = ((__dst) & ~(BIT_MASK(__bf))) | (((__val) << (bs ## __bf)) & (BIT_MASK(__bf))))
}
SET_BITS(MCDR2, MCDR2_ADDRESS, RegisterNumber);
方法D:
#define bwMCDR2_ADDRESS 4
#define bsMCDR2_ADDRESS 17
#define bmMCDR2_ADDRESS BIT_MASK(MCDR2_ADDRESS)
#define BIT_MASK(__bf) (((1U << (bw ## __bf)) - 1) << (bs ## __bf))
#define SET_BITS(__dst, __bf, __val)
((__dst) = ((__dst) & ~(BIT_MASK(__bf))) |
(((__val) << (bs ## __bf)) & (BIT_MASK(__bf))))
SET_BITS(MCDR2, MCDR2_ADDRESS, RegisterNumber);
函数和宏函数的区别就在于,宏函数占用了大量的空间,而函数占用了时间。大家要知道的是,函数调用是要使用系统的栈来保存数据的,如果编译器里有栈检查选项,一般在函数的头会嵌入一些汇编语句对当前栈进行检查;同时,CPU也要在函数调用时保存和恢复当前的现场,进行压栈和弹栈操作,所以,函数调用需要一些CPU时间。而宏函数不存在这个问题。宏函数仅仅作为预先写好的代码嵌入到当前程序,不会产生函数调用,所以仅仅是占用了空间,在频繁调用同一个宏函数的时候,该现象尤其突出。 D方法是我看到的最好的置位操作函数,是ARM公司源码的一部分,在短短的三行内实现了很多功能,几乎涵盖了所有的位操作功能。C方法是其变体,其中滋味还需大家仔细体会。
第2招:数学方法解决问题
现在我们演绎高效C语言编写的第二招——采用数学方法来解决问题。
数学是计算机之母,没有数学的依据和基础,就没有计算机的发展,所以在编写程序的时候,采用一些数学方法会对程序的执行效率有数量级的提高。
举例如下,求 1~100的和。
方法E
int I , j;
for (I = 1 ;I<=100; I ++){
j += I;
}
方法F
int I;
I = (100 * (1+100)) / 2
这个例子是我印象最深的一个数学用例,是我的计算机启蒙老师考我的。当时我只有小学三年级,可惜我当时不知道用公式 N×(N+1)/ 2 来解决这个问题。方法E循环了100次才解决问题,也就是说最少用了100个赋值,100个判断,200个加法(I和j);而方法F仅仅用了1个加法,1 次乘法,1次除法。效果自然不言而喻。所以,现在我在编程序的时候,更多的是动脑筋找规律,最大
第3招:使用位操作,减少除法和取模的运算
在计算机程序中,数据的位是可以操作的最小数据单位,理论上可以用“位运算”来完成所有的运算和操作。一般的位操作是用来控制硬件的,或者做数据变换使用,但是,灵活的位操作可以有效地提高程序运行的效率。举例如下:
方法G
int I,J;
I = 257 /8;
J = 456 % 32;
方法H
int I,J;
I = 257 >>3;
J = 456 - (456 >> 4 << 4);
在字面上好像H比G麻烦了好多,但是,仔细查看产生的汇编代码就会明白,方法G调用了基本的取模函数和除法函数,既有函数调用,还有很多汇编代码和寄存器参与运算;而方法H则仅仅是几句相关的汇编,代码更简洁,效率更高。当然,由于编译器的不同,可能效率的差距不大,但是,以我目前遇到的MS C ,ARM C 来看,效率的差距还是不小。相关汇编代码就不在这里列举了。
运用这招需要注意的是,因为CPU的不同而产生的问题。比如说,在PC上用这招编写的程序,并在PC上调试通过,在移植到一个16位机平台上的时候,可能会产生代码隐患。所以只有在一定技术进阶的基础下才可以使用这招。
第4招:汇编嵌入
高效C语言编程的必杀技,第四招——嵌入汇编。
“在熟悉汇编语言的人眼里,C语言编写的程序都是垃圾”。这种说法虽然偏激了一些,但是却有它的道理。汇编语言是效率最高的计算机语言,但是,不可能靠着它来写一个操作系统吧?所以,为了获得程序的高效率,我们只好采用变通的方法 ——嵌入汇编,混合编程。
举例如下,将数组一赋值给数组二,要求每一字节都相符。
char string1[1024],string2[1024];
方法I
int I;
for (I =0 ;I<1024;I++)
*(string2 + I) = *(string1 + I)
方法J
#ifdef _PC_
int I;
for (I =0 ;I<1024;I++)
*(string2 + I) = *(string1 + I);
#else
#ifdef _ARM_
__asm
{
MOV R0,string1
MOV R1,string2
MOV R2,#0
loop:
LDMIA R0!, [R3-R11]
STMIA R1!, [R3-R11]
ADD R2,R2,#8
CMP R2, #400
BNE loop
}
#endif
方法I是最常见的方法,使用了1024次循环;方法J则根据平台不同做了区分,在ARM平台下,用嵌入汇编仅用128次循环就完成了同样的操作。这里有朋友会说,为什么不用标准的内存拷贝函数呢?这是因为在源数据里可能含有数据为0的字节,这样的话,标准库函数会提前结束而不会完成我们要求的操作。这个例程典型应用于LCD数据的拷贝过程。根据不同的CPU,熟练使用相应的嵌入汇编,可以大大提高程序执行的效率。
虽然是必杀技,但是如果轻易使用会付出惨重的代价。这是因为,使用了嵌入汇编,便限制了程序的可移植性,使程序在不同平台移植的过程中,卧虎藏龙,险象环生!同时该招数也与现代软件工程的思想相违背,只有在迫不得已的情况下才可以采用。切记,切记。
限度地发挥数学的威力来提高程序运行的效率。
C语言程序设计技巧之命令行参数
在许多应用软件运行时都带有命令行参数,其实这些命令行参数在C语言编写的程序中也可以实现,灵活地运用命令行参数进行处理可以有效地提高程序的运行效率,收到事半功倍的效果。
C语言中有关命令行参数涉及到程序的主函数main(int argc,char *argv[]这样两个参数,其中,int argc表示命令行参数的个数(包括可执行程序名本身),char *argv[]表示每个参数的具体内容,argv[0]为命令行中可执行程序名本身,argv[1]为命令行中第二个参数的内容,依次类推。如下例输出命令行参数的个数及参数的内容:
main (int argc,char *argv[],
{int I;
printf(\n命令行中可执行文件名为:%s,argv[0]);
printf(\n总共有%d个参数:,argc);
I=0;
while(argc>=1)
{printf(″%s ,argv[I++]);
argc--;}
}
命令行参数用的最多还是在诸如DIR A:等之类带有盘符、路径或文件名这样的命令行中,所以说灵活处理这一类参数才能有效地提高程序的运行效果。譬如DIR命令,其后可以是盘符,可以是路径,也可以是文件名,如何区分这一参数呢?请看下例(此程序模拟DIR命令,程序要求在命令行输入一个参数:盘符或路径或文件名,若无参数或参数多于一个都将取默认的参数“*.*”)。
\*--------------------
功能:模拟DIR命令进行处理命令行参数
--------------------*/
#include
#include
#include
#inchlude
int j,num=0;
char ss[20],path[50],path2[50];
void main (int argc,char *argv[])
{
struct ffblk f;
int done;
if(argc==2) /*取命令行参数到数组中*/
strcpy(ss,argv[1]);
else
strcpy(ss,″*.*″); /*给数组赋值缺省参数*/
if (((ss[strlen(ss)-1]==′\\′||((ss[strlen(ss)-1]==':'))
strcat(ss,″*.*″); /*若参数为路径或盘符,则加上″*.*″ */
getcwd(path1,50); /*取当前路径*/
if (chdir(ss)==0) /*判断参数是否为路径*/
strcat(ss,\\*.*); /*若路径末没有带\,则加上*.* */
chdir(path1); /*恢复原来路径*/
strcpy(path2,ss);
for(j=strlen(path2);j>0;j--)/*提取参数中的路径到path2 */
{if((path2[j]=='\\'))||(path2[j]==':')){
path2[j+1]='\0';
goto senull;}
}
path2[0]='\0';
senull:
if(strlen(path2)==0) /* 若给出的参数中没带路径,则取当前路径*/
strcpy(path2,path1);
printf(\n**模拟DIR**\n 命令目录路径%s,path2);
done=findfirst(ss,&f,55); /*查找第一个配匹的文件*/
j=1;
while(!done)
{if (f.ff_attrib!=0x10) /* 若文件属性不是目录 */
printf(\n %15s %20ld,f.ff_name,f.ff_fsize);
else
printf(\n &11s ,f.ff_name);
num++;
j++;
if(j==23)
printf(\n --------More (按任意键继续)----);
getch();
j=0;
printf(″\n (目录路径%s)″,path2);}
done=findnext(&f); /*查找下一个配匹的文件*/
}
printf(″\n 当前目录中总共有%d个文件.\n″,num);
C语言的32个关键字
一、数据类型关键字(12个):
1、char [tʃɑ:]:声明字符型变量或函数
(1)主要内容字符:容纳单字符的一种基本数据类型;(2)n.炭;女清洁工 vt.烧焦;
(3)字符类型:字符型(Char) c、字符串型(String) s 、二进制型(Binary) bn、布尔型(Boolean) b 、日期时间型(DateTime) d 、数组型(Array) a、象型(Object) o 、循环控制变量通常使用单一的字符;
2、double [ˈdʌbəl] :声明双精度变量或函数
(1)n. 两倍;(2)a. 两倍的,双重的;(3)v. 加倍的,快步走,加倍努力
3、enum :声明枚举类型
(1)枚举:枚举是一个被命名的整型常数的;(2)枚举类型;(3)列举型;
(4)列举enumerate [iˈnju:məreit]
4、float [fləut] :声明浮点型变量或函数
(1)浮点数、(2)浮点型、(3)漂浮、(4)浮动
5、int[int]:声明整型变量或函数
(1)符号整数、(2)取整、(3)Int是 integer ['intidʒə] 的简写
6、long [lɔŋ] :声明长整型变量或函数
(1)长整型(2)a./ ad.长(期)的(地)(3) n.长时间(4)vi.渴望
7、short [ʃɔ:t] :声明短整型变量或函数
(1)a. 短的,矮的、(2)n. 短裤、(3)adv. 短暂地;突然地,急地
8、signed:声明有符号类型变量或函数
(1)有符号的、(2)带正负号、(3)sign [sain] n.标记,符号;招牌;迹象 v.签(署)
9、struct:声明结构体变量或函数
(1)n.结构(2)结构体(4)创建构架数组(3)structural[ˈstrʌktʃərəl]a.结构的
10、union [ˈju:niən]:声明共用体(联合)数据类型
(1)联合、(2)n.工会,联盟、(3)合并、(4)团结
11、unsigned [ʌn'saind]:声明无符号类型变量或函数
(1)无符号的
12、void [vɔid] :声明函数无返回值或无参数,声明无类型指针(基本上就这三个作用)
(1)a.无效的、(2)没有的、(3)vt.使无效、(4)n.空虚感
二、控制语句关键字(12个):
A循环语句
1、for [fə, fɔ:]:一种循环语句(可意会不可言传)
2、do [du, du:] :循环语句的循环体
3、while [wail] :循环语句的循环条件
(1)conj.当…的时;(2)而;(3)虽然 n.一会儿 vt.消磨
4、break [breik]:跳出当前循环
(1)中断、(2)断开、(3)n.休息 vt.打破
5、 continue[kənˈtinju:]:结束当前循环,开始下一轮循环
(1)v.继续,延续,延伸
B条件语句
1、if [if]: 条件语句
(1)条件函数、(2)conj.如果,假如、(3)是否、(4)即使、(5)无论何时
2、else [els] :条件语句否定分支(与 if 连用)
(1)a. 别的(2)ad. 其他,另外
3、goto:无条件跳转语句
(1)跳转、(2)转向((3)跳转到
C开关语句
1、switch [switʃ]:用于开关语句
(1)n. 开关,转换,接通或切断…电流,转动、(2)v. 转变,切换,摆动
2、case [keis]:开关语句分支
(1)n.事例、(2)情况、(3)手提箱(4)盒(5)案例
3、default [diˈfɔ:lt]:开关语句中的“其他”分支
(1)预设、(2)n. 假设值,默认(值),不履行责任,缺席(3)v. 默认,不履行义务,缺席,拖欠(4) [计算机] 缺省
D返回语句
1、return [riˈtə:n]:子程序返回语句(可以带参数,也看不带参数)
(1)v.返回、(2)恢复、(3)归还、(4)盈利
三、存储类型关键字(4个)
1、auto [ˈɔ:təu] :声明自动变量(一般不使用)
(1)自动的、(2)汽车automobile [ˈɔ:təməubi:l]
2、extern:声明变量是在其他文件正声明(也可以看做是引用变量)
(1)外部(的)、(2)external [ikˈstə:nəl]a.外部的,外面的,外表的
3、register [ˈredʒistə]:声明积存器变量
(1)寄存器、(2)注册(表)(3)登记(表)
4、static[ˈstætik]:声明静态变量
(1)a. 静态的,静电的、(2)n. 静电,静电干扰
四、其它关键字(4个):
1、const :声明只读变量
(1)常量、(2)常数、(3)编译时常量
2、sizeof:计算数据类型长度
(1)n. …的大小、(2)占的字节数(3)size [saiz]n.大小,尺寸 vt.按大小排列(或分类)
3、typedef:用以给数据类型取别名(当然还有其他作用)
(1)n. 类型定义、(2)数据类型说明(3)type [taip]n.类型,种类,品种;铅字 v.打(字)
4、volatile [ˈvɔlətail]:说明变量在程序执行中可被隐含地改变
(1)a.动荡不定的、(2)反复无常的、(3)易挥发的