#创作灵感:来自五湖四海不同行业的程序员写的一些程序,完全有着原来行业的习惯,写的我头皮发麻,学习更好的规范就是为了避免一堆堆的无聊注释以及一些看起来没软用的写法。
以下内容参考华为公司的C语言编程规范要求,旨在避免大家在工程内编译使用多个C文件的时候出现的一些问题。
C语言编程规范
1--特殊关键词的用法
1--extern的用法
它的意思是声明一个变量或函数是在其他文件或模块中定义的,但在此处需要被引用或访问。
例如你在main.c文件中定义了num的变量,但是要在app.c文件中使用它,那么就需要在app,c使用的时候,加上extern;在声明函数的时候,要在另一个文件使用它,可以使用extern。
2--static的用法
用在变量前,称为静态变量:
- 定义与初始化:静态变量在程序运行期间保持其值不变,并且其初始化只会在第一次定义时进行,之后的调用不会再次初始化。
-
生存期:静态变量的生存期在程序的整个运行期间,而不仅仅是在声明它的函数调用期间。这意味着静态变量会在程序启动时分配内存,并在程序终止时释放内存。
-
保持状态:静态变量的值在函数调用之间保持不变。这对于需要在多次函数调用之间共享信息的情况非常有用,例如计数器或缓存。
-
作用域:静态变量的作用域局限于定义它的函数内部,但与其他局部变量不同,静态变量在函数调用结束后仍然保持其值。
-
与全局变量的区别:静态变量与全局变量类似,但它们的作用域不同。全局变量的作用域是整个程序,而静态变量的作用域局限于定义它的函数内部。另外,全局变量在定义它的文件外也可以被访问,而静态变量只能在定义它的函数内部被访问
用在函数前,称为静态函数:
-
作用域限制:静态函数的作用域被限制在声明它的源文件(或编译单元)内部,不能在其他文件中被访问。这有助于确保函数的私有性,防止其他文件中的代码无意或恶意地调用它。
-
命名空间隔离:静态函数的名称在整个程序中具有唯一性,因为它们只在其源文件内部可见。这减少了命名冲突的风险,因为其他文件中可以有相同名称的函数,而不会导致冲突。
-
与全局函数的区别:静态函数与全局函数类似,但它们的作用域不同。全局函数可以在其他文件中被访问和调用,而静态函数只能在当前文件内部被调用。
3--const 的用法
const
用于声明一个常量,表示该变量的值在程序运行期间不能被修改。
const int a = 10; // 定义一个常量
const int *p = &a; //定义一个指向常量的指针 指针不能修改所指向常量的值
const int *const r = &a; // r 是一个指向常量的常量指针,既不能修改 r 的值,也不能通过 r 修改 a 的值
const int *getMaxValueAddress(const int *array, int size) {
int maxIndex = 0;
for (int i = 1; i < size; i++) {
if (array[i] > array[maxIndex]) {
maxIndex = i;
}
}
return &array[maxIndex];
}
// 函数返回的指针指向的内容是不可修改的
void printArr(const int *array, int size)//函数传入的参数是常量指针,函数体内不可以修改值
{ // 函数体内不能修改 array 指向的内容
for (int i = 0; i < size; i++) {
printf("%d ", array[i]);
}
}
typedef struct {
const char *name; // 指向的字符串内容不可变 定义的结构体成员是一个指向字符的指针,不能修改值
int age;
} Person;
4--条件编译的用法
条件编译是一种编程技术,允许只编译源文件中满足条件的程序段,忽略不满足条件的代码,
-
选择性编译:条件编译允许只编译源文件中满足条件的程序段,忽略不满足条件的代码。
-
减少内存开销:通过只编译必要的代码段,生成的目标程序更短,减少了内存的开销。
-
提高程序效率:只编译必要的代码段可以提高程序的执行效率。
-
增强可移植性和灵活性:条件编译使得程序能够在不同的软硬件环境下运行,提高了程序的可移植性和灵活性。
主要使用的语句是 #ifndef:检查某个宏是否没定义 #ifdef :检查某个宏是否已经定义 #endif:条件编译的结束语 #if defined:等效于#ifdef,检查某个宏是否已经定义
#if 检查某个值的定义 注意一个#if 对应着一个#endif
#ifndef _APP_H
#define _APP_H
#define APPMode 1
#if APPMode == 1
void APPTime1MS (void);
#elif APPMode == 2
void APPTime2MS(void);
#else
printf("lack of define")
#ifdef APPMode
void APPICR(void);
#endif
#endif
5--宏定义
主要格式为:
#define APPMode 1
还有一种用法是,用来简短定义函数:
#define RUN(a,b) a*b
虽然这样使用看起来较为方便,实际上存在一定风险,例如c/RUN(a,b) 就是c/a*b,跟我们预期的结果不一样,所以要注意使用方式,写成 ((a)*(b)),就十分安全
函数复杂起来的话,也可以使用大括号来包含几行。
#defineFOO(x) {
printf("arg is %s\n", x);
do_something_useful(x);
}
同样的问题是 只是define,碰到if (1) FOO(10) 这种句子,宏定义也只有一行会执行。改一下:
#define FOO(x) do{
printf("arg is %s\n", x);
do_something_useful(x);
}while(0)
所以没有必要,不建议这样使用,直接使用函数即可。
2--关于头文件
1、只适合放置函数接口的声明,不适合放置函数体的实现
意思就是头文件里只放函数的声明,而函数的函数体实际在对应的C文件里。
例如在app.h里,我们这么写:
#ifndef _APP_H //条件编译 防止该头文件被多个include的时候反复编译 记住就行
#define _APP_H
void APPitimer(void);
void APPICR(void);
#endif
这里需要提一嘴的是:在H文件里声明函数的时候,默认就是extern 类型
在app.c文件里,我们可以include 对应的头文件,然后写函数的实体。
#incldue ""app.h"
void APPtimer (void)
{
//相应函数实体
}
void APPICR (void)
{
//相应函数实体
}
2、文件的包括应当是朝着稳定的方向,而不需要包括一些无用的头文件、后续不需要直接使用的头文件,或者重复包括。
这句话的意思是不要瞎include,很多时候我们可能在头文件里就include一些其他的头文件。
例如,如果H文件没有包括保护(#ifndef 那一句)的时候,当app.h 和motor.h两个头文件同时包括time.h的话,main.c包括app.h、motor.h,当你编译的时候,time.h就会报错提示重复include了。所以,写保护很重要。
再例如,假如你在time.h和motor.h文件里定义了同一个名字的宏,直接include也会导致重复包括的错。
最常见的情况是假如你在使用ADC读取电压的时候,需要同时在FLASH里面写入电压数据,那这个函数写入电压数据的函数该写在哪里呢?首先一般肯定不是写在底层驱动里,ADC.C里面肯定是只负责外设读取数据,不负责处理这个数据的,写的地方肯定是上层应用层里。
这个时候你肯定需要include FLASH.h 和ADC.h才才能将电压数据写入数据
如果你要在app.h和motor.h内使用一些time.h里的内容的时候,建议在app.c和motor.c里面include,不要在H头文件里直接include。
3、每一个C文件都应该包含一个.h文件,用以声明对外的函数接口
意思很明显,如果是内部单独使用的函数声明,可以就在内部消化即可,对外使用的函数接口才在头文件里,这一点要求不是很严苛,你可以都写进去。
4、头文件里面不要随便定义变量
防止当你include的时候,变量名重复了。只调用函数来寻找变量,不调用变量去寻找函数。
5、只能通过include的方式来获得其他模块提供的函数接口,不允许在C文件内使用extern来获得函数接口
同第3点,私密性的体现。
3--关于函数
函数的名称尽量要容易区分和阅读,例如以英文名的大小写来实现,你可以首字母大写,相应单词间加下划线,也可以全都小写。
1、一个函数只完成一个功能
功能区分开,不要混为一谈。
2、重复多次的内容尽量写为函数
简化代码结构
3、函数体内只有一行代码的,建议不要写了
设立函数的必要性不是很高,当然设立的话也没有太大问题。
4、函数体内代码过长,超过50行(非空非符号非注释行)
实际在一些通信和外设的使用过程中,50行代码的使用时比较少的。
5、代码块的嵌套不要太深,超过5个
意思就是{ }这俩符号的使用不要嵌套太深,for循环里面嵌套if循环,里面还在嵌套。
6、参数的合法性检查
一般要统一规定,参数合法性要么由调用者检查,要么函数自己检查。例如对于RTC实时时钟的初始化,传入的参数实际上是时间,当然要检查,像2月30号这种错误就避免掉。
7、函数的参数不宜可变,或者过多
主要是防止调用的时候出错,冗杂或者过界。
4--标识符的定义
主要是常用的命名风格,尽量简单可读。
首字母大写,单词间下划线相连;全都小写,单词间下划线相连。
单词尽量写完整,常见的可以写为缩写,例如error ---err。
单词的意思尽量明确代表你的动作要求,例如FLASH读出数据---flash_read_data( )。
5--关于注释
注释主要用于解释说明琐屑的内容为何,一般不需要太多注释,主要是函数、文件的前端需要进行注释,注释内容包括但不限于 功能、日期、作者等等。