信息发布→ 登录 注册 退出

C++数据结构模板进阶的多方面分析

发布时间:2026-01-11

点击量:
目录
  • 非类型模板参数
  • 模板的特化
    • 函数模板的特化
    • 类模板的特化
  • 模板的分离编译
    • 总结

      ⭐️博客代码已上传至gitee:https://gitee.com/byte-binxin/cpp-class-code

      非类型模板参数

      模板参数分类类型形参与非类型形参。

      • 类型模板形参:出现在模板参数列表中,跟在class或者typename后面的参数类型名称。(这个我们之前有讲过)
      • 非类型模板形参:用一个常量作为模板的一个参数,必须是整形家族中的类型参数,否则不行。他在模板中可以当常量使用。

      实例:

      // 类型模板参数
      namespace wxj
      {
      	// 非类型模板参数 N 是一个常量参数,只能是整形家族的:int short char long  long long    自定义类型和其他类型都不能作费类型模板参数
      	// 必须在编译期就能确认结果
      	template<class T, size_t N = 10>
      	class Array
      	{
      	public:
      		Array()
      			:_size(N)
      		{}
      		T& operator[](size_t i)
      		{
      			return _arr[i];
      		}
      		const T& operator[](size_t i) const
      		{
      			return _arr[i];
      		}
      		size_t size()
      		{
      			return _size;
      		}
      		bool empty()
      		{
      			return _size == 0;
      		}
      	private:
      		T _arr[N];
      		size_t _size;
      	};
      
      	void TestArray()
      	{
      		Array<int, 5> arr;
      		for (size_t i = 0; i < arr.size(); ++i)
      		{
      			arr[i] = i;
      		}
      		for (size_t i = 0; i < arr.size(); ++i)
      		{
      			cout << arr[i] << " ";
      		}
      		cout << endl;
      	}
      }
      
      int main()
      {
      	wxj::TestArray();
      	return 0;
      }

      代码运行结果如下:

      看上面,我们定义了一个数组类,空间大小由N决定,类型是Array<T, size_t>

      注意:

      • 非类型形参必须是整形家族中的类型,浮点数和类对象都不行。
      • 非类型的模板形参必须在编译期间就能确认结果。

      模板的特化

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

      函数模板的特化

      特化的步骤

      • 必须先有一个基础的函数模板
      • 关键字template后面接一对空的尖括号<>
      • 函数名后跟一对尖括号<>,里面指定需要的特化的类型
      • 函数形参列表:必须和函数模板的基础参数类型完全一致

      实例

      // 模板的特化    模板的特殊化
      template<class T>
      bool IsEqual(T& left, T& right)
      {
      	return left == right;
      }
      
       // 特化  针对某些类型进行特殊化处理
      template<>
      bool IsEqual<const char* const>(const char* const& left, const char* const& right)
      {
      	return strcmp(left, right) == 0;
      }

      注意: 一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出。

      bool IsEqual(char* left, char* right)
      {
      	return strcmp(left, right) == 0;
      }

      类模板的特化

      类模板的特化分为全特化和偏特化。

      全特化: 对类模板参数列表的类型全部都确定(明确指定)

      template <class T1, class T2>
      class Date
      {
      public:
      	Date()
      	{
      		cout << "Date<T1, T2>" << endl;
      	}
      private:
      	T1 _d1;
      	T2 _d2;
      };
      
      // 全特化
      template<>
      class Date<int, double>
      {
      public:
      	Date()
      	{
      		cout << "Date<int, double>" << endl;
      	}
      private:
      	int _d1;
      	double _d2;
      };

      偏特化: 堆类模板的参数列表中部分参数进行确定化分为部分特化和参数进一步限制

      部分特化

      // 部分
      template<class T2>
      class Date<int, T2>
      {
      public:
      	Date()
      	{
      		cout << "Date<int, T2>" << endl;
      	}
      private:
      	int _d1;
      	T2 _d2;
      };

      参数进一步限制 如下有T*和T&,是模板的类型转为指针类型和引用类型

      // 参数进一步限制  堆模板参数更进一步的条件限制
      template <class T1, class T2>
      class Date<T1*, T2&>
      {
      public:
      	Date(int& a)
      		:_d2(a)
      	{
      		cout << "Date<T1*, T2&>" << endl;
      	}
      private:
      	T1* _d1;
      	T2& _d2;
      };

      实例 我们试着实例化几个对象,看他们用的是哪个模板

      int main()
      {
      	Date<int, int> d1;
      	Date<int, double> d2;
      	Date<int, float> d3;
      	int a = 10;
      	Date<int*, int&> d4(a);
      	return 0;
      }

      代码运行结果:

      模板的分离编译

      分离编译: 我们对这个应该是不陌生的,就是把函数的声明放在一个叫**.h的文件中,实现都放在一个叫.cpp**的文件中,这样方便我们管理。

      下面我们试着对模板进行分离编译:

      // a.h
      #pragma once
      
      
      // 普通函数
      void Swap(int& a, int& b);
      // 函数模板
      template<class T>
      T Add(const T& a, const T& b);
      
      // a.cpp
      #define _CRT_SECURE_NO_WARNINGS 1
      #include "a.h"
      
      // 普通函数
      void Swap(int& a, int& b)
      {
      	int tmp = a;
      	a = b;
      	b = tmp;
      }
      // 函数模板
      template<class T>
      T Add(const T& a, const T& b)
      {
      	return a + b;
      }
      
      // test.cpp
      #include "a.h"
      
      int main()
      {
      	int a = 3;
      	int b = 4;
      
      	Swap(a, b);
      	cout << "a = " << a << " b = " << b << endl;
      	cout << Add(a, b) << endl;
      	
      	return 0;
      }

      代码运行结果如下

      代码运行时发生了报错,说Add这个函数是没有见过的。得出结论:函数模板不能分离编译,普通函数可以。

      为什么会这样呢?

      C++程序运行一般经过几个阶段:预处理——>编译——>汇编——>链接(更详细的内容可以参考往期博客——程序的编译)

      • 模板在.cpp中定义了,由于不知道T的类型,所以没有对模板进行实例化。
      • a.h 和 a.cpp 走的是两条不同的路,两条路都没有对模板进行实例化(因为不知道T的类型)。
      • 因为没有对模板进行实例化,所以没有函数参数,也就没有函数地址,所以在链接时,test.cpp中的调用Add函数时,没有函数地址,call调用不到Add函数,所以报错。

      解决方法:

      • 暴力:不分离编译,统一放在一个.h或.hpp的文件中
      • 模板定义位置显示实例化(不推荐,这样就失去了泛型的特点)

      总结

      模板进阶也就是这些内容了,喜欢的话,欢迎收藏支持~

      在线客服
      服务热线

      服务热线

      4008888355

      微信咨询
      二维码
      返回顶部
      ×二维码

      截屏,微信识别二维码

      打开微信

      微信号已复制,请打开微信添加咨询详情!