模板初阶<C++初阶>(跑路人笔记)

简介: 模板初阶<C++初阶>(跑路人笔记)

前言

主要讲述一下模板怎么用,以及注意事项让我们可以使用模板,后续细节会在模板进阶的时候讲到.

先用一下模板给大家看看吧.

template<typename T>
void Swap(T& a, T& b)
{
  T tmp = a;
  a = b;
  b = tmp;
}

我们这个Swap函数就可以完成任何类型的调用(前提是a,b类型相同–原因后面会讲).如我们传两个int两个double我们的Swap函数都可以完美运行.来看看吧


image.png


这个功能的实现其实也是很简单的我们的编译器会根据传来的类型生成一个对应类型的函数,我们先简单看一下VS的反汇编代码吧.


image.png



image.png

看上面两个函数的指令我们就可以看出我们的Swap函数生产了两个.


而这就是模板的主要功能:


函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定 类型版本.


也就是说我们的模板可以无视类型来帮你生成一个相对应类型的函数.


很像auto关键字,但是auto无法作为函数参数,而我们的模板用于函数.


但是模板有他的使用规则.


模板

函数模板

模板使用:


template<typename T1, typename T2,......,typename Tn>
  TN       函数名(TN ..,T. .. , ...){}
//返回值类型 函数名(参数列表){}

image.png


注意: 我们每创建一个模板函数都要在其上方写template<….>两者必须对应.


我们再看看我们上面的例子


template<class T>
void Swap(T& a, T& b)
{
  T tmp = a;
  a = b;
  b = tmp;
}



如果我们还想再创建一个模板函数


如下


image.png


看右图我们的错误演示和左图的正确演示就可以看出了.我们每个模板函数都要与template<..>对应.


模板声明问题

我们大部分人呢,习惯于将声明放在.h的头文件里,把函数主体放在.cpp中再把main函数单独放在另一个.cpp文件中,但是模板函数不允许我们这样做.


来看看问题吧


结构如下(注:声明也是需要加上template<……>)


image.png


发生错误如下,这些是连接错误,其实原因也很简单


image.png


我们的test.cpp因为没办法接收到参数,所以test.cpp也就没办法在创建符号表的时候创建出对应名称的连接处,所以我们的main.cpp部分的文件其实就只是得到了一个声明仅此而已.


类似下面代码,报错也相同.


image.png


毕竟我们的头文件会展开的=.=


问题解决

所以我们如果非要声明让模板部分和main函数部分分隔开的话,我们建议是使用.hpp为后缀的头文件并把模板函数定义和声明都放在.hpp后缀的文件内.


image.png


其实原因也很简单因为我们的.hpp会在main.cpp哪里展开所以其实也就类似于我们没有分开.


但是如果还是想分成.h .cpp .cpp 的格式的话.


我们就可以选择实例化指定.


如下图:我们虽然还是.h .cpp .cpp的格式但是却可以正常运行


image.png


这样我们的test.cpp部分函数会因为下面的实例化指定在汇编的时候生成对应声明的实例化函数.


来看看linux的反汇编是否和我们推理相同呢?


image.png


是的通过objdump工具我可以看出我们的两种不同Swap函数(注:模板生成的函数的反汇编名字和普通函数反汇编名有所不同.)


(ps:如果想自己操作的话请转至[linux操作](# linux操作))


类模板

结构如下:


template<class T1, class T2, ..., class Tn>
class 类模板名
{
 // 类内成员定义
};



事例如下:


// 动态顺序表
// 注意:Vector不是具体的类,是编译器根据被实例化的类型生成具体类的模具
template<class T>
class Vector
{
public:
  Vector(size_t capacity = 10)
  : _pData(new T[capacity])
  , _size(0)
  , _capacity(capacity)
  {}
  // 使用析构函数演示:在类中声明,在类外定义。
  ~Vector();
  void PushBack(const T& data);
  void PopBack();
  // ...
  T& operator[](size_t pos)
  {
  assert(pos < _size);
  return _pData[pos];
  }
private:
  T* _pData;
  size_t _size;
  size_t _capacity;
};
// 注意:类模板中函数放在类外进行定义时,需要加模板参数列表
template <class T>
Vector<T>::~Vector()
{
  if (_pData)
  delete[] _pData;
  _size = _capacity = 0;
}




注意我们上面的类只是一个模板我们实例化格式如下


// Vector类名,Vector<int>才是类型

Vector<int> s1;

Vector<double> s2;



其实在使用的时候也就是在类名和对象名之间加个<类型>.


模板使用实例

先看看函数模板的使用吧.


如果我们想两个不同类型的做函数形参要这么办呢?


很简单如下:


我们可以创建个两个模板参数的模板.


#include<iostream>
using namespace std;
template <typename T1, typename T2>
void Test(T1 a, T2 b)
{
  return;
}
int main()
{
  Test(1, 1.0);
  return 0;
}



模板参数也可以当做函数的返回值.


而且我们可以给我们的模板参数赋予缺省值


赋值方式如下


template <typename T1 = char, typename T2 = double>


我们也可以在使用的时候显示实例化来创建一个指定类型的模板实例化.


#include<iostream>
using namespace std;
template <typename T1 = char, typename T2 = double>
void Test(T1 a, T2 b)
{
  return;
}
int main()
{
  Test<int, double>(1, 1.0);//其中<>里的两个类型会分别传给T1和T2
  return 0;
}


如果我们定义了一个和模板功能名称相同的函数会发生什么呢?


代码如下


#include<iostream>
using namespace std;
int Add(int a, int b)
{
  return a + b;
}
template <typename T>
T Add(T& a, T& b)
{
  return a + b;
}
int main()
{
  int a = 1;
  int b = 2;
  Add<int>(a, b);
  Add(a, b);
  return 0;
}



让我们到linux里看看


image.png


虽然在我们的印象里他们是相同的但是其实在反汇编后的命名还是不相同的.所以两者可以共存.


linux操作

创建两个文件一个是test.cpp 一个是main.cpp 然后把代码放入


//test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
template<class T>
void Swap(T& a, T& b);
template<class T>
void Swap(T& a, T& b)
{
  T tmp = a;
  a = b;
  b = tmp;
}
template
void Swap<int>(int& a, int& b);
template
void Swap<double>(double& a, double& b);
//main.cpp
#include<iostream>
using namespace std;
template<class T>
void Swap(T& a, T& b);
int main()
{
  int a = 1;
  int b = 2;
  Swap(a, b);
  double c = 1.2;
  double d = 2.1;
  Swap(c, d);
  cout << "a>:  " << a << endl << "b>:  " << b << endl << "c>:  " << c << endl << "d>:  " << d << endl;
  return 0;
}



保存退出后

在命令行分别写下如下指令

g++ test.cpp main.cpp -o TEST

objdump -S TEST

就可以查找到Swap的两个函数了

相关文章
|
2月前
|
存储 算法 C++
C++ STL 初探:打开标准模板库的大门
C++ STL 初探:打开标准模板库的大门
122 10
|
1月前
|
安全 编译器 C++
【C++11】可变模板参数详解
本文详细介绍了C++11引入的可变模板参数,这是一种允许模板接受任意数量和类型参数的强大工具。文章从基本概念入手,讲解了可变模板参数的语法、参数包的展开方法,以及如何结合递归调用、折叠表达式等技术实现高效编程。通过具体示例,如打印任意数量参数、类型安全的`printf`替代方案等,展示了其在实际开发中的应用。最后,文章讨论了性能优化策略和常见问题,帮助读者更好地理解和使用这一高级C++特性。
54 4
|
1月前
|
算法 编译器 C++
【C++】模板详细讲解(含反向迭代器)
C++模板是泛型编程的核心,允许编写与类型无关的代码,提高代码复用性和灵活性。模板分为函数模板和类模板,支持隐式和显式实例化,以及特化(全特化和偏特化)。C++标准库广泛使用模板,如容器、迭代器、算法和函数对象等,以支持高效、灵活的编程。反向迭代器通过对正向迭代器的封装,实现了逆序遍历的功能。
36 3
|
1月前
|
编译器 C++
【c++】模板详解(1)
本文介绍了C++中的模板概念,包括函数模板和类模板,强调了模板作为泛型编程基础的重要性。函数模板允许创建类型无关的函数,类模板则能根据不同的类型生成不同的类。文章通过具体示例详细解释了模板的定义、实例化及匹配原则,帮助读者理解模板机制,为学习STL打下基础。
33 0
|
2月前
|
编译器 程序员 C++
【C++打怪之路Lv7】-- 模板初阶
【C++打怪之路Lv7】-- 模板初阶
20 1
|
2月前
|
存储 编译器 C++
【C++篇】引领C++模板初体验:泛型编程的力量与妙用
【C++篇】引领C++模板初体验:泛型编程的力量与妙用
50 9
|
2月前
|
编译器 C语言 C++
C++入门6——模板(泛型编程、函数模板、类模板)
C++入门6——模板(泛型编程、函数模板、类模板)
70 0
C++入门6——模板(泛型编程、函数模板、类模板)
|
2月前
|
算法 编译器 C++
【C++篇】领略模板编程的进阶之美:参数巧思与编译的智慧
【C++篇】领略模板编程的进阶之美:参数巧思与编译的智慧
97 2
|
2月前
|
存储 算法 编译器
【C++】初识C++模板与STL
【C++】初识C++模板与STL
|
2月前
|
编译器 C++
【C++】模板进阶:深入解析模板特化
【C++】模板进阶:深入解析模板特化
106 0