关于函数返回值的几种情况

简介: 在一个函数的内部,return的时候返回的都是一个拷贝,不管是变量、对象还是指针都是返回拷贝,但是这个拷贝是浅拷贝。分为以下几种情况:1)返回一个基本类型的变量;2)返回非动态分配的指针;3)返回动态分配的指针;4)返回非基本类型(对象等)…

关于函数返回值的几种情况  

在一个函数的内部,return的时候返回的都是一个拷贝,不管是变量、对象还是指针都是返回拷贝,但是这个拷贝是浅拷贝。

分为以下几种情况:1)返回一个基本类型的变量;2)返回非动态分配的指针;3)返回动态分配的指针;4)返回非基本类型(对象等)…

1.    如果返回一个基本类型的变量,比如:


int a;


a = 5;


return a;


那么就会a的一个拷贝,即5返回,然后a就被销毁了。尽管a被销毁了,但它的副本5还是成功地返回了,所以这样做没有问题。



2.   但是对于非动态分配(new/malloc)得到的指针,像1那么做就会有问题,比如在某个函数内部:


int a[] = {1, 2};


return a;


那么也会返回指针a的一个拷贝,我们假定a的地址值为0x002345FC,那么这个0x2345FC是能够成功返回的。当return执行完成后,a就要被销毁,也就是0x002345FC所指向的内存被回收了。如果这时候在函数外面,去地址0x002345FC取值,那得到的结果肯定是不对的。这就是为什么不能返回局部指针的原因。返回局部变量的引用的道理和这个类似。注意:不能返回局部变量的指针或引用!


     注意:《高质量程序设计指南中C++/C语言》中指出:return语句不可返回“堆栈内存”的指针或者“引用”,因为该内存单元在函数体结束时就被自动释放。


强调举例一下,看下面的程序:



#include<iostream>

using namespace std;

char *GetMemory(void)    // char *GetMemory(char *p,int num)

{

char p[ ] = "hello world"; // p= (char *)malloc(sizeof(char) *num);

return p;

}

int main ()

{

      char *str = NULL;

      str = GetMemory() ; //str = GetMemory(str,100) ;

      cout << str ;

return 0;

}

答案,可能是乱码,也可能是正常输出。因为GetMemory返回的是指向“栈内存”的指针,该指针的地址不是NULL,但其原来的内容已经被清除,新内容不可知。


为什么不是输出的是数组的首地址呢??如果函数是黑体部分的话,可以利用函数返回值来传递动态内存。那是因为注释部分是堆操作,不是会被os自动释放的栈内存。


但是若用非黑体的部分怎么就不行了呢?栈内存就是编译器自动支配的,也就是说非动态分配的东西基本上都在栈中


这些问题可以用下面的例子回答:



int testA (void)

{

int b = 1 ;

return b ;

}

char * testB (void)

{

char str[] = "abc" ;

return str ; //但这样的方法是不推荐的

}

int main()

{

printf( " the value of testA is %d \n", testA() ) ;

printf( " the value of testB is %c ", *( testB() ) ) ;  

}

image.png


对于返回值的情况:


testA与main函数同在栈区,testA结束时C++创建临时变量,然后将返回值复制给该临时变量。printf( " the value of testA is %d \n", testA() ) 时输出的是该临时变量的值,testA中的b已经不存在。


对于返回指针的情况:这是最复杂的部分。首先,对于上面的情形:返回一个数组的首地址,由于是返回char *类型,所以C++会首先创建一个char *类型的临时变量,再把该数组的首地址赋给临时变量;函数结束后该数组也就被销毁,这就意味着临时变量指向了一个“未声明的地址”,幸运的情况下,这段内存暂时还没有被其他的数据所覆盖,因此还能输出正确的内容。在testB里面,如果换成char* str="abc";return str; 由于这时str指向的是全局数据区的一段内存地址,所以函数结束后临时变量也指向该地址,所以编译器不会提出警告。但这样的方法是不推荐的。


返回引用:这中情况的效率最高,它直接返回一个对象,不产生返回值的副本。但同时也要注意避免返回局部引用的情况。



3.  对于返回(动态分配得到的)指针的另外一种情况,比如在函数内部:


int a = new int(5);


return a;


这样做是可以的。return a执行完后,a并没有被销毁(必须要用delete才能销毁a),所以这里返回的a是有效的。



4.    如果不是基本数据类型,比如:


class A


{


public:


              OtherClass * ...


};



如果在某个函数内部有一个A类的局部变量,比如:


A a;


return a;


这时候也会返回a的一个拷贝,如果A没有写深拷贝构造函数,就会调用缺省的拷贝构造函数(浅拷贝),这样做就会失败的;


如果A中提供了深拷贝构造函数,则这样做就是可以的。



实验代码如下:


#include <iostream>

using namespace std;

int some_fun1()

{

      cout << "\nsome_fun1()..." << endl;

      int a = 5;

      return a;                   //OK

}

int* some_fun2()

{

      cout << "\nsome_fun2()..." << endl;

      int a = 5;

      int *b = &a;

      return b;                   // not OK

}

int* some_fun3()

{

      cout << "\nsome_fun3()..." << endl;

      int *c = new int(5);

      return c;                   // OK, return c执行完后,并没被销毁(必须要用delete才能销毁)

}

class CSomething

{

public:

     int a;

      int b;

public:

      CSomething(int a, int b)

      {

             this->a = a;  

             this->b = b;

      }

};

class CA

{

public:

      CA(CSomething* sth)

      {

             this->sth = new CSomething(sth->a, sth->b);

      }

      // 如果不实现深拷贝,请注释这个拷贝构造函数

      CA(CA& obj)

      {

             sth = new CSomething((obj.sth)->a, (obj.sth)->b);

      }

      ~CA()

      {

             cout << "In the destructor of class CA..." << endl;

            if (NULL != sth)

             {

                    delete sth;

             }

      }

       void Show()

      {

             cout << "(" << sth->a << ", " << sth->b << ")" << endl;

      }

     void setValue(int a, int b)

     {

             sth->a = a;

             sth->b = b;

      }

      void getSthAddress()

      {

             cout << sth << endl;

      }

private:

      CSomething* sth;            // 以指针形式存在的成员变量

};

CA some_fun4()

{

      cout << "\nsome_fun4()..." << endl;

      CSomething c(1, 2);

      CA a(&c);

      cout << "\\ some_fun4()..." << endl;

      return a;                       // 如果CA没有实现深拷贝,则not OK;如果实现深拷贝,则OK

}

int main(int argc, char* argv[])

{

      int a = some_fun1();

      cout << a << endl;              // OK

      int *b = some_fun2();

      cout << *b << endl;             // not OK,即便返回结果正确,也不过是运气好而已

       int *c = some_fun3();           // OK, return c执行完后,c并没有被销毁(必须要用delete才能销毁)

       cout << *c << endl;

      delete c;

      CA d = some_fun4();           // 如果CA没有实现深拷贝,则not OK;如果实现深拷贝,则OK

      d.Show();

      return 0;

}

         参考了网上部分资源!


相关文章
|
4月前
|
C++
c++函数参数
c++函数参数
42 0
|
1月前
|
程序员 C语言
指针在函数参数和返回值中的使用
指针在函数参数和返回值中的使用
32 9
|
4月前
|
C语言
用指针函数变量调用函数
用指针函数变量调用函数
17 2
|
4月前
|
存储 C语言
使用函数指针变量调用函数
使用函数指针变量调用函数
27 0
|
4月前
|
搜索推荐 程序员 C++
用函数指针变量调用函数
用函数指针变量调用函数
25 4
|
4月前
|
存储 C语言
C语言函数的返回值
C语言函数的返回值
42 0
|
4月前
函数参数
函数参数。
41 0
|
C++
c++引用作为函数参数和函数返回值
c++引用作为函数参数和函数返回值
66 0
|
编译器
【为什么】将指针传递给函数
【为什么】将指针传递给函数
|
C语言
函数类型和函数指针
函数类型和函数指针
94 0