前言

自动推导的用处配合上模板大概才有真正的火花


正文

1
2
3
4
5
6
7
8
9
10
11
int Ave(int a, int b){
return (a + b) / 2;
}

int Ave(float a, int b){
return (a + b) / 2;
}

double Ave(int a, float b){
return (a + b) / 2;
}

首先瞅一个函数重载,我们简单做个模板:

1
2
3
4
template<typename T>
T Ave(T a, T b){
return (a + b) / 2;
}

但是这个模板似乎只能适应第一个函数,为什么呢?
因为T只代表了一个类型,所以当你函数的参数类型不同时,单纯的参数就搞不明白了。

聪明娃一下子就想到用两个参数了:

1
2
3
4
template<typename T1,typename T2>
T1 Ave(T2 a, T1 b){
return (a + b) / 2;
}

但是这种虽然是两种参数了,而且返回值是根据模板1来的,还是有点糙。
你说用<>去指定1和2是什么类型的参数,其实意义也没有太大。

且看重载的第三个函数,发现返回值,参数都不相同。。要用模板就要用三个了。

1
2
3
4
template<typename T1,typename T2,typename T3>
T3 Ave(T1 a, T2 b){
return (a + b) / 2;
}

用三个模板就有不好使的点

  1. T1和T2可以通过形参去确定类型,返回值似乎就成了孤儿
  2. 虽然可以指定,但是这种写法就要一次性指定三个了,除非说T1做返回值类型,这样倒是只要做一个指定
    1
    2
    3
    4
    5
    6
    7
      template<typename T1,typename T2,typename T3>
    T1 Ave(T2 a, T3 b){
    return (a + b) / 2;
    }
    int main(){
    std::cout << Ave<int>(100,200.0f) << "\n";
    }

学习了函数模板不要拘束自己的思想:

1
2
3
4
5
6
template<typename T1,typename T2,typename T3>
T1 Ave(T2 a, T3 b,int c){
T3 x;
T2 *p;
return (a + b) / 2;
}

虽然上面是给编译器提供了一个模板,但是下面本质还是自定义参数,你仍可以使用它完成函数能完成的操作


玩一玩闹一闹

1
2
3
4
5
6
7
8
9
10
11
12
template<typename T1,typename T2>
decltype(auto) toMax(T1 &a, T2 &b){
return a > b ? a : b;
}
int main(){
float a = 1000;
int b = 15464894;

toMax(a,b); //猜想一下toMax的返回值类型是什么?为什么?

return 0;
}

ps: 不同类型直接的引用存在类型转换问题


回顾正式-函数模板参数的默认值

在玩函数的时候我们知道可以给形参赋予默认值,以应对一些情况不是很有必要输入的地方。

1
2
3
int stringL(char &a, int len, bool flag=true){
//只是假设,没有啥大意义
}

那么函数模板的参数也就是typename T=xxx之类的操作

1
2
template<typename T1=int,typename T2=double>
//指定完类型甚至都不用推导了,当然不可能都指定类型,不然还要这种模板干什么。

拿我们之前说的三个模板参数的问题:

1
2
3
4
5
6
7
template<typename T1,typename T2,typename T3>
T1 Ave(T2 a, T3 b){
return (a + b) / 2;
}
int main(){
std::cout << Ave<int>(100,200.0f) << "\n";
}

需要指定一下返回值类型在每次调用的时候都指定也挺累,所以可以使用默认值

1
2
3
4
5
6
7
template<typename T1=int,typename T2,typename T3>
T1 Ave(T2 a, T3 b){
return (a + b) / 2;
}
int main(){
std::cout << Ave(100,200.0f) << "\n";
}

还有一种用法就是让一个参数=前面某个参数的类型:

1
2
3
4
5
6
7
template<typename T1,typename T2,typename T3=T1>
T1 Ave(T2 a, T3 b){
return (a + b) / 2;
}
int main(){
std::cout << Ave(100,200.0f) << "\n";
}

当然用法归用法,怎么用还得看你想怎么实现。

既然有指定模板类型参数,就衍生了非类型的模板参数

1
2
3
4
template<typename T1,typename T2,typename T3=T1,int max,int min>
T1 Ave(T2 a, T3 b){
return (a + b) / 2;
}

这样有点不直观,随便找个例子:

1
2
3
4
5
6
7
8
9
10
template<typename T,int max,int min>
bool testHp(T &hp, T shanghai){
hp -= shanghai;
if (hp > max || hp < min) hp = 0; //假设异常就=0直接给他抬走,实际游戏肯定不会这么无赖
return hp;
}
int main(){
int hp = 1000;
testHp(hp, 100); //肯定会报错
}

因为我们忽略了模板max和min。但是这样写在后面,然后你指定的时候它是顺位来的所以要修改一下

1
2
3
4
5
6
7
8
9
10
11
12
template<int max,int min, typename T>
bool testHp(T &hp, T shanghai){
hp -= shanghai;
if (hp > max || hp < min) hp = 0;
return hp;
}
int main(){
int hp = 1000;
testHp<1200,900>(hp, 100);

return 0;
}

可以看到hp的值改变了

回过头,max和min算什么?肯定不是变量,因为它俩存在于模板参数定义中。
虽然模板的特色就是替换,但max目前是被替换成了1200,这就是临时的常量,肯定是不能被修改的,有疑惑可以去函数里试试能不能修改max或者min。
然后传递个变量: 看到报错了
但是把x设置为常量:

1
2
3
const int x = 1200;
int hp = 1000;
testHp<x,900>(hp, 100);

这样是没问题的。

也就是说在模板参数里的非类型参数是算常量

最后鸡贼的时候,默认参数默认参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template<int max=1200,int min=1000, typename T>
bool testHp(T &hp, T shanghai){
hp -= shanghai;
if (hp > max || hp < min) hp = 0;
return hp;
}
/*
template< typename T, int max=1200,int min=1000>
bool testHp(T &hp, T shanghai){
hp -= shanghai;
if (hp > max || hp < min) hp = 0;
return hp;
}
*/

就不需要指定了,默认给了,如果不想改动,一般就放到后面就行了


拓展

利用模板计算数组平均数:

1
2
3
4
5
6
int pj(int *p,int count){
}
//如果传递指针,还要带上长度,会很麻烦
//int pj(int ch[]){} 这种就更没法用了。

int pj(int (&ch)[5]){} //指定的话也会很尴尬,效果跟之前的差不多

但是利用模板,可以让长度达到自适应效果:

1
2
3
4
5
6
7
8
template<typename T,short count>
T Ave(T(&arry)[count]){
T ary{};
for (auto x:arry){
ary += x;
}
return ary / count;
}

可以看到传参的时候都不需要用,分隔长度。这也是因为模板自动推敲出了count这个长度的参数

但是不足之处是对于char类型数组,毕竟T是模板,当返回值是一个char类型的值打印出来就很怪。


模板排序大法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
template<typename T,unsigned count>
void Sort(T(&ary)[count]){
for (unsigned i = 0; i < count; i++){
for (unsigned j = 0; j < count - i - 1; j++){
if (ary[j] > ary[j + 1]){
T tmp = ary[j];
ary[j] = ary[j + 1];
ary[j + 1] = tmp;
}
}
}
}
//本质上算不得什么,但是通过结合学习,可以做成这样的模板也挺好的,既可以忽略数组个数,也能自适应
int main(){
int a[6]{ 15,48,11,42,100,20 };
short a1[5]{ 1,50,3,11,20 };
Sort(a);
Sort(a1);

for (auto x : a) std::cout << x << "\t";
std::cout << std::endl;
for (auto x : a1)std::cout << x << "\t";

return 0;
}

效果如图,算法采用冒泡,因为写法好记。。。怎么理解冒泡找视频有动画排序的过程加深理解。


结语

  • 模板 会根据 类型生成不同的函数重载 : 详情见debug 调用两个不同类型的函数 看call 函数时内存地址
1
template<class T>

模板其实还有一种声明类为参数的方式,但是这里目前还是处于面向过程就等后面做到模板类在回顾。