<C++> 模板-下

目录

前言

一、非类型模板参数

二、类模板的特化

1. 概念

2. 函数模板特化

3. 类模板特化

4. 全特化

5. 偏特化

5.1 特化部分参数

5.2 对某些类型的进一步限制

三、模板的分离编译

1. 概念

2. 分离编译

3. 解决方法

1. 显式实例化 

2.  在一个文件内写声明和定义

四、模板总结

1. 优点

2. 缺点

总结


前言

        一般情况下template<class / typename T> 中 classtypename 没有什么区别,但是有一些特殊情况,typename 与 class 是有区别的

        例如:我们泛型了Print函数,实现可以打印任意容器内容的功能

template<class Container>
void Print(const Container& v)
{
	Container::const_iterator it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

int main()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	Print(v);

	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	Print(lt);
	return 0;
}

        但是编译不通过,编译器要求必须在 Container::const_iterator 之前加上 typename 作为前缀,即

	typename Container::const_iterator it = v.begin();

问题:为什么需要加上typename呢?

  • 这是因为编译器无法分辨 Container::const_iterator 究竟是什么,因为此时Container还没有实例化,所以 Container::const_iterator 它可能表示的是类的静态成员变量,也可能是类的内嵌类型(内部类或typedef的类型)
class A
{
public:
	int begin()
	{
		return 0;
	}
	static int const_iterator;
};

int main()
{

	A aa;
	A::const_iterator it = aa.begin();
    编译器报错,因为A::const_iteratot本身是变量,不是类型
    所以,如果没有typename,编译器是不知道它究竟是类型还是变量

	return 0;
}
  • Container可能是类型或对象,若为类型,语法可以通过;若为对象,语法错误

解决:

        在 Container::const_iterator 前加上typename,以此来告诉编译器后面的Container::const_iterator是一个类型,后面的行为是合乎语法的,等Container实例化之后,在确定后面是什么类型

总结:

  • Container是一个没有实例化的模板,那么都需要在Container::iterator 之前加上typename,即使是vector<T>类型
  • 只要我们使用没有实例化的类模板时,都需要加上typename声明,例如priority_queue模板参数less在使用未实例化的模板时就加上了typename

这里我们也可以使用auto关键字,

auto it = v.begin();

就不需要typename声明 。在Container之后 auto 自动推导类型


一、非类型模板参数

        有时候,我们可能需要非类型的模板参数,来实现所需的类

例如:当我们实现静态的栈时,需要提前知道数组需要开辟多大的空间,但是如果都统一的开同样大的空间,难免会遇到空间冗余或太小等情况。所以,当我们在实例化对象时,如果可以指定数组空间大小,那么就可以解决上面的问题,此时C++11就升级了模板,增加了非类型模板参数,以实现将一个常量作为模板参数

template<class T, size_t N>
class Stack
{

private:
	T _a[N];
	int _top;
};

int main()
{
	Stack<int, 10> st1;
	Stack<int, 100> st2;

	return 0;
}

对于非模板类型参数

        1. 只能为整型(char也可以),但例如double、string等类型都不可以

        2. 是常量        

因为非模板类型参数大部分都是用在T _a[N]情况,所以最初设计的是整型

在std中还有一个容器array,它就使用了size_t非模板类型参数

array<int, 10> a;

        它对比C语言的数组几乎没有任何优势,这就显得委员会更新的很鸡肋,array的优点几乎只有对越界情况检查,越界读写都能检查,而普通数组不能检查越界读,少部分越界写可以检查,但如果仅此优点,vector完全可以替代。

 

二、类模板的特化

1. 概念

        在类名或函数名后用<>指定特化的参数,特化后的类的成员变量根据自己的需求编写,因为它与原模版已经成为了不同的类,成为了分支。一般特化的类都是很小的类

        顾名思义,对模板类型进行特殊化处理,如果调用时,实参类型与特化的模板参数类型匹配,则优先调用特化的模板函数或类。

        没有特化也可以,但是有特化会更方便

        通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果。

2. 函数模板特化

template<class T>
bool Less(T left, T right)
{
	return left < right;
}

int main()
{
	cout << Less(1, 2) << endl;

	int a = 1, b = 2;
	cout << Less(&a, &b) << endl;
	return 0;
}

        当我们实参传递地址时,比较的是地址的大小,这与我们的意愿相反,这时我们可以使用特化这一功能,针对 int* 类型特化。在调用时,有现成的函数的就用现成的;没现成的用模板实例化合适的函数:

template<class T>
bool Less(T left, T right)
{
	return left < right;
}

template<>
bool Less<int*>(int* left, int* right)
{
	cout << "bool Less<int*>(int* left, int* right)" << endl;
	return *left < *right;
}

        但是,如果只是为了比较int型指针指向的值的大小,我们可以不使用模板,直接重载函数即可


bool Less(int* left, int* right)
{
	cout << "bool Less(int* left, int* right)" << endl;

	return *left < *right;
}

        如果是多类型指针,那么还是需要使用模板

template<class T>
bool Less(T* left, T* right)
{
	cout << "bool Less(T* left, T* right)" << endl;
	return *left < *right;
}

3. 类模板特化

        上面是针对函数模板举例,对于函数模板的特化,可以使用重载替代,但是对于类模板的特化就不同了

template<class T1, class T2>
class Data
{
public:
	Data()
	{
		cout << "Data<T1, T2>" << endl;
	}
private:
	T1 _d1;
	T2 _d2;
};

template<>
class Data<int, double>
{
public:
	Data()
	{
		cout << "Data<int, double>" << endl;
	}
};

        又例如priority_queue中Less仿函数的特化,因为在Less仿函数编写时,已经讲解了如果实参是Date*类型,那么每次比较的是指针的大小,这是错误的,所以我们可以特化类型Date*

template<class T>
class Less
{
public:
    bool operator()(const T& x, const T& y)
    {
        return x < y;
    }
};
template<>
class Less<Date*>
{
public:
    这里去掉&符号,因为const修饰不到&,会优先修饰*
    bool operator()(const Date* x, const Date* y)
    {
        return *x < *y;
    }
};

        特化后,就可以在实例化时只传一个参数,编译器用第一个模板参数去匹配,若与特化版本类型相同,则直接使用特化版本

priority_queue<Date*> pq;

        还可以改为模板T*,适配所有指针类型

template<class T1, class T2>
class Less<T1*, T2*>
{
public:
    bool operator()(const T* x, const T* y)
    {
        return *x < *y;
    }
};

4. 全特化

如果将全部的模板类型都特化即为全特化,全特化后template<>内将没有内容

template<>
class Data<int, double>
{
public:
	Data()
	{
		cout << "Data<int, double>" << endl;
	}
private:
    //T1 _d1;
    //T2 _d2;
};

        这样的特化,会在实参类型为int、double时被调用

5. 偏特化

5.1 特化部分参数

        只将一部分模板类型特化即为偏特化

        适配此类型(T, double)时,优先调用

template<class T1>
class Data<T1, double>
{
public:
	Data()
	{
		cout << "Data<T1, double>" << endl;
	}
private:
	//T1 _d1;
};

若第二个不是double,则会调用T1, T2模板

5.2 对某些类型的进一步限制

        例如:限制参数类型全部都是指针类型时

template<class T1, class T2>
class Data<T1*, T2*>
{
public:
	Data()
	{
		cout << "Data<T1*, T2*>" << endl;
	}
private:
};
template<>
class Data<int, double>
{
public:
	Data()
	{
		cout << "Data<int, double>" << endl;
	}
private:
    //T1 _d1;
    //T2 _d2;
};

        也可以特化引用

template<class T1, class T2>
class Data<T1&, T2&>
{
public:
	Data()
	{
		cout << "Data<T1&, T2&>" << endl;
	}
private:
};

三、模板的分离编译

1. 概念

        一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式
        模板不支持直接的分离编译

2. 分离编译

// a.h
template<class T>
T Add(const T& left, const T& right);

// a.cpp
template<class T>
T Add(const T& left, const T& right)
{
     return left + right;
}

// main.cpp
#include"a.h"
int main()
{
     Add(1, 2);
     Add(1.0, 2.0);
 
     return 0;
}

C/C++程序运行要经历四个阶段

        预处理——>编译——>汇编——>链接

预处理 :替换头文件,将.h文件在cpp文件展开,生成 .i 文件

编译:对程序按照语言特性进行词法、语法、语义分析是否错误,并确认已定义的函数的地址,只有声明的函数没有地址,检查无误后生成 .s 汇编文件。因为有声明,这是一种承诺,函数的定义部分编译器会在链接步骤拿着修饰后的函数名去其他文件符号表寻找,所以编译检查可以通过。

汇编:生成 .o 文件,即二进制文件

链接:将文件链接起来,生成 a.out文件

        程序链接错误,Add找不到函数地址,这就是模板分离编译的坏处 

3. 解决方法

1. 显式实例化 
template<class T>
T Add(const T& left, const T& right)
{
     return left + right;
}

template
class Add<int>;

template
class Add<double>;
  •  这样就失去了模板的意义,对于每一种类型都需要手动显式实例化,所以不推荐这种解决方法
2.  在一个文件内写声明和定义
  • 类内部一般将代码量较短的直接写在类内部,成为内联函数,对于代码量较长的模板函数,可以在类内部声明,在类外部但在同一文件内定义

四、模板总结

1. 优点

  • 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库STL因此而产生
  • 增强了代码的灵活性,如适配器、仿函数

2. 缺点

  • 模板会导致代码膨胀问题,导致编译时间变长
  • 出现模板编译错误时,错误信息很乱,不容易定位错误

总结

        模板还是有很多细节需要掌握的,了解模板知识后再练习大多数问题就可以解决了

        最后,如果小帅的本文哪里有错误,还请大家指出,请在评论区留言(ps:抱大佬的腿),新手创作,实属不易,如果满意,还请给个免费的赞,三连也不是不可以(流口水幻想)嘿!那我们下期再见喽,拜拜!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/172165.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于nodejs学校宿舍管理系统-计算机毕设 附源码45118

nodejs学校宿舍管理系统 摘要 信息化社会内需要与之针对性的信息获取途径&#xff0c;但是途径的扩展基本上为人们所努力的方向&#xff0c;由于站在的角度存在偏差&#xff0c;人们经常能够获得不同类型信息&#xff0c;这也是技术最为难以攻克的课题。针对学校宿舍管理系统等…

Java_异常详解

前言 异常是什么,异常如何抛出,如何抛出自定义异常,异常处理主要的五个关键字&#xff1a;throw,try,catch,finally,throws ,异常的处理流程 异常是什么 在Java中&#xff0c;将程序执行过程中发生的不正常行为称为异常。比如之前写代码时经常遇到的&#xff1a; 1. 算数异…

PySide6 Tutorials (一)表格小部件魔改

前言 Pyside6官方教程给了一个使用表格显示颜色的教程&#xff0c;原教程地址如下&#xff1a;源地址&#xff0c; 结合前面button信号的学习&#xff0c;就魔改添加了如下功能&#xff1a;增加一列按钮&#xff0c;可以修改该行的颜色值&#xff0c;通过点击按钮生成指定的颜…

口袋参谋:找关键词的三种方法!

​如何找热搜关键词&#xff1f;99%的商家都不知道。那么今天可以根据我说的三种方法去做。 第一种方法&#xff1a;利用竞争对手 通过分析竞争对手&#xff0c;正在使用和采取何种优化方法&#xff0c;来帮助你理解市场上正在流行什么样的关键字&#xff0c;这些热词可以直接从…

uniapp中swiper 轮播带左右箭头,点击切换轮播效果demo(整理)

可以点击箭头左右切换-进行轮播 <template><view class"swiper-container"><swiper class"swiper" :current"currentIndex" :autoplay"true" interval"9000" circular indicator-dotschange"handleSw…

golang指针学习

package mainimport "fmt"func main() {name:"飞雪无情"nameP:&name//取地址fmt.Println("name变量的内存地址为:",&name)fmt.Println("name变量的值为:",name)fmt.Println("name变量的内存地址为:",nameP)fmt.Prin…

中大型企业网搭建(毕设类型)

毕业设计类别 某大学网络规划与部署 目录 某大学网络规划与部署 第一章项目概述 1.1 项目背景 1.2 网络需求分析 第二章网络总体设计方案 2.1 网络整体架构 2.2 网络设计思路 第三章 网络技术应用 3.1 DHCP 3.2 MSTP 3.3 VRRP 3.4 OSPF 3.5 VLAN 3.6 NAT 3.7 WLAN 3…

完美解决:yum -y install nginx 报出 没有可用软件包 nginx。错误:无须任何处理

目录 一、问题&#xff1a; 二、原因&#xff1a; 三、解决方法&#xff1a; 一、问题&#xff1a; [rootlocalhost ~]# yum -y install nginx 已加载插件&#xff1a;fastestmirror Loading mirror speeds from cached hostfile * base: mirrors.bfsu.edu.cn * extras: m…

纽扣电池/含纽扣电池产品上架亚马逊各国法规标准要求16 CFR 第 1700.15/20 ANSI C18.3M(瑞西法案认证)

亚马逊纽扣电池认证标准有哪些&#xff1f; 一、美国站&#xff08;亚马逊纽扣电池/含纽扣电池商品&#xff09;安全测试标准要求&#xff1a; 16 CFR 第 1700.15 、16 CFR 第 1700.20 ANSI C18.3M、警示标签声明要求&#xff08;第 117-171 号公众法&#xff09; 二、澳大…

SQL的连接join

一、连接说明 union、intersect等集合运算&#xff0c;它的特征是以 “行” 为单位进行操作&#xff0c;通俗点说&#xff0c;就是进行这些集合运算&#xff0c;会导致记录行数的增减&#xff0c;使用union会增加记录行数&#xff0c;使用 intersect 或 expect 会减少行记录&a…

java中,通过替换word模板中的关键字后输出一个新文档

一、要用到的jar包 我已上传了相关的jar包&#xff0c;需要的可以通过以下链接直接下载&#xff1a; https://download.csdn.net/download/qq_27387133/88558034 具体jar包截图&#xff1a; 二、实现的代码 注意&#xff1a;文件要用docx格式!!! word变量替换的方法&#…

模板初阶学习

✨前言✨ &#x1f4d8; 博客主页&#xff1a;to Keep博客主页 &#x1f646;欢迎关注&#xff0c;&#x1f44d;点赞&#xff0c;&#x1f4dd;留言评论 ⏳首发时间&#xff1a;2023年11月21日 &#x1f4e8; 博主码云地址&#xff1a;博主码云地址 &#x1f4d5;参考书籍&…

docker-compose安装harbor

docker-compose安装harbor 环境&#xff1a;centos7 1、安装docker 官方文档 https://docs.docker.com/engine/install/centos/ 1、卸载旧版本 $ sudo yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate …

设计模式—结构型模式之享元模式

设计模式—结构型模式之享元模式 享元模式(Flyweight Pattern)&#xff0c;运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象&#xff0c;而这些对象都很相似&#xff0c;状态变化很小&#xff0c;可以实现对象的多次复用。对象结构型。 在享元模式中可以共…

linux制作 ext4镜像image 脚本demo

结构如下&#xff1a; build_linux_targetfs.sh #!/bin/bashCHECK_MARK"\033[0;32m\xE2\x9C\x94\033[0m" X_MARK"\033[0;1;31mX\033[0m"export TOP_DIR$PWD export TARGET_IMAGE_PATH$TOP_DIR/filesystem/targetfs-images export BSP_IMAGE_PATH${TOP_DI…

vue项目中element-ui对话框el-dialog嵌套显示时多了一个遮罩层解决办法

在对话框里又嵌套了一个对话框展示时&#xff0c;多了一个遮罩层&#xff0c;如下图所示&#xff1a; 解决办法如下&#xff1a; 给对话框添加append-to-body 属性&#xff0c;参考以下代码&#xff1a; <el-dialog :visible.sync"dialogVisible" append-to-body …

56、修改Integer缓存上限

第一步&#xff1a; 第二步&#xff1a; 第三步&#xff1a;修改Integer缓存上限 运行代码 Testpublic void integerTest() {int a 100;Integer b 100;System.out.println(a b);Integer a1 Integer.valueOf(127);Integer b1 127;System.out.println(a1 b1);Integer …

html滑动文章标题置顶

position: sticky; 基于用户的滚动位置来定位 首先封装一个组件 例如&#xff1a;AAA组件&#xff08;注意&#xff0c;只能有一层盒子&#xff0c;不能在外面继续包一层div&#xff09; <template><div class"box">{{title}}</div> </templa…

企业域名邮箱申请流程指南:轻松搭建高效的企业邮箱系统

对于企业和个人来说拥有自己的域名和邮箱是展示形象和开展业务的重要工具&#xff0c;很多初学者可能对企业域名邮箱申请流程感到迷惑。企业域名邮箱申请流程分两步申请域名和创建邮箱&#xff0c;本文将详细介绍这两个步骤&#xff0c;帮助大家更好地理解和操作。 一、申请域名…

【Linux】缓冲区+磁盘+动静态库

一、缓冲区 1、缓冲区的概念 缓冲区的本质就是一段用作缓存的内存。 2、缓冲区的意义 节省进程进行数据IO的时间。进程使用fwrite等函数把数据拷贝到缓冲区或者外设中。 3、缓冲区刷新策略 3.1、立即刷新&#xff08;无缓冲&#xff09;——ffush() 情况很少&#xff0c…