• 网络学院
  • IT资讯
  • 操作系统
  • 网络技术
  • 软件应用
  • 办公软件
  • 编程技术
  • 网站架设
  • 数据库类
  • 平面设计
  • 多媒体类
  • 游戏资讯
  • 教学论文
  • 认证考试
C++箴言:声明为非成员函数时机
  站点:
  • 首 页
  • 最新软件
  • 文章教程
  • 国内软件
  • 国外软件
  • 绿色软件
  • 源码下载
  • 字体下载
C++箴言:声明为非成员函数时机
软件发布 C++箴言:声明为非成员函数时机
网络软件 系统工具 应用软件 联络聊天 图形图像 多媒体类 行业软件 游戏娱乐 编程开发 安全相关 教育教学 数码软件 绿软下载
热门软件: QQ 瑞星 pplive e话通 木马克星 千千静听 office2000 五笔字根 Photoshop 视频分割
返回文章教程首页 >> 文章首页 >> 编程技术 >> C/C++教程 >> C++箴言:声明为非成员函数时机

C++箴言:声明为非成员函数时机

添加时间: 2006-2-12 8:36:28  作者: C++教程  阅读次数:85   来源: http://www.d9soft.com

          我谈到让一个类支持隐式类型转换通常是一个不好的主意。当然,这条规则有一些例外,最普通的一种就是在创建数值类型时。例如,如果你设计一个用来表现有理数的类,允许从整数到有理数的隐式转换看上去并非不合理。这的确不比 C++ 的内建类型从 int 到 double 的转换更不合理(而且比 C++ 的内建类型从 double 到 int 的转换合理得多)。在这种情况下,你可以用这种方法开始你的 Rational 类:

  

  class Rational {

   public:

    Rational(int numerator = 0, // ctor is deliberately not explicit;

    int denominator = 1); // allows implicit int-to-Rational

    // conversions

  int numerator() const; // accessors for numerator and

    int denominator() const; // denominator - see Item 22

  

   private:

    ...

  };

  你知道你应该支持类似加,乘等算术运算,但是你不确定你应该通过成员函数还是非成员函数,或者,非成员的友元函数来实现它们。你的直觉告诉你,当你拿不准的时候,你应该坚持面向对象。你知道这些,于是表示,有理数的乘法与 Rational 类相关,所以在 Rational 类内部为有理数实现 operator* 似乎更加正常。与直觉不符,将函数放置在它们所关联的类的内部的主意有时候与面向对象的原则正好相反,但是让我们将它放到一边,来研究一下将 operator* 作为 Rational 的一个成员函数的主意:

  

  class Rational {

  public:

  ...

  

  const Rational operator*(const Rational& rhs) const;

  };

  (如果你不能确定为什么这个函数声明为这个样子——返回一个 const by-value 的结果,却持有一个 reference-to-const 作为它的参数。)

  这个设计让你在有理数相乘时不费吹灰之力:

  

  Rational oneEighth(1, 8);

  Rational oneHalf(1, 2);

  

  Rational result = oneHalf * oneEighth; // fine

  

  result = result * oneEighth; // fine

  但是你并不感到满意。你还希望支持混合模式的操作,以便让 Rationals 能够和其它类型(例如,int)相乘。毕竟,很少有事情像两个数相乘那么正常,即使它们碰巧是数字的不同类型。
  当你试图做混合模式的算术运算时,可是,你发现只有一半时间它能工作:

  

  result = oneHalf * 2; // fine

  result = 2 * oneHalf; // error!

  这是一个不好的征兆。乘法必须是可交换的,记得吗?

  当你重写最后两个例子为功能等价的另一种形式时,问题的来源就变得很明显了:

  

  result = oneHalf.operator*(2); // fine

  result = 2.operator*(oneHalf); // error!

  对象 oneHalf 是一个包含 operator* 的类的实例,所以编译器调用那个函数。然而,整数 2 与类没有关系,因而没有 operator* 成员函数。编译器同样要寻找能如下调用的非成员的 operator*s(也就是说,在 namespace 或全局范围内的 operator*s):

  

  result = operator*(2, oneHalf); // error!

  但是在本例中,没有非成员的持有一个 int 和一个 Rational 的 operator*,所以搜索失败。

  再看一眼那个成功的调用。你会发现它的第二个参数是整数 2,然而 Rational::operator* 却持有一个 Rational 对象作为它的参数。这里发生了什么呢?为什么 2 在一个位置能工作,在其它地方却不行呢?

  发生的是隐式类型转换。编译器知道你传递一个 int 而那个函数需要一个 Rational,但是它们也知道通过用你提供的 int 调用 Rational 的构造函数,它们能做出一个相配的 Rational,这就是它们的所作所为。换句话说,它们将那个调用或多或少看成如下这样:

  

  const Rational temp(2); // create a temporary

  // Rational object from 2

  

  result = oneHalf * temp; // same as oneHalf.operator*(temp);

  当然,编译器这样做仅仅是因为提供了一个非显性的构造函数。如果 Rational 的构造函数是显性的,这些语句都将无法编译:

  

  result = oneHalf * 2; // error! (with explicit ctor);

  // can’t convert 2 to Rational

  

  result = 2 * oneHalf; // same error, same problem

  支持混合模式操作失败了,但是至少两个语句的行为将步调一致。

  然而,你的目标是既保持一致性又要支持混合运算,也就是说,一个能使上面两个语句都可以编译的设计。让我们返回这两个语句看一看,为什么即使 Rational 的构造函数不是显式的,也是一个可以编译而另一个不行:

  

  result = oneHalf * 2; // fine (with non-explicit ctor)

  result = 2 * oneHalf; // error! (even with non-explicit ctor)

  其原因在于仅仅当参数列在参数列表中的时候,它们才有资格进行隐式类型转换。而对应于成员函数被调用的那个对象的隐含参数—— this 指针指向的那个——根本没有资格进行隐式转换。这就是为什么第一个调用能编译而第二个不能。第一种情况包括一个参数被列在参数列表中,而第二种情况没有。

  你还是希望支持混合运算,然而,现在做到这一点的方法或许很清楚了:让 operator* 作为非成员函数,因此就允许便一起将隐式类型转换应用于所有参数:

  

  class Rational {

  

  ... // contains no operator*

  };

  const Rational operator*(const Rational& lhs, // now a non-member

  const Rational& rhs) // function

  {

  return Rational(lhs.numerator() * rhs.numerator(),

  lhs.denominator() * rhs.denominator());

  }

  Rational oneFourth(1, 4);

  Rational result;

  

  result = oneFourth * 2; // fine

  result = 2 * oneFourth; // hooray, it works!

  这样的确使故事有了一个圆满的结局,但是有一个吹毛求疵的毛病。operator* 应该不应该作为 Rational 类的友元呢?

  在这种情况下,答案是不,因为 operator* 能够根据 Rational 的 public 接口完全实现。上面的代码展示了做这件事的方法之一。这导出了一条重要的结论:与成员函数相对的是非成员函数,而不是友元函数。太多的程序员假设如果一个函数与一个类有关而又不应该作为成员时(例如,因为所有的参数都需要类型转换),它应该作为友元。这个示例证明这样的推理是有缺陷的。无论何时,只有你能避免友元函数,你就避免它,因为,就像在现实生活中,朋友的麻烦通常多于他们的价值。当然,有时友谊是正当的,但是事实表明仅仅因为函数不应该作为成员并不自动意味着它应该作为友元。 本 Item 包含真理,除了真理一无所有,但它还不是完整的真理。当你从 Object-Oriented C++ 穿过界线进入 Template C++而且将 Rational 做成一个类模板代替一个类,就有新的问题要考虑,也有新的方法来解决它们,以及一些令人惊讶的设计含义。

  Things to Remember

  ·如果你需要在一个函数的所有参数(包括被 this 指针所指向的那个)上使用类型转换,这个函数必须是一个非成员。

 

上下文章:

 

上一篇文章: C++程序设计从零开始之指针 下一篇文章: C#利用正则表达式实现字符串搜索

相关文章:

  • OracleSQL内置函数大全之三
  • Oracle常用的OCI函数上
  • OracleSQL内置函数大全之二
  • Oracle常用的OCI函数下
  • Oracle常用的OCI函数中

相关软件:

  • 函数图像作图器 1.0
  • DLL导出函数查看器 V1.3
  • VB函数添加大师 V2.23
  • BOOM超级团队成员系统 v1.5
  • Excel2003函数应用完全手册(PDF)
  • VB API函数参考手册(CHM)

 

快速导航

  • 网络学院
  • 精品汇聚
  • 字体下载
  • 教程下载
  • ASP源码
  • PHP源码
  • Net源码
  • JSP 源码

编程技术分类导航

  • ASP & ASP.NET教程
  • PHP教程
  • JSP教程
  • C/C++教程
  • VB & VB.NET教程
  • VC教程
  • Delphi教程
  • BCB教程
  • VFP教程
  • PB教程
  • JAVA教程
  • XML教程
  • C#教程
  • CGI教程

本类经典文章推荐

  • C/C++作用域引申出的编码规范
  • 程序宝典:C++学习感想
  • C语言:黑客学员必修课(一)
  • C语言:黑客学员必修课(二)
  • 使用多线程实现数据实时采集
  • C++编程中的四个调试小技巧
  • C语言程序设计基础之联合
  • 学C++不得不看的一篇文章
  • C,曾经的程序员梦
  • 水滴石穿C语言之可变参数问题

C/C++教程阅读排行

  • 学C++不得不看的一篇文章
  • 水滴石穿C语言之可变参数问题
  • C语言:黑客学员必修课(一)
  • 程序宝典:C++学习感想
  • 我的Visual C++入门之路
  • 初学者学习C++的50条忠告
  • C++中的struct专题研究
  • C++箴言:只要可能就用const
  • 强大的语言——C入门
  • C++编程中的四个调试小技巧

编程技术阅读总排行

  • VB入门教程之一
  • Java连接数据库实例
  • 第二章 PowerBuilder 入门之创建新...
  • VC++之List Box/Check List Box控...
  • 第一章 什么是PowerBuilder
  • VC++ List Ctrl控件
  • VC++ Combo Box/Combo Box Ex控件
  • 学C++不得不看的一篇文章
  • VB入门教程之二
  • VC++之Button控件

广告位置

字母检索 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 回到顶部

关于我们 | 版权声明 | 免责条款 | 广告联系 | 软件发布 | 下载帮助 | 下载排行 | 网站地图 | 特别鸣谢 | 友情连接

copyright; 2005-2008 D9soft.com 第九软件网 版权所有