/*
* 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等,它们通常可用作函数内的局部变量。
优先级
|
运算符
|
结合律
|
从
高
到
低
排
列
|
( ) [ ] -> .
|
从左至右
|
! ~ ++ -- (类型) sizeof
+ - * &
|
从右至左
|
|
* / %
|
从左至右
|
|
+ -
|
从左至右
|
|
<< >>
|
从左至右
|
|
< <= > >=
|
从左至右
|
|
== !=
|
从左至右
|
|
&
|
从左至右
|
|
^
|
从左至右
|
|
|
|
从左至右
|
|
&&
|
从左至右
|
|
||
|
从右至左
|
|
?:
|
从右至左
|
|
= += -= *= /= %= &= ^=
|= <<= >>=
|
从左至右
|
有时候我们可能会看到 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 定义的宏常量是全局的,不能达到目的,于是想当然地认为应该用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,它返回的始终是同一个“仅仅读”的内存块。
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); // 动态内存会自己主动释放吗?
}
|
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++语言採用重载机制的一个理由。比如演示样例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
}
|