理解C语言——从小菜到大神的晋级之路(15)——完结篇:C编程风格

简介: 本期视频链接:点击这里有人说过:“程序源代码其实是跟人阅读的,只是恰好机器可以编译而已”。

本期视频链接:点击这里

有人说过:“程序源代码其实是跟人阅读的,只是恰好机器可以编译而已”。编程初学者常常会有这样一个观念,就是我的程序只要编译通过了,运行没有问题那就万事大吉了。至于代码的编写规不规范,完全就是无关紧要的小事情。如果是处于学习阶段,比如为了完成在学校的C语言课的作业,那么花心思在代码规范上的确没有特别的必要,因为这些代码基本不会进入实用工程,也不会被很多人阅读到。

但是,如果应用到了工程领域,比如在软件/互联网企业的技术研发部门,或者Github等平台上的开源工程,那么编程的规范性将变得无比重要。因为在这些场合,你写的代码将被许多人阅读,并且可能会成为许多人进行后续开发的基础。此时,差劲的代码风格将严重拉低其他开发人员的工作效率。因此,我们推荐从一开始学习便养成一个良好的编程习惯,维持一个合理的代码风格,这样对未来的工作大有裨益。

C语言编程风格的内容相当庞大,这里只挑选一部分相对常用而且比较重要的内容作为参考,主要分为5个部分,包括排版、注释、命名、变量/结构、函数等。

1、排版

程序排版使得代码的结构更加清晰明了,而且有助于理解上下文的逻辑关系。
(1)程序块应根据上下文关系采用缩进风格,缩进的长度根据具体标准规定;
(2)独立的程序块之间、变量说明之后必须加空行;比如:

int fun()
{
     int nVal1 = 0, nVal2 = 5, nSum;

     {
          nSum = nVal1 + nVal2;
     }

     printf("Sum is %d\n", nSum);
}

(3)一条语句占一行,不允许将两条语句写在一行中;
(4)对于存在判断、循环的代码,像if/for/do/while/case/swith/default等部分独占一行,且无论执行部分有多少条语句,都必须使用大括号{ };
(5)包裹代码块的大括号{ }必须另起一行,不要跟随上一行代码的末尾;且大括号也要符合代码缩进规则;

2、注释

注释虽然不影响程序的运行,但仍然是代码的重要组成部分。完善的代码注释对快速理解代码的功能具有重要意义,相反如果代码逻辑复杂且没有注释,或注释不完整、不科学,那么旁人很难理解这段代码究竟是做什么的。需注意一点,别人无法理解的程序即使运行良好,也永远都是垃圾代码。

添加注释需要注意,注释应简洁、有效,有助于提升对代码的理解。所以添加注释应注意不要添加一些完全无意义或者错误的信息。通常,我们认为一套代码按照优劣分为4个等级:
第一等级:不需要注释,通过优秀的代码风格、标识符命名和代码的上下文关系就可以达到高可读性的代码;
第二等级:代码的命名和组织规范、风格稍显不足,但有完善的注释;
第三等级:代码风格和注释都不够完善,但是组织了较为完善的文档在一定程度弥补了这一缺陷;
第四等级:代码风格、注释和文档都不足,这种就属于其他人难以理解的垃圾代码。

函数头部的注释:
在函数头部应添加注释,说明函数的功能、参数、返回值等信息。下面的注释格式比较完善,不一定要局限与此,但建议保留其中的大部分信息:

/*************************************************
  Function: // 函数名称
  Description: // 函数功能、性能等的描述
  Calls: // 被本函数调用的函数清单
  Called By: // 调用本函数的函数清单
  Input: // 输入参数说明,包括每个参数的作
  // 用、取值说明及参数间关系。
  Output: // 对输出参数的说明。
  Return: // 函数返回值的说明
  Others: // 其它说明
*************************************************/

代码中的注释:
语句的注释应在被注释语句的正上方或右方。如果是在上方的话,除非十分必要否则不要再代码和注释之间插入空格。
对于具有物理含义的常量和变量,以及数据结构,除非命名本身是充分注释的,在声明时必须加以注释。
全局变量要有详细的注释,包括对其功能、取值范围、使用的函数以及存取时的注意事项等。
注释与上方的代码用一行空格间隔。
对选择、循环语句应当添加注释,说明分支、循环体的意义。
在程序块结束的大括号右方添加注释,说明匹配的程序块开始位置。
如以下代码:

if (...)
{
     program code
     while (index < MAX_INDEX)
     {
          program code
     } /* end of while (index < MAX_INDEX) */ // 指明该条while语句结束
} /* end of if (...)*/ // 指明是哪条if语句结束

3、标识符命名

标识符命名是代码风格中的重要组成部分,甚至直接决定了代码可读性的高低。最常用的标识符无非就是常量名、变量/结构体名、函数名、宏定义、标签名等。对不同的标识符类型一般适用不同的要求,但有一些基本要求是一致的:标识符的命名必须清晰明了,含义明确,尽量少地使用缩写;严禁使用无意义的单个字母如a, b, i, m, n或者func1, fun等无意义的单词或缩写用于命名;

(1)常量、变量、函数命名:

对于变量名和函数名,通常比较常用的有两种命名法:驼峰命名法和下划线命名法,这两种方法的根本区别在于通过怎样的方式来分隔标识符命名中的逻辑断点。
驼峰命名法:通过大小写字母的变化进行分隔,如:int imgWidth = 0; char *studentName = “Jerry”;
下划线命名法:通过下划线进行分隔,如:double earth_moon_distance;

对于函数名和变量名,一般可以使用不同的命名方法,但是需要注意的是只要选定的命名规范就要从头到尾保持不变。通常,我个人的习惯是,变量名和结构体名使用驼峰命名法,变量名用小写开头,结构体用大写开头;函数名使用下划线命名法,公有API以大写字母开头,私有函数以小写开头并声明为static类型。

另外,对于常量、结构体成员变量、全局变量,还可以参考匈牙利命名法的原则,在变量名前加入前缀c_、m_和g_。

(2)宏定义命名

对于宏定义的命名,一律全部使用大写字母,逻辑断点采用下划线分隔,如
#define MAX_ARRAY_LENGTH 256
对于头文件保护作用的宏定义,则以头文件的文件名命名,逻辑断点和扩展名前的点全部用下划线替代,并且在首位各添加一个下划线,如

//ImageProcessing.h
#ifndef _IMAGE_PROCESSING_H_
#define _IMAGE_PROCESSING_H_

/*code*/

#endif

(3)标签名

通常标签名配合goto语句一起使用。由于goto本来就是比较冷门的语句,标签也不是很常用。如果用到,则全部使用小写字母,并且在结尾加_label,如:

void test()
{
     /*code*/
     goto end_label;
     /*code*/

end_label:
     /*code*/     
}

4、变量和结构使用规范

变量和结构的使用是编程中最为频繁的动作,如果能正确规范变量的使用,那么对整体的编程风格的提升大有帮助。
(1)变量定义之后立刻初始化。通常数值型变量定以后可以立刻初始化为0、某个负数或其他无意义的数值,指针变量定义后立刻初始化为NULL。这样在后面使用变量时可以更方便地判断变量是否已经被正确地处理,防止无意中使用了未经初始化的值。
(2)除非特别必要,否则尽量减少全局变量的使用,对于跨文件使用的全局变量更要慎重。全局变量是造成代码之间耦合的重要因素,通常使用全局变量越多,代码就越难以维护。
(3)对于数值完全不应当改变的量,一律定义为常量,防止被误修改。
(4)定义一个结构体的功能应当越具体越好,不应定义一个实现多种功能的结构。另一个体现是,不要定义规模过于庞大的结构,这样不但在运行时浪费系统资源,而且逻辑上难以理解。
(5)除非特别必要,尽量减少变量类型之间的强制转换。因为强制转换实际上也是需要计算机额外操作的,过多的强制转换对系统资源也是一种浪费。
(6)定义结构体时注意优化成员之间的顺序,尽量减少因为字节对齐导致的存储空间浪费。

5、函数使用规范

  • 对于可能出现执行错误(如打开文件失败等)的函数,一律通过返回值返回错误码,且错误码用宏定义预先定义好。
  • 除非专门用来设计输出随机信息的函数,所有的函数都应当是可预测的,即相同的输入永远产生相同的输出。
  • 一个函数只完成一个较小的功能,避免出现一个完成大量不相关功能的超长函数。
  • 严格区分输入和输出参数,对于函数体中不应该改变的参数全部声明为const类型。
  • 在函数正式开始进行处理之前,检查输入参数以及其他用到的外部的有效性。
  • 避免使用过长的参数表,可以把相关的参数封装成一个结构体并以该结构体作为参数。
目录
相关文章
|
1月前
|
NoSQL C语言 索引
十二个C语言新手编程时常犯的错误及解决方式
C语言初学者常遇错误包括语法错误、未初始化变量、数组越界、指针错误、函数声明与定义不匹配、忘记包含头文件、格式化字符串错误、忘记返回值、内存泄漏、逻辑错误、字符串未正确终止及递归无退出条件。解决方法涉及仔细检查代码、初始化变量、确保索引有效、正确使用指针与格式化字符串、包含必要头文件、使用调试工具跟踪逻辑、避免内存泄漏及确保递归有基准情况。利用调试器、编写注释及查阅资料也有助于提高编程效率。避免这些错误可使代码更稳定、高效。
265 12
|
1月前
|
C语言
【C语言的完结】:最后的测试题
【C语言的完结】:最后的测试题
19 3
|
2月前
|
存储 算法 Linux
C语言 多进程编程(一)进程创建
本文详细介绍了Linux系统中的进程管理。首先,文章解释了进程的概念及其特点,强调了进程作为操作系统中独立可调度实体的重要性。文章还深入讲解了Linux下的进程管理,包括如何获取进程ID、进程地址空间、虚拟地址与物理地址的区别,以及进程状态管理和优先级设置等内容。此外,还介绍了常用进程管理命令如`ps`、`top`、`pstree`和`kill`的使用方法。最后,文章讨论了进程的创建、退出和等待机制,并展示了如何通过`fork()`、`exec`家族函数以及`wait()`和`waitpid()`函数来管理和控制进程。此外,还介绍了守护进程的创建方法。
C语言 多进程编程(一)进程创建
|
2月前
|
Linux C语言
C语言 多进程编程(三)信号处理方式和自定义处理函数
本文详细介绍了Linux系统中进程间通信的关键机制——信号。首先解释了信号作为一种异步通知机制的特点及其主要来源,接着列举了常见的信号类型及其定义。文章进一步探讨了信号的处理流程和Linux中处理信号的方式,包括忽略信号、捕捉信号以及执行默认操作。此外,通过具体示例演示了如何创建子进程并通过信号进行控制。最后,讲解了如何通过`signal`函数自定义信号处理函数,并提供了完整的示例代码,展示了父子进程之间通过信号进行通信的过程。
|
2月前
|
Linux C语言
C语言 多进程编程(四)定时器信号和子进程退出信号
本文详细介绍了Linux系统中的定时器信号及其相关函数。首先,文章解释了`SIGALRM`信号的作用及应用场景,包括计时器、超时重试和定时任务等。接着介绍了`alarm()`函数,展示了如何设置定时器以及其局限性。随后探讨了`setitimer()`函数,比较了它与`alarm()`的不同之处,包括定时器类型、精度和支持的定时器数量等方面。最后,文章讲解了子进程退出时如何利用`SIGCHLD`信号,提供了示例代码展示如何处理子进程退出信号,避免僵尸进程问题。
|
2月前
|
消息中间件 Unix Linux
C语言 多进程编程(五)消息队列
本文介绍了Linux系统中多进程通信之消息队列的使用方法。首先通过`ftok()`函数生成消息队列的唯一ID,然后使用`msgget()`创建消息队列,并通过`msgctl()`进行操作,如删除队列。接着,通过`msgsnd()`函数发送消息到消息队列,使用`msgrcv()`函数从队列中接收消息。文章提供了详细的函数原型、参数说明及示例代码,帮助读者理解和应用消息队列进行进程间通信。
|
2月前
|
缓存 Linux C语言
C语言 多进程编程(六)共享内存
本文介绍了Linux系统下的多进程通信机制——共享内存的使用方法。首先详细讲解了如何通过`shmget()`函数创建共享内存,并提供了示例代码。接着介绍了如何利用`shmctl()`函数删除共享内存。随后,文章解释了共享内存映射的概念及其实现方法,包括使用`shmat()`函数进行映射以及使用`shmdt()`函数解除映射,并给出了相应的示例代码。最后,展示了如何在共享内存中读写数据的具体操作流程。
|
2月前
|
消息中间件 Unix Linux
C语言 多进程编程(二)管道
本文详细介绍了Linux下的进程间通信(IPC),重点讨论了管道通信机制。首先,文章概述了进程间通信的基本概念及重要性,并列举了几种常见的IPC方式。接着深入探讨了管道通信,包括无名管道(匿名管道)和有名管道(命名管道)。无名管道主要用于父子进程间的单向通信,有名管道则可用于任意进程间的通信。文中提供了丰富的示例代码,展示了如何使用`pipe()`和`mkfifo()`函数创建管道,并通过实例演示了如何利用管道进行进程间的消息传递。此外,还分析了管道的特点、优缺点以及如何通过`errno`判断管道是否存在,帮助读者更好地理解和应用管道通信技术。
|
2月前
|
存储 Ubuntu Linux
C语言 多线程编程(1) 初识线程和条件变量
本文档详细介绍了多线程的概念、相关命令及线程的操作方法。首先解释了线程的定义及其与进程的关系,接着对比了线程与进程的区别。随后介绍了如何在 Linux 系统中使用 `pidstat`、`top` 和 `ps` 命令查看线程信息。文档还探讨了多进程和多线程模式各自的优缺点及适用场景,并详细讲解了如何使用 POSIX 线程库创建、退出、等待和取消线程。此外,还介绍了线程分离的概念和方法,并提供了多个示例代码帮助理解。最后,深入探讨了线程间的通讯机制、互斥锁和条件变量的使用,通过具体示例展示了如何实现生产者与消费者的同步模型。
|
2月前
|
Linux C语言
C语言 多进程编程(七)信号量
本文档详细介绍了进程间通信中的信号量机制。首先解释了资源竞争、临界资源和临界区的概念,并重点阐述了信号量如何解决这些问题。信号量作为一种协调共享资源访问的机制,包括互斥和同步两方面。文档还详细描述了无名信号量的初始化、等待、释放及销毁等操作,并提供了相应的 C 语言示例代码。此外,还介绍了如何创建信号量集合、初始化信号量以及信号量的操作方法。最后,通过实际示例展示了信号量在进程互斥和同步中的应用,包括如何使用信号量避免资源竞争,并实现了父子进程间的同步输出。附带的 `sem.h` 和 `sem.c` 文件提供了信号量操作的具体实现。
下一篇
无影云桌面