文章概述
- 类模板出现的原因;
- 几个重要概念的区分;
- 单个类模板语法:
- 继承中的类模板语法;
- 类模板语法知识体系梳理
类模板出现的原因
两个或者多个类的功能是相同的,仅仅是数据类型不同。类模板将数据类型和算法实现了分离。
几个重要概念的区分
- 函数模板和模板函数?
函数模板是一个产生函数的蓝图(抽象);
模板函数是函数模板的实例。
- 类模板和模板类
类模板是一个产生类的蓝图(抽象);
模板类是类模板的实例。
单个类模板语法
类模板是由模板说明和类说明构成的。
template <模板参数列表>
类声明
注意: 模板参数至少在类声明中出现一次; 类模板用于实现成员变量的参数化。
template<typename T>
class Tclass
{
public:
T a;
public:
Tclass(T a)
{
this->a = a;
}
T getA()
{
return a;
}
};
int main()
{
//创建实例
Tclass<int> a(10); //类模板是抽象的,需要用类型具体化,C++编译器才会分配内存,使用模板参数列表
cout << a.getA() << endl;
return 0;
}
总结: (单个类模板怎么产生对象)
类模板(抽象:类型参数化)——(通过模板参数列表)——–>模板类————>定义具体的对象
<单个类模板做函数参数>
//模板类
template<typename T>
class Tclass
{
public:
T a;
public:
Tclass(T a)
{
this->a = a;
}
T getA()
{
return a;
}
};
void UseR(Tclass<int>& a)
{
cout << a.getA()<<endl;
}
int main()
{
//创建实例,需要显示的说明什么类型
Tclass<int> a(10);
UseR(a);
return 0;
}
总结: 类模板实例化创建对象或者做函数形参时,都需要C++编译器分配内存,所以需要知道怎么分配内存,即函数参数列表不能忽略。
继承中的类模板语法
<一. 从模板类A中派生普通类B>
//从模板类A中派生普通类B
template<typename T>
class TA
{
public:
T a;
public:
TA(int a)
{
this->a = a;
}
};
class B:public TA<int> //继承的是具体的类,不是抽象类(模板类,不是类模板)
{
public:
int b;
public:
B(int a,int b):TA<int>(a) //TA<int>(a)需要调用父类的构造函数
{
this->b = b;
}
int getB()
{
return b;
}
};
int main()
{
B b(8,10);
b.getB();
return 0;
}
注意: 普通类继承模板类时,需要实例化类模板。C++编译器需要知道父类是什么类型的,才能分配内存空间。总而言之,类模板派生时,需要实例化。
<二. 从模板类A中派生模板类B>
//从模板类A中派生普通类B
template<typename T>
class TA
{
public:
T a;
public:
TA(int a)
{
this->a = a;
}
};
template<typename T>
class B:public TA<T>
{
public:
int b;
public:
B(int a,int b):TA<T>(a)
{
this->b = b;
}
int getB()
{
return b;
}
};
int main()
{
B<int> b(8,10);
cout << b.getB() << endl;
return 0;
}
类模板语法知识体系梳理
针对类的普通函数,友元函数,构造函数的说明:
<一. 所有的类模板函数写在类的内部>
//所有的类模板函数写在类的内部
template<typename T>
class Test
{
//友元函数(普通的友元函数)
friend Test<T> sumMy(Test<T>& a,Test<T>& b)
{
Test<T> temp;
temp.x = a.x + b.x;
return temp;
}
//友元函数实现<<运算符(返回引用主要是链式编译)
friend ostream& operator<<(ostream& out,Test<T>& a)
{
out << a.x ;
return out;
}
private:
T x;
public:
//构造函数
Test(T x)
{
this->x = x;
}
Test()
{
this->x = 2;
}
//普通函数
T getX()
{
return x;
}
};
int main()
{
Test<int> a(10);
Test<int> b(20);
cout << a << endl;
Test<int> c = sumMy(a,b);
cout << c << endl;
return 0;
}
通过实例发现,不管是构造函数,普通函数,普通的友元函数或者友元函数重载运算符,写在函数的内部都没有问题。
<二. 所有的类模板函数写在类的外部(同一个文件中)>
(1). 先完成构造函数和普通函数
//所有的类模板函数写在类的内部
template<typename T>
class Test
{
private:
T x;
public:
//构造函数
Test(T x);
Test();
//普通函数
T getX();
};
//构造函数
template<typename T>
Test<T>::Test()
{
this->x = 2;
}
template<typename T>
Test<T>::Test(T x)
{
this->x = x;
}
//普通函数
template<typename T>
T Test<T>::getX()
{
return x;
}
int main()
{
Test<int> a(10);
Test<int> b(20);
cout<<a.getX();
return 0;
}
编译之后,正常运行。说明构造函数和普通函数写在类的外部没有问题
(2). 友元函数重载运算符
//所有的类模板函数写在类的内部
template<typename T>
class Test
{
//友元函数实现<<运算符(返回引用主要链式编译)
friend ostream& operator<<(ostream& out, Test<T>& a);
private:
T x;
public:
//构造函数
Test(T x);
Test();
//普通函数
T getX();
};
//构造函数
template<typename T>
Test<T>::Test()
{
this->x = 2;
}
template<typename T>
Test<T>::Test(T x)
{
this->x = x;
}
//普通函数
template<typename T>
T Test<T>::getX()
{
return x;
}
//友元函数
template<typename T>
ostream& operator<<(ostream& out, Test<T>& a)
{
out << a.x;
return out;
}
int main()
{
Test<int> a(10);
//使用,会发现报错
cout << a << endl;
return 0;
}
错误:
无法解析的外部符号 "class std::basic_ostream<char,struct std::char_traits<char> > & __cdecl operator<<(class std::basic_ostream<char,struct std::char_traits<char> > &,class Test<int> &)" (??6@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@std@@AAV01@AAV?$Test@H@@@Z),该符号在函数 _main 中被引用
这个错误表示,C++编译器无法根据friend ostream& operator<<(ostream& out, Test<T>& a);(函数头),找到函数的定义。
为什么会产生这个错误了???
归根结底是与函数模板的二次编译有关, 第一次编译看到这个函数,检查语法错误而已;
friend ostream& operator<<(ostream& out, Test<T>& a);
但是使用的时候(第二次编译)
cout << a << endl;
产生的函数的函数头与第一次编译看到的函数头不一样,所以C++编译器根据函数头找不到函数定义。
解决办法:
friend ostream& operator<<<T>(ostream& out, Test<T>& a);
(3).普通的友元函数
template<typename T>
class Test
{
//友元函数(普通的友元函数)
friend Test<T> sumMy(Test<T>& a, Test<T>& b);
//友元函数实现<<运算符(返回引用主要是链式编译)
friend ostream& operator<< <T> (ostream& out, Test<T>& a);
private:
T x;
public:
//构造函数
Test(T x);
Test();
//普通函数
T getX();
};
//构造函数
template<typename T>
Test<T>::Test()
{
this->x = 2;
}
template<typename T>
Test<T>::Test(T x)
{
this->x = x;
}
//普通函数
template<typename T>
T Test<T>::getX()
{
return x;
}
//友元函数重载运算符
template<typename T>
ostream& operator<<(ostream& out, Test<T>& a)
{
out << a.x;
return out;
}
//普通的友元函数
//友元函数(普通的友元函数)
template<typename T>
Test<T> sumMy(Test<T>& a, Test<T>& b)
{
Test<T> temp;
temp.x = a.x + b.x;
return temp;
}
int main()
{
Test<int> a(10);
Test<int> b(10);
//这里会出现问题
Test<int> c=sumMy(a,b);
return 0;
}
错误的原因和友元函数重载运算符的一样,函数头找不到函数体。
解决办法:
a. 类前面添加前置声明:
//需要在类前增加 类的前置声明 函数的前置声明
template<typename T>
class Test;
template<typename T>
Test<T> sumMy(Test<T>& a, Test<T>& b);
b. 类的内部普通的友元函数:
//友元函数(普通的友元函数)
friend Test<T> sumMy<T>(Test<T>& a, Test<T>& b);
c. 调用的时候:
Test<int> c=sumMy<int>(a,b);
改正后的代码:
//1)需要在类前增加 类的前置声明 函数的前置声明
template<typename T>
class Test;
template<typename T>
Test<T> sumMy(Test<T>& a, Test<T>& b);
template<typename T>
class Test
{
//友元函数(普通的友元函数)
friend Test<T> sumMy<T>(Test<T>& a, Test<T>& b);
//友元函数实现<<运算符(返回引用主要是链式编译)
friend ostream& operator<< <T> (ostream& out, Test<T>& a);
private:
T x;
public:
//构造函数
Test(T x);
Test();
//普通函数
T getX();
};
//构造函数
template<typename T>
Test<T>::Test()
{
this->x = 2;
}
template<typename T>
Test<T>::Test(T x)
{
this->x = x;
}
//普通函数
template<typename T>
T Test<T>::getX()
{
return x;
}
//友元函数重载运算符
template<typename T>
ostream& operator<<(ostream& out, Test<T>& a)
{
out << a.x;
return out;
}
//普通的友元函数
//友元函数(普通的友元函数)
template<typename T>
Test<T> sumMy(Test<T>& a, Test<T>& b)
{
Test<T> temp;
temp.x = a.x + b.x;
return temp;
}
int main()
{
Test<int> a(10);
Test<int> b(10);
Test<int> c=sumMy<int>(a,b);
cout << c << endl;
return 0;
}
<三. 所有的类模板函数写在类的外部(不在同一个文件中)>
//----------Testl.h文件
#include<iostream>
using namespace std;
//所有的类模板函数写在类的内部
template<typename T>
class Test
{
//友元函数实现<<运算符(返回引用主要是链式编译)
friend ostream& operator<< <T> (ostream& out, Test<T>& a);
private:
T x;
public:
//构造函数
Test(T x);
Test();
//普通函数
T getX();
};
//----------Testl.cpp文件
#include "stdafx.h"
#include "TestL.h"
template<typename T>
Test<T>::Test()
{
this->x = 2;
}
template<typename T>
Test<T>::Test(T x)
{
this->x = x;
}
//普通函数
template<typename T>
T Test<T>::getX()
{
return x;
}
//友元函数重载运算符
template<typename T>
ostream& operator<<(ostream& out, Test<T>& a)
{
out << a.x;
return out;
}
//main函数文件
//注意: 这时候头文件应该改为: #include "TestL.cpp"(类模板的函数声明和定义
//应该放在一起)
#include "TestL.cpp"
int main()
{
Test<int> a(10);
return 0;
}
对于这个普通的友元函数未能找到解决办法。
总结:
a. 重载<<,>>运算符,我们应该使用友元函数,别的使用成员函数。(友元函数慎用)
b. 类模板中,最好将函数的定义和声明写在类的内部,其次是写在不同的文件。