第七章 函数
7.1 函数的定义
函数由函数名以及一组操作数类型 唯一地表示,函数的操作数,即形参,在一对圆括号中声明,形参与形参之间以逗号分隔,函数执行的运算在一个称为函数体 的块语句中定义,每一个函数都有一个相关联的 返回类型
函数调用:C++语言使用调用操作符 (call operator,即一对圆括号)实现函数调用,调用操作符,需要操作数并产生一个结果,调用操作符的操作数 是 函数名和一组(有可能是空的)由逗号分隔的实参,函数调用的结果类型是函数返回值的类型,该运算的结果本身就是函数的返回值,函数调用做了两件事情:用对应的实参初始化函数的形参,并将控制权转移给被调函数,主调函数的执行被挂起,被调函数开始执行
函数体是一个作用域:函数体是一个语句块,定义了函数的具体操作,这个块语句包含在一对花括号中,形成了一个新的作用域
形参和实参: 函数的形参为函数提供了已命名的局部存储空间,形参是在函数的形参表中定义的,并由调用函数时传递给函数的实参初始化,实参则是一个表达式,可以是变量或字面值常量,甚至是包含一个或几个操作符的表达式
7.1.1 函数返回类型
函数的返回类型可以是内置类型、类类型 或 复合类型(int&, string*),还可以是void类型,函数不能返回另一个函数或者内置数组类型,但可以返回指向函数的指针,或 指向数组元素的指针,函数必须指定返回类型
7.1.2 函数形参表
函数形参表可以为空,但不能省略,没有任何形参的函数可以用空形参表或含有单个关键字void的形参表来表示,形参表由一系列用逗号分隔的 参数类型 和(可选的)参数名 组成,如果两个参数具有相同的类型,则其类型必须重复声明,参数名是可选的,但在函数定义中,通常所有参数都要命名,参数必须在命名后才能使用
调用函数时,对于一个实参,其类型都必须与对应的形参类型相同,或具有可被转换为该形参类型的类型,调用函数时,传递过多的实参、忽略某个实参或传递错误类型的实参,会导致严重的运行时错误
7.2 参数的传递
7.2.1 非引用形参
普通的非引用类型的参数,通过复制对应的实参,实现初始化,函数并没有访问调用所传递的实参本身,因此不会修改实参的值
函数的形参可以是指针,此时将复制实参指针,与其他非引用类型的形参一样,该类形参的任何改变也仅作用于局部副本,如果函数将新指针值赋给形参,主调函数使用的实参指针的值没有改变,如果函数形参是非const类型的指针,则函数 可以通过指针实现赋值,修改指针所指向对象的值
如果函数使用非引用的 形参(const或非const),由于实参是 以副本的形式传递,因此,可以 给该函数传递const实参,也可以传递非const的实参
给函数传递实参,遵循变量初始化的规则,非引用类型的形参以相应的 实参的副本 初始化,对(非引用)形参的任何修改仅作用于局部副本,并不影响实参本身
不适宜使用非引用形参(复制实参)的情况:当需要 在函数中修改实参的值 时;当需要 以大型对象作为实参传递 时;当没有办法实现对象的复制 时
7.2.2 引用形参
为避免传递副本的开销,可将形参指定为引用类型,对引用类型的任何修改会直接影响实参本身,应将不需要修改相应实参的引用形参 定义为const引用
非const引用 形参不能是const实参,非const引用 实参不能是右值,非const引用 不能是需要转换类型的对象
#include <iostream> using std::cout; using std::endl; int main() { short x = 15; const short y = 12; short &rx = x; //正确 const short &ry1 = y; //正确, const 引用是 const对象 const short &r = 123.123;//正确, const 引用是 const对象 可以是右值 可以隐形转换 cout << rx << "," << ry1 << "," << r << endl; //short &rx = 10; //错误,非const引用不可以是右值 //int &rx = x; //错误,非const需要隐形转换 //short &ry2 = y; //错误,非const引用不可以是const对象 return 0; }
7.2.3 vector和其他容器类型的形参
函数不应该有vector或其他标准容器类型的形参,调用非引用vector形参的函数将会复制vector的每一个元素,为避免复制vector的角度出发,应考虑将形参声明为引用类型,事实上,C++程序员倾向于通过传递指向容器中需要处理的元素的迭代器来传递容器
7.2.4 数组形参
数组有两个特殊的性质:一是不能复制数组;二是使用数组名时,数组名会自动转化为指向其第一个元素的指针,因此,将数组形参直接定义为指针比使用数组语法定义更好,编译器忽略为任何数组形参指定的长度,实参是指向数组第一个元素的指针,形参复制的是这个指针的值,函数可以通过改变所指向的数组元素的值,不需要修改数组形参的元素时,函数应该将形参定义为 指向const对象的指针
如果形参是数组的引用,编译器不会将数组实参转化为指针,而是 传递数组的引用本身,编译器检查数组实参的大小与形参的大小是否匹配,传递任意大小的数组的引用形参,可以使用 函数模板,定义一个 非类型模板形参 的形式实现
#include <iostream> using std::cout; using std::endl; void f( const int (&arr)[3] ){ // 数组引用,编译器检查数组实参的大小是否与形参匹配 for(int i=0; i<3; ++i){ cout << arr[i] << ","; } } int main() { int arr[] = {1,2,3}; f( arr ); return 0; }
多维数组的传递:所谓多维数组实际是值数组的数组,和其他数组一样,多维数组以指向0号元素的指针方式传递,多维数组的元素本身就是数组,除了第一维以外的所有维的长度 都是 元素类型 的一部分,必须明确指定,实际上,形参是一个指针,指向数组的数组中的元素
7.3 return 语句
return 语句用于结束当前正在执行的函数,并将控制权返回给调用此函数的函数,return语句有两种形式:return;,return expression;
7.3.1 没有返回值的函数
不带返回值的return语句只能用于返回类型为void的函数,一般情况下,返回类型是void的函数使用return语句是为了引起函数的强制结束,隐式的return发生在最后一个语句后,返回类型为void的函数,可以返回另一个返回类型同样是void的函数的调用结果
7.3.2 具有返回值的函数
任何返回类型不是void的函数都必须返回一个值,而且这个返回值的类型必须和函数的返回类型相同,或者能隐式转化为函数的返回类型
允许主函数main没有返回值就结束,编译器会隐式地插入返回0的语句,返回0表示程序运行成功,其他大部分返回值则表示失败,cstdlib头文件定义了两个预处理变量,分别表示程序运行成功(EXIT_FAILURE)和失败(EXIT_SUCCESS)
返回非引用类型:函数的返回值 用于初始化 在调用函数处创建的临时对象,用函数返回值初始化临时对象与用实参初始化形参的方法是一样的,如果返回类型不是引用,在调用函数的地方会将函数返回值复制给临时对象,当函数返回非引用类型时,其返回值可以是局部对象,也可以是求解表达式的结果
当函数返回引用类型时,没有复制返回值,返回的是对象本身,千万不能返回局部变量的引用,千万不要返回指向局部对象的指针,返回引用的函数返回一个左值,因此,这样的函数可用于任何要求使用左值的地方
7.3.3 递归
直接或间接调用自己的函数称为递归函数,递归函数必须定义一个终止条件,否则,函数就会永远递归下去,这意味着函数会一直调用自身直到程序栈耗尽,主函数main不能调用自身
int rgcd(int v1, int v2 ) { if(v2 != 0 ) return rgcd(v2, v1%v2 ); return v1; }
7.4 函数声明
函数声明可以和函数的定义分离,一个函数只能定义一次,但是可以声明多次,函数声明由函数返回类型、函数名和形参列表组成,形参列表必须包括形参类型,但是不必对形参命名,这三个元素被称为 函数原型,函数原型描述了函数的接口,函数声明的形参名会被忽略,如果在声明中给出了形参的名字,它应该用作辅助文档,定义函数的源文件应包含声明该函数的头文件,可使编译器检查该函数的定义和声明是否一致
默认实参:调用函数时,可以省略有默认值的实参,编译器会为我们省略的实参提供默认值,
默认实参是通过给形参表中的形参提供明确的初始值来指定的,程序员可以为一个或多个形参定义默认值,但是,如果一个形参具有默认实参,那么,它后面所有的形参都必须有默认实参,调用包含默认实参的函数时,如果提供了实参,则它将覆盖默认的实测值,否则,函数将使用默认实参值,如果默认实参是一个表达式,而且默认值用作实参,则在调用函数时求解该表达式,设计带有默认实参的函数,其中部分工作就是排列形参,使最少使用默认实参的形参排在最前,最可能使用默认实参的形参排在最后
既可以在函数声明也可以在函数定义中指定默认实参,但是,在一个文件中,只能为一个形参指定默认实参一次,通常,应在函数声明中指定默认实参,并将该声明放在合适的头文件中,如果在函数定义的形参表中提供默认实参,那么只有在包含该函数定义的源文件中调用调用该函数时,默认实参才是有效的
7.5 局部对象
在C++语言中,每个名字都有作用域,而每个对象都有生命期,名字的作用域指的是知道该名字的程序文本区,对象的生命期是在程序执行过程中对象存在的时间,在函数中定义的 形参和变量的名字 只位于函数的作用域中:这些名字只在函数体中可见,通常,变量名从声明或定义的地方开始到包围它的作用域结束处都是可用的
7.5.1 自动对象
只有当定义它的函数被调用时才存在的对象称为自动对象
7.5.2 静态局部对象
静态局部对象确保不迟于 在程序执行流程第一次经过该对象的定义语句时 进行初始化,这种对象一旦被创建,在程序结束前都不会被撤销,当定义静态局部对象的函数结束时,静态局部对象不会撤销,在该函数被多次调用的过程中,静态局部对象会持续存在并保持它的值
7.6 内联函数
7.7 类的成员函数
isbn ( International Standard Book Number 国际标准书号 )
7.8 重载函数
在C++中,函数可以重载,只要函数中 形参的个数与类型不同,则同一个函数名可以用于定义不同的函数,编译器将根据函数调用时实参确定调用哪个一个函数
7.9 指向函数的指针
函数指针是指向函数的指针,可以使用 typedef 定义函数指针类型
指向函数的指针可用于调用它指向的函数(函数指针解引用,explicitly dereferenced,显式引用),可以不需要解引用操作符(implicitly dereferenced 隐式引用),直接通过指针调用函数
函数指针,可以作为函数的参数、函数的返回类型;引用函数名但又没有调用函数,函数名自动解释为函数指针;阅读函数指针声明的最佳方法是从声明的名字开始由里而外理解
#include <iostream> using std::cout; using std::endl; typedef int (*PF)( int, int ); //定义,函数指针类型 int add( int m, int n){ return m+n; } //阅读函数指针声明的最佳方法是从声明的名字开始由里而外理解 //ff(int),ff是一个函数,带有一个int型的形参 //该函数返回 int (*)(int, int ),它是一个指向函数的指针,所指向的函数返回int并带有两个分别是int,int型的形参 /* int (*ff(int x))(int p, int y){ //函数ff的返回类型是函数指针PF cout << x << endl; return add; } */ PF ff( int x) { //函数ff的返回类型是函数指针PF cout << x << endl; return add; //引用函数名但又没有调用函数,函数名自动解释为函数指针 } int main() { cout << ff(1100)(111,222); return 0; }
允许将函数的形参,定义为函数类型,但是,函数的返回类型不能是函数
#include <iostream> using std::cout; using std::endl; typedef int func( int, int ); //定义函数类型 int add( int m, int n){ return m+n; } int subtract( int x, int y){ return x - y; } void function1(func f){ //参数是函数类型,返回类型void cout << f(111,222) << endl; } int (*function2(func f) )(int x, int y){ //参数是函数类型,返回类型函数指针 cout << f(111,222) << endl; return subtract; } func* function3(func f ){ //参数是函数类型,返回类型函数指针 cout << "function3" << endl; return add; } int main() { function1( add ); cout << "-------------------------" << endl; cout << function2( add )(999,888) << endl; cout << "-------------------------" << endl; cout << function3( add )(100,200) << endl; return 0; }