函数的定义
包括返回类型、函数名、形参列表以及函数体 “()”是调用运算符,即一对圆括号,作用于一个表达式,表达式是函数或者指向函数的指针。括号内是用逗号隔开的形参列表
执行函数的第一步是隐式地定义并初始化(利用实参)形参
大多数类型可作为函数返回类型。数组类型或函数类型不能作为返回类型(函数返回是值传递,而数组不能被拷贝),但可以是指向数组或函数的指针
局部静态对象:在程序的执行路径第一次经过对象定义语句时初始化,且直到程序终止才被销毁。期间对象所在的函数结束执行也对它无影响。通过将局部对象定义成static类型做到
函数声明(函数原型):函数名使用前需要先声明。函数可以声明多次,但只能定义一次。函数声明无需函数体,用分号代替即可。
分离式编译:把程序分割到几个文件中去,每个文件单独编译,形成各自的.obj(Windows)或.o(Unix)文件,再由编译器负责把对象文件链接在一起形成可执行文件(模板不能采用分离式编译,因为模板函数的代码不能直接编译成二进制代码,需要经过实例化。因此常把模板的实现也放在头文件中)c++分离式编译模型
参数传递:形参初始化机理与变量初始化一样,根据形参类型氛围引用传递和值传递。值传递是将实参的拷贝后赋值给形参。为避免拷贝(类类型对象较大导致拷贝较低效、或类类型不支持拷贝)应使用引用
const的使用
书上的示例:
const int a=1;//a的值不能改变
int i=a;//可以把a的值拷贝给i,忽略了顶层const
int * const p=&i;//不能改变p的值,即p存着的地址
*p=0;//可以改变p指向对象的值
当形参是const时,用实参初始化形参会忽略掉顶层const,当形参有顶层const时,传给它常量或非常量对象皆可
int i=1;
const int *cp=&i;//可以用非常量初始化底层const对象,但cp不能改变i的值
int *p=cp;//错误,顶层const对象不能初始化非常量,否则p可以去改变i的值
应尽量使用常量引用。不能把const对象,字面值或需要类型转换的对象传递给普通的引用形参
数组形参
数组无法拷贝,无法以值传递的方式使用数组参数。数组作为形参时会转换成指针,指向数组首元素
void f(const int*);
void f(const int[]);
void f(const int[10]);
上面三个函数声明等价,形参都是const int*,实参自动转化为指向数组首元素的指针,数组大小对函数调用无影响
数组引用形参
void f(int (&arr)[10]);//arr是引用,绑定到size为10的int类型数组上
void f(int &arr[10]);//错误将arr声明成了引用的数组,但引用不是一种类型
这是数组大小构成了数组类型的一部分,编译器会检查形参和实参的大小,只能将函数作用于大小为10的数组
传递多维数组
void f(int (*p)[10]);//p为指针,指向含有10个整数的数组
int *p[10];//p为数组名,含有10个整型指针的数组
int (*p)[10];//p为指针,指向size为10的整形数组
可变形参
C++11新标准提供了两种方法解决处理不同数量实参的函数:若实参类型相同,可以使用initializer_list的标准库类型;若实参类型不同,使用可变参数模板
initializer_list是一种模板类型,需要指明元素类型:
void f(initializer_list<int> il){
for(auto beg=il.begin();beg!=il.end();++beg)
...
}
f({1,2,3});//实参序列放在花括号内
void f(const string& s, initializer_list<int> il){
...
}
f("hello",{1,2});
返回值
函数返回一个值的方式与初始化一个变量或形参的方式完全相同(因此不能返回数组),返回值用于初始化语句的一个临时量,该临时量就是函数调用的结果
不能返回局部对象的引用或指针,函数执行完毕后局部对象的内存空间被释放,引用或指针将指向无效区域
返回引用的函数调用是左值,可以进行赋值操作
列表初始化返回值
vector<int> f(){
return {1,2,3,4};
}
main()返回值
主函数返回值可以看作状态指示器,返回0表示执行成功,其他值表示失败,非零值的含义具体依机器而定。为了使返回值与机器无关,cstdlib头文件定义了两个预处理变量分别表示成功和失败,EXIT_SUCCESS和EXIT_FAILURE
递归
函数直接或间接调用自身,主函数不能调用自己
返回数组指针
数组不能被拷贝,因此函数无法返回数组,但可以返回数组的指针或引用
可以使用类型别名返回:
typedef int arrT[10];//arrT是类型别名,表示的类型是含有10个整数的数组
using arrT=int[10];//跟上面等效
arrT* f(int i);//f返回一个指向含有10个整数的数组的指针
声明一个返回数组指针的函数
int (*f(int i))[10];//函数返回一个指向大小为10的整数数组的指针
尾置返回类型
尾置返回类型跟在形参列表后面并以一个 -> 符号开头。在本该出现返回类型的地方放置auto,以表示函数真正的返回类型是跟在形参列表之后:
auto f(int i) -> int(*)[10];
decltype声明返回类型
已经知道函数返回的指针将指向哪个数组时:
int array[]={1,2,3};
decltype(array) *f(int i){//decltype并不负责把数组类型转换成指针
...
return &array;//想返回指针还需要加*符号
}
函数重载
函数类型由返回类型和形参数量、类型共同决定,同一作用域内函数名相同但形参列表不同,成为函数重载
不允许两个函数除了返回类型之外的部分都相同。如两个函数形参列表相同但返回类型不同,在匹配实参时两个函数都能精确匹配,因而产生二义性的错误
重载匹配
在同一作用域内,若多个同名函数的声明均可见,当调用函数时,需要根据实参与声明可见的重载函数集中的某一个函数关联起来。函数匹配包括下列过程:
1. 确定候选函数:与被调函数同名;其声明在函数调用发生处可见
2. 考察调用提供的实参,从候选函数中选择可行函数:形参数量与实参数量匹配;每个实参与形参的类型相同或可转换成形参类型
3. 寻求最佳匹配:实参类型与形参类型越接近,匹配得越好。精确匹配由于需要类型转换的匹配
* 含有多个形参的函数匹配:当有且只有一个函数满足:该函数每个实参的匹配都不劣于其他函数需要的匹配;至少有一个实参的匹配由于其他可行函数的匹配
重载与作用域
发生函数调用时,编译器先会在内层的局部作用域进行上述重载匹配过程,一旦找到同名的函数声明,则编译器会忽略掉外层作用域的同名实体。如果匹配不成功则编译不通过
void print(int arg) {
std::cout << arg;
}
void print(double arg) {
std::cout << arg;
}
void print(std::string arg) {
std::cout << arg;
}
int main()
{
void print(std::string);//屏蔽了主函数作用域外的声明
print(2.3);//编译不同过,double类型不能转化成string类型
std::cout << std::endl;
return 0;
}
void print(int arg) {
std::cout << arg;
}
void print(double arg) {
std::cout << arg;
}
void print(std::string arg) {
std::cout << arg;
}
int main()
{
void print(int);//屏蔽了主函数作用域外的声明
print(2.3);//匹配成功,double类型转换成int类型
std::cout << std::endl;
return 0;
}
const形参
常量指针(顶层const)作为形参,与普通指针无法区分,因为都可以通过指针改变实参的值
int f1(int);
int f1(const int);//重复定义,无论加不加const限制,实参的值都无法改变
int f2(int*);
int f2(int* const);//重复定义,形参是常量指针,但依旧可以通过指针改变实参的值
int f3(int &);
int f3(const int&);//新函数,实参为常整型时调用这个函数
int f4(int *);
int f4(const int*);//新函数
函数指针
函数指针是指向函数的指针,即函数入口地址。函数类型由返回类型和形参类型共同决定,与函数名无关
int f(int);//f是函数类型
int (*pf)(int);//pf指向一个函数,该函数有一个int形参,返回类型是int
int *pf(int);//pf是函数类型,有一个int形参,返回指向int类型的指针
函数指针的使用
int f1(int arg){
...
}
int f2(double);
...
int (*pf)(int)=f1;
int (*pf)(int)=&f1;//等价的函数指针赋值语句
//可直接用指针调用函数,可以不提前解引用指针
int t=pf(3);//调用函数f1
t=(*pf)(3);//等价的调用语句
//不同函数指针不存在类型转换
pf=f2;//错误
//可以为函数指针赋一个nullptr或值为0的整型常量表达式
//此时指针不指向任何函数
pf=0;//正确
函数指针形参
函数类型和数组类似,不能定义函数类型的形参,但形参可以是函数指针
void f(int pf(int &));//形参是函数类型,会自动转换成指向函数的指针
void f(int (*pf)(int &));//等价声明
//可以把函数作为实参使用,此时它会自动转换成指针,还是与数组类似
若函数声明较复杂,可以用类型别名(typedef)和decltype简化使用函数指针的代码
typedef decltype(f) pf1;//pf1是函数类型
typedef decltype(f) *pf2;//pf2是函数指针
//f是函数类型,故decltype返回的是函数类型,它不会将函数类型自动转换成指针
返回指向函数的指针
还是与数组类似,不能返回一个函数类型,但可以返回函数指针。编译器不会自动把返回类型由函数类型转换成函数指针,需要写成函数指针形式
可以使用类型别名处理
using F=int(int*);//F是函数类型
using PF=int(*)(int*);//PF是函数指针
//**********************
//声明返回函数指针的函数
PF f1(int);//正确,f1的形参是int,返回指向函数的指针
F f1(int);//错误,不能返回函数类型
F *f1(int);//正确,显式指定了返回类型是函数指针
//**********************
//直接声明
int (*f(int))(int*);
//首先f有形参,故f是个函数;f前有*,说明返回值是指针;再看最外一层发现
//指针的类型包括了形参int*,说明f是指向函数的指针,该函数返回值是int
//**********************
//利用尾置返回类型,与数组类似
auto f(int) -> int(*)(int*);