点蓝色字关注“CurryCoder的程序人生”

微信公众号:CurryCoder的程序人生
欢迎关注我,一起学习,一起进步!


1.问题的引入

C++支持隐式类型转换,但通常情况下是不好的。然而,本这条规定也有例外。最常见的例外情况发生在建立数值类型时,假设你开始设计如下有理数类Rational:

class Rational {public:    Rational(int numerator = 0, int denominator = 1);   // 注意:此处的构造函数为隐式的,因为没有使用explicit关键字修饰    int numerator() const;    int denominator() const;private:    // ...};

你想支持加减乘除等算术运算,但你不确定是否该有成员函数、non-member函数、non-member non-friend函数来实现它们。直觉告诉我们,当我你犹豫不决时,请从面向对象的角度去思考。因为有理数相乘与Rational类有关,因此很自然地想应该在Rational类的内部为有理数实现重载*运算符函数opeator*()。下面,先考虑将operator*()写成Rational成员函数的写法:

class Rational {public:    // ...    // 成员函数    const Rational operator* (const Rational& rhs) const;   // 注意:成员函数的形参中有一个默认参数this,指向的是调用该成员函数的对象!private:    // ...};

假设你不明白为啥上面的成员函数被声明为此种形式,即它返回一个const by-value结果,但接收的参数是一个reference-to-const实参,请先学习前面的文章尽量以const、enum、inline替换#defineC++中多用引用传递方式替换值传递方式C++中不要随便返回对象的引用。上面的成员函数能够让你将两个有理数以最普通的方式进行相乘。如下所示:

Rational oneEighth(1, 8);Rational oneHalf(1, 2);Rational result = oneHalf * oneEighth;result = result * oneEighth;

但是,你以为这样就结束啦?不不不,你还希望支持混合运算即有理数与int类型之间的相乘。但是,当你尝试混合运算的时候,不幸发生了,你发现只有一半行得通。如下所示:

Rational oneHalf(1, 2);Rational result = oneHalf * 2;  // 正确result = 2 * oneHalf;  // 错误

出问题了哦,乘法不是应该满足交换律嘛。如果你对上面的代码段进行重写,如下所示:

Rational oneHalf(1, 2);Rational result = oneHalf.operator*(2);result = 2.operator*(oneHalf);

2.分析问题,追根溯源正如你所思考的那样,oneHalf是一个内含operator*成员函数的类的对象,所以编译器调用该函数。但是,整数2并没有相应的类,也就没有operator*成员函数。编译器也会试图寻找(即在命名空间或global作用域内查找)可以被以下调这样调用的non-member函数operator*

result = operator*(2, oneHalf);  // 错误

但是,本例中并不存在这样接收int和Rational作为参数的non-member函数operator*,因此查找失败再回过头看看先前成功的那个调用,注意它的第二个参数是int类型2,但是Rational::operator*需要的实参可是Rational对象哦。这是咋回事呢?为啥2在这里就可以接收,而在另一个调用中就不可以呢?

原因:因为这里发生了所谓的隐式类型转换。编译器知道你正在传递一个int,而函数需要的却是Rational,但它也知道只要调用Rational构造函数并赋予你所提供的int,就可以变出一个适当的Rational,于是编译器就那样做了。换句话来说,调用动作在编译器眼中有点像下面的代码:

const Rational temp(2);   // 根据整型2建立创建一个临时的 Rational对象result = oneHalf * temp;  // 等价于oneHalf.operator*(temp)

当然,只因为构造函数是隐式的,即non-explicit构造函数,编译器才会去这样做。如果Rational的构造函数是explicit,下面的代码没有一个是正确的。

result = oneHalf * 2;  // 错误! (在explicit显式构造函数的情况下,无法将2转换为Rational)result = 2 * oneHalf;  // 仍然错误!

如果你就是希望能希望支持有理数的混合算术运算,即希望有方法将以上的两个语句都可以通过编译。现在,我们继续探究为什么即使Rational构造函数不是显示explicit的,仍然只有一个可以通过编译呢?

result = oneHalf * 2;  // 正确! (在non-explicit构造函数的情况下,可以将2转换为Rational)result = 2 * oneHalf;  // 错误!

根本原因:只有当参数出现在构造函数的初始化列表中,这个参数才是隐式类型转换的合格参与者。这是因为成员函数本身就自带了一个隐藏参数this指针,它指向调用成员函数的那个对象。因此,第一次调用可以通过,第二次调用是失败的。因为第一次调用伴随一个放在构造函数初始化列表中的参数,第二次调用则不是。3.柳暗花明又一村

最终解决方法让operator*成为一个non-member函数,允许编译器在每个实参身上执行隐式类型转换。如下所示:

class Rational {public:    Rational(int numerator = 0, int denominator = 1);   // 注意,此处的构造函数为隐式的,因为没有使用explicit关键字修饰    int numerator() const;    int denomiator() const;private:    // ...};

// non-member函数const Rational operator* (const Rational& lhs, const Rational& rhs){    return Rational(lhs.numerator() * rhs.numerator(), lhs.denomiator() * rhs.denomiator());}

int main(){    Rational oneFourth(1, 4);    Rational result;    result = oneFourth * 2;    // result = 2 * oneFourth; 也通过了编译!    return 0;}

此外,还有一点需要考虑:operator*是否应该成为Rational类的友元函数呢?答案是否定的。因为operator*可以完全通过Rational的public接口完成任务,上面的代码段就是这样做的。因此,可以得到一个结论:成员函数的对立面是non-member函数,而不是友元函数。现实中太多的程序员假设,如果一个与某类相关的函数不是一个它的成员函数,就应该是该类的友元函数。这实际上是错误的观点,上面的代码段就可以证明这个观点太过牵强。记住:无论何时如果你可以避免友元函数就去避免它,因为不像现实生活中“多个朋友多条路”。在C++程序中,友元函数带来的麻烦往往多于其产生的价值。

4.总结

(1) 如果你需要为某个函数的所有参数(包括this指针所指向的那个隐藏参数)进行类型转换,那么这个函数必须是non-member。

觉得好看,请点这里↓

不存在从node到node*的适当转换函数_C++中参数需要类型转换,请不要用成员函数...相关推荐

  1. node搭建的一个应用在前端项目中的可切换接口的代理服务器

    web项目的编写过程当中,常常会出现前后端进度不一致的情况,就像谈恋爱的两个人如果步调不一致,那么肯定会很累,更有甚者因节奏的不一样导致分手的下场,所以为了避免前后端走到"分手"的 ...

  2. DOMException: Failed to execute ‘appendChild‘ on ‘Node‘: This node type does

    在使用nuxt时出现了下面得错误 DOMException: Failed to execute 'appendChild' on 'Node': This node type does 最后把cre ...

  3. [Node.js] node.js入门

    什么是nodejs 1.Node.js官网地址 中文 1.Node是一个构建于Chrome V8引擎之上的一个Javascript运行环境 Node是一个运行环境,作用是让js拥有开发服务端的功能 2 ...

  4. 解决win7被node.js抛弃的问题 - 1)npm/node_modules/node/bin/node: line 1: This: command not found 2)win8.1或以上

    效果图 出现过的问题,以及解决办法 问题1: C:\Users\mac\AppData\Roaming\npm/node_modules/node/bin/node: line 1: This: co ...

  5. NS2的NODE类——node

    本文转自:http://hi.baidu.com/wirelesscat/blog/item/67c6db4633f71e016b63e59b.html 同时推荐一个很好的博客,这里有连载的 ns2 ...

  6. Error in nextTick: “NotFoundError: Failed to execute ‘insertBefore‘ on ‘Node‘: The node before which

    背景: 在子组件和和父组件来回调用其内部函数时,并通过v-if频繁控制显示/销毁,出现了如下报错信息: vue.runtime.esm.js:619 [Vue warn]: Error in next ...

  7. 本地文件上传服务器node插件,node 搭建本地文件上传服务器

    一直想尝试使用node搭建个文件服务器,今天简单写了一下,后面会慢慢完善,一起学习哈 首先,我们在做文件上传的时候,很多时候会使用到文件服务器. 从前端来说的话, 我们把数据上传到文件服务器上,然后文 ...

  8. OpenVX中 graph与node之间的关系,以及在CNN中的定位

    OpenVX中 graph与node之间的关系,以及在CNN中的定位 很多小伙伴都知道,OpenVX是用来处理图像的一组API,核心是kernel函数的实现,OpenVX对于图像处理是调用底层硬件的G ...

  9. Cocos2d-x Lua Node与Node层级架构

    Cocos2d-x Lua采用层级(树形)结构管理场景.层.精灵.菜单.文本.地图和粒子系统等节点(Node)对象.一个场景包含了多个层,一个层又包含多个精灵.菜单.文本.地图和粒子系统等对象.层级结 ...

最新文章

  1. python【Numpy科学计算库】连女朋友都会用的Numpy(真の能看懂~!)
  2. 浏览器打开域名变成localhost_史上最全微信域名防封API原理及实现方案
  3. JSON.stringify(value[, replacer[, space]])
  4. UE4学习-材质快捷键及材质帮助手册
  5. 教你几招识别和防御Web网页木马
  6. Mybatis入门---一对多、多对多
  7. Python 数据分析三剑客之 Pandas(五):统计计算与统计描述
  8. 篇幅达2840页、目录就有31页,这位华人小哥的博士论文堪比教材
  9. python 爬虫代理_python 爬虫 使用代理 的问题
  10. 从尾到头打印单链表(C语言)
  11. 2020年“双11”各家晒出成绩单,你还没付完尾款,有的人已经收货了!
  12. 微服务升级_SpringCloud Alibaba工作笔记0006---spring gateway工作流程
  13. 189. Rotate Array
  14. python没有pygame_Python菜鸟快乐游戏编程_pygame
  15. 两个需求理论:马斯洛需求层次理论和KANO模型
  16. linux 渗透 系统,初识Linux渗透:从枚举到内核利用
  17. 淘宝商城发公告释疑2012新规 称调整绝不是涨价
  18. vim正则表达式(转)
  19. ️ 后羿采集器——最良心的爬虫软件
  20. FFmpeg视频剪辑拼接

热门文章

  1. 使用maven+eclipse搭建最简单的struts2的HelloWorld
  2. 一步一步 Mac OS X 与 Windows 7 双系统共存 的“黑苹果” 电脑 安装 入门
  3. ECS应用管理最佳实践
  4. Android 图像合成技术Xformodes图片剪裁
  5. BookBlock - 效果非常真实的书本翻页预览
  6. 【译】Android系统简介—— Activity
  7. 再放QQ微博邀请码(2)
  8. php 输出函数结果,PHP向浏览器输出内容的4个函数总结
  9. python中给出一个不超过10的正整数n_求计算机大佬解答python题
  10. python lambda_Python 匿名函数 lambda