• 【C++】超详细入门——详解函数返回类型


    ⚓️作者简介:即将大四的北京某能源高校学生。

    📚座右铭:“九层之台,起于垒土” 。所以学习技术须脚踏实地。

    📖这里推荐一款刷题、模拟面试神器,可助你斩获大厂offer:点我免费刷题、模拟面试


    函数是一个命名了的代码块,我们通过调用函数指向相应的代码,函数通常具有参数,对参数进行操作后会产生相应的作用或结果。我们可以使用return语句终止当前正在执行的函数并将控制权返回到调用该函数的地方。
    根据函数是否有返回值,可将函数分为有返回值的函数和无返回值的函数。

    无返回值的函数

    无返回值的函数没有返回值,所以可以没有 return 语句或仅有 return; 来结束函数的执行。如果没有 return 语句,函数会在最后一句后面隐式地执行 return,return; 也可以在函数中间,用于提前结束函数的执行。

    void swap(int &v1, int &v2){
    	if(v1 == v2)
    		return ; //程序提前退出,类似循环中的break。
    	int tmp = v2;
    	v2 = v1;
    	v1 = tmp;
    	//这里隐式执行return;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这个函数首先检查值是否相等,相等则直接退出函数,不相等则进行交换,最后一句赋值语句后面隐式执行return语句。

    void 函数也可以使用 return expression;这种形式的语句,不过 expression 需要是一个返回 void 的函数,返回其他类型将会编译出错。

    有返回值的函数

    有返回值的函数使用 return expression;语句返回值,返回值不能为void。expression 的类型必须与函数类型相同,或者能隐式转换为函数类型。

    C++无法确定结果的正确性,但是可以保证每个return语句的结果类型相同。
    与无返回值的函数一样,返回类型与函数类型不同的话,编译器将会检测出这个错误

    如何返回值

    C++ 中从函数返回(不考虑异常、 longjmp 等非正常退出方式)的过程是个稍微有点长的话题…,这里我也是略知一二,很浮浅地简介一下。

    首先初始化返回值,返回值的过程和返回类型有关:

    • 返回类型为void 。则这步什么也不做。
    • 返回类型为引用。则这步进行一次引用绑定,而此绑定不会延长临时对象生存期。故如果绑定到了临时对象(以及默认情况下函数内定义的局部对象)则必然会产生悬垂引用(后面还会提到)。
    • 返回类型为对象类型,则这步有不同的情况:
      • 一般从 return 的操作数复制初始化产生一个纯右值,语义上通常没有临时对象。
      • 如果 return 的操作数是一个忽略顶层 const 后与函数返回类型相同的,非 static ,且不是形参的局部变量,则标准允许编译器令这个局部变量与函数返回的结果最终形成的对象成为同一对象。这里最终形成的对象可能是用函数返回值初始化的同类型变量,或者函数返回值形成的临时对象等等。
      • 如果 return 的操作数是一个对象类型或右值引用类型的变量,包括形参,但不是 static,则首先把该操作数当作右值(即优先进行移动操作);如果不能选择正确的函数,再当作左值。

    初始化完返回值后,依次进行以下操作:

    1. 按创建顺序的逆序析构 return 语句中产生的临时对象;
    2. 按通常离开作用域规则的规则,析构其他函数体内(非 static)的局部变量。(这里“析构”的说法不太严谨,对于非类类型可以认为就是使对象不再存在了。)

    在标准语义中,控制已经离开了该函数,函数调用表达式的类型和值类别按以下方式确定:

    • 若函数是析构函数或返回 void 的函数,则表达式为 void 类型纯右值。不考虑构造函数,因为不存在直接调用构造函数的函数调用表达式。
    • 若函数返回类型为左值引用或右值引用,则表达式分别为左值或亡值,类型为被引用的类型。
    • 若函数返回类型为对象类型,则表达式为纯右值,类型为返回类型;但如果返回类型不是类类型,则表达式类型需要去掉 cv 限定(例如即使函数返回类型是 const int ,函数调用表达式的类型也还是 int )。

    不要返回局部变量或临时量的引用或指针

    函数调用完成,函数体内的局部变量的内存被释放了,因为局部变量的生命期仅存在于函数的作用域。如果返回值的类型为引用或指针的话,该局部变量消失,调用函数得到的引用绑定的对象不再存在,即引用无效,或者调用函数得到的指针也将指向一个不存在的对象。此时引用与指针会变成垂悬引用或垂悬指针。

    const string &func(){
    	string result;
    	//操作result
    	if(result.empty())
    		return "empty";
    	else return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    上面两条语句都会返回未定义的值,第一条返回的是局部临时量的引用,第二条返回的是局部变量的引用,两者在函数调用完都会被释放,此时的引用就与一个空地址绑定,成了垂悬引用。

    使用尾置返回类型或decltype

    介绍尾置返回类型前先看一下返回一个指向数组的指针的函数:

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

    我们按照下面的顺序来理解这个函数声明:

    • func(int i) 表示调用该函数时需要传入一个int型的实参。
    • (*func(int i)) 表示函数返回的结果是一个指针。
    • (*func(int i))[10] 表示函数返回的指针指向一个含有十个元素的数组。
    • int (*func(int i))[10] 表示数组中的元素是int型。

    由此可见返回复杂类型的函数的定义是很麻烦的,一不小心就会出错,所以我们可以使用尾置返回类型或decltype来简化函数的声明方法。事实证明,这些方法用起来是很有效的。

    在本该出现函数类型的地方使用关键字 auto 替代,将函数真正的返回类型置于函数的形参列表之后,中间用 -> 连接,这样就是尾置返回类型声明函数的方法了。

    下面使用尾至返回类型声明上面的函数:

    auto func(int i)->int (*)[10];
    
    • 1

    这样就很简单明了,函数与返回类型分开,可以逐个击破,也不容易混淆。

    另外一个使用decltype的方法也能简化复杂函数的声明:

    int arr[10];
    decltype(arr) *func(int i);
    
    • 1
    • 2

    使用关键字decltype提取出arr的类型为含有十个元素的数组,func前面的 * 运算符表示func返回的是指针,所以func返回的类型就是指向含有十个元素的数组的指针。

    使用decltype同样可以将函数的返回类型提取出来,但需要一个变量来声明函数的返回类型,所以用起来就没有尾置返回类型明了。

    🎉总的来说本文讲解的函数返回类型的知识点对于入门的初学者是足够了,再深究的话需要较深厚的计算机组成的基础与足够的C++编程经验。博主争取努力学习,再将这些深入的知识浅显易懂地讲给大家。


    希望大家多多关注,三连支持。你们的支持是我源源不断创作的动力。

  • 相关阅读:
    Redis 常见问题
    ESP32网络开发实例-使用MQTT进行消息订阅与发布
    Vue中axios拦截器怎么使用
    Ubuntu22.04 交叉编译GCC13.2.0 for Rv1126
    threejs的阴影
    go实现复杂度与简单排序算法
    数据库设计规范
    Python深度学习:融合网络 | LSTM网络和ResNet网络融合 | 含随机生成的训练数据集
    2023NOIP A层联测9-天竺葵
    使用 TiUP 部署 TiDB 集群
  • 原文地址:https://blog.csdn.net/weixin_45773137/article/details/126575767