阅读 293

c++从入门到放弃(五)函数基础

​ 坑!

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转换实现匹配(底层const3.通过类型提升实现的匹配
			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表示没有指向任何位置
//当使用重载函数的时候,上下文必须清晰的界定到底使用的是哪个函数,不可以一对多
复制代码