C++函数返回机制,返回类型

news2024/9/29 9:19:57

return语句终止当前正在执行的函数并将控制权返回到调用该函数的地方。

return语句有两种形式

return;

return expression;

无返回值函数

没有返回值的return语句只能用在返回类型是void的函数中。

返回void的函数不要求必须有return语句,因为这类函数的最后一句后面会隐式地执行return.

也就是说,下面这两种写法是等价的

void A()
{
return;
}

void A()
{
}


一个返回类型是void的函数也能使用return语句的第二种形式,不过此时return语句的expression必须是另一个返回void的函数。强行令void函数返回其他类型的表达式将产生编译错误。

void B(...)
{
	;
}
void A()
{
	return B();//B的返回类型是void,所以可以
}
int B(...)
{
	;
}
void A()
{
	return B();//B的返回类型不是void,所以不可以
}



有返回值函数

return 语句的第二种形式提供了函数的结果。

只要函数的返回类型不是 void,则该函数内的每条return语句必须返回一个值。return语句返回值的类型必须与函数的返回类型相同,或者能隐式地转换成函数的返回类型。


尽管C++无法确保结果的正确性,但是可以保证每个return语句的结果类型正确。也许无法顾及所有情况,但是编译器仍然尽量确保具有返回值的函数只能通过一条有效的return语句退出。

例如:
 

int A()
{
return;//编译器会报错
}

第一段代码的return语句是错误的,因为它没有返回值,编译器能检测到这个错误。 

int A(int w)
{int i;
for(i=0;i<10;++i)
{
if(i==w)
return i;
}
}

 错误是函数在for 循环之后没有提供return语句。在上面的程序中,如果i!=w,则函数在执行完for循环后还将继续其执行过程,显然应该有一条return语句专门处理这种情况。编译器也许能检测到这个错误,也许不能:如果编译器没有发现这个错误,则运行时的行为将是未定义的。

在含有return语句的循环后面应该也有一条return语句,如果没有的话该程序就是错误的。很多编译器都无法发现此类错误.

值是如何被返回的

返回值

返回一个值的方式和初始化一个变量或形参的方式完全一样:返回的值用于初始化调
用点的一个临时量,该临时量就是函数调用的结果。

必须注意当函数返回局部变量时的初始化规则。

例如我们书写一个函数,给定计数值、单词和结束符之后,判断计数值是否大于1:如果是,返回单词的复数形式;如果不是,返回单词原形:

//如果ctr的值大于1,返回word的复数形式
string make_plural(size_t ctr, const string &word, const string &ending)
{
return (ctr >1) ? word + ending : word;
}


该函数的返回类型是string,意味着返回值将被拷贝到调用点。因此,该函数将返回word
的副本或者一个未命名的临时string对象
,该对象的内容是word和ending的和。

我们再看一个例子

int A()
{
int a=9;
return a;
}

...
int b=A();

函数返回值是int类型,执行return语句的同时把a的值拷贝到一个临时的int变量上面,然后销毁局部变量a,然后再将这个临时变量赋给调用对象——变量b 

 返回引用

同其他引用类型一样,如果函数返回引用,则该引用仅是它所引对象的一个别名。

举个例子来说明,假定某函数挑出两个string形参中较短的那个并返回其引用:

//挑出两个string对象中较短的那个,返回其引用
const string &shorterstring(const string &sl, const string &s2)
{
return sl.size() <= s2.size() ? sl:s2;
}


其中形参和返回类型都是const string的引用,不管是调用函数还是返回结果都不会真正拷贝string对象。

不要返回局部对象的引用或指针

函数完成后,它所占用的存储空间也随之被释放掉。因此,函数终止意味着局部变量的引用将指向不再有效的内存区域:

//严重错误:这个函数试图返回局部对象的引用
const int& A()
{
int a;
if(a==3)
return a;
else
return 4;

}

 
上面的两条return 语句都将返回未定义的值,也就是说,试图使用返回值将引发未定义的行为,对于第一条return语句来说,显然它返回局部对象的引用。对于第二条return语句来说,它返回了一个临时的int值。

当函数结束时,临时对象占用的空间也就随之释放了,这两条return语句都指向了不可再用的内存空间。

有人可能好奇的去试了一下

int& AA()
{
	int a = 9;
	return a;
}


int main()
{
	int g = AA();
cout<<g<<endl;
}

发现打印出了9啊。这是为什么呢? 

在这个代码中,函数AA返回一个对局部变量a的引用。但是,这里有一个重要的问题:局部变量a在函数返回后会被销毁,这意味着返回的引用指向了一个不再存在的变量。这通常会导致未定义行为,因为程序可能会尝试访问已经被释放的内存。

在大多数编译器和设置下,你可能会看到g的值是9,但这完全是偶然的。这是因为局部变量a在函数返回后可能仍然保留在内存中的那个位置,并且它的值没有被其他数据覆盖。但是,这并不意味着这样的代码是安全的或可预测的。

现在,来详细解释一下函数返回的过程:

  • 当AA函数被调用时,它会在栈上分配空间来存储局部变量a,并给它赋值为9。
  • 然后,AA函数返回这个局部变量的引用。这本质上是一个内存地址。
  • 在main函数中,你试图将这个引用赋值给一个整数变量g。但是,因为引用本质上是一个地址,所以这里发生了隐式解引用,即g被赋予了地址指向的值,即9。
  • 最后,你打印出g的值,它看起来是9。

然而,由于前面提到的未定义行为,这个程序可能会在不同的编译器、不同的优化级别或不同的运行环境下表现出不同的行为。因此,这种代码是不安全的,应当避免使用。

int* B()
{
int b;
int*c=&b;
return c;
}

同样,返回局部对象的指针也是错误的一旦函数完成,局部对象被释放,指针将指向一个不存在的对象。

同样的道理,我们也来像上面一样来看看下面这样的程序

#include<iostream>
using namespace std;

int* B()
{
	int b=5;
	int* c = &b;
	return c;
}


int main()
{
	int *g = B();
	cout << *g << endl;
}

我们发现,打印出了5,这又是为啥呢?

在这段代码中,函数B返回一个指向局部变量的指针。局部变量b在函数B的栈帧中分配,并在函数返回后不再存在。然而,由于你返回了指向它的指针,你仍然可以通过这个指针访问它的内存位置(尽管这是未定义行为,因为b已经不再是一个有效的对象)。

这里是为什么这段代码打印出5的原因:

  1. 当调用函数B时,它在栈上分配了局部变量b,并将其初始化为5。
  2. 接下来,创建了一个指向b的指针c。
  3. 函数B返回这个指针c,即返回了b的地址。
  4. 在main函数中,你接收了这个返回的指针并将其存储在g中。
  5. 通过解引用g(即*g),你访问了之前b所在的内存位置,并打印出了存储在那里的值,即5。

然而,这种行为是不安全的,因为它依赖于局部变量的栈内存位置在函数返回后不会被改变。在实际编程中,这种做法是不推荐的,因为它可能导致不可预测的行为和程序崩溃,尤其是在更复杂的程序或者存在并发操作时

正确的做法是避免返回指向局部变量的指针,或者确保在返回指针之前,所指向的数据已经分配在了一个合适的持久存储位置,比如动态分配的内存(使用new或malloc等)。

关于函数返回过程:

  • 函数B执行完毕后,它的栈帧会被销毁,包括其中的局部变量。然而,返回的指针c(或者说,它所指向的地址)并不会自动失效。
  • 当main函数接收这个指针并尝试访问它时,它实际上是在访问一个已经被销毁的栈帧中的内存位置。如果这个内存位置在函数返回后没有被其他函数调用覆盖,那么你可能仍然能够看到原来的值(就像在这个例子中一样)。但是,这是不可预测的,并且是不安全的。

因此,尽管这段代码在某些情况下可能看起来能够正常工作,但它实际上包含了严重的错误和潜在的风险。

 

返回类类型的函数和调用运算符

和其他运算符一样,调用运算符也有优先级和结合律。用运算符的优先级与点运算符和箭头运算符相同,并且也符合左结合律。

因此,如果函数返回指针、引用或类的对象,我们就能使用函数调用的结果访问结果对象的成员。

例如,我们可以通过如下形式得到较短string对象的长度:

// 调用string 对象的size成员,该string对象是由shorterString函数返回的

auto sz = shorterString(si, s2).size();


因为上面提到的运算符都满足左结合律,所以 shorterString 的结果是点运算符的左侧运算对象,点运算符可以得到该string对象的size成员,size又是第二个调用运算符的左侧运算对象。

引用返回左值

  • 函数的返回类型决定函数调用是否是左值。
  • 调用一个返回引用的函数得到左值,其他返回类型得到右值。
  • 可以像使用其他左值那样来使用返回引用的函数的调用,特别是,我们能为返回类型是非常量引用的函数的结果赋值:
#include<iostream>
using namespace std;

int& A(int &a)
{
return a;
}
int main()
{
int a=9;
cout<<a<<endl;
A(a)=99;//返回值当左值用
cout<<a<<endl;
}

 


把函数调用放在赋值语句的左侧可能看起来有点奇怪,但其实这没什么特别的。

返回值是引用,因此调用是个左值,和其他左值一样它也能出现在赋值运算符的左侧。

如果返回类型是常量引用,我们不能给调用的结果赋值,这一点和我们熟悉的情况是一样的:

列表初始化返回值

C++11新标准规定,函数可以返回花括号包围的值的列表。类似于其他返回结果,此处的列表也用来对表示函数返回的临时量进行初始化。如果列表为空,临时量执行值初始化;否则,返回的值由函数的返回类型决定。

举个例子,回忆6.2.6节(第198页)的error msg函数,该函数的输入是一组可变数量的string实参,输出由这些string对象组成的错误信息。在下面的函数中,我们返回一个vector对象,用它存放表示错误信息的string对象:

vector<string> process()

{//...
// expected 和actual是string对象
if (expected.empty())
return {}; // 返回一个空vector对象
else if (expected == actual)
return {"functionX","okay"}; // 返回列表初始化的vector对象
else
return {"functionx", expected, actuall};

}


第一条return语句返回一个空列表,此时,process函数返回的vector对象是空的。如果expected不为空,根据expected和actual是否相等,函数返回的vector对象分别用两个或三个元素初始化。

  • 如果函数返回的是内置类型,则花括号包围的列表最多包含一个值,而且该值所占空间不应该大于目标类型的空间。
  • 如果函数返回的是类类型,由类本身定义初始值如何使用。
int C()
{
	return{ 1 };
}

主函数main的返回值

之前介绍过,如果函数的返回类型不是void,那么它必须返回一个值。

但是这条规则有个例外:我们允许main函数没有return语句直接结束。

如果控制到达了main函数的结尾处而且没有return语句,编译器将隐式地插入一条返回0的return语句。

返回数组指针

因为数组不能被拷贝,所以函数不能返回数组。不过,函数可以返回数组的指针或引用。虽然从语法上来说,要想定义一个返回数组的指针或引用的函数比较烦琐,但是有一些方法可以简化这一任务,

其中最直接的方法是使用类型别名:

typedef int arrT[10]; //arrT是一个类型别名,它表示的类型是含有10个整数的数组
using  arxT = int[10]: //arrT的等价声明
artT* func(int i); //func 返回一个指向含有10个整数的数组的指针


其中arrT是含有10个整数的数组的别名。因为我们无法返回数组,所以将返回类型定义成数组的指针。因此,func函数接受一个int实参,返回一个指向包含10个整数的数组的指针。
声明一个返回数组指针的函数

要想在声明func时不使用类型别名,我们必须记住,数组的维度应跟随在要定义的数组名之后:

int arr[10]; // arr是一个含有10个整数的数组
int *p1[10]; // p1是一个含有10个指针的数组
int (*p2)[10]= &arr; // p2是一个指针,它指向含有10个整数的数组


和这些声明一样,如果我们想定义一个返回数组指针的函数,则数组的维度必须跟在函数名字之后。然而,函数的形参列表也跟在函数名字后面且形参列表应该先于数组的维度

因此,返回数组指针的函数形式如下所示:

Type (*function (parameter_list))[dimension]

类似于其他数组的声明,Type表示元素的类型,dimension 表示数组的大小。(*function(parameter_list))两端的括号必须存在,就像我们定义p2时两端必须有括号一样。如果没有这对括号,函数的返回类型将是指针的数组。

举个具体点的例子,下面这个func函数的声明没有使用类型别名:

int (*func(int i))[10];


可以按照以下的顺序来逐层理解该声明的含义:

  • func(int i)表示调用func函数时需要一个int类型的实参。
  • (*func(int i))意味着我们可以对函数调用的结果执行解引用操作。
  • (*func(int i))[10]表示解引用func的调用将得到一个大小是10的数组。
  • int (*func(int i))[10]表示数组中的元素是int类型。

使用尾置返回类型

在C++11新标准中还有一种可以简化上述func声明的方法,就是使用尾置返回类。

任何函数的定义都能使用尾置返回,但是这种形式对于返回类型较复杂的函数最有效,比如返回类型是数组的指针或者数组的引用

尾置返回类型跟在形参列表后面并以一个>符号开头。

为了表示函数真正的返回类型跟在形参列表之后,我们在本应该出现返回类型的地方放置一auto:

// func 接受一个int 类型的实参,返回一个指针,该指针指向含有10个整数的数组
auto func(int i) -> int(*) [10];


因为我们把函数的返回类型放在了形参列表之后,所以可以清楚地看到func函数返回的是一个指针,并且该指针指向了含有10个整数的数组。

我们再看一个例子

#include<iostream>
using namespace std;
auto BB() -> int
{
	int a = 9;
	return a;
}
auto BBB() -> int*
{
	int a = 9;
	return &a;
}
int main()
{
	cout << BB() << endl;
	cout << BBB() << endl;
}

使用 decltype

还有一种情况,如果我们知道函数返回的指针将指向哪个数组,就可以使用decltype关键字声明返回类型。
例如,下面的函数返回一个指针,该指针根据参数i的不同指向两个已知数组中的某一个:
 

int odd[] = {1, 3,5,7,9};
int even[] = {0, 2, 4, 6, 8};
//返回一个指针,该指针指向含有5个整数的数组
decltype (odd) *arrPtr(int i)

{return (i %2) ? &odd: &even;// 返回一个指向数组的指针

}

使用关键字 decltype 表示它的返回类型是个指针,并且该指针所指的对象与odd的类型一致。

因为odd是数组,所以arrPtr返回一个指向含有5个整数的数组的指针。

有一个地方需要注意:decltype并不负责把数组类型转换成对应的指针,所以decltype的结果是个数组,要想表示arrPt返回指针还必须在函数声明时加一个*符号

我们再看一个例子

#include<iostream>
using namespace std;

int a = 9;
decltype(a) BB()
{
	int a = 9;
	return a;
}

int main()
{
cout<<BB()<<endl;//结果是9
}



 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1541217.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

手撕算法-接雨水

描述 分析 i位置能积累的雨水量&#xff0c;等于其左右两边最大高度的最小值。为了能获取i位置左右两边的最大高度。使用动态规划。两个dp数组&#xff1a; leftMaxrightMax 其中 leftMax[i] 代表i位置左边的最大高度rightMax[i] 代表i位置右边的最大高度 初始状态&#x…

新手装修:卫生间渗水原因及解决方法。福州中宅装饰,福州装修

引言 瓷砖渗水问题常常发生在卫生间区域&#xff0c;需要及时处理以免造成地面滑倒和墙面霉菌等问题&#xff0c;为了解决这一问题&#xff0c;我们应该怎么做呢&#xff1f; 首先要检查水管是否漏水&#xff0c;可以进行打压测试来确认是否存在漏水情况。如果发现水管破损造成…

php 快速入门(一)

一、配置系统环境 1.1 安装软件 1、安装php的开发软件&#xff1a;phpstorm 在这个软件中写代码 2、安装php的运行软件&#xff1a;phpstduy 写好的php程序需要放到phpstduy中&#xff0c;用户才能访问和测试 安装过程注意事项&#xff1a;安装的路径中不能有空格和中文字符&…

什么是 PDAF?它是如何工作的?相位检测自动对焦解释

常见问题解答 什么是相位对焦 PDAF 代表相位检测自动对焦。这是一种自动对焦方法,可以检测光线进入相机时的行进和交汇位置。在智能手机中,这是在传感器级别完成的。为了使物体聚焦,光线应该在同一点相遇。如果不这样做,系统将确定如何调整镜头以达到焦点。 PDAF 好用吗…

HTTP --- 下

目录 1. HTTP请求方式 1.1. HTML 表单 1.2. GET && POST方法 1.2.1. 用 GET 方法提交表单数据 1.2.2. 用 POST 方法提交表单数据 1.2.3. 总结 1.3. 其他方法 2. HTTP的状态码 2.1. 重定向 2.1.1. 临时重定向 && 永久重定向 2.1.2. 302 &&…

UE5 C++ 3D血条 响应人物受伤 案例

一.3Dwidget 1.创建C Userwidget的 MyHealthWidget&#xff0c;声明当前血量和最大血量 UCLASS() class PRACTICEC_API UMyHealthWidget : public UUserWidget {GENERATED_BODY() public:UPROPERTY(EditAnywhere,BlueprintReadWrite,Category "MyWidget")float C…

基于Springboot+Vue的在线考试系统!免费领取源码

今天给大家分享一套基于SpringbootVue的在线考试系统源码&#xff0c;在实际项目中可以直接复用。(免费提供&#xff0c;文末自取) 一、系统运行图 1、登陆页面 2、后台管理 3、全套环境资源 二、源码免费领取方式 关注本号&#xff0c;回复 考试 关注本号&#xff0c;回复…

【数据结构】快速排序(用递归)

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家了解快速排序&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 一. 基本思想二. 快速排序2.1 hoare版本2.2 挖坑法2.3 前后指针法2.4 快速排序优化三数取中法…

数据结构:堆和二叉树遍历

堆的特征 1.堆是一个完全二叉树 2.堆分为大堆和小堆。大堆&#xff1a;左右节点都小于根节点 小堆&#xff1a;左右节点都大于根节点 堆的应用&#xff1a;堆排序&#xff0c;topk问题 堆排序 堆排序的思路&#xff1a; 1.升序排序&#xff0c;建小堆。堆顶就是这个堆最小…

设计模式-访问者(Visitor)模式详解和应用

文章目录 前言访问者模式介绍结构包含的角色应用场景代码示例访问者模式的扩展访问者模式优缺点总结 前言 最近在做一个根据数学表达式生成java执行代码的功能&#xff0c;其中用到了访问者模式。使我对访问者模式有了更深入的理解。故写下此篇文章分享出来&#xff0c;不足之…

ios逆向某易新闻 md5+aes

本期的案例比较简单&#xff0c;也许是ios逆向算法本来就比较简单的原因&#xff0c;所以前面我就多扯一些爬虫和逆向的东西。之前写的文章都是js逆向和android逆向的案例&#xff0c;这也是首篇ios的案例&#xff0c;所以会从入门开始讲起。 3大逆向对比 首先爬虫工程师大部…

Objective-C—Class底层结构探索,真心分享给你!!!

isa 走位图 在讲 OC->Class 底层类结构之前&#xff0c;先看下下面这张图&#xff1a; 通过isa走位图 得出的结论是&#xff1a; 1&#xff0c;类&#xff0c;父类&#xff0c;元类都包含了 isa, superclass 2&#xff0c;对象isa指向类对象&#xff0c;类对象的isa指向了元…

C语言操作符和数据类型的存储详解

CSDN成就一亿技术人 目录​​​​​​​ 一.操作符 一.算数操作符&#xff1a; 二.位移操作符&#xff1a; 三.位操作符&#xff1a; 四.赋值操作符&#xff1a; 五.单目操作符&#xff1a; 六.关系操作符&#xff1a; 七.逻辑操作符&#xff1a; 八.条件操作符&…

Java学习笔记 | JavaSE基础语法05 | 方法

文章目录 0.前言1. 方法概述2. 方法的定义和调用2.1 无参数方法定义和调用2.2 带参数方法定义和调用1 带参数方法定义和调用2 形参和实参3 带参数方法练习 2.3 带返回值方法的定义和调用1 带返回值方法定义和调用2 带返回值方法练习13 带返回值方法练习24 带返回值方法练习3 3.…

V R元宇宙平台的未来方向|V R主题馆加 盟|游戏体验馆

未来&#xff0c;VR元宇宙平台可能会呈现出以下发展趋势和可能性&#xff1a; 全面融合现实与虚拟世界&#xff1a; VR元宇宙平台将更加无缝地融合现实世界和虚拟世界&#xff0c;用户可以在虚拟环境中进行各种活动&#xff0c;与现实世界进行互动&#xff0c;并且体验到更加逼…

C程序编译、链接与项目构建

C程序编译、链接与项目构建 摘要C编译环境静、动态库介绍gcc与g和程序编译、链接Visual Studio创建和链接库动态库的显示调用 Make介绍安装使用 CMake介绍安装使用构建方式内部构建外部构建构建使用静/动态库常用[系统]变量常用指令CMake模块 Make与CMake的联系与区别 摘要 本…

代码随想录|Day26|贪心01|455.分发饼干、376.摆动序列、53.最大子数组和

455.分发饼干 大尺寸的饼干既可以满足胃口大的孩子也可以满足胃口小的孩子。 局部最优&#xff1a;尽量确保每块饼干被充分利用 全局最优&#xff1a;手上的饼干可以满足尽可能多的孩子 思路&#xff1a;大饼干 尽量分给 大胃口孩子 将小孩和饼干数组排序&#xff0c;我们从大到…

40+重量级DFLab合成模型含各种神丹底丹万能模型合集分享

之前玩DFL软件积累下来的资源&#xff0c;部分模型非常稀缺&#xff0c;之前买的都很贵&#xff0c;现在不玩了&#xff0c;分享给有缘人&#xff0c;懂货的自然懂。必须懂得怎么用再下载&#xff0c;否则对你没有任何价值。点击下载 所见即所得。其中包含几个重量级稀缺资源&…

新款理想L7一边增配一边减配,难怪大家都去买华为问界

文 | AUTO芯球 作者 | 雷歌 我真是要被理想汽车笑死了&#xff0c;真不愧是“定语榜单之王”。 几年前理想汽车搞了一个“中国市场新势力品牌销量周榜” 两个定语&#xff0c;将比亚迪&#xff0c;特斯拉排除在外&#xff0c;自己在自己打造的榜单里做了一年多的冠军宝座。…

试题E(求阶乘)

解题思路&#xff1a; 写不出来&#xff0c;看的题解。要想凑个10&#xff0c;就必须要有一个2和5&#xff0c;但是明显在一个阶乘里&#xff0c;因子为2的数量一定多余5的数量&#xff0c;所以计算5的数量。 解题代码&#xff1a; import java.util.Scanner; ​ public clas…