版权和版本号的声明
/* * Copyright (c) 2001,上海贝尔有限公司网络应用事业部 * All rights reserved. * * 文件名: filename.h * 文件标识: 见配置管理计划书 * 摘 要: 简要描写叙述本文件的内容 * * 当前版本号: 1.1 * 作 者: 输入作者(或改动者)名字 * 完毕日期: 2001年7月20日 * * 代替版本号: 1.0 * 原作者 : 输入原作者(或改动者)名字 * 完毕日期: 2001年5月10日 */ |
头文件的结构
// 版权和版本号声明见演示样例1-1,此处省略。 #ifndef GRAPHICS_H // 防止graphics.h被反复引用 #define GRAPHICS_H #include <math.h> // 引用标准库的头文件 … #include “myheader.h” // 引用非标准库的头文件 … void Function1(…); // 全局函数声明 … class Box // 类结构声明 { … }; #endif |
定义文件的结构
// 版权和版本号声明见演示样例1-1,此处省略。 #include “graphics.h” // 引用头文件 … // 全局函数的实现体 void Function1(…) { … } // 类成员函数的实现体 void Box::Draw(…) { … } |
头文件的作用
文件夹结构
章 程序的版式
空行
// 空行 void Function1(…) { … } // 空行 void Function2(…) { … } // 空行 void Function3(…) { … } | // 空行 while (condition) { statement1; // 空行 if (condition) { statement2; } else { statement3; } // 空行 statement4; } |
代码行
int width; // 宽度 int height; // 高度 int depth; // 深度 | int width, height, depth; // 宽度高度深度 |
x = a + b; y = c + d; z = e + f; | X = a + b; y = c + d; z = e + f; |
if (width < height) { dosomething(); } | if (width < height) dosomething(); |
for (initialization; condition; update) { dosomething(); } // 空行 other(); | for (initialization; condition; update) dosomething(); other(); |
代码行内的空格
void Func1(int x, int y, int z); // 良好的风格 void Func1 (int x,int y,int z); // 不良的风格 |
if (year >= 2000) // 良好的风格 if(year>=2000) // 不良的风格 if ((a>=b) && (c<=d)) // 良好的风格 if(a>=b&&c<=d) // 不良的风格 |
for (i=0; i<10; i++) // 良好的风格 for(i=0;i<10;i++) // 不良的风格 for (i = 0; I < 10; i ++) // 过多的空格 |
x = a < b ? a : b; // 良好的风格 x=a<b?a:b; // 不好的风格 |
int *x = &y; // 良好的风格 int * x = & y; // 不良的风格 |
array[5] = 0; // 不要写成 array [ 5 ] = 0; a.Function(); // 不要写成 a . Function(); b->Function(); // 不要写成 b -> Function(); |
对齐
void Function(int x) { … // program code } | void Function(int x){ … // program code } |
if (condition) { … // program code } else { … // program code } | if (condition){ … // program code } else { … // program code } |
for (initialization; condition; update) { … // program code } | for (initialization; condition; update){ … // program code } |
While (condition) { … // program code } | while (condition){ … // program code } |
假设出现嵌套的{},则使用缩进对齐,如: { … { … } … } | |
长行拆分
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(); } |
修饰符的位置
凝视
/* * 函数介绍: * 输入參数: * 输出參数: * 返回值 : */ void Function(float x, float y, float z) { … } | if (…) { … while (…) { … } // end of while … } // end of if |
类的版式
class A { private: int i, j; float x, y; … public: void Func1(void); void Func2(void); … } | class A { public: void Func1(void); void Func2(void); … private: int i, j; float x, y; … } |
章 命名规则
共性规则
几十年前老ANSI C规定名字不准超过6个字符,现今的C+ +/C不再有此限制。一般来说,长名字能更好地表达含义,所以函数名、变量名、类名长达十几个字符不足为怪。那么名字是否越长约好?不见得! 比如变量名maxval就比maxValueUntilOverflow好用。单字符的名字也是实用的,常见的如i,j,k,m,n,x,y,z等,它们通常可用作函数内的局部变量。
简单的Windows应用程序命名规则
简单的Unix应用程序命名规则
章 表达式和基本语句
运算符的优先级
优先级 | 运算符 | 结合律 |
从 高 到 低 排 列 | ( ) [ ] -> . | 从左至右 |
! ~ ++ -- (类型) sizeof + - * & | 从右至左 | |
* / % | 从左至右 | |
+ - | 从左至右 | |
<< >> | 从左至右 | |
< <= > >= | 从左至右 | |
== != | 从左至右 | |
& | 从左至右 | |
^ | 从左至右 | |
| | 从左至右 | |
&& | 从左至右 | |
|| | 从右至左 | |
?: | 从右至左 | |
= += -= *= /= %= &= ^= |= <<= >>= | 从左至右 |
复合表达式
if 语句
有时候我们可能会看到 if (NULL == p) 这样古怪的格式。不是程序写错了,是程序猿为了防止将 if (p == NULL) 误写成 if (p = NULL),而有意把p和NULL颠倒。编译器觉得 if (p = NULL) 是合法的,可是会指出 if (NULL = p)是错误的,由于NULL不能被赋值。
循环语句的效率
for (row=0; row<100; row++) { for ( col=0; col<5; col++ ) { sum = sum + a[row][col]; } } | for (col=0; col<5; col++ ) { for (row=0; row<100; row++) { sum = sum + a[row][col]; } } |
l 【建议4-4-2】假设循环体内存在逻辑推断,而且循环次数很大,宜将逻辑推断移到循环体的外面。演示样例4- 4(c)的程序比演示样例4-4(d)多运行了N-1次逻辑推断。而且由于前者老要进行逻辑推断,打断了循环“流水线”作业,使得编译器不能对循环进行优化处理,减少了效率。假设N很大,最好採用演示样例4-4(d)的写法,能够提高效率。假设N很小,两者效率区别并不明显,採用演示样例4-4(c)的写法比較好,由于程序更加简洁。
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(); } |
语句的循环控制变量
for (int x=0; x<N; x++) { … } | for (int x=0; x<=N-1; x++) { … } |
语句
语句
章 常量
为什么须要常量
与 #define的比較
常量定义规则
类中的常量
有时我们希望某些常量仅仅在类中有效。由于#define 定义的宏常量是全局的,不能达到目的,于是想当然地认为应该用const修饰数据成员来实现。const数据成员的确是存在的,但其含义却不是我们所期望的。const数据成员仅仅在某个对象生存期内是常量,而对于整个类而言却是可变的,由于类能够创建多个对象,不同的对象其const数据成员的值能够不同。
章 函数设计
參数的规则
返回值的规则
函数内部实现的规则
其他建议
使用断言
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; } |
非常少有比跟踪到程序的断言,却不知道该断言的作用更让人沮丧的事了。你化了非常多时间,不是为了排除错误,而仅仅是为了弄清楚这个错误究竟是什么。有的时候,程序猿偶尔还会设计出有错误的断言。所以假设搞不清楚断言检查的是什么,就非常难推断错误是出如今程序中,还是出如今断言中。幸运的是这个问题非常好解决,仅仅要加上清楚的凝视就可以。这本是显而易见的事情,但是非常少有程序猿这样做。这好比一个人在森林里,看到树上钉着一块“危急”的大牌子。但危急究竟是什么?树要倒?有废井?有野兽?除非告诉人们“危急”是什么,否则这个警告牌难以起到积极有效的作用。难以理解的断言经常被程序猿忽略,甚至被删除。[Maguire, p8-p30]
引用与指针的比較
章 内存管理
内存分配方式
常见的内存错误及其对策
指针与数组的对照
char a[] = “hello”; a[0] = ‘X’; cout << a << endl; char *p = “world”; // 注意p指向常量字符串 p[0] = ‘X’; // 编译器不能发现该错误 cout << p << endl; |
不能对数组名进行直接复制与比較。演示样例7-3-2中,若想把数组a的内容复制给数组b,不能用语句 b = a ,否则将产生编译错误。应该用标准库函数strcpy进行复制。同理,比較b和a的内容是否同样,不能用if(b==a) 来推断,应该用标准库函数strcmp进行比較。
语句p = a 并不能把a的内容复制指针p,而是把a的地址赋给了p。要想复制a的内容,能够先用库函数malloc为p申请一块容量为strlen(a)+1个字符的内存,再用strcpy进行字符串复制。同理,语句if(p==a) 比較的不是内容而是地址,应该用库函数strcmp来比較。
// 数组… char a[] = "hello"; char b[10]; strcpy(b, a); // 不能用 b = a; if(strcmp(b, a) == 0) // 不能用 if (b == a) … |
// 指针… int len = strlen(a); char *p = (char *)malloc(sizeof(char)*(len+1)); strcpy(p,a); // 不要用 p = a; if(strcmp(p, a) == 0) // 不要用 if (p == a) … |
char a[] = "hello world"; char *p = a; cout<< sizeof(a) << endl; // 12字节 cout<< sizeof(p) << endl; // 4字节 |
void Func(char a[100]) { cout<< sizeof(a) << endl; // 4字节而不是100字节 } |
指针參数是怎样传递内存的?
void GetMemory(char *p, int num) { p = (char *)malloc(sizeof(char) * num); } |
void Test(void) { char *str = NULL; GetMemory(str, 100); // str 仍然为 NULL strcpy(str, "hello"); // 执行错误 } |
毛病出在函数GetMemory 中。编译器总是要为函数的每一个參数制作暂时副本,指针參数p的副本是 _p,编译器使 _p = p。假设函数体内的程序改动了_p的内容,就导致參数p的内容作对应的改动。这就是指针能够用作输出參数的原因。在本例中,_p申请了新的内存,仅仅是把 _p所指的内存地址改变了,可是p丝毫未变。所以函数GetMemory并不能输出不论什么东西。其实,每运行一次GetMemory就会泄露一块内存,由于没实用free释放内存。
void GetMemory2(char **p, int num) { *p = (char *)malloc(sizeof(char) * num); } |
void Test2(void) { char *str = NULL; GetMemory2(&str, 100); // 注意參数是 &str,而不是str strcpy(str, "hello"); cout<< str << endl; free(str); } |
char *GetMemory3(int num) { char *p = (char *)malloc(sizeof(char) * num); return p; } |
void Test3(void) { char *str = NULL; str = GetMemory3(100); strcpy(str, "hello"); cout<< str << endl; free(str); } |
char *GetString(void) { char p[] = "hello world"; return p; // 编译器将提出警告 } |
void Test4(void) { char *str = NULL; str = GetString(); // str 的内容是垃圾 cout<< str << endl; } |
char *GetString2(void) { char *p = "hello world"; return p; } |
void Test5(void) { char *str = NULL; str = GetString2(); cout<< str << endl; } |
函数Test5 执行尽管不会出错,可是函数GetString2的设计概念却是错误的。由于GetString2内的“hello world”是常量字符串,位于静态存储区,它在程序生命期内恒定不变。不管什么时候调用GetString2,它返回的始终是同一个“仅仅读”的内存块。
和delete把指针怎么啦?
char *p = (char *) malloc(100); strcpy(p, “hello”); free(p); // p 所指的内存被释放,可是p所指的地址仍然不变 … if(p != NULL) // 没有起到防错作用 { strcpy(p, “world”); // 出错 } |
动态内存会被自己主动释放吗?
void Func(void) { char *p = (char *) malloc(100); // 动态内存会自己主动释放吗? } |
杜绝“野指针”
有了malloc/free为什么还要new/delete ?
class Obj { public : Obj(void){ cout << “Initialization” << endl; } ~Obj(void){ cout << “Destroy” << endl; } void Initialize(void){ cout << “Initialization” << endl; } void Destroy(void){ cout << “Destroy” << endl; } }; |
void UseMallocFree(void) { Obj *a = (obj *)malloc(sizeof(obj)); // 申请动态内存 a->Initialize(); // 初始化 //… a->Destroy(); // 清除工作 free(a); // 释放内存 } |
void UseNewDelete(void) { Obj *a = new Obj; // 申请动态内存而且初始化 //… delete a; // 清除而且释放内存 } |
内存耗尽怎么办?
void main(void) { float *p = NULL; while(TRUE) { p = new float[1000000]; cout << “eat memory” << endl; if(p==NULL) exit(1); } } |
的使用要点
为什么free函数不象malloc函数那样复杂呢?这是由于指针p的类型以及它所指的内存的容量事先都是知道的,语句free(p)能正确地释放内存。假设p是NULL指针,那么free对p不管操作多少次都不会出问题。假设p不是NULL指针,那么free对p连续操作两次就会导致程序执行错误。
的使用要点
一些心得体会
章 C++函数的高级特性
函数重载的概念
在C ++程序中,能够将语义、功能类似的几个函数用同一个名字表示,即函数重载。这样便于记忆,提高了函数的易用性,这是C++语言採用重载机制的一个理由。比如演示样例8-1-1中的函数EatBeef,EatFish,EatChicken能够用同一个函数名Eat表示,用不同类型的參数加以差别。
void EatBeef(…); // 能够改为 void Eat(Beef …); void EatFish(…); // 能够改为 void Eat(Fish …); void EatChicken(…); // 能够改为 void Eat(Chicken …); |
所以仅仅能靠參数而不能靠返回值类型的不同来区分重载函数。编译器依据參数为每一个重载函数产生不同的内部标识符。比如编译器为演示样例8-1-1中的三个Eat函数产生象_eat_beef、_eat_fish、_eat_chicken之类的内部标识符(不同的编译器可能产生不同风格的内部标识符)。
演示样例8-1-3中,第一个output函数的參数是int类型,第二个output函数的參数是float类型。由于数字本身没有类型,将数字当作參数时将自己主动进行类型转换(称为隐式类型转换)。语句output(0.5)将产生编译错误,由于编译器不知道该将0.5转换成int还是float类型的參数。隐式类型转换在非常多地方能够简化程序的书写,可是也可能留下隐患。
# include <iostream.h> void output( int x); // 函数声明 void output( float x); // 函数声明 void output( int x) { cout << " output int " << x << endl ; } void output( float x) { cout << " output float " << x << endl ; } void main(void) { int x = 1; float y = 1.0; output(x); // output int 1 output(y); // output float 1 output(1); // output int 1 // output(0.5); // error! ambiguous call, 由于自己主动类型转换 output(int(0.5)); // output int 0 output(float(0.5)); // output float 0.5 } |
成员函数的重载、覆盖与隐藏
#include <iostream.h> class Base { public: void f(int x){ cout << "Base::f(int) " << x << endl; } void f(float x){ cout << "Base::f(float) " << x << endl; } virtual void g(void){ cout << "Base::g(void)" << endl;} }; |
class Derived : public Base { public: virtual void g(void){ cout << "Derived::g(void)" << endl;} }; |
void main(void) { Derived d; Base *pb = &d; pb->f(42); // Base::f(int) 42 pb->f(3.14f); // Base::f(float) 3.14 pb->g(); // Derived::g(void) } |
#include <iostream.h> class Base { public: virtual void f(float x){ cout << "Base::f(float) " << x << endl; } void g(float x){ cout << "Base::g(float) " << x << endl; } void h(float x){ cout << "Base::h(float) " << x << endl; } }; |
class Derived : public Base { public: virtual void f(float x){ cout << "Derived::f(float) " << x << endl; } void g(int x){ cout << "Derived::g(int) " << x << endl; } void h(float x){ cout << "Derived::h(float) " << x << endl; } }; |
void main(void) { Derived d; Base *pb = &d; Derived *pd = &d; // Good : behavior depends solely on type of the object pb->f(3.14f); // Derived::f(float) 3.14 pd->f(3.14f); // Derived::f(float) 3.14 // Bad : behavior depends on type of the pointer pb->g(3.14f); // Base::g(float) 3.14 pd->g(3.14f); // Derived::g(int) 3 (surprise!) // Bad : behavior depends on type of the pointer pb->h(3.14f); // Base::h(float) 3.14 (surprise!) pd->h(3.14f); // Derived::h(float) 3.14 } |
class Base { public: void f(int x); }; |
class Derived : public Base { public: void f(char *str); }; |
void Test(void) { Derived *pd = new Derived; pd->f(10); // error } |
參数的缺省值
#include <iostream.h> void output( int x); void output( int x, float y=0.0); |
void output( int x) { cout << " output int " << x << endl ; } |
void output( int x, float y) { cout << " output int " << x << " and float " << y << endl ; } |
void main(void) { int x=1; float y=0.5; // output(x); // error! ambiguous call output(x,y); // output int 1 and float 0.5 } |