2-4 函数形参与实参介绍
在上节课中,我们了解到函数可以向调用者返回值。我们运用这一知识创建了模块化的 getValueFromUser 函数,并在本程序中使用了它:
#include <iostream>
int getValueFromUser()
{
std::cout << "Enter an integer: ";
int input{};
std::cin >> input;
return input;
}
int main()
{
int num { getValueFromUser() };
std::cout << num << " doubled is: " << num * 2 << '\n';
return 0;
}
然而,如果我们也想把输出行放入它自己的函数中呢?你可以尝试如下写法:
#include <iostream>
int getValueFromUser()
{
std::cout << "Enter an integer: ";
int input{};
std::cin >> input;
return input;
}
// This function won't compile
void printDouble()
{
std::cout << num << " doubled is: " << num * 2 << '\n';
}
int main()
{
int num { getValueFromUser() };
printDouble();
return 0;
}
这段代码无法编译,因为函数 printDouble 不知道 num 是哪个标识符。你可以尝试在函数 printDouble() 内部将 num 定义为变量:
void printDouble()
{
int num{}; // we added this line
std::cout << num << " doubled is: " << num * 2 << '\n';
}
虽然这解决了编译器报错问题,使程序能够编译通过,但程序仍无法正常运行(始终输出“0 doubled is: 0”)。问题的核心在于函数printDouble无法获取用户输入的数值。
我们需要某种方式将变量num的值传递给函数printDouble,以便printDouble能在函数体内使用该值。
函数形参与实参
在许多情况下,能够向被调用的函数传递信息非常有用,这样函数就能获得可操作的数据。例如,若要编写一个求和函数,我们需要在调用时告知函数应加和的两个数值。否则函数如何知道该加和什么?我们通过函数形参与实参来实现这一目的。
翻译说明:
英文 专业翻译 说明 parameter 形参(形式参数) 函数定义时的占位符变量 argument 实参(实际参数) 函数调用时传入的具体值
函数形参function parameter是在函数头部使用的变量。函数形参的工作方式与函数内部定义的变量几乎完全相同,但存在一个区别:它们由函数调用方提供的值进行初始化。
函数参数通过在函数名后的括号内定义,多个参数用逗号分隔。
以下是具有不同形参数量的函数示例:
// This function takes no parameters
// It does not rely on the caller for anything
void doPrint()
{
std::cout << "In doPrint()\n";
}
// This function takes one integer parameter named x
// The caller will supply the value of x
void printValue(int x)
{
std::cout << x << '\n';
}
// This function has two integer parameters, one named x, and one named y
// The caller will supply the value of both x and y
int add(int x, int y)
{
return x + y;
}
实参argument是在函数调用时由调用方传递给函数的值:
doPrint(); // this call has no arguments
printValue(6); // 6 is the argument passed to function printValue()
add(2, 3); // 2 and 3 are the arguments passed to function add()
请注意,多个实参也用逗号分隔。
形参与实参如何协同工作
当函数被调用时,该函数的所有形参都会被创建为变量,每个实参的值会被复制到对应的形参中(使用复制初始化)。这个过程称为值传递pass by value。采用值传递的函数形参称为值传递参数value parameters。
例如:
#include <iostream>
// This function has two integer parameters, one named x, and one named y
// The values of x and y are passed in by the caller
void printValues(int x, int y)
{
std::cout << x << '\n';
std::cout << y << '\n';
}
int main()
{
printValues(6, 7); // This function call has two arguments, 6 and 7
return 0;
}
当函数 printValues 被调用并传入实参 6 和 7 时,printValues 的形参 x 被创建并初始化为值 6,printValues 的形参 y 被创建并初始化为值 7。
这将产生以下输出:

请注意,实参数量通常必须与函数形参数量匹配,否则编译器将报错。传递给函数的实参可以是任何有效的表达式(因为实参本质上只是形参的初始化器,而初始化器可以是任何有效的表达式)。
修复我们的挑战程序
现在我们拥有了修复本课开头展示的程序所需的工具:
#include <iostream>
int getValueFromUser()
{
std::cout << "Enter an integer: ";
int input{};
std::cin >> input;
return input;
}
void printDouble(int value) // This function now has an integer parameter
{
std::cout << value << " doubled is: " << value * 2 << '\n';
}
int main()
{
int num { getValueFromUser() };
printDouble(num);
return 0;
}
在此程序中,变量 num 首先被初始化为用户输入的值。随后调用函数 printDouble,并将实参 num 的值复制到函数 printDouble 的 value 形参中。函数 printDouble 随后使用形参 value 的值。
将返回值用作实参
在上面的问题中,我们可以看到变量 num 仅被使用一次,用于将函数 getValueFromUser 的返回值传递给函数 printDouble 的调用实参。
我们可以将上述示例稍作简化如下:
#include <iostream>
int getValueFromUser()
{
std::cout << "Enter an integer: ";
int input{};
std::cin >> input;
return input;
}
void printDouble(int value)
{
std::cout << value << " doubled is: " << value * 2 << '\n';
}
int main()
{
printDouble(getValueFromUser());
return 0;
}
现在,我们直接将函数 getValueFromUser 的返回值作为实参传递给函数 printDouble!
虽然这个程序更简洁(且明确表明用户输入的值不会用于其他用途),但你可能会觉得这种“紧凑语法”有些难以阅读。如果你更习惯使用变量版本的写法,那也没问题。
形参与返回值如何协同工作
通过同时使用形参和返回值,我们可以创建函数来接收数据作为输入,对其进行计算,并将结果返回给调用方。
下面是一个非常简单的函数示例,它将两个数字相加并返回结果给调用方:
#include <iostream>
// add() takes two integers as parameters, and returns the result of their sum
// The values of x and y are determined by the function that calls add()
int add(int x, int y)
{
return x + y;
}
// main takes no parameters
int main()
{
std::cout << add(4, 5) << '\n'; // Arguments 4 and 5 are passed to function add()
return 0;
}
执行从 main 函数顶部开始。当 add(4, 5) 被求值时,函数 add 被调用,形参 x 初始化为值 4,形参 y 初始化为值 5。
函数 add 中的 return 语句将 x + y 求值为 9,该值随后返回给 main。此值 9 随后被发送到 std::cout 以在控制台打印。
输出:

以图示形式:

更多示例
让我们再看看一些函数调用:
#include <iostream>
int add(int x, int y)
{
return x + y;
}
int multiply(int z, int w)
{
return z * w;
}
int main()
{
std::cout << add(4, 5) << '\n'; // within add() x=4, y=5, so x+y=9
std::cout << add(1 + 2, 3 * 4) << '\n'; // within add() x=3, y=12, so x+y=15
int a{ 5 };
std::cout << add(a, a) << '\n'; // evaluates (5 + 5)
std::cout << add(1, multiply(2, 3)) << '\n'; // evaluates 1 + (2 * 3)
std::cout << add(1, add(2, 3)) << '\n'; // evaluates 1 + (2 + 3)
return 0;
}
该程序输出结果如下:

第一条语句很直白。
在第二条语句中,实参是需要先求值再传递的表达式。具体来说,1 + 2 计算结果为 3,因此 3 被复制到形参 x 中。3 * 4 计算结果为 12,因此 12 被复制到形参 y 中。add(3, 12) 最终求解为 15。
接下来的两条语句同样相对简单:
int a{ 5 };
std::cout << add(a, a) << '\n'; // evaluates (5 + 5)
在此情况下,add() 被调用时,a 的值会被复制到两个形参 x 和 y 中。由于 a 的值为 5,add(a, a) = add(5, 5),最终结果为 10。
现在让我们看看这组语句中的第一个棘手案例:
std::cout << add(1, multiply(2, 3)) << '\n'; // evaluates 1 + (2 * 3)
当执行add函数时,程序需要确定形参x和y的值。x的处理很简单,因为我们直接传入了整数1。而要获取参数y的值,则需要先计算multiply(2, 3)。程序调用 multiply 函数,初始化 z = 2 和 w = 3,因此 multiply(2, 3) 返回整数值 6。该返回值 6 现可用于初始化 add 函数的 y 形参。add(1, 6) 返回整数 7,该值随后传递给 std::cout 进行打印。
简化表述:
add(1, multiply(2, 3)) 求值为 add(1, 6) 求值为 7
下述语句看似复杂,因为add函数的实参之一是另一个add调用。
std::cout << add(1, add(2, 3)) << '\n'; // evaluates 1 + (2 + 3)
但此案例与前例完全相同。add(2, 3) 首先求解,得到返回值 5。此时可求解 add(1, 5),其计算结果为 6,该值被传递给 std::cout 进行打印。
更简洁的表达:
add(1, add(2, 3)) 求值为 add(1, 5) => 结果为 6
未引用的参数与未命名的形参
在某些情况下,你会遇到函数中存在未在函数体内使用的形参。这些参数被称为未引用参数unreferenced parameters。
一个简单的例子:
void doSomething(int count) // warning: unreferenced parameter count
{
// This function used to do something with count but it is not used any longer
}
int main()
{
doSomething(4);
return 0;
}

与未使用的局部变量类似,编译器可能会发出警告,指出变量 count 已被定义但未被使用。
在函数定义中,函数形参的名称是可选的。因此,当函数形参需要存在但未在函数体内使用时,可以直接省略其名称。没有名称的形参称为无名参数unnamed parameter:
void doSomething(int) // ok: unnamed parameter will not generate warning
{
}

谷歌C++风格指南建议使用注释来记录未命名的形参是什么:
void doSomething(int /*count*/)
{
}
作者注
你可能好奇为何要编写一个参数值未被使用的函数。这种情况最常出现在类似以下的情形中:
- 假设我们有一个带单一参数的函数。后来函数经过某种更新,该参数的值不再需要。如果直接移除这个现已废弃的函数参数,那么所有现有的函数调用都会失效(因为函数调用提供的参数数量超过了函数能接受的范围)。这将迫使我们查找所有函数调用并移除多余参数,可能耗费大量精力(且需要反复测试)。在无法掌控所有调用代码的情况下,这种操作甚至可能无法实现。因此我们通常选择保留该参数,使其不执行任何操作。
对于高级读者
其他出现此情况的场景:
++ 和 -- 运算符存在前缀与后缀变体(例如 ++foo 与 foo++)。通过未引用的函数形参来区分此类运算符重载属于前缀还是后缀情况。相关内容将在第 21.8 课——重载增量与减量运算符中讲解。
当需要根据类型模板参数的类型(而非值)来判断某些情况时。
作者注
若您仍对无名参数unnamed parameters感到困惑,请不必担心。在后续课程中我们将再次遇到它们,届时将结合更多上下文说明其适用场景。
最佳实践:
当函数形参存在但未在函数体内使用时,请勿为其命名。可选择性地在注释中添加名称。
结论
函数形参和返回值是实现函数可重用性的关键机制,它使我们能够编写执行特定任务的函数,并将获取或计算的结果返回给调用方,而无需事先了解具体的输入或输出内容。
测验时间
问题 #1
这段程序代码有什么问题?
#include <iostream>
void multiply(int x, int y)
{
return x * y;
}
int main()
{
std::cout << multiply(4, 5) << '\n';
return 0;
}
显示答案
multiply() 的返回类型为 void,这意味着它是一个不返回值的函数。由于该函数试图返回一个值(通过 return 语句),因此会引发编译器错误。其返回类型应为 int。
问题 #2
这个程序片段存在哪两个问题?
#include <iostream>
int multiply(int x, int y)
{
int product { x * y };
}
int main()
{
std::cout << multiply(4) << '\n';
return 0;
}
显示答案
问题1:main()向multiply()传递了一个实参,但multiply()需要两个实参。
问题2:multiply()没有返回语句。
问题 #3
下列程序会输出什么值?
#include <iostream>
int add(int x, int y, int z)
{
return x + y + z;
}
int multiply(int x, int y)
{
return x * y;
}
int main()
{
std::cout << multiply(add(1, 2, 3), 4) << '\n';
return 0;
}
显示答案
乘法运算被调用时,x = add(1, 2, 3),y = 4。首先,CPU解析x = add(1, 2, 3),该运算返回1 + 2 + 3,即x = 6。乘法运算multiply(6, 4) = 24,即为最终结果。
问题 #4
编写一个名为 doubleNumber() 的函数,该函数接受一个整数形参。函数应返回形参值的两倍。
显示答案
int doubleNumber(int x)
{
return 2 * x;
}
问题 #5
- 编写一个完整的程序:从用户处读取一个整数,使用你在前一问题中编写的 doubleNumber() 函数将其值翻倍,然后将翻倍后的值打印到控制台。

显示答案
#include <iostream>
int doubleNumber(int x)
{
return 2 * x;
}
int main()
{
std::cout << "Enter an integer value: ";
int x{};
std::cin >> x;
std::cout << doubleNumber(x) << '\n';
return 0;
}
注:您可能还会想到其他(类似的)解决方案。在C++中,实现相同功能的方法往往多种多样。

浙公网安备 33010602011771号