关于模板的大致认识【C++】

news/2024/12/5 12:34:54/

文章目录

  • 函数模板
    • 函数模板的原理
    • 函数模板的实例化
    • 模板参数的匹配原则
  • 类模板
    • 类模板的定义格式
    • 类模板的实例化
  • 非类型模板参数
  • typename 与class
  • 模板的特化
    • 函数模板特化
    • 类模板特化
      • 全特化
      • 偏特化
  • 模板的分离编译

函数模板

函数模板的原理

template <typename T> //模板参数 ——类型 
void Swap(T& x1, T& x2)
{T tmp = x1;x1 = x2;x2 = tmp;
}
int main()
{int a = 0, b = 1;double c = 1.1, d = 2.2;swap(a, b);swap(c, d);int* p1 = &a;int* p2 = &b;swap(p1, p2);return 0;
}

函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器
在这里插入图片描述

在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此

函数模板的实例化

用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化

1、隐式实例化:让编译器根据实参推演模板参数的实际类型

template<class T>
T Add(const T& left , const T& right)
{return left + right;
}
int main()
{int a1 = 10, a2 = 20;double d1 = 10.1, d2 = 20.2;//函数模板根据调用,自动推导模板参数的类型 ,实例化对应的参数 cout << Add(a1, a2) << endl;cout << Add(d1, d2) << endl;//实参传递的类型, 推演T的类型  cout << Add( a1, (int)d1  ) << endl;cout << Add( (double)a1, d1) << endl;//显示实例化 ,用指定的类型实例化 ,相当于隐式类型转换 cout << Add<int> (a1, d1) << endl;cout << Add<double>(a1, d1) << endl;return 0;
}

2、显式实例化:在函数名后的<>中指定模板参数的实际类型

template<class T>
T Add(const T& left , const T& right)
{return left + right;
}
int main()
{int a1 = 10, a2 = 20;double d1 = 10.1, d2 = 20.2;//函数模板根据调用,自动推导模板参数的类型 ,实例化对应的参数 cout << Add(a1, a2) << endl;cout << Add(d1, d2) << endl;//实参传递的类型, 推演T的类型  cout << Add( a1, (int)d1  ) << endl;cout << Add( (double)a1, d1) << endl;//显示实例化 ,用指定的类型实例化 ,相当于隐式类型转换 cout << Add<int> (a1, d1) << endl;cout << Add<double>(a1, d1) << endl;return 0;
}
template<class T >T * Alloc(int n )
{return new T[n];
}int main(){//有些函数无法自动推导函数模板的类型,实例化对应的参数,只能显式实例化double *p1 = Alloc <double>(10);return 0;}

模板参数的匹配原则

类模板

类模板 ,无法推演实例化,所以类模板都是显式实例化

class Stack
{
public :Stack(int capacity = 3){_array= new  T[capacity];_size = 0;_capacity = 0; }void Push(const T &  data){_array[_size++] = data;}~Stack(){free(_array);_size = _capacity = 0;}
private :T * _array;int _size;int _capacity;
};
int main()
{Stack <int> s1(); // int Stack <double> s2();//double Stack <char> s3();//char//Stack <int ,doule> s2();return 0; 
}

类模板的定义格式

函数类模板的声明和定义分离

template<class T>class Stack
{
public://声明 Stack(int capacity );void Push(const T& data){_array[_size++] = data;}~Stack(){free(_array);_size = _capacity = 0;}
private:T* _array;int _size;int _capacity;
};
//定义 
template<class T>Stack<T>::Stack(int capacity )
{_array = new  T[capacity];_size = 0;_capacity = 0;
}
int main()
{Stack <int> s1(); // int Stack <double> s2();//double Stack <char> s3();//char//Stack <int ,doule> s2();return 0;
}

对于普通类,类名和类型是一样的,但是对于类模板 ,类名和类型是不一样的 上面的代码中Stack是类名 ,但是Stack < T >是类型

类模板的实例化

非类型模板参数

模板参数可分为类型形参非类型形参

类型形参: 出现在模板参数列表中,跟在class或typename关键字之后的参数类型名称。

非类型形参: 用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。

如果此时有一个需求,实现一个静态数组的类,就需要用到非类型模板参数

template<class T, size_t N> //N:非类型模板参数
// N是常量 ,且N必须是整形
class StaticArray
{
public:size_t arraysize(){return N;}
private:T _array[N]; //利用非类型模板参数指定静态数组的大小
};
int main()
{StaticArray<int, 10> a1; //定义一个大小为10的静态数组cout << a1.arraysize() << endl; //10StaticArray<int, 100> a2; //定义一个大小为100的静态数组cout << a2.arraysize() << endl; //100return 0;
}

注意:

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

typename 与class

一般来说,typename 和class 没有什么区别,但是在有一种情景下是有区别的

template<class Container>
void Print(  const Container& v  )
{//Container::const_iterator it = v.begin();是不行的// 因为编译不确定Container::const_iterator是类型还是对象// typename的作用就是明确告诉编译器这里是类型,等模板实例化再去找typename 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> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);Print(lt1);return 0;
}

以上情景需要使用typename

模板的特化

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

模板的特化 即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。
模板特化中分为函数模板特化 和 类模板特化。

函数模板特化

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;
}
int main()
{int a = 1, b = 2;cout << Less(1, 2);cout << endl;cout << Less(&a, &b);return 0;
}

类模板特化

不仅函数模板可以进行特化,类模板也可以针对特殊类型进行特殊化实现,并且类模板的特化又可分为全特化和偏特化(半特化)。

全特化

全特化即是将模板参数列表中所有的参数都确定化。

例如,对于以下类模板:

template<class T1, class T2>
class Data
{
public:Data() {cout<<"Data<T1, T2>" <<endl;}private:T1 _d1;T2 _d2;
};template<>
class Data<int, char>
{
public:Data() {cout<<"Data<int, char>" <<endl;}private:int _d1;char _d2;
};void TestVector()
{Data<int, int> d1;Data<int, char> d2;
}

偏特化

偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。

偏特化有以下两种表现方式:
1、部分特化 ,将模板参数类表中的一部分参数特化

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;
};
int main()
{Data<double, int> d1;//偏特化Data<int, double> d2;//调用基础的模板return 0;
}

2、参数更进一步的限制

template<class T1, class T2>
class Data
{
public:Data(){cout<<"Data<T1, T2>" <<endl;}
private:T1 _d1;T2 _d2;
};
//偏特化:对类型的进一步限制
template<class T1, class T2>
class Data<T1*, T2*>
{
public:Data() { cout << "Data<T1*, T2*>" << endl; }
private:
};int main()
{Data<int, int > d1;Data<int, double > d2;Data<int*, double > d3;Data<int*, double* > d4;Data<void*, void* > d5;return 0;
}

模板的分离编译

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

在分离编译模式下,我们一般创建三个文件

一个头文件用于进行函数声明
一个源文件用于对头文件中声明的函数进行定义
最后一个源文件用于调用头文件当中的函数

举个例子:如果对一个加法函数模板进行分离编译

在这里插入图片描述

如果这三个文件生成可执行文件时,会在链接阶段产生报错
这是为什么呢?

C / C++程序要运行起来一般要经历以下四个步骤:
预处理
编译
汇编
链接
如果需要详细的了解这四个步骤,请点击这里

在这里插入图片描述

这三个文件经过预处理后就只剩下两个文件了

Visual Studio平台:

预处理后就进行编译,
虽然在 main.i 当中有调用Add函数的代码,但是在 main.i 里面也有Add函数模板的声明,因此在编译阶段并不会发现任何语法错误,之后便顺利将 Add.i 和 main.i 翻译成了汇编语言,

到汇编阶段,此阶段利用 Add.s 和 main.s 这两个文件分别生成了两个目标文件,

前面的预处理、编译和汇编都没有问题,现在就需要将生成的两个目标文件进行链接操作了,但在链接时发现,在main函数当中调用的两个Add函数实际上并没有被真正定义,主要原因是函数模板并没有生成对应的函数,因为在全过程中都没有实例化过函数模板的模板参数T,所以函数模板根本就不知道该实例化T为何类型的函数。

模板分离编译失败的原因:
在函数模板定义的地方(Add.cpp)没有进行实例化,而在需要实例化函数的地方(main.cpp)没有模板函数的定义,无法进行实例化。

Linux平台下:

预处理后就需要进行编译,就生成了 Add.s 和 main.s 文件。

汇编:生成了 Add.o 和 main.o 两个目标文件。

如果你觉得这篇文章对你有帮助,不妨动动手指给点赞收藏加转发,给鄃鳕一个大大的关注你们的每一次支持都将转化为我前进的动力!!!


http://www.ppmy.cn/news/1052998.html

相关文章

远程控制:用了向日葵控控A2后,我买了BliKVM v4

远程控制电脑的场景很多&#xff0c;比如把办公室电脑的文件发到家里电脑上&#xff0c;但是办公室电脑旁边没人。比如当生产力用的电脑一般都比较重&#xff0c;不可能随时带在身边&#xff0c;偶尔远程操作一下也是很有必要的。比如你的设备在工况恶劣的环境中&#xff0c;你…

linux 免交互

Linux 免交互 1、免交互概念2、基本免交互的例子2.1命令行免交互统计2.2使用脚本免交互统计2.3使用免交互命令打印2.4免交互修改密码2.5重定向查看2.6重定向到指定文件2.7重定向直接指定文件2.8使用脚本完成重定向输入2.9免交互脚本完成赋值变量2.10关闭变量替换功能&#xff0…

Transformer在医学影像中的应用综述-分类

文章目录 COVID-19 Diagnosis黑盒模型可解释的模型 肿瘤分类黑盒模型可解释模型 视网膜疾病分类小结 总体结构 COVID-19 Diagnosis 黑盒模型 Point-of-Care Transformer(POCFormer)&#xff1a;利用Linformer将自注意的空间和时间复杂度从二次型降低到线性型。POCFormer有200…

09 数据库开发-MySQL

文章目录 1 数据库概述2 MySQL概述2.1 MySQL安装2.1.1 解压&添加环境变量2.1.2 初始化MySQL2.1.3 注册MySQL服务2.1.4 启动MySQL服务2.1.5 修改默认账户密码2.1.6 登录MySQL 2.2 卸载MySQL2.3 连接服务器上部署的数据库2.4 数据模型2.5 SQL简介2.5.1 SQL通用语法2.3.2 分类…

NLP与大模型主题全国师资培训班落地,飞桨持续赋能AI人才培养

为了推动大模型及人工智能相关专业人员的培养&#xff0c;8月11日-8月13日&#xff0c;由中国计算机学会主办、机械工业出版社、北京航空航天大学、百度飞桨联合承办 “CCF群星计划之文心高校行- NLP与大模型”主题师资培训班&#xff08;以下简称培训班&#xff09;在北京天信…

数据库范式使用规范

好的设计会尽可能少的引入冗余数据&#xff0c;或做有损拆分&#xff0c;而是使用规范的方法找到正确的分解。而范式则是关系数据库实现设计优化的通用手段。范式与关系数据库的关系可以参考笔者之前的WIKI。 【强制】数据库设计优先满足第三范式(3NF)&#xff0c;如果无法满足…

无脑入门pytorch系列(五)—— nn.Dropout

本系列教程适用于没有任何pytorch的同学&#xff08;简单的python语法还是要的&#xff09;&#xff0c;从代码的表层出发挖掘代码的深层含义&#xff0c;理解具体的意思和内涵。pytorch的很多函数看着非常简单&#xff0c;但是其中包含了很多内容&#xff0c;不了解其中的意思…

Mysql with as定义子查询

文章目录 1. 定义2. 适用场景3. 语法4. 示例 1. 定义 使用with as 可以让子查询重用相同的with查询块&#xff0c; 并在select查询块中直接引用&#xff0c; 一般用在select查询块会多次使用某个查询sql时&#xff0c; 会把这个sql语句放在with as 中&#xff0c; 作为公用的表…