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

简介: 在一个函数的内部,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;

}

         参考了网上部分资源!


相关文章
|
小程序 前端开发 JavaScript
宠托师微信小程序---开题报告分享
宠托师微信小程序---开题报告分享
654 0
|
机器学习/深度学习 编解码
一种用于心音分类的轻量级1D-CNN+DWT网络
这是由National Institute of Technology Rourkela, Central University of Rajasthan发布在**2022 ICETCI**的论文,利用离散小波变换(DWT)得到的多分辨率域特征对1D-CNN模型进行心音分类训练。
836 1
|
4月前
|
安全 Linux 网络安全
云服务器上线后必须完成的安全设置清单
云服务器上线前,务必完成基础安全配置:关闭非必要服务、使用强密码或SSH密钥、修改默认远程端口、限制登录IP、禁用root直连、及时更新补丁、精细配置防火墙、监控关键日志、定期备份并验证恢复。安全无小事,基础防护是稳定运营的第一道防线。(239字)
549 0
云服务器上线后必须完成的安全设置清单
|
3月前
|
人工智能 JSON API
嘴炮Hermes:我干完了!实际啥也没做,咋整?
本文揭秘AI Agent“嘴炮”现象(执行幻觉):模型不真实调用工具,却编造看似完美的执行报告。以Hermes生成壁纸任务为例,剖析其成因——LLM本质是概率文本生成器。提出两大缓解方案:开启`tool_use_enforcement: true`强制校验 + 在SOUL.md中植入“执行铁律”,显著降低幻觉率。
296 0
|
8月前
|
机器学习/深度学习 数据挖掘 调度
基于Stacking集成学习的数据回归预测(4种基学习器PLS、SVM、决策、KNN,多种元学习器比选)MATLAB代码
🔥 内容介绍 一、引言:回归预测痛点与集成学习破局之道 1.1 回归预测的应用价值与单一模型局限 在工业质检(如食品成分含量预测)、金融量化(如股价波动预测)、环境监测(如 PM2.5 浓度预测)等场景中,回归预测的精度直接决定决策效果。但实际应用中,单一模型往往存在明显短板: 偏最小二乘(PLS)擅长处理高维共线性数据,却难以拟合非线性关系; 支持向量机(SVM)能捕捉非线性特征,却对参数敏感、泛化能力不稳定; 随机森林(RF)抗过拟合能力强,却在高维数据下训练效率低。 这些局限导致单一模型难以适配复杂数据的多元特征,而集成学习通过 “模型组合” 思路,成为突破精度瓶颈的关键。
291 9
|
8月前
|
弹性计算 网络协议 安全
2026年阿里云最新活动参考:云服务器实时活动与优惠政策,购买和试用教程
2026年,阿里云持续推出多样化的优惠政策与活动,帮助个人开发者、中小企业及大型企业以更低的成本上云。本文将基于最新资讯,全面解析阿里云服务器的优惠政策、购买与试用教程以及当前热门活动,为您提供一站式上云指南。无论是初学者还是资深用户,都能通过本文找到适合自身需求的优惠方案,以供大家了解最新优惠信息及选择参考。
798 16
|
8月前
|
人工智能
全栈AI服务世界!一图看懂阿里云2025全球化进展
全栈AI服务世界!一图看懂阿里云2025全球化进展
723 2
|
9月前
|
人工智能 编解码 算法
2025年数字人制作工具选型指南:从架构集成与渲染精度出发的五大评估维度
本文深入剖析数字人技术选型难题,聚焦渲染精度、生成效率与全链路集成三大核心维度。对比必火AI、UE5高定方案与开源工具的技术优劣,揭示“少样本训练+4K超清输出+多模态融合”如何破解工程落地瓶颈,助力团队避开API碎片化与实时性误区,实现分钟级视频批量生成,为不同场景提供高效可落地方案。
|
弹性计算 负载均衡 网络协议
阿里云SLB深度解析:从流量分发到架构优化的技术实践
本文深入探讨了阿里云负载均衡服务(SLB)的核心技术与应用场景,从流量分配到架构创新全面解析其价值。SLB不仅是简单的流量分发工具,更是支撑高并发、保障系统稳定性的智能中枢。文章涵盖四层与七层负载均衡原理、弹性伸缩引擎、智能DNS解析等核心技术,并结合电商大促、微服务灰度发布等实战场景提供实施指南。同时,针对性能调优与安全防护,分享连接复用优化、DDoS防御及零信任架构集成的实践经验,助力企业构建面向未来的弹性架构。
1118 76
|
存储 人工智能 自然语言处理
AI-Compass GraphRAG技术生态:集成微软GraphRAG、蚂蚁KAG等主流框架,融合知识图谱与大语言模型实现智能检索生成
AI-Compass GraphRAG技术生态:集成微软GraphRAG、蚂蚁KAG等主流框架,融合知识图谱与大语言模型实现智能检索生成