以下的这些要点是对全部的C++程序猿都适用的。我之所以说它们是最重要的,是由于这些要点中提到的是你通常在C++书中或站点上无法找到的。如:指向成员的指针,这是很多资料中都不愿提到的地方,也是常常出错的地方,甚至是对一些高级的C++程序猿也是如此。 这里的要点不不过解释如何写出更好的代码,很多其它的是展现出语言规则里面的东西。非常显然,它们对C++程序猿来说是永久的好资料。我相信这一篇文章会使你收获不小。 首先,我把一些由不同层次的C++程序猿常常问的问题归到一起。我惊奇的发现有非常多是有经验的程序猿都还没意识到 .h 符号是否还应该出如今标准头文件里。 要点1: <iostream.h> 还是 <iostream>? 非常多C++程序猿还在使用<iostream.h>而不是用更新的标准的<iostream>库。这两者都有什么不同呢?首先,5年前我们就開始反对把.h符号继续用在标准的头文件里。继续使用过时的规则可不是个好的方法。从功能性的角度来讲,<iostream>包括了一系列模板化的I/O类,相反地<iostream.h>只不过支持字符流。另外,输入输出流的C++标准规范接口在一些微妙的细节上都已改进,因此,<iostream>和<iostream.h>在接口和运行上都是不同的。最后,<iostream>的各组成都是以STL的形式声明的,然而<iostream.h>的各组成都是声明成全局型的。 由于这些实质上的不同,你不能在一个程序中混淆使用这两个库。做为一种习惯,在新的代码中一般使用<iostream>,但假设你处理的是过去编写的代码,为了继承能够用继续用<iostream.h>旧保持代码的一致性。 要点2:用引用传递參数时应注意的地方 在用引用传递參数时,最好把引用声明为const类型。这样做的优点是:告诉程序不能改动这个參数。在以下的这个样例中函数f()就是传递的引用: |
void f(const
int & i); int main() { f(2); /* OK */ } |
这个程序传递一个參数2给f()。在执行时,C++创建一个值为2的int类型的暂时变量,并传递它的引用给f().这个暂时变量和它的引用从f()被调用開始被创建并存在直到函数返回。返回时,就被立即删除。注意,假设我们不在引用前加上const限定词,则函数f()可能会更改它參数的值,更可能会使程序产生意想不到的行为。所以,别忘了const。 这个要点也适用于用户定义的对象。你能够给暂时对象也加上引用假设是const类型: |
struct A{}; void f(const A& a); int main() { f(A()); // OK,传递的是一个暂时A的const引用 } |
要点3:“逗号分离”表达形式 “逗号分离”表达形式是从C继承来的,使用在for-和while-循环中。当然,这条语法规则被觉得是不直观的。首先,我们来看看什么是“逗号分离”表达形式。 一个表达式由一个或多个其他表达式构成,由逗号分开,如: |
if(++x, --y, cin.good()) //三个表达式 |
这个if条件包括了三个由逗号分离的表达式。C++会计算每一个表达式,但完整的“逗号分离”表达式的结果是最右边表达式的值。因此,仅当cin.good()返回true时,if条件的值才是true。以下是还有一个样例: |
int j=10;
int i=0; while( ++i, --j) { //直到j=0时,循环结束,在循环时,i不断自加 } |
要点4,使用全局对象的构造函数在程序启动前调用函数 有一些应用程序须要在主程序启动前调用其他函数。如:转态过程函数、登记功能函数都是必须在实际程序执行前被调用的。最简单的办法是通过一个全局对象的构造函数来调用这些函数。由于全局对象都是在主程序開始前被构造,这些函数都将会在main()之前返回结果。如: |
class Logger int main() |
全局对象log在main()执行之前被构造,log调用了函数activate_log()。从而,当main()開始执行时,它就能够从log文件里读取数据。
|
void (*p[10]) (void (*)()); |
P是一个“由10个指针构成的指向一个返回void类型且指向还有一个无返回和无运算的函数的数组”。这个麻烦的语法真是让人难以辨认,不是吗?你事实上能够简单的通过typedef来声明相当于上面语句的函数。首先,使用typedef声明“指向一个无返回和无运算的函数的指针”: |
typedef void (*pfv)(); |
接着,声明“还有一个指向无返回且使用pfv的函数指针”: |
typedef void (*pf_taking_pfv) (pfv); |
如今,声明一个由10个上面这种指针构成的数组: |
pf_taking_pfv p[10]; |
与void (*p[10]) (void (*)())达到相同效果。但这样是不是更具有可读性了! |
指向数据成员的指针 |
int * pi; |
定义一个指向为int型的类的数据成员: |
int A::*pmi; //pmi是指向类A的一个int型的成员 |
你能够这样初始化它: |
class A { public: int num; int x; }; int A::*pmi = & A::num; |
上面的代码是声明一个指向类A的一个int型的num成员并将它初始化为这个num成员的地址.通过在pmi前面加上*你就能够使用和更改类A的num成员的值: |
A a1, a2; int n=a1.*pmi; //把a1.num赋值给n a1.*pmi=5; // 把5赋值给a1.num a2.*pmi=6; // 把6赋值给6a2.num |
假设你定义了一个指向类A的指针,那么上面的操作你必须用 ->*操作符取代: |
A * pa=new A; int n=pa->*pmi; pa->*pmi=5; |
指向函数成员的指针 它由函数成员所返回的数据类型构成,类名后跟上::符号、指针名和函数的參数列表。举个样例:一个指向类A的函数成员(该函数返回int类型)的指针: |
class A
{ public: int func (); }; int (A::*pmf) (); |
上面的定义也就是说pmf是一个指向类A的函数成员func()的指针.实际上,这个指针和一个普通的指向函数的指针没什么不同,仅仅是它包括了类的名字和::符号。你能够在在不论什么使用*pmf的地方调用这个函数 |
func(): pmf=&A::func; A a; (a.*pmf)(); //调用a.func() |
假设你先定义了一个指向对象的指针,那么上面的操作要用->*取代: |
A *pa=&a; (pa->*pmf)(); //调用pa->func() |
指向函数成员的指针要考虑多态性。所以,当你通过指针调用一个虚函数成员时,这个调用将会被动态回收。还有一个须要注意的地方,你不能取一个类的构造函数和析构函数的地址。 要点7、避免产生内存碎片 要点8、是delete还是delete[] |
int *p=new
int[10]; delete p; //错误,应该是:delete[] p |
上面的程序是全然错误的。其实,在一个平台上使用delete取代delete[]的应用程序或许不会造成系统崩溃,但那纯粹是运气。你不能保证你的应用程序是不是会在还有一个编译器上编译,在还有一个平台上执行,所以还是请使用delete[]。 要点9、优化成员的排列一个类的大小能够被以下的方式改变: |
struct A { bool a; int b; bool c; }; //sizeof (A) == 12 |
在我的电脑上sizeof (A) 等于12。这个结果可能会让你惊讶,由于A的成员总数是6个字节:1+4+1个字节。那另6字节是哪儿来的?编译器在每一个bool成员后面都插入了3个填充字节以保证每一个成员都是按4字节排列,以便分界。你能够降低A的大小,通过下面方式: |
struct B { bool a; bool c; int b; }; // sizeof (B) == 8 |
这一次,编译器仅仅在成员c后插入了2个字节。由于b占了4个字节,所以就非常自然地把它当作一个字的形式排列,而a和c的大小1+1=2,再加上2个字节就刚好按两个字的形式排列B。 |
要点10、为什么继承一个没有虚析构函数的类是危急的? 一个没有虚析构函数的类意味着不能做为一个基类。如std::string, std::complex, 和 std::vector 都是这种。为什么继承一个没有虚析构函数的类是危急的?当你公有继承创建一个从基类继承的相关类时,指向新类对象中的指针和引用实际上都指向了起源的对象。由于析构函数不是虚函数,所以当你delete一个这种类时,C++就不会调用析构函数链。举个样例说明: |
class A { public: ~A() // 不是虚函数 { // ... } }; class B: public A
//错; A没有虚析构函数 int main() |
要点11、以友元类声明嵌套的类 当你以友元类声明一个嵌套的类时,把友元声明放在嵌套类声明的后面,而不前面。 |
class A
{ private: int i; public: class B //嵌套类声明在前 { public: B(A & a) { a.i=0;}; }; friend class B;//友元类声明 }; |
假设你把友元类声明放在声明嵌套类的前面,编译器将抛弃友元类后的其他声明。 |