【C++】再谈模板,深入理解C++模板

news2024/12/22 16:57:36

深入理解C++模板

  • typename和class的区别
  • 非类型模板参数
  • 模板的特化
    • 函数模板特化
    • 类模板特化
      • 全特化
      • 偏特化
  • 模板分离编译
    • 模板的分离编译
    • 解决方法
  • 总结
  • 🍀小结🍀

🎉博客主页:小智_x0___0x_

🎉欢迎关注:👍点赞🙌收藏✍️留言

🎉系列专栏:C++初阶

🎉代码仓库:小智的代码仓库

typename和class的区别

在C++中,typenameclass关键字都可以用于模板参数声明,它们的作用是相同的,用于指定一个类型参数。但是,在一些情况下,typenameclass更加灵活。

首先,当模板参数是一个嵌套类型的时候,必须使用typename关键字来告诉编译器这是一个类型而不是一个静态成员变量或者函数。例如:

template<class Container>
void Print(const Container& v)
{
	// 编译不确定Container::const_iterator是类型还是对象
	// typename就是明确告诉编译器这里是类型,等模板实例化再去找
	typename Container::const_iterator it = v.begin(); //必须使用 typename 关键字
	auto it = v.begin();//当然使用auto就不会有以上的各种问题
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

【补充】:当模板参数是一个模板类型的时候,必须使用class关键字来指定这个模板参数。例如:

template <class T, template <class> class Container>
class MyClass {
public:
    Container<T> c;
};

在这个例子中,Container是一个模板类,它接受一个类型参数,因此必须使用class关键字来指定。

总的来说,虽然在大多数情况下typenameclass可以互换使用,但是在一些特殊情况下,必须使用其中的一个关键字来保证代码正确性。

非类型模板参数

  • 模板参数分类类型形参与非类型形参。
  • 类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
  • 非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。

静态栈是一种使用数组实现的栈,它的大小在编译时确定。我们可以使用非类型模板参数来指定静态栈的大小。下面是一个简单的静态栈的实现:

template <typename T, int N>
class StaticStack {
public:
    void push(const T& value) {
        if (size_ < N) {
            data_[size_++] = value;
        } else {
            cout<<"StaticStack is full"<<endl;
        }
    }

    void pop() {
        if (size_ > 0) {
            --size_;
        } else {
            cout<<"StaticStack is empty"<<endl;
        }
    }

    T& top() {
        if (size_ > 0) {
            return data_[size_ - 1];
        } else {
            cout<<"StaticStack is empty"<<endl;
        }
    }

    bool empty() const {
        return size_ == 0;
    }

    int size() const {
        return size_;
    }

private:
    T data_[N];
    int size_ = 0;
};

在这个例子中,StaticStack是一个模板类,它接受两个参数:T表示栈中元素的类型,N表示栈的大小。data_是一个长度为N的数组,用于存储栈中的元素。其他成员函数实现了栈的基本操作。

我们可以使用这个静态栈来存储任何类型的数据,并且在编译时指定栈的大小。例如,下面的代码创建了一个能够存储10个整数的静态栈:

StaticStack<int, 10> s;
s.push(1);
s.push(2);
s.push(3);
std::cout << s.top() << std::endl; // 输出 3
s.pop();
std::cout << s.top() << std::endl; // 输出 2

在这里插入图片描述

在这个例子中,我们使用了非类型模板参数10来指定静态栈的大小。这使得我们可以在编译时就确定静态栈的大小,从而避免了动态分配内存的开销,提高了代码的效率。

【注意】:

  1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
  2. 非类型的模板参数必须在编译期就能确认结果。

模板的特化

在C++中,模板的特化是指为某些特定的模板参数提供一个专门的实现。模板特化可以用于优化代码、解决特殊情况下的问题等。

模板特化有两种形式:完全特化和部分特化。完全特化是指为某些特定的模板参数提供一个完全不同的实现,而部分特化是指为某些特定的模板参数提供一个更为通用的实现。

函数模板特化

函数模板的特化步骤:

  1. 必须要先有一个基础的函数模板
  2. 关键字template后面接一对空的尖括号<>
  3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
  4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
template<class T>
bool Less(T left, T right)
{
    return left < right;
}

// 函数模板的特化
template<>
bool Less<int*>(int* left, int* right)
{
	return *left < *right;
}

但是一般函数遇到需要特化的情况可以直接重载:

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

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;

	double c = 1.1, d = 2.2;
	cout << Less(&c, &d) << endl;

	return 0;
}

在这里插入图片描述

类模板特化

全特化

全特化是指为某些特定的模板参数提供一个完全不同的实现。在C++中,全特化是指为所有的模板参数提供一个专门的实现。全特化可以用于优化代码、解决特殊情况下的问题等。

下面是一个使用全特化的例子:

template <typename T>
class MyClass {
public:
    void print() {
        std::cout << "Generic implementation" << std::endl;
    }
};

template <>
class MyClass<int> {
public:
    void print() {
        std::cout << "Specialized implementation for int" << std::endl;
    }
};

template <>
class MyClass<double> {
public:
    void print() {
        std::cout << "Specialized implementation for double" << std::endl;
    }
};

int main() {
    MyClass<char> c1;
    c1.print(); // 输出 "Generic implementation"

    MyClass<int> c2;
    c2.print(); // 输出 "Specialized implementation for int"

    MyClass<double> c3;
    c3.print(); // 输出 "Specialized implementation for double"

    return 0;
}

在这个例子中,MyClass是一个模板类,我们为MyClass<int>MyClass<double>提供了专门的实现。当我们创建一个MyClass<int>或者MyClass<double>对象时,它会调用对应的专门实现;而对于其他类型的对象则会使用通用的实现。

需要注意的是,在使用全特化时,必须使用空的模板参数列表来表示这是一个特化版本。例如,上面例子中的template <> class MyClass<int>template <> class MyClass<double>就是空的模板参数列表。

总的来说,全特化可以使得模板更加灵活和通用,同时也可以优化代码性能。但是,在使用全特化时需要谨慎,避免过度使用导致代码难以维护。

偏特化

偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。比如对于以下模板类:

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

偏特化有两种表现方式:

  • 部分特化
    将模板参数类表中的一部分参数特化。
// 将第二个参数特化为int
template <class T1>
class Data<T1, int>
{
public:
    Data() { cout << "Data<T1, int>" << endl; }
private:
    T1 _d1;
    int _d2;
};
  • 参数更进一步的限制
    偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版
    本。
//两个参数偏特化为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public:
    Data() { cout << "Data<T1*, T2*>" << endl; }

private:
    T1 _d1;
    T2 _d2;
};
//两个参数偏特化为引用类型
template <typename T1, typename T2>
class Data <T1&, T2&>
{
public:
    Data(const T1& d1, const T2& d2)
        : _d1(d1)
        , _d2(d2)
    {
        cout << "Data<T1&, T2&>" << endl;
    }

private:
    const T1& _d1;
    const T2& _d2;
};
void test()
{
    Data<double, int> d1; // 调用特化的int版本
    Data<int, double> d2; // 调用基础的模板
    Data<int*, int*> d3; // 调用特化的指针版本
    Data<int&, int&> d4(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;
}
//Test.cpp
#include"a.h"
int main()
{
    Add(1, 2);
    Add(1.0, 2.0);

    return 0;
}

我们以上面这个例子来测试一下模板分离编译>

在这里插入图片描述
可以发现这里报错了。
在这里插入图片描述

解决方法

  1. 将声明和定义放到一个文件 xxx.cpp 里面或者xxx.h其实也是可以的。推荐使用这种。
  2. 模板定义的位置显式实例化。这种方法不实用,不推荐使用。
template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}
template int Add<int>(const int& left, const int& right);//显示实例化
template double Add<double>(const double& left, const double& right);

这样实例化之后就可以正常编译运行了。

总结

【优点】:

  1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
  2. 增强了代码的灵活性

【缺点】:

  1. 模板会导致代码膨胀问题,也会导致编译时间变长
  2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误

🍀小结🍀

今天我们学习了深入理解C++模板相信大家看完有一定的收获。
种一棵树的最好时间是十年前,其次是现在! 把握好当下,合理利用时间努力奋斗,相信大家一定会实现自己的目标!加油!创作不易,辛苦各位小伙伴们动动小手,三连一波💕💕~~~,本文中也有不足之处,欢迎各位随时私信点评指正!
本篇代码已上传gitee仓库

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux---详解进程信号

进程信号 &#x1f373;信号理解&#x1f9c8;什么是信号&#xff1f;&#x1f95e;进程信号&#x1f953;查看系统信号&#x1f969;在技术角度理解信号&#x1f357;注意 &#x1f356;信号处理&#x1f9c7;信号异步机制 &#x1f354;信号产生&#x1f35f;通过终端按键产生…

解决VScode下载太慢的问题记录

最近突然想重新下载vscoded便携免安装版&#xff0c;发现下载很慢&#xff0c;于是乎查询一下&#xff0c;以便记录 下载地址 VScode官方网站&#xff1a; https://code.visualstudio.com/ 根据个人的需求选择下载&#xff0c;页面加载下载需要等一会&#xff0c; 然后就会…

Oracle输出文本平面(CSV、XML)文本数据详细过程

此过程是提供给前端,调用的接口,为报表提供”下载“功能。以下是本人在测试环境的测试,有什么不足的地方,请留言指教,谢谢。 1、测试表 分别对测试表输出csv、xml两种格式文件数据。前期的准备工作。 --在服务器端创建directory,用管理员用户 create or replace directo…

Python系列学习第二章-Python语言基本语法元素

hello&#xff0c;这里是Token_w的文章&#xff0c;主要讲解python的基础学习&#xff0c;希望对大家有所帮助 整理不易&#xff0c;感觉还不错的可以点赞收藏评论支持&#xff0c;感谢&#xff01; Python程序说它可以倒背如流&#xff0c;人类的你要不要默写一下保留字来试试…

Android 之 Paint API —— ColorFilter (颜色过滤器) (2-3)

本节引言&#xff1a; 上一节中我们讲解了Android中Paint API中的ColorFilter(颜色过滤器)的第一个子类&#xff1a; ColorMatrixColorFilter(颜色矩阵颜色过滤器)&#xff0c;相信又开阔了大家的Android图像处理视野&#xff0c; 而本节我们来研究它的第二个子类&#xff1a;L…

h5百度地图聚合---切换tab时,聚合不能清除

项目&#xff1a;taro3vue3 描述&#xff1a;切换tab的时候用map.clearOverlays清除&#xff0c;但是地图缩放下聚合又出现了 解决&#xff1a;地图组件监听makers的时候 if (oldVal.length) {map.clearOverlays()markerClusterer.clearMarkers() }

数仓学习---13、报表数据导出

星光下的赶路人star的个人主页 莫见长安行乐处&#xff0c;空令岁月易蹉跎 文章目录 一、报表数据导出1.1 MySQL建库建表1.1.1 创建数据库1.1.2 创建表 1.2 数据导出1.2.1 DataX配置文件生成脚本1.2.2 编写每日导出脚本 一、报表数据导出 为方便报表应用使用数据&#xff0c;需…

解决 cannot execute binary file: Exec format error

问题&#xff1a;cannot execute binary file: Exec format error 解决 cannot execute binary file: Exec format error 原因&#xff1a; "cannot execute binary file: Exec format error" 错误通常发生在尝试执行一个不兼容的二进制文件时。这可能是因为你正在…

python中使用cProfile可视化并解决性能瓶颈问题

大家好&#xff0c;帕累托法则讲到&#xff1a;“在大多数情况下&#xff0c;80%的结果来自于20%的原因。”作为一名程序员&#xff0c;当代码运行速度不尽如人意时&#xff0c;就需要花费大量时间对代码进行相应的重构&#xff0c;但在许多情况下&#xff0c;所得到的速度提升…

【Python入门系列】第十八篇:Python自然语言处理和文本挖掘

文章目录 前言一、Python常用的NLP和文本挖掘库二、Python自然语言处理和文本挖掘1、文本预处理和词频统计2、文本分类3、命名实体识别4、情感分析5、词性标注6、文本相似度计算 总结 前言 Python自然语言处理&#xff08;Natural Language Processing&#xff0c;简称NLP&…

吴恩达ChatGPT《LangChain Chat with Your Data》笔记

文章目录 1. Introduction2. Document Loading2.1 Retrieval Augmented Generation&#xff08;RAG&#xff09;2.2 Load PDFs2.3 Load YouTube2.4 Load URLs2.5 Load Notion 3. Document Splitting3.1 Splitter Flow3.2 Character Splitter3.3 Token Splitter3.4 Markdown Spl…

如何在3ds max中创建可用于真人场景的巨型机器人:第 3 部分

推荐&#xff1a; NSDT场景编辑器助你快速搭建可二次开发的3D应用场景 1. 创建腿部装备 步骤 1 打开 3ds Max。 打开在本教程最后一部分中保存的文件。 打开 3ds Max 步骤 2 转到创建> 系统并单击骨骼。 创建>系统 步骤 3 为的 侧视口中的腿&#xff0c;如下图所示…

【C++】开源:Linux端ALSA音频处理库

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍Linux端ALSA音频处理库。 无专精则不能成&#xff0c;无涉猎则不能通。。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&#xff0c…

12.(开发工具篇vscode+git)vscode 不能识别npm命令

1&#xff1a;vscode 不能识别npm命令 问题描述&#xff1a; 解决方式&#xff1a; &#xff08;1&#xff09;右击VSCode图标&#xff0c;选择以管理员身份运行&#xff1b; &#xff08;2&#xff09;在终端中执行get-ExecutionPolicy&#xff0c;显示Restricted&#xff…

vue2项目迁移到vue3中的改动——基础积累

最近在跟着大神学习vue3的内容&#xff0c;发现之前vue2写的代码可以直接照搬到vue3中&#xff0c;但是有一些需要改动的内容&#xff0c;下面做一下记录。 1.定义对象时&#xff0c;需要指定每个属性值 例如&#xff1a;listQuery:{} 如果使用&#xff1a;listQuery.Filter…

vue3+elementplus后台管理系统,实现侧边栏菜单显示到主内容区域

目录 1 创建页面2 设置路由3 修改首页4 首页的完整代码总结 我们已经使用vue3和elmentplus初步搭建了首页&#xff0c;上一篇中有个问题没解决&#xff0c;就是在侧边栏导航功能里&#xff0c;如果点击菜单希望是在首页打开页面而不是跳转到新页面。以下是我们希望实现的效果 这…

B/B+树算法

B树 基本概述 B树又称多路平衡搜索树。一棵m阶B树&#xff0c;要么是空树&#xff0c;要么满足以下特性&#xff1a; 每个节点最多有m棵子树根节点至少有两棵子树内部节点&#xff08;除根和叶子节点以外的节点&#xff09;至少有⌈m/2⌉棵子树关键字个数比子树个数少1终端节…

字符函数和字符串函数解析及模拟实现

字符函数和字符串函数解析及模拟实现 1. 求字符串长的函数1.1[strlen](https://legacy.cplusplus.com/reference/cstring/strlen/?kwstrlen)1.2 strlen()模拟实现 2. 长度不受限制的字符串函数2.1[strcpy](https://legacy.cplusplus.com/reference/cstring/strcpy/?kwstrcpy)…

数据结构与算法——什么是队列(队列存储结构)

队列&#xff0c;和栈一样&#xff0c;也是一种对数据的"存"和"取"有严格要求的线性存储结构。 与栈结构不同的是&#xff0c;队列的两端都"开口"&#xff0c;要求数据只能从一端进&#xff0c;从另一端出&#xff0c;如下图所示&#xff1a; 通…

vue中使用jsMind生成思维导图 截图功能踩坑

npm i jsmind先安装&#xff0c;再引入 import jsmind/style/jsmind.css import jsMind from jsmind/js/jsmind.js require(jsmind/js/jsmind.draggable.js) require(jsmind/js/jsmind.screenshot.js)正常引入是这样的&#xff0c;然后渲染也没问题 <template><div …