【c++】第三章 模板与泛型

第1节  模板概念、函数模板定义、调用

一、概述

1、所谓泛型编程,是以独立于任何特定类型的方式编写代码,使用泛型编程时,我们需要提供具体程序实例所操作的类和值。

2、模板是泛型编程的基础,是创建类或者函数的蓝图或公式(编译时变成类或函数)

3、模板支持将类型作为参数,从而实现了对泛型程序设计的直接支持

二、模板函数的定义

使用模板的目的:比如对于返回int和double的相同功能的函数,其实有很多代码是重复的!

template<typename/class 模板名>  参数列表多个用,分隔。

只带类型模板参数的模板函数定义:

template<typename T>    //模板参数列表
T funcadd(T a, T b) {
    T sum = a + b;
    return sum;
}

三、函数模板的调用

调用时,编译器根据实参推断T是什么,推断不出来的时候需要主动提供。

int k1 = funcadd(1,2);

此时编译器推断T为int,所以实例化出一个把T改为int的函数版本。

四、非类型模板参数(int、double之类的)

模板实例化时,非类型模板参数的值必须是常量表达式(编译时就能确定)

带非类型模板参数的定义:

template<int a,int b>    //非类型模板参数
int funcadd2() {
    int sum = a + b;
    return sum;
}
template<typename T,int a, int b> 
int funcadd3(T c) {
    int sum = int(c) + a + b;
    return sum;
}

带非类型模板参数的实现(系统以<>中类型为准进行T的类型推断,而不是()里内容的类型):

int k2 = funcadd2<10, 20>();	//显示提供参数
int k3 = funcadd3<int,200,300>(100);

比较两个字符串是否相等(系统先判断L1为4,L2为5,然后调用成员函数比较两个字符串):

template<unsigned L1, unsigned L2>    //非类型模板参数
inline    //模板函数可以是内联函数
int charscomp(string p1,string p2){    
//上面这一行其实应该这么写:int charscomp(const char(&p1)[L1], const char(&p2)[L2]){
    return strcmp(p1,p2);
}
//实现
int k4 = charscomp("xzh", "xzh1");	//系统会根据字符串长度推断L1和L2是多少
//k4=-1

五、总结

1、模板定义并不会导致编译器生成代码,实例化后才有了版本;

2、函数模板定义通常放在.h文件中,多个文件include同一个template是没有重定义问题的(普通函数就不行)!

 

第2节  类模板概念、定义、使用

一、概述:用类模板实例化一个特定的类

编译器不能为类模板推断模板类型参数,所以为了使用,需要在<>提供额外信息!

二、类模板定义

实例化类模板时,必须有类的全部信息,包括类模板中成员函数的函数体(写在类模板中或写在一个.h文件中)。

template<typename T>	//myvector这个容器中保存的元素类型为T
class myvector {
public:
    typedef T* myiterator;    //省的以后每次都T*了
public:
    myvector();
    myvector& operator=(const myvector &);	//不需要类模板参数
    //或者写成 myvector<T>& operator=(const myvector &);
public:
    myiterator mybegin();	//起始位置
    myiterator myend();
public:
    //void func() {}
    //如果写在类定义外面,成员函数的模板参数就体现出来了
    void func();
};

在这个类模板的定义中:myvector是类模板名,myvector<int>是类型名。

三、类模板的成员函数

1、类模板成员函数的函数体若写在类模板中,会被隐式声明为inline

2、每一次对类模板的实例化,每个新实例都有自己的成员函数,这些成员函数的模板参数肯定是不同的(int,double等)

3、若成员函数写在定义外,需要特别注意格式!!!

//普通成员函数的实现
template<typename T>    //这里要加上类模板定义的头
void myvector<T>::func() {    //这里要加上一个<T>,有几个参数,加几个参数的名字
    //...
    return;
}
//构造函数的实现
template<typename T>
myvector<T>::myvector() {
    //...
    return;
}

类模板成员函数的实例化和调用:

myvector<int> myvec;	//这行生成了一个具体的类,int取代T
myvec.func();

先调用myvector类的构造函数,再调用成员函数func()。

四、模板类名字的使用

template<typename T>
myvector<T>& myvector<T>::operator=(const myvector &) {   //<t>表示返回的是实例化了的myvector
    return *this;
}

五、非类型模板参数

template<typename T,int size = 10>    //定义的时候赋初值
class myarray {
private:
    T arr[size];    //可以直接用size
public:
    void myfunc();
};

template<typename T, int size>
void myarray<T,size>::myfunc() {    //重点是<T,size>的写法
    cout << size << endl;
}

//调用
myarray<int, 100> tmparray;
tmparray.myfunc();    //100
myarray<int> tmparray2;
tmparray2.myfunc();    //10

※ 限制:浮点型(double、float)和类类型不能做非类型模板参数!

 

第3节  用typename场合、默认模板参数、趣味写法分析

一、typename使用场合

1、在模板定义里,表明其后的模板参数是参数类型(这个typename可以换成class)

template<typename T,int a>

2、使用类的类型成员,用typename来标识一下这是一个类型

//类中定义:myiterator为类型成员
typedef T* myiterator;

//类外实现返回值为myiterator的mybegin()函数
typename myvector<T>::myiterator myvector<T>::mybegin();

这里如果不写typename关键字,编译器不知道myiterator是静态成员还是类型!

二、函数指针做其他函数参数

//定义
typedef int(*FunType)(int, int);
int fun(int a, int b) {
    return a + b;
}
void testfun(int i,int j, FunType t) {
    cout << "testfun" << endl;
}

//调用
testfun(0, 0, fun);

三、默认模板参数

1、类模板

若类模板中如此定义
template<typename T=int,int size = 10>
可以这样调用
myarray<> abc;

若类模板中如此定义
template<typename T,int size = 10>
可以这样调用
myarray<int> def;

2、函数模板

模板定义行初始化 + 模板函数参数列表初始化

typedef int(*FunType)(int, int);
int fun(int a, int b) {
    return a + b;
}
template<typename T,typename F=FunType>    //默认给F一个FunType类型
void testfunc(const T&i, const T&j, F tmp= fun){    //默认值必须有
    cout << tmp(i,j) << endl;
}
//调用时输出8
testfunc(3,5);

 

第4节  成员函数模板、显式实例化、声明

一、普通类的成员函数模板

即:类中的成员函数时个模板,这个函数不可以是virtual的

二、类模板的成员函数模板

类模板的成员函数只有用到的时候才进行实例化!

三、模板显式实例化、模板声明

为了防止在多个.cpp文件中实例化相同的类模板,c++11提出显式实例化

template A<float>;    //只有一个cpp中有此定义即可,编译器会实例化出一个A<float>
extern template A<float>;    //其他cpp的声明

 

第5节  using定义模板别名,显式指定模板参数

一、using定义模板别名

普通用法:

typedef map<string,int> m;
//或者
using m = map<string,int>;

需求:我想定义一个map,key是string,但value不固定,可能是int,可能是string,怎么实现?

typedef方法:

template<typename st>
struct map_s{
    typedef map<string,st> m;
}
//调用
map_s<int>::m map1;
map1.insert({"xzh",1});

using方法(using用于定义模板类型的时候功能大于typedef):

template<typename st>
using map_s = map<string,st>;    //using是用来给模板类型其别名的
//调用
map_s<int> map2;
map2.insert({"xzh",1});

二、显式指定模板参数

要与对应的模型参数匹配(在尖括号中自己写的类型优先与系统对于括号里参数的类型推断)!

 

第6节  模板全特化、偏特化(局部特化)

一、类模板特化

特化:对特殊模板类型参数进行特殊的对待(再写一个覆盖),写专用代码!

※ 必须现有泛化版本才能存在特化版本

1、类模板全特化

1)常规全特化:所有类型模板参数都用具体类型代替!(参数相同,系统优先特化版本,可以写无限个)

template<typename T,typename U>
struct st {
    void func() {
        cout << "泛化版本" << endl;
    }
};
template<>
struct st<double,int> {
    void func() {
        cout << "全特化版本" << endl;
    }
};

//调用
st<double, double> test1;
test1.func();    //泛化版本
st<double, int> test2;
test2.func();    //全特化版本

2)特化类的成员函数而不是模板

template<typename T,typename U>
struct st {
    st() {
    	cout << "泛化版本的构造函数" << endl;
    }
    void func() {
    	cout << "泛化版本的func" << endl;
    }
};
template<>
struct st<double,int> {
    st() {
    	cout << "全特化版本的构造函数" << endl;
    }
    void func() {
    	cout << "全特化版本的func" << endl;
    }
};
template<>
void st<double, double>::func() {
    cout << "对全特化的类模板中的成员函数的特殊处理!" << endl;
};

//调用
st<double, double> test1;
test1.func();
//结果为
//泛化版本的构造函数
//对全特化的类模板中的成员函数的特殊处理!
st<double, int> test2;
test2.func();
//结果为
//全特化版本的构造函数
//全特化版本的func

若调用的参数类型只符合特化的成员函数,则先调用泛化版本的构造函数,再调用特化的成员函数!

2、类模板偏特化(局部特化)

1)模板参数数量:绑了两个留了一个

template<typename T,typename U>
struct tc {
    void functest() {
    	cout << "泛化版本" << endl;
    }
};
template<typename T>
struct tc<T,int> {
    void functest() {
    	cout << "偏特化版本" << endl;
    }
};

//调用
tc<char, int> t1;
t1.functest();    //偏特化版本
tc<double, string> t2;
t2.functest();    //泛化版本

2)模板参数范围:const int < int;T* < T;T&、T < T

template<typename T>
struct tc {
    void functest() {
    	cout << "泛化版本" < endl;
    }
};
template<typename T>
struct tc<const T> {
    void functest() {
    	cout << "const的特化版本" << endl;
    }
};
template<typename T>
struct tc<T*> {
    void functest() {
    	cout << "T*的特化版本" << endl;
    }
};

//调用
tc<int*> t1;
t1.functest();    //T*的特化版本
tc<const int> t2;
t2.functest();    //const的特化版本

特化部分更改的是 struct tc<x> x处的参数! 

三、函数模板特化

1、函数模板全特化:等价于实例化一个函数模板

template<typename T,typename U>
void fun(const T&i, const U&j) {
    cout << "泛化版本" << endl;
    cout << i << endl;
    cout << j << endl;
}
template<>
void fun(const int&i, const double&j) {
    cout << "特化版本" << endl;
    cout << i << endl;
    cout << j << endl;
}
void fun(int a,double b) {
    cout << "普通函数版本" << endl;
}

//调用
fun(7, 7);    //泛化版本
fun(7, 7.5);    //普通函数版本,若没有普通函数,调用特化版本

※ 编译器选择顺序:普通函数(重载函数)→ 特化版本 → 泛化版本

如传一个字符串,数组类型模板参数优先于指针类型模板参数

2、函数模板偏特化:不能偏特化!

三、模板特化版本放置建议

模板的定义和实现放在.h文件中,模板特化、泛化版本也放在同一个.h文件中。

 

第7节  可变参模板

允许模板中含有零到任意个模板参数。

一、可变参函数模板

1、简单范例

template<typename... T>
void myfunct(T... args) {
    cout << sizeof...(T) << endl;
    //等价于cout << sizeof...(args) << endl;
}
void func() {
    myfunct();    //0
    myfunct("afd","we",4);    //3
    myfunct(10,20);    //2
}

【值得注意】

a)args称为一堆或一包参数,这些参数的类型可以各不相同

b)T后面跟了...就被称为可变参类型,代表一包类型,那args就是一包形参

template<typename T, typename... U>
void myfunct(const T& args1, const U&... args2) {    //注意...出现的位置!!!
    cout << sizeof...(args2) << endl;
}
void func() {
    myfunct(135);    //0
    myfunct("afd", "we", 4);    //2
    myfunct(10, 20);    //1
}

2、参数包的展开

如何获取这一包参数并进行处理?

答:一般都是用一个递归函数来展开参数包,这样就需要一个参数包展开函数和一个同名的递归终止函数。

每次把args2的内容拆成一个和n个!

void myfunct() {
    cout << "end" << endl;
}
template<typename T, typename... U>
void myfunct(const T& args1, const U&... args2) {
    //cout << sizeof...(args2) << endl;
    cout << "收到的参数值为:" << args1 << endl;
    myfunct(args2...);
}
void func() {
    myfunct(5,"afd", "we", 4);
}

二、可变参类模板(用到的不多)

通过递归继承方式展开参数包,比较复杂。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值