这个条款可以看成是条款24的续集,我们先简单回顾一下条款24,它说了为什么类似于operator *这样的重载运算符要定义成非成员函数(是为了保证混合乘法2*SomeRational或者SomeRational*2都可以通过编译,2不能同时进行隐式类型转换成某个Rational,再作this用)。

所以我们一般将之定义成友元函数,像下面这样:

 class Rational
{
private:
int numerator;
int denominator;
public:
Rational(int n = , int d = ): numerator(n), denominator(d){assert(denominator != );}
int GetNumerator() const{return numerator;}
int GetDenominator() const {return denominator;}
friend const Rational operator* (const Rational& r1, const Rational& r2);
};
const Rational operator* (const Rational& r1, const Rational& r2)
{
return Rational(r1.numerator * r2.numerator, r1.denominator * r2.denominator);
}

现在我们来引入模板,可以像下面这样写,注意这里的operator*是一个独立的模板函数:

 template <class T>
class Rational
{
private:
T Numerator;
T Denominator; public:
Rational(const T& Num = , const T& Den = ) : Numerator(Num), Denominator(Den){}
const T GetNumerator() const
{
return Numerator;
} const T GetDenominator() const
{
return Denominator;
} string ToString() const
{
stringstream ss;
ss << Numerator << "/" << Denominator;
return ss.str();
}
}; template <class T>
const Rational<T> operator* (const Rational<T>& a, const Rational<T>& b)
{
return Rational<T>(a.GetNumerator() * b.GetNumerator(),
a.GetDenominator() * b.GetDenominator() );
}

但下面main函数的两行却都不能通过编译:

 int main()
{
Rational<int> a(, );
Rational<int> c = a * ; // 不能通过编译!
c = * a; // 不能通过编译!
cout << c.ToString() << endl;
}

原因是编译器推导T出现了困难,a * 2在编译器看来,可以由a是Rational<int>将T推导成int,但是2是什么,理想情况下编译器会尝试将它先转换成一个Rational<int>,并将T推导成int,但事实上编译器在“T推导过程中从不将隐式类型转换函数纳入考虑”。所以无论是a * 2还是2 * a都是不能通过编译的,一句话,隐式转换+推导T不能被同时被编译器接受。

解决问题的思路便接着产生,编译器既然不能同时接受这两个过程,就让它们事先满足好一个条件,再由编译器执行另一个过程好了。

如果把这个operator*放在template class里面,也就是先在生成模板类的那一步就定下T,这样编译器只要执行隐式转换这一步就可以了。

因此我们可以这样来改:

 template <class T>
class Rational
{

friend Rational operator* (const Rational& a, const Rational& b);
}; template <class T>
const Rational<T> operator* (const Rational<T>& a, const Rational<T>& b)
{
// 这里友元函数的声明并不是用来访问类的私有成员的,而是用来进行事先类型推导的
return Rational<T>(a.GetNumerator() * b.GetNumerator(),
a.GetDenominator() * b.GetDenominator() );
}

注意红色部分,我们添加了一个友元函数的声明,果然编译通过了,但链接时又报错了,原因是链接器找不到operator*的定义,这里又要说模板类中的一个特殊情况了,它不同与普通的类,模板类的友元函数只能在类中实现,所以要把函数体部分移至到类内,像下面这样:

 template <class T>
class Rational
{

friend Rational operator* (const Rational& a, const Rational& b)
{
return Rational (a.GetNumerator() * b.GetNumerator(),
a.GetDenominator() * b.GetDenominator());
}

}

这下编译和链接都没有问题了。这里还要说一下,就是移至类内后,T的标识符可以不写了,但如果非要写成下面这样,自然也是OK的。

 friend Rational<T> operator* (const Rational<T>& a, const Rational<T>& b)
{
return Rational<T>(a.GetNumerator() * b.GetNumerator(),
a.GetDenominator() * b.GetDenominator());
}

operator*里面只有一句话,但如果friend函数里面的东西太多了,可以定义一个辅助方法,比如DoMultiply(),这个DoMultiply可以放在类外去实现,DoMultiply本身不支持混合乘法(2 * SomeRational或者SomeRational * 2),但由于在operator*里面已经进行了隐式类型转换,所以到DoMultiply这一级是没有问题的。

最后总结一下:

当我们编写一个class template,而它所提供之“与此template相关的”函数支持“所有参数之隐式类型转换”时,请将那些函数定义为“class template内部的friend函数”。

最新文章

  1. 【adb】adb基本命令总结
  2. 【原创】jQuery 仿百度输入标签插件
  3. Java基础知识学习(二)
  4. Maven学习(九)插件介绍
  5. Web API 身份验证 不记名令牌验证 Bearer Token Authentication
  6. 避免硬编码你的PostgreSQL数据库密码
  7. java总结第三次//类和对象2、3
  8. Docker-网络基础配置
  9. SSH Secure Shell Client的傻瓜式使用方法
  10. 中国海洋大学第四届朗讯杯高级组 A 2718 Rocky(模拟)
  11. cocos2dx系列笔记(2)- windows环境配置后续之 Android环境配置
  12. LeetCode (10): Regular Expression Matching [HARD]
  13. 关于js中的事件
  14. javaWeb学习总结(9)- JSTL标签库之核心标签
  15. 如何编写一个稳定的网络程序(TCP)
  16. 消除ExtJS6的extjs-trila字样
  17. Android数据库框架——GreenDao轻量级的对象关系映射框架,永久告别sqlite
  18. python练习题目
  19. Magicodes.NET框架之路——V0.0.0.5 Beta版发布
  20. 运用Python计算Π的多少(大致计算)

热门文章

  1. 点击div外面该div消失(二)
  2. jquery 判断手势滑动方向(上下左右)
  3. Java程序员的日常—— Properties文件的读写
  4. Atitit.信息论原理概论attilax总结
  5. iOS开发--JSON
  6. Java数组一定要初始化才能使用吗?
  7. 解决Visual Studio 2010/2012在调试时lock文件的方法
  8. Zookeeper开源客户端框架Curator简介[转]
  9. Qt txt文本中获取字符串的问题
  10. Scala 深入浅出实战经典 第67讲:Scala并发编程匿名Actor、消息传递、偏函数解析