模板再认识

在前面的文章中我写了关于模板的一些简单的认识,现在我们来再次认识模板

文章目录

  • 1.非类型模板参数
  • 2.模板特化
    • 1). 模板特化的写法
    • 2). 类模板特化
    • 3). 函数模板特化
    • 4). 模板全/偏特化
  • 3.模板分离编译

1.非类型模板参数

在模板中还有一种是非类型的模板参数。我们代码展示:

#define N 10template<class T>
class stack {
private:T* _arr[N];size_t size;size_t capacity;
};int main()
{stack<int> st1;return 0;
}

这是我们以前编写静态容器的方式,通过宏定义来实现,但是有一个缺陷,那就是我们假如还要建立一个大小为100的静态栈呢?把N改成一百吗?我那我们有想要一个大小为10的静态栈和大小为10000的静态栈呢?这时候就会非常浪费空间。所以这时候就有了非类型模板参数的出现。

template <class T, size_t N>
class stack {
private:T* _arr[N];size_t size;size_t capacity;
};int main()
{stack<int, 10> st1;stack<int, 100> st2;return 0;
}

在这里插入图片描述
这样就可以解决上面的问题了。非类型模板参数目前只支持整形(char, short,int)
在这里插入图片描述
可以看到STL中有一个容器静态数组也用了非类型模板参数。

2.模板特化

关于模板我们有函数模板,类模板,模板中也有一种新的知识,叫做模板特化。

1). 模板特化的写法

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

2). 类模板特化

话不多说,上代码:

#include <iostream>using namespace std;namespace xxx{template<class T>class stack{public:stack(){cout << "stack():template<class T>" << endl;}};template<>class stack<int>{public:stack(){cout << "stack():template<>" << endl;}};
}int main()
{xxx::stack<double> s1;xxx::stack<int> s2;return 0;
}

在这里插入图片描述
我们可以看到当两种实例化的对象他们所调用的构造函数是不同的,原因是当对象实例化的时候,他们会去找“最符合”的类来实例化自己。

下面那个类就是模板的特化。特化是为了处理一些类型错误的场景。下面举个例子:

class A
{
public:A(int aa, int a):_aa(aa), _a(a){}bool operator>(const A& y) const{if (_aa > y._aa) return true;else if (_aa == y._aa){if (_a > y._a) return true;else return false;}else return false;}
private:int _aa = 0;int _a = 0;
};template <class T>
class Greater {
public:bool operator()(const T& x, const T& y){return x > y;}
};template <>
class Greater<A*> {
public:bool operator()(const A* x,  const A* y){return *x > *y;}
};int main()
{A a1(100, 10);A a2(120, 10);cout << Greater<A>()(a1, a2) << endl;A* pa1 = new A(100, 1);A* pa2 = new A(122, 1);cout << Greater<A*>()(pa1, pa2) << endl;return 0;
}

我们定义了这么一个类,他里面有两个int成员,比较大小的方法经过了重载。现在哦我们用一个仿函数,来比较他们的大小。
在这里插入图片描述

我们发现,我们相比较的是对象的内容,但是第二组比较显然是比较了他俩的地址大小(由于申请内存时申请的地方不确定,所以大小关系也就不确定),所以为了不出现这样的情况,我们对仿函数特化一下:

class A
{
public:A(int aa, int a):_aa(aa), _a(a){}bool operator>(const A& y) const{if (_aa > y._aa) return true;else if (_aa == y._aa){if (_a > y._a) return true;else return false;}else return false;}
private:int _aa = 0;int _a = 0;
};template <class T>
class Greater {
public:bool operator()(T& x, T& y){return x > y;}
};template <>
class Greater<A*> {
public:bool operator()(A* x,  A* y){return *x > *y;}
};int main()
{A a1(100, 10);A a2(120, 10);cout << Greater<A>()(a1, a2) << endl;A* pa1 = new A(100, 1);A* pa2 = new A(122, 1);cout << Greater<A*>()(pa1, pa2) << endl;return 0;
}

在这里插入图片描述
可以发现这样就解决了。

3). 函数模板特化

同理照搬即可,只是写法上与类模板有点不一样

class A
{
public:A(int aa, int a):_aa(aa), _a(a){}bool operator>(const A& y) const{if (_aa > y._aa) return true;else if (_aa == y._aa){if (_a > y._a) return true;else return false;}else return false;}
private:int _aa = 0;int _a = 0;
};template <class T>
bool Greater(const T& x, const T& y)
{return x > y;
}template <>
bool Greater<A*>(const A* x, const A* y)
{return *x > *y;
}int main()
{A a1(100, 10);A a2(120, 10);cout << Greater(a1, a2) << endl;A* pa1 = new A(100, 1);A* pa2 = new A(122, 1);cout << Greater(pa1, pa2) << endl;return 0;
}

如果把上述代码,运行的话,会发现有语法错误,错误出现在仿函数的特化中。只需要这么写就可以了:

template <class T>
bool Greater(const T& x, const T& y)
{return x > y;
}template <>
bool Greater<A*>(A* const & x, A* const  & y)
{return *x > *y;
}

但是这么写有点恶心,但是我们换一种写法:

template <class T>
bool Greater(const T& x, const T& y)
{return x > y;
}//template <>
//bool Greater<A*>(A* const & x, A* const  & y)
//{
//	return *x > *y;
//}bool Greater(const A*  x, const A*  y)
{return *x > *y;
}

下面这个函数,当函数模板实例化出模板函数的时候会与那个函数构成重载。不需要模板特化就可以写的简单易懂,也能实现要求。所以类模板推荐使用特化,函数模板不推荐

4). 模板全/偏特化

这个很好理解,结合开始对模板特化的理解,类模板实例化的成模板类的时候会找“最匹配的”类来实例化:

template<class T1, class T2>
class A
{
public:A(){cout << "A:template<class T1, class T2>" << endl;}};
//偏特化
template<class T1>
class A<T1, int>
{
public:A(){cout << "A:template<class T1>" << endl;}};
//全特化
template<>
class A<int, int>
{
public:A(){cout << "A:template<>" << endl;}};int main()
{A<double, double> a1;A<double, int> a2;A<int, int> a3;return 0;
}

在这里插入图片描述

3.模板分离编译

我们先写三个文件:
test.cpp

#include "func.h"int main()
{printf<int>();return 0;
}

func.cpp

#include "func.h"template<class T>
void printf()
{cout << "void printf()" << endl;
}

func.h

#pragma once
#include <iostream>
using namespace std;template<class T>
void printf();

我们写完后,发现写的没啥问题,但是一运行就出错了。出的是一个链接错误。这其中的原因是:
在形成可执行程序的过程中。首先要预处理,编译,汇编,链接过程最后才能形成可执行程序。
在编译过程检查语法的时候,test.cpp文件里因为有了print函数的声明,所以没有报错,而func.cpp中的print函数它是一个模板函数,模板是需要成为可执行程序后运行起来需要他时才会生成模板函数或模板类,所以编译器就没有实现这个函数。自然在链接过程就没有对应的函数地址来生成符号表。所以会报链接错误,所以我们应该实例化实现才可以:
func.cpp:

template<>
void printf<int>()
{cout << "void printf()" << endl;
}

类模板也会出现这样的情况。
test.cpp

#include "func.h"int main()
{A<int> a;return 0;
}

func.cpp

#include "func.h"template<class T>
A<T>::A()
{cout << "A()" << endl;
}

func.h

#pragma once
#include <iostream>
using namespace std;template<class T>
class A {
public:A();
};

这样也会出现链接错误,也是得显式写明类型。

func.cpp

#include "func.h"template<>
A<int>::A()
{cout << "A()" << endl;
}

所以,还是建议写模板的时候,不要声明定义分离了。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

人工智能(6):机器学习基础环境安装与使用

1 库的安装 整个机器学习基础阶段会用到Matplotlib、Numpy、Pandas等库&#xff0c;为了统一版本号在环境中使用&#xff0c;将所有的库及其版本放到了文件requirements.txt当中&#xff0c;然后统一安装 新建一个用于人工智能环境的虚拟环境 mkvirtualenv ai matplotlib3.8…

DALL·E 3:OpenAI的革命性图像生成模型与ChatGPT的融合

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

Unity⭐️Win和Mac安卓打包环境配置

文章目录 🟥 配置Android SDK1️⃣ 配置 SDK Platforms2️⃣ 配置 SDK Tools🎁 Android SDK Build-Tools🎁 Android SDK Command-line Tools(latest)🎁 Android SDK Tools(Obsolete)🟧 配置NDK🟩 配置JDK前情提示: 此方法适用于Windows/Mac 在配置时注意开启 🪜 …

ubuntu安装Anaconda

下载 Anaconda 进入 Ubuntu&#xff0c;自己新建下载路径&#xff0c;输入以下命令开始下载 注意&#xff0c;如果不是 x86_64&#xff0c;需要去镜像看对应的版本&#xff08;https://mirrors.bfsu.edu.cn/anaconda/archive/?CM&OA&#xff09; wget https://mirrors.…

蓝桥杯中级题目之组合(c++)

系列文章目录 数位递增数_睡觉觉觉得的博客-CSDN博客拉线开关。_睡觉觉觉得的博客-CSDN博客蓝桥杯中级题目之数字组合&#xff08;c&#xff09;_睡觉觉觉得的博客-CSDN博客 文章目录 系列文章目录前言一、个人名片二、描述三、输入输出以及代码示例1.输入2.输出3.代码示例 总…

怎么从A和B仓库执行分别fetch操作?

目录 1.问题2.描述3.解决问题 1.问题 我希望从A仓库拉代码后推送到B仓库&#xff0c;结果A仓库代码新增分支后 在执行fetch时默认仓库地址为B仓库&#xff0c;导致fetch失败。 2.描述 在实际项目开发中我们可能会出现需要将同一个服务的代码推送到不同的代码仓库&#xff0c…

【C++】C++11新特性之右值引用与移动语义

文章目录 一、左值与左值引用二、右值与右值引用三、 左值引用与右值引用比较四、右值引用使用场景和意义1.左值引用的短板2.移动构造和移动赋值3.STL中右值引用的使用 五、万能引用与完美转发1.万能引用2.完美转发 一、左值与左值引用 在C11之前&#xff0c;我们把数据分为常…

开源博客项目Blog .NET Core源码学习(5:mapster使用浅析)

开源博客项目Blog使用mapster框架映射对象&#xff0c;主要是在数据库表对象及前端数据对象之间进行映射&#xff0c;本文学习并记录项目中mapster的使用方式。   App.Hosting项目的program文件中调用builder.Services.AddMapper函数进行对象模型自动映射&#xff0c;而该函数…

【C++】:类和对象(中)之拷贝构造函数+赋值运算符重载

拷贝构造函数 概念 在现实生活中&#xff0c;可能存在一个与你一样的自己&#xff0c;我们称其为双胞胎 那在创建对象时&#xff0c;可否创建一个与已存在对象一某一样的新对象呢&#xff1f; 拷贝构造函数&#xff1a;只有单个形参&#xff0c;该形参是对本类类型对象的引用…

在 Ubuntu 22.04安装配置 Ansible

一、按官网指引安装 我使用的ubuntu22.04版本&#xff0c;使用apt安装。官网指引如下&#xff1a; $ sudo apt-get install software-properties-common $ sudo apt-add-repository ppa:ansible/ansible $ sudo apt-get update $ sudo apt-get install ansible 由于内部网络…

Cloud Studio连接MySQL,Access denied for一系列问题

官方文档有写如何安装Mysql $ apt update $ apt install mysql-server mysql-client -y$ service mysql start mysql -uroot -p123456进入MySQL命令行 问题出在连接数据库这一步&#xff0c;命令行能进去&#xff0c;但是数据库插件和代码都连不上 Access denied for 大概率…

【网络协议】聊聊网关 NAT机制

再宿舍的时候&#xff0c;其实只能通过局域网进行处理&#xff0c;但是如果接入互联网&#xff0c;一般是配置路由器当然还有网关。 MAC头和IP头的细节 一旦配置了IP地址和网关&#xff0c;就可以制定目标地址进行访问。 MAC头主要信息目标和源MAC地址&#xff0c;以及协议类…