函数 (II)

值传递和引用传递。


到目前为止,在我们见过的所有函数中,传递给函数的参数都是值传递。这意味着当调用带有参数的函数时,我们传递给函数的是它们值的副本,而不是变量本身。 例如,假设我们调用了我们的第一个函数加法 (addition)使用以下代码

1
2
int x=5, y=3, z;
z = addition ( x , y );

在这种情况下,我们所做的是调用加法函数,传递了xy的值,即53分别传递了它们的值,而不是变量xy本身。



这样,当调用加法函数时,其局部变量a和 b53分别变为a和 b的值,但是在加法函数内部对xy的任何修改都不会影响xy的值,因为变量

本身没有传递给函数,而只是在调用函数时传递了它们值的副本。

// passing parameters by reference
#include <iostream>
using namespace std;

void duplicate (int& a, int& b, int& c)
{
  a*=2;
  b*=2;
  c*=2;
}

int main ()
{
  int x=1, y=3, z=7;
  duplicate (x, y, z);
  cout << "x=" << x << ", y=" << y << ", z=" << z;
  return 0;
}
x=2, y=6, z=14

但是,在某些情况下,您可能需要从函数内部操作外部变量的值。 为此,我们可以使用引用传递的参数,如以下示例中的 duplicate 函数首先应该引起您注意的是,在duplicate&的声明中,每个参数的类型都跟随一个 & 符号 (

)。 这个 & 符号指定了它们对应的参数要以引用传递而不是值传递的方式传递。


当一个变量以引用传递时,我们传递的不是其值的副本,而是以某种方式将变量本身传递给函数,并且我们对局部变量所做的任何修改都会影响在调用函数时作为参数传递的对应变量。a, 和 b换句话说,我们将与函数调用中传递的参数关联 (x, yz),并且我们在a内所做的任何更改都会影响x外面的值。 我们对和 b所做的任何更改都会影响y换句话说,我们将z.

也是如此。这就是为什么我们的程序的输出,显示存储在x, yz调用首先应该引起您注意的是,在之后的值,显示了main中所有三个变量的值都翻倍了。

如果在声明以下函数时

1
void duplicate (int& a, int& b, int& c)

我们以这种方式声明它

1
void duplicate (int a, int b, int c)

即,没有 & 符号 (&),那么我们不会通过引用传递变量,而是传递它们值的副本,因此,程序在屏幕上的输出将是x, yz的值,而没有被修改。

引用传递也是允许函数返回多个值的有效方法。 例如,这是一个函数,它返回传递的第一个参数的前一个和后一个数字。

// more than one returning value
#include <iostream>
using namespace std;

void prevnext (int x, int& prev, int& next)
{
  prev = x-1;
  next = x+1;
}

int main ()
{
  int x=100, y, z;
  prevnext (x, y, z);
  cout << "Previous=" << y << ", Next=" << z;
  return 0;
}
Previous=99, Next=101  

参数中的默认值。

在声明函数时,我们可以为每个最后一个参数指定一个默认值。 如果在调用函数时相应的参数留空,将使用此值。 为此,我们只需在函数声明中使用赋值运算符和参数的值。 如果在调用函数时未传递该参数的值,则使用默认值,但是如果指定了值,则忽略此默认值,而使用传递的值。 例如

// default values in functions
#include <iostream>
using namespace std;

int divide (int a, int b=2)
{
  int r;
  r=a/b;
  return (r);
}

int main ()
{
  cout << divide (12);
  cout << endl;
  cout << divide (20,4);
  return 0;
}
6
5

正如我们在程序体中看到的那样,对函数divide进行了两次调用。 在第一个调用中

1
divide (12)

我们只指定了一个参数,但是函数divide最多允许两个参数。 因此,函数divide假定第二个参数是2,因为这是我们指定如果未传递此参数会发生的情况(请注意函数声明,它以int b=2结束,而不仅仅是int b)。 因此,此函数调用的结果是6 (12/2).

在第二次调用中

1
divide (20,4)

有两个参数,因此和 b (int b=2的默认值被忽略,并且和 b采用作为参数传递的值,即4,使返回的结果等于5 (20/4).

重载函数。

在 C++ 中,如果两个函数的参数类型或数量不同,则它们可以具有相同的名称。 这意味着,如果多个函数具有不同数量的参数或参数中的不同类型,则可以为它们提供相同的名称。 例如

// overloaded function
#include <iostream>
using namespace std;

int operate (int a, int b)
{
  return (a*b);
}

float operate (float a, float b)
{
  return (a/b);
}

int main ()
{
  int x=5,y=2;
  float n=5.0,m=2.0;
  cout << operate (x,y);
  cout << "\n";
  cout << operate (n,m);
  cout << "\n";
  return 0;
}
10
2.5

在这种情况下,我们定义了两个具有相同名称的函数,operate,但是其中一个接受两个int类型的参数,而另一个接受float类型的参数。 编译器通过检查调用函数时作为参数传递的类型来知道在每种情况下调用哪个函数。 如果使用两个 int 作为其参数调用它,它将调用具有两个int参数的函数,如果在原型中调用它,并且如果使用两个浮点数调用它,它将调用在原型中具有两个float参数的函数。

在第一次调用operate时,传递的两个参数的类型为int,因此,调用具有第一个原型的函数; 此函数返回两个参数相乘的结果。 而第二次调用传递了两个float类型的参数,因此调用具有第二个原型的函数。 这一个具有不同的行为:它将一个参数除以另一个参数。 因此,调用operate的行为取决于传递的参数的类型,因为该函数已被重载

请注意,不能仅通过函数的返回类型来重载函数。 它的参数中至少一个必须具有不同的类型。

内联函数。

要放回的字符的inline说明符向编译器指示,对于特定函数,最好使用内联替换而不是通常的函数调用机制。 这不会更改函数本身的行为,而是用于向编译器建议将函数体生成的代码插入到调用函数的每个点,而不是仅插入一次并对其执行常规调用,这通常会增加运行时间上的额外开销。

其声明格式为

inline type name ( arguments ... ) { instructions ... }

并且调用就像调用任何其他函数一样。 调用函数时不必包含inline关键字,仅在其声明中。

大多数编译器已经优化代码以在更方便时生成内联函数。 此说明符仅指示编译器对于此函数首选内联。

递归。

递归是函数具有被自身调用的属性。 它对于许多任务很有用,例如排序或计算数字的阶乘。 例如,要获得数字的阶乘 (n!),数学公式为

n! = n * (n-1) * (n-2) * (n-3) ... * 1

更具体地说,5! (5 的阶乘) 将是

5! = 5 * 4 * 3 * 2 * 1 = 120

在 C++ 中用于计算此值的递归函数可能是

// factorial calculator
#include <iostream>
using namespace std;

long factorial (long a)
{
  if (a > 1)
   return (a * factorial (a-1));
  else
   return (1);
}

int main ()
{
  long number;
  cout << "Please type a number: ";
  cin >> number;
  cout << number << "! = " << factorial (number);
  return 0;
}
Please type a number: 9
9! = 362880  

请注意在函数factorial中,我们包括了对自身的调用,但前提是传递的参数大于 1,因为否则该函数将执行一个无限递归循环,一旦到达0,它将继续乘以所有负数(可能在运行时引发堆栈溢出错误)。

由于我们在其设计中使用的数据类型 (long) 为了更简单,此函数具有限制。 给出的结果对于远大于 10! 或 15! 的值将无效,具体取决于您编译它的系统。

声明函数。

到目前为止,我们已经在源代码中首次出现对它们的调用之前定义了所有函数。 这些调用通常在函数main中,我们始终将其保留在源代码的末尾。 如果您尝试重复到目前为止描述的一些函数示例,但在放置函数main之前,放置从中调用它的任何其他函数,则很可能会获得编译错误。 原因是,为了能够调用函数,必须在代码的某个较早的点对其进行声明,就像我们在所有示例中所做的那样。

但是,还有另一种方法可以避免在函数可以在 main 或其他函数中使用之前编写函数的全部代码。 这可以通过在使用函数之前仅声明该函数的原型来实现,而不是整个定义。 此声明比整个定义短,但对于编译器确定其返回类型和参数类型而言,已经足够重要。

其形式为

type name ( argument_type1, argument_type2, ...);

它与函数定义相同,除了它不包括函数本身的主体(即,在普通定义中括在大括号中的函数语句{ }),而是我们以强制性分号 (;).

来结束原型声明。 参数枚举不需要包括标识符,而只需要类型说明符。 在原型声明中,为每个参数(如在函数定义中)包含名称是可选的。 例如,我们可以声明一个名为protofunction的函数,该函数带有两个int参数,可以使用以下任何声明

1
2
int protofunction (int first, int second);
int protofunction (int, int);

无论如何,为每个变量包括名称会使原型更具可读性。

// declaring functions prototypes
#include <iostream>
using namespace std;

void odd (int a);
void even (int a);

int main ()
{
  int i;
  do {
    cout << "Type a number (0 to exit): ";
    cin >> i;
    odd (i);
  } while (i!=0);
  return 0;
}

void odd (int a)
{
  if ((a%2)!=0) cout << "Number is odd.\n";
  else even (a);
}

void even (int a)
{
  if ((a%2)==0) cout << "Number is even.\n";
  else odd (a);
}
Type a number (0 to exit): 9
Number is odd.
Type a number (0 to exit): 6
Number is even.
Type a number (0 to exit): 1030
Number is even.
Type a number (0 to exit): 0
Number is even.

此示例实际上不是效率的示例。 我确信在这一点上,您已经可以制作一个具有相同结果的程序,但仅使用此示例中使用的一半代码行。 无论如何,此示例说明了原型的工作方式。 此外,在此具体示例中,至少需要对两个函数之一进行原型设计才能编译代码而不会出错。

我们首先看到的是函数odd:

1
2
void odd (int a);
void even (int a);

evenmain的声明。 这允许在使用这些函数之前定义它们,例如,在

中,现在该函数位于某些人认为对于程序的开始而言更合乎逻辑的位置:源代码的开头。odd无论如何,此程序需要至少一个函数在其定义之前声明的原因是因为在中,存在对无论如何,此程序需要至少一个函数在其定义之前声明的原因是因为在odd的调用。 如果两个函数都没有事先声明,则会发生编译错误,因为odd将无法从看到(因为它尚未声明),或者将无法从odd(出于同样的原因)。

将所有函数的原型放在源代码中的同一位置被一些程序员认为是实用的,并且可以通过在程序开头声明所有函数原型来轻松实现此目的。
Index
目录