【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可以相互替代,都是合法的。

相关内容

热门资讯

怎么解除订阅安卓系统,安卓系统... 你是不是也和我一样,手机里订阅了好多服务,结果现在想解除订阅,却一头雾水?别急,今天就来手把手教你如...
安卓系统停用怎么开启,轻松恢复... 亲爱的手机控们,你是否曾经遇到过安卓系统突然停用的情况,让你手忙脚乱,不知所措?别担心,今天就来教你...
安卓系统电池健康度,电池健康度... 你有没有发现,你的安卓手机最近是不是有点儿不给力了?电池续航能力大不如前,充电速度也慢了不少?别急,...
安卓系统按键怎么截图,安卓系统... 你是不是也和我一样,有时候想截个图分享给朋友,却发现安卓手机的截图功能有点神秘呢?别急,今天就来手把...
购票系统安卓源代码,架构设计与... 你有没有想过,那些我们每天离不开的购票系统,它们背后的秘密是什么呢?今天,就让我带你一探究竟,揭开购...
安卓手机系统后台测试,深度解析... 你有没有发现,你的安卓手机后台总是悄悄地忙碌着?别小看了这些后台程序,它们可是手机系统稳定运行的关键...
安卓系统重启的图标,解锁设备新... 手机突然重启,是不是心里有点慌?别急,今天就来和你聊聊安卓系统重启的图标,让你一眼就能认出它,再也不...
车载智慧屏安卓系统,智能出行新... 你有没有发现,现在的车载智慧屏越来越智能了?尤其是那些搭载了安卓系统的,简直就像是个移动的小电脑,不...
安卓系统连上网权限,解锁设备无... 你有没有发现,你的安卓手机里有些应用总是偷偷连上网?别小看这个小小的网络权限,它可是能影响你隐私、消...
安卓谷歌操作系统,探索安卓谷歌... 你知道吗?在智能手机的世界里,有一个操作系统可是无人不知、无人不晓,那就是安卓谷歌操作系统。它就像一...
安卓系统手写%怎样调出,具体实... 你有没有遇到过这种情况:在使用安卓手机的时候,突然想用手写输入法来记录一些灵感或者重要信息,可是怎么...
安卓手机重置 系统设置,轻松恢... 手机用久了是不是感觉卡顿得厉害?别急,今天就来教你怎么给安卓手机来个大变身——重置系统设置!想象你的...
win如何安装安卓系统,Win... 哇,你有没有想过,让你的Win系统也能玩转安卓应用?没错,就是那种在手机上轻松自如的安卓系统,现在也...
苹果qq和安卓系统,跨平台体验... 你有没有发现,现在手机市场上,苹果和安卓的较量可是越来越激烈了呢!咱们就来聊聊这个话题,看看苹果QQ...
显示最好的安卓系统,探索最新旗... 你有没有想过,为什么安卓系统那么受欢迎呢?它就像一个魔法盒子,里面装满了各种神奇的魔法。今天,就让我...
安卓app怎么降级系统,系统版... 你有没有发现,有时候安卓手机的系统更新后,新功能虽然炫酷,但老系统用起来更顺手呢?别急,今天就来教你...
雷军脱离安卓系统,引领科技变革... 你知道吗?最近科技圈可是炸开了锅,因为我们的雷军大大竟然宣布要脱离安卓系统,这可真是让人大跌眼镜啊!...
安卓系统自动开网络,安卓系统自... 你有没有发现,手机里的安卓系统有时候会自动开启网络连接,这可真是让人又爱又恨啊!有时候,你正专心致志...
安卓系统怎样控制后台,因为服务... 手机里的安卓系统是不是感觉越来越卡了?后台程序太多,不仅耗电还影响性能。别急,今天就来教你怎么巧妙地...
安卓系统打游戏推荐,一触即达! 你有没有发现,现在手机游戏越来越好玩了?不管是休闲小游戏还是大型MMORPG,都能在手机上畅玩。但是...