demo 1
#include <iostream>
#include <cstdio>
using namespace std;
//template <typename T>
class Complex
{
friend ostream &operator << (ostream &out, Complex &c2);
public:
Complex(int a, int b) : a(a), b(b) {}
Complex operator + (Complex &c2)
{
Complex tem(a + c2.a, b + c2.b);
return tem;
}
void printCom()
{
cout << a << " + " << b << "i\n";
}
private:
int a;
int b;
};
ostream &operator << (ostream &out, Complex &c2)
{
out << c2.a << " + " << c2.b << "i\n";
return out;
}
int main()
{
Complex c1(1, 2);
c1.printCom();
Complex c2(3, 4);
c2.printCom();
Complex c3 = c1 + c2;
c3.printCom();
cout << c3 << endl;
return 0;
}
上述是一个简单的复数类,并重载了+运算符和 << 运算符。
下面拓展,变成模板类:
demo 2
#include <iostream>
#include <cstdio>
using namespace std;
template <typename T>
class Complex
{
friend Complex& mySub(Complex &c1, Complex &c2) // 写在里面没什么问题
{
Complex tmp(c1.a - c2.a, c1.b - c2.b);
return tmp;
}
friend ostream &operator << (ostream &out, Complex &c2)
{
out << c2.a << " + " << c2.b << "i\n";
return out;
}
public:
Complex(T a, T b) : a(a), b(b) {}
Complex operator + (Complex &c2)
{
Complex tem(a + c2.a, b + c2.b);
return tem;
}
void printCom()
{
cout << a << " + " << b << "i\n";
}
private:
T a;
T b;
};
// 运算符重载的正规写法
// 重载 << >> 只能用友元函数,其他运算符重载都要写成成员函数,不要滥用友元函数
/* 这部分定义必须写进类的内部
ostream &operator << (ostream &out, Complex &c2)
{
out << c2.a << " + " << c2.b << "i\n";
return out;
}
*/
int main()
{
// 需要把模板类具体化之后才能定义对象,C++编译器需要分配内存
Complex<int> c1(1, 2);
c1.printCom();
Complex<int> c2(3, 4);
c2.printCom();
Complex<int> c3 = c1 + c2;
c3.printCom();
cout << c3 << endl;
// 滥用友元函数
{
Complex<int> c3 = mySub(c1, c2);
cout << c3;
}
return 0;
}
这样看起来问题也不大。
继续,把所有成员函数都放到类外部,先还是写在同一个cpp文件中:
demo 3
#include <iostream>
#include <cstdio>
using namespace std;
template <typename T>
class Complex; // 解决mySub友元函数的滥用
template <typename T>
Complex<T> mySub(Complex<T> &c1, Complex<T> &c2); // 解决mySub友元函数的滥用
template <typename T>
class Complex
{
// 这个友元函数是滥用
friend Complex<T> mySub<T>(Complex<T> &c1, Complex<T> &c2); // 写在里面没什么问题
// 这样55行会报错
//friend ostream &operator << (ostream &out, Complex &c2);
// 解决方案
friend ostream &operator << <T> (ostream &out, Complex &c2);
public:
Complex(T a, T b);
Complex operator + (Complex &c2);
void printCom();
private:
T a;
T b;
};
// 构造函数拿到写在类的外部
template <typename T>
Complex<T>::Complex(T a, T b)
{
this->a = a;
this->b = b;
}
template <typename T>
void Complex<T>::printCom()
{
cout << a << " + " << b << "i\n";
}
// 注意函数参数和函数返回值都需要进行类型具体化
template <typename T>
Complex<T> Complex<T>::operator + (Complex<T> &c2)
{
Complex tem(a + c2.a, b + c2.b);
return tem;
}
// 报错的本质:模版是两次编译运行的,第一次生成的函数头和第二次生成的函数头不一样
// 友元函数实现 << 运算符重载
template <typename T>
ostream &operator << (ostream &out, Complex<T> &c2)
{
out << c2.a << " + " << c2.b << "i\n";
return out;
}
// 报错
/*
1>templateComplex2.obj : error LNK2019: 无法解析的外部符号
"class std::basic_ostream<char,struct std::char_traits<char> >
& __cdecl operator<<(class std::basic_ostream<char,struct std::char_traits<char> >
&,class Complex<int> &)" (??6@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@std@@AAV01@AAV?$Complex@H@@@Z),
该符号在函数 _main 中被引用
*/
//
// 滥用友元函数
template <typename T>
Complex<T> mySub(Complex<T> &c1, Complex<T> &c2)
{
Complex<T> tmp(c1.a - c2.a, c1.b - c2.b);
return tmp;
}
// 报错。。。。。。
// 解决方案,做前置声明
int main()
{
// 需要把模板类具体化之后才能定义对象,C++编译器需要分配内存
Complex<int> c1(1, 2);
c1.printCom();
Complex<int> c2(3, 4);
c2.printCom();
Complex<int> c3 = c1 + c2;
c3.printCom();
cout << c3 << endl;
// 滥用友元函数
{
Complex<int> c3 = mySub(c1, c2);
cout << c3;
}
return 0;
}
demo 3出现了几个错误,代码中都注释了,尤其友元函数的滥用一定要注意,千万别再不能用友元函数的地方用友元函数,demo 3中出现的问题还都解决了,可是当把类写到.cpp和.h文件中还会出现新的问题,先总结在同一文件下:
所有的类模板函数写在类的外部,在一个cpp中
//构造函数 没有问题
//普通函数 没有问题
//友元函数:用友元函数重载 << >>
// friend ostream& operator<< <T> (ostream &out, Complex<T> &c3) ;
//友元函数:友元函数不是实现函数重载(非 << >>)
//1)需要在类前增加 类的前置声明 函数的前置声明
template<typename T>
class Complex;
template<typename T>
Complex<T> mySub(Complex<T> &c1, Complex<T> &c2);
//2)类的内部声明 必须写成:
friend Complex<T> mySub <T>(Complex<T> &c1, Complex<T> &c2);
//3)友元函数实现 必须写成:
template<typename T>
Complex<T> mySub(Complex<T> &c1, Complex<T> &c2)
{
Complex<T> tmp(c1.a - c2.a, c1.b - c2.b);
return tmp;
}
//4)友元函数调用 必须写成
Complex<int> c4 = mySub<int>(c1, c2);
cout << c4;
结论:友元函数只用来进行左移友移操作符重载。
归纳以上的介绍:可以这样声明和使用类模版:
1) 先写出一个实际的类。由于其语义明确,含义清楚,一般不会出错。
2) 将此类中准备改变的类型名(如int要改变为float或char)改用一个自己指定的虚拟类型名(如上例中的numtype)。
3) 在类声明前面加入一行,格式为:
template <class 虚拟类型参数>
如:
template <class numtype> //注意本行末尾无分号
class Compare
{…}; //类体
4) 用类模板定义对象时用以下形式:
类模板名<实际类型名> 对象名;
类模板名<实际类型名> 对象名(实参表列);
如:
Compare<int> cmp;
Compare<int> cmp(3,7);
5) 如果在类模板外定义成员函数,应写成类模板形式:
template <class 虚拟类型参数>
函数类型 类模板名<虚拟类型参数>::成员函数名(函数形参表列) {…}
关于类模板的几点说明:
1) 类模板的类型参数可以有一个或多个,每个类型前面都必须加class,如:
template <class T1,class T2>
class someclass
{…};
在定义对象时分别代入实际的类型名,如:
someclass<int,double> obj;
2) 和使用类一样,使用类模板时要注意其作用域,只能在其有效作用域内用它定义对象。
3) 模板可以有层次,一个类模板可以作为基类,派生出派生模板类。