一个典型的函数定义包括:返回类型、函数名、由0个或多个形参组成的列表以及函数体。其中,形参以逗号隔开,形参的列表位于一对圆括号内。
通过调用运算符来执行函数。调用运算符的形式是一对圆括号,它作用于一个表达式,该表达式是函数或者指向函数的指针;圆括号内是一个用逗号隔开的实参列表,我们用实参初始化函数的姓参(实参是形参的初始值)。
函数返回类型不能是数组类型或者函数类型,但可以是指向数组或函数的指针。
函数声明:函数三要素(返回类型、函数名、形参类型)描述了函数的接口,说明了函数的全部信息。函数声明也称作函数原型。建议函数在头文件中声明,在原文件中定义。
// 函数声明 void print(vector<int>::const_iterator beg, vector<int>::const_iterator end);
形参的类型决定了形参和实参交互的方式。如果形参是引用类型,它将绑定到对应的实参上;否则将实参的值拷贝后赋给形参。
当形参是引用类型时,我们说它对应的实参被引用传递或者函数被传引用调用;引用形参是它对应的实参的别名。
当实参的值被拷贝给形参时,形参和实参是两个相互独立的对象。我们说这样的实参被值传递或者函数被传值调用。
使用引用可以避免拷贝,特别是拷贝大的类类型对象或者容器对象比较低效,甚至有的类类型根本就不支持拷贝操作。如果函数无须改变引用形参的值,最好将其声明为常量引用。
bool isShorter(const string &s1, const string &s2) { return s1.size() < s2.size() }
数组有两个特殊的性质:不允许拷贝数组以及使用数组时通常将其转换成指针。因为不能拷贝数组,所以我们无法以值传递的方式使用数组参数。因为数组会被转换成指针,所以当我们为函数传递一个数组时,实际上传递的是指向该数组首元素的指针。
因为数组是以指针的形式传递函数的,所以一开始函数就不知道数组的确切尺寸,调用者应该为此提供一些额外的信息。
管理数组实参的三种方式:
void print(const char *cp) { if (cp) while (*cp) cout << *cp++; }
void print(const int *beg, const int *end) { while (beg != end) cout << *beg++ << endl; }
void print(const int ia[], size_t size) { for (size_t i = 0; i != size; ++i){ cout << ia[i] << endl; } }
数组形参与const:当函数不需要对数组元素执行写操作的时候,数组形参应该是指向const的指针。只有当函数确实要改变元素值的时候,才把形参定义成指向非常量的指针。
数组引用形参
void print(int (&arr)[10]) { for (auto elem : arr) cout << elem << endl; }
// &arr两端的括号必不可少 f(int &arr[10]) // 将arr声明成了引用的数组 f(int (&arr)[10]) // arr是具有10个整数的整型数组的引用
// *matrix两端的括号必不可少 int *matrix[10] // 10个指针构成的数组 int (*matrix)[10] // 指向含有10个整数的数组的指针
只要函数的返回类型不是void,则该函数内的每一条return语句必须返回一个值。return语句返回值的类型必须与函数的返回类型相同,或者能隐式地转换成函数的返回类型。但有个例外,我们允许main函数没有return语句就结束。如果控制到达了main函数的结尾处而且没有return语句,编译器将隐式地插入一条返回0的return语句。
值是如何被返回的:返回的值用于初始化调用点的一个临时量,该临时量就是函数调用的结果。如果函数返回引用,则该引用仅是它所引对象的一个别名。