C语⾔的语法风格与代码书写规范指南
C代码:
#include <stdio.h>
int main(void)
{
printf("That is Right Style\n");
return 0;
}
在⼀个标准的C语⾔程序中,最特殊的莫过于main函数了,⽽说到底它就是⼀个函数⽽已,仅仅因为它地位特殊拥有第⼀执⾏权⼒,换句话说,难道因为⼀个⼈是省长它就不是⼈类了?所以函数该有的它都应该有,那么函数还有什么呢?
函数⼤体上分为内联函数(C99)(内联函数并⾮C++专属,C语⾔亦有,具体见前⽅链接)和⾮内联的普通
函数,它们之间有⼀个很明显的特点(⼀般情况下),那就是不写原型直接在main函数上⽅定义,即使不加'inline'关键字,也能被编译器默认为内联函数,但之后带来的某些并发问题就不是编译器考虑的了。
普通函数正确的形式应该为声明与定义分离,声明就是⼀个函数原型,函数原型应该有⼀个函数名字,⼀个参数列表,⼀个返回值类型和⼀个分号。定义就是函数的内在,花括号内的就是函数的定义:
//...
int function(int arg_1, float arg_2);
//...
int main(int argc, char* argv[])
{
int output = function(11, 22.0);
printf("%d\n",output);
return 0;
}
int function(int arg_1, float arg_2)
{
int return_value = arg_1;
float temp_float = arg_2;
return return_value;
}
依上所述,当⾮必要时,在⾃⼰编写函数的时候请注意在开头(main函数之前)写上你的函数的原型,并且在末尾(main函数之后)写上你的函数定义,这是⼀个很好的习惯以及规范。所谓代码整洁之道,就是如此。
函数的另⼀种分类是,有返回值和⽆返回值,返回值的类型可以是内建(build-in)的也可以是⾃⼰定义
的(struct, union之类),⽆返回值则是void。
为什么我们⼗分谴责void main()这种写法?因为这完全是中国式教育延伸出来的谭式写法,main函数的返回值看似⽆⽤,实际上是由操作系统接收,在Windows操作系统下也许⽆甚"⼤碍"(实际上有),当你使⽤Linux的过程中你会清晰的发现⼀个C语⾔程序的main返回值关系到⼀个系统是否能正常,⾼效的运⾏,这⾥稍微提⼀句,0在Linux程序管道通信间代表着⽆错可⾏的意思。所以请扔掉void main这种写法。
为什么我们对 main()这种省略返回值的写法置有微词?能发明这种写法的⼈,必定是了解了,在C语⾔中,如果⼀个函数不显式声明⾃⼰的返回值,那么会被缺省认为是int,但这⼀步是由编译器掌控,然⽽C语⾔设计之初便是让我们对⼀切尽可能的掌握,⽽⼀切不确定因⼦我们都不应该让它存在。其次有⼀个原则,能⾃⼰做的就不要让编译器做。
为什么我们对参数放空置有不满(int main())?在C语⾔中,⼀个函数的参数列表有三种合法形态:
int function();
int function(void);
int function(int arg_n);
int function(int arg_n, ...);
第⼀种代表拥有未知个参数,第⼆种代表没有参数,第三种代表有⼀个参数,第四种代表拥有未知个参数,并且第⼀个参数类型为int,未知参数在C语⾔中有⼀个解决⽅案就是,可变长的参数列表,具体参考C标准库,在此我们解释的依据就是,我们要将⼀切都掌控在⾃⼰的⼿中,我们不在括号内填写参数,代表着我们认为⼀开始的意思是它为空,正因此我们就应该明确说明它为void,⽽不该让它成为⼀个未知参数长度的函数,如此在你不⼩⼼传⼊参数的时候,编译器也⽆法发现错误。
int main(int argc, char* argv[]) 和 int main(void)才是我们该写的C语⾔标准形式
对于缩进,除了编译器提供的符号缩进之外,我们可以⾃⼰给⾃⼰⼀个规范(请少⽤或者不⽤Tab),⽐如每⼀块代码相教上⼀个代码块有4格的缩进。
对于学习C语⾔,请使⽤.c⽂件以及C语⾔编译器练习以及编写C程序,请不要再使⽤C++的⽂件编写C语⾔程序,并且⾃圆其
说为了效率⽽使⽤C++的特性在C语⾔中,我们是祖国的下⼀代,是祖国的未来,请不要让⾃⼰毁在当下,珍爱编程,远离清华⼤学出版社。
之所以如此叙述,并不是因为情绪,⽽是当真如此,下⽅代码:
/*file: test.c*/
#include <stdio.h>
#define SIZES 5
int main(void)
{
int* c_pointer = malloc(SIZES * sizeof(int));
/*发⽣了⼀些事情*/
free(c_pointer);
return 0;
}
这是⼀段标准的C语⾔程序,但是它能在C++个编译器下编译运⾏吗?换句话说当你将⽂件扩展名由.c改为.cpp之后,它能编译通过吗?答案是不能。
为什么?答案是C++并不⽀持void*隐式转换为其他类型的指针,但是C语⾔允许。还有许许多多C于C++不相同的地⽅,兴许有⼈说C++是C的超集,但我并不这么认为,⼀门语⾔的出现便有它的意义所在,关键在于我们如何发挥它的最⼤优势,⽽不是通过混淆概念来增强实⽤性。
程序式⼦的写法
⼀个⼈活在世界上,时时刻刻都注意着⾃⼰的⾔⾏举⽌,⽽写程序也是如此,对于⼀个规范的能让别⼈读懂的程序⽽⾔,我们应该尽可能减少阻碍因⼦,例如:
int main(void)
{int complex_int=100;
int i,j,k,x;
for(int temp=0;temp<complex_int;++temp){k=temp;
x=k+complex_int;}
printf(complex_int="%d is k=%d x=%d\n",complex_int,k,x);
return 0;}
对于上述的代码,我总是在班级⾥的同学⼿下出现,但这段代码除了让别⼈困惑以外,⾃⼰在调试的时候也是⼗分不⽅便,每每遇到问题了,即便IDE提⽰了在某处错误,你也不到问题所在,经常有⼈来问我哪⾥错了,⼤部分情况都是少了分号,括号,或者作⽤域超过,原因在哪?
要是⼀开始将代码写清楚了,这种情况简直是凤⽑麟⾓,想遇上都难。对于⼀个代码⽽⾔,我们应该注意让其变得清晰。
等号两边使⽤空格:
int complex_int = 100;
使⽤多个变量的声明定义,或者函数声明定义,函数使⽤时,注意⽤空格分开变量:
int i, j, k, x;//但是⼗分不建议这么声明难以理解意义的变量
printf("complex_int = %d is k = %d x = %d\n", complex_int, k, x);
void present(int arg_1, double arg_2);
对于⼀个清晰的程序⽽⾔,我们要让每⼀个步骤清晰且有意义,这就要求我们在编写程序的时候尽量能让代码看起来结构化,或者整体化。尽量让每个程序式⼦为⼀⾏,如果有特别的需要让多个式⼦写在同⼀⾏,可以使⽤,操作符进⾏组合,但是会让程序更难理解,⽇后调试的时候也更难发现错误。
/*Style 1*/
for(int temp = 0;temp < complex_int;++temp)
{
k = temp;
x = k + complex_int;
}
/*Style 2*/
for(int temp = 0;temp < complex_int;++temp){
k = temp;
x = k + complex_int;
}
对于上⽅的代码,是C语⾔代码花括号的两种风格,最好能选择其中⼀种作为⾃⼰的编程风格,这样能让你的程序看起来更加清晰,混合使⽤的利弊并不好说,关键还是看个⼈风格。
对于作⽤域⽽⾔,在C语⾔中有⼀个经常被使⽤的特例,当⼀个条件语句,或者循环只有⼀条语句的时候,我们常常省略了花
括号{},⽽是仅仅使⽤⼀个分号作为结尾,这在很多情况下让代码不再啰嗦:
if(pointo_int == NULL)
fprintf(stderr, "The pointer is NULL!\n");
else
{
printf("%d\n",*pointo_int);
pointo_int = pointo_int->next;
}
在这段代码中if语句下⽅的代码并没有使⽤{}运算符进⾏指明,但是根据语法,该语句的确是属于if语句的作⽤范围内,如果我们此时写上了{}反⽽会令代码看起来过于啰嗦。但是有的时候,这条特性并不是那么的有趣,当使⽤嵌套功能的时候,还是建议使⽤{}进⾏显式的范围规定,⽽不是使⽤默认的作⽤域:
for(int i = 0;i< 10;++i)
for(int k = 0;k < 10;++k)
while(flag != 1)
set_value(arr[i][k]);
这段代码,看起来⼗分简洁,但是确实是⼀个很⼤的隐患,当我们要调试这段代码的时候,总是需要修改它的构造,⽽这就带来了潜在的隐患。所以建议在使⽤嵌套的时候,⽆论什么情况,都能使⽤{}进⾏包装。
综上所述,在开始编写⼀个标准C语⾔程序的时候,请先把下⾯这些东西写上:
#include <stdio.h>
int main(void)
{
return 0;
}
C代码规范
命名
只要提到代码规范,就不得不说的⼀个问题。
在⼀些⼩的演⽰程序中,也许费尽⼼思去构思⼀个命名是⼀件⼗分傻的⾏为,但是只要程序上升到你需要严正设计,思考,复查的层次,你就需要好好考虑命名这个问题。
函数命名:
C语⾔中,我们可以让下划线或者词汇帮助我们表达函数功能:
前缀:
set 可以表⽰设置⼀个参数为某值
get 可以表⽰获取某⼀个参数的值
is 可以表⽰询问是否是这种情况
后缀:
max/min 可以表⽰某种操作的最⼤(⼩)次数
cnt 可以表⽰当前的操作次数
key 某种关键值
size_t get_counts();
size_t retry_max();
retry是什么意思
int is_empty();
需要注意的只是,不要让命名过于赘述其义,只简单保留动作以及⽬的即可,详细功能可以通过⽂档来进⾏进⼀步的解释。结构体命名:
由于结构体的标签,不会污染命名,即标签不在命名搜索范围之内,所以可以放⼼使⽤:
有⼈习惯使⽤ typedef, ⽽有⼈喜欢使⽤ struct tag obj,后者⽐较多,但是前者也不失为⼀种好⽅法,仁者见仁智者见智。
/*⽅法1*/
struct inetaddr_4{
int port;
char * name;
};
struct inetaddr_4 *addr_info;
/*⽅法2*/
typedef struct _addr{
int port;
char * name;
}inetaddr_4;
inetaddr_4 *addr_info_2;
两者同处⼀个⽂件内亦不会发⽣编译错误。
变量命名
所有字符都使⽤⼩写
含义多的可以⽤ _ 进⾏辅助
以 = 为标准进⾏对齐
类型,变量名左对齐。
等号左右两端,最少有⼀个空格。
int main(void)
{
int  counts = 0;
inetaddr_4 *addr = NULL;
return 0;
}
为了防⽌指针声明定义时候出错,将 * 紧贴着变量名总不会出错。
inetaddr_4 *addr, object, *addr_2;
其中 addr 和 addr_2 是指针,⽽ object 则是⼀个栈上的完整对象,并不是指针。
全局变量能少⽤就少⽤,必须要⽤的情况下,可以考虑添加前缀 g_
int g_counts;
#define 命名
所有字符都是⽤⼤写,并⽤ _ 进⾏分割。
如果多于⼀个语句,使⽤ do{...}while(0) 进⾏包裹,防⽌ ; 错误。
#define SWAP(x, y)  \
do{      \
x = x + y;  \
y = x - y;  \
x = x - y;  \
}while(0)
当然这个交换宏实际上有⼀点缺陷,在⼤后⽅会提出。此处是代码规范,就不重复强调。
enum 命名
所有字符都是⽤⼤写,并⽤ _ 进⾏分割
与 define 相⽐,enum适⽤于同⼀类型的常量声明,⽽不是单⼀独⽴的常量。往往出现都是成组。格式化代码
花括号 {}
混合使⽤符合节俭思想,但会稍微有⼀点结构紊乱。
单⼀使⽤能更好让代码结构清晰。
所谓混合,单⼀指的是是否⼀直使⽤ {} 进⾏代码包裹。
有⼈认为当单⼀语句的时候不必要添加 {},有的⼈则习惯添加
当作⽤域超过⼀个屏幕的时候,可以适当的使⽤注释来指明 {} 作⽤域
while(1){
if(tmp == NULL){
break;
}
else if(fanny == 1){
... ⼤概超过了⼀个屏幕的代码
} /*else if fanny*/
}/*end while*/
如果是代码量少的情况下,但嵌套⽐较多,也可以使⽤这个⽅式进⾏注释。
括号 ()
有⼈建议除了函数调⽤以外,在条件语句等类似情况下使⽤ () 要在关键字后空⼀格,再接上 ()语句,对于这⼀点,我个⼈习惯是不空格,但总有这种说法。
if (space == NULL) {
/**TODO**/
}
while(1){
/**我习惯于如此写**/
}
strcpy(str1, str2); /**第⼀种写法是为了和函数调⽤写法进⾏区分**/
return 0;
switch
⼀定要放⼀个 default 在最后,即使它永远不会⽤到。
每个 case 如果需要使⽤新变量,可以⽤ {} 包裹起来,并在⾥⾯完成所有操作。
switch(...)
{
case 1:
/**TODO**/
break;
case 2:
{
int new_vari;
/**创建新变量则⽤ {} 包裹起来**/
}
break;
default:
call_error();
}
goto
虽然许多⼈,许多书都提醒不再使⽤ goto 关键字,⽽是使⽤ setjmp 和 longjmp来取代它,但是这还是那句话,仁者见仁智者见智,如果 goto 能够让代码清晰,那何乐⽽不为呢,这个观点也是最近才体会到的(并⾮我⼀⼰之⾔)。
具体使⽤可以查询官⽅⽂档。
语句
应该让完整的语句在每⼀⾏中,只出现⼀次。
对于变量声明定义亦是如此
原因是这样能让⽂档更有针对性
头⽂件保护
对于头⽂件⽽⾔,在⼀个程序中有可能被多次包含(#include),如果缺少头⽂件保护,则会发⽣编译错误
不要将 _ 作为宏的开头或者结尾。
#ifndef VECTOR_H_INCLUDE
#define VECTOR_H_INCLUDE
/**TODO**/
#endif
C语⾔的宏有诸多弊端,所以尽量使⽤ inline 函数来代替宏。在⼤后⽅会有解释
但是,请不要因此抛弃了宏,⽐如在 C11 中有⼀个新兴的宏。
变量

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。