重载操作符与转换

--赋值、下标、成员訪问操作符

一、赋值操作符

类赋值操作符接受类类型形參,通常该形參是对类类型的const引用,但也能够是类类型或对类类型的非const引用。假设未定义这个操作符,则编译器将合成它。类赋值操作符必须是类的成员,以便编译器能够知道是否须要合成一个。并且还能够为一个类定义很多附加的赋值操作符,这些赋值操作符会由于右操作数的不同而构成重载!如string类型:

  1. string car("Volks");
  2. car = "Studebaker"; //接受C风格字符串实參
  3.  
  4. string model;
  5. model = 'T'; //接受字符实參
  6. model = car; //接受string实參

为了支持这些操作符,string类应包括例如以下的成员:

  1. class string
  2. {
  3. public:
  4. string &operator=(const string &);
  5. string &operator=(const char *);
  6. string &operator=(char);
  7.  
  8. //...
  9. };

赋值操作符能够重载,不管形參为何种类型,赋值操作符都必须定义为成员函数!这一点与复合赋值操作符不同。

赋值必须返回*this的引用

为了与内置类型达成一致,赋值返回一个引用,同一时候也能够不用创建和撤销结果的暂时副本。返回值一般是左操作数的引用:

  1. Sales_item &Sales_item::operator+=(const Sales_item &rhs)
  2. {
  3. units_sold += rhs.units_sold;
  4. revenue += rhs.revenue;
  5.  
  6. return *this;
  7. }

【最佳实践】

一般而言:赋值操作符和复合赋值操作符应返回左操作数的引用!

  1. //P442 习题14.14
  2. Sales_item &Sales_item::operator=(const string &str)
  3. {
  4. isbn = str;
  5.  
  6. return *this;
  7. }
  1. //习题14.15/16
  2. class CheckoutRecord
  3. {
  4. public:
  5. typedef unsigned Date;
  6. //...
  7. CheckoutRecord &operator=(const CheckoutRecord &rhs);
  8. //新加入�一本书
  9. CheckoutRecord &operator=(const string &);
  10. //新加入�一位等待读者
  11. CheckoutRecord &operator=(const pair<string,string> &);
  12.  
  13. private:
  14. double book_id;
  15. string title;
  16. Date date_borrowed;
  17. Date date_due;
  18. pair<string,string> borrower;
  19. vector< pair<string,string> * > wait_list;
  20. };
  21.  
  22. CheckoutRecord &CheckoutRecord::operator=(const CheckoutRecord &rhs)
  23. {
  24. book_id = rhs.book_id;
  25. title = rhs.title;
  26. date_borrowed = rhs.date_borrowed;
  27. date_due = rhs.date_due;
  28. borrower = rhs.borrower;
  29.  
  30. //为vector赋值
  31. wait_list.clear();
  32. for (vector< pair<string,string> * >::const_iterator iter = rhs.wait_list.begin();
  33. iter != rhs.wait_list.end(); ++iter)
  34. {
  35. pair<string,string> *ppair = new pair<string,string>;
  36. *ppair = **iter; //复制的是,vector中的对象,而不是指针!
  37.  
  38. wait_list.push_back(ppair);
  39. }
  40.  
  41. return *this;
  42. }
  43.  
  44. CheckoutRecord &CheckoutRecord::operator=(const string &Title)
  45. {
  46. title = Title;
  47.  
  48. return *this;
  49. }
  50.  
  51. CheckoutRecord &CheckoutRecord::operator=(const pair<string,string> &newReader)
  52. {
  53. pair<string,string> *ppair = new pair<string,string>;
  54. *ppair = newReader;
  55.  
  56. wait_list.push_back(ppair);
  57.  
  58. return *this;
  59. }

二、下标操作符

能够从容器中检索单个元素的容器类通常会定义下标操作符,即operator[]。如vector和string。

下标操作符必须定义为类成员函数。

1、提供读写訪问

定义下标操作符比較复杂的地方在于,它在用作赋值的左右操作符数时都应该能表现正常。下标操作符出如今左边,必须生成左值,能够指定引用作为返回类型而得到左值。仅仅要下标操作符返回引用,就可用作赋值的随意一方。

【最佳实践】

类定义下标操作符时,一般须要定义两个版本号:一个为非const成员并返回引用,还有一个为const成员并返回引用!

2、原型下标操作

  1. class Foo
  2. {
  3. public:
  4. int &operator[] (const size_t);
  5. const int &operator[] (const size_t) const;
  6.  
  7. private:
  8. vector<int> data;
  9. };
  10.  
  11. int &Foo::operator[](const size_t index)
  12. {
  13. return data[index]; //注意:没有范围检測...
  14. }
  15.  
  16. //除了函数原型不同之外,没有其它不论什么区别!
  17. const int &Foo::operator[] (const size_t index) const
  18. {
  19. return data[index];
  20. }

  1. //P443 习题14.17/18/19
  2. class CheckoutRecord
  3. {
  4. public:
  5. typedef unsigned Date;
  6. typedef vector< pair<string,string> *>::size_type sizeType;
  7. //...
  8.  
  9. pair<string,string> &operator[] (const size_t);
  10. const pair<string,string> &operator[] (const size_t) const;
  11.  
  12. const pair<string,string> &get_a_waiter(sizeType) const;
  13.  
  14. private:
  15. double book_id;
  16. string title;
  17. Date date_borrowed;
  18. Date date_due;
  19. pair<string,string> borrower;
  20. vector< pair<string,string> * > wait_list;
  21. };
  22.  
  23. pair<string,string> &CheckoutRecord::operator[](const size_t index)
  24. {
  25. return *wait_list.at(index);
  26. }
  27.  
  28. const pair<string,string> &CheckoutRecord::operator[](const size_t index) const
  29. {
  30. return *wait_list.at(index);
  31. }
  32.  
  33. /**尽管下标操作符使用简单,可是他也有他的缺点
  34. *如:下标操作符适合于表示“获取容器中特定元素”的语义
  35. *而获取一个等待者则语义不明
  36. *这样就不如使用一个普通的成员函数表意明白!
  37. */
  38. //事实上就是将函数原型做了略微的修改,其它方面都不做更改
  39. const pair<string,string> &CheckoutRecord::get_a_waiter(sizeType index) const
  40. {
  41. return *wait_list.at(index);
  42. }

三、成员訪问操作符

为了支持指针型类,比如迭代器,C++语言同意重载解引用操作符(*)和箭头操作符(->)。

箭头操作符必须定义为类成员函数。解引用操作符不要求定义为成员,可是将他作为成员一般也是正确的!

1、构建更安全的指针

解引用操作符和箭头操作符经常使用在实现智能指针的类中。作为样例,假定想要定义一个类类型表示指向第十二章Screen类型对象的指针,将该类命名为ScreenPtr。

不用为 ScreenPtr类定义默认构造函数。因此,我们知道一个ScreenPtr对象将总 是指向一个Screen对象,不会有未绑定的ScreenPtr。应用程序能够使用ScreenPtr对象而无须首先測试它是否指向一个Screen对象。

像HasPtr类一样,ScreenPtr类将对其指针进行使用计数。我们将定义一个伙伴类保存指针及其相关使用计数:

  1. class ScrPtr
  2. {
  3. friend class ScreenPtr;
  4. Screen *sp;
  5. size_t use;
  6. ScrPtr(Screen *p):sp(p),use(1){}
  7. ~ScrPtr()
  8. {
  9. delete sp;
  10. }
  11.  
  12. };

ScrPtr保存指针及其相关使用计数。将ScreenPtr设为友元,以便ScreenPtr能够訪问使用计数。

  1. class ScreenPtr
  2. {
  3. public:
  4. ScreenPtr(Screen *p):ptr(new ScrPtr(p)){}
  5. ScreenPtr(const ScreenPtr &orig):ptr(orig.ptr)
  6. {
  7. ++ ptr->use;
  8. }
  9.  
  10. ScreenPtr &operator=(const ScreenPtr &);
  11.  
  12. ~ScreenPtr()
  13. {
  14. if ( -- ptr->use == 0 )
  15. delete ptr;
  16. }
  17.  
  18. private:
  19. ScrPtr *ptr;
  20. };

由于没有默认构造函数,所以ScreenPtr类型的每一个对象都必须提供一个初始化函数,初始化函数必须是还有一个ScreenPtr对象或者运行动态分配的Screen指针。

  1. ScreenPtr p1; //Error
  2. ScreenPtr p2(new Screen); //OK

2、支持指针操作

指针支持的基本操作有解引用操作和箭头操作,我们的类能够这样定义这些操作:

  1. class ScreenPtr
  2. {
  3. public:
  4. Screen &operator*()
  5. {
  6. return * ptr->sp;
  7. }
  8. Screen *operator->()
  9. {
  10. return ptr -> sp;
  11. }
  12.  
  13. const Screen &operator*() const
  14. {
  15. return * ptr -> sp;
  16. }
  17. const Screen *operator->() const
  18. {
  19. return ptr -> sp;
  20. }
  21. //AS Before...
  22.  
  23. private:
  24. ScrPtr *ptr;
  25. };

3、重载解引用操作符

我们的解引用操作符有const和非 const版本号。它们的区别在于返回类型:const成员返回const引用以防止用户改变基础对象

4、重载箭头操作符

箭头操作符与众不同。它可能表现得像二元操作符一样:接受一个对象和一个成员名。对对象解引用以获取成员。不管外表怎样,箭头操作符不接受显式形參

这里没有第二个形參,由于->的右操作数不是表达式,相反,是相应着类成员的一个标识符。没有明显可行的途径将一个标识符作为形參传递给函数,相反,由编译器处理获取成员的工作

当这样编写时:

  1. point -> action();

由于优先级规则,它实际等价于这样编写:

  1. (point -> action) ();

换句话说,我们想要调用的是对point->action求值的结果。编译器这样对该代码进行求值【以下的一段话不太好理解,注意】:

1)假设point是一个指针,指向具有名为action的成员的类对象,则编译器将代码编译为调用该对象的 action成员。

2)否则,假设action是定义了operator->操作符的类的一个对象,则point-> action 与point.operator->()->action同样。即,运行point的operator-> (),然后使用该结果反复这三步。

3)否则,代码出错。

5、使用重载箭头

能够这样使用ScreenPtr对象訪问Screen对象的成员:

  1. Screen myScreen;
  2. ScreenPtr p(&myScreen);
  3. p -> display();

由于 p是一个ScreenPtr对象,p->display的含义与对(p.operator->())->display求值同样。对p.operator->()求值将调用ScreenPtr类的operator->,它返回指向Screen对象的指针,该指针用于获取并运行ScreenPtr所指对象的display成员。

6、对重载箭头的返回值的约束(最后两段不太好理解(ˇ_ˇ)~)

重载箭头操作符必须返回指向类类型的指针,或者返回定义了自己的箭头操作符的类类型对象!

假设返回类型是指针,则内置箭头操作符可用于该指针,编译器对该指针解引用并从结果对象获取指定成员。

假设返回类型是类类型的其它对象(或是这样的对象的引用),则将递归应用该操作符。编译器检查返回对象所属类型是否具有成员箭头,假设有,就应用那个操作符;否则,编译器产生一个错误。这个过程继续下去,直到返回一个指向带有指定成员的的对象的指针,或者返回某些其它值,在后一种情况下,代码出错。

  1. //P446 习题14.20
  2. ScreenPtr &ScreenPtr::operator=(const ScreenPtr &rhs)
  3. {
  4. ++ rhs.ptr -> use;
  5.  
  6. if (-- ptr->use == 0 )
  7. {
  8. delete ptr;
  9. }
  10. ptr = rhs.ptr;
  11.  
  12. return *this;
  13. }

  1. //习题14.21
  2. class NoName
  3. {
  4. public:
  5. NoName(Screen *p):ptr(new ScreenPtr(p)){}
  6.  
  7. ScreenPtr operator-> ()
  8. {
  9. return *ptr;
  10. }
  11. const ScreenPtr operator-> () const
  12. {
  13. return *ptr;
  14. }
  15.  
  16. private:
  17. ScreenPtr *ptr;
  18. };

  1. //习题14.22
  2. /*
  3. *==操作符须要在类中声明
  4. * friend bool operator==(const ScreenPtr &lhs,
  5. const ScreenPtr &rhs);
  6. */
  7. inline
  8. bool operator==(const ScreenPtr &lhs,const ScreenPtr &rhs)
  9. {
  10. return lhs.ptr == rhs.ptr;
  11. }
  12. inline
  13. bool operator!=(const ScreenPtr &lhs,const ScreenPtr &rhs)
  14. {
  15. return !(lhs == rhs);
  16. }

C++ Primer 学习笔记_60_重载操作符与转换 --赋值、下标、成员訪问操作符的更多相关文章

  1. C++ Primer 学习笔记_63_重载运算符和转换 --转换和类类型【上】

    重载运算符和转换 --转换与类类型[上] 引言: 在前面我们提到过:能够用一个实參调用的位 unsignedchar 相同范围的值,即:0到255. 这个类能够捕获下溢和上溢错误,因此使用起来比内置u ...

  2. C++ Primer 学习笔记_61_重载操作符与转换 --自增/自减操作符

    重载操作符与转换 --自增/自减操作符 引言: 自增,自减操作符常常由诸如迭代器这种类实现,这种类提供相似于指针的行为来訪问序列中的元素.比如,能够定义一个类,该类指向一个数组并为该数组中的元素提供訪 ...

  3. C++ Primer 学习笔记_62_重载操作符与转换 --调用操作符和函数对象

    重载操作符与转换 --调用操作符和函数对象 引言: 能够为类类型的对象重载函数调用操作符:一般为表示操作的类重载调用操作符! struct absInt { int operator() (int v ...

  4. C++ Primer笔记12_运算符重载_递增递减运算符_成员訪问运算符

    1.递增递减运算符 C++语言并不要求递增递减运算符必须是类的成员.可是由于他们改变的正好是所操作对象的状态.所以建议设定为成员函数. 对于递增与递减运算符来说,有前置与后置两个版本号,因此.我们应该 ...

  5. C++ Primer学习笔记(三) C++中函数是一种类型!!!

    C++中函数是一种类型!C++中函数是一种类型!C++中函数是一种类型! 函数名就是变量!函数名就是变量!函数名就是变量! (---20160618最新消息,函数名不是变量名...囧) (---201 ...

  6. C++ Primer学习笔记(二)

    题外话:一工作起来就没有大段的时间学习了,如何充分利用碎片时间是个好问题. 接  C++ Primer学习笔记(一)   27.与 vector 类型相比,数组的显著缺陷在于:数组的长度是固定的,无法 ...

  7. C++ Primer 学习笔记_53_类和数据抽象 --友元、static员

    分类 --友元.static成员 一.友元 友元机制同意一个类将对其.友元关系:一个样例 如果一个窗体管理类Window_Mgr可能须要訪问由其管理的Screen对象的内部数据.Screen应该同意其 ...

  8. C++ Primer 学习笔记_38_STL实践与分析(12)--集成的应用程序容器:文本查询程序

    STL实践与分析 --容器的综合应用:文本查询程序 引言: 本章中最重点的实例.由于不须要用到multiset与multimap的内容.于是将这一小节提到了前面.通过这个实例程序,大师分析问题的智慧, ...

  9. WebSocket学习笔记IE,IOS,Android等设备的兼容性问

    WebSocket学习笔记IE,IOS,Android等设备的兼容性问 一.背景 公司最近准备将一套产品放到Andriod和IOS上面去,为了统一应用的开发方式,决定用各平台APP嵌套一个HTML5浏 ...

随机推荐

  1. Bugzilla使用手册及解决方案

    Bugzilla使用手册 Bugzilla 是一个开源的缺陷跟踪系统(Bug-Tracking System),它可以管理软件开发中缺陷的提交(new),修复(resolve),关闭(close)等整 ...

  2. CocoaPods on Xcode 6 and Yosemite

    老子今天又给环境跪了..... cocoapods 在升级完新系统以后无法工作 解决cocoapods 在 mac 10.10下报错 错误例如以下. /System/Library/Framework ...

  3. BZOJ 2287: 【POJ Challenge】消失之物( 背包dp )

    虽然A掉了但是时间感人啊.... f( x, k ) 表示使用前 x 种填满容量为 k 的背包的方案数, g( x , k ) 表示使用后 x 种填满容量为 k 的背包的方案数. 丢了第 i 个, 要 ...

  4. docker学习笔记1:docke环境的查看

    本文的操作是在ubuntu操作系统下的. 一.环境检查 当登录一个安装了docker的机器后,首先我们要检查下docker环境如何. 1.命令:docker -v 上述命令返回安装的docker的版本 ...

  5. C++ vs.net设置UTF8字符

    1.将main.cpp改成utf-8编码,方法是点击main.cpp,然后选择菜单文件->高级保存选项.[所有源码都要转换成uft-8] 2.在你的main函数里,设置如下代码,完美解决qt5的 ...

  6. 搭建python集成开发环境.

    需要搭建的内容一共有三项, python ,wxpython 以及spe.     其中spe 是python 的可视化集成开发环境(ide) , 其需要python GUI图形库wxpython的支 ...

  7. 普通图片转ascii码字符图

    效果图 基本思路 把图片每个像素点的信息拿出来,最重要的是拿到rgb的值 把每个像素点由rgb转成灰度图像,即0-255 给0-255分级,把每个等级的像素点转换成ascii码,完成 实现 第一步:获 ...

  8. perl 为什么要用引用来做对象呢?

    perl 为什么要用引用来做对象呢? 因为一个重要的原因是 my 引用 脱离作用域,外部仍旧生效

  9. extjs_04_grid(弹出窗口&amp;行编辑器 CRUD数据)

    1.弹出窗口(添加.删除) watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWRhbV93enM=/font/5a6L5L2T/fontsize/400/f ...

  10. 【图像处理】Gabor过滤器

    Gabor内核参考wiki 使用实数Real的公式计算核函数代码: Mat getGaborFilter(float lambda, float theta, float sigma2,float g ...