欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

第八章

程序员文章站 2022-07-12 18:03:48
...

内联函数:

inline double square(double x) {return x*x};

引用:
必须在声明引用的同时将其初始化,而不能像指针那样,先声明再赋值:

int a;
int & red;//错误!!极端错误!声明时必须先初始化。
red = a;

所以这样看起来,引用更像const指针,必须在创建时进行初始化,一旦与某个变量关联,必须一直捆绑,不能再关联其他。

函数参数为引用时,如果不改变参数值,应当尽可能的将形参声明为const引用:
1、首先能防止因外修改原数据
2、const形参的函数能够处理const和非const实参,否则只能接受非const数据。
3、const引用使函数能够正确生成并使用临时变量。

在函数返回时,切勿返回指向函数中临时变量的引用或者指针!!因为引用相当与别名,指针相当于存放变量的地址。一旦函数结束后,存储临时变量的地址和临时变量本身都将被释放掉,没有了。
解决方法一般是返回传入函数的实参的引用,这样返回的引用也就指向了这些数据,只要实参进来了,这些数据肯定是有的,不会消失。
而返回传入实参的引用时,也应该注意,尽量将返回的引用也设为const类型,加入不改变原实参的话。

const my_type& accumulate(const my_type& a);//函数原型

因为如果不加的话,有可能误写如下函数,导致原参数改变:

accumulate(a) = b;

因为返回的是原实参的引用,所以,整个accumulate(a)调用之后,就是a的一个引用,很明显,一个参数的引用是可以被赋值的。如果返回是加上了const,则上语句就会报错,不允许这么做!可见处处是坑啊

默认参数:
参数的默认值只在函数原型中设定。要为某个参数设置默认值,则它右边的所有参数都必须提供默认值!不能跳着来,随便设定哪个参数有默认值

int harpo(int n, int m=4, int j=5);//没毛病
int chico(int n, int m=4, int j);//不行,不能单独中间的某个参数有默认值,后面没了。

而且实参按照从左到右的顺序一次被赋值给相应的形参,不能跳过任何参数

haipo(3, ,8);//这样是不行的!!不要想当然中间那个不是有默认值4麽?!!!不行。

函数重载
简单讲,函数名称相同,特征标不同,即为函数重载。
编译器在检查函数特征标时,将类型引用和类型本身视为同一个特征标。

double cube(double x);
double cube(double & x);//这俩搞一起会报错,因为不是重载,有歧义性。
double a = 3.0;
cube(a);//因为在这种调用的情况下,就有歧义了。

函数重载一般用于执行相同的任务,但是使用不同形式的数据是,才会用函数重载。并不什么场合都乱用。

函数模板
先帖一下典型用法:

#include<iostream>
template<typename T>
void swap(T& a, T& b);//这里注意一下,这句跟上方的类型定义T其实是一个语句,但是分两行写,可以发现,上一句没有结束分号。

int main
{
int a = 3;
int b = 4;
swap(a, b);

double c = 5.0;
double d = 6.0;
swap(c, d);

return 0;
}

template<typename T>
void swap(T& a, T& b)
{
T = temp;
temp = a;
a = b;
b = temp;
}

当然模板也能重载,就是多来几个参数列表不同的模板就是了。

下面引出函数模板中比较绕的两个名词:
具体化与实例化。

从头开始。我们最初想到用模板,就是需要对多种不同类型的的数据使用同一种算法时,使用模板,减少因类型引起的重复工作,说白了就是为了省劲。比如上面的交换函数,如果不用模板,俩int我得定义一个函数,俩double我得定义一个函数,然后short,long,float等等,没完了。。。所以就想出了模板。但是随着使用就会发现,有一定的局限性,就是前面说的各种类型用模板好用,是因为模板函数的实现代码是完全一样的,全是这样:

T = temp;
temp = a;
a = b;
b = temp;

无论short ,double,int等等,都是这样,所以模板好用。
但是假如来了个数组类型的a和b呢?首先函数实现代码不可能再这样写了, 但是无论是数组还是int,总的逻辑还是一个交换,只是参数类型不同了,我还想写成一个函数名swap。此时就需要用到:
显式具体化:

template<typename T>
void swap(T& a, T& b);

template <> void swap(int a[], int b[]);//显式具体化

int main{...}

template<typename T>
void swap(T& a, T& b)
{...}

template <> void swap(int a[], int b[])
{...}//后面要有自己的模板函数定义,定义好此种特殊类型的运算规则就可以了,这样就互不影响了。

显式实例化:
显式实例化是与隐式实例化相对的。
先说下函数模板的性质,
函数模板本身并不会生成函数定义,它只是一个生成函数定义的方案,告诉你怎么生成函数定义!!!程序想用函数,必须有函数定义,所以编译器就根据模板去生成函数定义,这个过程叫模板实例化。
一般情况下在什么时候实例化呢?就在程序中调用函数的时候隐式的去实例化:

swap(a, b);//这句就隐式的进行了模板的实例化

如果我不希望在函数调用时被隐式的实例化,而是想自己手动可控的去实例化函数模板呢,这就是显示实例化了。看程序:

template<typename T>
void swap(T& a, T& b);

template void swap<int>(int a, int b);//显式实例化,手动去将模板实例化为一个int类型的函数定义。

int main{...}

template<typename T>
void swap(T& a, T& b)
{...}  //下方还是模板函数的定义,并没有实例化自己的定义

总结几个点:
1、显式具体化是告诉编译器,模板定义的运算规则对于这个类型不好用哈,我重新定义一个此类型的运算规则(一般像类与基本类型的一些同样名称的操作,在具体实现上,代码一般很不一样)。
显示实例化是我手动实例化出一个模板适用类型的函数定义,不想等着调用时隐式实例化出函数定义,比如上面手动实例化出的int类型交换函数定义。
2、显式具体化,用template <>打头。显示实例化用template打头。
3、显式具体化后方必须有自己的模板函数定义。显示实例化只需在模板原型下写一句实例化语句即可,不需要有自己的模板函数定义。

好,终于到了这里,看起来模板比较好用了,没有什么BUG了。但是,用着用着又发现问题了。。。

来看个膈应事:

template<typename T1, typename T2>
void sum(T1 x, T2 y)
{
?type? sum = x + y;//问题在这里,写到这一句的时候,这个sum定义成什么类型呢?
decltype(x + y) sum = x + y;//这样用就好了,表面上看其实也是一种后确定类型的方法,就是在x+y运算进行之前,我并不知道sum的类型,所以干脆就用这俩的运算来定义类型吧。
}

好了,decltype关键字来了,功能就是根据运算规则和运算元素自动去推断类型!

刚刚是赋值导致的事先不能确定类型的情况,还有就是函数返回值时:

template<typename T1, typename T2>
?type? sum(T1 x, T2 y)//模板的返回值类型怎么写呢?
{
 return x + y;//模板有返回值了
}

模板的返回值类型你可能很自然的想到decltype(x + y),写成这样:

template<typename T1, typename T2>
decltype(x + y) sum(T1 x, T2 y)//直接把decltype(x + y)摆上,是不对滴!
{
 return x + y;//模板有返回值了
}

图样图森跑,执行decltype(x + y)时,xy还没定义呢?哪来的xy??最早是在参数列表里定义的,你在参数列表之前用,不好使的。
所以很简单,移到后面不就可以了么。。。

template<typename T1, typename T2>
auto sum(T1 x, T2 y) -> decltype(x + y)//前面加个auto,后面来个->一指,完事。看起来有点像swift哦。。。
{
 return x + y;//模板有返回值了
}