《高质量C编程核心注意事项》核心笔记 - 全书

2023-12-27 00:32

本文主要是介绍《高质量C编程核心注意事项》核心笔记 - 全书,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

【文件结构】
版权和版本的声明位于头文件和定义文件的开头:
/*
* Copyright (c) 2017, xxx有限公司xxx部
* All rights reserved.
*
* 文件名称: filename.h
* 文件标识: 见配置管理计划书
* 摘 要: 简要描述本文件的内容
*
* 当前版本: 1.1
* 作 者: 输入作者(或修改者)名字
* 完成日期: 2017年5月20日
*
* 取代版本: 1.0
* 原作者 : 输入原作者(或修改者)名字
* 完成日期: 2017年5月20日
*/

>>头文件结构
1. 版权和版本声明;
2. 预处理块;
3. 类或结构声明;
4. 函数声明;
规则:头文件卫士、头文件引用<>和""、只放声明不放定义。

>>定义文件/源文件结构
1. 版权和版本声明;
2. 必要的头文件引用;
3. 程序的实现体(数据和代码);

根据具体实际项目情况,将头文件与定义文件分类单独存放(include目录,source目录),或者按模块创建目录存放(模块目录,每个模块目录里包含.h和.c/.cpp)。


【程序的版式】
>>空行

1. 在每个类声明之后、每个函数定义结束之后都要加空行。
2. 在一个函数体内,逻揖上密切相关的语句之间不加空行,其它地方应加空行分隔。

>>代码行
1. 一行代码只做一件事情,比如变量只定义一个,或语句只写一条。这样的代码容易阅读,并且方便于写注释。
2. if、 for、 while、 do等语句自占一行,执行语句不得紧跟其后。不论执行语句有多少都要加{}。这样可以防止书写失误。
3. 尽可能在定义变量的同时初始化该变量(就近原则)。如果变量的引用处和其定义处相隔比较远,变量的初始化很容易被忘记。如果引用了未被初始化的变量,可能会导致程序错误。

>>代码行内的空格
1. 关键字之后均要留空格。如后面跟小括号'()'的关键字也最好留空格,突出关键字;
2. 函数名之后不要留空格,紧跟左括号'(',以与关键字区别;
3. '('向后紧跟,')',',',';'向前进跟,紧跟处不留空格;
4. ','之后要留空格,';'不是一行结束时也要留空格;
5. 二元操作符的前后应当加空格,一元操作运算符如!, ~, ++等不加空格;

>>对齐
1. 程序的分界符‘{’和‘}’应独占一行并且位于同一列,同时与引用它们的语句左对齐。
2. { }之内的代码块在‘{’右边数格处左对齐。

>>长行拆分
1. 代码行最大长度宜控制在 70 至 80 个字符以内。代码行不要过长,否则眼睛看不过来,也不便于打印。
2. 长表达式要在低优先级操作符处拆分成新行,操作符放在新行之首(以便突出操作符)。拆分出的新行要进行适当的缩进,使排版整齐,语句可读。

>>修饰符的位置
1. 应当将修饰符 * 和 & 紧靠变量名;

>>注释
1. 程序块的注释常采用“/*…*/”,行注释一般采用“//…”
2. 注释是对代码的“提示”,而不是文档。程序中的注释不可喧宾夺主,注释太多了会让人眼花缭乱。注释的花样要少。
3. 如果代码本来就是清楚的,则不必加注释。
4. 边写代码边注释,修改代码同时修改相应的注释,以保证注释与代码的一致性。不再有用的注释要删除。
5. 注释应当准确、易懂,防止注释有二义性。
6. 尽量避免在注释中使用缩写,特别是不常用缩写。
7. 注释的位置应与被描述的代码相邻,可以放在代码的上方或右方,不可放在下方。
8. 当代码比较长,特别是有多重嵌套时,应当在一些段落的结束处加注释,便于阅读。
/*
* 函数介绍:
* 输入参数:
* 输出参数:
* 返回值 :
*/
void Function(float x, float y, float z)
{…
}


【命名规则】

>>共性规则

1. 标识符应当直观且可以拼读,可望文知意,不必进行“解码”。标识符最好采用英文单词或其组合,便于记忆和阅读。
2. 标识符的长度应当符合“min-length && max-information”原则。例如变量名 maxval 就比 maxValueUntilOverflow好用。单字符的名字也是有用的,常见的如 i,j,k,m,n,x,y,z 等,它们通常可用作函数内的局部变量。
3. 命名规则尽量与所采用的操作系统或开发工具的风格保持一致。
    例如 Windows 应用程序的标识符通常采用“大小写”混排的方式,如 AddChild。
    而 Unix 应用程序的标识符通常采用“小写加下划线”的方式,如 add_child。
4. 程序中不要出现仅靠大小写区分的相似的标识符。
5. 程序中不要出现标识符完全相同的局部变量和全局变量,尽管两者的作用域不同而不会发生语法错误,但会使人误解。
6. 变量的名字应当使用“名词”或者“形容词+名词”。
7. 全局函数的名字应当使用“动词”或者“动词+名词”(动宾词组)。类的成员函数应当只使用“动词”,被省略掉的名词就是对象本身。
8. 用正确的反义词组命名具有互斥意义的变量或相反动作的函数等。
9. 尽量避免名字中出现数字编号,如 Value1,Value2 等,除非逻辑上的确需要编号。这是为了防止程序员偷懒,不肯为命名动脑筋而导致产生无意义的名字(因为用数字编号最省事)。

/* 应用程序命名规则常用手段 */
常量全用大写的字母,用下划线分割单词。
静态变量加前缀 s_(表示 static)。
如果不得已需要全局变量,则使全局变量加前缀 g_(表示 global)。
类的数据成员加前缀 m_(表示 member),这样可以避免数据成员与成员函数的参数同名。


【表达式和基本语句】
1. 如果代码行中的运算符比较多,用括号确定表达式的操作顺序,避免使用默认的优先级。
2. 不要编写太复杂的复合表达式。例如:i = a >= b && c < d && c + f <= g + h ;
3. 不要有多用途的复合表达式。例如:d = (a = b + c) + r ;
4. 不要把程序中的复合表达式与“真正的数学表达式”混淆。

>>if语句
1. 不可将布尔变量直接与 TRUE、 FALSE 或者 1、 0 进行比较。例如 Visual C++ 将 TRUE 定义为1,而 Visual Basic 则将 TRUE 定义为-1。
2. 应当将整型变量用“==”或“! =”直接与 0 比较。
3. 不可将浮点变量用“==”或“! =”与任何数字比较。
4. 应当将指针变量用“==”或“! =”与 NULL 比较。
提示:鼓励 if (NULL == p) 这样古怪的格式,有利于编译器查找对==的手误。

>>循环语句的效率

1. 在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外层,以减少 CPU 跨切循环层的次数。
2. 如果循环体内存在逻辑判断,并且循环次数很大,宜将逻辑判断移到循环体的外面。

>>for语句
1. 不可在 for 循环体内修改循环变量,防止 for 循环失去控制。
2. 建议 for 语句的循环控制变量的取值采用“半开半闭区间”写法。
    x 值属于半开半闭区间“0 =< x < N”,起点到终点的间隔为 N,循环次数为 N。
    x 值属于闭区间“0 =< x <= N-1”,起点到终点的间隔为 N-1,循环次数为 N。

>>switch语句

1. 每个 case 语句的结尾不要忘了加 break,否则将导致多个分支重叠(除非有意使多个分支重叠)。
2. 不要忘记最后那个 default 分支。即使程序真的不需要 default 处理,也应该保留语句 default : break; 这样做并非多此一举,而是为了防止别人误以为你忘了 default 处理。


【常量】
1. 尽量使用含义直观的常量来表示那些将在程序中多次出现的数字或字符串。
例如:
#define MAX 100             /* C 语言的宏常量 */
const int MAX = 100;        // C++ 语言的 const 常量
const float PI = 3.14159;   // C++ 语言的 const 常量
2. 在 C++ 程序中只使用 const 常量而不使用宏常量,即 const 常量完全取代宏常量。
3. 需要对外公开的常量放在头文件中,不需要对外公开的常量放在定义文件的头部。为便于管理,可以把不同模块的常量集中存放在一个公共的头文件中。
4. 如果某一常量与其它常量密切相关,应在定义中包含这种关系,而不应给出一些孤立的值。


【函数设计】

>>参数的规则

1. 参数的书写要完整,不要贪图省事只写参数的类型而省略参数名字。如果函数没有参数,则用 void 填充。
2. 参数命名要恰当,顺序要合理。
3. 如果参数是指针,且仅作输入用,则应在类型前加 const ,以防止该指针在函数体内被意外修改。
4. C++中如果输入参数以值传递的方式传递对象,则宜改用“ const &”方式来传递,这样可以省去临时对象的构造和析构过程,从而提高效率。
5. 避免函数有太多的参数,参数个数尽量控制在 5 个以内。如果参数太多,在使用时容易将参数类型或顺序搞错。
6. 尽量不要使用类型和数目不确定的参数。

>>返回值的规则
1. 不要省略返回值的类型。
2. 函数名字与返回值类型在语义上不可冲突。
3. 不要将正常值和错误标志混在一起返回。正常值用输出参数获得,而错误标志用 return 语句返回。
4. 有时候函数原本不需要返回值,但为了增加灵活性如支持链式表达,可以附加返回值。
5. 如果函数的返回值是一个对象,有些场合用“引用传递”替换“值传递”可以提高效率。而有些场合只能用“值传递”而不能用“引用传递”,否则会出错。

>>函数内部实现的规则
1. 在函数体的“入口处”,对参数的有效性进行检查。
    很多程序错误是由非法参数引起的,我们应该充分理解并正确使用“断言”(assert)来防止此类错误。
2. 在函数体的“出口处”,对 return 语句的正确性和效率进行检查。
    (1)return语句不可返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。
    (2)要搞清楚返回的究竟是“值”、“指针”还是“引用”。
    (3)如果函数返回值是一个对象,要考虑 return 语句的效率。

>>函数的其他建议
1. 函数的功能要单一,不要设计多用途的函数。
2. 函数体的规模要小,尽量控制在 50 行代码之内。
3. 尽量避免函数带有“记忆”功能。相同的输入应当产生相同的输出。
带有“记忆”功能的函数,其行为可能是不可预测的,因为它的行为可能取决于某种“记忆状态”。这样的函数既不易理解又不利于测试和维护。在 C/C++语言中,函数的 static 局部变量是函数的“记忆”存储器。建议尽量少用 static 局部变量,除非必需。
4. 不仅要检查输入参数的有效性,还要检查通过其它途径进入函数体内的变量的有效性,例如全局变量、文件句柄等。
5. 用于出错处理的返回值一定要清楚,让使用者不容易忽视或误解错误情况。

>>使用断言
程序一般分为 Debug 版本和 Release 版本, Debug 版本用于内部调试, Release 版本发行给用户使用。
断言 assert 是仅在 Debug 版本起作用的宏(assert 不是函数,而是宏),它用于检查“不应该”发生的情况。在运行过程中,如果 assert 的参数为假,那么程序就会中止(一般地还会出现提示对话,说明在什么地方引发了 assert)。
例如:
void *memcpy(void *pvTo, const void *pvFrom, size_t size)
{assert((pvTo != NULL) && (pvFrom != NULL)); // 使用断言byte *pbTo = (byte *)pvTo;                 // 防止改变 pvTo 的地址byte *pbFrom = (byte *)pvFrom;             // 防止改变 pvFrom 的地址while(size-- > 0 )*pbTo++ = *pbFrom++ ;return pvTo;
}
1. 使用断言捕捉不应该发生的非法情况。不要混淆非法情况与错误情况之间的区别,后者是必然存在的并且是一定要作出处理的。
2. 在函数的入口处,使用断言检查参数的有效性(合法性)。
3. 在编写函数时,要进行反复的考查,并且自问:“我打算做哪些假定?”一旦确定了的假定,就要使用断言对假定进行检查。
4. 当进行防错设计时,如果“不可能发生”的事情的确发生了,则要使用断言进行报警。


【内存管理】
内存分配方式有三种:静态存储区分配、栈上分配、堆上分配。
1. 用 malloc 或 new 申请内存之后,应该立即检查指针值是否为 NULL。防止使用指针值为 NULL 的内存。
2. 不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。
3. 避免数组或指针的下标越界,特别要当心发生“多 1”或者“少 1”操作。
4. 动态内存的申请与释放必须配对,防止内存泄漏。
5. 用 free 或 delete 释放了内存之后,立即将指针设置为 NULL,防止产生“野指针”。
/* 一定要使用指针参数去申请内存,就必须使用 二级指针 */void GetMemory2(char **p, int num)
{*p = (char *)malloc(sizeof(char) * num);
}void Test2(void)
{char *str = NULL;GetMemory2(&str, 100); // 注意参数是 &str,而不是 strstrcpy(str, "hello");cout<< str << endl;free(str);str = NULL;
}
经验教训:
(1)越是怕指针,就越要使用指针。不会正确使用指针,肯定算不上是合格的程序员。
(2)必须养成“使用调试器逐步跟踪程序”的习惯,只有这样才能发现问题的本质。


【其他编程经验】

>>使用 const 提高函数的健壮性

1. 如果输入参数采用“指针传递”,那么加 const 修饰可以防止意外地改动该指针,起到保护作用。
2. 如果输入参数采用“值传递”,由于函数将自动产生临时变量用于复制该参数,该输入参数本来就无需保护,所以不要加 const 修饰。
3. 如果给以“指针传递”方式的函数返回值加 const 修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加 const 修饰的同类型指针。
4. 如果函数返回值采用“值传递方式”,由于函数会把返回值复制到外部临时的存储单元中,加 const 修饰没有任何价值。

>>提高程序的运行效率
1. 不要一味地追求程序的效率,应当在满足正确性、可靠性、健壮性、可读性等质量因素的前提下,设法提高程序的效率。
2. 以提高程序的全局效率为主,提高局部效率为辅。
3. 在优化程序的效率时,应当先找出限制效率的“瓶颈”,不要在无关紧要之处优化。
4. 先优化数据结构和算法,再优化执行代码。
5. 有时候时间效率和空间效率可能对立,此时应当分析哪个更重要,作出适当的折衷。例如多花费一些内存来提高性能。
6. 不要追求紧凑的代码,因为紧凑的代码并不能产生高效的机器码。

>>一些有益的建议
1.   当心那些视觉上不易分辨的操作符发生书写错误。
2.   我们经常会把“==”误写成“=”,象“||”、“&&”、“<=”、“>=”这类符号也很容易发生“丢 1”失误。然而编译器却不一定能自动指出这类错误。
3.   变量(指针、数组)被创建之后应当及时把它们初始化,以防止把未被初始化的变量当成右值使用。
4.   当心变量的初值、缺省值错误,或者精度不够。
5.   当心数据类型转换发生错误。尽量使用显式的数据类型转换(让人们知道发生了什么事),避免让编译器轻悄悄地进行隐式的数据类型转换。
6.   当心变量发生上溢或下溢,数组的下标越界。
7.   当心忘记编写错误处理程序,当心错误处理程序本身有误。
8.   当心文件 I/O 有错误。
9.   避免编写技巧性很高代码。
10. 不要设计面面俱到、非常灵活的数据结构。
11. 如果原有的代码质量比较好,尽量复用它。但是不要修补很差劲的代码,应当重新编写。
12. 尽量使用标准库函数,不要“发明”已经存在的库函数。
13. 尽量不要使用与具体硬件或软件环境关系密切的变量。
14. 把编译器的选择项设置为最严格状态。
15. 如果可能的话,使用 PC-Lint、 LogiScope 等工具进行代码审查。


C/C++代码审查表.doc


这篇关于《高质量C编程核心注意事项》核心笔记 - 全书的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/541326

相关文章

Andrej Karpathy最新采访:认知核心模型10亿参数就够了,AI会打破教育不公的僵局

夕小瑶科技说 原创  作者 | 海野 AI圈子的红人,AI大神Andrej Karpathy,曾是OpenAI联合创始人之一,特斯拉AI总监。上一次的动态是官宣创办一家名为 Eureka Labs 的人工智能+教育公司 ,宣布将长期致力于AI原生教育。 近日,Andrej Karpathy接受了No Priors(投资博客)的采访,与硅谷知名投资人 Sara Guo 和 Elad G

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

【编程底层思考】垃圾收集机制,GC算法,垃圾收集器类型概述

Java的垃圾收集(Garbage Collection,GC)机制是Java语言的一大特色,它负责自动管理内存的回收,释放不再使用的对象所占用的内存。以下是对Java垃圾收集机制的详细介绍: 一、垃圾收集机制概述: 对象存活判断:垃圾收集器定期检查堆内存中的对象,判断哪些对象是“垃圾”,即不再被任何引用链直接或间接引用的对象。内存回收:将判断为垃圾的对象占用的内存进行回收,以便重新使用。

Go Playground 在线编程环境

For all examples in this and the next chapter, we will use Go Playground. Go Playground represents a web service that can run programs written in Go. It can be opened in a web browser using the follow

bytes.split的用法和注意事项

当然,我很乐意详细介绍 bytes.Split 的用法和注意事项。这个函数是 Go 标准库中 bytes 包的一个重要组成部分,用于分割字节切片。 基本用法 bytes.Split 的函数签名如下: func Split(s, sep []byte) [][]byte s 是要分割的字节切片sep 是用作分隔符的字节切片返回值是一个二维字节切片,包含分割后的结果 基本使用示例: pa

深入理解RxJava:响应式编程的现代方式

在当今的软件开发世界中,异步编程和事件驱动的架构变得越来越重要。RxJava,作为响应式编程(Reactive Programming)的一个流行库,为Java和Android开发者提供了一种强大的方式来处理异步任务和事件流。本文将深入探讨RxJava的核心概念、优势以及如何在实际项目中应用它。 文章目录 💯 什么是RxJava?💯 响应式编程的优势💯 RxJava的核心概念

论文阅读笔记: Segment Anything

文章目录 Segment Anything摘要引言任务模型数据引擎数据集负责任的人工智能 Segment Anything Model图像编码器提示编码器mask解码器解决歧义损失和训练 Segment Anything 论文地址: https://arxiv.org/abs/2304.02643 代码地址:https://github.com/facebookresear