您的位置:首页 > 健康 > 养生 > ab客外贸营销下载_怎样建立平台_广东河源最新疫情_seo教程 seo之家

ab客外贸营销下载_怎样建立平台_广东河源最新疫情_seo教程 seo之家

2025/3/12 0:38:47 来源:https://blog.csdn.net/2301_77578940/article/details/143169145  浏览:    关键词:ab客外贸营销下载_怎样建立平台_广东河源最新疫情_seo教程 seo之家
ab客外贸营销下载_怎样建立平台_广东河源最新疫情_seo教程 seo之家

【C++】模板

  • 泛型编程
  • 函数模板
    • 函数模板的使用
    • 函数模板的原理
    • 函数模板的实例化
      • 隐式实例化
      • 显式实例化
    • 模板参数匹配原则
  • 类模板
    • 类模板的使用
    • 类模板的实例化
    • 类模板的原理
  • 非类型参数
  • 按需实例化
  • 模板的特化
    • 函数模板特化
    • 类模板特化
      • 全特化
      • 偏特化/半特化
  • 模板分离编译

泛型编程

实现一个 int 类型的交换函数,可以这样写:

void swap(int& left, int& right)
{int tmp = left;left = right;right = tmp;
}

如果有交换其他类型的需要,可以进行函数重载

void swap(int& left, int& right)
{int tmp = left;left = right;right = tmp;
}void swap(double& left, double& right)
{double tmp = left;left = right;right = tmp;
}void Swap(char& left, char& right)
{char temp = left;left = right;right = temp;
}

这种编程方式的缺点:

  1. 代码复用率低,重载的函数仅仅是类型不同
  2. 灵活性差,如果有新的类型交换需求,就需要手动重载相应的函数

所以这里引入泛型编程,泛型编程允许我们编写一份多种数据类型通用的代码,无需为每种类型编写相同的逻辑,是一种代码复用的手段。就像是提供一种模具,然后往模具中填充不同的材料(类型),获得相似但又不同的部件(完整代码)

在这里插入图片描述

模板是 C++ 中实现泛型编程的主要工具,模板分为函数模板类模板

函数模板

函数模板的使用

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

其中,class 和 typename 关键字任选一个使用

使用模板写一个交换函数

template <class T>
void my_swap(T& left, T& right)
{T tmp = left;left = right;right = tmp;
}

测试:

在这里插入图片描述

函数模板的原理

函数模板本身并不是一个函数,而是编译器用来实例化具体函数的模具,函数模板并没有减少应该写的代码。例如 int 类型的 swap 和 double 类型的 swap,这些函数的代码都由编译器根据模板来生成

编译阶段,编译器需要根据传入的参数类型推演生成相应数据类型的函数代码,以供调用

在这里插入图片描述

所以我们写函数模板,轻松了自己,辛苦了编译器

函数模板的实例化

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

隐式实例化

编译器根据参数类型,自动推演函数类型,并实例化出函数

template <class T>
T Add(const T& x, const T& y)
{return x + y;
}int main()
{int a = 1, b = 2;Add(a, b); // 隐式实例化double c = 1.1, d = 2.2;Add(c, d); // 隐式实例化return 0;
}

显式实例化

当出现以下情况时,隐式实例化会编译出错

int a = 1;
double b = 1.1;
Add(a, b);

在这里插入图片描述

这是因为第一个参数是 int,那么编译器就会将函数模板中的 T 实例化为 int;但是函数的第二个参数是 double,而 T 只有一个且已经实例化为 int,不可以再实例化为 double,所以不会匹配。

那么 double 不会强转为 int 吗?可以是可以,但是编译器一般不会主动进行强转操作

要解决以上问题,有两种方式:

  1. 传参时,将 double 类型的 b 强转为 int 类型
Add(a, (int)b);
  1. 显式实例化:在函数名后加<>,并在<>中指定函数模板使用的类型
Add<int>(a, b);

模板参数匹配原则

  1. 非模板函数可以和函数模板名字相同,它们可以同时存在
// 非模板函数
int Add(const int& x, const int& y)
{cout << "Add(const int& x, const int& y)" << endl;return x + y;
}
// 函数模板
template <class T1, class T2>
T1 Add(const T1& x, const T2& y)
{cout << "Add(const T1& x, const T2& y)" << endl;return x + y;
}
  1. 如果函数调用的参数同时匹配非模板函数和函数模板,那么会优先调用非模板函数
int a = 1, b = 1;
Add(a, b); // 优先调用非模板函数

在这里插入图片描述

可以这样理解:

  • 函数模板只是食材,想吃的话还得动手做成饭菜
  • 非模板函数是现成的饭菜,可以直接拿来吃
  • 如果两者都存在,肯定是优先选择现成的,省时省力
  1. 如果函数模板推演生成出的函数更加匹配,那就使用函数模板

在这里插入图片描述

类模板

类模板的使用

template <class/typename T1, class/typename T2,...,class/typename Tn>
class 类名
{// ...
};

同样,class 和 typename 关键字任选一个使用

编写一个类模板,A类中的成员变量 _a 可以是任何类型

template <class T>
class A
{
public:A(const T& val = T()):_a(val){}
private:T _a;
};

类模板的实例化

类模板的实例化必须在类模板名字后面加<>,在<>指定实例化的类型

A<int> a(1);
A<double> b(1.1);

在这里插入图片描述

类模板的原理

类模板并不是真正的类,只有编译器实例化出的类才是真正的类

同样是在编译阶段,编译器根据参数类型实例化出相应数据类型的类,原理与函数模板相同,这里不再赘述

非类型参数

模板参数不仅可以是类型形参,还可以是非类型形参

  • 类型形参:跟在 class/typename 之后的参数,表示数据类型
  • 非类型形参:通常是用一个常数作为模板的参数,在模板中将该参数作为常量使用

例如,Array 类模板,在这个类模板的参数中

  • T 是类型参数
  • N 是非类型参数
template <class T, size_t N = 10>
class Array
{
public:T& operator[](const size_t pos){return _array[pos];}size_t size(){return _size;}
private:T _array[N];size_t _size = N;
};

非类型参数的作用在这里类似,可以指定静态数组的大小,但是更加灵活,可以在实例化对象时显式传参,以此来限定每个静态数组的大小

Array<int, 10> a1;
Array<double, 5> a2;

在这里插入图片描述

注意:在 C++20 之前,非类型参数只可以是**:**

  1. 整型
  2. 枚举类型
  3. 指针或者引用

C++20 之后,非类型参数的类型限制有所放宽,浮点数、类对象以及字符串允许作为非类型模板参数

按需实例化

类模板的实例化并不是一股脑全部实例化,编译器会按需实例化,即只会实例化用到的成员

例如,故意在成员函数 operator[] 中写一个 bug

template <class T, size_t N = 10>
class Array
{
public:T& operator[](const size_t pos){size(1);// bugreturn _array[pos];}size_t size(){return _size;}
private:T _array[N];size_t _size = N;
};

但是不调用 operator[],就不会出错

void test5()
{Array<int, 10> a1;Array<double, 5> a2;
}

在这里插入图片描述

调用,编译器会实例化,那么就会检查出语法错误

在这里插入图片描述

我们编写的模板,可以理解为一个半成品,编译器只会检查一些基本的语法,例如检查; ()等。在预处理阶段,编译器会将模板实例化成具体的类,之后进行语法编译

模板的特化

使用模板我们可以只编写一份多种类型通用的代码,剩下的交给编译器来做,但是对于一些特殊情况可能会得到错误结果,我们需要特殊处理这些特殊情况

此时有一个用于小于比较的函数模板

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

以下操作可以得到正确结果:

cout << Less(1, 2) << endl;
cout << Less(2.2, 1.1) << endl;

在这里插入图片描述

但是如果传递的是指针,就会得出错误的结果

在这里插入图片描述

传入指针,我们想要的是比较指针指向的值,而模板比较的是指针

此时,就需要对模板进行特化:在原有模板基础上,针对特殊类型做出特殊处理。模板特化分为函数模板特化类模板特化

函数模板特化

类模板特化格式:

  • 需要有一个基础模板
  • template后面跟空的<>
  • 函数名后跟<>,里面指定要特化的类型
  • 函数的参数列表,将基础模板的模板函数参数列表进行特化
// 基础
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;
}bool Less(int* left, int* right)
{return *left < *right;
}

类模板特化

全特化

全特化是指将模板参数列表中所有的参数都确定化,如下:

// 基础模板
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;
};

在这里插入图片描述

偏特化/半特化

偏特化/半特化并不是指将基础模板特化一半,而是有以下两种形式:

  1. 将模板参数特化一部分
// 偏特化,将第二个参数特化
template <class T1>
class Data<T1, char>
{
public:Data(){cout << "Data<T1, char>" << endl;}
private:T1 _d1;char _d2;
};

测试:

在这里插入图片描述

d1 适配基础模板,d2 适配全特化的 <int, char> 模板,d3 适配偏特化的 <T1, char> 模板

  1. 不是特化一部分参数,而是对基础模板的参数列表做出一定限制
// 偏特化,将两个参数特化为指针类型
template <class T1, class T2>
class Data<T1*, T2*>
{
public:Data(){cout << "Data<T1*, T2*>" << endl;}
private:T1* _d1;T2* _d2;
};

测试:

在这里插入图片描述

模板分离编译

模板支持声明与定义分离,但是一定要在同一个文件中,如下:

// Array.h
// 声明
template <class T, size_t N = 10>
class Array
{
public:size_t size();
private:T _array[N];size_t _size = N;
};
// 定义
template <class T, size_t N> // 声明与定义的缺省值不可以同时给
size_t Array<T, N>::size() // 声明类域
{return _size;
}

但是如果模板的声明与定义不在同一文件,例如声明在头文件,定义在源文件,那么就会出现链接错误

// Array.h
// 声明
template <class T, size_t N = 10>
class Array
{
public:size_t size();
private:T _array[N];size_t _size = N;
};// Array.cpp
#include "Array.h"
// 定义
template <class T, size_t N> // 声明与定义的缺省值不可以同时给
size_t Array<T, N>::size() // 声明类域
{return _size;
}// main.cpp
#include "Array.h"
void test8()
{Array<int, 10> a1;cout << a1.size() << endl;
}
int main()
{test8();return 0;
}

运行,出现链接错误

在这里插入图片描述

size() 函数有声明,有定义,为什么会找不到呢?这就涉及到程序的编译链接过程了

经过预处理后,头文件会展开。例如头文件 Array.h 会展开在 main.cpp 文件中,这样 main 文件中就有了 size() 的声明,虽然没有拿到 size() 函数的地址,但是有声明就可以先通过编译阶段,到了链接阶段再去其他文件去找函数地址

但是很明显,最后并没有找到 size() 函数的地址,因为 size() 根本没有实例化。这又是为什么呢?

经过预处理后,main.cpp 和 Array.cpp 是这样的情况:

  • mian.cpp 也就是调用函数的地方,虽然知道类模板要实例化成什么类型,但是只有函数的声明,没有定义,也就没有函数的地址
  • Array.cpp 是类模板的成员函数定义的地方,但是这里并不知道类模板要实例化成什么类型,也就无法实例化,无法产生函数地址

要解决这个问题,有两种方法

  1. 在模板定义的地方显式实例化,不推荐,因为每实例化一个不同类型的对象,就要显式实例化一次,很麻烦
// Array.cpp
#include "Array.h"
// 定义
template <class T, size_t N> // 声明与定义的缺省值不可以同时给
size_t Array<T, N>::size() // 声明类域
{return _size;
}
// 显式实例化
template
class Array<int>;
  1. 模板的声明与定义放在同一文件中,推荐
// Array.h
// 声明
template <class T, size_t N = 10>
class Array
{
public:size_t size();
private:T _array[N];size_t _size = N;
};
// 定义
template <class T, size_t N> // 声明与定义的缺省值不可以同时给
size_t Array<T, N>::size() // 声明类域
{return _size;
}

方法

  1. 在模板定义的地方显式实例化,不推荐,因为每实例化一个不同类型的对象,就要显式实例化一次,很麻烦
// Array.cpp
#include "Array.h"
// 定义
template <class T, size_t N> // 声明与定义的缺省值不可以同时给
size_t Array<T, N>::size() // 声明类域
{return _size;
}
// 显式实例化
template
class Array<int>;
  1. 模板的声明与定义放在同一文件中,推荐
// Array.h
// 声明
template <class T, size_t N = 10>
class Array
{
public:size_t size();
private:T _array[N];size_t _size = N;
};
// 定义
template <class T, size_t N> // 声明与定义的缺省值不可以同时给
size_t Array<T, N>::size() // 声明类域
{return _size;
}

这样在编译阶段,既有声明又有定义,还可以直接实例化模板,拿到函数的地址

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com