5.1 返回值

优质
小牛编辑
129浏览
2023-12-01

前面我们用过的一些内置函数(如数学函数)都会生成结果值。也就是说,调用函数的效果是产生一个新值,一般我们会把这个值赋给变量,或用作表达式的一部分。例如:

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变量的绝对值。