摘自:http://man.chinaunix.net/develop/c&c++/c/c.htm
前 言
7.8 有了 malloc/free 为什么还要 new/delete ?
附录 A : C++/C 代码审查表
文 件结构 | ||
重要性 | 审查项 | 结论 |
| 头文 件和定义文件的名称是否合理? |
|
| 头文 件和定义文件的目录结构是否合理? |
|
| 版权 和版本声明是否完整? |
|
重 要 | 头文 件是否使用了 ifndef/define/endif 预处理块 ? |
|
| 头文 件中是否只存放“声明”而不存放“定义” |
|
| …… |
|
程 序的版式 | ||
重要性 | 审查项 | 结论 |
| 空行 是否得体? |
|
| 代码 行内的空格是否得体? |
|
| 长行 拆分是否得体? |
|
| “{” 和 “}” 是否各占一行并且对齐于同一列? |
|
重 要 | 一行 代码是否只做一件事?如只定义一个变量,只写一条语句。 |
|
重 要 | If 、 for 、 while 、 do 等语句自占一行,不论执行语句多少都要加“ {} ”。 |
|
重 要 | 在定 义变量(或参数)时,是否将修饰符 * 和 & 紧靠变量名? |
|
| 注释 是否清晰并且必要? |
|
重 要 | 注释 是否有错误或者可能导致误解? |
|
重 要 | 类结 构的 public, protected, private 顺序是否在所有的程序中 保持一致? |
|
| …… |
|
命 名规则 | ||
重要性 | 审查项 | 结论 |
重 要 | 命名 规则是否与所采用的操作系统或开发工具的风格保持一致? |
|
| 标识 符是否直观且可以拼读? |
|
| 标识 符的长度应当 符合“min-length && max-information”原则? |
|
重 要 | 程序 中是否出现 相同的局部变量和全部变量? |
|
| 类 名、函数名、变量和参数、常量的书写格式是否遵循一定的规则? |
|
| 静态 变量、全局变量、类的成员变量是否加前缀? |
|
| …… |
|
表 达式与基本语句 | ||
重要性 | 审查项 | 结论 |
重 要 | 如果 代码行中的运算符比较多,是否已经用括号清楚地确定表达式的操作顺序? |
|
| 是否 编写太复杂或者多用途的复合表达式? |
|
重 要 | 是否 将复合表达式与“真正的数学表达式”混淆? |
|
重 要 | 是否 用隐含错误的方式写 if 语句 ? 例如 ( 1 )将 布尔变量直接与 TRUE 、 FALSE 或者 1 、 0 进行比 较。 ( 2 )将 浮点变量用“==”或“!=”与任何数字比较 。 ( 3 )将 指针变量用“==”或“!=”与NULL比较 。 |
|
| 如果 循环体内存在逻辑判断,并且循环次数很大,是否已经将逻辑判断移到循环体的外面? |
|
重 要 | Case语句的结尾是否忘了加break? |
|
重 要 | 是否 忘记写switch的default分支? |
|
重 要 | 使用goto 语句时是否留下隐患? 例如跳过了某些对象的构造、变量的初始化、重要的计算等。 |
|
| …… |
|
常 量 | ||
重要性 | 审查项 | 结论 |
| 是否 使用含义直观的常量来表示那些将在程序中多次出现的数字或字符串? |
|
| 在C++ 程序中,是否用const常量取代宏常量? |
|
重 要 | 如果 某一常量与其它常量密切相关,是否在定义中包含了这种关系? |
|
| 是否 误解了类中的const数据成员?因为const数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变 的。 |
|
| …… |
|
函 数设计 | ||
重要性 | 审查项 | 结论 |
| 参数 的书写是否完整?不要贪 图省事只写参数 的类型而 省略参数名字。 |
|
| 参数 命名、顺序是否合理? |
|
| 参数 的个数是否太多? |
|
| 是否 使用类型和数目不确定的参数? |
|
| 是否 省略了函数返回值的类型? |
|
| 函数 名字与返回值类型在语义上是否冲突? |
|
重 要 | 是否 将正常值和错误标志混在一起返回?正常值应当用输出参数获得,而错误标志用 return 语 句返回。 |
|
重 要 | 在函 数体的“入口处”,是否用 assert 对参数的有效性进行检查? |
|
重 要 | 使用 滥用了 assert ? 例如混淆非法情况与错 误情况,后者是必然存在的并且是一定要作出处理的。 |
|
重 要 | return 语句是否返回指向“栈内存”的“指针”或者“引用”? |
|
| 是否 使用 const 提高函数的健壮性? const 可以强制保护函数的参数、返回值,甚至函数的定义体。“ Use const whenever you need ” |
|
| …… |
|
内 存管理 | ||
重要性 | 审查项 | 结论 |
重 要 | 用malloc或new申请内存之后,是否立即检查指针值是否为NULL?(防止使用指针值为NULL的内存) |
|
重 要 | 是否 忘记为数组和动态内存赋初值?( 防止 将未被初始化的内存作为右值使用) |
|
重 要 | 数组 或指针的下标是否越界? |
|
重 要 | 动态 内存的申请与释放是否配对?(防止内存泄漏) |
|
重 要 | 是否 有效地处理了“内存耗尽”问题? |
|
重 要 | 是否 修改“指向常量的指针”的内容? |
|
重 要 | 是否 出现野指针?例如 ( 1 )指针变量没有被初始化。 ( 2 )用 free 或 delete 释放了内存之后,忘记将指针设置为 NULL 。 |
|
重 要 | 是否 将 malloc/free 和 new/delete 混淆使用? |
|
重 要 | malloc语句是否正确无误?例如 字节数是否正确?类型转换是否 正确? |
|
重 要 | 在创 建与释放动态对象数组时, new/delete 的语句是否 正确无误? |
|
| …… |
|
C++ 函数的高级特性 | ||
重要性 | 审查项 | 结论 |
| 重载 函数是否有二义性? |
|
重 要 | 是否 混淆了成员函数的重载、覆盖与隐藏? |
|
| 运算 符的重载是否符合制定的编程规范? |
|
| 是否 滥用内联函数?例如函数体内的代码比较长,函数体内出现循环。 |
|
重 要 | 是否 用内联函数取代了宏代码? |
|
| …… |
|
类 的构造函数、析构函数和赋值函数 | ||
重要性 | 审查项 | 结论 |
重 要 | 是否 违背编程规范而让 C++ 编译器自动为类产生四个缺省的函数: (1) 缺省的 无参数构造函数;(2)缺 省的拷贝构造函数;(3)缺省的析构函数;(4)缺省的赋值函数。 |
|
重 要 | 构造 函数中是否遗漏了某些初始化工作? |
|
重 要 | 是否 正确地使用构造函数的初始化表? |
|
重 要 | 析构 函数中是否遗漏了某些清除工作? |
|
| 是否 错写、错用了拷贝构造函数和赋值函数? |
|
重 要 | 赋值 函数一般分四个步骤:(1)检查自赋值;(2)释放原有内存资源;(3)分配新的内存资源,并复制内容;(4)返 回 *this。是否遗漏了重要步骤? |
|
重 要 | 是否 正确地编写了派生类的构造函数、析构函数、赋值函数?注意事项: ( 1 )派生类不可能继承基类的构造函数、析构函数、赋值函数。 ( 2 )派生类的构造函数应在其初始化表里调用基类的构造函数。 ( 3 )基类与派生类的析构函数应该为虚(即加 virtual 关键字)。 ( 4 )在编写派生类的赋值函数时,注意不要忘记对基类的数据成员重新赋值。 |
|
| …… |
|
类 的高级特性 | ||
重要性 | 审查项 | 结论 |
重 要 | 是否 违背了继承和组合的规则? ( 1 )若在逻辑上 B 是 A 的“一种”,并且 A 的所有功 能和属性对 B 而言都有意义,则允许 B 继承 A 的功能和 属性。 ( 2 )若在逻辑上 A 是 B 的“一部分”( a part of ),则不允许 B 从 A 派生,而是要用 A 和其它东 西组合出 B 。 |
|
| …… |
|
其 它常见问题 | ||
重要性 | 审查项 | 结论 |
重 要 | 数据 类型问题: (1) 变量的数据类型有错误吗? (2) 存在不同数据类型的赋值吗? (3) 存在不同数据类型的比较吗? |
|
重 要 | 变量 值问题: (1) 变量的初始化或缺省值有错误吗? (2) 变量发生上溢或下溢吗? (3) 变量的精度够吗? |
|
重 要 | 逻辑 判断问题: (1) 由于精度原因导致比较无效吗? (2) 表达式中的优先级有误吗? (3) 逻辑判断结果颠倒吗? |
|
重 要 | 循环 问题: (1) 循环终止条件不正确吗? (2) 无法正常终止(死循环)吗? (3) 错误地修改循环变量吗? (4) 存在误差累积吗? |
|
重 要 | 错误 处理问题: (1) 忘记进行错误处理吗? (2) 错误处理程序块一直没有机会被运行? (3) 错误处理程序块本身就有毛病吗?如报告的错误与实际错误不一致,处理方式不正确等等。 (4) 错误处理程序块是“马后炮”吗?如在被它被调用之前软件已经出错。 |
|
重 要 | 文件 I/O 问题: (1) 对不存在的或者错误的文件进行操作吗? (2) 文件以不正确的方式打开吗? (3) 文件结束判断不正确吗? (4) 没有正确地关闭文件吗? |
|
附录 B : C++/C 试题
本试题仅用于考查 C++/C 程序员的基本编程技能。内容限于 C++/C 常用语法,不涉及数据结构、算法以及深奥的语法。考试成绩能反映出考生的编程质量以及对 C++/C 的理解程度,但不能反映考生的智 力和软件开发能力。
笔试时间 90 分钟。请考生认真答题,切勿轻视。
一、请填写 BOOL , float, 指针变量 与“零值”比较的 if 语句。( 10 分)
提示:这里“零值”可以是 0, 0.0 , FALSE 或者“空指针”。例如 int 变量 n 与“零值”比较的 if 语句为:
if ( n == 0 )
if ( n != 0 )
以此类推。
请写出 BOOL flag 与“零值”比较的 if 语句:
|
请写出 float x 与“零值”比较的 if 语句:
|
请写出 char *p 与“零值”比较的 if 语句:
|
二、以下为 Windows NT 下的 32 位 C++ 程序,请计算 sizeof 的值( 10 分)
char str[] = “Hello” ; char *p = str ; int n = 10; 请计算 sizeof (str ) =
sizeof ( p ) =
sizeof ( n ) = | void Func ( char str[100]) { 请计算 sizeof( str ) = }
|
void *p = malloc( 100 ); 请计算 sizeof ( p ) =
|
三、简答题( 25 分)
1 、头文件中的 ifndef/define/endif 干什么用?
2 、 #include <filename.h> 和 #include “filename.h” 有什么区别?
3 、 const 有什么用途?(请至少说明两种)
4 、在 C++ 程序中调用被 C 编译器编译后的函数,为什么要加 extern “C” 声明?
5 、请简述以下两个 for 循环的优缺点
// 第一个 for (i=0; i<N; i++) { if (condition) DoSomething(); else DoOtherthing(); } | // 第二个 if (condition) { for (i=0; i<N; i++) DoSomething(); } else { for (i=0; i<N; i++) DoOtherthing(); } |
优点:
缺点:
| 优点:
缺点:
|
四、有关内存的思考题( 20 分)
void GetMemory(char *p) { p = (char *)malloc(100); } void Test(void) { char *str = NULL; GetMemory(str); strcpy(str, "hello world"); printf(str); }
请问运行Test函数会有什么样的结果? 答:
| char *GetMemory(void) { char p[] = "hello world"; return p; } void Test(void) { char *str = NULL; str = GetMemory(); printf(str); }
请问运行Test函数会有什么样的结果? 答: |
Void GetMemory2(char **p, int num) { *p = (char *)malloc(num); } void Test(void) { char *str = NULL; GetMemory(&str, 100); strcpy(str, "hello"); printf(str); } 请问运行Test函数会有什么样的结果? 答:
| void Test(void) { char *str = (char *) malloc(100); strcpy(str, “ hello ” ); free(str); if(str != NULL) { strcpy(str, “ world ” ); printf(str); } } 请问运行Test函数会有什么样的结果? 答:
|
五、编写 strcpy 函数( 10 分)
已知 strcpy 函数的原型是
char *strcpy(char *strDest, const char *strSrc);
其中 strDest 是目的字符串, strSrc 是源字符串。
( 1 )不调用 C++/C 的字符串库函数,请编写函数 strcpy
( 2 ) strcpy 能把 strSrc 的内容复制到 strDest ,为什么还要 char * 类型的返回值?
六、编写类 String 的构造函数、析 构函数和赋值函数( 25 分)
已知类 String 的原型为:
class String
{
public:
String(const char *str = NULL); // 普通构造函数
String(const String &other); // 拷贝构造函数
~ String(void); // 析构函数
String & operate =(const String &other); // 赋值函数
private:
char *m_data; // 用于保存字符串
};
请编写 String的上述4个函数。
附录 C : C++/C 试题的答案与评分标准
一、请填写 BOOL , float, 指针变量 与“零值”比较的 if 语句。( 10 分)
请写出 BOOL flag 与“零值”比较的 if 语句。( 3 分) | |
标准答案: if ( flag ) if ( !flag ) | 如下写法均属不良风格,不得分。 if (flag == TRUE) if (flag == 1 ) if (flag == FALSE) if (flag == 0) |
请写出 float x 与“零值”比较的 if 语句。( 4 分) | |
标准答案示例: const float EPSINON = 0.00001; if ((x >= - EPSINON) && (x <= EPSINON) 不可将浮点变量用“==”或“!=”与数字比较,应该设法转化成“>=”或“<=”此类形式。
|
如下是错误的写法,不得分。 if (x == 0.0) if (x != 0.0)
|
请写出 char *p 与“零值”比较的 if 语句。( 3 分) | |
标准答案: if (p == NULL) if (p != NULL) | 如下写法均属不良风格,不得分。 if (p == 0) if (p != 0) if (p) if (!) |
二、以下为 Windows NT 下的 32 位 C++ 程序,请计算 sizeof 的值( 10 分)
char str[] = “Hello” ; char *p = str ; int n = 10; 请计算 sizeof (str ) = 6 ( 2 分)
sizeof ( p ) = 4 ( 2 分)
sizeof ( n ) = 4 ( 2 分) | void Func ( char str[100]) { 请计算 sizeof( str ) = 4 ( 2 分) }
|
void *p = malloc( 100 ); 请计算 sizeof ( p ) = 4 ( 2 分)
|
三、简答题( 25 分)
1 、头文件中的 ifndef/define/endif 干什么用?( 5 分)
答:防止该头文件被重复引用。
2 、 #include <filename.h> 和 #include “filename.h” 有什么区别?( 5 分)
答:对于 #include <filename.h> ,编译器从标准库路径开始搜索 filename.h
对于 #include “filename.h” ,编译器从用户的工作路径开始搜索 filename.h
3 、 const 有什么用途?(请至少说明两种)( 5 分)
答:( 1 )可以定义 const 常量
( 2 ) const 可以修饰函数的参数、返回值,甚至函数的定义体。被 const 修饰的东西都受到强 制保护,可以预防意外的变动,能提高程序的健壮性。
4 、在 C++ 程序中调用被 C 编译器编译后的函数,为什么要加 extern “C” ? ( 5 分)
答: C++ 语言支持函数重载, C 语言不支持函数重载。函数 被 C++ 编译后在库中的名字与 C 语言的不同。假设某个函数的原型为: void foo(int x, int y);
该函数被 C 编译器编译后在库中的名字为 _foo , 而 C++ 编译器则会产生像 _foo_int_int 之 类的名字。
C++ 提供了 C 连接交换指定符号 extern “ C ”来解决名字匹配问题。
5 、请简述以下两个 for 循环的优缺点( 5 分)
for (i=0; i<N; i++) { if (condition) DoSomething(); else DoOtherthing(); } | if (condition) { for (i=0; i<N; i++) DoSomething(); } else { for (i=0; i<N; i++) DoOtherthing(); } |
优点:程序简洁
缺点:多执行了N-1次逻辑判断,并且打断了循环“流水线”作业,使得编译器不能对循环进行优化处理,降低了效率。 | 优点:循环的效率高
缺点:程序不简洁
|
四、有关内存的思考题(每小题 5 分,共 20 分)
void GetMemory(char *p) { p = (char *)malloc(100); } void Test(void) { char *str = NULL; GetMemory(str); strcpy(str, "hello world"); printf(str); }
请问运行Test函数会有什么样的结果? 答:程序崩溃。 因为 GetMemory 并不能传递 动态内存, Test 函数中的 str一直都是 NULL。 strcpy(str, "hello world");将使 程序崩溃。
| char *GetMemory(void) { char p[] = "hello world"; return p; } void Test(void) { char *str = NULL; str = GetMemory(); printf(str); }
请问运行Test函数会有什么样的结果? 答:可能是乱码。 因为GetMemory返回的是指向“栈内存”的指针,该指针的地址不是 NULL,但其原现的内容已经被清除,新内容不可知。 |
void GetMemory2(char **p, int num) { *p = (char *)malloc(num); } void Test(void) { char *str = NULL; GetMemory(&str, 100); strcpy(str, "hello"); printf(str); } 请问运行Test函数会有什么样的结果? 答: ( 1 )能够输出 hello ( 2 )内存泄漏
| void Test(void) { char *str = (char *) malloc(100); strcpy(str, “ hello ” ); free(str); if(str != NULL) { strcpy(str, “ world ” ); printf(str); } } 请问运行Test函数会有什么样的结果? 答:篡改动态内存区的内容,后果 难以预料,非常危险。 因为free(str);之后,str成为野指针, if(str != NULL)语句不起作用。
|
五、编写 strcpy 函数( 10 分)
已知 strcpy 函数的原型是
char *strcpy(char *strDest, const char *strSrc);
其中 strDest 是目的字符串, strSrc 是源字符串。
( 1 )不调用 C++/C 的字符串库函数,请编写函数 strcpy
char *strcpy(char *strDest, const char *strSrc);
{
assert(( str Dest!=NULL) && ( strSrc !=NULL)); // 2分
char *address = str Dest; // 2分
while( (* str Dest++ = * strSrc ++) != ‘ /0 ’ ) // 2分
NULL ;
return address ; // 2分
}
( 2 ) strcpy 能把 strSrc 的内容复制到 strDest ,为什么还要 char * 类型的返回值?
答:为了实现链式表达式。 // 2分
例如 int length = strlen( strcpy( strDest, “hello world”) );
六、编写类 String 的构造函数、析 构函数和赋值函数( 25 分)
已知类 String 的原型为:
class String
{
public:
String(const char *str = NULL); // 普通构造函数
String(const String &other); // 拷贝构造函数
~ String(void); // 析构函数
String & operate =(const String &other); // 赋值函数
private:
char *m_data; // 用于保存字符串
};
请编写 String的上述4个函数。
标准答案:
// String的析构函数
String:: ~String(void) // 3分
{
delete [] m_data;
// 由于m_data是内部数据类型,也可以写成 delete m_data;
}
// String的普通构造函数
String:: String(const char *str) // 6分
{
if(str==NULL)
{
m_data = new char[1]; // 若能加 NULL 判断则更好
*m_data = ‘ /0 ’ ;
}
else
{
int length = strlen(str);
m_data = new char[length+1]; // 若能加 NULL 判断则更好
strcpy(m_data, str);
}
}
// 拷贝构造函数
String::String(const String &other) // 3分
{
int length = strlen(other.m_data);
m_data = new char[length+1]; // 若能加 NULL 判断则更好
strcpy(m_data, other.m_data);
}
// 赋值函数
String & String::operate =(const String &other) // 13分
{
// (1) 检查自赋值 // 4分
if(this == &other)
return *this;
// (2) 释放原有的内存资源 // 3分
delete [] m_data;
// (3)分配新的内存资源,并复制内容 // 3分
int length = strlen(other.m_data);
m_data = new char[length+1]; // 若能加 NULL 判断则更好
strcpy(m_data, other.m_data);
// (4)返回本对象的引用 // 3分
return *this;
}