【C++泛型学习笔记】类模板、变量模板和别名模板
admin
2024-03-20 10:28:02
0

学习参考书籍:王健伟《C++新经典:模板与泛型编程》

类模板

和函数模板一样,类模板可以理解为产生类的模具,通过给定的模板参数生成具体的类。vector容器就是一个类模板应用的例子,vector可以存放不同类型的数据类型元素,其就是通过引入类模板来减少不同类型元素存储时重复的代码,代码更加精简和通用。示例如下:

#include 
using namespace std;template //标识符为T的模板参数,表示myvector容器所保存的元素类型
class myvector
{
public:typedef T* myiterator;	// 迭代器
public:myvector();	//构造函数myvector(T tmpt) // 带参数的构造函数{}myvector& operator=(const myvector&);	// 重载赋值运算符,在类模板中使用模板名可以不用提供模板参数,如myvector
public:void myfunc(){cout << "mufunc() 被调用" << endl;}
public:// 迭代器接口myiterator mybegin();	//迭代器起始位置myiterator myend();		//迭代器结束位置	
};template
myvector::myvector()		// 类外构造函数实现
{}int main()
{myvector tempvec;	//T被替换成int,即指定模板参数T为intmyvector tempvec1(6);    // 不用指定模板参数tempvec.myfunc();		//调用类模板中的普通成员函数
}

对于类模板myvector,myvector称为类名类模板,myvector称为类型名,其中T称为模板参数,T本身代表容器中的元素类型。在类模板内部类型名可以简写成类名,如myvector& operator=(const myvector&);,但在类模板外不可以,如myvector::myvector()

对于类模板的模板参数推导,代码myvector tempvec1(6); 中,我们通过调用含参数的构造函数实例化模板类,编译器通过传入的实参类型可以自动推导出T的类型。而myvector tempvec;则是通过类名<类型>指定T的类型(倘若没有带参数的构造函数,T仍需指定)。实现模板参数推导的功能是通过使用推断指南(deduction guide),其作用为推断类模板参数时提供推断指引。一般我们常见的(如上面代码中的例子)都为隐式推断指南,无需指定(前提是待传入参数的构造函数存在)编译器自动推断。当然,程序员也可以自定义推断指南(如不存在构造函数的情况)。形式如下:

template
A(T,T)->A;

对于类模板的特化类模板的全特化如下:

template<>
class  myvector
{
public:myvector(){cout << "全特化版本" << endl;}void myfunc();
};void myvector::myfunc()
{cout << "全特化版本的myfunc" << endl;
}int main()
{myvector tempvec;	tempvec.myfunc();
}

需要区分的是,泛化版本的类模板和全特化版本的类模板只是同名,两者实例化后的对象是完全不同的两个类,即成员属性和函数无法共享。其次对在全特化版本类模板外定义的成员函数不能在开头加template<>,相当于全特化后变成了一个普通类。

普通成员函数和静态变量的全特化如下:

#include 
using namespace std;template //标识符为T的模板参数,表示myvector容器所保存的元素类型
class myvector
{
public:typedef T* myiterator;	// 迭代器static int m_stc;	// 静态变量声明
public:myvector();	//构造函数myvector(T tmpt) // 带参数的构造函数{}myvector& operator=(const myvector&);	// 重载赋值运算符,在类模板中使用模板名可以不用提供模板参数,如myvector
public:void myfunc();public:// 迭代器接口myiterator mybegin();	//迭代器起始位置myiterator myend();		//迭代器结束位置	
};template
myvector::myvector()		// 类外构造函数实现
{cout << "泛化版本的构造函数被调用" << endl;
}template<>
class  myvector
{
public:myvector(){cout << "全特化版本" << endl;}void myfunc();
};void myvector::myfunc()
{cout << "全特化版本的myfunc" << endl;
}template
void myvector::myfunc()
{cout << "泛化版本的普通成员函数myfunc()被调用" << endl;
}template<>
void myvector::myfunc()
{cout << "泛化版本的普通成员函数myfunc()的全特化版本被调用" << endl;
}template
int myvector::m_stc = 10;template<>
int myvector::m_stc = 100;int main()
{myvector tempvec;	tempvec.myfunc();cout << tempvec.m_stc << endl;
}

在上述代码的mian函数中,我们使用myvector tempvec;指令模板参数类型为float实例化了类模板,因为myvector类模板存在全特化版本,所以编译器会先找到全特化版本,但是由于全特化版本中模板参数特化类型为int,class myvector,所以只能使用泛化版本,因此运行的是泛化版本的构造函数。接着,运行代码tempvec.myfunc();,tempvec是泛化版本示例化后的类,所以myfunc函数也是泛化版本中的,不过在泛化版本中存在全特化的同名函数,所以会优先选择其。不过由于该成员函数的全特化版本模板参数类型为double,void myvector::myfunc(),不是该实例化类中模板参数float,所以该全特化函数不会取代原来的同名成员函数。对于模板类中的静态变量m_stc同理。亦然,我们如果最开始指定模板类型为double,那么会依次执行泛化版本的构造函数,泛化版本的全特化myfunc成员函数,输出泛化版本的m_stc静态变量。想一想,如果实例化类的模板参数类型为int,那么cout << tempvec.m_stc << endl;必然会报错,因为类模板的全特化版本中没有这一静态变量。

注意:如果进行了普通成员函数或静态成员变量的全特化,那么就无法用这些全特化时指定的类型对整个类模板进行全特化了。因为在对成员函数或静态成员变量进行了全特化后导致实例化了对应类型的类模板,如果再次进行全特化,将不会重复进行相同类型的实例化,编译器报错。

对于类模板的偏特化有两种:一是模板参数数量上的偏特化;一是模板参数范围上的偏特化。具体实现和原理类似于函数模板的偏特化。

对于默认参数

  • 与函数模板的默认参数不同,类模板的默认参数规定:如果某一模板参数具有默认值,那么其后所有的模板参数都得有默认值。
  • 有默认值的模板参数在类模板实例化时可以不用提供,如myvector<> tmpvec
  • 后面的模板参数可以依赖前面的模板参数,如template
  • 还可以在模板声明中指定默认参数。

类型别名,可以通过typedef或者using关键字给类型名起一个别名。

typedef TC IF_TC;
using IF_TCU = TC;

和函数模板一样,类模板中同样也可以有非类型模板参数,但全局指针、浮点数和字符串常量不能作为非类型模板参数。

成员函数模板

示例如下:

#include 
using namespace std;template
class A 
{
public:A(double v1, double v2)	// 普通构造函数{cout << "A::A(double,double)执行了!" << endl;}A(T1 v1, T1 v2)		// 使用类模板参数类型的构造函数{cout << "A::A(T1,T1)执行了!" << endl;}templateA(T2 v1, T2 v2);	// 构造函数模板templatevoid myfunc(T3 tmpt)	// 普通成员函模板{cout << tmpt << endl;}T1 m_ic;static constexpr int m_stcvalue = 200;
};// 在类外实现类模板的构造函数模板
template	// 先写类模板的模板参数列表
template	// 再写构造函数模板自己的模板参数列表
A::A(T2 v1, T2 v3)
{cout << "A::A(T2,T2)执行了!" << endl;
}int main()
{A a(1, 2);a.myfunc(3);
}
  • 类模板中的成员函数,只有在源代码中调用时,对应成员函数才会出现在实例化的类模板中。
  • 类模板中的成员函数模板,只有在源代码中调用时,对应成员函数模板的具体实例才会出现在实例化的类模板中。
  • 编译器目前不支持虚成员函数模板。

拷贝构造函数模板不等同且永远不可能成为拷贝构造函数,拷贝赋值运算符模板不等同且永远不可能成为拷贝赋值运算符。类型相同的对象拷贝构造调用的是拷贝构造函数,类型不同的对象拷贝构造调用的是拷贝构造函数模板,并不会因为找不到对应调用对象而且调用另一个。

对于成员函数模板也具有特化版本。

相关资料表示,C++标准不允许在类模板之外全特化一个未被全特化的类模板的成员函数模板。即在类模板外,如果要全特化一个成员函数模板,需要确保该成员函数模板所属的类模板为全特化版本。

类模板嵌套

类模板中套类模板和类中类相差不大。需要注意的是,将子类模板的成员函数写在父类模板的泛化版本之外,应当如下形式:

template	//父类模板参数列表
template	//子类模板参数列表
void A::B::myfunc()
{
}

变量模板与成员变量模板

变量模板定义和使用如下:

#include 
using namespace std;template
T myvar{};	// 变量模板,{}为零初始化int main()
{myvar = 13;myvar = 13.1;cout << myvar << " " << myvar << endl;
}

不同的指定类型得到不同的变量。

template
T myvar{};	// 泛化版本template<>
char myvar{};	// 全特化版本,myvar可以当作char类型使用template
T myvar{120};	// 偏特化版本template
T myvar{};		// 默认模板参数template
T myvar[val];		// 非类型模板参数template
class A
{
public:templatestatic W m_tpi;		// 成员变量模板
};// 成员变量模板在类模板外定义
template
template
W A::m_tpi = 5;

别名模板与成员别名模板

别名模板的作用主要是简化书写。

#include template
using str_map_t = std::map;	// 别名模板template
class A
{templateusing str_map_t = std::map;	// 成员别名模板
public:str_map_t map1;
};int main()
{str_map_t map1;map1.insert({'one', 1});map1.insert({'two', 2});A obja;obja.map1.insert({'one', 1});
}

模板模板参数

之前我们学习的模板参数有类型模板参数和非类型模板参数,这一部分将提出模板模板参数,即模板参数本身为模板,将类模板当作参数传递到另一个模板中。我们在学习类模板的时候知道vector、list等容器类其实也是类模板,所以若我们想要将这些容器类作为模板参数传入模板中,写法如下:

#include 
#include 
#include 
using namespace std;template class Container = std::vector>
//template typename Container = std::vector>
class myclass
{
public:Container myc;
public:void func();myclass(){for (int i = 0; i < 10; i++){myc.push_back(i);	// 本行代码正确性取决于模板参数类型}}
};template class Container>
void myclass::func()
{cout << "mstifiy" << endl;
}int main()
{myclass mylistobj; // double是容器中的元素类型,list是容器类型mylistobj.func();std::cout << mylistobj.myc.size() << endl;
}

其中重点注意模板参数列表的写法:

template class Container = std::vector>
template typename Container = std::vector>	// class也能替换成typename修饰
template typename Container = std::vector>	// W为容器模板的类型模板参数,不能用,故省略
template typename Container>	// 没有默认模板参数

class和typename可以相互替代,都是合法的。

相关内容

热门资讯

安卓系统的如何测试软件,从入门... 你有没有想过,你的安卓手机里那些神奇的软件是怎么诞生的呢?它们可不是凭空出现的,而是经过一系列严格的...
小米8安卓系统版本,安卓系统版... 你有没有发现,手机更新换代的速度简直就像坐上了火箭呢?这不,小米8这款手机自从上市以来,就凭借着出色...
华为手机安卓系统7以上,创新体... 你有没有发现,最近华为手机越来越受欢迎了呢?尤其是那些搭载了安卓系统7.0及以上版本的机型,简直让人...
儿童英语免费安卓系统,儿童英语... 哇,亲爱的家长朋友们,你是否在为孩子的英语学习发愁呢?别担心,今天我要给你带来一个超级好消息——儿童...
ios系统切换安卓系统还原,还... 你有没有想过,有一天你的手机从iOS系统切换到了安卓系统,然后再从安卓系统回到iOS系统呢?这听起来...
灵焕3装安卓系统,引领智能新体... 你知道吗?最近手机圈里可是掀起了一股热潮,那就是灵焕3这款神器的安卓系统升级。没错,就是那个曾经以独...
安卓系统指南针软件,探索未知世... 手机里的指南针功能是不是让你在户外探险时倍感神奇?但你知道吗,安卓系统中的指南针软件可是大有学问呢!...
华为是不用安卓系统了吗,迈向自... 最近有个大新闻在科技圈里炸开了锅,那就是华为是不是不再使用安卓系统了?这可不是一个简单的问题,它涉及...
安卓系统热点开启失败,排查与解... 最近是不是你也遇到了安卓系统热点开启失败的小麻烦?别急,让我来给你详细说说这个让人头疼的问题,说不定...
小米max2系统安卓,安卓系统... 你有没有听说过小米Max2这款手机?它那超大的屏幕,简直就像是个移动的电脑屏幕,看视频、玩游戏,那叫...
电池健康怎么保持安卓系统,优化... 手机可是我们生活中不可或缺的好伙伴,而电池健康度就是它的生命力。你有没有发现,随着使用时间的增长,你...
安卓手机怎么调系统颜色,安卓手... 你有没有发现,你的安卓手机屏幕颜色突然变得不那么顺眼了?是不是也想给它换换“脸色”,让它看起来更有个...
安卓系统清粉哪个好,哪款清粉工... 手机用久了,是不是觉得卡得要命?别急,今天就来聊聊安卓系统清理垃圾哪个软件好。市面上清理工具那么多,...
华为被限制用安卓系统,挑战安卓... 你知道吗?最近科技圈可是炸开了锅!华为,这个我们耳熟能详的名字,竟然因为一些“小插曲”被限制了使用安...
安卓系统是不是外国,源自外国的... 你有没有想过,我们每天离不开的安卓系统,它是不是外国货呢?这个问题听起来可能有点奇怪,但确实很多人都...
安卓系统缺少文件下载,全面解析... 你有没有发现,用安卓手机的时候,有时候下载个文件真是让人头疼呢?别急,今天就来聊聊这个让人烦恼的小问...
kktv系统刷安卓系统怎么样,... 你有没有听说最近KKTV系统刷安卓系统的事情?这可是个热门话题呢!咱们一起来聊聊,看看这个新玩意儿到...
安卓系统连接电脑蓝牙,操作指南... 你有没有遇到过这种情况:手机里堆满了各种好用的应用,可就是想找个方便快捷的方式,把手机里的音乐、照片...
安卓车机11.0系统包,智能驾... 你有没有发现,最近你的安卓车机系统好像悄悄升级了呢?没错,就是那个安卓车机11.0系统包!这可不是一...
安卓系统最高到多少,从初代到最... 你有没有想过,你的安卓手机系统升级到哪一步了呢?是不是好奇安卓系统最高能到多少呢?别急,今天就来带你...