C++ 模板与泛型(8/7未完待续)

文章目录

1.模板概念与函数模板的定义、调用

1.1 模板概念

  • 模板一般分为函数模板和类模板
  • 类模板:举例比如vector就是类模板,通过尖括号传入一个数据类型参数,编译器就通过传递进去的类型参数int生成一个真正的类

1.2 函数模板的定义

  • 把两个int类型的参数相加和两个float类型参数相加,属于函数重载
template<typename T>
T funcadd(T a,T b)
{
	T addhe = a + b;
	return adddhe;
}

1.3 函数模板的调用

  • 模板参数是推导出来的,根据传入的实参类型来推到
int he = funcadd(3,1);//推到成int类型
float he = funcadd(3.1f,1.2f);//推导成float,如下所示
float funcadd(float a, float b)
{
	float addhe = a + b;
	return addhe;
}
float he = funcadd(3,1.2f);//这样的话就报错了

1.4 非类型模板(参数必须是常量表达式)

  • 类型参数表示的是一个类型,非类型参数表示的是一个值
  • 第一种写法,但是有时候系统推断不出来
template<int a,int b>	//定义函数模板
int funcaddv2()
{
	int addhe = a + b;
	return addhe;
}
//调用
int result = funcaddv2<12,13>();//通过<>来传递参数
  • 第二种写法:更安全
template<typename T,int a,int b>
int funcaddv3(T c)
{
	int addhe = (int)c + a + b;
	return addhe;
}
//调用
int result = funcaddv3<int,11,12>(13);//得出是36
//若int改成double,系统会以传递进去的类型为准,而不是以13推断出来的类型为准

2.类模板概与类模板的定义、使用

2.1 类模板概念

  • 为了避免出现很多重复的代码,引入了类模板,通过往这个类模板中传递不同的类型或非类型参数,从而实现一套代码

2.2 类模板的定义

  • 以实现一个vector为例子
//myvector.h
#ifndef __MYVECTOR__
#define __MYVECTOR__

template<typename T>
class myvector
{
public:
    typedef T* myiterator;          //迭代器

public:
    myvector();                     //构造函数
    ~myvector();                    //
    myvector& operator=(const myvector&);//赋值运算符重载
    								//参数可以写成(const myvector<int>&)

private:
    //迭代器接口
    myiterator mybegin();           //迭代器起始位置
    myiterator myend();             //迭代器结束位置

};

#endif

//main函数
myvector<int> tmpvec;//T换成了int

2.3 类模板的成员函数

  • 一个实例化的模板,它的成员只有在使用的时候才会被实例化
//类里面的成员函数
public:
	void myfunc();

//myfunc()成员函数的外部实现
template<typename T>
void myvector<T>::myfunc()
{
}

//构造函数的外部实现
template<typename T>
myvector<T>::myvector()
{
}

2.4 类模板名字的使用

  • 类外面实现赋值运算符的重载代码
template<typename T>
myvector<T>& myvector<T>::operator=(const myvector<T>&)//第一个<T>表示返回的是一个实例化的myvector,第三个<T>不是必加
{
	//.....
	return *this;
}

2.5 非类模板参数的使用

  • 创建一个新文件myarry.h,模拟一个类似数组行为的类模板
#ifndef __MYARRAY__
#define __MYARRAY__

template<typename T,int size = 10>
class myarray
{
public:
    myarray();
    ~myarray();

public:
    void myfunc();

private:
    T arr[size];
};

template<typename T,int size>
void myarray<T,size>::myfunc()
{
    
}


#endif

3.使用typename的场合、函数模板、默认模板参数与趣味写法分析

3.1 typename的使用场合

3.2 函数指针作为其他函数的参数

3.3 函数模板趣味用法举例

3.4 默认模板参数

4.成员函数模板,模板显式实例化与声明

4.1 普通类的成员函数模板

#include <iostream>
using namespace std;
class A
{
public:
    template<typename T>
    void myft(T tmpt)   //成员函数模板
    {
        std::cout<<tmpt<<std::endl;
    }
};

int main()
{
    A a;
    a.myft(3);
    return 0;
}
//输出
3

4.2 类模板的成员函数模板

#include <iostream>
using namespace std;

template<typename C>
class A
{
public:
    template<typename T2>
    A(T2 v1,T2 v2)//构造函数也引入自己的模板参数T2,和整个类的模板参数C没有关系
    {

    }

    template<typename T>
    void myft(T tmpt)   //成员函数模板
    {
        std::cout<<tmpt<<std::endl;
    }
    C m_ic;
};

int main()
{   
    A<float> a(1,2);
    A<float> a2(1.1,2.2);
    a.myft(3);
    return 0;
}
//输出
3

4.3 模板显示实例化和声明

//------------------------
//ca.h
#ifndef __CAH__
#define __CAH__

using namespace std;

template <typename C>
class A
{
public:
    template<typename T2>
    A(T2 v1,T2 v2);
    template<typename T>
    void myft(T tmpt)
    {
        std::cout<<tmpt<<std::endl;
    }
    C m_ic;
};

template<typename C>
template<typename T2>
A<C>::A(T2 v1,T2 v2)
{
    std::cout<<v1<<" "<<v2<<std::endl;
}

#endif
//------------------------
//test.cpp
#include <iostream>
#include <vector>
#include "ca.h"
void myfunc()
{
    A<float>a(1,2);
}
//------------------------
//MyProject.cpp
#include <iostream>
#include <vector>
#include "ca.h"

int main()
{
    A<float> a(1,2);
    A<float> a2(1.1,2.2);
    a.myft(3);//打印3
    return 0;
}
  • 当两个.cpp代码中的"A<float.>a(1,2);"这行代码在编译时,因为每个.cpp文件独立编译,所以编译器在MyProject.cpp中会实例化出一个A<float.>类,在test.cpp中也会实例化出一个A<float.>类,增加了很多没有必要的编译时间
  • 解决方法:显示实例化,也叫做实例化定义
template A<float>;//实例化定义,只有一个.cpp文件里面这样写,编译器为其生成代码
//这行代码的意思是让编译器实例化出一个A<float>,然后其他的.cpp不需要实例化了
//其他所有.cpp文件都这样写
extern template A<float>;//模板实例化声明
//ca.h
#ifndef __CAH__
#define __CAH__

using namespace std;

//增加一个函数模板
template<typename T>
void myfunc(T v1,T v2)
{
    std::cout<<v1+v2<<std::endl;
}

template <typename C>
class A
{
public:
    template<typename T2>
    A(T2 v1,T2 v2);
    template<typename T>
    void myft(T tmpt)
    {
        std::cout<<tmpt<<std::endl;
    }
    C m_ic;
};

template<typename C>
template<typename T2>
A<C>::A(T2 v1,T2 v2)
{
    std::cout<<v1<<" "<<v2<<std::endl;
}

#endif

//----------------------------------
//test.cpp
#include <iostream>
#include <vector>
#include "ca.h"

//增加
template void myfunc(int& v1,int& v2);//函数模板实例化定义,编译器会为其生成实例化代码
void myfunc()
{
    A<float>a(1,2);
}

//--------------------------------------
//MyProject.cpp
#include <iostream>
#include <vector>
#include "ca.h"

//增加函数模板实例化声明
extern template void myfunc(int& v1,int& v2);//函数模板实例化声明
//这样的话,float版本的A类模板(A<float>)以及int版本的myfunc函数模板就都会在test.cpp文件中实例化,而不会在MyProject.cpp文件中实例
//当然如果int作为模板参数的类模板A还会再MyProject.cpp中被实例化
//缺点:其他所有类模板的所有成员函数都实例化了,不管用没用到
int main()
{
    A<float> a(1,2);
    A<float> a2(1.1,2.2);
    a.myft(3);//打印3
    A<int> d(6,7);
    return 0;
}

5.using定义模板别名与显示指定模板参数

5.1 using定义模板别名

  • 1)前面直到typedef一般用来定义类型别名
    例子:
例如:
typedef unsigned int uint_t;//相当于给unsigned int 类型起了个别名uint_t
//using uint_t = unsigned int;
typedef std::map<std::string,int> map_s_i;//长名字改成短名字
//using map_s_i = std::map<std::string,int>;

map_s_i mymap;
mymap.insert({"first",1});//插入元素到容器,容器中每个元素都是一个键值对
  • 2)但是typedef固定了类型,下面是C++98的方法
template<typename wt>
struct map_s
{
	typedef std::map<std::string,wt> type;//定义了一个类型
};
//main函数
map_s<int>::type map1;//等价于std::map<std::string,int> map1;
map1.insert({"first",1});
  • 3)C++11的做法
template<typename T>
using str_map_t = std::map<std::string,T>;
//主函数
str_map_t<int> map1;
map1.insert({"first",1});
  • 4)using定义一个函数指针类型
typedef int(*FunType)(int,int);//返回类型是int,形参是两个int类型
using FunType = int(*)(int,int);//注意第一个圆括号中间的内容变成(*)了
  • 5)using定义类型相关的模板
template<typename T>
using myfunc_M = int(*)(T,T);//指针所代表的函数返回类型是int类型,后面的()里是两个形参的类型
//main主函数
myfunc_M<int> pointFunc;//函数指针,该函数返回一个int,参数是两个int,注意myfunc_M<int>
						//是类型名(类型别名),并不是一个类模板实例化后的类
int RealFunc(int i ,int j)
{
	return 3;
}
//main函数
pointFunc = RealFunc;			//把函数地址赋给函数指针
cout<<pointFunc(1,6)<<endl;		//3:通过函数指针调用函数
  • 6)总结一下:

1.用using定义类型相关模板与定义普通函数类型差不多,只是在前面要增加一个template开头的模板参数类型
2.在using中使用的这种模板,既不是类模板,也不是函数模板,可以看成是别名模板

5.2 显示指定模板参数

template<typename T1,typename T2,typename T3>
T1 sum(T2 i,T3 j)
{
	T1 result = i + j;
	return result;
}
//main主函数中
auto result = sum<double,double,double>(20000000,20000000);//第一个double表示是result的返回类型,必须显示,告诉编译器
cout<<result<<endl;

6.模板全特化与偏特化(局部特化)

6.1 类模板特化

6.1.1 类模板全特化
6.1.1.1 常规全特化
  • 下面是泛化类模板的定义,写在MyProject.cpp上面:
template<typename T,typename U>
struct TC
{
    TC()
    {
        std::cout<<"TC泛化版本构造函数"<<std::endl;
    }

    void funcTest()
    {
        std::cout<<"TC泛化版本构造函数"<<std::endl;
    }

};
  • 例子1:针对T和U都为int类型的特化版本要这样写,下面代码要放在TC类模板泛化版本代码的下面(因为所有类型模板参数哦都用具体类型代表,因此下面版本是一个全特化版本)
template<typename T,typename U>
struct TC
{
    TC()
    {
        std::cout<<"TC泛化版本构造函数"<<std::endl;
    }

    void funcTest()
    {
        std::cout<<"TC泛化版本构造函数"<<std::endl;
    }

};

template<> //全特化所有类型模板参数都用具体类型代表,所以<>里就空了
struct TC<int,int>
{
    TC()
    {
        std::cout<<"TC<int,int>特化版本构造函数"<<std::endl;
    }

    //这里可以对该特化版本做单独处理
    void funcTest()
    {
        std::cout<<"TC<int,int>特化版本"<<std::endl;
    }

};

int main()
{
    TC  <int,int> tcint;//TC<int,int>特化版本构造函数
    tcint.funcTest();
    
    return 0;
}

在这里插入图片描述

6.1.1.2 特化类模板的成员函数
template<typename T,typename U>
struct TC
{
    TC()
    {
        std::cout<<"TC泛化版本构造函数"<<std::endl;
    }

    void funcTest()
    {
        std::cout<<"TC泛化版本构造函数"<<std::endl;
    }

};

template<>      //全特化
void TC <double,double>::funcTest()
{
    std::cout<<"TC<double,double>的funcTest()特化版本"<<std::endl;
}

int main()
{
    TC <double,double> tdbledbl;        //TC泛化版本构造函数
    tdbledbl.funcTest();                //TC <double,double>的funcTest()特化版本
    return 0;
}

6.1.2 类模板偏特化(局部特化)

  • 上面讲的是全特化,也就是把所有类型模板参数都用具体类型代表。这里讲解的是偏特化,也叫局部特化。从模板参数的数量和范围两部分上讲:
6.1.2.1 模板参数数量上的偏特化
//模板参数数量上的偏特化
template<typename T,typename U,typename W>
struct TCP
{
    TCP()
    {
        std::cout<<"TCP泛化版本构造函数"<<std::endl;
    }

    void funcTest()
    {
        std::cout<<"TCP泛化版本"<<std::endl;
    }

};

template<typename U>
struct TCP<int,U,double>
{
    TCP()
    {
        std::cout<<"TCP<int,U,double>偏特化版本构造函数"<<std::endl;
    }

    void funcTest()
    {
        std::cout<<"TCP<int,U,double>偏特化版本"<<std::endl;
    }

};

int main()
{
    TCP<double,int,double> tcpdi;           //TCP泛化版本构造函数
    tcpdi.funcTest();                       //TCP泛化版本

    TCP<int,int,double>    tcpii;           //TCP<int,U,double>偏特化版本构造函数
    tcpii.funcTest();

    return 0;
}
  • 打印效果
    在这里插入图片描述
6.1.2.2 模板参数范围上的偏特化
  • 参数范围的概念:原来是int类型,如果变成const int类型,就是类型范围上就变小了!
  • 或任意T类型变成T*(从数据类型缩小为指针类型)
  • 或左值引用、右值引用针对T来说,从类型范围上都变小了
template<typename T>
struct TCF
{
    TCF()
    {
        std::cout<<"TCF泛化版本构造函数"<<std::endl;
    }

    void funcTest()
    {
        std::cout<<"TCF泛化版本"<<std::endl;
    }

};

template<typename T>
struct TCF<const T>
{
    TCF()
    {
        std::cout<<"TCF<const T>特化版本构造函数"<<std::endl;
    }

    void funcTest()
    {
        std::cout<<"TCF<const T>特化版本"<<std::endl;
    }

};

template<typename T>
struct TCF<T*>
{
    TCF()
    {
        std::cout<<"TCF<T*>特化版本构造函数"<<std::endl;
    }

    void funcTest()
    {
        std::cout<<"TCF<T*>特化版本"<<std::endl;
    }

};

template<typename T>
struct TCF<T&>
{
    TCF()
    {
        std::cout<<"TCF<T&>特化版本构造函数"<<std::endl;
    }

    void funcTest()
    {
        std::cout<<"TCF<T&>特化版本"<<std::endl;
    }

};

template<typename T>
struct TCF<T&&>
{
    TCF()
    {
        std::cout<<"TCF<T&&>特化版本构造函数"<<std::endl;
    }

    void funcTest()
    {
        std::cout<<"TCF<T&&>特化版本"<<std::endl;
    }

};


int main()
{
    TCF<double> td1;         //TCF泛化版本构造函数
    td1.funcTest();          //TCF泛化版本

    TCF<double*> td2;         
    td2.funcTest();          

    TCF<const int> td3;         
    td3.funcTest();

    TCF<int&> td4;         
    td4.funcTest();          
    
    TCF<int&&> td5;         
    td5.funcTest();          

    return 0;
}

在这里插入图片描述

6.2 函数模板特化


  • 未完待续
  • 、、---------------------------------------------
6.2.1 函数模板全特化
6.2.2 函数模板偏特化

6.3 模板特化版本放置位置建议

  • 模板定义和实现一啊不能都放在.h文件中。
  • 模板的特化版本应该和模板泛化版本等都放在同一个.h文件中,并且特化版本一般放在泛化版本的后面即可

7.可变参数模板与模板模板参数

7.1 可变参数模板

7.1.1 简单范例
7.1.2 参数包的展开

7.2 可变参类模板

7.2.1 通过递归继承方式展开参数包
7.2.2 通过递归组合方式展开参数包
7.2.3 通过tuple和递归调用展开参数包
7.2.4 总结

7.3 模板模板参数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值