C++函数返回值

首先,强调一点,和函数传参一样,函数返回时也会做一个拷贝。从某种角度上看,和传参一样,也分为三种:

  • 一般(传统)返回:返回任意类型的数据类型,会有一个拷贝,对于复杂对象效率低下;例如:int test(){}或者 Point test(){}
  • 返回指针:返回一个指针,也叫指针类型的函数;例如:int *test(){} 或者 Point *test(){}
  • 返回引用:返回一个引用,也叫引用类型的函数;例如:int &test(){}或者 Point &test(){}

一般来说,在函数内对于存在栈上的局部变量的作用域只在函数内部,在函数返回后,局部变量的内存会自动释放。因此,如果函数返回的是局部变量的值,不涉及地址,程序不会出错;但是如果返回的是局部变量的地址(指针)的话,就会造成野指针,程序运行会出错。因为函数只是把指针复制后返回了,但是指针指向的内容已经被释放,这样指针指向的内容就是不可预料,调用就会出错。

1、指针类型的函数:

若函数的返回值是指针,该函数就是指针类型的函数。(即函数return一个指针,该指针可以是任何类型的)

1)指针类型的函数定义:

<类型> *函数名(参数)

2)说明:

  • 不要将非静态局部地址用作函数返回值:因为局部地址在离开函数后就失效了。
  • 可以在函数中用动态内存分配(new)的地址返回,但需要注意内存分配和释放不在同一级别,不要忘记释放,否则内存泄露;
  • 可以在主调函数中定义数组,函数中对该数组进行操作,然后返回其中一个元素的地址;

2、引用类型的函数:

1)引用类型函数的定义:

<类型> &函数名(参数)

2)说明:

前面我们知道,传参时传递指针操作起来比较麻烦,C++为了简化,约定出来了传引用(使用起来和传值一样,但背后效果和传指针一样)。而引用类型函数,确不一样,他使用起来和传统的返回一样,但是没有指针函数的效果。

所以,我们不建议使用,下面的例子中也不会使用这种方法!

3、综合示例:

1)返回栈内局部变量:

#include <iostream>
using namespace std;
int fun1() {
    int i = 1;
    cout<<"fun1 i address"<<&i<<endl;
    return i;//ok,返回值是i值得拷贝
}
int *fun2() {//指针类型的函数
    int i = 2;
    int *ip = &i;
    cout<<"fun2 i address"<<ip<<endl;
    return ip; // Wrong!返回值是ip指针的拷贝,但该地址在函数结束后会释放变得无效
}
int main() {
    int r1 = fun1();
    cout<<"main fun1 return i address"<<&r1<<endl;
    cout << r1 << endl; // 1
 
    int *r2 = fun2();
    cout<<"main fun2 return i address"<<r2<<endl;
    //这里有可能出错:具体看对应的内存是否被覆盖,但总之该内存已无效
    cout << *r2 << endl;//0

    return 0;
}

输出:

fun1 i address0x7ffc49e9b69c
main fun1 return i address0x7ffc49e9b6b4
1
fun2 i address0x7ffc49e9b694
main fun2 return i address0x7ffc49e9b694
0

我们在看一个对象的例子:

#include <iostream>

using namespace std;

class Point {
  public:
    Point(int a,int b):x(a),y(b){}
    int getX();
    void setX(int x);
  private:
    int x,y;
};

int Point::getX(){
  return x;
}
void Point::setX(int a) {
  x = a;
}
Point func(int x) {
  Point p(x,100);
  cout<<"func1 p address:"<<&p<<endl;
  return p;//ok,发生一次Point拷贝
}
Point *func2(int x) {//指针函数
  Point p(x,200);
  cout<<"func2 p address:"<<&p<<endl;
  return &p;//wrong,返回值是p地址的拷贝,但该地址在函数结束后会被释放变得无效
}
main() {
  Point p = func(1);
  cout<<"main return p address:"<<&p<<endl;
  cout<<"main return p x:"<<p.getX()<<endl;

  Point *p2 = func2(2);
  cout<<"main return p address:"<<p2<<endl;
  cout<<"main return p x:"<<p2->getX()<<endl;
}

编译的时候会有一个警告:

test88.cpp: In function ‘Point* func2(int)’:
test88.cpp:26:9: warning: address of local variable ‘p’ returned [-Wreturn-local-addr]
   Point p(x,200);
         ^

输出:

func1 p address:0x7fff0f005270
main return p address:0x7fff0f005290
main return p x:1
func2 p address:0x7fff0f005270
main return p address:0x7fff0f005270
main return p x:6299776

结论对于栈内局部变量,采用一般的返回值,实际上是对返回值的一次值拷贝,在内存里会有两个示例;对于指针类型函数的返回值,实际上是对地址的一次拷贝,内存只有一个示例,但该地址是一个非法的地址,在使用时会出现问题

2)返回字符串:

通过 char* s = “Hello”; 的方式得到的是一个字符串常量 Hello,存放在只读数据段(.rodata section),把该字符串常量的只读数据段的首地址赋值给了指针 s,所以函数返回时,该字符串常量所在的内存不会被回收,所以能正确地通过指针访问。

#include <iostream>
using namespace std;

char *fun1() {
  char *s="hello";
  return s;//ok
}

int main() {
  char *c1 = fun1();
  cout<<c1<<endl;
  //常量,无法在修改 
  
  return 0;
}

3)静态变量:

可以把局部变量声明为static静态变量。这样变量存储在静态存储区,程序运行过程中一直存在。

int *fun3(){
  static int i = 5;
  cout<<"fun3 i address:"<<&i<<endl;
  return &i;
}
int main() {

  int *r1 = fun3();
  cout<<"main return i address:"<<r1<<endl;
  cout<<*r1<<endl;
}

输出:

fun3 i address:0x602078
main return i address:0x602078
5

4)数组:

数组是不能作为函数的返回值的。因为编译器会把数组名认为是局部变量(数组)的地址。返回一个数组,实际上是返回指向这个数组首地址的指针。函数结束后,数组作为局部变量被释放,这个指针则变成了野指针。但是声明数组是静态的,然后返回是可以的。

int *fun4() {
  static int a[2]={4,5};
  cout<<"fun4 a[] address:"<<&a<<endl;
  return a;
}
int main() {
  int *r2 = fun4();
  cout<<"main return a[] address:"<<r2<<endl;
  cout<<*r2<<endl;
}

输出:

fun4 a[] address:0x60207c
main return a[] address:0x60207c
4

5)堆内变量:

函数返回指向存储在堆上的变量的指针是可以的。但是,程序员要自己负责在函数外释放(free/delete)分配。

int *fun5() {
  int *j = new int;
  *j = 99;
  cout<<"fun5 j address:"<<j<<endl;
  return j;
}
int main() {

  int *r3 = fun5();
  cout<<"main return j address:"<<r3<<endl;
  cout<<*r3<<endl;
  *r3 = 100;
  cout<<*r3<<endl;
  delete r3;
}

输出:

fun5 j address:0x208b010
main return j address:0x208b010
99
100

综上,C++的函数返回和函数传参有所不同,返回值和传参一样也有三种类型:

  • 使用一般(传统)的函数返回,对于复杂对象会涉及到拷贝效率问题;
  • 使用指针类型的函数会有很多限制和弊端(容易内存泄露);
  • 引用类型的函数又是一个鸡肋;

所以一般C++函数都是用传址的方式进行双向数据绑定,而返回值仅仅是一个成功或失败的标志。

发布了800 篇原创文章 · 获赞 460 · 访问量 436万+

猜你喜欢

转载自blog.csdn.net/liuxiao723846/article/details/104825422