【C++泛型学习笔记】类模板、变量模板和别名模板

2023-05-16

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

类模板

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

#include <iostream>
using namespace std;

template<typename T> //标识符为T的模板参数,表示myvector容器所保存的元素类型
class myvector
{
public:
	typedef T* myiterator;	// 迭代器
public:
	myvector();	//构造函数
    myvector(T tmpt) // 带参数的构造函数
    {
        
    }
	myvector& operator=(const myvector&);	// 重载赋值运算符,在类模板中使用模板名可以不用提供模板参数,如myvector<T>
public:
	void myfunc()
	{
		cout << "mufunc() 被调用" << endl;
	}
public:
	// 迭代器接口
	myiterator mybegin();	//迭代器起始位置
	myiterator myend();		//迭代器结束位置	
};

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

}


int main()
{
	myvector<int> tempvec;	//T被替换成int,即指定模板参数T为int
    myvector tempvec1(6);    // 不用指定模板参数
	tempvec.myfunc();		//调用类模板中的普通成员函数
}

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

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

template<typename T>
A(T,T)->A<T>;

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

template<>
class  myvector<int>
{
public:
	myvector()
	{
		cout << "全特化版本" << endl;
	}

	void myfunc();
};

void myvector<int>::myfunc()
{
	cout << "全特化版本的myfunc" << endl;
}

int main()
{
	myvector<int> tempvec;	
	tempvec.myfunc();
}

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

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

#include <iostream>
using namespace std;

template<typename T> //标识符为T的模板参数,表示myvector容器所保存的元素类型
class myvector
{
public:
	typedef T* myiterator;	// 迭代器
	static int m_stc;	// 静态变量声明
public:
	myvector();	//构造函数
	myvector(T tmpt) // 带参数的构造函数
	{

	}
	myvector& operator=(const myvector&);	// 重载赋值运算符,在类模板中使用模板名可以不用提供模板参数,如myvector<T>
public:
	void myfunc();

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

template<typename T>
myvector<T>::myvector()		// 类外构造函数实现
{
	cout << "泛化版本的构造函数被调用" << endl;
}

template<>
class  myvector<int>
{
public:
	myvector()
	{
		cout << "全特化版本" << endl;
	}

	void myfunc();
};

void myvector<int>::myfunc()
{
	cout << "全特化版本的myfunc" << endl;
}

template<typename T>
void myvector<T>::myfunc()
{
	cout << "泛化版本的普通成员函数myfunc()被调用" << endl;
}

template<>
void myvector<double>::myfunc()
{
	cout << "泛化版本的普通成员函数myfunc()的全特化版本被调用" << endl;
}

template<typename T>
int myvector<T>::m_stc = 10;

template<>
int myvector<double>::m_stc = 100;


int main()
{
	myvector<float> tempvec;	
	tempvec.myfunc();
	cout << tempvec.m_stc << endl;
}

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

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

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

对于默认参数

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

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

typedef TC<int, float> IF_TC;
using IF_TCU = TC<int, float>;

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

成员函数模板

示例如下:

#include <iostream>
using namespace std;

template<typename T1>
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;
	}
	template<typename T2>
	A(T2 v1, T2 v2);	// 构造函数模板

	template<typename T3>
	void myfunc(T3 tmpt)	// 普通成员函模板
	{
		cout << tmpt << endl;
	}
	T1 m_ic;
	static constexpr int m_stcvalue = 200;
};

// 在类外实现类模板的构造函数模板
template<typename T1>	// 先写类模板的模板参数列表
template<typename T2>	// 再写构造函数模板自己的模板参数列表
A<T1>::A(T2 v1, T2 v3)
{
	cout << "A::A(T2,T2)执行了!" << endl;
}


int main()
{
	A<float> a(1, 2);
	a.myfunc(3);
}
  • 类模板中的成员函数,只有在源代码中调用时,对应成员函数才会出现在实例化的类模板中。
  • 类模板中的成员函数模板,只有在源代码中调用时,对应成员函数模板的具体实例才会出现在实例化的类模板中。
  • 编译器目前不支持虚成员函数模板。

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

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

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

类模板嵌套

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

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

变量模板与成员变量模板

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

#include <iostream>
using namespace std;

template<typename T>
T myvar{};	// 变量模板,{}为零初始化

int main()
{
	myvar<int> = 13;
	myvar<double> = 13.1;
	cout << myvar<int> << " " << myvar<double> << endl;
}

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

template<typename T>
T myvar{};	// 泛化版本

template<>
char myvar<int>{};	// 全特化版本,myvar<int>可以当作char类型使用

template<typename T>
T myvar<T *>{120};	// 偏特化版本

template<typename T = int>
T myvar{};		// 默认模板参数

template<typename T, int val>
T myvar[val];		// 非类型模板参数

template<typename T>
class A
{
public:
	template<typename W>
    static W m_tpi;		// 成员变量模板
};

// 成员变量模板在类模板外定义
template<typename T>
template<typename W>
W A<T>::m_tpi = 5;

别名模板与成员别名模板

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

#include <map>

template<typename T>
using str_map_t = std::map<std::string, T>;	// 别名模板

template<typename T>
class A
{
    template<typename T>
	using str_map_t = std::map<std::string, T>;	// 成员别名模板
public:
    str_map_t<int> map1;
};

int main()
{
    str_map_t<int> map1;
    map1.insert({'one', 1});
    map1.insert({'two', 2});
    A<float> obja;
    obja.map1.insert({'one', 1});
}

模板模板参数

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

#include <iostream>
#include <vector>
#include <list>
using namespace std;

template<typename T, template<class> class Container = std::vector>
//template<typename T, template<typename W> typename Container = std::vector>
class myclass
{
public:
	Container<T> myc;
public:
	void func();
	myclass()
	{
		for (int i = 0; i < 10; i++)
		{
			myc.push_back(i);	// 本行代码正确性取决于模板参数类型
		}
	}
};

template<typename T, template<class> class Container>
void myclass<T, Container>::func()
{
	cout << "mstifiy" << endl;
}

int main()
{
	myclass<double, list> mylistobj; // double是容器中的元素类型,list是容器类型
	mylistobj.func();
	std::cout << mylistobj.myc.size() << endl;
}

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

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

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【C++泛型学习笔记】类模板、变量模板和别名模板 的相关文章

  • JDBC的学习(02)

    关于JDBC的学习 xff08 2 xff09 文章目录 关于JDBC的学习 xff08 2 xff09 Blob类型和批量操作1 Blob类型操作2 批量操作 数据库事务1 事务的概念2 事务的ACID属性3 数据库的四种隔离级别数据库的
  • 吐血整理的IDEA个人配置

    IDEA的个人配置 这两天突然发现电脑的浏览器主页被捆绑成hao123的主页了 xff0c 出于各种方法都没能解决这个问题 xff0c 一气之下 xff0c 重装了系统 xff0c 由于忘记了很多软甲的配置都放在C盘下了 xff0c 导致都
  • 两个报错:Ubuntu进入root后无法使用anaconda 和Your shell has not been properly configured to use ‘conda activate‘

    这个是由于我在root权限下没有添加anaconda的路径导致的 首先先进入普通用户 xff0c 看一下自己的anaconda的路径 echo PATH 然后再进入root sudo su 查看一下root下的路径 如果缺失进行添加 exp
  • 抖音品质建设 - iOS启动优化之原理篇

    前言 启动是 App 给用户的第一印象 xff0c 启动越慢用户流失的概率就越高 xff0c 良好的启动速度是用户体验不可缺少的一环 启动优化涉及到的知识点非常多面也很广 xff0c 一篇文章难以包含全部 xff0c 所以拆分成两部分 xf
  • c语言_多线程入门_信号量Semaphore

    也属于线程同步的一种方式 信号量 Semaphore 定义 xff1a 有时被称为信号灯 xff0c 是在多线程环境下使用的一种设施 xff0c 是可以用来保证两个或多个关键代码段不被并发调用 目的 xff1a 类似计数器 xff0c 常用
  • Ubuntu 18.04 LTS 配置VNC Server后 VNC Viewer连接灰屏

    这几天在配置实验室的服务器的时候发现了这一问题 xff0c 前前后后搞了好久 xff0c 后来我也不知道为啥就搞好了 xff0c 网上也有好多类似的解决方法 xff0c 但总感觉每个人都不一样 xff0c 这里就记录一下自己的解决方法 xf
  • debian10 更换阿里源

    阿里云镜像官网 xff1a https developer aliyun com mirror 1 先备份 span class token function cp span etc apt sources list etc apt sou
  • mysql常见问题

    1 错误 xff1a W GPG error http repo mysql com apt debian buster InRelease The following signatures couldn 39 t be verified
  • Unity实现在Android端获取Android手机的唯一ID(设备号)(亲测Android11可用)

    Unity实现在Android端获取Android手机的唯一ID xff08 设备号 xff09 亲测Android11可用 备注 测试版本Unity2020 xff0c 理论上Unity2018以上都可用 xff0c 未做测试 文章初衷
  • 远程桌面连接后闪退的解决方法

    远程桌面连接后闪退的解决方法 xff1a 1 打开注册表编辑器 xff0c 找到Memory Management文件夹 xff1b 2 新建DWORD键值 xff0c 名称为SessionlmageSize xff0c 值为0x00000
  • Java代码实现上传视频获取视频某一帧作为截图封面(二)

    上一个文章讲了Java代码实现上传视频获取视频某一帧作为截图封面的一种方法 xff0c 现在讲述第二种方法 为什么要在这里讲这种方法呢 xff1f 第一 这种方法生成的图片占用的空间更小第二 这种方法可以获取很多信息 一 根上一篇文章一样导
  • IDEA import导入的类明明存在,却飘红,你可以这样做

    今天刚刚代码自己的idea xff0c 发现有的类中出现了爆红的错误提示 明明这个类昨天下班之前测试过了 xff0c 是好用的而且爆红的类还是自己写的 xff0c 在别的类中import进去怎么会提示没有作用呢 想必你也可能会在开发中遇到这
  • mysql数据库sql优化(五)看这里之--覆盖索引

    目录 什么是覆盖索引 xff1f 举例一 举例二 覆盖索引的利弊 好处 弊端 什么是覆盖索引 xff1f 理解方式一 xff1a 索引是高效找到行的一个方法 xff0c 但是一般数据库也能使用索引找到一个列的数据 xff0c 因此它不必读取
  • 抖音品质建设 - iOS启动优化《实战篇》

    前言 启动是 App 给用户的第一印象 xff0c 启动越慢 xff0c 用户流失的概率就越高 xff0c 良好的启动速度是用户体验不可缺少的一环 启动优化涉及到的知识点非常多 xff0c 面也很广 xff0c 一篇文章难以包含全部 xff
  • 如何使用idea来设置文件模板,方便创建配置文件

    很多时候 xff0c 我们需要重复的创建模板文件 比如我们在学习Mybatis的时候 xff0c 需要创建mybatis config xml文件以及xxxmapper xml文件 很多时候这些文件的格式是类似的 xff0c 我们需要做的就
  • Mybatis学习之数据库字段与实体属性的映射

    目录 准备 问题 方式一 xff1a 给查询的字段起别名 方式二 xff1a 修改核心配置文件mybatis config xml 方式三 xff1a 通过resultMap自定义映射 准备 我们的表的字段以及表数据如下 生成的实体类的属性
  • Maven学习之使用idea开发工具创建父工程以及子工程(子模块)

    目录 概述 创建父工程 创建project 配置Maven信息 配置本次新建项目的Maven环境 配置全局的Maven环境 创建Java模块的子工程 创建Web模块的子工程 修改打包方式 概述 之前我们在将Maven的时候都是实用的手动创建
  • Java别在使用普通的照片上传了,你可以使用开源的minio实现图片的上传,方便又简单

    目录 前沿 minio介绍 使用步骤 1 下载minio文件 2 创建文件夹并上传minio文件 3 操作如下指令 编辑 4 访问 5 创建存储桶 6 设置 编辑 7 测试图片上传 8 Java程序的使用步骤 1 在pom中加入如下依赖 2
  • 微信支付的回调函数实现验签以及解密

    当我使用的微信的依赖版本大于0 4 2的时候 xff0c 就可以使用一下方法进行验签和解密 现在我是用的版本是0 4 8 lt dependency gt lt groupId gt com github wechatpay apiv3 l
  • 介绍一款特别好用的java反编译工具jd-gui

    目录 写在前面 开始 写在前面 之前用过另一款java反编译工具jad 但是这个工具有个问题就是对于一些java8的新特性 xff0c 比如lambda表达式是解析不出来的 xff0c 更不用说java9和java17了 关于这款工具的使用

随机推荐