C语言技巧篇.docx
- 文档编号:11144412
- 上传时间:2023-02-25
- 格式:DOCX
- 页数:30
- 大小:31.41KB
C语言技巧篇.docx
《C语言技巧篇.docx》由会员分享,可在线阅读,更多相关《C语言技巧篇.docx(30页珍藏版)》请在冰豆网上搜索。
C语言技巧篇
优化C代码常用的几招:
在性能优化方面永远注意80-20原则,即20%的程序消耗了80%的运行时间,因而我们要改进效率,最主要是考虑改进那20%的代码。
不要优化程序中开销不大的那80%,这是劳而无功的。
第一招:
以空间换时间
计算机程序中最大的矛盾是空间和时间的矛盾,那么,从这个角度出发逆向思维来考虑程序的效率问题,我们就有了解决问题的第1招--以空间换时间。
比如说字符串的赋值:
方法A:
通常的办法
#defineLEN32
charstring1[LEN];
memset(string1,0,LEN);
strcpy(string1,"Thisisaexample!
!
");
方法B:
constcharstring2[LEN]="Thisisaexample!
";
char*cp;
cp=string2;
使用的时候可以直接用指针来操作。
从上面的例子可以看出,A和B的效率是不能比的。
在同样的存储空间下,B直接使用指针就可以操作了,而A需要调用两个字符函数才能完成。
B的缺点在于灵活性没有A好。
在需要频繁更改一个字符串内容的时候,A具有更好的灵活性;如果采用方法B,则需要预存许多字符串,虽然占用了大量的内存,但是获得了程序执行的高效率。
如果系统的实时性要求很高,内存还有一些,那我推荐你使用该招数。
第二招:
使用宏而不是函数。
这也是第一招的变招。
函数和宏的区别就在于,宏占用了大量的空间,而函数占用了时间。
大家要知道的是,函数调用是要使用系统的栈来保存数据的,如果编译器里有栈检查选项,一般在函数的头会嵌入一些汇编语句对当前栈进行检查;同时,CPU也要在函数调用时保存和恢复当前的现场,进行压栈和弹栈操作,所以,函数调用需要一些CPU时间。
而宏不存在这个问题。
宏仅仅作为预先写好的代码嵌入到当前程序,不会产生函数调用,所以仅仅是占用了空间,在频繁调用同一个宏的时候,该现象尤其突出。
举例如下:
方法C:
#definebwMCDR2_ADDRESS4
#definebsMCDR2_ADDRESS17
intBIT_MASK(int__bf)
{
return((1U<<(bw##__bf))-1)<<(bs##__bf);
}
voidSET_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:
#definebwMCDR2_ADDRESS4
#definebsMCDR2_ADDRESS17
#definebmMCDR2_ADDRESSBIT_MASK(MCDR2_ADDRESS)
#defineBIT_MASK(__bf)
(((1U<<(bw##__bf))-1)
<<(bs##__bf))
#defineSET_BITS(__dst,__bf,__val)
\
((__dst)=((__dst)&~(BIT_MASK(__bf)))
|\
(((__val)<<(bs##__bf))
&(BIT_MASK(__bf))))
SET_BITS(MCDR2,MCDR2_ADDRESS,
RegisterNumber);
D方法是我看到的最好的置位操作函数,是ARM公司源码的一部分,在短短的三行内实现了很多功能,几乎涵盖了所有的位操作功能。
C方法是其变体,其中滋味还需大家仔细体会。
C语言高效编程技巧:
第1招:
以空间换时间
计算机程序中最大的矛盾是空间和时间的矛盾,那么,从这个角度出发逆向思维来考虑程序的效率问题,我们就有了解决问题的第1招——以空间换时间。
例如:
字符串的赋值。
方法A,通常的办法:
#defineLEN32
charstring1[LEN];
memset(string1,0,LEN);
strcpy(string1,“Thisisaexample!
!
”);
方法B:
constcharstring2[LEN]=“Thisisaexample!
”;
char*cp;
cp=string2;
(使用的时候可以直接用指针来操作。
)
从上面的例子可以看出,A和B的效率是不能比的。
在同样的存储空间下,B直接使用指针就可以操作了,而A需要调用两个字符函数才能完成。
B的缺点在于灵活性没有A好。
在需要频繁更改一个字符串内容的时候,A具有更好的灵活性;如果采用方法B,则需要预存许多字符串,虽然占用了大量的内存,但是获得了程序执行的高效率。
如果系统的实时性要求很高,内存还有一些,那我推荐你使用该招数。
该招数的变招——使用宏函数而不是函数。
举例如下:
方法C:
#definebwMCDR2_ADDRESS4
#definebsMCDR2_ADDRESS17
intBIT_MASK(int__bf)
{
return((1U<<(bw##__bf))-1)<<(bs##__bf);
}
voidSET_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:
#definebwMCDR2_ADDRESS4
#definebsMCDR2_ADDRESS17
#definebmMCDR2_ADDRESSBIT_MASK(MCDR2_ADDRESS)
#defineBIT_MASK(__bf)(((1U<<(bw##__bf))-1)<<(bs##__bf))
#defineSET_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
intI,j;
for(I=1;I<=100;I++){
j+=I;
}
方法F
intI;
I=(100*(1+100))/2
这个例子是我印象最深的一个数学用例,是我的计算机启蒙老师考我的。
当时我只有小学三年级,可惜我当时不知道用公式N×(N+1)/2来解决这个问题。
方法E循环了100次才解决问题,也就是说最少用了100个赋值,100个判断,200个加法(I和j);而方法F仅仅用了1个加法,1次乘法,1次除法。
效果自然不言而喻。
所以,现在我在编程序的时候,更多的是动脑筋找规律,最大限度地发挥数学的威力来提高程序运行的效率。
第3招:
使用位操作
实现高效的C语言编写的第三招——使用位操作,减少除法和取模的运算。
在计算机程序中,数据的位是可以操作的最小数据单位,理论上可以用“位运算”来完成所有的运算和操作。
一般的位操作是用来控制硬件的,或者做数据变换使用,但是,灵活的位操作可以有效地提高程序运行的效率。
举例如下:
方法GintI,J;I=257/8;J=456%32;方法HintI,J;I=257>>3;J=456-(456>>4<<4); 在字面上好像H比G麻烦了好多,但是,仔细查看产生的汇编代码就会明白,方法G调用了基本的取模函数和除法函数,既有函数调用,还有很多汇编代码和寄存器参与运算;而方法H则仅仅是几句相关的汇编,代码更简洁,效率更高。
当然,由于编译器的不同,可能效率的差距不大,但是,以我目前遇到的MSC,ARMC来看,效率的差距还是不小。
相关汇编代码就不在这里列举了。
运用这招需要注意的是,因为CPU的不同而产生的问题。
比如说,在PC上用这招编写的程序,并在PC上调试通过,在移植到一个16位机平台上的时候,可能会产生代码隐患。
所以只有在一定技术进阶的基础下才可以使用这招。
第4招:
汇编嵌入 高效C语言编程的必杀技,第四招——嵌入汇编。
“在熟悉汇编语言的人眼里,C语言编写的程序都是垃圾”。
这种说法虽然偏激了一些,但是却有它的道理。
汇编语言是效率最高的计算机语言,但是,不可能*着它来写一个操作系统吧?
所以,为了获得程序的高效率,我们只好采用变通的方法——嵌入汇编,混合编程。
举例如下,将数组一赋值给数组二,要求每一字节都相符。
charstring1[1024],string2[1024];方法IintI;for(I=0;I<1024;I++)*(string2+I)=*(string1+I)方法J#ifdef_PC_intI;for(I=0;I<1024;I++)*(string2+I)=*(string1+I);#else#ifdef_ARM___asm{MOVR0,string1MOVR1,string2MOVR2,#0loop:
LDMIAR0!
[R3-R11]STMIAR1!
[R3-R11]ADDR2,R2,#8CMPR2,#400BNEloop}#endif 方法I是最常见的方法,使用了1024次循环;方法J则根据平台不同做了区分,在ARM平台下,用嵌入汇编仅用128次循环就完成了同样的操作。
这里有朋友会说,为什么不用标准的内存拷贝函数呢?
这是因为在源数据里可能含有数据为0的字节,这样的话,标准库函数会提前结束而不会完成我们要求的操作。
这个例程典型应用于LCD数据的拷贝过程。
根据不同的CPU,熟练使用相应的嵌入汇编,可以大大提高程序执行的效率。
虽然是必杀技,但是如果轻易可能使用会付出惨重的代价。
这是因为,使用了嵌入汇编,便限制了程序的可移植性,使程序在不同平台移植的过程中,卧虎藏龙,险象环生!
同时该招数也与现代软件工程的思想相违背,只有在迫不得已的情况下才可以采用。
切记,切记。
链表的C语言实现之动态链表:
一、为什么用动态内存分配
但我们未学习链表的时候,如果要存储数量比较多的同类型或同结构的数据的时候,总是使用一个数组。
比如说我们要存储一个班级学生的某科分数,总是定义一个float型(存在0.5分)数组:
floatscore[30];
但是,在使用数组的时候,总有一个问题困扰着我们:
数组应该有多大?
在很多的情况下,你并不能确定要使用多大的数组,比如上例,你可能并不知道该班级的学生的人数,那么你就要把数组定义得足够大。
这样,你的程序在运行时就申请了固定大小的你认为足够大的内存空间。
即使你知道该班级的学生数,但是如果因为某种特殊原因人数有增加或者减少,你又必须重新去修改程序,扩大数组的存储范围。
这种分配固定大小的内存分配方法称之为静态内存分配。
但是这种内存分配的方法存在比较严重的缺陷,特别是处理某些问题时:
在大多数情况下会浪费大量的内存空间,在少数情况下,当你定义的数组不够大时,可能引起下标越界错误,甚至导致严重后果。
那么有没有其它的方法来解决这样的外呢体呢?
有,那就是动态内存分配。
所谓动态内存分配就是指在程序执行的过程中动态地分配或者回收存储空间的分配内存的方法。
动态内存分配不象数组等静态内存分配方法那样需要预先分配存储空间,而是由系统根据程序的需要即时分配,且分配的大小就是程序要求的大小。
从以上动、静态内存分配比较可以知道动态内存分配相对于景泰内存分配的特点:
1、不需要预先分配存储空间;
2、分配的空间可以根据程序的需要扩大或缩小。
二、如何实现动态内存分配及其管理
要实现根据程序的需要动态分配存储空间,就必须用到以下几个函数
1、malloc函数
malloc函数的原型为:
void*malloc(unsignedintsize)
其作用是在内存的动态存储区中分配一个长度为size的连续空间。
其参数是一个无符号整形数,返回值是一个指向所分配的连续存储域的起始地址的指针。
还有一点必须注意的是,当函数未能成功分配存储空间(如内存不足)就会返回一个NULL指针。
所以在调用该函数时应该检测返回值是否为NULL并执行相应的操作。
下例是一个动态分配的程序:
#include
#include
main()
{
intcount,*array;/*count是一个计数器,array是一个整型指针,也可以理解为指向一个整型数组的首地址*/
if((array(int*)malloc(10*sizeof(int)))==NULL)
{
printf(不能成功分配存储空间。
);
exit
(1);
}
for(count=0;count〈10;count++)/*给数组赋值*/
array[count]=count;
for(count=0;count〈10;count++)/*打印数组元素*/
printf(%2d,array[count]);
}
上例中动态分配了10个整型存储区域,然后进行赋值并打印。
例中if((array(int*)malloc(10*sizeof(int)))==NULL)语句可以分为以下几步:
1)分配10个整型的连续存储空间,并返回一个指向其起始地址的整型指针
2)把此整型指针地址赋给array
3)检测返回值是否为NULL
2、free函数
由于内存区域总是有限的,不能不限制地分配下去,而且一个程序要尽量节省资源,所以当所分配的内存区域不用时,就要释放它,以便其它的变量或者程序使用。
这时我们就要用到free函数。
其函数原型是:
voidfree(void*p)
作用是释放指针p所指向的内存区。
其参数p必须是先前调用malloc函数或calloc函数(另一个动态分配存储区域的函数)时返回的指针。
给free函数传递其它的值很可能造成死机或其它灾难性的后果。
注意:
这里重要的是指针的值,而不是用来申请动态内存的指针本身。
例:
int*p1,*p2;
p1=malloc(10*sizeof(int));
p2=p1;
……
free(p2)/*或者free(p2)*/
malloc返回值赋给p1,又把p1的值赋给p2,所以此时p1,p2都可作为free函数的参数。
malloc函数是对存储区域进行分配的。
free函数是释放已经不用的内存区域的。
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)易挥发的
C语言指针的奥妙
指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。
要搞清一个指针需要搞清指针的四方面的内容:
指针的类型,指针所指向的类型,指针的值或者叫指针所指向的内存区,还有指针本身所占据的内存区。
让我们分别说明。
先声明几个指针放着做例子:
例一:
(1)int*ptr;
(2)char*ptr;
(3)int**ptr;
(4)int(*ptr)[3];
(5)int*(*ptr)[4];
指针的类型
从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。
这是指针本身所具有的类型。
让我们看看例一中各个指针的类型:
(1)int*ptr;//指针的类型是int*
(2)char*ptr;//指针的类型是char*
(3)int**ptr;//指针的类型是int**
(4)int(*ptr)[3];//指针的类型是int(*)[3]
(5)int*(*ptr)[4];//指针的类型是int*(*)[4]
怎么样?
找出指针的类型的方法是不是很简单?
指针所指向的类型
当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。
从语法上看,你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。
例如:
(1)int*ptr;//指针所指向的类型是int
(2)char*ptr;//指针所指向的的类型是char
(3)int**ptr;//指针所指向的的类型是int*
(4)int(*ptr
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 语言 技巧