c++(模版)

目录

函数模板格式 

 函数模版原理

函数模板的实例化

模板参数的匹配原则

类模板


函数模板格式 


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

template<typename T>
void Swap( T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}

 函数模版原理

在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应
类型的函数以供调用

函数模板概念

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

函数模板的实例化

不同类型的参数使用函数模板时,称为函数模板的实例化

模版不建议声明和定义分离到两个文件.h 和.cpp会出现链接错误
模板参数实例化分为:隐式实例化和显式实例化

隐式实例化(模板实参省略):让编译器根据实参推演模板参数的实际类型   Add(a1, (int)d1)

template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
} 
int main()
{
	int a1 = 10, a2 = 20;
	double d1 = 10.0, d2 = 20.0;
	Add(a1, a2);
	Add(d1, d2);
	cout << Add(a1, (int)d1) << endl;
	return 0;
}

显式实例化:在函数名后的<>中指定模板参数的实际类型    Add<int>(a1, d1)

template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
} 
int main()
{
	int a1 = 10, a2 = 20;
	double d1 = 10.0, d2 = 20.0;
	Add(a1, a2);
	Add(d1, d2);
	cout << Add(a1, (int)d1) << endl;
	cout << Add<int>(a1, d1) << endl;
	return 0;
}

模板参数的匹配原则

1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数


// 专门处理int的加法函数
int Add(int left, int right)
{
	return left + right;
} 
//通用加法函数
template<class T>
T Add(T left, T right)
{
	return left + right;
} 
void Test()
{
	Add(1, 2); // 与非模板函数匹配,编译器不需要特化
	Add<int>(1, 2); // 调用编译器特化的Add版本
}
int main()
{
	Test();
}

2. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板

// 专门处理int的加法函数
int Add(int left, int right)
{
	return left + right;
	
} 
// 通用加法函数
template<class T1, class T2>
	T1 Add(T1 left, T2 right)
	{
		return left + right;
	}
	void Test()
	{
		Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化
		Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的
	}
int main()
{
	Test();
}

3. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换

类模板

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

// 类模版
template<typename T>
class Stack
{
	public :
	Stack(size_t capacity = 4)
	{
		_array = new T[capacity];
		_capacity = capacity;
		_size = 0;
	} 
	~Stack()
	{
		delete[] _array;
		_array = nullptr;
		_size = _capacity = 0;
	}
	void Push(const T& data)
	{
		if (_size == _capacity)
		{
			T* tmp = new T[_capacity * 2];
			memcpy(tmp, _array, sizeof(T) * _size);
			delete[]_array;

			_array = tmp;
			_capacity *= 2;
		}
		_array[_size++] = data;
	}
private:
	T* _array;
	size_t _capacity;
	size_t _size;
};

int main()
{
	//类模板都是显示实例化
	Stack<int> s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);
	Stack<double> s2;
	s2.Push(1.1);
	s2.Push(2.1);
	s2.Push(3.1);
	s2.Push(4.1);
}
// 类模版
template<typename T>
class Stack
{
	public :
	Stack(size_t capacity = 4)
	{
		_array = new T[capacity];
		_capacity = capacity;
		_size = 0;
	} 
	~Stack()
	{
		delete[] _array;
		_array = nullptr;
		_size = _capacity = 0;
	}
	void Push(const T& data);
private:
	T* _array;
	size_t _capacity;
	size_t _size;
};

//声明和定义分离时,需要再写一次类模板
template<typename T>
void Stack<T>::Push(const T& data)
{
	if (_size == _capacity)
	{
		T* tmp = new T[_capacity * 2];
		memcpy(tmp, _array, sizeof(T) * _size);
		delete[]_array;

		_array = tmp;
		_capacity *= 2;
	}
	_array[_size++] = data;
}
int main()
{
	//类模板都是显示实例化
	Stack<int> s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);
	Stack<double> s2;
	s2.Push(1.1);
	s2.Push(2.1);
	s2.Push(3.1);
	s2.Push(4.1);
}

 知识点

template <typename T, int size>
class Array {
public:
    T arr[size]; // 使用非类型模板参数
};
template <typename T>
void func(T arg) {
    // 函数体
}
//T是一个类型模板参数

 可用来创建动态增长和减小的数据结构(模板可以具有非类型参数,用于指定大小,可以根据指定的大小创建动态结构)

模板类型无关,提高了代码复用性

模板运行时不检查数据类型,也不保证类型安全,相当于类型的宏替换

平台无关的,可移植性(只要支持模板语法,模板的代码就是可移植的)

模板实参省略意思为隐式实例化

类模板与模板类所指的不是同一概念(类模板是一个类家族,模板类是通过类模板实例化的具体类)   

(MyClass 是一个类模板,而 MyClass<int>MyClass<double> 和 MyClass<std::string> 是模板类的具体实例)

类模板中的成员函数全是模板函数(所有类模板的成员函数,放在类外定义时,需要在函数名前加类名,而类名实际为ClassName<T>,所以定义时还需加模板参数列表)

template<class T>
   size_t Stack<T>::size(){
        return _size;
   }

 非类型模板参数

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

namespace bite
{
	// 定义一个模板类型的静态数组
	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;
	};
}

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

template<size_t N = 10, bool flag = false>
class Stack
{
private:
	int _a[N];
	int _top;
};

// C++20
template<double D>
class A
{
	private:

};
int main()
{
	Stack<> s0;//c++14支持,建议使用
	Stack s0;//c++20支持该写法
	Stack<5> s1;
	Stack<10> s2;

	return 0;
}

array对比静态数组,越界读和写都有检查

静态数组: 越界检查的问题
                   静态数组,抽查
                   越界读不检查,越界写抽查

array在栈上开辟空间,比vector在堆上开辟空间效率更高

 模板的特化

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理

#include"iostream"
using namespace std;
class Date
{
	friend ostream& operator<<(ostream& _cout, const Date& d);
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}

	bool operator<(const Date& d)const
	{
		return (_year < d._year) ||
			(_year == d._year && _month < d._month) ||
			(_year == d._year && _month == d._month && _day < d._day);
	}

	bool operator>(const Date& d)const
	{
		return (_year > d._year) ||
			(_year == d._year && _month > d._month) ||
			(_year == d._year && _month == d._month && _day > d._day);
	}
private:
	int _year;
	int _month;
	int _day;
};
// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{
	return left < right;
} 
int main()
{
	cout << Less(1, 2) << endl; // 可以比较,结果正确
	Date d1(2022, 7, 7);
	Date d2(2022, 7, 8);
	cout << Less(d1, d2) << endl; // 可以比较,结果正确
	Date* p1 = &d1;
	Date* p2 = &d2;
	cout << Less(p1, p2) << endl; // 可以比较,结果错误
	return 0;
}

p1指向的d1显然小于p2指向的d2对象,但是Less内部并没有比较p1和p2指向的对象内
容,而比较的是p1和p2指针的地址,这就无法达到预期而错误。

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

函数模板特化

 1. 必须要先有一个基础的函数模板
2. 关键字template后面接一对空的尖括号<>
3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。

#include"iostream"
using namespace std;
class Date
{
	friend ostream& operator<<(ostream& _cout, const Date& d);
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}

	bool operator<(const Date& d)const
	{
		return (_year < d._year) ||
			(_year == d._year && _month < d._month) ||
			(_year == d._year && _month == d._month && _day < d._day);
	}

	bool operator>(const Date& d)const
	{
		return (_year > d._year) ||
			(_year == d._year && _month > d._month) ||
			(_year == d._year && _month == d._month && _day > d._day);
	}
private:
	int _year;
	int _month;
	int _day;
};

// 函数模板 -- 参数匹配
template<class T>
//bool LessFunc(T const & left, T const & right)
bool LessFunc(const T& left, const T& right)
{
	return left < right;
}

// 特化
template<>
bool LessFunc<const Date*>(const Date* const& left, const Date* const& right)
{
	return *left < *right;
}

template<>//Date*特殊处理
//const此时修饰的*指向的内容,该写法与原类模板不符
//bool LessFunc<Date*>(const Date* & left,const Date* & right)
bool LessFunc<Date*>(Date* const& left, Date* const& right)
{
	return *left < *right;
}

// 推荐
bool LessFunc(const Date* left, const Date* right)
{
	return *left < *right;
}

bool LessFunc(Date* left, Date* right)
{
	return *left < *right;
}
int main()
{
	cout << Less(1, 2) << endl; // 可以比较,结果正确
	Date d1(2022, 7, 7);
	Date d2(2022, 7, 8);
	cout << Less(d1, d2) << endl; // 可以比较,结果正确
	Date* p1 = &d1;
	Date* p2 = &d2;
	cout << Less(p1, p2) << endl; // 可以比较,结果错误
	const Date* p3 = &d1;
	const Date* p4 = &d2;
	cout << Less(p3, p4) << endl; // 可以比较,结果错误

	/*const int i = 0;
	int const j = 0;
	const int& rx = i;
	int const& ry = i;*/

	return 0;
}

 类模板特化

全特化

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

#include"iostream"
using namespace std;
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; }

};
void TestVector()
{
	Data<int, int> d1;
	Data<int, char> d2;
}
int main()
{
	TestVector();
}

 偏特化

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; }
};

// 偏特化/半特化
template<class T1>
class Data<T1, double>
{
public:
	Data() { cout << "Data<T1, double>" << endl; }
};

template<class T1>
class Data<T1, char>
{
public:
	Data() { cout << "Data<T1, char>" << endl; }
};

// 偏特化,传的类型是指针
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public:
	Data()
	{
		cout << "Data<T1*, T2*>" << endl;
	}
};
template <typename T1, typename T2>
class Data <T1&, T2&>
{
public:
	Data()
	{
		cout << "Data<T1&, T2&>" << endl;
	}
};
template <typename T1, typename T2>
class Data <T1&, T2*>
{
public:
	Data()
	{
		cout << "Data<T1&, T2*>" << endl;
	}
};
int main()
{
	Data<int, int> d1;
	Data<int, char> d2;
	Data<int, double> d3;
	Data<char, double> d4;
	Data<char, char> d5;
	Data<char*, char*> d6;
	Data<int*, char*> d7;
	Data<int&, char&> d8;
	Data<int&, double&> d9;
	Data<int&, int*> d10;
	return 0;
}

 

模板分离编译

 

解决方法

1. 将声明和定义放到一个文件 "xxx.hpp" 里面或者xxx.h其实也是可以的。推荐使用这种。
2. 模板定义的位置显式实例化。这种方法不实用,不推荐使用。

模板总结

【优点】
1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
2. 增强了代码的灵活性
【缺陷】
1. 模板会导致代码膨胀问题,也会导致编译时间变长
2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值