022+limou+C语言函数规范

0.前言

这里是limou3434的一篇博文,感兴趣的话您可以看看我的其他文章。

在C语言中,最重要的概念实际上是函数,因为函数最能体现C语言模块化、面向过程的特征。

本次我会和您探讨一些函数的编码规范、一些函数的设计原则和技巧.

1.函数编码风格

  1. 函数定义与定义直接最好加上空行分割,以突出模块化的风格
  2. 逻辑密切的语句不要在中间加空行
  3. 在一个函数体内,变量定义与函数语句之间要加上空格
int main()
{
  	//变量定义
  	int a = 0;
  	int b = 0;
  	int c = 0;
  
  	//函数内的其他代码语句
  	for(int i = a; i < 5; i++)
    {
    	printf("%d %d\n", b++, c++);
    }
	return 0;
}
  1. 在代码逻辑复杂的情况下,在分支语句和循环语句之后需要加上适当的注释,方便区分各分支和循环体,这是一个很好的习惯
while(某些条件){
	巨长的代码
    if(){
    	巨长的代码
        if(){
          	巨长的代码
        }//end of if1
	}//end of if2
}//end of while
  1. 修改别人的代码不要轻易删除别人的代码,应该使用适当的注释将其注释掉,再写上自己的代码
  2. 特别不推荐使用Tab来缩减代码,而应该使用两个空格或四个空格,原因是因为使用Tab缩减的代码在不同平台有可能得到的解释不一样,有的编译器将Tab解释为8个字符,而有的又解释为4个字符,这就极有可能造成缩进混乱的情况
  3. 代码一行不能太长,只要保证屏幕上能完整输出即可
  4. 长表达式要在低优先级操作符处划分新行,操作符放在新行之首(以便突出操作符)。拆分出的新行要进行适当的缩进,使排版整齐,语句可读。另外函数参数过长也可以做换行处理
if(条件1
  && 条件2
  && 条件3
  && 条件4)
{
	//具体代码
}
  1. 用正确的反义词命名具有互斥意义的变量或相反动作的函数
  2. 如果代码中的运行符过多,则使用圆括号,让代码条理更加清楚,更加易读
  3. 逻辑操作符在保留易读性的同时,尽可能进行简化,例如在某些情况可以不使用“!”

2.函数设计的一般原则和技巧

  1. 原则上,非必要不使用全局变量,因为全局变量生命周期过长,容易出错,也会长时间占用空间,但是在某些游戏开发中的确是需要使用某些全局变量
  2. 函数形参的顺序和命名要合理恰当
void my_strcpy(char *strSource, char *strDestination);
//这样对于程序员,哪怕是没有查看函数定义,也会下意识的认为该函数是右边字符参数拷贝给左边参数,因此就会下意识写出下面这样的代码,这是普遍认可的主流写法
char str[20];
my_strcpy(str, "hello word!");
  1. 如果参数是指针,且只是作为输入参数使用,则应在类型前面加上const,以防止指针在函数体内被意外修改,这是一种预防性编程
  2. return不可以返回指向“栈内存”的指针,因为该内存在函数体结束时被销毁
  3. 在函数的参数入口处,如果传入指针,需要做一些合法性校验(当然,这是在“未使用指针使用NULL初始化”规范下才能进行所谓的“合法性”,如果没有这一规范限制,理论上C语言是没有办法检测一个指针是否真的“合法”)。

5.1.需要注意的是assert也不要过于滥用,这是因为assert只有在Debug中才是有效的,如果是在Release模式下,assert就会被编译器自动去掉(如果经过测试就会发现两个模式下的报错是不太一样的,原因就是assert被去掉了)。而这样如果用户在使用Release模式时候,一旦发生错误就没有assert来提示错误(这就相当于测试代码的时候四处使用assert防备,而真正到了使用代码的时候这些防备反倒是全部卸去了…)
5.2.使用assert还有一个问题,有的时候就要给函数传递空指针,比如“树的前序遍历”中就需要传递NULL来停止递归,而不是直接简单除暴使用assert来终止整个程序

  1. 函数的功能最好比较单一,不要设计多用途的函数,函数解决A问题就解决A问题,不要即解决A问题又解决B问题
  2. 函数内部尽量不要只用静态局部变量(static),因为多次使用这种带有“记忆”的函数后,这个全局变量是不确定的
  3. 避免函数参数过多,使用麻烦
  4. 有时候函数不需要返回值,但是为了能够链式使用函数,有时也可以考虑给该函数加上返回值
//例如strcpy函数,
char* strcpy(char* strDest, const char* strSrc);
//链式使用函数
char str[20];
int length = strlen(strcpy(str, "helood word!"));
  1. 函数名和返回值在语义上不可冲突
//例如strcpy函数,
char* strcpy(char* strDest, const char* strSrc);
//链式使用函数
char str[20];
int length = strlen(strcpy(str, "helood word!"));
  1. 汇编语言应被封装并隔离,最好同时定义成宏(注意在C语言是可以使用和汇编语言混用的,用来实现跟加高效和精确的程序,如果是做应用开发这个规则很难用到)
  2. 初始化非零数组和结构体时最好用花括号配对
//这样可以,但是可读性低 
int arr1[3][2] = { 1, 2, 3, 4, 5, 6 };

//这样写可读性高
int arr2[3][2] = { {1, 2}, {3, 4}, {5, 6} }
  1. non-void的函数(也就是返回值为void的函数)里,所有退出路径都应该包含return语句。有时经常出现一个“不是所有的控件路径都有返回值”
//有的时候在函数体内写二元判断表达式如下
int function(int a)
{
	if(a > 0)
    {
    	printf("a = %d\n", a);
    }
  	else
    {
    	printf("非正数\n");
    }
}
//改写成下面这种方式会更好,更加确保每个控件路径一定会有返回值
int function(int a) 
{ 
  	if(a > 0) 
    { 
      	printf("a = %d\n", a); 
    }
  	printf("非正数\n"); 
}
  1. 需要用到数组大小时,尽量使用sizeof来求得数组大小,例如:遍历数组的时候
  2. 使用memcpy、strcpy等库函数之前,必须先校验目的地指针是否有效,并且判断写入的长度
  3. 对指向不同数组或数组元素的两个指针做运算时会导致未定义的行为,因此必须保证这两个指针指向的是同一个数组
#include <stdio.h>
int main()
{
	int arr1[3] = { 1, 2, 3 };
	int arr2[4] = { 0, 0, 0, 0 };
	int* p1 = arr1 + 1;
	int* p2 = arr2 + 2;
	printf("%zd", p2 - p1);//编译器不会报警!这种结果是不可预知的
	return 0;
}
  1. 标准库中保留的标识符、宏、函数,不能被定义、被重定义、取消定义(例如使用“#undef LINE”)
  2. 不要轻易使用错误提示error(这又超出了学习C语言的学习边界,以后再来说)

“errno做为C的简捷工具,在理论上是有用的,但在实际中标准没有很好地定义它。一个非零值可以指示问题的发生,也可以不用它指示;做为结果不应该使用它。即使对于那些已经良好定义了errno的函数而言,宁可在调用函数前检查输人值,也不依靠errno来捕获错误。”——来自《C语言深度解剖》

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

limou3434

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值