模板2

  • 模板参数分类(了解)
    1.类型形参(类型不确定):在模板参数列表中,跟在class或者typename之类的参数类型名称,例如:template< class T>。
    2.非类型形参(类型已经具体):就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
#include <iostream>
using namespace std;
//定义一个模板类型的静态数组
namespace bite{
    
    
//N就是非类型模板参数,并且在类模板中是常量
template<class T,size_t N>
class arr{
    
    
public:
    arr()
      :size_(0)
    {
    
    
       N=100;//编译器报错,因为N必须是常量
    }
private:
T array[N];//N表示底层空间有多大
size_t size_;
};
}
int main()
{
    
    
bite::arr<int,20> arr1;
return 0;
}

注意:非类型模板参数必须在编译期间就能确定结果,也就是说必须为常量;浮点数、类对象以及字符串是不允许作为非类型模板参数的。

  • 模板的特化
    通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到错误结果,因此需要对模板进行特化。
    1.模板特化概念:在原模板类型的基础上,针对特殊类型进行特殊化的实现方式。
    2.模板特化分为类模板特化和函数模板特化
    A.函数模板的特化
    方式:
    (1)前提:必须有一个基础的函数模板
    (2)关键字template后面接空的尖括号<>
    (3)函数名后跟一队尖括号,尖括号中指定需要特化的类型
    (4)函数形参表:必须要和模板函数的基础参数类型完全相同,如果不同的话可能会报一些奇怪的错误
#include <iostream>
using namespace std;
template<class T>
 T& Max( T& left,  T& right)
{
    
    
	return left>right?left:right;
}
//对上述函数模板进行特化
template<>
char*&  Max< char* >(char*&  left, char*&  right)
{
    
    
	if (strcmp(left, right) > 0)
		return left;

	return right;
}
int main()
{
    
    
//如果没有函数模板的特化,它调用之间的函数模板会产生错误,Max函数返回的是str1,因为其比较的是地址
    char* str1 = "hello";
    char* str2 = "World";
	cout << Max(str1, str2) << endl;
	return 0;
}
#include <iostream>
using namespace std;
template<class T>
//此时指针p的指向可以修改,指针指向的空间的内容不可以修改
void Test( const T p)
{
    
    
	*p = 100;//但编译发现其可以通过,产生矛盾
	//其实是因为编译器将cosnt T p 改为了T const p
}
int main()
{
    
    
	int a = 10;
	int *pa = &a;

	Test(pa);
    //这两者没有任何区别
	const int b1 = 10;
	int const b2 = 20;
	return 0;
}

根据上述代码可以看出使用函数模板特化有时会变得麻烦,因此一般情况下不会使用函数模板特化,而是会直接将特定类型的函数给出

#include <iostream>
using namespace std;
template<class T>
T& Sub(T& left,T& right)
{
    
    
return left-right;
}

const char* Sub(char* left,char* right)
{
    
    
if (strcmp(left, right) > 0)
		return left;

	return right;
}
int main()
{
    
    
const char* str1="hello";
const char* str2="world";
cout<<Max(str1,str2)<<endl;
return 0;
}

B.类模板特化(全特化、偏特化)
全特化:即将模板参数列表中所有的参数都确定化

#include <iostream>
using namespace std;
//template后面跟一个空的尖括号<> ;类名称后面跟一个尖括号<>,将指定需要特化的类型放入其中
template<class T1,class T2>
class Data{
    
    
public:
	Data()
	{
    
    
		cout << "Data<T1,T2>()" << endl;
	}
private:
	T1 _data1;
	T2 _data2;
};

template<>
class Data < int, double >
{
    
    
public:
	Data()
	{
    
    
		cout << "Data<int,data>" << endl;
	}
private:
	int _data1;
	double _data2;
};
int main()
{
    
    
	Data<int, int> d1;
	Data<int, double> d2;//执行的是全特化版本
	Data<double, double> d3;
	return 0;
}

偏特化:任何针对模板参数进一步进行条件限制设计的特化版本
偏特化的两种表现方式:
部分特化(将模板参数列表中的一部分参数特化)
参数进一步的限制(让模板参数的限制更加严格)

//a.部分参数特化
//在实例化期间,只要第二个参数是int类型的,都会走偏特化版本
template<class T1>
class Data < T1, int >
{
    
    
public:
	Data()
	{
    
    
		cout << "Data<T1,int>" << endl;
	}
private:
	T1 data1;
	int data2;
};

//b.让模板参数的限制更加严格(参数进一步的限制)
template<class T1,class T2>
//两个参数偏特化为指针类型
class Data < T1*, T2* >
{
    
    
public:
	Data()
	{
    
    
		cout << "Data<T1*,T2*>" << endl;
	}
private:
	T1* data1;
	T2* data2;
};
//两个参数偏特化为引用类型
template<class T1&,class T2&>
class Data < T1&, T2& >
{
    
    
public:
	Data(const T1& _d1,const T2& _d2)
		:data1(_d1)
		, data2(_d2)
	{
    
    
		cout << "Data<T1&,T2&>" << endl;
	}
private:
	const T1& data1;
	const T2& data2;
};
int main()
{
    
    
	Data<int, int> d1;//部分特化

	Data<int, double> d2;//基础的模板

	Data<double, int> d3;//部分特化

	Data<int*, int*> d4;//使用特化的指针部分

	Data<int&, double&> d5;//使用特化的引用版本
	
	return 0;
}

3.类型萃取:类模板特化的实现
eg:实现一个通用的拷贝函数(由于是通用的,因此必须所有类型都可以,类型分为内置类型和自定义类型)

#include <iostream>
using namesapce std;
template<class T>
struct TypeTraits
{
    
    
	typedef FalseType PODTYPE;
};
//类模板特化
template<>
struct TypeTraits < int >
{
    
    
	typedef TrueType PODTYPE;
};

template<>
struct TypeTraits < double >
{
    
    
	typedef TrueType PODTYPE;
};

template<>
struct TypeTraits < short >
{
    
    
	typedef TrueType PODTYPE;
};

template<>
struct TypeTraits < long >
{
    
    
	typedef TrueType PODTYPE;
};

template<>
struct TypeTraits < long long >
{
    
    
	typedef TrueType PODTYPE;
};

template < class T >
void Copy(T* dst, T* src, size_t size, TrueType)
{
    
    
	//内置类型拷贝方法
	//优点:效率高 缺点:浅拷贝,可能造成内存泄漏
		memcpy(dst, src, size*sizeof(T));
}
template <class T>
void Copy(T* dst, const T* src, size_t size, FalseType)
{
    
    
	// 自定义类型拷贝方法
	//优点:一定不会出错 缺点:效率低
	for (size_t i = 0; i < size; ++i)
		dst[i] = src[i];
}
template<class T>
void Copy(T* dst, const T* src, size_t size)
{
    
    
	Copy(dst, src, size, TypeTraits<T>::PODTYPE());
}
class String
{
    
    
public:
	String(const char* str = "")
	{
    
    
		if (nullptr == str)
			str = "";

		_str = new char[strlen(str) + 1];
		strcpy(_str, str);
	}
	// 如果类中涉及到资源管理时,拷贝构造函数&赋值运算符重载&析构函数必须同时提供
	String(const String& s)
		:_str(new char[strlen(s._str) + 1])
	{
    
    
		strcpy(_str, s._str);
	}

	String& operator=(const String& s)
	{
    
    
		if (this != &s)
		{
    
    
			char* temp = new char[strlen(s._str) + 1];
			strcmp(temp, s._str);
			delete[] _str;
			_str = temp;
		}
		return *this;
	}
	~String()
	{
    
    
		if (_str)
		{
    
    
			delete[] _str;
			_str = nullptr;
		}
	}
private:
	char* _str;
};
int main()
{
    
    
//内置类型
	int array1[] = {
    
     1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	int array2[sizeof(array1) / sizeof(array1[0])];
	Copy(array2, array1, 10);
//自定义类型
	String s1[] = {
    
     "1111", "2222", "3333" };
	String s2[3];
	Copy(s2, s1, 3);
	
	return 0;
}
  • 模板的分离编译
    1.分离编译:一个程序由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译。
    2.头文件中一般放置的是声明,源文件中放置的是定义,头文件不会参与编译。
    程序运行的步骤:预处理–编译–汇编–链接
    预处理器把头文件展开放到包含它的文件中,链接时将目标文件拼接在一起,解决地址问题。
//a.h
#pragma once
template<class T>
T Add(const T& left, const& right);

//a.cpp
#include "a.h"
template<class T>
T Add(const T& left, const& right)
{
    
    
	return left + right;
}
//main.cpp
#include <iostream>
#include "a.h"
int main()
{
    
    
	Add(1, 2);
	Add(1.0, 2.0);
	return 0;
}

【解析】
在a.cpp中,编译器没有看到对Add模板函数的实例化,因此不会产生具体的Add函数
main.cpp经过编译,生成目标文件main.obj,在main.obj中调用Add和Add,编译器在其链接时才会找其地址,但是这两个函数没有实例化没有生成具体代码,找不到函数入口地址,因此链接时报错。

猜你喜欢

转载自blog.csdn.net/weixin_43807876/article/details/114528305