从一个C++程序谈起──引用、堆栈参数传递及其它
#include “stdafx.h”
#include “iostream.h”
#include “string.h”
class Person{
private:
char *m_pName:
public:
Person(char *);?牔?
~Person();?煟牔?
char* getName(Person);??
};
Person::Person(char*s){?牓?
int l=strlen(s)+1;??
m_pName= new char[l];?牔?
strcpy(m_pName,s);??
?爙
Person::~Person(){?煟牓?
Person* p;??
p=this;
delete [] m_pName;??
?爙
char* Person::getName(Person person){?牓?
return person.m_pName;
?爙
int main(int argc, char* argv[]){?煠牐牓?
Person me(“John”);??
Person* p1=&me;??
Person you(“Tom”);??
Person* p2=&you;??
cout<<me.getName(you)<<“\n”;??
return 0;??
以上程序看上去很简单,也没什么问题。但是,实际上运行该程序,系统会报如下错误:Debug Assertion Failed! _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse))。原来是程序在调用dbgHeap.c时出了问题。为了弄清问题的根源我们给程序加入一些调试行,修改如下:
Person::~Person(){?煟牓?
Person* p;??
p=this;
delete [] m_pName;??
?爙
char* Person::getName(Person person){??
Person* p;??
char* ch;
ch=person.m_pNamep;??
p=&person;
return person.m_pName;??
?爙
int main(int argc, char* argv[])??
?焮
Person me(“John”);??
Person* p1=&me
Person you(“Tom”);??
Person* p2=&you;
cout<<me.getName(you)<<“\n”;
return 0;
?爙
在调试状态下单步运行该程序,观察各指针的值,在我的机器上的运行结果如下??
me: 对象地址: 0x0012ff70 m_pName地址:0x009b0050
you: 对象地址: 0x0012ff68 m_pName地址: 0x009b1ff0
参数person:?? 对象地址: 0x0012ff0c m_pName地址: 0x009b1ff0
由于在C++中参数传递是“值传递”的,在调用GetName(Person person)函数时,系统给形参分配存储单元。问题在于,这种分配内存的方法是在堆栈上分配的,而堆栈是后进先出的。所以当程序退出该函数时,系统会收回为形参分配的内存,于是调用Person的析构函数,释放为对象you的名字“Tom”分配的内存,而此时you并未使用完,所以当程序运行到最后,调用对象you调用其析构函数真正要释放为you分配的内存时,由于内存已被错误地释放,于是就会出现“Debug Assertion Failed”的错误。
改正的方法其实也很简单,用引用作形参即可。此时方法的声明改为:
char* getName(name&);??
在实现中相应地改为:
char* Person::getName(Person& person)
其他都不用改变就一切OK了。这就是使用引用的好处。此时的内存分配情况如下^14020502a^所示:
引用相当于为变量起了一个别名,其作用和指针相同,但不像指针那样繁琐和难于理解。通过使用引用来传递参数,形参和实参都指向一块内存,由于没有在堆栈上分配新的内存,程序从getName(name&)返回时不会调用析构函数。对象me和you是分配在堆栈上的,在程序运行出其作用域后自行删除。此处即是主程序结束时。而存放Person的名字的内存是在构造函数中调用new分配在堆上的,分配在堆上的内存不会自动地释放,要调用delete显式释放。此处是在析构函数中释放的。所以修改后的程序在程序运行到最后调用类的析构函数时会正确地释放内存。
你可以在调试状态下单步运行以上程序,认真观察内存地址的值和程序的流向,相信你会对内存分配,参数传递,引用等概念有一个更清晰的认识。