坑!
5.1 参数
int f(i++,i)
//默认是不清楚先计算i还是i++的,没有规定实参的求值顺序,但是,可以假设从后往前计算,c语言中是这个过程
int f(int a,int)
//形参名是可选的,一般情况下需要一个名字,不提供也行,就是找不到这个形参
int f()
{
static int a=0;
//这个局部静态对象的生命周期是一直到整个进程结束!进程!
//未显式初始化则默认初始化为0(内置类型)
}
int f(int ,int);
//声明里面不一定需要形参名,注意是声明!
//函数三要素:返回类型(不写其实默认为int),函数名,形参类型
int f(int &a,int &b)
//传引用,可修改
//但是不可以传一个字面值,比如f(1,2),引用类型不可以初始化为字面值
int f(const int &a,const int &b)
//这就可以了。。。。f(1,2)没有问题
//使用引用可以避免拷贝,特别大的字符串的时候!
//传递数组的时候,实际上传递的是数组的指针
//多维数组就不说了
//如果有问题,想想这样初始化这个形参是否会出现问题
如果不清楚形参的数量,可以使用省略符形参
void foo(int a,...)
void foo(...)
5.2 返回值
在含有return语句的循环后面应该也有一条return语句,如果没有的话就是错误的,如果编译器没有发现这个错误,则运行时的行为就是未定义的。
for(int i=0;i<n;i++)
if(i>8)
return;
return; //必须写以防止最后不返回
不要返回局部对象的引用或者指针,局部变量使用的空间在函数结束后是会被释放的,所以不可以返回局部变量的指针或者引用。但是返回局部变量的值是可以的,因为使用的是拷贝的数据。
const string &manip()
{
string ret;
if(!ret.empty())
return ret; //错误,返回局部变量的引用
else
return "abv"; //错误,这是一个局部临时量,在函数结束的时候一样会被清空空间
}
返回类类型或者引用,指针之类的函数,可以使用函数调用的结果访问其成员
auto z=shorterString(s1,s2).size();
//返回一个引用类型的函数可以当做左值
char &get_val(string &s,int x)
{return s[x];}
int main()
{
get_val(s,x)=1; //直接当做左值修改
}
也可以使用列表初始化返回
return {1,2,3} //一般用来返回容器类型
5.3 重载
如果同一作用域内的几个函数名字相同但是形参列表不同(形参数量或形参类型),则称之为重载函数。
注意:main函数是不能重载的
int fun(int a,int b);
int fun(int a)
int fun(char c)
int fun(int c) //这就不是重载
int fun(int) //这也不是重载
注意,函数参数是不认可顶层const的,所以默认顶层const加不加都一样。
int fun(int a);
int fun(const int a); //不可以,顶层const
int fun(int *a);
int fun(int * const a); //不可以,顶层const
//如果形参是某种类型的指针或者引用,则通过区分其指向的是常量对象还是非常量对象可以实现函数重载,此时const是底层的
int fun(int &);
int fun(const int &); //可以重载,底层const
int fun(int *);
int fun(const int *); //可以重载,底层const
注意需要避免函数重载的二义性!
在不同的作用域中无法重载函数!
5.4 特殊特性
可以为函数形参赋值默认实参(在声明中赋值)
int fun(int a,int b,int c=1);
//可以为一个或者多个形参赋值默认值,但是,一旦一个形参赋值了,则后面的形参都必须有默认实参
int fun(int a,int b=1,int c); //无法分辨fun(1,2)的二义性
//注意无法修改一个已经存在的默认值
int fun(int ,int ,int c=2); //错误
//但是可以添加默认实参
int fun(int a,int b=2,int c=1); //正确
##函数调用时实参按其位置解析,默认实参负责填补函数调用缺少的尾部实参
int fun(string a="123",int a=1,int b=2);
fun(,2,3); //错误,默认补充尾部默认参数,想修改哪个那前面的都不能跳过!
//只能忽略尾部的实参
fun("1",2+1); //假如想修改b这个程序会和预期不符,所以要消除二义性
内联函数,可以避免函数调用的开销,不会进入到函数的地址区,而是通过编译器直接将函数代码"粘贴"到对应位置,所以不适用于大型的函数,只适用于小型的,频繁调用的函数。
inline int f(int a,int b)
{return a>b?a:b;}
cout<<f(1,2)<<endl;
//等价于
cout<<1>2?1:2<<endl;
assert预处理宏
assert(expr)
//表达式为假则输出信息并终止程序的运行
//为真则什么也不做
5.5 函数匹配
具体操作如下
void f();
void f(int);
void f(int,int);
void f(double,double=3.14);
f(5.6);
(1)确定候选函数
1.与被调用的函数重名
2.其声明在调用点可见
//无淘汰
(2)确定可行函数
1.形参数量与本次调用提供的实参数量相等(考虑默认参数可是可行的)
2.每个参数的类型与对应的形参类型相同,或者能转换成形参类型
//淘汰第1,3
//如果这步未找到可行函数,编译器会报无匹配函数错误
(3)寻找最佳匹配
寻找形参类型与实参类型最匹配的那个函数
最佳匹配等级:
1.精确匹配
实参类型和形参类型相同
实参从数组类型或函数类型转换成对应的指针类型
向实参添加顶层const或者从实参中删除顶层const
2.通过const转换实现匹配(底层const)
3.通过类型提升实现的匹配
4.通过算术类型转换或指针转换实现的匹配
5.通过类类型转换实现的匹配
(如果重载函数的区别只是多了个底层const的话,将从函数调用的实际参数类型(常量或者非常量)来判断)
多个形参的函数匹配
1.该函数每个实参的匹配都不劣于其他可行函数需要的匹配
2.至少有一个实参的匹配优于其他可行函数提供的匹配
如果检查了之后没有找到唯一的一个函数脱颖而出,则编译器报告二义性调用的信息。
//f(42,2.56)
//典型的二义性错误
5.6 函数指针
函数指针指向的是函数不是对象。
int max(int a,int b);
//声明一个可以指向该函数的指针,只需要用指针替换函数名即可
int (*p)(int ,int);
//将函数赋值给函数指针的时候可以忽略取地址符
p=max;
p=&max; //加不加都一样
//调用函数指针指向的函数的时候用不用解引用指针也一样
cout<<p(1,2)<<endl;
cout<<(*p)(1,2)<<endl;
//可以为函数指针赋值0或者nullptr表示没有指向任何位置
//当使用重载函数的时候,上下文必须清晰的界定到底使用的是哪个函数,不可以一对多