常用的20个C++技巧
这些小技巧之所以特别,是因为这些信息通常吧不能在C++书籍或者网站上找到。比如说,成员指针,即使对于高级程序员也是比较棘手,和易于产生bugs的,是应该尽量避免的问题之一。
技巧1:用还是?这不是一个问题!
很多的C++程序员依旧使用,而非最新的、标准编译生成的库。这两个库之间有什么区别呢?首先,针对用.h作为标准头文件的标识符这一问题,五年前就已经不被推荐使用了。在新的代码中再使用这种不被认同的表示方式绝不是一个好主意。在功能方面,包括模板化的IO类,它同时支持窄字符和宽字符;而却只支持以char为导向的流。第三,在C++的iostream接口标准规格在许多微妙的方面发生了变化。所以,的接口与实现与存在着一定得差异。最后,,组件声明于std命名空间中,而组件是全局性的。
因为二者之间存在着这些重大分歧,你不能在同一程序中混合使用两个库。作为一条准则:使用代替,除非你处理了那些只与兼容的遗留代码。
技巧2:左值的引用,要注意!
左值和右值是C++编程的基本概念。从本质上说,右值是一种不能出现在赋值表达式左侧的表达式。相较而言,左值是一个你可以写入数值的对象或者内存块。引用可以指向左值,也可以是右值。但是,由于对右值的语言限制,所以你必须了解在向右值绑定引用时所要遵循的规则。
只要引用绑定的对象是一个const类型,那么就可以对右值绑定引用。这一规则背后的理由很简单:你不能试图去改变一个右值,常量的引用保证了程序不会通过引用去改变右值。在下面的例子中,函数f()输入一个const int的引用:
void f(const int & i);
int main()
{
f(2); /* OK */
}
程序将右值2作为一个参数传入函数f()。在实时运行中,C++会生成一个类型为int值为2的临时对象,并将其与引用i绑定。临时对象和他的引用存在与函数f()从触发到返回整个过程;函数返回后,他们被立即销毁。注意,如果我们声明引用i时没有使用const标识符,函数f()就可以修改它的参数,从而引起未定义的行为。所以,你只能向常量对象绑定引用。
同样的准则适用于自定义对象类性。只有当临时对象是常量时,你才能绑定引用。
void f(const int & i);
int main()
{
f(2); /* OK */
}
技巧3:奇怪的逗号分割表达式
逗号分隔的表达式是从C继承而来的。你很有可能会在使用for-循环和while-循环的时候经常使用这样的表达式。然而,在这方面的语言规则还远不直观。首先,让我们来看看什么是逗号分隔的表达式:这种表达式可能包含一个或多个用逗号分隔的子表达式。例如:
if(++x, --y, cin.good()) /three expressions 三个表达式/
IF条件包含由逗号分隔的三个表达式。C++确保每表达式都被执行,产生其副作用。然而,整个表达式的值仅是最右边的表达式的结果。因此,只有cin.good()返回true时,上述条件才为真。再举一个逗号表达式的例子:
int j=10;
int i=0;
while( ++i, --j)
{
/只要j不为0,在循环执行/
}
int j=10;
int i=0;
while( ++i, --j)
{
/if (j!=0) loop/
}
技巧4:如何在程序启动前调用函数?
某些应用程序需要在调用主要程序之前开始启动功能。例如,polling(轮询),billing(***),和logger(日志记录)等函数必须在调用实际的程序之前开始。最简单的实现这一目标的方式是调用一个全局对象的构造函数。因为从概念上说,全局对象是在程序开始之构造的,这个函数会在main()开始之前返回。例如:
class Logger
{
public:
Logger()
{
activate_log();
}
};
Logger log; /global instance/
int main()
{
record * prec=read_log();
//… application code
}
全局对象log在main()开始之前完成构造。在构造过程中,log触发了函数activate_log()。当main()开始后,它就可以从日志文件中读取数据。
// 续 任何时候都适用的20个C++技巧 <5-8> 内存管理
毫无疑问,内存管理是在C++编程中最复杂和最易出错的问题之一。能够直接地访问原始内存,动态地分配内存空间,以及C++的高效性决定它必须有一些非常严格的规则;如果你不遵守将难以避免那些内存相关的错误或者程序运行时的崩溃。
指针是访问内存的主要手段。 C++可以分为两个主要类别:指向数据的指针和指向函数的指针。第二大类又可以分为两个子类类:普通函数指针和成员函数指针。在下面的技巧中,我们将深入探讨这些问题,并学习一些方法,简化指针的使用,同时隐藏它们的笨拙语法。
指向函数的指针很可能是C++中一种最不具可读性的语法结构。唯一的可读性更差的似乎只有成员指针。第一个技巧会教你如何提高普通的函数指针的可读性。这是你理解C++成员指针的前提。接下来,我们将学习如何避免内存碎片,并告诉你其可怕的后果。最后,我们讨论delete和delete []的正确使用方法;它常常会是众多错误和误解的来源。
技巧5:函数指针的繁琐语法?!见鬼去吧!!
你能告诉我下面定义的含义么?
void (p[10]) (void ()());
p是“一个包含10个函数指针的数组,这些函数返回为空,其参数为{【(另外一个无参数返回为空)的函数】的指针}。”如此繁琐的语法几乎难以辨认,难道不是吗?解决之道在何方?你可以通过typedef来合理地大大地去简化这些声明。首先,声明一个无参数、返回空的函数的指针的typedef,如下所示:
typedef void (*pfv)();
接下来 声明另一个typedef,一个指向参数为pfv返回为空的函数的指针:
typedef void (*pf_taking_pfv) (pfv);
现在,再去声明一个含有10个这样指针的数组就变得轻而易举,不费吹灰之力了:
pf_taking_pfv p[10]; /等同于void (p[10]) (void ()()); 但更具可读性/
===================================================
===================================================
技巧6:函数指针的枝枝节节
类有两类成员:函数成员和数据成员。同样,也就有两种类别的成员指针:成员函数指针和数据成员指针。后者不太常见,因为,一般来说,类是没有公共数据成员的。当使用传统C代码的时候,数据成员指针才是有用的,因为传统C代码中包含的结构体或类是具有公开数据成员的。
在C++中,成员指针是最为复杂的语法结构之一;可是,这却是一个非常强大而重要的特性。它们可以使您在不知道这个函数的名字的前提下调用一个对象的成员函数。这是非常方便的回调实现。同样的,你可以使用一个数据成员指针来监测和修改数据成员的值,而不必知道它的名字。
指向数据成员的指针
虽然成员指针的语法可能会显得有点混乱,但是它与普通指针的形式比较一致和类似,只需要在星号之前加上类名::即可。例如,如果一个普通的整形指针如下所示:
int * pi;
那么,你就可以按照下面的方式来定义一个指向类A的整形成员变量的指针:
int A::pmi; / pmi is a pointer to an int member of A*/
你需要按照这样的方式初始化成员指针:
class A
{
public:
int num;
int x;
};
int A::pmi = & A::num; / 1 */
标号1的语句声明了一个指向类A的整形成员的指针,它用A类对象中的成员变量num的地址实现了初始化。使用pmi和内置操作符.*,你可以监测和修改任何一个A类型的对象中的num的值。
A a1, a2;
int n=a1.pmi; / copy a1.num to n */
a1.pmi=5; / assign the value 5 to a1.num */
a2.pmi=6; / assign the value 6 to a2.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