第七章 函数(part1) 函数的定义

7.1. 函数的定义

函数由函数名以及一组操作数类型唯一地表示。函数的操作数,也即形参,在一对圆括号中声明,形参与形参之间以逗号分隔。

函数执行的运算在一个称为函数体的块语句中定义。每一个函数都有一个相关联的返回类型

考虑下面的例子,这个函数用来求出两个 int 型数的最大公约数:

     // return the greatest common divisor
     int gcd(int v1, int v2)
     {
         while (v2) {
             int temp = v2;
             v2 = v1 % v2;
             v1 = temp;
         }
         return v1;
     }

这里,定义了一个名为 gcd 的函数,该函数返回一个 int 型值,并带有两个 int 型形参。

调用 gcd 函数时,必须提供两个 int 型值传递给函数,然后将得到一个 int 型的返回值。

函数的调用

C++ 语言使用调用操作符(即一对圆括号)实现函数的调用。正如其他操作符一样,调用操作符需要操作数并产生一个结果。

调用操作符的操作数是函数名和一组(有可能是空的)由逗号分隔的实参

函数调用的结果类型就是函数返回值的类型,该运算的结果本身就是函数的返回值:

     // get values from standard input
     cout << "Enter two values: \n";
     int i, j;
     cin >> i >> j;
     // call gcd on arguments i and j
     // and print their greatest common divisor
     cout << "gcd: " << gcd(i, j) << endl;

如果给定 15 和 123 作为程序的输入,程序将输出 3。

函数调用做了两件事情:用对应的实参初始化函数的形参,并将控制权转移给被调用函数。

主调函数的执行被挂起,被调函数开始执行。函数的运行以形参的(隐式)定义和初始化开始。

也就是说,当我们调用 gcd 时,第一件事就是创建名为 v1v2int 型变量,并将这两个变量初始化为调用gcd 时传递的实参值。

在上例中,v1 的初值为 i,而 v2 则初始化为 j 的值。


函数体是一个作用域
函数体是一个语句块,定义了函数的具体操作。通常,这个块语句包含在一对花括号中,形成了一个新的作用域。
和其他的块语句一样,在函数体中可以定义变量。在函数体内定义的变量只在该函数中才可以访问。
这种变量称为 局部变量,它们相对于定义它们的函数而言是“局部”的,其名字只能在该函数的作用域中可见。这种变量只在函数运行时存在。

当执行到 return 语句时,函数调用结束。被调用的函数完成时,将产生一个在 return 语句中指定的结果值。
执行 return 语句后,被挂起的主调函数在调用处恢复执行,并将函数的返回值用作求解调用操作符的结果,继续处理在执行调用的语句中所剩余的工作。


形参和实参

类似于局部变量,函数的形参为函数提供了已命名的局部存储空间。

它们之间的差别在于形参是在函数的形参表中定义的,并由调用函数时传递函数的实参初始化。

实参则是一个表达式。它可以是变量或字面值常量,甚至是包含一个或几个操作符的表达式。

在调用函数时,所传递的实参个数必须与函数的形参个数完全相同。

与初始化式的类型必须与初始化对象的类型匹配一样,实参的类型也必须与其对应形参的类型完全匹配:实参必须具有与形参类型相同、或者能隐式转换为形参类型的数据类型。

7.1.1. 函数返回类型

函数的返回类型可以是内置类型(如 int 或者 double)、类类型或复合类型(如 int&string*),还可以是 void 类型,表示该函数不返回任何值。

下面的例子列出了一些可能的函数返回类型:

     bool is_present(int *, int);       // returns bool
     int count(const string &, char);   // returns int
     Date &calendar(const char*);       // returns reference to Date
     void process();                    // process does not return a value

函数不能返回另一个函数或者内置数组类型,但可以返回指向函数的指针,或指向数组元素的指针的指针:

     // ok: pointer to first element of the array
     int *foo_bar() { /* ... */ }
这个函数返回一个 int 型指针,该指针可以指向数组中的一个元素。

函数必须指定返回类型

在定义或声明函数时,没有显式指定返回类型是不合法的:

     // error: missing return type
     test(double v1, double v2) { /* ... */ }

早期的 C++ 版本可以接受这样的程序,将 test 函数的返回类型隐式地定义为 int 型。但在标准 C++ 中,上述程序则是错误的。

<Note>:

在 C++ 标准化之前,如果缺少显式返回类型,函数的返回值将被假定为 int 型。

早期未标准化的 C++ 编译器所编译的程序可能依然含有隐式返回 int 型的函数。

7.1.2. 函数形参表

函数形参表可以为空,但不能省略。没有任何形参的函数可以用空形参表或含有单个关键字 void 的形参表来表示。例如,下面关于process 的声明是等价的:

     void process() { /* ... */ }      // implicit void parameter list

     void process(void){ /* ... */ }  // equivalent declaration

形参表由一系列用逗号分隔的参数类型和(可选的)参数名组成。如果两个参数具有相同的类型,则其类型必须重复声明:

     int manip(int v1, v2) { /* ... */ }      // error
     int manip(int v1, int v2) { /* ... */ }  // ok

参数表中不能出现同名的参数。类似地,局部于函数的变量也不能使用与函数的任意参数相同的名字。

参数名是可选的,但在函数定义中,通常所有参数都要命名。参数必须在命名后才能使用。

参数类型检查
<Note>:

C++ 是一种静态强类型语句,对于每一次的函数调用,编译时都会检查其实参。

调用函数时,对于每一个实参,其类型都必须与对应的形参类型相同,或具有可被转换为该形参类型的类型。函数的形参表为编译器提供了检查实参需要的类型信息。例如,第 7.1 节定义的gcd 函数有两个int 型的形参:

     gcd("hello", "world"); // error: wrong argument types
     gcd(24312);            // error: too few arguments
     gcd(42, 10, 0);        // error: too many arguments
以上所有的调用都会导致编译时的错误。在第一个调用中,实参的类型都是 const char*,这种类型无法转换为 int 型,因此该调用不合法。

而第二和第三个调用传递的实参数量有误。在调用该函数时必须提供两个实参,实参数太多或太少都是不合法的。

如果两个实参都是 double 类型,又会怎样呢?调用是否合法?

     gcd(3.14, 6.29);      // ok: arguments are converted to int

在 C++中,答案是肯定的:该调用合法!正如第 5.12.1 节所示,double 型的值可以转换为int 型的值。

本例中的函数调用正涉及了这种转换——用 double 型的值来初始化 int 型对象。因此,把该调用标记为不合法未免过于严格。

更确切地说,(通过截断)double 型实参被隐式地转换为 int 型。由于这样的转换可能会导致精度损失,大多数编译器都会给出警告。

对于本例,该调用实际上变为:

     gcd(3, 6);
返回值是 3。

调用函数时传递过多的实参、忽略某个实参或者传递错误类型的实参,几乎肯定会导致严重的运行时错误!

对于大程序,在编译时检查出这些所谓的接口错误(interface error),将会大大地缩短“编译-调试-测试”的周期。













评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值