您的位置:首页 > 编程语言 > C语言/C++

新手向: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 关注 私信
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: