任何时候都适用的20个C++技巧 <5-8> 内存管理篇
2010-12-03 14:10 凌云健笔 阅读(2172) 评论(0) 编辑 收藏 举报毫无疑问,内存管理是在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的成员函数的指针,类A的成员函数返回int指针,无参数。事实上,一个成员函数指针看起来和普通的函数指针相似,除了它包含函数名加上::操作符。您可以使用.*操作符来调用pmf指向的成员函数:
pmf=&A::func;
A a;
(a.*pmf)(); /* invoke a.func() */
如果有的是一个对象的指针,你必须使用->*操作符:
A *pa=&a;
(pa->*pmf)(); /*calls pa->func() */
成员函数指针遵循多态性。因此,如果你通过这样一个指针调用虚成员函数,则会实现动态调用。但是需要注意的是,你不能将成员函数指针指向一个类的构造函数或者析构函数的地址。
技巧7:内存碎片,No!!No!!No!!
通常而言,应用程序是不受内存泄露影响的;但如果应用程序运行很长一段时间,频繁的分配和释放内存则会导致其性能逐渐下降。最终,程序崩溃。这是为什么呢?因为经常性的动态内存分配和释放会造成堆碎片,尤其是应用程序分配的是很小的内存块。支离破碎的堆空间可能有许多空闲块,但这些块既小又不连续的。为了证明这一点,请参看一下下面的堆空间表示。0表示空闲内存块,1表示使用中的内存块:
100101010000101010110
上述堆是高度分散。如果分配一个包含五个单位(即五个0)内存块,这将是不可能的,尽管系统总共中还有12个空闲空间单位。这是因为可用内存是不连续的。另一方面,下面的堆可用内存空间虽然少,但它却不是支离破碎的:
1111111111000000
你能做些什么来避免这样的堆碎片呢?首先,尽可能少的使用动态内存。在大多数情况下,可以使用静态或自动储存,或者使用STL容器。其次,尽量分配和重新分配大块的内存块而不是小的。例如,不要为一个单一的对象分配内存,而是一次分配一个对象数组。当然,你也可以将使用自定义的内存池作为最后的手段。
技巧8:对于Delete 和 Delete [],你要区分清楚
在程序员当中流传有一个众所周知的传说:对于内置类型,使用delete 代替delete []来释放内存是完全可以的。例如,
int *p=new int[10];
delete p; /*bad; should be: delete[] p*/
这是完全错误的方式。在C++标准明确指出,使用delete来释放任何类型的动态分配的数组,将会导致未定义行为。事实上,在某些平台上应用程序使用delete而非delete[]但是不死机可以归结为纯粹的运气:例如,针对内置数据类型,Visual C++通过调用free()同时实现了delete[]和delete。但是,我们并不能保证的Visual C++未来版本中将仍然坚持这么做。此外,也不会保证这个代码将适用于其他的编译器。总括来说,使用delete来代替delete [],或者用delete []来代替delete都很危险,应当尽量去避免。
// 续 任何时候都适用的20个C++技巧 <9-11> 性能的提高
作者: 凌云健笔
出处:http://www.cnblogs.com/lijian2010/
版权:本文版权归作者和博客园共有
转载:欢迎转载,为了保存作者的创作热情,请按要求【转载】
要求:未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任