C++函数返回机制,返回类型
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的原因:
- 当调用函数B时,它在栈上分配了局部变量b,并将其初始化为5。
- 接下来,创建了一个指向b的指针c。
- 函数B返回这个指针c,即返回了b的地址。
- 在main函数中,你接收了这个返回的指针并将其存储在g中。
- 通过解引用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
}
相关文章:

C++函数返回机制,返回类型
return语句终止当前正在执行的函数并将控制权返回到调用该函数的地方。 return语句有两种形式 return;return expression; 无返回值函数 没有返回值的return语句只能用在返回类型是void的函数中。 返回void的函数不要求必须有return语句,因为这类函数的最后一句…...
[linux] Key is stored in legacy trusted.gpg keyring
修复 Ubuntu 中的 “Key is stored in legacy trusted.gpg keyring” 问题_key is stored in legacy trusted.gpg keyring (/etc/-CSDN博客 复制到trusted.gpd.d 目录中(快速但不优雅的方法) 如果你觉得手动做上面的事情不舒服,那么,你可以忽略这个警告…...
阿里云部署OneApi
基于 Docker 进行部署 # 使用 SQLite 的部署命令: docker run --name one-api -d --restart always -p 3000:3000 -e TZAsia/Shanghai -v /home/ubuntu/data/one-api:/data justsong/one-api # 使用 MySQL 的部署命令,在上面的基础上添加 -e SQL_DSN&qu…...

MapReduce学习问题记录
1、如何跳过对某行数据的处理 第一行数据是字段名不需要处理,我们知道第一行偏移量是0(行记录的时候是从数组首地址开始,到了行标识符进行一次计数,这个计数就是行偏移量,从0开始),我们根据偏移…...
Elasticsearch优化
集群配置 1、调整副本数:考虑数据的可用性和读取性能,合理配置分片的副本数。 2、合理配置分片大小(分片的合理容量:10GB-50GB):避免分片过大,以确保更好的性能和均衡的负载。 3、监控集群状态:使用监控工…...

【Redis知识点总结】(六)——主从同步、哨兵模式、集群
Redis知识点总结(六)——主从同步、哨兵模式、集群 主从同步哨兵集群 主从同步 redis的主从同步,一般是一个主节点,加上多个从节点。只有主节点可以接收写命令,主节点接收到的写命令,会同步给从节点&#…...
Java面试题:设计一个线程安全的单例模式,并解释其内存占用和垃圾回收机制;使用生产者消费者模式实现一个并发安全的队列;设计一个支持高并发的分布式锁
Java深度面试题:设计模式、内存管理与并发编程的综合考察 随着Java技术的不断发展,对Java开发者的技术要求也在不断提高。设计模式、内存管理、多线程工具类以及并发工具包和框架等都是Java开发者必须掌握的核心知识点。本文将通过三道综合性的面试题&a…...

【硬件设计】以立创EDA举例——持续更新
【硬件设计】以立创EDA举例——持续更新 文章目录 前言立创EDA官网教程一、原理图二、PCB1.布局2.设计规则3.电流与线宽 4.PCB走线5.Polar Si90006.过孔7.铺铜总结 前言 提示:以下是本篇文章正文内容,下面案例可供参考 立创EDA官网教程 立创EDA使用教程…...

Chain of Note-CoN增强检索增强型语言模型的鲁棒性
Enhancing Robustness in Retrieval-Augmented Language Models 检索增强型语言模型(RALMs)在大型语言模型的能力上取得了重大进步,特别是在利用外部知识源减少事实性幻觉方面。然而,检索到的信息的可靠性并不总是有保证的。检索…...
Uniapp 的 uni.request传参后端
以下是使用Uniapp的交互数据的两种方式 后端使用Parameter接收数据 后端使用RequestBody接收Json格式数据 后端: CrossOrigin RestController RequestMapping("/user") public class UserController {GetMapping("/login")public String lo…...

数据可视化-ECharts Html项目实战(5)
在之前的文章中,我们学习了如何设置滚动图例,工具箱设置和插入图片。想了解的朋友可以查看这篇文章。同时,希望我的文章能帮助到你,如果觉得我的文章写的不错,请留下你宝贵的点赞,谢谢 数据可视化-ECharts…...

C++学习之旅(二)运行四个小项目 (Ubuntu使用Vscode)
如果是c语言学的比较好的同学 可以直接跟着代码敲一遍,代码附有详细语法介绍,不可错过 一,猜数字游戏 #include <iostream> #include <cstdlib> #include <ctime>int main() {srand(static_cast<unsigned int>(tim…...

数据分析与挖掘
数据起源: 规模庞大,结构复杂,难以通过现有商业工具和技术在可容忍的时间内获取、管理和处理的数据集。具有5V特性:数量(Volume):数据量大、多样性(Variety):…...

Maxwell监听mysql的binlog日志变化写入kafka消费者
一. 环境: maxwell:v1.29.2 (从1.30开始maxwell停止了对java8的使用,改为为11) maxwell1.29.2这个版本对mysql8.0以后的缺少utf8mb3字符的解码问题,需要对原码中加上一个部分内容 :具体也给大家做了总结 : 关于v1.…...
Kafka系列之:Kafka Connect REST API
Kafka系列之:Kafka Connect REST API 由于 Kafka Connect 旨在作为服务运行,因此它还提供了用于管理连接器的 REST API。此 REST API 可在独立模式和分布式模式下使用。可以使用侦听器配置选项来配置 REST API 服务器。该字段应包含以下格式的侦听器列表: protocol://host:p…...

DC-4靶机
一.环境搭建 1.下载地址 靶场下载地址:https://download.vulnhub.com/dc/DC-4.zip 下载不下来用迅雷下载 2.虚拟机配置 切换为nat模式 开启靶机,遇到所有的错误直接点重试或者是,开启后呈现为下图即可 二.开始渗透 1.信息收集 老规矩,…...

ideaSSM 高校公寓交流员管理系统bootstrap开发mysql数据库web结构java编程计算机网页源码maven项目
一、源码特点 idea 开发 SSM 高校公寓交流管理系统是一套完善的信息管理系统,结合SSM框架和bootstrap完成本系统,对理解JSP java编程开发语言有帮助系统采用SSM框架(MVC模式开发),系统具有完整的源代码和数据库&…...
Android studio添加阿里云仓库
在工程的settings.gradle中添加 repositories { google() jcenter() mavenCentral() maven { url https://jitpack.io } //阿里云镜像 maven { url https://maven.aliyun.com/repository/google } maven { url https:…...
每天一个数据分析题(二百二十)
在集成学习的GBDT算法中,每次训练新的决策树的目的是( )? A. 预测原始数据的标签 B. 预测上一个模型的残差 C. 降低模型的偏差 D. 降低模型的方差 题目来源于CDA模拟题库 点击此处获取答案...

Centos上安装Harbor并使用
harbor的安装与使用 Harbor介绍安装前的准备工作为Harbor自签发证书安装Harbor安装docker开启包转发功能和修改内核参数安装harbor扩展 Harbor 图像化界面使用说明测试使用harbor私有镜像仓库从harbor仓库下载镜像 Harbor介绍 容器应用的开发和运行离不开可靠的 镜像管理&…...

华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...

linux之kylin系统nginx的安装
一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源(HTML/CSS/图片等),响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址,提高安全性 3.负载均衡服务器 支持多种策略分发流量…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

ETLCloud可能遇到的问题有哪些?常见坑位解析
数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...

如何更改默认 Crontab 编辑器 ?
在 Linux 领域中,crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用,用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益,允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...
HybridVLA——让单一LLM同时具备扩散和自回归动作预测能力:训练时既扩散也回归,但推理时则扩散
前言 如上一篇文章《dexcap升级版之DexWild》中的前言部分所说,在叠衣服的过程中,我会带着团队对比各种模型、方法、策略,毕竟针对各个场景始终寻找更优的解决方案,是我个人和我司「七月在线」的职责之一 且个人认为,…...
深入理解 React 样式方案
React 的样式方案较多,在应用开发初期,开发者需要根据项目业务具体情况选择对应样式方案。React 样式方案主要有: 1. 内联样式 2. module css 3. css in js 4. tailwind css 这些方案中,均有各自的优势和缺点。 1. 方案优劣势 1. 内联样式: 简单直观,适合动态样式和…...
13.10 LangGraph多轮对话系统实战:Ollama私有部署+情感识别优化全解析
LangGraph多轮对话系统实战:Ollama私有部署+情感识别优化全解析 LanguageMentor 对话式训练系统架构与实现 关键词:多轮对话系统设计、场景化提示工程、情感识别优化、LangGraph 状态管理、Ollama 私有化部署 1. 对话训练系统技术架构 采用四层架构实现高扩展性的对话训练…...

华为OD机考- 简单的自动曝光/平均像素
import java.util.Arrays; import java.util.Scanner;public class DemoTest4 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint[] arr Array…...

使用homeassistant 插件将tasmota 接入到米家
我写一个一个 将本地tasmoat的的设备同通过ha集成到小爱同学的功能,利用了巴法接入小爱的功能,将本地mqtt转发给巴法以实现小爱控制的功能,前提条件。1需要tasmota 设备, 2.在本地搭建了mqtt服务可, 3.搭建了ha 4.在h…...