C++学习笔记(4)——模板

从今天开始就进入泛型程序设计了。。。

一、函数模板

函数模板格式(其中class可以替换为typename):

template <class 类型参数1, class 类型参数2, ...>
返回值类型 模板名(参数名)
{
	函数体;
}

例如:

#include<iostream>
using namespace std;

template <typename T> //typename可以替换为class
void cswap(T &x, T &y) //交换数值
{
	T tmp = x;
	x = y;
	y = tmp;
}

template <typename T>
T test(T o)
{
	return o+1;
}

int main()
{
	int n = 1, m = 2;
	cswap(n, m); //模板被替换为cswap(int &x, int &y),这叫做模板的实例化
	cout<<n<<" "<<m<<endl; //输出2 1
	
	double a = 1.1, b = 2.2;
	cswap(a, b); //模板被替换为cswap(double &x, double &y)
	cout<<a<<" "<<b<<endl; //输出2.2 1.1
	
	cout<<test(1)<<endl; //模板自动检测T为int类型,输出2
	cout<<test(1.2)<<endl; //模板自动检测T为double类型,输出2.2
	cout<<test<double>(3)<<endl;//可以明确模板应该用哪种类型
	
	return 0;
}

函数模板是可以重载的,形参表不一样即可。在多个函数和函数模板名字相同的情况下,一条函数的调用语句遵循如下顺序:

  1. 参数完全匹配的普通函数
  2. 参数完全匹配的模板函数
  3. 实参经过自动类型转换后能够匹配的普通函数
  4. 都找不到,编译出错

下面是一个例子:

#include<iostream>
using namespace std;

template <class T>
T test(T a, T b)
{
	cout<<"test1"<<endl;
}

template <class T, class T1>
T test(T a, T1 b)
{
	cout<<"test2"<<endl;
}

double test(double a, double b)
{
	cout<<"test3"<<endl;
}

int main()
{
	test(1, 2);//调用第一个模板
	test(1, 2.0);//调用第二个模板
	test(1.0, 2);//调用第二个模板
	test(1.0, 2.0);//调用普通函数
	return 0;
}

输出结果是:

test1
test2
test2
test3

下面来做一个实验:如果把第二个模板去掉,按照我们之前说的规则,程序将默认test(1, 2.0);test(1.0, 2);调用普通函数,也就是输出三次test3。如果把第二个模板和普通函数去掉,编译将出错,出错的将会是test(1, 2.0);test(1.0, 2);这两条语句,因为编译器不知道该把1换成double类型,还是把2.0换成int类型,出现了二义性,因此编译出错。

二、类模板

类模板格式(其中class可以替换为typename):

template <class 类型参数1, class 类型参数2, ...>
class 类模板名
{
	成员函数和成员变量
}

//如果是在类定义外部定义成员函数的,按照下面格式:
template <class 类型参数1, class 类型参数2, ...>
返回值类型 类模板名<class 类型参数1, class 类型参数2, ...>::成员函数名(参数名)
{
	do sth;
}
//如果是类内部定义的,那么按普通函数那种写法即可

由类模板实例化得到的类叫做模板类
例如:

#include<iostream>
using namespace std;

template <class T> //定义了一个类模板
class A{
	public:
		T n;
		template<class T2>
		T2 test(T2 a);
};

//按照格式,如果在类外面定义成员函数要多声明一次类模板的类型参数表
//如果是写在类定义内部的,就不需要多声明了,直接按照普通函数写法即可
//试过了,合并写成template<class T, class T2>是不行的
template <class T>
template <class T2>
T2 A<T>::test(T2 a) //记得类模板名后面加上类型参数表!!!
{
	return a+1;
}

int main()
{
	A <int>p; //创建对象时必须写类型参数表,要一一对应
	//这样就得到一个由类模板实例化的模板类
	cout<<p.test(1)<<endl; //成员函数模板被实例化
	return 0;
}

注意一下上面一些细节即可。

三、类模板与继承

有以下几种情况:

1.类模板→派生→类模板

template<class T1, class T2>
class A{
	T1 n;
	T2 m;
};

template<class T1, class T2>
class B:public A<T2, T1>{
	T1 n1;
	T2 m1;
};

template<class T>
class C:public B<T, T>{
	T n2;
	T m2;
};

int main()
{
	B <int, double>b;
	C <int>c;
	return 0;
}

十分easy,把所有T、T1、T2换成我们相应写的类型就可以了。生成对象b时,类B实例化成:class B<int, double>,类A实例化成:class A<double, int>。生成对象c时,类C实例化成:class C<int>,类B实例化成:class B<int, int>,类A实例化成:class A<int, int>

2.模板类→派生→类模板

这个其实就是类A派生类B的时候,类B把类A的类型参数表给填了,也即是类A被实例化,变成模板类。

template<class T1, class T2>
class A{
	T1 n;
	T2 m;
};

template<class T3, class T4>
class B:public A<int, double>{ //类A实例化后得到模板类:A<int, double>
//因此说模板类派生出了类B
	T n1;
	T m1;
};

int main()
{
	B <int>b;//这条语句会导致生成两个模板类:A<int, double>和B<int>
	return 0;
}

3.普通类→派生→类模板

这个就更easy了,直接看下面的例子:

class A{
	int n;
};

template<class T>
class B:public A{
	T m;
};

int main(){
	B <double>b;
	return 0;
}

4.模板类→派生→普通类
其实就是把上面的例子反过来而已:

template<class T>
class A{
	T n;
};

class B:public A<double>{
	int m;
};

int main(){
	B b;
	return 0;
}

上面这四种情况不用死记硬背,用模板类还是类模板继承看需求。

四、类模板与友元

1.函数、类、类的成员函数作为类模板的友元

这个还用说???

void test(){ }
class A{ };

class B{
	public:
		void fun(){ }
};

template<class T>
class C{
	friend void test();
	friend class A;
	friend void B::fun();
};

int main(){
	C <int>c;
	return 0;
}

2.函数模板作为类模板的友元
把上面的改动一下就是了。

template <class T1>
void test(T1 t){ }

class A{ };

class B{
	public:
		void fun(){ }
};

template<class T>
class C{
	template <class T1>
	friend void test(T1 t);
	
	friend class A;
	friend void B::fun();
};

3.函数模板作为类的友元

template<class T>
void fun(T n){ }

class A{
	public:
		template<class T>
		friend void fun(T n){ }
};

4.类模板作为类模板的友元
这个稍微复杂一些:

#include<iostream>
using namespace std;

template<class T2>
class B{
	public:
		T2 m;
		template <class T1>
		friend class A;//类模板A声明为友元
};

template<class T1>
class A{
	public:
		T1 n;
		template <class T2>
		void test(B <T2>b)//类型参数未知,可以写成<T2>b
		{
			b.m = 100;//一旦声明为友元,就可以访问类B的成员了
			cout<<b.m<<endl;
		}
};

int main()
{
	B <int>b;//把T2换成int
	A <int>a;//把T1换成int
	//还可以写成A <B<int> >a,注意,>>中间要有空格才能通过编译
	//意思是用B<int>替换成A模板中的T
	//这种写法有些奇怪,不是很懂
	a.test(b);//输出100
	return 0;
}

五、类模板中的静态成员

例子:

#include<iostream>
using namespace std;

template<class T>
class A{
	private:
		T n;
	public:
		static T sum;
		A(T _n) { n=_n; sum=sum+n; }
		~A() { sum=sum-n; }
		static void Print(){ cout<<sum<<endl; }
//复习:静态成员函数内部不能访问非静态成员变量,也不能调用非静态成员函数
}; 

template<> int A<int>::sum = 0;//对静态成员变量在类外部加以声明是必须的
//Dev C++把这个template<>去掉之后编译会出错
//VS可以把template<>去掉,而编译不出错
template<> double A<double>::sum = 0;

int main()
{
	A <int>a1(2);
	A <int>a2(3);
	a1.Print();
	
	A <double>a3(2.2);
	A <double>a4(3.6);
	a3.Print();
	
	return 0;
}

输出结果:

5
5.8

从这个输出结果可以看出,A<int>A<double>是两个不同的类,它们不会共享同一个静态成员变量sum。说白了,还是那句话,静态成员变量是属于一个类的,不属于某一个对象。

大概就写到这吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值