您的位置:首页 > 其它

函数参数----一道简单问题引发思考

2006-07-21 14:25 585 查看
函数参数

一道简单问题引发思考

KeyWord:

      函数参数,传值,传址

abstract

函数参数的传递我们经常的使用,从基本类型到各个自定义的类型或型别的传递,不同的应用有着不同的使用方法,今天我们就来深入的讨论一下有关函数参数传递方面比较细节的地方。

有这样一道题

void GetString (char* p)

{

      p = new char[10];

      strcpy(p,”World!”);

}

 

char* p = 0x0;

GetString(p);

cout << p <<endl;

可能很多人都知道这道题的答案,是的很简单,字符串指针依然为空,在试图输出的时候编译器出错。下面我们将整个过程分解成四个步骤来看一下整个过程中的各个变量在其生存周期过程中都干了些什么。

首先,初始化字符产指针p指向如下语句:char* p = 0x0。如下图所示:



 

接着,调用函数GetString(p),但是在调用发生后还未进入函数体内部之前,编译器为我们自动生成了一个基于原参数的临时的参数_p,也就是他的副本,该临时参数的值与原参数的值一样,由于p等于0故-p也等于0。如下图所示:
 



 

然后,进行内存的申请和赋值操作,执行如下的语句:

p = new char[10];

strcpy(p,”World!”);

      但其实上并不是我们看到的那样,应该是如下的格式:

_p = new char[10];

strcpy(_p,”World!”);

所有的操作都不是针对于字符串指针p进行的,而是针对他的副本_p进行的。如下图所示:
 



 

最后,当函数返回后,临时参数_p也被删除了,从而使得指向字符串”world!”的区域丢失,无法再进行释放,从而造成了内存泄露。
 



 

 

我们进一步将这个问题引伸,讨论一下较为复杂的关于类的传值操作,假设存在这样一个类:

class CA

{

char *pStr;

public:

      CA: pStr (0)(char* pStr)

      {

           this->pStr = new char[strlen(pStr) + 1];

           strcpy(this->pStr,pStr);

      }

      ~CA(){     delete [] pStr;  pStr  = NULL;}

 

      CA& operator = (const CA& other)  {         … …        }

………

void Print(){ cout << pStr << endl;}

};

有如下的执行语句:

Void TestCase(CA  a)

{

      //do something

}

CA  a(“hello!”);

TestCase(a);

a. Print();

你猜猜输出的是什么?是“hello!”吗?,还是其他的什么东西,有的朋友可能已经知道正确的结果:程序出错!是的,程序出错,就出在~CA(){    delete [] pStr;  pStr  = NULL;}。

让我跟踪一下,明明 pStr有数据但是为什么在执行delete [] pStr是程序会抛出异常呢?接下来我们将整个流程图解化,看看到底都发生了什么。

       首先,第一个执行语句就有很大的玄机:CA  a(“hello!”),相当于执行了如下的几步操作:

      //C++伪代码

      char noname [] = {” Hello!”};

注意:编译器自动产生一个匿名的字符串, 字符串从进入其所在的函数(不必执行到该语句)就已经申请的存储空间,并且其地址和内容不能改变,直道其所在的函数结束才消失。而不是在其所在的语句CA  a(“hello!”)结束后消失。

CA(char* _ noname)  // 此处产生noname的副本_ noname;

{

      pStr = new char[strlen(_noname) + 1];

      strcpy(pStr,_ noname);

}   

在执行完CA的构造函数后,“Hello!”产生了一个副本,由a.pstr指向它。执行结果如下图:



 

然后我们看一下TestCase(a),同样就像我们一开始探讨的值符串指针一样,编译器帮我们构造了一个a的副本我们暂且叫他_a,在尚未进行TestCase函数体内部程序之前的存储是什么样的呢?我们看一下图:



然后程序执行完毕,撤销临时变量_a,调_a的析构函数并删除pStr,等一下!_a.pStr指向的正是编译器产生的匿名数组的内容,由于其在其所在的函数执行期内不可变,于是编译器就会抛出错误来,于是程序就此打住了。

其实解决方法很简单,我们只需添加一个拷贝构造函数就行了。

      CA(const CA& other)

      {

           pStr = new char[strlen(other.pStr) + 1];

           strcpy(pStr, other.pStr);

      }

这样的话,程序在进行a的副本创建时,自动执行了副本的拷贝构造函数,然后由构造函数为其制作了一个“hello”的副本,而这一份副本是可以删除的,程序在执行到析构函数时就不会出错了。当然对原数据没有产生任何的影响,完全是在被调用函数内部自生自灭。

其实,有很多的原因使得我们不赞成使用这种传递具体值的方式:首先从效率上而言,这种方式要进行自身的构造和析构,而构造和析构具有一定的时间和空间成本,从而使执行时的效率大大折扣;其次尽管我们知道它在被调用函数的任何修改都不会影响返回结果,但是我们还有更好的解决方法:只需为传递参数添加一个简单的const修饰符就可以解决这个问题,就像这样“const CA& a”一样问题就可以解决 J。

 

 

原创文章,转载请注明出处!

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息