C++ Primer 返回值和return语句

欢迎阅读我的 【C++Primer】专栏

专栏简介:本专栏主要面向C++初学者,解释C++的一些基本概念和基础语言特性,涉及C++标准库的用法,面向对象特性,泛型特性高级用法。通过使用标准库中定义的抽象设施,使你更加适应高级程序设计技术。希望对读者有帮助!

在这里插入图片描述
在这里插入图片描述

目录

  • 6.3 返回类型和return语句
    • 无返回值函数
    • 有返回值函数
    • 值是如何被返回的
    • 不要返回局部对象的引用或指针
    • 引用返回左值
    • 列表初始化返回值
    • 主函数main的返回值
    • 返回数组指针
    • 使用尾置返回类型
    • 使用decltype

6.3 返回类型和return语句

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

return ;
return expression;

无返回值函数

没有返回值的return语句只能用在返回类型是void的函数中。返回void的函数不要求非得有return语句,因为在这类函数的最后一句后面会隐式地执行return。

通常情况下,void函数如果想在它的中间位置提前退出,可以使用return语句。return的这种用法有点类似于我们用break语句退出循环。例如,可以编写一个swap函数,使其在参与交换的值相等时什么也不做直接退出:

void swap(int& v1,int&v2)
{
//如果两个值是相等的,则不需要交握,直接退出
if(vl==v2)return;//如果程序执行到了这里,说明还需要继续完成树些功能int tmp=v2;v2=v1;v1=tmp;//此处无须显式的return语句
}

这个函数首先检查值是否相等,如果相等直接退出函数;如果不相等才交换它们的值。在最后一条赋值语句后面隐式地执行return。

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

有返回值函数

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

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

//因为含有不正确的返回值,所以这段代码无法通过编译
bool str_subrange(const string&str1,const string&str2)
{
//大小相同:此时用智通的相等性判断结果作为返回值
if(str1.size()==str2.size())return str1==str2;//正确:==运算符返回布尔值//得到较短string对象的大小,条件运算符。auto size=(str1.size() < str2.size())?str1.size():str2.size();//检查两个strtng对象的对应字符是否相等,以较短的字符串长度为限
for(decltype(size)i=0;i!=size;++){if(str1[i]==str2[i])return;//错误##1:没有返回值,编译器将报告这一错误
}//错误 #2:控制流可能尚未返回任何值就结束了函数的执行
//编译器可能检查不出这一错误
}

for循环内的return语句是错误的,因为它没有返回值,编译器能检测到这个错误。第二个错误是函数在for循环之后没有提供return语句。在上面的程序中,如果一个string对象是另一个的子集,则函数在执行完for循环后还将继续其执行过程,显然应该有一条return语句专门处理这种情况。编译器也许能检测到这个错误,也许不能;如果编译器没有发现这个错误,则运行时的行为将是未定义的。

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

值是如何被返回的

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

必须注意当函数返回局部变量时的初始化规则。例如我们书写一个函数,给定计数值、单词和结束符之后,判断计数值是否大于1:如果是,返回单词的复数形式;如果不是,返回单词原形:

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

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

同其他引用类型一样,如果函数返回引用,则该引用仅是它所引对象的一个别名。举个例子来说明,假定树函数挑出两个string形参中较短的那个并返回其引用:

//挑出两个string对象中较短的那个,返回其引用
const string& shorterString(const string&sl,const string&e2)
{return(ctr>1)?word+ending:word;return s1.size()<=s2.size()?s1:s2;
}

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

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

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

//严重错误:这个函数试图返回局部对象的引用
const string&manip()
{
string ret;
//以某种方式改变一下ret
if(ret.empty())return ret;//错误:返回局部对象的引用!
elsereturn "Empty";//错误:“Empty“是一个局部临时量
)

上面的两条return语句都将返回未定义的值,也就是说,试图使用manip函数的返回值将引发未定义的行为。对于第一条return语句来说,显然它返回的是局部对象的引用。在第二条return语句中,字符串字面值转换成一个局部临时string对象,对于manip来说,该对象和ret一样都是局部的。当函数结束时临时对象占用的空间也就随之释放掉了,所以两条return语句都指向了不再可用的内存空间。

要想确保返回值安全,我们不妨提问:引用所引的是在函数之前已经存在的哪个对象?

如前所述,返回局部对象的引用是错误的;同样,返回局部对象的指针也是错误的。-旦函数完成,局部对象被释放,指针将指向一个不存在的对象。返回类类型的函数和调用运算符和其他运算符一样,调用运算符也有优先级和结合律。调用运算符的优先级与点运算符和箭头运算符相同,并且也符合左结合律。因此,如果函数返回指针、引用或类的对象,我们就能使用函数调用的结果访间结果对象的成员。例如,我们可以通过如下形式得到较短string对象的长度:

//调用string对象的size成员,该string对象是由shorterString函数返回的
auto sz=shorterString(s1,s2).size();

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

引用返回左值

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

char &get_val(string&str,string::size_type ix)
{return str[ix];//get_val假定素引值是有效的
}
int main()
{string s("a value");cout << s << endl;//输出avalueget_val(s,0)='A';//将s[0]的值改为cout << s << endl;//输出Avaluereturn 0;
}把函数调用放在赋值语句的左侧可能看起来有点奇怪,但其实这没什么特别的。返回值是引用,因此调用是个左值,和其他左值一样它也能出现在赋值运算符的左侧。如果返回类型是常量引用,我们不能给调用的结果赋值,这一点和我们熟悉的情况是-样的:```cpp
shorterString("hi","bye")="X";//错误;返回值是个常量

列表初始化返回值

C++11新标准规定,函数可以返回花括号包围的值的列表。类似于其他返回结果,此处的列表也用来对表示函数返回的临时量进行初始化。如果列表为空,临时量执行值初始化;否则,返回的值由函数的返回类型决定。举个例子,error_msg函数,该函数的输入是一组可变数量的string实参,输出由这些string对象组成的错误信息。在下面的函数中,我们返回一个vector对象,用它存放表示错误信息的string对象:

vector<string> process()
{// ...//expected和actual是string对象if(expected.empty())return {};else if (expected == actual)return{"functionXm","okay"};//返回列表初始化的vector对象elsereturn{"EunctionX",expected,actual};
}

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

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

主函数main的返回值

之前介绍过,如果函数的返回类型不是void,那么它必须返回一个值。但是这条规则有个例外:我们允许main函数没有return语句直接结束。如果控制到达了main函数的结尾处而且没有return语句,编译器将隐式地插入一条返回0的return语句。

main函数的返回值可以看做是状态指示器。返回0表示执行成功,返回其他值表示执行失败,其中非0值的具体含义依机器而定。为了使返回值与机器无关,cstdlib头文件定义了两个预处理变量,我们可以使用这两个变量分别表示成功与失败:

int main()
{
if(some_failure)return EXIT_FAILRUE;//定义在cstdlib头文件中
else:return EXIT_SUCCESS;//定义在cstdlib头文件中
}因为它们是预处理变量,所以既不能在前面加上std::,也不能在using声明中出现。递归如果一个函数调用了它自身,不管这种调用是直接的还是问接的,都称该函数为递归出数(recursive function)。举个例子,我们可以使用递归函数重新实现求阶乘的功能:```cpp
//计算val的阶乘,即1*2*3…*val
int factorial(int val)
{if(val>1)return factorial(val-1)*val;return 1;
}

在上面的代码中,我们递归地调用factorial函数以求得从val中减去1后新数字的阶乘。当val递减到1时,递归终止,返回1。

在递归函数中,一定有树条路径是不包含递归调用的;否则,函数将"永远"递归下去,换句话说,函数将不断地调用它自身直到程序栈穿间耗尽为止。我们有时候会说这种函数含有递归循环(recursion loop)。在factorial函数中,递归终止的条件是val等于1。

下面的表格显示了当给factorial函数传入参数5时,函数的执行轨迹。

factorial(5)的执行轨迹

调用返回
factorial(5)factorial(4)*5120
factorial(4)factorial(3)*424
factorial(3)factorial(2)*36
factorial(2)factorial(1)*22
factorial(1)11

main函数不能调用它自己。

返回数组指针

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

typedef int arrT[10];//arrT是一个类型别名,它表示的类型是含有10个//整数的数组
using arrT=int[10];     //arrT的等价声明
arrT *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]

类似于其他数组的声明,Tipe表示元素的类型,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))[10]表示数组中的元素是int类型。

使用尾置返回类型

在C++11新标准中还有一种可以简化上述func声明的方法,就是使用尾置返回类型(trailing return type)。任何函数的定义都能使用尾置返回,但是这种形式对于返回类型比较复杂的函数最有效,比如返回类型是数组的指针或者数组的引用。尾置返回类型跟在形参列表后面并以一个->符号开头。为了表示函数真正的返回类型跟在形参列表之后,我们在本应该出现返回类型的地方放置一个auto:

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

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

使用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;//返回一个指向数组的指针   
}arrPtr使用关键字decltype表示它的返回类型是个指针,并且该指针所指的对象与odd的类型一致。因为odd是数组,所以arrPtr返回一个指向含有5个整数的数组的指针。有一个地方需要注意:decltype并不负责把数组类型转换成对应的指针,所以decltype的结果是个数组,要想表示arrPtr返回指针还必须在函数声明时加一个*符号。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

dma_ddr 的编写 通过mig控制ddr3

此外还有别的模块 本模块是 其中一个 timescale 1ns/1ps module dma_ctrl (input wire ui_clk , //100MHZ 用户时钟input wire ui_rst_n ,//写fifo的写端口 input wire wf_wr_clk , //由数据产生模块的时…

【15】思科AireOS:创建使用 PSK 认证的 WLAN

1. 概述 在 Cisco AireOS 无线局域网控制器(WLC)上,您可以配置基于预共享密钥(PSK)的 WLAN,以提供无线访问。PSK 认证是一种 WPA2/WPA3 个人模式下常用的认证方式,适用于家庭或小型企业环境。 本指南将详细介绍如何在 Cisco AireOS WLC 上配置 PSK 认证的 WLAN,并确保…

基于css实现正六边形的三种方案

方案一&#xff1a;通过旋转三个长方形生成正六边形 分析&#xff1a; 如下图所示&#xff0c;我们可以通过旋转三个长方形来得到一个正六边形。疑问&#xff1a; 1. 长方形的宽高分别是多少&#xff1f; 设正六边形的边长是100&#xff0c;基于一些数学常识&#xff0c;可以…

Python用PyMC3马尔可夫链蒙特卡罗MCMC对疾病症状数据贝叶斯推断

全文链接&#xff1a;https://tecdat.cn/?p39937 本文聚焦于马尔可夫链蒙特卡罗&#xff08;MCMC&#xff09;方法在贝叶斯推断中的Python实现。通过介绍MCMC的基础原理、在贝叶斯推断中的应用步骤&#xff0c;展示了其在解决复杂分布采样问题上的强大能力。同时&#xff0c;借…

linux--关于makefile

makefile文件 可以指定编译顺序&#xff0c;这样方便一个项目的多个文件要编译的挨个操作的麻烦。 makefile文件的命名&#xff1a;makefile 或者 Makefile 必须是这俩&#xff0c;系统才能识别 规则的书写语法如下&#xff1a; 一个makefile内可以有多个规则 目标:依赖a 依…

何须付费免费它不香吗

聊一聊 又是一年开学季。 开学了发一些应时期的小软件。 今天给大家分享一款学校班级课程表工具。 这款工具可以投放在学校电子大屏上。 支持学校的白板软件。 软件介绍 学校班级课程表 工具界面清爽&#xff0c;信息能一目了然。 虽然看感觉功能简单&#xff0c;但每个…

day10后期软件

美图秀秀&#xff1a;美拍拼图、Facetune&#xff1a;面部重塑、泼辣修图&#xff1a;手机中的PS Lightroom&#xff1a;精致调色 想法类&#xff1a; 玩创意特效 PicsArt:很强大 排版类&#xff1a;精美杂志风 MOLDIV&#xff1a;无损排版&#xff1b;天天P图&#xff1a…

用大模型学大模型03-数学基础 概率论 条件概率 全概率公式 贝叶斯定理

要深入浅出地理解条件概率与贝叶斯定理&#xff0c;可以从以下几个方面入手&#xff0c;结合理论知识和实例进行学习&#xff1a; 贝叶斯定理与智能世界的暗语 条件概率&#xff0c;全概率公式与贝叶斯公式的推导&#xff0c;理解和应用 拉普拉斯平滑 贝叶斯解决垃圾邮件分类 …

Calico网络组件本地部署支持IPv6(Kubernetes)

知其然 问题背景 因项目现场的网络正逐步从IPv4向IPv6迁移&#xff0c;这几年现场服务器基本上都配置了双栈&#xff1b;但随着IPv6铺开&#xff0c;出现了很多纯IPv6的服务器&#xff0c;并且要求通信优先使用IPv6。 在项目建设之初&#xff0c;其实就考虑了上述情况&#…

【Python】函数

个人主页&#xff1a;Guiat 归属专栏&#xff1a;Python 文章目录 1. 函数的定义1.1 基本定义方式1.2 函数名和参数 2. 函数的调用2.1 基本调用方式2.2 参数传递 3. 函数的返回值3.1 return 语句3.2 返回多个值 4. 函数的作用域4.1 局部变量4.2 全局变量 5. 匿名函数&#xff0…

PbootCMS增加可允许上传文件类型,例如webp、mov等文件格式扩展

在PbootCMS日常使用过程中&#xff0c;会涉及一些非常见的文件格式上传。 这时候就需要在PbootCMS配置文件中追加一些允许上传文件扩展名。 操作步骤 1、打开/config/config.php文件&#xff0c;大约在30行&#xff0c;修改upload配置信息&#xff1a; // 上传配置upload &…

EasyRTC视频通话WebP2P技术:轻量化SDK助力嵌入式设备实时音视频通信

在智能硬件井喷式发展的当下&#xff0c;嵌入式设备对实时音视频通信的需求正从实验室走向千行百业。然而&#xff0c;当开发者尝试将传统RTC&#xff08;实时通信&#xff09;方案移植到MCU、边缘计算终端等资源受限设备时&#xff0c;往往会遭遇一道难以逾越的屏障——Flash存…

Vript-Hard——一个基于高分辨率和详细字幕的视频理解算法

一、概述 多模态学习的最新进展促进了对视频理解和生成模型的研究。随之而来的是&#xff0c;对高分辨率视频和详细说明所建立的高质量数据集的需求激增。然而&#xff0c;由于时间因素的影响&#xff0c;视频与文本的配对不像图像那样容易。准备视频和文本配对是一项困难得多…

如何调整 Nginx工作进程数以提升性能

&#x1f3e1;作者主页&#xff1a;点击&#xff01; Nginx-从零开始的服务器之旅专栏&#xff1a;点击&#xff01; &#x1f427;Linux高级管理防护和群集专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2025年2月15日14点20分 Nginx 的工作进程数&#xff0…

SolidWorks速成教程P3-6【零件 | 第六节】——草图封闭轮廓所选轮廓厚度为零的报错

到这里,我们已经将特征成型的常用功能学完。这节我来继续讲解一下 SolidWorks中建模的一些容易忽略的问题(草图封闭轮廓&所选轮廓&厚度为零的报错)。 目录 1.草图封闭轮廓 2.所选轮廓 3.厚度为零的报错 1.草图封闭轮廓 我们在拉伸凸台时,一般都会绘制封闭的草…

关于post和get的请求参数问题

今天在和泓宇交流的时候&#xff0c;谈到了关于postman测试接口的问题。我昨天在postman测试的时候&#xff0c;对于条件查询不知道怎么测试&#xff0c;脑子里很混乱。今天&#xff0c;泓宇借着条件查询这个机会给我讲了讲get和post的请求参数的知识&#xff0c;趁着现在有记忆…

UE5中的四元数

UE5中的四元数 绕任意轴旋转四元数与矩阵四元数与欧拉角将一个向量旋转到另一个向量插值Reference 我们知道&#xff0c;四元数是除了欧拉角&#xff0c;旋转矩阵之外&#xff0c;主要用来描述旋转的量。四元数直观的定义就是 q [ c o s ( θ 2 ) , s i n ( θ 2 ) N ] q [c…

AUTOSAR MCAL层ETH模块(1)——通信原理

基础了解 ETH&#xff0c;以太网&#xff0c;简单来讲就是将几台设备连接起来形成网络。这种连接是点到点之间的。以太网的传输速率为10M~10G&#xff0c;速度非常快。 为了实现网络通信的标准化&#xff0c;普及网络应用&#xff0c;国际标准化组织(ISO)将整个以太网通信结构制…

什么是网关?网关有什么作用?API网关的主要功能,SpringCloud可以选择有哪些API网关?什么是限流算法?网关如何实现限流?一篇文章读懂网关的前世今生

1、什么是网关&#xff1f; API网关&#xff08;API Gateway&#xff09;是一种中间层服务器&#xff0c;用于集中管理&#xff0c;保护和路由对后端服务的访问。它充当了客户端与后端服务之间的入口点&#xff0c;提供了一组统一的接口管理和控制API的访问。 2、网关示意图 3…

【技术产品】DS三剑客:DeepSeek、DataSophon、DolphineSchduler浅析

引言 在大数据与云原生技术快速发展的时代&#xff0c;开源技术成为推动行业进步的重要力量。本文将深入探讨三个备受瞩目的开源产品组件&#xff1a;DeepSeek、DataSophon 和 DolphinScheduler&#xff0c;分别从产品定义、功能、技术架构、应用场景、优劣势及社区活跃度等方面…