当前位置:Linux教程 - Linux - Unix(Linux) C编程问题精粹

Unix(Linux) C编程问题精粹

Unix(Linux) C编程问题精粹




=======================================
|版权声明:你可以自由复制与分发本文档 |
|如果你要修改本文档或提出更好建议,请 |
|先通知文章的作者。而不要仅在公告栏中 |
|贴出。因为作者可能漏看。无论如何,都 |
|须保留本声明。 |
| |
| |
| [email protected] |
| |
|04-11-2000 V0.1 |
=======================================

文章目录
第一章:前言
第二章:约定
第三章:开始任务
第四章:使用lint
第五章:使用make
第六章:优质无错编程
第七章:调试技术
第八章:其它更好的文档

第一章:前言


对于C语言,有人认为它已经落伍了.对于这个问题,仁者见仕,智者见智.的确,C++比C有更强大的诸多优势.但C++是建立在C之上的.这也是Herbert Schildt所著的<>在全世界畅销不衰的原因.更何况,要深入学习LINUX就必需要有相当的C功底.(这也是我搜集整理本文的根由:-)
现结合个人在编程中的体会,为使新手少走弯路,为老手锦上添花,因此无论你是使用C或C++编程,也无论你是程序设计的初学者还是成熟的专业人员,均会发现,本文将会对你有所收益.当然,我尽力写得清晰易懂,又不古板.
我爱C.(正如世人爱上帝一样:-)..

你可以在forum.linuxaid.com.cn上获得此帖的文本.而其HTML版本正在赶制之中......
如果你是在一个月之后看到本文,那么此文或许已经更新了:-)


第二章:约定

专业的源程书写风格.
先看看世界级C大师的源程书写风格.如 Steve Maguire 就有许多不错的建议.

[]倡导使用易于理解的""匈牙利式""的命名约定.
所有的字符变量均以ch开始; 如: char ch_****;
所有的字节变量均冠以b; 如: byte b_****;
所有的长字变量均冠以l; 如: long l_****;
所有的指针变量均冠以P; 如: char *p_ch_****;
建议类型派生出的基本名字之后加上一个以大写字母开头的""标签"".如:
分析 char **ppchMydata;
其让人一眼就能看出它代表一个指向字符指针Mydata的指针.
""匈牙利式""命名的最大不足是难念:-(( .但相对于不是总统演讲稿的C源程来说,这又算得了什么?想想看以下的数据命名:
char a,b,c;
long d,e,f;
.
.
.
(反正我是不会再看下去了...)

[]倡导规范书写.
如果你思如泉涌,而不去也不及顾虑书写格式,那也没关系.在将其交出去之前,用cb命令格式化你的源程.虽然源程的格式不会影响到你编译结果的正确性,但切记,能让其他的程序员能轻松地阅读它.否则没人会理你的.
关于cb命令的更多用法,可以用man cb来参考其手册页.
当然除了cb之外,还有更多更好的.但cb是你在任何UNIX(LINUX)上都找得到的.更何况它并不差.


第三章:开始任务

开始任务之前,先做个深呼吸!



[]其他文档你准备好了吗?
你是不是除了C源程之外一无所有了吗?兵马未动,粮草先行.你必须先清楚该程序所要完成的功能.在开始写程序之前,对程序的功能应有规范说明.书写规范书和确知程序功能的一个方法是先编写相应的操作手册.如果你是一人单干,劝你首先写需求书.切记切记,这对你意味着事半功倍的大好事.
一个实例:我计划为本行的信贷子功能模块打一个补丁.我用10周的时间用来写规划书,需求书,操作流程,使用说明等等文档.之后用2周的时间编写程序,在初步测试(1周)后递交给各信贷部门测试使用.然后根据反馈的信息再更改相应文档,并根据文档修改源程.6个月后发布正式版.

[]一定该遵循ANSI标准吗?
如果你仅使用ANSI的标准首标文件,恭喜你,你的程序有着全世界范围内的广泛支持和兼容.光明无限.但你必须在通用与专用之间做出取舍,对不起,我帮不了你.
我的原则是:核心用ANSI,界面按需而取.这样在转换平台时仅需另编用户界面而已.实用至上嘛.
附:ANSI 标准C头文件






是不是很寒酸?

[]再续前缘?
在得到新任务之后并在开始该新任务之前应马上回想有哪些是曾经拥有的.旧调重弹远比另起炉灶来的高效与环保.

[]是否该有自已的库?
我的答案是应该有自已的特色库,并与ANSI兼容.与3.8不同的是,你仅需在源程序之后附上自已的专用库就可以了.其次在有了自已的库后,源码会很精炼的.不用去羡慕别人了吧.

[]要学会条件编译.注意你的平台特性.(高手的标志?)
除非你确定你要写的程序是在某特定的OS特定的硬件平台而量身定做.否则应注意数据类型的长度,精度都是不同的,不要想当然.有时甚至是不同的编译器的差异都要考虑考虑.

....
....(欢迎您来充实此处空白)
....

好了,在任务中,又有哪些细节呢?

[]我是不是葛郎台?
不要那么吝啬.在源程序中加入详尽的注释以使自己和他人即使在许多年以后仍能读明白它是什么样的程序.
用注释行分离各个函数.

[]删除不需要的代码时要小心.
一个好建议是:使用#ifdef DEL,而不是简单地注释掉甚至是粗暴地直接dd.如果你是使用/* ... */,但一旦要删除的代码有很多行,或注释中以有注释时,这就可能不那么好使了.

[]如何给源程序文件命名?
表现特色且不与任何原有应用名相同.一个简单地方法就是试试看,系统有什么样地反应?

[]一次只修改一个地方.

[]一次只编写一个单一功能的函数。

[]编写通用程序.
只有当程序编写完,并且完成了所需要的性能要求之后,再反过头来优化该程序.

[]不要使用a.out作为结果.你大可以使用与源程相同的可执行文件名.

[]是否一定要用VI编辑?
LINUX下有许多专用编程编辑器.它们能使你有更高的效率和更低的低级输入错误,但我还是要劝你至少要熟练掌握VI.毕竟VI遍地开花.

[]协同作业.请相信,你不是在孤军作战.因此,你有必要熟练掌握一些其它的工具.如


....
....(欢迎您来充实此处空白)
....


第四章:使用lint

lint没有你想象中的那样糟糕.相反,一旦源程序形成了没有LINT错误的形式,将很容易保持下去,并享受到如此而带来的好处.

[]在cc(gcc)之前就应使用LINT.
lint是一语法检查程序,对于这个多嘴的婆婆来说,你应有足够的耐心.虽然你知道自已在干什么,但在CC之前使用LINT总是一个好习惯.

[]lint有哪些特色?
在编译之前使用lint的重要原因是LINT不但能发现ANSI C中的语法错误,而且也能指出潜在的问题或是难于移植于另一机器的代码问题.除了能指出简单语法错误之外,LINUT还能基于以下原因指出另外的错误:
A.无法达到的语句.
B.没有进入循环.
C.没有被使用的变量.
D.函数参数从未使用.
E.没有赋值之前自动使用参数.
F.函数在有些地方有返回值,但在其他地方不返回.
G.函数调用在不同地方使得参数个数不同.
H.错误使用结构指针.
I.模糊使用操作符优先级.
呵呵呵,挺有用的吧!

[]如何控制LINT的输出?
有时LINT会有一大屏一大屏的警告信息.但似乎并未指出错误.为了找出潜在的错误则需费心费力地浏览这些大量的警告信息.
但如果你的程序会分出几个独立的模块,在初级启动LINT时不要用可选项.当对这些模块进行更改或扩充时,可以忽略与代码无关的某些警告.为此可用以下选择项:
-h 对判别是否有错,类型是否正确不给出启发式测试.
-v 不管函数中没有定义的参数
-u 不管被使用的变量和函数没有定义或定义了但没有使用.

[]干脆,在程序中插入指令来影响LINT运行.它看样子有些像注释.
/*NOTREACHED*/ 不可达到的代码不给信息说明.
/*VARARGSn*/ 函数的变量个数不作通常的检查,只检查开始n个参数的数据类型.
/*NOSTRUCT*/ 对下一个表达式不作严格类型检查.
/*ARGUSED*/ 下一函数中,不给出没被使用参数的警告信息.
/*LINTLIBRARY*/ 置于文件的开头,它将不给出没被使用函数的警告信息.

关于LINT的更多用法,请用man lint来获知.


第五章:使用make

[]什么是make?

Unix(Linux)是一个天生的开发平台,我为此感到高兴.make是一个强力的工具.它能自动跟踪相互依赖的源代码块并组成一程序,使得很容易建立一可执行程序.Make就是这种有依赖关系的部分和代码之间所作的规格说明.


[] 所有的程序都要使用make?
是的.尽管你只有几个简单的模块,但你需要有一种结构来支持它从简单走向复杂.除非你的程序已经盖棺定论.

[]Makefile由哪些组成?
Makefile由以下几个部分组成:

注释.
^^^^
使用#符号插入.make将忽略#之后的任何内容以及其后的RETURN键.

变量.
^^^^
make允许定义与SHELL变量类似的有名变量.比如,你定义了SOURCES=prog.c,那么该变量的值$(SCOURES)就包含了源文件名.

依赖关系.
^^^^^^^^
左边是目标模块,后接一冒号.再接与该模块有依赖关系的模块.

命令.
^^^^
以TAB键开始(即使用相同数量的空格也不能代替它).


[]Makefile示例
下面介绍一个简单的示例来说明make的用法.假设你的程序有两个源文件main.c和myc.c,一个位於子目录include下的头文件myhead.h,一个库由三个源文件myrout1.c,myrout2.c,myrout3.c产生.
其makefile文件为:
#一个基本的MAKEFILE文件.
#其中包括个人的头文件和个人库.
HEADERS=include/myhead.h
SOURCES=main.c myc.c
PRODUCT=$(HOME)/bin/tool
LIB=myrout.a
LIBSOURES=myrout1.c myrout2.c myrout3.c
CC=cc
CFLAGS=-g
all:$(PRODUCT)
$(PRODUCT):$(SOURCES)
$(CC)$(CFLAGS) -o $(PRODUCT)$(SOURCES)
lint:$(PRODUCT)
lint $(SOURCES)$(LIBSOURCES)
哈哈,挺象SHELL编程的.如果你与我一样使用LINUX下的gcc,那么只要把上面的CC=cc改为CC=gcc即可.怎么样,想来一个更复杂点的吗?

[]一个更为复杂的Makefile
你是否注意到,在上例中,只要启动make,就会重新编译所有源代码.
如果你能看懂以下的makefile,恭喜恭喜,你通关了.
#一个更为复杂的makefile
HEADERS=include/myhead.h
SOURES=main.c myc.c
OBJECTS=main.c myc.c
PRODUCT=$(HOME)/bin/tool
LIB=myrout.a
LIBSOURCES=myrout1.c myrout2.c myrout3.c
LIBOBJECTS=$(LIB)(myrout1.o)$(LIB)(myrout2.o)$(LIB)(myrout3.o)
INCLUDE=include
CC=cc
CFLAGS=-g -Xc
LINT=lint
LINTFLAGS=-Xc
all:$(PRODUCT)
$(PRODUCT):$(OBJECTS)$(LIB)
$(CC)(CFLAGS)-o$(PRODUCT)$(OBJECTS)$(LIB)
.c.o: $(HEADERS)
$(CC)$(CFLAGS) -c I$(INCLUDE)$<
$(LIB):$(HEADERS)$(LIBSOURCES)
$(CC) $(CFLAGS) -c $(?:.o=.c)
ar rv $(LIB) $?
rm $?
.c.c:;
lint: $(PRODUCT)
$(LINT)$(LINIFLAGS)$(SOURCES)$LIBSOURCES)




第六章:优质无错编程


亲爱的,检查一下,你是否注意到了以下的细节?也就是说,你是否是一个合格的,能编写优质无错代码的程序员?要永远记住,编写无错代码是程序员的责任,而不是测试员.(摘录于本人的""细节页"",因此本节将永远不会保持完整,欢迎您来充实她)

[]所有程序员至少出现过的一个错误:
if(a=3){......}如果a等于3,那么......
你至少要养成这样的习惯:当判断一个变量与一个常量是否相等时,将常量写在前面.这样即使你一不小心写成这样:if(3=a){......}在cc 之前就可以很容易发现它.


[]老调重弹:逻辑操作符的优先权.
我不愿多嘴.总之,如果你一定要编写如下代码时:
if(a&0x1&&b&0x2){......}
你的手头最好有一本详尽的指南.或者你是这方面的专家.

[]尽量不使用int数据类型.
这仅是一个忠告.你大可使用char,short,long数据类型.若干年以后,当你成长为高手之时,你会发现此时我的良苦用心.

[]对于非整型函数一定要完整定义.
如 long float jisuan(char chArr[],int chNum)
{ long float lMydata;
...
...
return(lMydata); }

[]对于非整型函数的输入要当心.
如 long float lfNum;
...
...
scanf(""%lf"",&lfNum);

[]float 型的有效数字为7位.当多于7位时,第8位及以后的位将不准确,可以将其定义为long float型.

[]文件的输入出尽量采用fread fwrite函数.只有当另有用途时才用fprintf fscanf 函数.

[]对于数组及字符串的比较操作时要确认以''''结束.


第七章:调试技术


调试技术在本文中不太好说,之所以将其独立成章是想套用M$的老话:""在下一版本中将会做得更好"":-((.其实这类文章在全国各大BBS上满天飞.

在此我只想说说程序员的应尽职责之一:在程序中使用断言.
~~~~
[]既要维护程序的交付版本,又要维护程序的调试版本,这时可以利用断言补救.

[]要使用断言对函数参数进行确认.

[]要从程序中删除无定义的特性,或者在程序中使用断言来检查出无定义特性的非法使用.

[]不要浪费别人的时间,详细说明不清楚的断言.

[]消除所做的隐式假定,或者利用断言检查其正确性.

[]利用断言来检查不可能发生的情况.

一个实例:我在我的源程序中都使用断言.在本人所编制的全国电子汇兑模糊检索功能模块测试中,前台人员气喘吁吁地告诉我,屏幕上出现了不认识的英文.我说最前面的是哪几个数字,然后根据此数字查阅断言文档,原来是前日日终未正常结束,经查只有半个库.这种情况极少发生,但不是不可能发生.使用断言能及时正确地判别是否是程序的错误还是外部的因素.因此使用断言,是将错误消灭在发生错误之前的一个极其重要的手法.这也是判断一个程序员是否具备良好素质的一个方面.


第八章:其它更好的文档


<> Kenneth H. Rosen ...etc.
<> UNIX Software Operation.
<> Herbert Schildt.
<> Steve Maguire.
<> Daniel Barlow.
<> 谭浩强.

NOTE:本人的<>相信能在8周后能与读者见面.它详尽细致的讨论了低级库curses,窗口,panel库,menu库,form库,TAM变换库等的使用和注意事项,并给出了可用的实例.由于全文庞大复杂,所以写得慢了些.(慢工出细活?)敬请见谅.此外,由于本人的经验所限,本人所讨论的所有东西有可能是过时的,如果你也是为金融,邮电,证券等只用字符终端的行业开发应用的话,你就找对了.与此相反,如果你是新潮一族的话,请将这些难啃的东西DEL掉.呵呵.....