C++ 类模板

目录

        1、定义

2、验证类模板生成的类定义

3、非类型参数

4、模板别名

5、模板类

6、多个参数类型

7、类型参数默认值

8、模板类作为模板函数的入参

9、模板具体化

10、成员模板

11、将模板类用作类型参数

12、模板类中的友元


1、定义

     类模板的概念用法同之前讲解过的函数模板是基本一致的,类模板不是一个完整的可以被单独编译的类定义,只是一个模板而已,因为类型对类模板而言是未知的,编译器会根据使用模板的实际类型在编译期生成对应的完整的类定义,然后用该类定义生成对应的类实例。参考如下示例:

#include 
#include 

using std::cout;

//template表示声明一个模板,class也可以用typename代替,可以有多个参数类型
//Type表示一个类型名,不一定是具体的类名,可以是int这种基本类型或者模板类型
//注意模板类同模板函数都不是具体的类和函数,不能单独编译
//template 
template 
class Stack
{
private:
    Type * items;
    int size;
    int top;
public:
    Stack(int size);
    ~Stack();
    bool isempty();
    bool isfull();
    bool push(Type & item);
    Type & pop();
};

template 
Stack::Stack(int size)
{
	this->size=size;
	this->top=0;
	this->items=new Type[size];
	cout<<"Stack(int size) size:"<
Stack::~Stack()
{
	delete[] items;
}

template 
bool Stack::isempty()
{
    return top == 0;
}

template 
bool Stack::isfull()
{
    return top == size;
}

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

template 
Type & Stack::pop()
{
    if (top > 0)
    {
    	//C++中不能把引用置空,所以这里实际未pop掉
        return items[--top];
    }
    else
    	//C++中没有空引用,引用不能为空
        return items[0];
}

int main(){
	Stack a(5);
	int b=1,c=2,d=3;
	a.push(b);
	a.push(c);
	a.push(d);
    int d2=a.pop();
    int c2=a.pop();
    int b2=a.pop();
    int b3=a.pop();

    cout<<"d2->"<"<"<"<

2、验证类模板生成的类定义

      类模板会根据实际使用的类型生成对应类型下的类定义,然后用该类定义生成类实例,怎么验证生成了不同的类定义了?main方法修改如下:

int main(){
	Stack a(3);
	Stack a2(3);
	Stack d(3);
	Stack d2(3);
	return 0;
}

反汇编main方法,结果如下: 

C++ 类模板_第1张图片

可以看出分别生成了Stack 和Stack两个类定义,他们的构造函数和析构函数的地址是不一样的,而属于同一个类型的a和a2, d和d2调用的构造函数和析构函数的地址是一样的。

3、非类型参数

      模板参数中出现的常量表达式称为非类型参数,如下列示例中的n,非类型参数的类型只能是整形,枚举,引用或者指针,注意模板代码不能修改非类型参数的值,也不能获取非类型参数的地址,因为该参数在编译结束后在生成的类定义中会被替换成常量。非类型参数也是模板参数的一部分,因此类型参数相同而非类型参数不同依然会生成不同的类定义。对于数组这类需要在编译期确认数组大小的场景可以使用非类型参数传递数组大小,从而利用栈帧分配内存,效率更高,适合容量小的数组;如果是利用构造函数传递数组大小则只能通过new在堆内存中分配数组内存,并且需要在析构函数中用delete释放数组内存,适合容量较大的数组场景。如下示例:

#include 
#include 

using std::cout;

#define TEMP template 

//其中n表示非类型或者表达式参数,n的类型只能是整形,枚举,引用或者指针,且必须是常量表达式
//T相同而n不同将生成两个不同的类定义,如ArrayTP,ArrayTP
TEMP
class ArrayTP
{
private:
    T ar[n];
public:
    ArrayTP() {};
    explicit ArrayTP(const T & v);
    //重载下标运算符时应该提供两种形式的重载函数,第一种允许修改元素,第二种不允许
    T & operator[](int i);
    //方法后的const表示该方法不修改类成员属性
    T operator[](int i) const;
    void show();
};

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

TEMP
T & ArrayTP::operator[](int i)
{
    if (i < 0 || i >= n)
    {
        std::cerr << "Error in array limits: " << i
            << " is out of range\n";
        std::exit(EXIT_FAILURE);
    }
    return ar[i];
}

TEMP
T ArrayTP::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]; 
}

TEMP
void ArrayTP::show()
{
	for (int i = 0; i < n; i++)
	        cout< a(0);
	a[0]=1;
	a[1]=2;
	a[2]=3;
	a.show();
	return 0;
}

4、模板别名

      使用using指令对类模板重命名,使用typedef或者using指令对类模板的实例重命名,基于上节的ArrayTP示例,main方法修改如下:

//模板重命名,声明必须放在函数外
template
	using array = ArrayTP;

int main() {

	array a(0);
	a[0] = 1;
	a[1] = 2;
	a[2] = 3;
	a.show();

	//使用typedef或者using对某个模板类重命名
//	typedef ArrayTP arrayi;
	using arrayi=ArrayTP ;
	arrayi b(0);
	b[0] = 1;
	b[1] = 2;
	b[2] = 3;
	b.show();

	return 0;
}

5、模板类

     用类模板生成的类叫做模板类,模板类同正常的类一样可以作为基类被继承,可以作为组件类,可作为其他模板类的类型参数,只是需要注意模板类的类名必须带上明确的类型参数,如上例中的ArrayTP视为一个完整的类名。

用作基类的示例:

class IntArray:public ArrayTP{
public:
	IntArray();
	int sum();
};

IntArray::IntArray():ArrayTP(0){

}

int IntArray::sum(){
	int sum=0;
	for(int i=0;i<10;i++){
		sum+=operator [](i);
	}
	return sum;
}

int main(){
	IntArray a;
	a[0]=1;
	a[1]=2;
	a[2]=3;
	a.show();
	cout<<"sum="<

   用作组合类的示例如下:

class IntArray{
private:
	ArrayTP array;
public:
	IntArray();
	int sum();
	void set(int i,int val);
	int get(int i);
};

IntArray::IntArray():array(0){

}

int IntArray::sum(){
	int sum=0;
	for(int i=0;i<10;i++){
		sum+=array[i];
	}
	return sum;
}

void IntArray::set(int i,int val){
	array[i]=val;
}
int IntArray::get(int i){
    return array[i];
}

int main(){
	IntArray a;
	a.set(0,1);
	a.set(1,2);
	a.set(2,3);
	cout<<"a.get(2)="<

 用作模板类型参数时,如果模板类所属的类模板和目标类模板是同一个则称为模板递归,如下示例:

int main(){
	//其效果跟二维数组类似,第一维是3,第二维是6
	ArrayTP,3> a;
	a[0]=ArrayTP(1);
	a[1]=ArrayTP(2);
	a[2]=ArrayTP(3);
	for(int i=0;i<3;i++){
	   a[i].show();
	}
	return 0;
}

6、多个参数类型

     同函数模板,类模板同样支持多个参数类型,如下示例:

#include 
#include 

#define TEMP template 

TEMP
class Pair
{
private:
    T1 a;
    T2 b;
    int type=typeNum;
public:
    T1 & first();
    T2 & second();
    T1 first() const { return a; }
    T2 second() const { return b; }
    int getType() const { return type; }
    Pair(const T1 & aval, const T2 & bval) : a(aval), b(bval) { }
    Pair() {}
};

TEMP
T1 & Pair::first()
{
    return a;
}

TEMP
T2 & Pair::second()
{
    return b;
}


int main()
{
   Pair a(1,"test");
   std::cout<<"first:"<

7、类型参数默认值

     C++允许给类模板的类型参数和非类型参数提供默认值,注意如果全部使用默认值则类模板后面需加上空的<>,表示这是一个模板类,否则编译器将其视为普通的类而找不到该类的定义。模板函数只允许对非类型参数提供默认值,不能对类型参数提供默认值,但是GUN C下无此限制。如下示例:

#include 
#include 

#define TEMP template 

template 
class Pair
{
private:
    T1 a;
    T2 b;
    int type=typeNum;
public:
    T1 & first();
    T2 & second();
    T1 first() const { return a; }
    T2 second() const { return b; }
    int getType() const { return type; }
    Pair(const T1 & aval, const T2 & bval) : a(aval), b(bval) { }
    Pair() {}
};

TEMP
T1 & Pair::first()
{
    return a;
}

TEMP
T2 & Pair::second()
{
    return b;
}

template 
void show(T1 t,T2 t2){
	std::cout<<"T1:"<< t <<",T2:"< a(1,"test");
   std::cout<<"first:"<(1,"test2");
   return 0; 
}

8、模板类作为模板函数的入参

      模板类作为模板函数的入参时,模板类的参数类型信息会自动传递到模板函数中,如下示例:

#include 
#include 

#define TEMP template 

template 
class Pair
{
private:
    T1 a;
    T2 b;
    int type=typeNum;
public:
    T1 & first();
    T2 & second();
    T1 first() const { return a; }
    T2 second() const { return b; }
    int getType() const { return type; }
    Pair(const T1 & aval, const T2 & bval) : a(aval), b(bval) { }
    Pair() {}
};

TEMP
T1 & Pair::first()
{
    return a;
}

TEMP
T2 & Pair::second()
{
    return b;
}

template 
void show(Pair & pair){
	std::cout<<"typeNum:"< a(1,"test");
   //如果显示指定,则必须为11,否则报错参数类型匹配错误
   //不显示指定的情况下默认使用入参的参数类型信息
   show<>(a);
   //全部使用默认值时必须带上<>,表示这是一个类模板,否则编译器认为这是一个普通的类而无法解析
   Pair<> b(2,"test2");
   show<>(b);
   return 0; 
}


9、模板具体化

     部分场景下需要改写模板类的实现以定制部分特殊类型的行为,可通过模板具体化即明确指定在使用某种类型下模板类的实现来实现上述需求,有多个类型参数时还允许部分具体化,即只指定部分类型参数。当编译器匹配具体的模板类时会根据当前参数类型选择具体化程度最高的一个模板类,按需生成类定义。

#include 
#include 

#define TEMP template 

template 
class Pair
{
private:
    T1 a;
    T2 b;
public:
    T1 & first();
    T2 & second();
    Pair(T1 i,T2 j):a(i),b(j){}
};

TEMP
T1 & Pair::first()
{
    return a;
}

TEMP
T2 & Pair::second()
{
    return b;
}

//显示具体化,覆盖原来的实现
template <>
class Pair
{
private:
    int a;
    double b;
public:
    int & first();
    double & second();
    Pair(int i,double j):a(i),b(j){}
};


int & Pair::first(){
	a++;
	return a;
}

double & Pair::second(){
    b++;
    return b;
}

//部分具体化,覆盖原来的实现
template 
class Pair
{
private:
    T1 a;
    double b;
public:
    T1 & first();
    double & second();
    Pair(T1 i,double j):a(i),b(j){}
};

template 
T1 & Pair::first(){
	a+=2;
	return a;
}

template 
double & Pair::second(){
    b+=2;
    return b;
}

int main()
{
   using std::cout;
   //优先使用具体化程度最高的实现
   Pair a(1,2);
   cout<<"first="< b(1,2);
   cout<<"first="< c(1,2);
   cout<<"first="<

执行结果如下:

C++ 类模板_第2张图片

10、成员模板

    模板类或者模板方法本身可以作为结构,类或者模板类的成员,注意模板作为成员时其类型参数与外部的模板类型参数是独立的,可以使用外部的类型参数或者单独定义一个类型参数。如下示例:

#include 
using std::cout;
using std::endl;

template 
class beta
{
private:
	//内部模板类
    template 
    class hold
    {
    private:
        V val;
    public:
        hold(V v  = 0) : val(v) {}
        void show() const { cout << val << endl; }
        V Value() const { return val; }
    };
    hold q;
    hold n;
public:
    beta( T t, int i) : q(t), n(i) {}
    //模板方法
    template
    U blab(U u, T t) { return (n.Value() + q.Value()) * u / t; }
    void Show() const { q.show(); n.show();}
};

int main()
{
    beta guy(3.5, 3);
    guy.Show();
    //自动根据参数类型自动将U设为int
    cout << guy.blab(10, 2.3) << endl;
    //显示指定U的类型
    cout << guy.blab(10, 2.3) << endl;
    cout << guy.blab(10.0, 2.3) << endl;
    return 0; 
}

  也可以把将模板成员的定义放在模板类的外面,更能体现两者类型参数的独立性,如下所示:

#include 
using std::cout;
using std::endl;

//T作用于整个beta模板类,调用该模板类必须指明T的具体类型
template
class beta {
private:
	//V作用于hold模板类
	template
	class hold;
	hold q;
	hold n;
public:
	beta(T t, int i) :
			q(t), n(i) {
	}
	//U只作用于blab模板方法
	template
	U blab(U u, T t);
	void Show() const;
};

template
template
class beta::hold {
private:
	V val;
public:
	hold(V v = 0) :
			val(v) {
	}
	void show() const {
		cout << val << endl;
	}
	V Value() const {
		return val;
	}
};

template
template
U beta::blab(U u, T t) {
	return (n.Value() + q.Value()) * u / t;
}

template
void beta::Show() const {
	q.show();
	n.show();
}

int main() {
	beta guy(3.5, 3);
	guy.Show();
	//自动根据参数类型自动将U设为int
	cout << guy.blab(10, 2.3) << endl;
	//显示指定U的类型
	cout << guy.blab(10, 2.3) << endl;
	cout << guy.blab(10.0, 2.3) << endl;
	return 0;
}

11、将模板类用作类型参数

      模板类本身可以作为另一个模板类的类型参数,不同于第四节中模板类作为类型参数时实际是隐式实例化的模板类作为类型参数,这里没有隐式实例化,只是表明该参数是模板类类型而已,具体是哪个模板类根据实际传入的模板类类型确认。如下所示:

#include 

using std::cout;

template 
class Printer{
private:
	T val;
public:
	Printer(T i):val(i){}
	void show();
};

template 
void Printer::show(){
   cout<<"print val="< class U,class V>
class Shower
{
private:
	U s1;
	U s2;
public:
    Shower(int i,double j):s1(i),s2(j) {};
    void print();
};

template