C++primeplus p462-p482

1.定义类模板

(1)模板类的格式

所有的模板都要这样开头:

template < class Type >

  • class是变量的类型名
  • Type是变量名称
  • template是告诉编译器,将要定义一个模板。

attention:
1.使用class并不意味着Type必须是一个类,只是说明Type是一个通用的类型说明符。可以使用typename来替换他以免混淆。

template< typename Type>

2.Type只是一个参数名,可以用自己喜欢的名称代替它。常见的命名是T

template< typename T>

2.一个模板具体实现的程序

首先要知道:模板只是一个说明,不是具体的实现,他只是告诉编译器如何生成类和类对象成员函数等等。
模板的具体实现才是最终的目的,模板的具体实现被称作实例化或具体化。

由于模板不是函数,它不能单独编译。模板必须与特定的模板实例化请求一起使用。所以,将所有模板信息放入头文件是最好的。

//头文件名stackp.h
#pragma once
template<class T>   //必须有这个声明
class Stack
{
private:
	enum{MAX=10};
	T items[MAX];
	int top;
public:
	Stack();
	bool isempty();
	bool isfull();
	bool push(const T& item);
	bool pop(T& item);
};


template<class T>   //同理,‘template<class T>’ 这句不能少
Stack<T>::Stack()    
//错误的写法:Stack::Stack(),  这样因为Stack是模板类,需要特殊声明
{
	top = 0;
}


template<class T>
bool Stack<T>::isempty()
{
	return top == 0;
	//翻译一下。
	/*if (top == 0)
		return true;
	else
		return false;*/
}


template<class T>
bool Stack<T>::isfull()
{
	return top == MAX;
}

template<class T>
bool Stack<T>::push(const T& item)  //进栈,
{
	if (top < MAX)
	{
		items[top++] = item;
		return true;
	}

	else
		return false;
}

template<class T>
bool Stack<T>::pop(T& item)  //出栈,
{
	if (top > 0)
	{
		item = items[--top];
		return true;
	}
	else
		return false;
}


前面提到,如果只有模板是不能生成模板类的。比如:冰淇淋模具不能单独生成冰淇淋,还需要在里面放入制作冰淇淋的材料才行。
例如:

Stack< int> bucket;
//int将会像函数的实参一样传递给形参,将T替换成int。

#include<iostream>
#include<string>
#include<cctype>
#include"stackp.h"
using std::cin;
using std::cout;


int main()
{
	Stack<std::string>st;
	char ch;
	std::string po;
	cout << "请输入A以添加采购订单,:\n" << "P处理采购订单,或Q退出.\n";
	while (cin >> ch && std::toupper(ch) != 'Q')  //C 库函数 int toupper(int c) 把小写字母转换为大写字母。
	{
		while (cin.get() != '\n')
			continue;
		if (!std::isalpha(ch))
		{
			cout << '\a';
			continue;
		}
		switch (ch)
		{
		case 'A':
		case 'a':
			cout << "Enter a PO number to add: ";
			cin >> po;
			if (st.isfull())
				cout << "stack already full\n";
			else
				st.push(po);
			break;
		case 'P':
		case 'p':
			if (st.isempty())
				cout << "stack already empty\n";
			else
			{
				st.pop(po);
				cout << "PO #" << po << " popped\n";
				break;
			}
		}
		cout << "请输入A以添加采购订单,:\n" << "P处理采购订单,或Q退出.\n";
	}
	return 0;
}

3.指针作为模板类型

已经知道,内置类型和类对象都可以用作类模板Stack< Type>的类型,那么指针可以吗。

可以。

但是要注意指针是否匹配函数的实现。比如;
Stack< char *>po;
将string po替换成char *po=new char[40];
这看起来和类模板的函数没有冲突,但是实际上没有意义(对于现实意义来说)。
这样做为输入的字符串提供了空间,且po是变量,与pop()的代码兼容。然而,这里会遇到问题:只有一个po变量,该变量总是指向相同的内存单元。虽然每次读取字符串的时候,char *指向的内容都会改变,但是不管内存里放的内容如何改变,该内存的地址不会改变,所以在上面的程序中使用char *作为模板类的参数还是不行的(即使没有物理上的错误)

正确使用指针栈

使用指针数组;每个指针都指向不同的字符串,如此就可以解决问题了。
但是需要包含复制构造函数,赋值运算符。

#pragma once



template<class Type>
class Stack
{
private:
	enum { SIZE = 10 };
	int stacksize;
	Type* items;
	int top;

public:
	explicit Stack(int ss = SIZE);
	Stack(const Stack& st);
	~Stack() { delete[]items; }
	bool isempty()
	{
		return top == 0;
	}

	bool isfull()
	{
		return top == stacksize;
	}

	bool push(const Type& item);
	bool pop(Type& item);
	Stack& operator=(const Stack& st);
};



template<class Type>
Stack<Type>::Stack(int ss) :stacksize(ss), top(0)
{
	items = new Type[stacksize];
}

template<class Type>
Stack<Type>::Stack(const Stack& st)  //复制构造函数
{
	stacksize = st.stacksize;
	top = st.top;
	items = new Type[stacksize];
	for (int i = 0; i < top; i++)
		items[i] = st.items[i];
}


template<class Type>
bool Stack<Type>::push(const Type& item)
{
	if (top < stacksize)
	{
		items[top++] = item;
		return true;
	}
	else
		return false;

}


template<class Type>
bool Stack<Type>::pop(Type& item)
{
	if (top > 0)
	{
		item = items[--top];
		return true;
	}

	else
		return false;
}

template<class Type>
Stack<Type>& Stack<Type>::operator=(const Stack<Type>& st)
{
	if (this == &st)
		return *this;
	delete[]items;
	stacksize = st.stacksize;
	top = st.top;
	items = new Type[stacksize];
	for (int i = 0; i < top; i++)
		items[i] = st.items[i];
	return *this;
}


//原型将赋值运算符函数的返回类型声明为stack引用,而实际的模板函数定义将类型定义为Stack<Type>.前者是后者的缩写,但只能在类中使用。
//也就是说,可以在模板声明或模板函数定义内使用Stack,但是在类的外面,即指定返回类型或使用作用域解析运算符时,必须使用完整的Stack<Type>

#include<iostream>
#include<string>
#include<ctime>
#include<cctype>
#include"stackp.h"
using std::cin;
using std::cout;

const int Num = 10;
int main()
{
	std::srand(std::time(0));
	cout << "请输入栈的大小:";
	int stacksize;
	cin >> stacksize;
	Stack<const char*>st(stacksize);

	const char* in[Num] = { "1:HANK Gilgamwsh","2:kiki isheta","3:Better raker","4:Ian Flaengy","5:Wolfgang kinble","6:Portia Koop","7:Joy Almondo","8:Xaverie Paprika","9:Juan Moore","10:Misha Mache" };


	const char* out[Num];

	int processed = 0;
	int nextin = 0;
	while (processed < Num)
	{
		if (st.isempty())
			st.push(in[nextin++]);
		else if (st.isfull())
			st.pop(out[processed++]);
		else if (std::rand() % 2 && nextin < Num)
			st.push(in[nextin++]);
		else
			st.pop(out[processed++]);
	}
	for (int i = 0; i < Num; i++)
		cout << out[i] << std::endl;
	return 0;
}

4.数组模板示例和非类型参数

模板常用作容器类,这是因为类型参数的概念非常适用于将相同的存储方案用于不同的类型。确实,为容器类提供可重用代码是引入模板的主要动机。

#ifndef ARRAYTP_H_
#define ARRAYTP_H_

#include<iostream>
#include<cstdlib>


template<class T,int n>
class ArrayTP
{
private:
	T ar[n];
public:
	ArrayTP() {};
	explicit ArrayTP(const T& v);
	virtual T& operator[](int i);
	virtual T operator[](int i)const;
};


template<class T,int n>
ArrayTP<T, n>::ArrayTP(const T& v)
{
	for (int i = 0; i < n; i++)
		ar[i] = v;
}

template<class T,int n>
T& ArrayTP<T, n>::operator[](int i)   //注意这里不是写ArrayTP,而是ArrayTP<T,n>,这是因为ArrayTP不是一个完整的类,要加上数据类型才是完整的类。
{
	if (i < 0 || i >= n)
	{
		std::cerr << "Error in array limits: " << i << " is out of range\n";
		std::exit(EXIT_FALLURE);
	}
	return ar[i];
}

template<class T,int n>
T ArrayTP<T, n>::operator[](int i)const     //这个[]重载运算符和上面的差别仅仅一个引用。
{
	if (i < 0 || i >= n)
	{
		std::cerr << "Error in array limits: " << i << " is out of range\n";
		std::exit(EXIT_FAILURE);
	}
	return ar[i];
}
#endif // !1

上面的程序有一个细微的差别,就是模板头的格式
template<class T,int n>
关键字class指出T为类型参数,int指出n的类型为int,这种参数(指定特殊的类型而不是用作泛型名)称为非类型参数,或表达式参数。假设有下面的声明:
ArrayTP<double,12>eggweight;
这将导致编译器定义名为ArrayTP<double,12>的类,并创建一个类型为ArrayTP<double,12>的eggweight对象。那么定义类的时候,编译器将使用double替换T,使用12替换n。

当然表达式参数有一定的限制。

(1)非类型参数的限制

  1. 表达式参数可以是整型,枚举和引用,指针。

因此,double m是不合法的,但是double *m是合法的。

  1. 模板代码不能修改参数的值,也不能使用参数的地址。

比如,ArrayTP模板中不能使用++n和&n的表达式。

  1. 实例化模板的时候,用作表达式参数的值必须是常量表达式。

(2)与stack类相比的优缺点

与Stack中使用的构造函数方法相比,这种改变数组大小的方法有一个优点。
构造函数方法使用的是new和delete管理的堆内存,而表达式参数方法使用的是为自动变量维护的内存栈。这样,执行速度会更快。

但是缺点是:
每组数组大小都会生成自己的模板,,,也就是说,下面的声明将生成两个独立的类声明:

ArrayTP<double ,12>eggweight;
ArrayTP<double, 13>donuts;

而使用Stack只需要生成一个类声明,并将数组大小信息传递给类的构造函数

Stack< int >eggs(12);
Stack< int >dunkers(13);

另一个区别是,构造函数方法更通用,这是因为数组大小作为类成员(而不是硬编码)存储在定义中的。这样可以将一种尺寸的数组赋给另一尺寸的数组,也可以创建允许数组大小可变的类。

5.模板的多功能性

可以将常规类的技术用于模板类。模板类可用作基类,也可以用作组件类,还可以用作其它模板的类型参数。

template<typename T>  //模板类作为常规类
class Array
{
private:
	T entry;
	...
};


template<typename T>   
class GrowArray:public Array<Type>    //模板类作为基类
{
	...
}


template <typename  Tp>
class stack
{
	Array<tp>ar;    //模板类作为组件类
	...
}

...
Array<Stack<int>>asi;    //模板类作为其它类型的模板参数

(1)递归使用模板

模板的一个多功能在于,可以递归时候模板。例如:对于前面的数组模板定义,可以这样使用它
ArrayTP< ArrayTP<int,5>,10>twodee;

这表示,twodee对象是一个包含10俄国元素的数组,其中每个元素都是包含5个int元素的数组。
与之等价的常规数组声明如下:
int twodee[10][5];

注意,在模板语法中,维的顺序与等价的二维数组相反。

#include<iostream>
#include<cstdio>
#include"arraytp.h"
using namespace std;


int main(void)
{
	ArrayTP<int, 10>sums;   //10个元素的1维数组
	ArrayTP<double, 10>aves;   //10个元素的一维数组
	ArrayTP<ArrayTP<int, 5>, 10>twodee;   //里面是第二维,外面是第一维  ,相当于a[10][5]


	int i, j;
	for (i = 0; i < 10; i++)
	{
		sums[i] = 0;
		for (j = 0; j < 5; j++)
		{
			twodee[i][j] = (i + 1) * (j + 1);
			sums[i] += twodee[i][j];
		}
		aves[i] = (double)sums[i] / 10;

	}
	for (i = 0; i < 10; i++)
	{
		for (j = 0; j < 5; j++)
		{
			cout.width(2);
			cout << twodee[i][j] << " ";
		}
		cout << " :sum = ";
		cout.width(3);
		cout << sums[i] << ", average = " << aves[i] << endl;
	}

	cout << "Done.\n";


	return 0;
}

(2)使用多个类型参数

模板可以包含多个类型参数。例如,假设希望类可以保存两种值,则可以创建并使用Pair模板来保存两个不同的值(标准模板库提供了类似的模板,名pair)。

#include<iostream>
#include<string>
template<class T1,class T2>   //可以发现模板有两个类参数
class Pair
{
private:
	T1 a;
	T2 b;
public:
	T1& first();    //返回引用
	T2& second();
	T1 first()const { return a; }
	T2 second()const { return b; }
	Pair(const T1 &aval,const T2 &bval):a(aval),b(bval){}   //带有两个参数的构造函数
	Pair(){}  //默认构造函数
};



template<class T1,class T2>
T1& Pair<T1, T2>::first()   //注意不要将Pair<T1,T2>写成Pair.  因为在这里Pair只是一个没有参数的名字,不是常规的类名,也不具有常规意义。所以必须要加上<...>
{
	return a;
}

template<class T1,class T2>   //
T2& Pair<T1, T2>::second()
{
	return b;
}

int main()
{
	using std::cout;
	using std::endl;
	using std::string;
	Pair<string, int>ratings[4] =    //数组
	{
		Pair<string, int>("Jaquie's Frisco Al Fresco",4),
		Pair<string,int>("The Purpled Duck",5),
		Pair<string, int>("Cafe Souffle", 5),
		Pair<string, int>("Bertie's Eats", 3)
	};


	int joints = sizeof(ratings)/sizeof(Pair<string,int>);
	cout << "Rating:\tEatery\n";
	for (int i = 0; i < joints; i++)
	{
		cout << ratings[i].second() << ":\t" << ratings[i].first() << endl;
	}

	cout << "Oops!Revised rating:\n";
	ratings[3].first() = "Bertie's Fab Eats";
	ratings[3].second() = 6;
	cout << ratings[3].second() << ":\t" << ratings[3].first() << endl;
	return 0;
}

(3)默认类型模板参数

类模板参数的另一项新特性是,可以为类型参数提供默认值:
template<class T1,class T2=int>class Topo{…};
这样,如果忘记或者省略了T2的值,编译器将使用int:
Topo<double,double>m1; //T1 is double,T2 is double
Topo< double >m2; //T1 is double, T2 is int
虽然可以为类模板类型参数提供默认值,但不能为函数模板参数提供默认值。然而,可以为非类型参数提供默认值,这对于类模板和函数模板都是适用的。

6.模板的具体化

类模板和函数模板很像,都要隐式具体化,显示实例化和显示具体化,他们统称具体化(spacialazation)。模板以泛型的方式描述类,而具体化是使用具体的类型生成类声明。

(1)隐式实例化

到目前为止,使用的模板示例都是隐式实例化,即它们声明一个或者多个对象,指出所需要的类型,而编译器使用通用模板提供的处方生成具体的类定义:
ArrayTP<int , 100>stuff; //首先生成一个具体的类定义,再由定义生成具体的类对象

编译器在需要对象之前,不会生成类的隐式实例化:
ArrayTP<double, 30>*pt; //不会生成类对象
pt=new ArrayTP<double ,30>;
第二句话导致编译器生成类定义,并根据该类定义生成具体的类对象

(2)显示实例化

当使用关键字template并指出所需类型类声明类的时候,编译器将生成类声明的显示实例化。声明必须位于类模板定义所在的名称空间。例如,下面的声明将ArrayTP<string,100>声明为一个类:
template class ArrayTP<string,100>;
在这种情况下,虽然没有创建或提及类对象,编译器也将生成类声明(包括方法定义)。和隐式实例化一样,也将更具通用模板来生成具体化。

(3)显示具体化

显示具体化是特定类型的定义。有时候,可能需要在为特殊类型实例化时,对模板进行修改。这时就需要显示具体化(具体化优先级高于通用模板)

具体化类模板定义如下:
template<>class Classname<specialized_type_name>{…};

假设有一个模板

template<typename T>
class SortedArray
{
	...
};

该模板可以对int型数字进行比较,重载了>运算符。如果T表示一个类,则定义了T::operator>()方法,也可以。但如果T是const char*类型,使用>时,那么>的重载将不管用。那么可以创建一个具体实例化。

template<>class SortedArray<const char *>
{

};

其中的实现代码将使用strcmp()而不是>来比较数组值。现在,当请求const char *类型的SortedArray模板时,编译器将使用上述专用的定义,而不是通用的模板定义:

SortedArrayscores; //使用通用模板
SortedArray<const char *>dates; //如果只有通用模板,将使用通用模板,但是如果有显示具体化的定义,将使用显示具体化。

(4)部分具体化

显示具体化还包含了部分具体化,即可以给类型参数之一指定具体的类型。
template<class T1,class T2>
class pair
{

}; 通用模板
template< class T1>
class pair<T1, int>
{

}; 第二个参数具体化模板
关键字template后面的<>声明的是没有被具体化的类型参数。因此,上述第二个声明将T2具体化为int,但是T1保持不变。注意,如果指定所有的类型,则<>内将为空,这将导致显示具体化(也就是全部参数具体化):

template<>class pair<int,int>
{

};

如果有多个模板可以选择,将选择最具体的模板。
例如:
template<class T1,class T2>
class pair
{

}; 通用模板@1
template< class T1>
class pair<T1, int>
{

}; 第二个参数(int)具体化模板@2

pair<double,double>p1; //使用@1模板
pair<double,int>p2; //虽然也是两个参数,但是第二个参数是int,所以选择@2模板
pair<int,int>p3; //这个有一点疑惑,稍后解决

当然,指针作为参数来实现具体化也是可以的
template< class T>
class Feeb
{

};

template< class T*>
class Feeb
{

};
当类型是指针的时候,编译器选择指针版本。否则选择通用版本。

例如:
Feeb< char>fb1; //使用通用版本
Feeb< char*>fb2; //使用指针版本

如果没有指针版本,使用指针作为参数创造具体模板也是可以的,只不过是可能模板不能完成预期的事情。

部分具体化可以设置各种限制,有点复杂,如下
:

//常规模板(最正常的)
template<class T1,class T2, class T3>class  Trio{...};
//发现和常规模板在<>里面少了一个class T3,则说明这个参数要具体化,很显然被
//具体化为T2了
template<class T1,class T2>class Trio<T1,T2,T2>{...};

template<class T1>class Trio<T1,T1*,T1*>{...};

Trio<int,short,char *>t1; //使用通用模板
Trio<int ,short>t2; //使用第二个模板
Trio<char, char * ,char * >t3; //使用第三个模板

7.成员模板

模板可以用作结构,类,或者模板类的成员。
下面是一个示例,改模板将另一个模板类和模板函数作为其成员.

#include<iostream>
using std::cout;
using std::endl;
template<typename T>
class beta
{
private:
	template<typename V>   //这就是成员模板,就是一个新的模板,是外面这个大模板(beta)的一个成员.,有点套娃的感觉
	class hold
	{
	private:
		V val;
	public:
		hold(V v=0):val(v){}
		void show()const { cout << val << endl; }
		V value()const { return val; }
	};

	hold<T>q;
	hold<int>n;
public:
	beta(T t,int i):q(t),n(i){}
	template<typename U>    //又是一个成员模板
	U blab(U u, T t)
	{
		return (n.value() + q.value()) * u / t;
	}
	void Show()const
	{
		q.show();
		n.show();
	}
};


int main()
{
	beta<double>guy(3.5, 3);
	cout << "T was set to double\n";
	guy.Show();
	cout << "V was set to T,which is double,then V was set to int\n";
	cout << guy.blab(10, 2.3) << endl;
	cout << "U was set to int\n";
	cout << guy.blab(10.0, 2.3) << endl;
	cout << "U was set to double\n";
	cout << "Done\n";
	return 0;
}

8.模板用于参数

模板可以包含类型参数(typename T)和非类型参数(int n)。
除此之外,模板还可以包含本身就是模板的参数。
template<template< typename T>class Thing>
class Crab
模板参数是template< typename T>class Thing,其中template< typename T>class是类型,Thing是参数。
假设有:
Crab< King>legs;
为使上述声明被接收,模板参数king必须是一个模板类,其声明与模板参数Thing的声明匹配:
template< typename T>
class King
{

};

#include<iostream>
#include"D:\\洛谷暑假300道\\类模板14.13p463\\类模板14.13p463\\stackp.h"  //这个头文件前面有

template<template<typename T>class Thing>   //模板参数是模板类型
class Crab
{
private:
	Thing<int>s1;   //模板成员是模板类型
	Thing<double>s2;
public:
	Crab() {};
	bool push(int a, double x)
	{
		return s1.push(a) && s2.push(x);
	}
	bool pop(int& a, double& x)
	{
		return s1.pop(a) && s2.pop(x);
	}
};




int main()
{
	using std::cout;
	using std::endl;
	using std::cin;
	Crab<Stack>nebula;   //和King一样,如果用Stack定义模板类,那么需要有对应的Stack模板

	int ni;
	double nb;
	cout << "Enter int double pairs,such as 4  3.5(0   0   to  end):\n";
	while (cin >> ni >> nb && ni > 0 && nb > 0)
	{
		if (!nebula.push(ni, nb))
			break;
	}


	while (nebula.pop(ni, nb))
		cout << ni << "," << nb << endl;
	cout << "Done.\n";



	return 0;
}

/*50 22.48
25 33.87
60 19.12
0 0*/

可以混合使用模板参数和常规参数,例如:Crab类的声明可以像下面这样:

 template<template<typename T>class Thing,typename U,typename V>
 class Crab
 {
 private:
 	Thing<U>s1;
 	Thing<V>s2;
 	...
 }

现在,成员s1和s2可存储的数据类型为泛型,而不是用硬编码指定的类型。这要求将程序中nebula的声明修改成下面这样:
Crab<Stack, int ,double>nebula;
模板参数T表示一种模板类型,而类型参数U和V表示非模板类型。

9.模板类和友元

  1. 非模板友元
  2. 约束模板友元,即友元的类型取决于类被实例化时的类型
  3. 非约束模板友元,即友元的所有具体化都是类的每一个具体化的友元

下面分别介绍他们

(1)模板类的非模板友元

比如在模板类中将一个常规函数声明为一个友元:

template<class T>
class HasFriend
{	
	public:
	friend void counts();   //友元函数
	...
};

上述声明使counts()函数成为模板所有实例化的友元。例如,他将是类HasFriend< int>和HasFriend< string>的友元。(模板的功能更接近常规类了。。是吧。。应该是出于这层考虑)
counts()函数不是通过对象调用大的,也没有对象参数,那么它如何访问Hasfriend对象呢?有很多种可能,它可以访问全局对象;可以使用全局指针访问非全局对象;可以创建自己的对象,可以访问独立于对象的模板类的静态数据成员。
假设要为友元函数提供模板实参,可以如下所示来进行友元声明吗?
friend void report(Hasfriend &);
不行,因为不存在HasFriend这样的对象,而只有特定的具体化,如HasFriend< short>。要提供模板类参数,必须指明具体化。例如。;

template<class T>
class HasFriend
{
	friend void report(HasFriend<T>&);
	...
};

为理解上述代码的功能,想想声明一个特定类型的对象时,将生成的具体化:
HasFriend< int>hf;
编译器将用int替代模板参数T,因此友元声明的格如下:
class HasFriend< int>
{
friend void report(Hasfriend< int> &);

};
也就是说,带HasFriend< int>参数的report()将成为HasFriend< int>类的友元。同样,带HasFriend< double>参数的report()将是report()的一个重载版本—他是Hasfriend< double>类的友元。
注意,report()本身并不是模板函数,而只是使用一个模板作参数。这意味着必须为要使用的友元定义显示具体化:
void report(HasFriend< short>&){…};
void report(HasFriend< int>&){…};

下面程序的counts()函数是所有HasFriend具体化的友元,它报告两个特定的具体化的ct的值。该程序还提供两个report函数,它们分别是某个特定HasFriend()具体化的友元。

#include<iostream>
using std::cin;
using std::cout;
using std::endl;

template<typename T>
class HasFriend
{
private:
	T item;
	static int ct;   //这意味着这个类的每一个特定的具体化都将有自己地静态成员
public:
	HasFriend(const T& i) :item(i) { ct++; }
	~HasFriend() { ct--; }
	friend void counts();
	friend void reports(HasFriend<T>&);
};


template<typename T>
int HasFriend<T>::ct = 0;


void counts()
{
	cout << "int count: " << HasFriend<int>::ct << "; ";
	cout << "double count: " << HasFriend<double>::ct << endl;
}


void reports(HasFriend<int>& hf)
{
	cout << "HasFriend<int>: " << hf.item << endl;
}


void reports(HasFriend<double>& hf)
{
	cout << "HasFriend<double>: " << hf.item << endl;
}



int main()
{
	cout << "No object declared: ";
	counts();
	HasFriend<int>hfi1(10);
	cout << "After hfil declared: ";
	counts();
	HasFriend<int>hfi2(20);
	cout << "After hfi2 declared: ";
	counts();
	HasFriend<double>hfdb(10.5);
	cout << "After hfdb declared: ";
	counts();
	reports(hfi1);
	reports(hfi2);
	reports(hfdb);

	return 0;
}

(2)模板类的约束模板友元函数。

可以修改前一个示例,使友元函数本身成为模板。具体的说,为约束模板友元做准备,来使类的每一个具体化都获得一个与友元匹配的具体化。分以下三步:

  1. 首先,在类定义的前面声明每个模板函数。
template<typename T>void counts();   //只是声明,
template<typename T>void reports(T &);
  1. 然后,在函数中再次将模板声明为友元。这些语句根据类模板参数的类型声明具体化:
  template<typename TT>
  class HasFriendT
  {
  ...
  friend void const<TT>();
  friend void report<>(HasFriendT<TT>&);
  }

声明中的<>指出这是模板具体化。对于report(),<>可以为空,因为可以从函数参数推断出如下模板类型参数
HasFriendT< TT>
然而,也可以使用:
report< HasFriendT< TT>>(HasFriendT< TT>&),换句话说,编译器要知道具体要用什么参数类型来使用这个函数。

但是counts()函数没有参数,因此必须使用模板参数语法(< TT>)来指明其具体化。

  1. 最后一步,为友元提供模板定义
#include<iostream>
using namespace std;



template<typename T>void counts();  //声明友元函数
template<typename T>void report(T&);   




template<typename TT>
class HasFriendT
{
private:
	TT item;
	static int ct;
public:
	HasFriendT(const TT& i) :item(i) { ct++; }
	~HasFriendT() { ct--; }
	friend void counts<TT>();   //为什么要加<TT>?因为这个函数如果要被具体化,就要说明类型
	friend void report<>(HasFriendT<TT>&);    //<>里面可以为空,因为编译器从report()函数里面的参数也可以判断出要使用的具体化
};

template <typename T>
int HasFriendT<T>::ct = 0;   //静态变量需要加作用域说明符::


template<typename T>
void counts()
{
	cout << "template size: " << sizeof(HasFriendT<T>) << "; ";
	cout << "template counts():" << HasFriendT<T>::ct << endl;    
}



template<typename T>
void report(T& hf)
{
	cout << hf.item << endl;
}

int main()
{
	counts<int>();
	HasFriendT<int>hfi1(10);
	HasFriendT<int>hfi2(20);
	HasFriendT<double>hfdb(10.5);
	report(hfi1);
	report(hfi2);
	report(hfdb);
	cout << "counts<int>()output:\n";
	counts<int>();
	cout << "counts<double>()output:\n";
	counts<double>();
	return 0;
}

(3)模板类的非约束模板友元函数

前面的约束模板友元函数是在类外面声明的模板的具体化。int类具体化获得int函数具体化,以此类推。通过在类内部声明模板,可以创建非约束友元函数,即每个函数具体化都是每个类具体化的友元。对于非约束友元,友元模板类型参数与模板类类型参数是不同的:

template<typename T>
class ManyFriend
{
	...
	template<typename C,typename D>friend void show2(C &,D &);
};

下面是一个例子:

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

template<typename T>
class ManyFriend
{
private:
	T item;
public:
	ManyFriend(const T &i):item(i){}
	template<typename C, typename D>friend void show2(C&, D&);   //模板和友元写在一起
};


template<typename C, typename D>void show2(C& c, D& d)
{
	cout << c.item << ", " << d.item << endl;
}



int main()
{
	ManyFriend<int>hfi1(10);
	ManyFriend<int>hfi2(20);
	ManyFriend<double>hfdb(10.5);
	cout << "hfi1, hfi2: ";
	show2(hfi1, hfi2);
	cout << "hfdb, hfi2: ";
	show2(hfdb, hfi2);
	return 0;
}

9.模板别名

如果能为类型指定别名,将很方便,在模板设计中尤其如此。可使用typedef为模板具体化指定别名:

typedef std::array<double,12>arrd;
typedef std::array<int,12>arri;
typedef std::array<std::string,12>arrst;
arrd gallons;
arri days;
arrst months;

C++11新增了一项功能:
使用模板提供一系列别名,如下所示:

template<typename T>
  using arrrtype=std::array<T,12>;

这将arrtype定义为一个模板别名,可使用它来指定类型:

arrtype<double> gallons;
arrtype<int>days;
arrtype<std::string>months;

总结就是:
arrtype< T>表示类型std::array< T,12>

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值