C++模板的进阶

1.模板的基础内容

C++ 的模板是实现泛型编程的核心机制,它允许我们编写独立于具体数据类型的代码,从而实现代码复用并保证类型安全。

模板分为函数模板和类模板:

1.1函数模板

函数模板用于创建通用函数,可操作多种数据类型,而无需为每种类型重复编写函数。

cpp 复制代码
template <typename T>  // 声明模板参数T(T为类型占位符,typename可替换为class)
返回值类型 函数名(参数列表) {
    // 函数体(使用T作为类型)
}

模板的实例化:

隐式实例化:编译器根据传入的参数自动推断模板类型

cpp 复制代码
#include <iostream>

// 函数模板:交换两个同类型变量的值
template <typename T>
void swap(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}

int main() {
    int x = 10, y = 20;
    swap(x, y);  // 隐式实例化:编译器自动推断T为int
    std::cout << "x=" << x << ", y=" << y << "\n";  // 输出x=20, y=10

    double a = 3.14, b = 6.28;
    swap(a, b);  // 隐式实例化:T为double
    std::cout << "a=" << a << ", b=" << b << "\n";  // 输出a=6.28, b=3.14

    return 0;
}

显示实例化:手动指定模板类型,格式为函数名<类型>(参数)

cpp 复制代码
swap<int>(x, y);  // 显式指定T为int

1.2类模板

类模板用于创建通用类(如容器类),支持多种数据类型的成员变量和成员函数。

cpp 复制代码
template <typename T>  // 声明模板参数
class 类名 {
public:
    // 成员变量(可使用T作为类型)
    // 成员函数(可使用T作为类型)
};

实例化类模板时,必须显式指定类型(编译器无法自动推断)。

2.模板的进阶

2.1需要加typename的情况

cpp 复制代码
#include<iostream>
#include<vector>
#include<list>
using namespace std;

template<class Container>
void Print(const Container& v)
{
	// 编译不确定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);
    Print(v);

    list<int>li;
    li.push_back(1);
    li.push_back(2);
    li.push_back(3);
    Print(li);

    return 0;
}

2.2非类型模板参数

模板参数分类型形参与非类型形参
类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称
非类型形参:就是用一个常量作为类**(函数)模板的一个参数,在类(函数)**模板中可将该参数当成常量来使用

cpp 复制代码
namespace my
{
 // 定义一个模板类型的静态数组,N就是一个非类型模板参数,用来确定静态数组的大小
 template<class T, size_t N = 10>
 class array
 {
 public:
 T& operator[](size_t index){return _array[index];}
 const T& operator[](size_t index)const{return _array[index];}
 
 size_t size()const{return _size;}
 bool empty()const{return 0 == _size;}
 
 private:
 T _array[N];
 size_t _size;
 };
}

C++11中新增的容器array中,后面的那一个参数也是和上述代码一样作为非类型模板参数。

cpp 复制代码
#include<iostream>
#include<array>
using namespace std;
int main()
{
    array<int, 10>a;
    for (int i = 0; i < 10; i++)
    {
        a[i] = i;
    }
    for (auto e : a)
    {
        cout << e << " ";
    }
    cout << endl;
    return 0;
}

注意:

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

3.非类型的模板参数是常量,必须是整型。

2.3模板的特化

当需要为特定类型提供自定义实现时,可以使用模板特化(即 "特殊化" 某个类型的模板)。

2.3.1全特化

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

cpp 复制代码
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;
}

2.3.2偏特化

偏特化有两种表现形式:

1.将模板参数类表中的一部分参数特化。

cpp 复制代码
// 将第二个参数特化为int
template <class T1>
class Data<T1, int>
{
public:
 Data() {cout<<"Data<T1, int>" <<endl;}
private:
 T1 _d1;
 int _d2;
};

2.参数更进一步的限制
偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。

cpp 复制代码
//两个参数偏特化为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*>
{ 
public:
 Data() {cout<<"Data<T1*, T2*>" <<endl;}
 
private:
 T1 _d1;
 T2 _d2;
};

2.3.3示例

cpp 复制代码
#include<vector>
#include <algorithm>
template<class T>
struct Less
{
 bool operator()(const T& x, const T& y) const
 {
 return x < y;
 }
};

// 对Less类模板按照指针方式特化 全特化
template<>
struct Less<Date*>
{
 bool operator()(Date* x, Date* y) const
 {
 return *x < *y;
 }
};

//或者 进行偏特化
template<class T>
struct Less<T*>
{
 bool operator()(T* x, T* y) const
 {
 return *x < *y;
 }
};

int main()
{
 Date d1(2022, 7, 7);
 Date d2(2022, 7, 6);
 Date d3(2022, 7, 8);
 vector<Date> v1;
 v1.push_back(d1);
 v1.push_back(d2);
 v1.push_back(d3);
 sort(v1.begin(), v1.end(), Less<Date>());

 vector<Date*> v2;
 v2.push_back(&d1);
 v2.push_back(&d2);
 v2.push_back(&d3);
//如果上面不进行特化,则按指针比较,结果将是错误的
 sort(v2.begin(), v2.end(), Less<Date*>());

 return 0;
}

3.模板分离编译

模板的声明与定义分离开,在头文件中进行声明,源文件中完成定义,则会发生链接错误。

cpp 复制代码
#include<deque>
//stack.h
namespace my
{
	
	template<class T, class Container = std::deque<T>>
	class stack
	{
	public:
		void push(const T& x);
		void pop();

		T& top()
		{
			return _con.back();
		}

		size_t size()
		{
			return _con.size();
		}

		bool empty()
		{
			return _con.empty();
		}
	private:
		Container _con;
	};

}

//stack.cpp

#include"Stack.h"
namespace my
{
	template<class T, class Container>
	void stack<T, Container>::push(const T& x)
	{
		_con.push_back(x);
	}

	template<class T, class Container>
	void stack<T, Container>::pop()
	{
		_con.pop_back();
	}
}

//mian函数所在文件

int main()
{
	my::stack<int> st;  // call xxstackxx(0x324242)
	st.push(1);  // call xxpushi(?)
	st.pop();    // call xxpopi(?)

	st.size();   // call xxsizexx(0xdadada)
	return 0;
}

预处理,编译,汇编是没有问题,链接时会出现问题,链接时找到了函数的定义,但是函数没有实例化,也就是说不知道模板参数是什么类型,无法生成具体的函数,链接就会发生错误。

解决方法:

1.将声明和定义放到一个文件

cpp 复制代码
#include<deque>
//stack.h
namespace my
{
	
	template<class T, class Container = std::deque<T>>
	class stack
	{
	public:
		void push(const T& x);
		void pop();

		T& top()
		{
			return _con.back();
		}

		size_t size()
		{
			return _con.size();
		}

		bool empty()
		{
			return _con.empty();
		}
	private:
		Container _con;
	};

    template<class T, class Container>
	void stack<T, Container>::push(const T& x)
	{
		_con.push_back(x);
	}

	template<class T, class Container>
	void stack<T, Container>::pop()
	{
		_con.pop_back();
	}

}

2.模板定义的位置显式实例化

cpp 复制代码
//stack.cpp

#include"Stack.h"
namespace my
{
	template<class T, class Container>
	void stack<T, Container>::push(const T& x)
	{
		_con.push_back(x);
	}

	template<class T, class Container>
	void stack<T, Container>::pop()
	{
		_con.pop_back();
	}

    //需要实例化多少类型,写多少
    template
	class stack<int>;

	template
	class stack<double>;
}