c++ 函数




结构

结构就像是 函数中的变量 是按值传递 (就是他们都把实参变量的值  赋值给形参变量 不对原始变量进行修改 ) 所以函数中使用变量就很使用其他的变量没有什么区别了
但是 有一点就是结构加入很大的情况下 传入变量拷贝的时间就会很大,这种情况下为了节约效率我们 可以用 指针,但是跟多情况下 我们使用引用


指针

1声明函数指针

这就是c++函数中很牛叉的地方了 就是指针也可以指向函数 我们来看一下例子吧
//原型
int f(int a);
//函数指针
int (*p)(in a)
int (*p)(int )
这样很让人懵逼,起始就是函数名字 用(* a)代替就是了

2函数指针调用函数

//两种
int (*p)(int a){
return a;
}
//1
(*p)(5);
//2
p(5);
这两种都是对函数指针的引用,为什么会这样呢 ,因为有两派人,一派认为i(*p)表示函数名,一派表示p就表示函数名,所以c++折衷了两个都允许

3没有你想的那么简单

比如
const int *(*p)(const int * ,int)=f1
上面是一个 简单的函数指针 他的参数是 和返回值是const的,但是如果更复杂呢
const double *(*p[])(cinst double *,int)={f1,f2,f3}
上面这个式子就比较蛋疼了  他是一个数组 里面有三个指向函数的指针,这也许不麻烦,但是如果我们想把这些指针赋值给某些变量是 这就有点头大了,但是 我们可以用c++11中可以用auto自动推断类型
比如
auto p1=p[0];
auto p2=&p;
第一个表示 函数指针 ,第二个表示 数组的的指针 是不是很简单
但是 auto 只是保证右边的值 和左边的值类型一致 不检查你的逻辑对错,还有一点auto 还有一个很重的功能 以后单独讲
除了auto 我们还可以用typedef我们都知道typedef是给变量其别名的 也就是小名如
typedef int a
这样就可以用 a 来代替int了  所以我们可以这样
typedef constdouble *(*p)(const int *)
p p1=f1;
这样就可以实现 auto的功能了


默认参数

?给函数中的参数添加缺省值
void f(int a=0;int b=1;int c=1;)
有一点注意就是函数参数和 函数重载之间的二义性



函数返回const的引用

返回数据本体,函数在运行完毕之后会把return的值返回到临时一个内存地址中,和按值传递的意思一样。
我们要注意返回引用时注意这个引用是不是临时的
const int & f(int a){
int b=a;
return b;
}
上面的 b随着函数over 而消亡 ,所以返回的内存单元也将消失
为什么要用const的引用返回值呢?为了下面的值
f(a,b)=3;
是不是很酷(当然上面翻过了  3=f(a,b)就是错的了)




函数模板

 ?顾名思义就是为函数创建同一的模板

声明

template< typename AnyType>
void f(AnyType & a,AnyType b){
}

第一行指出 要创建一个模板,并将命名为AnyType,关键词template和typename是必需的,class 可以代替typename,可以用class 是因为向前兼容

注意:模板函数只是源代码阶段 ,在编译阶段编译器根据具体出来的模板类型 来定义独立的函数定义,就像手工定义这些函数一样,最终代码是不包含任何模板的


局限性

在函数模板中 typename更像是一种文本的替换

void f( T a,T b){
}

对于上面的模板中T 如果表示的是int ,double 的数据类型  a>b 是可以通过的,但是如果是数组和结构类型的还  a>b 是不可以通过的,但是有问题就有解决的办法


显示具体化(第三代显示具体化)

显示声明一种模板

void f(int  a,int  b);				//普通的函数

template<class T>				//普通的模板
void f(T a,T b);

template<> void f<int >(int ,int )		//普通显示具体化,不带变量名
template<> void f<int >(int a ,int b )		//普通显示具体化,带变量名
template <>void f(int ,int )			//因为编译器可以根据后面的具体类型来判断,所以可以取代哦<int>

编译器在选择 函数原型是的顺序 非模板>显示具体化>模板函数

显示实例化

上面说了模板并不是创建具体的函数实例,而是编译器根据具体的参数类型创建具体的函数代码,但是有一种方法就是直接创建函数实例,那就是显示实例化

template void f<int>(int ,int )

警告  上面的<int>是不能少的 且同一个文件(转换单元中使用同一类型的显示实例化和现实具体化将出错)

还可以在程序中使用函数来创建显示实例化

template <typename T>
T add(T a,T b){

	return a+b;

}
int  a=0;
double b=0;
cout<<add<double>(a,b)<<endl;
这里面 模板与函数调用 add(a,b)是不匹配的,因为参数不同,但是通过add<double>(a,b)可以强制转换为double类型的


重载模板函数

void f(T & ,T&)		
void f(T[] &,T[] &,int)//模板函数中也是可以有普通类型的

上面就是重载模板函数(其实还有复杂的情况,不过一般在编程中不会有人故意把模板写的很复杂的,因为这除了装逼没什么用),重载模板函数和默认参数之间有可能二义要注意

编译器选择哪个函数版本呢?

如今 我们讲了 函数重载,函数模板,函数模板的重载,c++找不到一模一样的原型时是不会停下脚步的,那么c++有一个什么样的策略来解决找“妈妈”的问题呢?

我妈把这个过程成为重载解析

第一步 创建候选函数列表,其中包含与被调用函数相同的函数和函数模板

第二步 使用候选函数列表创建可行函数列表,这些都是参数数目正确的函数,为此有一个隐式的转换序列,其中包括实参类型与相应的形参类型完全匹配的情况

第三步 确定是否有最佳的可行函数,如果有,则用它否则该函数条用出错


当然上面的话一般人是不能理解的,举个栗子


f('B')
void f(int);				//#1
flotf(flot,flot=3);			//#2
void f(char)				//#3
char * f(const char *)			//#4
char f(const chat *)			//#5
template <class T>void f(const T &)	//#6
template <class T>void f(T *)		//#7

对于 上面的f('B')而言我们只考虑特征标 而不考虑返回类型 其中#4和#7不可行,因为整形类型不能被隐式转换为指针类型,剩余的一个模板可用来生成具体化,其中T被替换为chat类型,这样剩下5个可行的函数,其中每一个函数,如果它是声明的唯一一个函数,都可以被使用

接下来编译器必须确定哪一个是最佳的,从最佳到最差的顺序如下

1完全匹配 ,但常规函数优先于模板

2提升转换 (例如 char和shorts 自动转换为int,float自动转换为double)

3标准转换(例如,int转换为char,long转换为double)

4用户定义的转换,如类声明中定义的转换

例如,函数#1优于#2,因为char到int转换为提升转换,而char到float的转换时标准转换。函数#3,函数#5,函数#6都优与#1,#2,因为都是完全匹配,#3,#5优#6,因为#6函数是模板,这种分析引出两个问题,什么是完全匹配, 如果两个函数(#3和#5)都是完全匹配,将如何办?,通常,有两个函数完全匹配时一种错误,但这一规则有两个例外。显然我们需要对这一点做更深入的探讨

1完全匹配和最佳匹配

在进行完全匹配时,c++允许做一些“无关紧要的转换”

从实参到形参
TypeType &
Type &Type
Type(argument-list)Type(*)(argument-list)
Typeconst Type
Typevolatile Type
Type *const Type
Type *volatile Type *
Type []* Type其中

其中上面Type为任意类型  Type(argument-list)意味着用作实参的函数名与用作形参的函数指针只要返回类型和参数列表相同

知道上面列表之后我们就知道了,多个匹配的原型时是否出现二义性

void f( int)		//#1
void f(const int)	//#2
void f(int &)		/#3
void f(const int &)	//#4

上面的例子应该会出现二义性 但是有时候即使两个函数完全匹配,但是仍可以完成重载解析,首先,指向非const数据的指针和引用优先与非const指针和引用参数匹配,在上面的例子中如果只定义#3和#4都是完全匹配,则选择#3 ,这个规则值只出现在指针和引用而普通的数据还是会出现二义性。

一个完全匹配优先与另一个情况是,其中一个是模板,而另一个不是,这种情况下非模板函数将优先与模板函数

如果两个都是模板函数,这较具体的模板函数优先,例如显示具体化优先于模板隐式生成的具体化

其中“最具体化”是指编译器那种类型执行的转换最小

template <class T>void f(T t) 
template<class T>void f(T *t) 


如果f(& a) 实参是一个地址会这么样呢 因为第二个具体化为指针 所以我们所第二个最具体

关键字decltype

在c++的模板中我们会遇到这样的问题


template<class T1,calss T2 >
void f(T1 a,T2 b){
    	decltype(x+y) xy=x+y;
	........
}

像上面那样 x+y的结果你不知道是什么类型 的所以我们用decltype

声明

<pre class="cpp" name="code"><span style="font-family:Arial;background-color: transparent;"></span>decltype(x) y;		//第一种声明

decltype(x+y) xy;	//声明和初始化分开
xy=x+y;

decltype (x+y) xy=x+y;//声明和初始化一起
 

然后 decltype 比上面的事例要复杂,编译器必须便利一个核对表例如decltype(expression) var

则核对表如下

第一步 如果expression 是一个没有括号括起来的标识符 则var 的类型与该标识符的类型相同,包括const

第二步 如果expression是一个函数调用则var 和返回值相同(编译器不会调用函数 只是根据原型 来确定返回类型)

第三步  如果expression是带括号的标识符,则是var的引用

double x=4.4;		
decltype ((x)) xx=x;	//xx double &	
decltype(x) xxx=x;	//xxx double

第四部 如果如果前面的条件都不满足 则var的类型月expression相同

需要多次声明可结合使用typedef和decltype

typedef decltype (x+y) xy;

c++后置返回类型

template<class T1,calss T2 >
Type? f(T1 a,T2 b){
    	 return x+y;
	
}


无法预知x+y的值什么什么类型的  如果设置decltype(x+y)但是此时还没有声明x和y,他们不再作用于内,必须声明参数后使用decltype

有一种就技术就是c++后置返回类型

auto f(T x ,T y)->decltype(x+y)

就是我们之前自动声明类型,其中auto起始就是一个占位符,他告诉编译器真正的类型在后面














评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值