模板初阶<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的两个函数了

相关文章
|
6天前
|
程序员 C++
C++模板元编程入门
【7月更文挑战第9天】C++模板元编程是一项强大而复杂的技术,它允许程序员在编译时进行复杂的计算和操作,从而提高了程序的性能和灵活性。然而,模板元编程的复杂性和抽象性也使其难以掌握和应用。通过本文的介绍,希望能够帮助你初步了解C++模板元编程的基本概念和技术要点,为进一步深入学习和应用打下坚实的基础。在实际开发中,合理运用模板元编程技术,可以极大地提升程序的性能和可维护性。
|
17天前
|
安全 编译器 C++
C++一分钟之-编译时计算:constexpr与模板元编程
【6月更文挑战第28天】在C++中,`constexpr`和模板元编程用于编译时计算,提升性能和类型安全。`constexpr`指示编译器在编译时计算函数或对象,而模板元编程通过模板生成类型依赖代码。常见问题包括误解constexpr函数限制和模板递归深度。解决策略包括理解规则、编写清晰代码、测试验证和适度使用。通过实战示例展示了如何使用`constexpr`计算阶乘和模板元编程计算平方。
36 13
|
14天前
|
存储 编译器 C++
【C++】详解C++的模板
【C++】详解C++的模板
|
4天前
|
Java 编译器 Linux
【c++】模板进阶
本文详细介绍了C++中的模板技术,包括非类型模板参数的概念、如何使用它解决静态栈的问题,以及模板特化,如函数模板特化和类模板特化的过程,以提升代码的灵活性和针对性。同时讨论了模板可能导致的代码膨胀和编译时间增加的问题。
7 2
|
13天前
|
C++ 开发者
C++一分钟之-编译时计算:constexpr与模板元编程
【7月更文挑战第2天】C++的`constexpr`和模板元编程(TMP)实现了编译时计算,增强代码效率。`constexpr`用于声明编译时常量表达式,适用于数组大小等。模板元编程则利用模板进行复杂计算。常见问题包括编译时间过长、可读性差。避免方法包括限制TMP使用,保持代码清晰。结合两者可以解决复杂问题,但需明确各自适用场景。正确使用能提升代码性能,但需平衡复杂性和编译成本。
33 3
|
12天前
|
编译器 C语言 C++
【C++】模板初阶(下)
C++的函数模板实例化分为隐式和显式。隐式实例化由编译器根据实参推断类型,如`Add(a1, a2)`,但`Add(a1, d1)`因类型不一致而失败。显式实例化如`Add&lt;double&gt;(a1, d1)`则直接指定类型。模板函数不支持自动类型转换,优先调用非模板函数。类模板类似,用于创建处理多种数据类型的类,如`Vector&lt;T&gt;`。实例化类模板如`Vector&lt;int&gt;`和`Vector&lt;double&gt;`创建具体类型对象。模板使用时,函数模板定义可分头文件和实现文件,但类模板通常全部放头文件以避免链接错误。
|
12天前
|
机器学习/深度学习 算法 编译器
【C++】模板初阶(上)
**C++模板简介** 探索C++泛型编程,通过模板提升代码复用。模板作为泛型编程基础,允许编写类型无关的通用代码。以`Swap`函数为例,传统方式需为每种类型编写单独函数,如`Swap(int&)`、`Swap(double&)`等,造成代码冗余。函数模板解决此问题,如`template&lt;typename T&gt; void Swap(T&, T&)`,编译器根据实参类型推导生成特定函数,减少重复代码,增强可维护性。模板分函数模板和类模板,提供处理不同数据类型但逻辑相似的功能。
|
12天前
|
算法 编译器 程序员
|
12天前
|
存储 编译器 程序员
|
15天前
|
安全 C++
详细解读c++异常模板复习
详细解读c++异常模板复习