企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
前面我们用过的一些内置函数(如数学函数)都会生成结果值。也就是说,调用函数的效果是产生一个新值,一般我们会把这个值赋给变量,或用作表达式的一部分。例如: ~~~ double e = exp(1.0); double height = radius * sin(angle); ~~~ 但到目前为止,我们编写的所有函数都是**void**函数,它们不返回任何值。调用void函数时,常见的是函数调用语句本身占一行,没有赋值操作: ~~~ nLines(3); countdown(n-1); ~~~ 本章我们将学习编写带有返回值的函数,因为没有更好的名字,我索性直接称之为“**有返回值的函数**”。第一个例子是area函数,它以一个double值为参数,返回以给定参数值为半径的圆的面积: ~~~ double area(double radius) { double pi = acos(-1.0); double area = pi * radius * radius; return area; } ~~~ 首先要注意到,该函数定义的开始部分与void函数(如果以“void”开始,则说明这是void函数)不同,这里使用了double,说明函数返回double类型的值。 再就是注意最后一行,这是return语句的一种可选形式,它带了一个返回值。这句话的意思是,”以其后的表达式为返回值,立即从函数返回。“表达式可以非常复杂,所以area函数可以简化为: ~~~ double area(double radius) { return acos(-1.0) * radius * radius; } ~~~ 另一方面,像area这样的**临时变量**会使调试更容易。不管哪种情况,return语句中表达式的类型必须与函数的返回类型匹配。换句话说,当把函数的返回类型声明为double时,就要保证函数最终会得到一个double值。如果不返回任何表达式,或者返回了类型不匹配的表达式,编译器都会报错。 有时包含多个返回语句是有用的,比如每个分支一个: ~~~ double absoluteValue(double x){ if (x < 0) { return -x; } else { reurn x; } } ~~~ 这些return语句分布在不同的条件分支中,只有一个能执行。虽然函数可以有多个return语句,但是只要其中一个执行,函数也就随之结束了,不会再执行后面的语句。 return语句后面的代码,或任何不可能执行到的代码,称为“**死代码**”。如果存在死代码,有的编译器会给出警告。 如果return语句在一个条件分支中,必须保证每个可能的路径都能碰到return语句。例如: ~~~ double absoluteValue(double x) { if(x < 0) { return –x; } else if(x > 0) { return x; } // 错误 } ~~~ 这个函数是错误的,因为当x为0的时候,所有条件都不满足,最终函数找不到相应的return语句。非常不幸,很多C++编译器并不捕捉此类错误,程序可以通过编译并运行,但是当x==0时返回值可能是任意值,而且在不同环境下也可能有不同表现。 现在你可能还是很讨厌看到编译错误信息,但是随着经验的增长,你会意识到:当程序有错误时,不出现编译错误会比出现更糟糕。 有时会有这样的事情,你用一些值测试了absoluteValue函数,而且该函数看起来是可以正常工作的,可是当你把程序交给别人在其他环境下测试时,却出现了不可思议的bug,经过几天的调试你才发现absoluteValue的实现有问题。要是编译器能早发现问题并警告你该多好啊! 从现在开始,如果编译器指出了程序中的错误,请不要抱怨编译器。相反,你应该感谢编译器帮你找出错误,而且节约了你数天的调试时间。有的编译器可以通过选项指定更严格的编译检查并报告所有错误。你要一直开着这些选项。 说句题外话,math库中的fabs函数能够正确计算double变量的绝对值。