C++ Primer 第六章 函数

oneNeko 于 2020-05-27 发布

这一章主要介绍函数的定义和声明、函数重载和函数指针。
函数其实就是一个命名了的代码块,我们通过调用函数执行相应的代码。

6.1 函数基础

一个典型的函数 (function) 定义包含以下部分:返回类型 (return type)、函数名字、形参 (parameter)以及函数体。形参以逗号隔开,形参的列表位于一对圆括号内。函数执行的操作语句块中说明,该语句块成为函数体。

函数的调用完成两项工作:传递实参初始化函数对应的形参、控制权转移给被调函数。
随后被调函数(隐式)定义并初始化它的形参,然后执行。
当遇到return时,函数执行结束,返回return语句的值(如果有的话),将控制权转移给主调函数

实参是形参的初始值,实参依据形参列表顺序依次初始化形参,注意 实参的求值顺序由编译器决定,和形参列表顺序无关, 实参必须与形参的类型相匹配,数量一致,类型不同时,会尝试隐式转换类型,失败则无法调用。

形参列表可以为空,但是不能省略;形参通常以逗号隔开,每个形参都是一个声明,形参不允许同名

void f1(){}
void f2(void){} //void兼容C语言
int f3(int v1,int v2){}
char* f4(char* v1){}

函数的返回类型可以是void,它表示不返回任何值。返回类型不能是数组或者函数类型,但可以是指向数组或者函数的指针。

6.1.1 局部对象

C++中,名字有 作用域,对象有 生命周期(lifetime)

函数体是一个语句块,块内构成新的作用域。形参和函数内部定义的变量统称为 局部变量(local variable),仅在函数的作用域里可见。
在所有函数体外定义的对象存在于程序的整个执行过程中,启动时创建,结束时销毁。 局部变量的生命周期依赖于定义的方式

只存在于块执行期间的对象成为 自动对象(automatic object),块执行结束之后,块中创建的自动对象的就变成未定义了。
形参是一种自动对象,函数开始时为形参申请空间,结束时销毁。

size_t count_calls(){
  static size_t ctr = 0;
  return ++ctr;
}
int main(){
  for (size_t i = 0;i != 10; ++i){
    cout<< count_calls() << endl;
  }
  return 0;
}

会输出1-10的数字
如果局部静态变量没有显式的初始值,他将执行值初始化,内置类型的局部静态变量初始化为0

6.1.2 函数声明

函数的名字必须在使用之前声明,类似于变量。函数只能定义一次,但能声明多次 唯一例外是如虚函数。函数声明如:

void print(vector<int>::const_iterator beg
           vector<int>::const_iterator end);

函数三要素(返回类型,函数名,形参类型)描述了函数的接口,说明了调用该函数所需要的所有信息。函数声明也称作 函数原型(function prototype)
建议变量、函数在头文件里声明

6.1.3 分离式编译

为了允许编写程序时按照逻辑关系划分,C++支持 分离式编译,允许我们把程序分割到几个文件中,每个文件单独编译。
如果我们修改了其中一个源文件,那么只需要重新编译那个改动了的文件。大部分编译器编译时通常会产生后缀名为.obj(Windows)或.o(UNIX)的文件,后缀名的含义时该文件包含对象代码(object code)。随后编译器将对象文件链接在一起形成可执行文件。编译过程如下:

$ CC -c factMain.cc #生成 factMain.o
$ CC -c fact.cc #生成 fact.o
$ CC factMain.o fact.o -o main #生成 main 或者 main.exe

6.2 参数传递

函数在被调用时,都会重新创建形参,并用传入的实参进行初始化。

6.2.1 传值参数

当初始化一个非引用类型的变量时,初始值被拷贝给对象,此时,对变量的改动不会影响初始值。

6.2.2 传引用参数

引用的操作实际上是作用在引用所引的对象上,如

int n = 0, i = 42;
int &r = n; //r绑定了n,相当于别名
r = 42; //现在n的值是42
r = n;
i = r;
void reset(int &i){ // i是传给reset函数的对象的另一个名字
  i = 0; //改变了i所引对象的值
}

此时,被改变的对象是传入的实参,在reset内部对i的使用即是对实参的使用

6.2.3 const 形参和实参

顶层const作用于对象本身,当实参初始化形参时会忽略掉顶层const。当形参有顶层const时,传递常量或者非常量都是可以的,但是某些时候会产生意想不到的结果

string::size_type find_char(string &s){};

调用时如find_char("hello")会出错

数组形参

数组不允许拷贝,因此通常是传递数组首元素的指针。虽然不能以值传递的方式传递数组,但我们可以把形参写成类似数组的方式

//尽管形式不同,但这3个print函数是等价的
//每个函数都有一个const int* 的形参
void print(const int*);
void print(const int[]);
void print(const int[10]); //实际只是const int*,只用于描述,实际‘10’并无意义

当编译器处理print调用时,只检查传入参数是否是const int*类型 在处理数组长度时,需要提供额外信息

数组引用形参

C++允许将变量定义成数组的引用,所以,形参也可以时数组的引用。此时引用形参绑定到对应的实参上,即绑定到数组上

void print(int (&arr)[10]){ //arr是具有10个整数的整型数组的引用
  for (auto elem : arr){
    cout << elem << endl;
  }
}
// ()必不可少
// f(int &arr[10]); 错误,arr成了引用的数组

传递多维数组

C++实际上并没有真正的多维数组,多维数组其实是数组的数组 所以传递的参数依然是数组首元素地址

void print(int (*matrix)[10],int rowsize); //指向含有10个整数的数组的指针
// ()不可或缺,缺少的话就成了指针数组

等价定义

void print(int matrix[][10],int rowsize);

6.2.5 main: 处理命令行选项

main函数如

int main(int argc,char *argv[]){...}
//argc为数组中字符串数量,argv是数组

注意 argv[0]保存的是程序的名字,并非用户输入

6.2.6 含有可变形参的函数

有时我们无法提前预知应该像函数传递几个实参,参见可变形参模板

void foo(pam_list,...);
void foo(...);

6.3 返回类型和return语句

return语句终止当前正在执行的函数并将控制权返回给调用该函数的地方。

6.3.1 无返回值函数

没有返回值的return语句只能用于返回类型是void的函数。可以省略,函数会隐式调用return;

6.3.2 有返回值函数

有返回值的return语句只能用于返回类型对应的值,允许隐形转换。 返回一个值的方式和初始化一个变量的方式完全一样。 如果函数返回引用,则该引用仅是它引对象的一个别名,不会真正拷贝对象。