C++模板

一、函数模板

(1)使用

内置类型

template <typename T>//先定义模板,T代表的是类型
void Swap(T& a, T& b)
{
	T tmp = a;
	a = b;
	b = tmp;
}

int main()
{
	int a = 1, b = 2;
	double c = 1.1, d = 2.2;
	swap(a, b);
	swap(c, d);
	cout << a << " " << b << endl;
	cout << c << " " << d << endl;
	return 0;
}

 自定义类型

class A
{
public:
	A(int a = 0)
		:_a(a)
	{

	}
	int get()
	{
		return _a;
	}
private:
	int _a;
};
template <typename T>//先声明模板,T代表的是类型
void Swap(T& a, T& b)
{
	//以下对自定义类型进行分析
	T tmp = a;//调用编译器生成的拷贝构造函数
	//下面两个调用编译器生成的赋值运算符重载函数
	a = b;
	b = tmp;
}

int main()
{
	A a(1);
	A b(2);
	swap(a, b);//这个交换要理解底层
	cout << a.get() << " " << b.get() << endl;
	return 0;
}

注意:typename是用来定义模板参数的关键字,也可以用class(不能用struct)
           template<里面可以声明不止一个类型>

(2)原理

 可以看到,两个swap调用的不是同一个函数,我们使用函数时调用的不是模板,而是编译器用这个模板生成的函数

 (3)函数模板实例化

template<typename T>
T Sum(const T& a, const T& b)
{
	return a + b;
}

int main()
{
	int a = 1;
	double b = 1.1;
	Sum(a, b);//这句报错,编译器用模板推导函数时发生返回值类型冲突(无法推导T是int还是double)
	Sum(a, (int)b);//解法一,用户自己强制类型转化
	// 注意:发生强转,要生成具有常性的临时变量,所以模板函数要用const的引用接收,实现权限平移
	Sum < int >(a, b);//解法二,显式实例化
	return 0;
}

(4)模板参数匹配原则

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版本,变成第一个函数
}

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

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

二、类模板

        类模板一定要显示实例化

template<typename T>
//可以看到模板类不想模板函数一样,它没有形参,无法通过1编译器判断参数类型,因此模板类调用时一定是显式调用
class A 
{
public:
	A(T a)
		:_a(a)
	{

	}
private:
	T _a;
};

int main()
{
	//模板类在实例化是一定要显式调用
	A<int>(1);
	A<double>(2);
	A<char>('a');
	return 0;
}

        定义与声明分离时

template<typename T>
//template<typename T>,这句话的作用范围只能是下方的那个类或者是那个函数或者类型
class A 
{
public:
	A(T a)
		:_a(a)
	{

	}
	void print(const T& _a);//类中声明
private:
	T _a;
};
//类外定义
template<typename T>//定义时要写模板关键字
void A<T>::print(const T& _a)//注意这里:类名<模板参数>=类型,这是模板类的类外定义要求
{
	cout << _a << endl;
}

int main()
{
	//模板类在实例化是一定要显式调用
    //A是类名,A<int> 才是类型
	A<int>(1);
	A<double>(2);
	A<char>('a');
	return 0;
}

三、模板进阶

        (1)来看下面这个模板函数

#include<iostream>
#include<vector>
using namespace std;

template<class Container>
void Print(const Container& v)
{
	//Container::const_iterator it = v.begin();这个是错误的写法
    typename Container::const_iterator it = v.begin();
    //auto it = v.begin();这个是auto的写法
	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);
	v.push_back(4);
	v.push_back(5);

	Print(v);
	return 0;
}

        按道理讲,每个容器都应该有const迭代器,这个模板应该可用于各种模板的打印,但这里编译会报错,需要在前面加上typename才会编译通过 

        原因如下:不加typename会使编译器无法判断it前面那坨是类型的名称还是类中的一个静态成员变量(静态成员变量的类外定义),加typename等于告诉它这坨是类型,当::前面的那个不是实例化(<int>)过的东西,就要加typename

        如果直接用auto就没什么事了,因为auto跟据=右边来推导auto的类型

        (2)非类型模板参数

        以下是一个例子

#include<iostream>
#include<vector>
using namespace std;
//静态栈
template<class T,size_t N>
class Stack
{
private:
	T _a[N];
	int _top;
};

int main()
{
	Stack<int, 10> st1;
	Stack<int, 100> st1;
	return 0;
}

        非类型模板参数(N)必须是一个整形常量,并且不能在类中修改

        这就让人联想起库中的arry容器

         它的本质是一个定长数组,有迭代器,没有插入,[ ]访问

        其实它和c的数组没有什么区别 ,arry的唯一好处就是越界检查严格,array对越界的检验非常严格,越界读写都能检查,普通数组,不能检查越界读,少部分越界写可以检查,是vector本身就有检查严格的特性(因为重载了[ ])

        (3)模板的特化

        先举几个个例子

#include<iostream>
using namespace std;
//T为一般类型时,调用这个
template<class T>
int Less(T left, T right)
{
	return left < right;
}

//函数模板的特化
//T为int*时,调用这个
template<>
int Less<int*>(int* left, int* right)
{
	return *left < *right;
}
//下面这个也可以
template<class T>
int Less(T* left, T* right)
{
	return *left < *right;
}
#include<iostream>
using namespace std;
//一般模板
template<class T1,class T2>
class Data
{
public:
	Data()
	{
		cout << "Data<T1,T2>" << endl;
	}

private:
	T1 _d1;
	T2 _d2
};
//对interesting,double特化处理,可优先调用
template<>
class Data<int,double>
{
public:
	Data()
	{
		cout << "Data<int,double>" << endl;
	}
private:
	int _d1;
	double _d2;
};

        全特化与偏特化

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

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

// 偏特化:可能是对某些类型的进一步限制
template<class T1, class T2>
class Data<T1*, T2*>
{
public:
    Data() { cout << "Data<T1*, T2*>" << endl; }
private:
    T1* _d1;
    T2* _d2;
};

         特化后的类相当于一个新类,可以在里面有不同的变量和函数,但是特化类不能独立存在

        (4)模板的分离编译

        1.以自创的stack类来举例

在stack类中写了push与pop函数的声明(.h文件中)

在.cpp文件中定义

 在test文件的main函数中调用

         运行一下就会发生编译报错

        2.以A类的两个func函数举例

.h文件中的A类创建,与func函数的声明

 在.cpp文件中对func1定义,对func2不定义

        运行一下也会编译报错

        3.原因解释

        对于func例子

         声明与定义分离会导致编译器在编译时无法确定函数的地址,编译能过是因为你有声明,声明是一种承诺,链接的时候再到其他文件去找定义,如果找到定义就不报错,找不到就编译报错

        对于push与size的例子

        可以看到push有模板,因为不知道T是什么,虽然可以通过声明承诺,但不能兑现地址,进而导致链接错误

        所有模板不能实现声明与定义的分离,那么怎么解决捏?看看这个例子的解决方法

        在定义底部加上模板实例化,你模板不知道怎么实例化,进而无法得出地址,所有我们采用直接告诉编译器的方式

        但如果你还要实现其他模板类,就还要加上实例化声明

        这样是很麻烦的,在看看其他方法

        我们采用在当前文件(.h中)声明和定义分离,就是直接在声明的文件中实现声明与定义分离就好

        小结:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值