编写高性能c/c++程序-面试指南

目录

程序的版式

关于代码的空格规范

对齐

长行拆分

修饰符的位置

注释

类的版式

命名规则

共性规则

简单的 Windows 应用程序命名规则

简单的 Unix 应用程序命名规则

表达式和基本语句

运算符的优先级

复合表达式

if 语句

循环语句的效率

for 语句的循环控制变量

switch 语句

goto 语句

常量

为什么需要常量

const 与 #define 的比较

常量定义规则

类中的常量

函数设计

参数的规则

返回值的规则

函数内部实现的规则

其它建议

使用断言

引用与指针的比较

内存管理 

内存分配方式

常见的内存错误及其对策

指针与数组的对比


程序的版式

一行代码只做一件事情,如只定义一个变量,或只写一条语句。这样 的代码容易阅读,并且方便于写注释。

if、for、while、do 等语句自占一行,执行语句不得紧跟其后。不论 执行语句有多少都要加{}。这样可以防止书写失误。

尽可能在定义变量的同时初始化该变量(就近原则) 引用处和其定义处相隔比较远,变量的初始化很容易被忘记。

关于代码的空格规范

【规则 2-3-1】关键字之后要留空格。象 const、virtual、inline、case 等关键字之后 至少要留一个空格,否则无法辨析关键字。象 if、for、while 等关键字之后应留一个 空格再跟左括号‘(’,以突出关键字。

【规则 2-3-2】函数名之后不要留空格,紧跟左括号‘(’,以与关键字区别

【规则 2-3-3】‘(’向后紧跟,‘)’、‘,’、‘;’向前紧跟,紧跟处不留空格。

【规则 2-3-4】‘,’之后要留空格,如 Function(x, y, z)。如果‘;’不是一行的结束 符号,其后要留空格,如 for (initialization; condition; update)

【规则 2-3-5】赋值操作符、比较操作符、算术操作符、逻辑操作符、位域操作符, 如“=”、“+=” “>=”、“<=”、“+”、“*”、“%”、“&&”、“||”、“<<”,“^”等二元 操作符的前后应当加空格。

对齐

【规则 2-4-1】程序的分界符‘{’和‘}’应独占一行并且位于同一列,同时与引用 它们的语句左对齐

【规则 2-4-2】{ }之内的代码块在‘{’右边数格处左对齐。

长行拆分

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

if ((very_longer_variable1 >= very_longer_variable12) 
&& (very_longer_variable3 <= very_longer_variable14) 
&& (very_longer_variable5 <= very_longer_variable16){ 
  dosomething(); 
} 
virtual CMatrix CMultiplyMatrix (CMatrix leftMatrix, 
  CMatrix rightMatrix); 
for (very_longer_initialization; 
  very_longer_condition; 
  very_longer_update) 
{ 
  dosomething(); 
} 

修饰符的位置

【规则 2-6-1】应当将修饰符 * 和 & 紧靠变量名

例如: char *name; int *x, y; // 此处 y 不会被误解为指针

注释

【规则 2-7-1】注释是对代码的“提示”,而不是文档。程序中的注释不可喧宾夺主, 注释太多了会让人眼花缭乱。注释的花样要少。

【规则 2-7-2】如果代码本来就是清楚的,则不必加注释。否则多此一举,

例如 i++; // i 加 1,多余的注释

【规则 2-7-3】边写代码边注释,修改代码同时修改相应的注释,以保证注释与代码 的一致性。不再有用的注释要删除

【规则 2-7-4】注释应当准确、易懂,防止注释有二义性。错误的注释不但无益反而 有害

【规则 2-7-5】尽量避免在注释中使用缩写,特别是不常用缩写。

【规则 2-7-6】注释的位置应与被描述的代码相邻,可以放在代码的上方或右方,不 可放在下方。 z 【规则 2-7-8】当代码比较长,特别是有多重嵌套时,应当在一些段落的结束处加注 ,便于阅读

类的版式

建议读者采用“以行为为中心”的书写方式,即首先考虑类应该提供什么样的函数。

  

命名规则

该命名规则的主要思想是“在变量和函数名中加入前缀以增进人们对程序的理解”。

倘若采用“匈牙利”命名规则,则应当写成

int iI, iJ, ik; // 前缀 i 表示 int 类型 
​
float fX, fY, fZ; // 前缀 f 表示 float 类型 

共性规则

【规则 3-1-1】标识符应当直观且可以拼读,可望文知意,不必进行“解码”

【规则 3-1-2】标识符的长度应当符合“min-length && max-information”原则。

【规则 3-1-3】命名规则尽量与所采用的操作系统或开发工具的风格保持

例如 Windows 应用程序的标识符通常采用“大小写”混排的方式,如 AddChi

而Unix 应用程序的标识符通常采用“小写加下划线”的方式,如 add_child。

【规则 3-1-4】程序中不要出现仅靠大小写区分的相似的标识符。 例如: int x, X; // 变量 x 与 X 容易混淆 void foo(int x); // 函数 foo 与 FOO 容易混淆 void FOO(float x);

【规则 3-1-6】变量的名字应当使用“名词”或者“形容词+名词”。 例如: float value; float oldValue; float newValue;

【规则 3-1-7】全局函数的名字应当使用“动词”或者“动词+名词”(动宾词组)。 类的成员函数应当只使用“动词”,被省略掉的名词就是对象本身。 例如: DrawBox(); // 全局函数 box->Draw()// 类的成员函数

【规则 3-1-8】用正确的反义词组命名具有互斥意义的变量或相反动作的函数等。 例如: int minValue; int maxValue; int SetValue(…); int GetValue(…);

【建议 3-1-1】尽量避免名字中出现数字编号,如 Value1,Value2 等,除非逻辑上的 确需要编号。这是为了防止程序员偷懒,

简单的 Windows 应用程序命名规则

【规则 3-2-1】类名和函数名用大写字母开头的单词组合而成。 例如: class Node; // 类名 class LeafNode; // 类名 void Draw(void); // 函数名 void SetValue(int value); // 函数名

【规则 3-2-2】变量和参数用小写字母开头的单词组合而成。

【规则 3-2-3】常量全用大写的字母,用下划线分割单词。 例如: const int MAX = 100; const int MAX_LENGTH = 100;

【规则 3-2-4】静态变量加前缀 s_(表示 static)。 例如: void Init(…) { static int s_initValue; // 静态变量 … }

【规则 3-2-6】类的数据成员加前缀 m_(表示 member),这样可以避免数据成员与 成员函数的参数同名。 例如: void Object::SetValue(int width, int height) { m_width = width; m_height = height; }

【规则 3-2-7】为了防止某一软件库中的一些标识符和其它软件库中的冲突,可以为 各种标识符加上能反映软件性质的前缀。例如三维图形标准 OpenGL 的所有库函数 均以 gl 开头,所有常量(或宏定义)均以 GL 开头。

简单的 Unix 应用程序命名规则

表达式和基本语句

运算符的优先级

【规则 4-1-1】如果代码行中的运算符比较多,用括号确定表达式的操作顺序,避免 使用默认的优先级

复合表达式

如 a = b = c = 0 这样的表达式称为复合表达式。允许复合表达式存在的理由是:(1) 书写简洁;(2)可以提高编译效率。但要防止滥用复合表达式

【规则 4-2-1】不要编写太复杂的复合表达式

例如: i = a >= b && c < d && c + f <= g + h ; // 复合表达式过于复杂

【规则 4-2-2】不要有多用途的复合表达式。 例如: d = (a = b + c) + r ; 该表达式既求 a 值又求 d 值。应该拆分为两个独立的语句: a = b + c; d = a + r;

【规则 4-2-3】不要把程序中的复合表达式与“真正的数学表达式”混淆

if 语句

布尔变量与零值比较

【规则 4-3-1】不可将布尔变量直接与 TRUE、FALSE 或者 1、0 进行比较

假设布尔变量名字为 flag,它与零值比较的标准 if 语句如下: if (flag) // 表示 flag 为真 高质量 C++/C 编程指南,v 1.0 2001 Page 28 of 101 if (!flag) // 表示 flag 为假 其它的用法都属于不良风格,例如: if (flag == TRUE) if (flag == 1 ) if (flag == FALSE) if (flag == 0)

整型变量与零值比较

【规则 4-3-2】应当将整型变量用“==”或“!=”直接与 0 比较

假设整型变量的名字为 value,它与零值比较的标准 if 语句如下: if (value == 0) if (value != 0) 不可模仿布尔变量的风格而写成 if (value) // 会让人误解 value 是布尔变量 if (!value)

浮点变量与零值比较

【规则 4-3-3】不可将浮点变量用“==”或“!=”与任何数字比较

千万要留意,无论是 float 还是 double 类型的变量,都有精度限制。所以一定要避 免将浮点变量用“==”或“!=”与数字比较,应该设法转化成“>=”或“<=”形式。 假设浮点变量的名字为 x,应当将 if (x == 0.0) // 隐含错误的比较 转化为 if ((x>=-EPSINON) && (x<=EPSINON)) 其中 EPSINON 是允许的误差(即精度

指针变量与零值比较

【规则 4-3-4】应当将指针变量用“==”或“!=”与 NULL 比较。 指针变量的零值是“空”(记为 NULL)。尽管 NULL 的值与 0 相同,但是两者意义不 同。假设指针变量的名字为 p,它与零值比较的标准 if 语句如下: if (p == NULL) // p 与 NULL 显式比较,强调 p 是指针变量 if (p != NULL) 不要写成 if (p == 0) // 容易让人误解 p 是整型变量 if (p != 0) 或者 if (p) // 容易让人误解 p 是布尔变量 if (!p)

循环语句的效率

【建议 4-4-1】在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的 循环放在最外层,以减少 CPU 跨切循环层的次数。例如示例 4-4(b)的效率比示例 4-4(a)的高

【建议 4-4-2】如果循环体内存在逻辑判断,并且循环次数很大,宜将逻辑判断移到 2001 Page 30 of 101 循环体的外面。示例 4-4(c)的程序比示例 4-4(d)多执行了 N-1 次逻辑判断。并且由 于前者老要进行逻辑判断,打断了循环“流水线”作业,使得编译器不能对循环进 行优化处理,降低了效率。如果 N 非常大,最好采用示例 4-4(d)的写法,可以提高 效率。如果 N 非常小,两者效率差别并不明显,采用示例 4-4(c)的写法比较好,因 为程序更加简洁。

for 语句的循环控制变量

【规则 4-5-1】不可在 for 循环体内修改循环变量,防止 for 循环失去控制。 【建议 4-5-1】建议 for 语句的循环控制变量的取值采用“半开半闭区间”写法。

switch 语句

goto 语句

常量

为什么需要常量

【规则 5-1-1】 尽量使用含义直观的常量来表示那些将在程序中多次出现的数字或 字符串。

例如: #define MAX 100 /* C 语言的宏常量 */ const int MAX = 100; // C++ 语言的 const 常量 const float PI = 3.14159; // C++ 语言的 const 常量

const 与 #define 的比较

C++ 语言可以用 const 来定义常量,也可以用 #define 来定义常量。但是前者比后 者有更多的优点: (1) const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安 全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会 产生意料不到的错误(边际效应)。 (2) 有些集成化的调试工具可以对 const常量进行调试,但是不能对宏常量进行调试。

【规则 5-2-1】在 C++ 程序中只使用 const 常量而不使用宏常量,即 const 常量完 全取代宏常量。

常量定义规则

【规则 5-3-1】需要对外公开的常量放在头文件中,不需要对外公开的常量放在定义 文件的头部。为便于管理,可以把不同模块的常量集中存放在一个公共的头文件中。 【规则 5-3-2】如果某一常量与其它常量密切相关,应在定义中包含这种关系,而不 高质量 C++/C 编程指南,v 1.0 2001 Page 34 of 101 应给出一些孤立的值。 例如: const float RADIUS = 100; const float DIAMETER = RADIUS * 2;

类中的常量

函数设计

本章重点论述函数的接口设计和内部实现的一些规则。 函数接口的两个要素是参数和返回值。C 语言中,函数的参数和返回值的传递方式 有两种:值传递(pass by value)和指针传递(pass by pointer)。C++ 语言中多了引用传 递(pass by reference)。由于引用传递的性质象指针传递,而使用方式却象值传递,初 学者常常迷惑不解,容易引起混乱

参数的规则

【规则 6-1-1】参数的书写要完整,不要贪图省事只写参数的类型而省略参数名字。 如果函数没有参数,则用 void 填充。 例如:

void SetValue(int width, int height); // 良好的风格 
void SetValue(int, int); // 不良的风格 
float GetValue(void); // 良好的风格 
float GetValue(); // 不良的风格 

【规则 6-1-2】参数命名要恰当,顺序要合理。

例如编写字符串拷贝函数 StringCopy,它有两个参数。如果把参数名字起为 str1 和 str2,例如 void StringCopy(char *str1, char *str2);

【规则 6-1-3】如果参数是指针,且仅作输入用,则应在类型前加 const,以防止该 指针在函数体内被意外修改

void StringCopy(char *strDestination,const char *strSource); 

【规则 6-1-4】如果输入参数以值传递的方式传递对象,则宜改用“const &”方式来 传递,这样可以省去临时对象的构造和析构过程,从而提高效率。

【建议 6-1-1】避免函数有太多的参数,参数个数尽量控制在 5 个以内。如果参数太 多,在使用时容易将参数类型或顺序搞错

【建议 6-1-2】尽量不要使用类型和数目不确定的参数。

int printf(const chat *format[, argument]…);

返回值的规则

【规则 6-2-1】不要省略返回值的

【规则 6-2-2】函数名字与返回值类型在语义上不可冲突

【规则 6-2-3】不要将正常值和错误标志混在一起返回。正常值用输出参数获得,而 错误标志用 return 语句返回

【建议 6-2-1】有时候函数原本不需要返回值,但为了增加灵活性如支持链式表达, 可以附加返回值。 例如字符串拷贝函数 strcpy 的原型: char *strcpy(char *strDest,const char *strSrc); strcpy 函数将 strSrc 拷贝至输出参数 strDest 中,同时函数的返回值又是 strDest。这 样做并非多此一举,可以获得如下灵活性: char str[20]; int length = strlen( strcpy(str, “Hello World”) );

【建议 6-2-2】如果函数的返回值是一个对象,有些场合用“引用传递”替换“值传 递”可以提高效率。而有些场合只能用“值传递”而不能用“引用传递”,否则会出 错。

函数内部实现的规则

规则 6-3-1】在函数体的“入口处”,对参数的有效性进行检查。 很多程序错误是由非法参数引起的,我们应该充分理解并正确使用“断言”(assert) 来防止此类错误。

【规则 6-3-2】在函数体的“出口处”,对 return 语句的正确性和效率进行检查

其它建议

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

使用断言

如果程序在 assert 处终止 了,并不是说含有该 assert 的函数有错误,而是调用者出了差错,assert 可以帮助我们 找到发生错误的原因

【规则 6-5-1】使用断言捕捉不应该发生的非法情况。不要混淆非法情况与错误情况 之间的区别,后者是必然存在的并且是一定要作出处理的。 z 【规则 6-5-2】在函数的入口处,使用断言检查参数的有效性(合法性)。 z 【建议 6-5-1】在编写函数时,要进行反复的考查,并且自问:“我打算做哪些假定?” 一旦确定了的假定,就要使用断言对假定进行检查。 z 【建议 6-5-2】一般教科书都鼓励程序员们进行防错设计,但要记住这种编程风格可 能会隐瞒错误。当进行防错设计时,如果“不可能发生”的事情的确发生了,则要 使用断言进行报警。

引用与指针的比较

内存管理 

内存分配方式

  1. 从静态存储区域分配。

  2. 在栈上创建。

  3. 从堆上分配,亦称动态内存分配。

常见的内存错误及其对策

常见的内存错误及其对策如下: 内存分配未成功,却使用了它。

编程新手常犯这种错误,因为他们没有意识到内存分配会不成功。常用解决办法是, 在使用内存之前检查指针是否为 NULL。如果指针 p 是函数的参数,那么在函数的入口 处用 assert(p!=NULL)进行检查。如果是用 malloc 或 new 来申请内存,应该用 if(p==NULL) 或 if(p!=NULL)进行防错处理。

内存分配虽然成功,但是尚未初始化就引用它。

犯这种错误主要有两个起因:一是没有初始化的观念;二是误以为内存的缺省初值 全为零,导致引用初值错误(例如数组)。

内存分配成功并且已经初始化,但操作越过了内存的边界。

例如在使用数组时经常发生下标“多 1”或者“少 1”的操作。特别是在 for 循环语 句中,循环次数很容易搞错,导致数组操作越界

忘记了释放内存,造成内存泄露。

含有这种错误的函数每被调用一次就丢失一块内存。刚开始时系统的内存充足,你 看不到错误。终有一次程序突然死掉,系统出现提示:内存耗尽。 动态内存的申请与释放必须配对,程序中 malloc 与 free 的使用次数一定要相同,否 则肯定有错误(new/delete 同理)

释放了内存却继续使用它

有三种情况: (1)程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内 存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。 (2)函数的 return 语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用”, 因为该内存在函数体结束时被自动销毁。 (3)使用 free 或 delete 释放了内存后,没有将指针设置为 NULL。导致产生“野指针”。 z 【规则 7-2-1】用 malloc 或 new 申请内存之后,应该立即检查指针值是否为 NULL。 防止使用指针值为 NULL 的内存。 z 【规则 7-2-2】不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右 值使用。 z 【规则 7-2-3】避免数组或指针的下标越界,特别要当心发生“多 1”或者“少 1” 操作。 z 【规则 7-2-4】动态内存的申请与释放必须配对,防止内存泄漏。 z 【规则 7-2-5】用 free 或 delete 释放了内存之后,立即将指针设置为 NULL,防止产 生“野指针”。

指针与数组的对比

xxx | xxx

------|---

cxxx | xxx|

猜你喜欢

转载自blog.csdn.net/qq_35119182/article/details/88405947
今日推荐