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

相关内容

热门资讯

【MySQL】锁 锁 文章目录锁全局锁表级锁表锁元数据锁(MDL)意向锁AUTO-INC锁...
【内网安全】 隧道搭建穿透上线... 文章目录内网穿透-Ngrok-入门-上线1、服务端配置:2、客户端连接服务端ÿ...
GCN的几种模型复现笔记 引言 本篇笔记紧接上文,主要是上一篇看写了快2w字,再去接入代码感觉有点...
数据分页展示逻辑 import java.util.Arrays;import java.util.List;impo...
Redis为什么选择单线程?R... 目录专栏导读一、Redis版本迭代二、Redis4.0之前为什么一直采用单线程?三、R...
【已解决】ERROR: Cou... 正确指令: pip install pyyaml
关于测试,我发现了哪些新大陆 关于测试 平常也只是听说过一些关于测试的术语,但并没有使用过测试工具。偶然看到编程老师...
Lock 接口解读 前置知识点Synchronized synchronized 是 Java 中的关键字,...
Win7 专业版安装中文包、汉... 参考资料:http://www.metsky.com/archives/350.htm...
3 ROS1通讯编程提高(1) 3 ROS1通讯编程提高3.1 使用VS Code编译ROS13.1.1 VS Code的安装和配置...
大模型未来趋势 大模型是人工智能领域的重要发展趋势之一,未来有着广阔的应用前景和发展空间。以下是大模型未来的趋势和展...
python实战应用讲解-【n... 目录 如何在Python中计算残余的平方和 方法1:使用其Base公式 方法2:使用statsmod...
学习u-boot 需要了解的m... 一、常用函数 1. origin 函数 origin 函数的返回值就是变量来源。使用格式如下...
常用python爬虫库介绍与简... 通用 urllib -网络库(stdlib)。 requests -网络库。 grab – 网络库&...
药品批准文号查询|药融云-中国... 药品批文是国家食品药品监督管理局(NMPA)对药品的审评和批准的证明文件...
【2023-03-22】SRS... 【2023-03-22】SRS推流搭配FFmpeg实现目标检测 说明: 外侧测试使用SRS播放器测...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
初级算法-哈希表 主要记录算法和数据结构学习笔记,新的一年更上一层楼! 初级算法-哈希表...
进程间通信【Linux】 1. 进程间通信 1.1 什么是进程间通信 在 Linux 系统中,进程间通信...
【Docker】P3 Dock... Docker数据卷、宿主机与挂载数据卷的概念及作用挂载宿主机配置数据卷挂载操作示例一个容器挂载多个目...