新手向:C++类模板详解(二)
2020-05-08 04:15
871 查看
在上一篇中,我们已经可以声明一个类模板,并且从类模板派生子类,那么在本文中我将继续用大白话讲述如何对类模板进行函数重载、运算符重载。同样,我们从一个最简单的类模板走起。
首先是含参构造函数重载,我们可在函数形参里定义未具体类型的变量:
template <typename T> class A { public: A(){} A(T m, T n){ this->m = m; this->n = n; } void print(){ cout << m << n << endl; } private: T m; T n; };
同样我们可以进行运算符重载,下面以 + << 为例:
template <typename T> class A { public: A(){} A(T m, T n){ this->m = m; this->n = n; } A& operator+(A &obj){ A tmp(m+obj.m,n+obj.n); return tmp; } friend ostream & operator<<(ostream &out, A &obj){ out << m << n << endl; return out; } void print(){ cout << m << n << endl; } private: T m; T n; };
到这里一切看起来还都挺简单,但是如果我们要把成员函数写在类的外边,情况貌似变复杂了,我们需要在每个成员函数前都加上
template关键字,并且必须要在类中友元函数名后面加上参数列表
<T>。
template <typename T> class A { public: A(); A(T m, T n); A operator+(A &obj); // ↓ 这个地方必须有个<T>,不然在调用这个函数时会报错 friend ostream & operator<< <T> (ostream &out, A &obj); private: T m,n; }; template <typename T> A<T>::A(){} template <typename T> A<T>::A(T m, T n) { this->m = m; this->n = n; } template <typename T> A<T> A<T>::operator+ (A<T> &obj) { A tmp(m+obj.m,n+obj.n); return tmp; } template <typename T> ostream & operator<< (ostream &out, A<T> &obj) { out << obj.m << obj.n << endl; return out; }
上面的代码在vs2017中,即使在类中友元函数名后面加上了参数列表
<T>,vs2017还是会显示错误,但是在进行编译生成的时候vs2017不会报错,可以正常生成exe
如果类中友元函数名后面不写参数列表
<T>,红色下划波浪线会消失,不调用这个友元函数不会报错,一旦调用了这个友元函数,那么将会发生很严重的错误:
所以必须要在类中友元函数名后面加上参数列表
<T>。
如果类模板遇上了非成员函数的友元函数,情况将变得更加复杂,因为不仅在函数定义前加上
template,而且还需要进行类模板的前置声明以及友元函数的前置声明,假如我们想要写一个非成员函数的友元函数
function,于是上边的例子可能就必须得写成这样:
template <typename T> class A ; template <typename T> A<T> function (A<T> &obj); template <typename T> class A { public: A(); A(T m, T n); A operator+(A &obj); void print(); friend ostream & operator<< <T> (ostream &out, A &obj); friend A<T> function<T> (A<T> &obj); private: T m,n; }; template <typename T> A<T>::A(){} template <typename T> A<T>::A(T m, T n) { this->m = m; this->n = n; } template <typename T> A<T> A<T>::operator+ (A<T> &obj) { A tmp(m+obj.m,n+obj.n); return tmp; } template <typename T> void A<T>::print() { cout << m << n << endl; } template <typename T> ostream & operator<< (ostream &out, A<T> &obj) { out << obj.m << obj.n << endl; return out; } template <typename T> A<T> function (A<T> &obj) { obj.m=obj.m+1; obj.n=obj.n+1; return obj; }
如果类模板中出现了
static修饰的属性,那么同一种具体化数据类型的模板类共用该属性,不同数据类型的模板类不共用该属性,举个简单的例子:
#include <iostream> template <typename T> class A { public: A(){} A(T m){ this->m = m; } void print(){ cout << m << endl; } private: T m; static T n; }; template <typename T> T A<T>::n = 0; int main() { T<int> t1,t2; T<char> t3,t4; return 0; }
在上面这个例子中,t1、t2共用一个n,t3、t4共用一个n。
如果是新手,可能还会遇到下面这种令人费解情况:
类模板的声明和定义分别写在.h和.cpp中,并且在.cpp中写有非成员函数的友元函数定义,但是编译的时候始终报错“未找到匹配的函数”。如果将友元函数定义写到了.h中,则在该友元函数有被调用的情况下,vs2017还是会报错“无法解析的外部符号”。为什么会这样呢?这是因为类模板与普通类的编译原理不一样,篇幅有限,不详细展开。
那么应该怎样解决这个问题呢?这个时候就不是包含.h头文件了,这时需要包含.cpp,尽管听起来很不可思议。于是上面的例子会变成这样:
//文件 A.h #include <iostream> using namespace std; template <typename T> class A { public: A(); A(T m, T n); A operator+(A &obj); friend ostream & operator<< <T> (ostream &out, A &obj); private: T m, n; };
//文件 A.cpp #pragma once #include "A.h" template <typename T> A<T>::A() {} template <typename T> A<T>::A(T m, T n) { this->m = m; this->n = n; } template <typename T> A<T> A<T>::operator+ (A<T> &obj) { A tmp(m + obj.m, n + obj.n); return tmp; } template <typename T> ostream & operator<< (ostream &out, A<T> &obj) { out << obj.m << obj.n << endl; return out; }
//文件 main.cpp #include "A.cpp" // 尽管这样写很怪异,但不得不这样写 using namespace std; int main() { A<int> tmp(1,2); cout << tmp << endl; return 0; }
至此,类模板的定义、派生、函数重载、运算符重载以及常见的问题已经讲述完毕。
涵煦 原创文章 4获赞 7访问量 3395 关注 私信相关文章推荐