第11章 使用类

本章内容包括:

  • 本章内容包括:
  • 运算符重载
  • 友元函数
  • 重载<<运算符,以便用于输出
  • 状态成员
  • 使用rand()生成随机值
  • 类的自动转换和强制类型转换
  • 类转换函数

    1. 学习C++的难点之一是需要记住大量的东西,但在拥有丰富的实践经验之前,根本不可能全部记住这些东西.从这种意义上说,学习C++就像学习功能复杂的字处理程序或电子制表程序一样.任何特性都不可怕,但多数人只掌握了哪些经常使用的特性.
    2. 正如C++创始人Bjarne Stroustrop在一次C++专业程序员大会上所建议的:”轻松地使用这样语言.不要觉得必须使用所有的特性,不要在第一次学习时就试图使用所有的特性.”

11.1 运算符重载

  • 运算符重载是一种形式的C++多态.
  • C++允许将运算重载扩展到用户定义的类型.
  • 要重载运算符,需使用被称为运算符函数的特殊函数形式.运算符函数的格式如下:operator op(argument list)

11.2 计算时间:一个运算符重载示例

  • 程序清单11.1 mytime0.h
  • 程序清单11.2 mytime0.cpp
  • 警告:不要返回指向局部变量或临时对象的引用.函数执行完毕后,局部变量和临时对象将消失,引用将指向不存在的数据.
  • 程序清单11.3 usetime0.cpp

11.2.1 添加加法运算符

  • 程序清单11.4 mytime1.cpp
  • 程序清单11.5 mytime1.cpp
  • 注意:在运算符表示法中,运算符左侧的对象是调用对象,运算符右边的对象是作为参数被传递的对象.
  • 程序清单11.6 usetime1.cpp
  • 总之,operator+()函数的名称使得可以使用函数表示法或运算符表示法来调用它.

11.2.2 重载限制

  • C++对用户定义的运算符重载的限制 
    1. 重载后的运算符必须至少有一个操作数是用户定义的类型,这将防止用户为标准类型重载运算符.
    2. 使用运算符时不能违反运算符原来的句法规则.
    3. 不能创建新运算符.
    4. 不能重载下面的运算符 
sizeof
.
.*
::
?:
typeid
const_cast
dynamic_cast
reinterpret_cast
static_cast
  • 表11.1
列1 列2 列3 列4 列5 列6
+ - * / % ^
& | ~= ! = <
> += -= *= /= %=
^= &= |= << >> >>=
<<= == != <= >= &&
|| ++ , ->* ->
() [] new delete new [] delete []

1. 表11.1中的大多数运算符都可以通过成员或非成员函数进行重载,但下面的运算符只能通过成员函数进行重载

  • =
  • ()
  • []
  • ->

11.2.3 其他重载运算符

  • 程序清单11.7 mytime2.h
  • 程序清单11.8 mytime2.cpp
  • 程序清单11.9 usetime2.cpp

11.3 友元

  • C++提供了另外一种形式的访问权限:友元.友元有3种: 
    • 友元函数
    • 友元类
    • 友元成员函数
  • 通过让函数称为类的友元,可以赋予该函数与类的成员函数相同的访问权限.
  • 非成员函数(记住,大多数运算符都可以通过成员或非成员函数来重载).非成员函数不能直接访问类的私有数据,至少常规非长远函数不能访问.然而,有一类特殊的非成员函数可以访问类的私有成员,他们被称为友元函数.

11.3.1 创建友元

  • 第一步是将其原型放在类声明中,并在原型声明前加上关键字friend;
  • 第二步是编写函数定义.因为它 不是成员函数,所以不要使用类似Time::限定符.另外,不要在定义中使用关键字friend.
  • 总之,类的友元函数是非成员函数,其访问权限与成员函数相同.
  • 友元是否有悖于OOP:应将友元函数看作类的扩展接口的组成部分.例如,从概念上看,double乘以Time和Time乘以double是完全相同的.也就是说,前一个要求有友元函数,后一个使用成员函数,这是C++句法的结果,而不是概念上的差别.通过使用友元函数和类方法,可以用同一个用户接口表达这两种操作.另外请记住,只有类声明可以决定哪一个函数是友元,因此类声明仍然控制了哪些函数可以访问私有数据.总之,类方法和友元知识表达类接口的两种不同机制.
  • 提示:如果要为类重载运算符,并将非类的项作为其第一个操作数,则可以用友元函数来反转操作数的顺序.

11.3.2 常用的友元:重载<<运算符

  1. <<的第一种重载版本 
    • 在UNIX,Linux和Windows命令行环境中,可将标准错误流重定向到文件.
  2. <<的第二种重载版本 
    • 警告:只有在类声明中的原型中才能使用friend关键字.除非函数定义也是原型,否则不能在函数定义中使用关键字.
  3. 程序清单11.10 mytime3.h
  4. 程序清单11.11 mytime3.cpp
  5. 程序清单11.12 usetime3.cpp

11.4 重载运算符:作为成员函数还是非成员函数

  • 注意:非成员版本的重载运算符函数所需的形参数目与运算符使用的操作数数目相同;而成员版本所需的参数数目少一个,因为其中的一个操作数是被隐式地传递的调用对象.
  • 对于某些运算符来说,成员函数是唯一合法的选择.在其他情况下,这两种格式没有太大的去别.有时,根据类设计,使用非成员函数版本可能更好(尤其是为类定义类型转换时).

11.5 再谈重载:一个矢量类

  • 程序清单11.13 vector.h

11.5.1 使用状态成员

  • 多种表示方式和类:可以用不同但等价的方式表示的量很常见.

11.5.2 为Vector类重载算术运算符

  • 提示:如果方法通过计算得到一个新的类对象,则应考虑是否可以使用类构造函数来完成这种工作.这样做不仅可以避免麻烦,而且可以确保新的对象是按照正确的方式创建的.
  • 1.乘法
  • 2.对已重载的运算符进行重载 
    • 注意:因为运算符重载是通过函数来实现的,所以只要运算符函数的特征标不同,使用的运算符数量与相应的内置C++运算符相同,就可以多次重载同一个运算符.

11.5.3 对实现的说明

  • 可以在一个程序中使用一种实现,而在另一个程序中使用另一种实现,但它们的用户接口相同.

11.5.4 使用vector类来模拟随机漫步

  • 程序清单11.15 randwalk.cpp

11.6 类的自动转换和强制类型转换

  • C++语言不自动转换不兼容的类型.
  • 程序清单11.16 stonewt.h
  • 程序清单11.17 stonewt.cpp 
    • 在C++中,接受一个参数的构造函数为将类型与该参数相同的值转换为类提供了蓝图.
    • 将构造函数用作自动类型转换函数似乎是一项不错的特性.然而,当程序员拥有更分股的C++经验时,将发现这种自动特性并非总是合乎需要的,因为这会导致意外的类型转换.因此,C++新增了关键字explicit用于关闭这种自动特性.
  • 注意:只接受一个参数的构造函数定义了从参数类型到类类型的转换.如果使用关键字explicit限定了这种构造函数,则它只能用于显示转换,否则也可以用于隐式转换.
  • 程序清单11.18 stone.cpp

11.6.1 转换函数

  • 转换函数是用户定义的强制类型转换,可以像使用强制类型转换那样使用它们.
  • 如何创建转换函数呢?要转换为typeName类型,需要使用的格式:operator typeName();
  • 请注意以下几点: 
    • 转换函数必须是类方法(意味着,它需要通过类对象来调用,从而告知函数要转换的值)
    • 转换函数不能指定返回类型
    • 转换函数不能有参数
  • 程序清单11.19 stonewt1.h
  • 程序清单11.20 stonewt1.cpp
  • 程序清单11.21 stone1.cpp
  • 原则上说,最好使用显式转换,而避免隐式转换.在C++98中,关键字explicit不能用于转换函数,但C++11消除了这种限制.
  • 警告:应谨慎地使用隐式转换函数.通常,最好选择仅在被显式地调用时才会执行的函数.
  • C++为类提供了下面的类型转换: 
    • 只有一个参数的类构造函数用于将类型与该参数相同的值转换为类类型.
    • 被称为转换函数的特殊类成员运算符函数,用于将类对象转换为其他类型.

11.6.2 转换函数和友元函数

  • 每一种方法都有其优点.第一种方法(依赖于隐式转换)是程序更简短,因为定义的函数较少.这也意味程序员需要完成的工作较少,出错的机会较小.这种方法的缺点是,每次需要转换时,都将调用转换构造函数,这增加时间和内存开销.
  • 第二种方法(增加一个显式的匹配类型的函数)则正号相反.它使程序较长,程序员需要完成的工作更多,但运行速度较快.

11.7 总结

  • 一般来说,访问私有类成员的唯一方法是使用类方法.C++使用友元函数来避开这种限制.要让函数称为友元,需要在类声明中声明该函数,并在声明前加上关键字friend.
  • 最常见的运算符重载任务之一是定义<<运算符,使之可与cout一起使用,来显示对象的内容.要让ostream对象称为第一个操作数,需要将运算符函数定义为友元;要使重新定义的运算符能与其自身拼接,需要将返回类型声明为ostream &.格式:
ostream & operator<<(ostream & os, const c_name & obj)
{
    os << ...;//display object contens
    return os;
}
  • 然而,如果类包含这样的方法,它返回需要显示的数据成员的值,则可以使用这些方法,无需在operator<<()中直接访问这些成员.在这种情况下,函数不必(也不应当)是友元.

11.8 复习题 
11.9 编程练习运算符重载

附件:本章源代码下载地址

猜你喜欢

转载自blog.csdn.net/weixin_39345003/article/details/82110414
今日推荐