练习一
以下函数完全没有检查可能的数据错误以及可能的执行失败。请指出此函数中所有可能发生错误的地方。本题并不考虑出现异常。
int *alloc_and_init(string file_name)
{
ifstream infile(file_name);
int elem_cnt;
infile >> elem_cnt;
int *pi = allocate_array(elem_cnt);
int elem;
int index = 0;
while(infile >> elem)
{
pi[index++] = elem;
}
sort_array(pi, elem_cnt);
register_data(pi);
return pi;
}
这是书中第203页的练习7.1。
我自己的答案:打开文件后,未对infile进行判断,是否打开文件成功;pi是否分配成功,未进行判断,它是否为null。
侯捷老师给的答案如下:
第一个错误便是“型别不符”。ifstream constructor 接受的参数型别是const char*而非string。这个没有注意到。解决方法是利用string的c_str member function取得其c-style字符串表现形式:
ifstream infile(file_name.c_str());
第二个错误是检查infile是否成功开启。
if ( !infile ) // 开启失败
第三个错误就是infile >> elem_cnt 可能执行失败。
如,文件内含的是文字,那么企图“读入某个数值并置于elem_cnt内”的操作便告失败。此外,文件也有可能是空的。必须检查读取是否成功。
infile >> elem_cnt;
if (! infile) // 读取失败
第四个错误int *pi = allocate_array(elem_cnt);
无论何时,当我们处理指针时,必须随时注意指针是否的确指向实际存在的对象。如果allocate_array()无法配置足够内存,pi便会被设为0,我们必须检验如下:
if ( ! pi ) // allocate_array() 没有配置到内存
需要说明的是:程序的假设是(1)elem_cnt代表文件中的元素个数;(2)数组索引值index绝不会发生溢出。但是除非我们检查,否则实在无法保证index永远不大于elem_cnt。
第一个错误和第三个错误,没有考虑到。分析看,对于“型别不符”这个问题,一直没有注意到。此外,对于读入文字,没有思考那么多。
努力学习ing……
posted @
2008-11-03 13:35 Sandy 阅读(347) |
评论 (0) |
编辑 收藏
什么是内存泄露
内存泄露是一种实现错误。它将会慢慢耗尽系统内存空间。当计算机运行进程的时候,可能需要或多或少的内存。这主要以来于进程每时每刻将要执行的命令。当进程需要更多的内存时,将给操作系统提出请求。当进程不再需要内存的时候,将会把内存释放掉,还给操作系统。这样其他进程才可以使用。如果进程没有正确的将内存还给操作系统,尽管它不再使用,但是内存的状态仍然是不可再分配。这将减少可用内存。
一般我们常说的内存泄露是指堆内存的泄露。堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定),使用完后必须释放的内存。应用程序一般使用malloc,realloc,new等函数从堆中分配到一块内存,使用完后,程序必须负责相应得调用free或delete释放该内存块,否则,这块内存就不能再次被使用,我们就说这块内存泄露了。
对于一些界面的资源,如window的创建,menu的创建,dc的创建等等,对于这些,我们需要在不使用它们的时候,调用相应的函数,进行释放。否则也将会造成内存泄露。
内存泄露的后果
内存泄露会因为减少可用内存的数量从而降低计算机的性能。最终,在最糟糕的情况下,过多的可用内存被分配掉导致全部或部分设备停止正常工作,或者应用程序崩溃。
内存泄露可能不严重,甚至能够被常规的手段检测出来。在现代操作系统中,一个应用程序使用的常规内存在程序终止时被释放。这表示一个短暂的应用程序中的内存泄露不会导致严重后果。
在以下情况下,内存泄露导致较严重的后果:
一是程序运行后置之不理,并且随着时间的流失消耗越来越多的内存(比如服务器上的后台任务,尤其是嵌入式系统中的后台任务,这些任务可能被运行后很多年内都置之不理);
二是新的内存被频繁的分配,比如当现实电脑游戏或动画视频画面时;
三是程序能够请求未被释放的内存(比如共享内存),甚至是程序终止的时候;
四是泄露在操作系统内部发生;
五是泄露在系统关键驱动中发生;
六是内存非常有限,比如在嵌入式系统或便携设备中
七是当运行于一个终止时内存并不自动释放的操作系统治上,而且一旦丢失,只能通过重启来恢复。
在这里,我着重强调一下嵌入式系统,由于系统内存非常有限,编写应用程序的时候,一定要防止内存泄露的发生。如果发生,可能是会使你编写的应用程序出现异常。或者你的系统的性能将会大大降低。甚至有时你不得不依靠重起系统来恢复。
内存泄露的检测
检测内存泄露的工具:debugnew
http://dev.csdn.net/article/58/58407.shtm
浅谈内存泄漏(二)
http://www.vczx.com/article/show.php?id=68
一个跨平台的 C++ 内存泄漏检测器
http://www-128.ibm.com/developerworks/cn/linux/l-mleak2/index.html
内存泄露检测
http://www.cppblog.com/Ipedo/archive/2005/10/27/867.aspx
posted @
2008-11-02 10:50 Sandy 阅读(373) |
评论 (0) |
编辑 收藏
提问的智慧 - How To Ask Questions The Smart Way
by Freesc Huang
http://fox23.cnblogs.com/
在各种技术社区,论坛和邮件组,用户和开发者们总是期望着自己能够获得帮助。在本文之前,曾经有过一些经典的文章,比如由Eric S. Raymond 和Rick Moen 撰写的How to Ask Questions the Smart Way --- 我得承认我盗用了这个文章的标题:)
不过在我所关注的一些技术社区内,还是有一些朋友经常华丽地忽视掉一些细节,尽管某些问题实际上对提问者本身没有什么关系,但是他们确实影响了看到这个问题的其他人的思路和感受。当然,我必须得承认,当写下本文之前,曾经我自己的某些提问可能也会有类似的问题:
注意社区板块分类
在您发布一个主题的时候,请先确保您所发布的主题的确是和该板块的定位相符的。一般的论坛板块应该都不是按板块1,板块2,板块N来命名的,请您尽量先找到合适的板块再提问。对于概要的问题不必放在细节的板块中去讨论,比如有关.Net Framework运行机制的讨论就不益放在Visual C#的板块中,而应该去.Net Framework的板块讨论。
标题格式
请您在标题中尽量提供一些问题相关描述,比如你有疑问的控件名称,出现的错误信息,使用的开发语言,开发环境,目标平台和版本等。这样既能方便回答问题的热心人及时有效地回复,又能方便以后的用户来查找类似问题。尽量不要使用“跪求达人!!”,“我要疯了!!”这类无意义的词汇。
有关FAQ
基本所有技术社区,特别是微软的社区,都会有提供FAQ之类的帖子或者公告,来帮助那些新手。先阅读这些FAQ可以更快地认识现状和了解常见的问题,也许你的问题就在里面。FAQ是新人必看的资料。
使用搜索
请在提问前充分使用你的搜索引擎,设想:如果你在某社区贴了一个问题,而当你回头在网上搜索的时候,立即发现一模一样的问题居然有数万条结果,你会觉得刚刚那个问题有些愚蠢。而更尴尬的是,你发现搜索结果的第一条居然就是前不久另一个同行发在同样的社区的,而且已经被解答了 = =!
心平气和地提问
不需要使用“十万火急!”,“在线急等!”这样的字样,每个人都很急切的希望知道问题的结果,但是不是每个人的问题都能及时被答复,这很正常。也不要在你的帖子中破口大骂,即便您出现的问题的确很可恶。更不要随意攻击某产品和它们的开发人员,因为这对解决你的问题没有任何帮助,没有人愿意和一个咆哮的吵架者探讨问题。
明确问题
一个明确的问题才有可能得到合适的回答,在您提问之前,您或许应该先通过自己的尝试尽量把您的问题明确化,具体化。否则很可能你在浪费彼此的时间。
贴代码是一种美德
谁都知道贴相关代码是表达问题最直观的方式,但是这个也是最容易被忽略的方式。很多开发者总是习惯一吐为快的感觉,说了很多话但是都没有说到点子上,对企图帮助你的人来说,你贴代码显然要比码字更容易让他们复现问题。
别忘了开发和运行环境
你的程序是用什么开发的,VC6? VS2003/2005/2008?基于.Net Framework 2.0, 3.5?什么运行环境?Windows XP?Windows Vista?Windows Mobile 6 Professional?
注意对问题现象的描述
这对你的提问很关键,我经常看到有这样的描述:“我试过了XXX函数,但是不行!”,请问不行是什么意思?有异常么?具体的错误信息是什么?还是根本没有任何反应?
不要多版面
把同样的问题放在多个版面没有任何意义,反而不方便提问者去查询有用信息。放到一个“合适”的版面即可。
不要求人代工
不论有偿还是无偿,在版面上求人代工都是不妥的,技术社区是专门用作交流技术而不是专门用作外包的,而且你也无权利转嫁老板给你的任何任务给其他人。
及时关闭已解决主题
如果你的问题已经得到满意的答案,请及时结贴,不要在同样的主题贴下面引出多个问题,不断提问。这样既不利于你后面的问题被看到,也不利于其他用户搜索问题。新的问题请开新的主题。
不要进行邮件骚扰
如非特别说明,不要通过邮件回复给社区用户,这不是他们来社区所希望的,在社区回帖或者在博客留言都是很好的方式,别人在有空的时候自然会看到你的问题。这也是对他人的尊重,通常大家只是希望在自己有空的时候去社区看看问题,谁也不希望在上班时邮箱被一堆来自持有各种心态的提问者的问题给暴掉。
表示感谢
别忘了对给予你帮助的人表示感谢,尽管这个对你的问题是否得到解答没有任何影响,但这是礼貌,是对别人劳动的尊重。如果您是属于提问之后不管的类型,有可能会让人产生对您RPWT的疑问,也许那些对你有过帮助的人就不再愿意回答你的问题了。
更多…
如果您和我一样对提问的技巧感兴趣,推荐您阅读以下文章:
How To Ask Questions The Smart Way
http://en.wikipedia.org/wiki/Wikipedia:Help_desk/How_to_ask
摘自:http://www.cnblogs.com/fox23/archive/2008/08/27/how-to-ask-questions-the-smart-way.html
posted @
2008-10-28 15:10 Sandy 阅读(150) |
评论 (0) |
编辑 收藏
模式对话框和非模式对话框的区别
一、 创建的区别
在WIN32中,模式对话框的创建一般是使用DialogBox来进行创建的。而非模式对话框则是利用CreateWindow来创建的。在MFC或是WTL中,模式对话框一般是使用DoModal,而非模式对话框的创建则是使用Create。
模式对话框创建后,程序的其他窗口便不能进行操作,必须将该窗口关闭后,其他窗口才能进行操作。而非模式对话框则无需这样,它不强制要求用户立即反应,而是与其他窗口同时接受用户操作。
二、 消息响应的区别
在消息响应方面,模式对话框和非模式对话框之间又有着很大的区别。模式对话框工作的时候,它有内部的消息泵机制,控件之间的交互不用我们人为的去控制,系统会帮助我们去处理。非模式对话框则像普通窗口一样,则由WinMain中书写的消息循环驱动。但由于是对话框,它对一些消息有特殊的处理。因此,在消息循环中,需要先对对话框提供截获消息的机会。
While (GetMessage(&msg, NULL, 0, 0))
{
if (hDlgModeless == 0 || !IsDialogMessage(hDlgModeless, &msg))
{
TranslateMessage(&msg);
DispatchMessage( &msg);
}
}
如果当前取得的消息是对话框的消息,IsDialogMessage 将它交由对话消息处理函数处理,并返回TRUE。不需要再派发了。
注意:这个方法并不是很好用,因为当对话框过多的时候,处理起来就比较麻烦了。另一种处理的方法是利用子类化控件的方法,来处理控件间的交互。
三、 销毁的区别
模式对话框的销毁是使用EndDialog,而非模式对话框的销毁是使用DestroyWindow.。所以我们在销毁对话框的时候,也要对其进行区别。
非模式对话框,用户关闭对话框时,对话框消息处理函数将收到WM_CLOSE消息,接到后调用DestroyWindow以销毁非模式对话框。
模式对话框,则一般响应IDOK和IDCANCEL。在PPC上,我们对于OK键和X键的处理要注意这点。
四、 其他
非模态对话框的模板必须具有Visible风格,否则对话框将不可见,而模态对话框则无需设置该项风格。更保险的办法是调用ShowWindow(hDialog, SW_SHOW)来显示对话框,而不管对话框是否具有Visible风格。
非模态对话框对象是用new操作符在堆中动态创建的,而不是以成员变量的形式嵌入到别的对象中或以局部变量的形式构建在堆栈上。通常应在对话框的拥有者窗口类内声明一个指向对话框类的指针成员变量,通过该指针可访问对话框对象。
通过调用Create函数来启动对话框,而不是DoModal,这是模态对话框的关键所在。由于Create函数不会启动新的消息循环,对话框与应用程序共用同一个消息循环,这样对话框就不会垄断用户的输入。Create在显示了对话框后就立即返回,而DoModal是在对话框被关闭后才返回的。众所周知,在MFC程序中,窗口对象的生存期应长于对应的窗口,也就是说,不能在未关闭屏幕上窗口的情况下先把对应的窗口对象删除掉。由于在Create返回后,不能确定对话框是否已关闭,这样也就无法确定对话框对象的生存期,因此只好在堆中构建对话框对象,而不能以局部变量的形式来构建之。
因为是用new操作符构建非模态对话框对象,因此必须在对话框关闭后,用delete操作符删除对话框对象。
必须有一个标志表明非模态对话框是否是打开的。这样做的原因是用户有可能在打开一个模态对话框的情况下,又一次选择打开命令。程序根据标志来决定是打开一个新的对话框,还是仅仅把原来打开的对话框激活。通常可以用拥有者窗口中的指向对话框对象的指针作为这种标志,当对话框关闭时,给该指针赋NULL值,以表明对话框对象已不存在了。
注意:在C++编程中,判断一个位于堆中的对象是否存在的常用方法是判断指向该对象的指针是否为空。这种机制要求程序员将指向该对象的指针初始化为NULL值,在创建对象时将返回的地址赋给该指针,而在删除对象时将该指针置成NULL值。
posted @
2008-10-26 21:47 Sandy 阅读(1381) |
评论 (1) |
编辑 收藏
最近看书,看到了引用,对其用法不是很了解。从各处汇总了一些知识,如下:
什么是引用
引用是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样。引用的声明方法:类型标识符 &引用名=目标变量名;
【例1】:
int a
int &ra=a; //定义引用ra, 它是变量a的引用,即别名
对引用的几点说明
(1)&在此不是求地址运算,而是起标识作用。
(2)类型标识符是指目标变量的类型。
(3)声明引用时,必须同时对其进行初始化。
(4)引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,且不能再把该引用名作为其他变量名的别名。
ra=1; 等价于 a=1;
(5)声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。故:对引用求地址,就是对目标变量求地址。&ra与&a相等
(6)不能建立数组的引用。因为数组是一个由若干个元素所组成的集合,所以无法建立一个数组的别名。
引用的用途
引用的主要用途是为了描述函数的参数和返回值,特别是为了运算符的重载。
1、 引用作为参数
引用的一个重要作用就是作为函数的参数。以前的C语言中函数参数传递是值传递,如果有大块数据作为参数传递的时候,采用的方案往往是指针,因为这样可以避免将整块数据全部压栈,可以提高程序的效率。但是现在(C++中)又增加了一种同样有效率的选择(在某些特殊情况下又是必须的选择),就是引用。
【例2】:
void swap(int &p1, int &p2) / /此处函数的形参p1, p2都是引用
{ int p; p=p1; p1=p2; p2=p; }
为在程序中调用该函数,则相应的主调函数的调用点处,直接以变量作为实参进行调用即可,而不需要实参变量有任何的特殊要求。如:对应上面定义的swap函数,相应的主调函数可写为:
【例3】:
main( )
{
int a,b;
cin>>a>>b; // 输入a,b两变量的值
swap(a,b); //直接以变量a和b作为实参调用swap函数
cout<<a<< ' ' <<b; //输出结果
}
上述程序运行时,如果输入数据10 20并回车后,则输出结果为20 10。
由【例2】可看出:
(1)传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。
(2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。
(3)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。
如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。
2、 引用作为返回值
如果一个函数返回了引用,那么该函数的调用也可以被赋值。这里有一函数,它拥有两个引用参数并返回一个双精度数的引用:
【例4】
double &max(double &d1,double &d2)
{
return d1>d2?d1:d2;
}
由于max()函数返回一个对双精度数的引用,那么我们就可以用max() 来对其中较大的双精度数加1:
max(x,y)+=1.0;
在Effecitve c++中指出,当你必须返回一个对象时不要试图返回一个引用。这又是为什么呢?我看了一下它的解释
考虑一个代表有理数的类,包含一个将两个有理数相乘的函数:
class Rational {
public:
Rational(int numerator = 0, // see Item 24 for why this
int denominator = 1); // ctor isn't declared explicit
...
private:
int n, d; // numerator and denominator
friend const Rational // see Item 3 for why the
operator*(const Rational& lhs, // return type is cons
const Rational& rhs);
};
operator* 的这个版本以传值方式返回它的结果,而且如果你没有担心那个对象的构造和析构的代价,你就是在推卸你的专业职责。如果你不是迫不得已,你不应该为这样的一个对象付出成本。所以问题就在这里:你是迫不得已吗?
哦,如果你能用返回一个引用来作为代替,你就不是迫不得已。但是,请记住一个引用仅仅是一个名字,一个实际存在的对象的名字。无论何时只要你看到一个引用的声明,你应该立刻问自己它是什么东西的另一个名字,因为它必定是某物的另一个名字。在这个 operator* 的情况下,如果函数返回一个引用,它必须返回某个已存在的而且其中包含两个对象相乘的产物的 Rational 对象的引用。
当然没有什么理由期望这样一个对象在调用 operator* 之前就存在。也就是说,如果你有
Rational a(1, 2); // a = 1/2
Rational b(3, 5); // b = 3/5
Rational c = a * b; // c should be 3/10
似乎没有理由期望那里碰巧已经存在一个值为十分之三的有理数。不是这样的,如果 operator* 返回这样一个数的引用,它必须自己创建那个数字对象。
一个函数创建一个新对象仅有两种方法:在栈上或者在堆上。栈上的生成物通过定义一个局部变量而生成。使用这个策略,你可以用这种方法试写 operator*:
const Rational& operator*(const Rational& lhs, // warning! bad code!
const Rational& rhs)
{
Rational result(lhs.n * rhs.n, lhs.d * rhs.d);
return result;
}
你可以立即否决这种方法,因为你的目标是避免调用构造函数,而 result 正像任何其它对象一样必须被构造。一个更严重的问题是这个函数返回一个引向 result 的引用,但是 result 是一个局部对象,而局部对象在函数退出时被销毁。那么,这个 operator* 的版本不会返回引向一个 Rational 的引用——它返回引向一个前 Rational;一个曾经的 Rational;一个空洞的、恶臭的、腐败的,从前是一个 Rational 但永不再是的尸体的引用,因为它已经被销毁了。任何调用者甚至于没有来得及匆匆看一眼这个函数的返回值就立刻进入了未定义行为的领地。这是事实,任何返回一个引向局部变量的引用的函数都是错误的。(对于任何返回一个指向局部变量的指针的函数同样成立。)
那么,让我们考虑一下在堆上构造一个对象并返回引向它的引用的可能性。基于堆的对象通过使用 new 而开始存在,所以你可以像这样写一个基于堆的 operator*:
const Rational& operator*(const Rational& lhs, // warning! more bad
const Rational& rhs) // code!
{
Rational *result = new Rational(lhs.n * rhs.n, lhs.d * rhs.d);
return *result;
}
哦,你还是必须要付出一个构造函数调用的成本,因为通过 new 分配的内存要通过调用一个适当的构造函数进行初始化,但是现在你有另一个问题:谁是删除你用 new 做出来的对象的合适人选?
即使调用者尽职尽责且一心向善,它们也不太可能是用这样的方案来合理地预防泄漏:
Rational w, x, y, z;
w = x * y * z;
这里,在同一个语句中有两个 operator* 的调用,因此 new 被使用了两次,这两次都需要使用 delete 来销毁。但是 operator* 的客户没有合理的办法进行那些调用,因为他们没有合理的办法取得隐藏在通过调用 operator* 返回的引用后面的指针。这是一个早已注定的资源泄漏。
似乎明白了一些:当你必须返回一个对象时不要试图返回一个引用。对于局部变量,返回其引用会造成错误。为了避免错误,我们应尽量不返回引用。
3、 常引用
const引用是指向const对象的引用:
const int ival = 1024;
const int &refVal = ival; // ok: both reference and object are const
int &ref2 = ival; // error: non const reference to a const object
常引用声明方式:const 类型标识符 &引用名=目标变量名;
用这种方式声明的引用,不能通过引用对目标变量的值进行修改,从而使引用的目标成为const,达到了引用的安全性。
【例5】:
int a ;
const int &ra = a;
ra=1; // 错误
a=1; // 正确
这不光是让代码更健壮,也有些其它方面的需要。
【例6】:假设有如下函数声明:
string foo( );
void bar(string & s);
那么下面的表达式将是非法的:
bar(foo( ))
bar("hello world");
原因在于foo( )和"hello world"串都会产生一个临时对象,而在C++中,这些临时对象都是const类型的。因此上面的表达式就是试图将一个const类型的对象转换为非const类型,这是非法的。
引用型参数应该在能被定义为const的情况下,尽量定义为const 。
Const 引用与非const引用的区别
非const引用只能绑定到与该引用同类型的对象。
const引用则可以绑定到不同但相关的类型的对象或绑定到左值。
指针和引用的差别
1. 非空的差别任何情况下都不能使用指向空值的引用.一个引用必须总是指向某个对象. 不存在的指向空值的引用这个事实意味着使用引用的代码效率比使用指针要高.
2. 合法性区别在使用引用之前不需要测试他的合法性.指针必须测试.
3. 可修改区别 指针可以被重新赋值给另一个不同的对象.但是引用总是指向在初始化的时候被制定的对象,以后不能改变.但是指定的对象其内容可以改变. 应该使用指针的情况: 可能存在不指向任何对象的可能性 需要在不同的时刻指向不同的对象(此时,你能够改变指针的指向) 应该使用引用的情况: 如果总是指向一个对象并且一旦指向一个对象后就不会改变指向,使用此时应使用引用。
要首先好好理解指针和引用的区别
指针与引用看上去完全不同(指针用操作符’*’和’->’,引用使用操作符’.’),但是它们似乎有相同的功能。指针与引用都是让你间接引用其他对象。你如何决定在什么时候使用指针,在什么时候使用引用呢?
总的来说,在以下情况下你应该使用指针,一是你考虑到存在不指向任何对象的可能(在这种情况下,你能够设置指针为空),二是你需要能够在不同的时刻指向不同的对象(在这种情况下,你能改变指针的指向)。如果总是指向一个对象并且一旦指向一个对象后就不会改变指向,那么你应该使用引用。
还有一种情况,就是当你重载某个操作符时,你应该使用引用。最普通的例子是操作符[]。这个操作符典型的用法是返回一个目标对象,其能被赋值。
vector<int> v(10); //建立整形向量(vector),大小为10
//向量是一个在标准C库中的一个模板 [Page]
v[5] = 10; // 这个被赋值的目标对象就是操作符[]返回的值
如果操作符[]返回一个指针,那么后一个语句就得这样写:
*v[5] = 10;
但是这样会使得v看上去象是一个向量指针。因此你会选择让操作符返回一个引用
当你知道你必须指向一个对象并且不想改变其指向时,或者在重载操作符并为防止不必要的语义误解时,你不应该使用指针。而在除此之外的其他情况下,则应使用指针 。
posted @
2008-10-12 21:18 Sandy 阅读(252) |
评论 (0) |
编辑 收藏
今日从网上看到一篇好文章,汇总汇总,又拼凑拼凑,便有了下文。
static关键字是C、C++中都存在的关键字, 它主要有三种使用方式, 其中前两种只指在C语言中使用, 第三种在C++中使用(C,C++中具体细微操作不尽相同, 本文以C++为准).
(1) 局部静态变量 静态局部变量有两个用法,记忆功能和全局生存期.
(2) 外部静态变量/函数 用于全局变量,主要作用是限制此全局变量被其他的文件调用
(3) 静态数据成员/成员函数 表示这个成员是属于这个类但是不属于类中任意特定对象
下面就这三种使用方式及注意事项分别说明
一、局部静态变量
在C/C++中, 局部变量按照存储形式可分为三种auto, static, register
与auto类型(普通)局部变量相比, static局部变量有三点不同
1. 存储空间分配不同
auto类型分配在栈上, 属于动态存储类别, 占动态存储区空间, 函数调用结束后自动释放, 而static分配在静态存储区, 在程序整个运行期间都不释放. 两者之间的作用域相同, 但生存期不同.
2. static局部变量在所处模块在初次运行时进行初始化工作, 且只操作一次
3. 对于局部静态变量, 如果不赋初值, 编译期会自动赋初值0或空字符, 而auto类型的初值是不确定的.(对于C++中的class对象例外, class的对象实例如果不初始化, 则会自动调用默认构造函数, 不管是否是static类型)
特点: static局部变量的”记忆性”与生存期的”全局性”
所谓“记忆性”是指在两次函数调用时, 在第二次调用进入时, 能保持第一次调用退出时的值.
示例程序一
#include <iostream>
using namespace std;
void staticLocalVar()
{
static int a = 0; // 运行期时初始化一次,下次再调用时,不进行初始化工作
cout<<"a="<<a<<endl;
++a;
}
int main()
{
staticLocalVar(); // 第一次调用, 输出a=0
staticLocalVar(); // 第二次调用, 记忆了第一次退出时的值, 输出a=1
return 0;
}
应用: 利用“记忆性”, 记录函数调用的次数(示例程序一)
利用生存期的“全局性”,改善
“return a pointer / reference to a local object”的问题. Local object的问题在于退出函数, 生存期即结束,利用static的作用, 延长变量的生存期.
示例程序二:
// IP address to string format
// Used in Ethernet Frame and IP Header analysis
const char * IpToStr(UINT32 IpAddr)
{
static char strBuff[16]; // static局部变量, 用于返回地址有效
const unsigned char *pChIP = (const unsigned char *)&IpAddr;
sprintf(strBuff, "%u.%u.%u.%u", pChIP[0], pChIP[1], pChIP[2], pChIP[3]);
return strBuff;
}
注意事项:
1. “记忆性”, 程序运行很重要的一点就是可重复性, 而static变量的“记忆性”破坏了这种可重复性, 造成不同时刻至运行的结果可能不同.
2. “生存期”全局性和唯一性. 普通的local变量的存储空间分配在stack上, 因此每次调用函数时, 分配的空间都可能不一样, 而static具有全局唯一性的特点, 每次调用时, 都指向同一块内存, 这就造成一个很重要的问题 ---- 不可重入性!!!
这样在多线程程序设计或递归程序设计中, 要特别注意这个问题.
下面针对示例程序二, 分析在多线程情况下的不安全性.(为方便描述, 标上行号)
①const char * IpToStr(UINT32 IpAddr)
② {
③ static char strBuff[16]; // static局部变量, 用于返回地址有效
④ const unsigned char *pChIP = (const unsigned char *)&IpAddr;
⑤ sprintf(strBuff, "%u.%u.%u.%u", pChIP[0], pChIP[1], pChIP[2], pChIP[3]);
⑥ return strBuff;
⑦ }
假设现在有两个线程A,B运行期间都需要调用IpToStr()函数, 将32位的IP地址转换成点分10进制的字符串形式.
现A先获得执行机会, 执行IpToStr(), 传入的参数是0x0B090A0A, 顺序执行完应该返回的指针存储区内容是: “10.10.9.11”, 现执行到⑥时, 失去执行权, 调度到B线程执行, B线程传入的参数是0xA8A8A8C0,执行至⑦, 静态存储区的内容是192.168.168.168. 当再调度到A执行时, 从⑥继续执行, 由于strBuff的全局唯一性, 内容已经被B线程冲掉, 此时返回的将是192.168.168.168字符串, 不再是10.10.9.11字符串.
补充:静态局部变量属于静态存储方式,它具有以下特点:
(1)静态局部变量在函数内定义 它的生存期为整个源程序,但是其作用域仍与自动变量相同,只能在定义该变量的函数内使用该变量。退出该函数后,尽管该变量还继续存在,但不能使用它。
(2)允许对构造类静态局部量赋初值 例如数组,若未赋以初值,则由系统自动赋以0值。
(3)对基本类型的静态局部变量若在说明时未赋以初值,则系统自动赋予0值。而对自动变量不赋初值,则其值是不定的。根据静态局部变量的特点, 可以看出它是一种生存期为整个源程序的量。虽然离开定义它的函数后不能使用,但如再次调用定义它的函数时,它又可继续使用, 而且保存了前次被调用后留下的值。因此,当多次调用一个函数且要求在调用之间保留某些变量的值时,可考虑采用静态局部变量。虽然用全局变量也可以达到上述目的,但全局变量有时会造成意外的副作用,因此仍以采用局部静态变量为宜
二、外部静态变量/函数
在C中static有了第二种含义:用来表示不能被其它文件访问的全局变量和函数。 但为了限制全局变量/函数的作用域, 函数或变量前加static使得函数成为静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件(所以又称内部函数)。注意此时, 对于外部(全局)变量, 不论是否有static限制, 它的存储区域都是在静态存储区, 生存期都是全局的. 此时的static只是起作用域限制作用, 限定作用域在本模块(文件)内部.
使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名。
示例程序三:
//file1.cpp
static int varA;
int varB;
extern void funA()
{
……
}
static void funB()
{
……
}
//file2.cpp
extern int varB; // 使用file1.cpp中定义的全局变量
extern int varA; // 错误! varA是static类型, 无法在其他文件中使用
extern vod funA(); // 使用file1.cpp中定义的函数
extern void funB(); // 错误! 无法使用file1.cpp文件中static函数
补充:全局变量(外部变量)的说明之前再冠以static 就构成了静态的全局变量。全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。 这两者在存储方式上并无不同。这两者的区别虽在于非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。从以上分析可以看出, 把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。因此static 这个说明符在不同的地方所起的作用是不同的。
三、静态数据成员/成员函数(C++特有)
C++重用了这个关键字,并赋予它与前面不同的第三种含义:表示属于一个类而不是属于此类的任何特定对象的变量和函数. 这是与普通成员函数的最大区别, 也是其应用所在, 比如在对某一个类的对象进行计数时,计数生成多少个类的实例, 就可以用到静态数据成员. 在这里面, static既不是限定作用域的, 也不是扩展生存期的作用, 指示变量/函数在此类中的唯一性. 这也是“属于一个类而不是属于此类的任何特定对象的变量和函数”的含义. 因为它是对整个类来说是唯一的, 因此不可能属于某一个实例对象的. (针对静态数据成员而言, 成员函数不管是否是static,在内存中只有一个副本.普通成员函数调用时, 需要传入this指针,static成员函数调用时, 没有this指针. )
请看示例程序四
class EnemyTarget {
public:
EnemyTarget() { ++numTargets; }
EnemyTarget(const EnemyTarget&) { ++numTargets; }
~EnemyTarget() { --numTargets; }
static size_t numberOfTargets() { return numTargets; }
bool destroy(); // returns success of attempt to destroy
// EnemyTarget object
private:
static size_t numTargets; // object counter
};
// class statics must be defined outside the class;
// initialization is to 0 by default
size_t EnemyTarget::numTargets;
在这个例子中, 静态数据成员numTargets就是用来计数产生的对象个数的.
在《c++ 程序设计语言》中,是这样运用的:
Static静态成员,它是类的一部分,但却不是该类的各个对象的一部分。一个static成员只有唯一的一份副本,但不像常规的非static成员那样在每个对象里各有一份副本。 与此类似,一个需要访问类成员,然而却并不需要针对特定对象去调用的函数,也被称为一个static成员函数。其好处在于消除了由于依赖全局量而引起的问题
Class Date
{
Int d, m, y;
Static Date default_date;
Public:
Date(int dd=0, int mm=0, int yy=0);
//……
Static void set_default(int, int, int);
};
静态成员可以像任何其他成员一样引用,此外,对于静态成员的引用不必提到任何对象,相反,在这里应该成员的名字加上作为限定词的类的名字。
Void f()
{
Date::set_default(4, 5, 1945);
}
静态成员(包括函数和数据成员)都必须在某个地方另行定义。如
Date Date:::default_date(16, 12, 1770);
Void Date::set_default(int d, int m, int y)
{
Date::default_date = Date(d, m, y);
}
补充:内部函数和外部函数
当一个源程序由多个源文件组成时,C语言根据函数能否被其它源文件中的函数调用,将函数分为内部函数和外部函数。
1 内部函数(又称静态函数)
如果在一个源文件中定义的函数,只能被本文件中的函数调用,而不能被同一程序其它文件中的函数调用,这种函数称为内部函数。
定义一个内部函数,只需在函数类型前再加一个“static”关键字即可,如下所示:
static 函数类型 函数名(函数参数表)
{……}
关键字“static”,译成中文就是“静态的”,所以内部函数又称静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件。
使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名,因为同名也没有关系。
2 外部函数
外部函数的定义:在定义函数时,如果没有加关键字“static”,或冠以关键字“extern”,表示此函数是外部函数:
[extern] 函数类型 函数名(函数参数表)
{……}
调用外部函数时,需要对其进行说明:
[extern] 函数类型 函数名(参数类型表)[,函数名2(参数类型表2)……];
posted @
2008-10-11 10:40 Sandy 阅读(353) |
评论 (1) |
编辑 收藏
在windows mobile 上如何修改菜单上的文字呢?
我原先也只是看别人的代码,然后copy过来自己用,有的时候弄不清楚所以然,就会出错。
通过下面的方法,我们可以修改菜单上的文字:
1
HMENU hMenu=NULL;
2![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
TBBUTTONINFO tbbi =
{0};
3
tbbi.cbSize = sizeof(tbbi);
4
tbbi.dwMask = TBIF_LPARAM | TBIF_BYINDEX;
5
SendMessage(hMenuHWND, TB_GETBUTTONINFO, 1, (LPARAM)&tbbi); //修改菜单项 在左边为0,在右边为1
6
hMenu = (HMENU)tbbi.lParam;
7
8
InsertMenu(hMenu,beforeItem,MF_BYCOMMAND,afterItem,sText); //加入含有欲改写文本的菜单项
9
DeleteMenu(hMenu,beforeItem,MF_BYCOMMAND); //删除被改写的菜单
通过上面的方法,我们就能修改菜单上的文字。
此外,学习一下TB_GETBUTTONINFO
消息:TB_GETBUTTONINFO
作用:This message retrieves the information for a button in a toolbar.
使用:
wParam = (WPARAM)(INT) iID; lParam = (LPARAM)(LPTBBUTTONINFO) lptbbi;
参数介绍:
iID Button identifier.
lptbbi Long pointer to a TBBUTTONINFO structure that receives the button information. The cbSize and dwMask members of this structure must be filled in prior to sending this message.
posted @
2008-07-30 18:41 Sandy 阅读(903) |
评论 (0) |
编辑 收藏
如何获得单个进程所占内存的大小,也许很简单,通过GetProcessMemoryInfo可以轻松获得,然而那是在PC上。但在windows mobile 上,这个函数不存在,它的实现机制我也不太清楚。所以如何获得一个进程的占用内存大小,则需要另辟蹊径。不过目前,我还没有找到。
我现在的方法,如下:
1
DWORD GetUsedMemory(DWORD pID)
2![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif)
{
3
DWORD memUsage = 0;
4
HANDLE hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPHEAPLIST, pID);
5
if (INVALID_HANDLE_VALUE != hSnapShot)
6![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
7
HEAPLIST32 heapList;
8
heapList.dwSize = sizeof(HEAPLIST32);
9
BOOL bOk = Heap32ListFirst(hSnapShot, &heapList);
10
for (; bOk; bOk = Heap32ListNext(hSnapShot, &heapList))
11![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
12
HEAPENTRY32 he;
13
he.dwSize = sizeof(HEAPENTRY32);
14
BOOL fOK = Heap32First(hSnapShot, &he, pID, heapList.th32HeapID);
15
for(; fOK; fOK = Heap32Next(hSnapShot, &he))
16![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
17
memUsage += he.dwBlockSize;
18
}
19
}
20![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
21![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
22![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
23
// 关闭快照句柄
24
CloseToolhelp32Snapshot(hSnapShot);
25
}
26![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
27
return memUsage;
28![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
29
}
以上的这个方法与实际有出入。
有没有更好的办法呢?希望研究过的和正在研究的人,或者感兴趣的人,能够指点一二,让我能够走出困惑。
posted @
2008-07-29 16:21 Sandy 阅读(1150) |
评论 (4) |
编辑 收藏
查了很多资料 ,都说对于多语言要这样写:
简体中文:
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
#ifdef _WIN32
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
#pragma code_page(936)
#endif //_WIN32
繁体中文
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHT)
#ifdef _WIN32
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL
#pragma code_page(950)
#endif //_WIN32
英语
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
#ifdef _WIN32
LANGUAGE LANG_CHINESE, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#endif //_WIN32
对于简体中文和英语而言,似乎还能显示正确,但对于繁体而言,代码页设置成950后,就会显示出问题,很是疑惑。但只将代码页改回936,这个问题就消失了,想不通为什么?
希望能在知道的多一点。
posted @
2008-05-13 13:48 Sandy 阅读(480) |
评论 (0) |
编辑 收藏