【《C++ Primer Plus》读书笔记】第11章 使用类

来源:互联网 发布:上海淘宝设计速成班 编辑:程序博客网 时间:2024/06/14 18:30

运算符重载

一个简单的时间函数相加的函数头来说明格式:

Time Time::operator +(const Time & t) const

函数体的实现就是按照常规来实现所有数据的处理,就像我们定义了一个sum()函数一样去实现。

然后调用的时候有两种形式:

//方式1, 太非主流了,直接调用函数。。。total = time_1.operator+(time_2);//方式2total = time_1 + time_2

支持连加吗?

那要看我们的函数返回值,如果返回的也是一个Time类型的对象,当然可以,如果是其他情况,返回值类型不是作用域的类型,就不可以了。

重载限制

  • 重载之后的运算符至少有一个操作数是用户定义的类型,防止用户重载C++为已有的一些数据类型定义的运算规则,比如不能把int类型的加法定义成计算差吧
  • 重载之后的运算符不能违反原来的语法规则,比如不能改变运算符的操作数,优先级等
  • 不能创建新的运算符
  • 有些运算符只能通过类成员函数来重载:
    = : 赋值运算符
    (): 函数调用运算符
    []: 下标运算符
    ->: 通过指针访问类成员的运算符
  • 有些运算符不能被重载,太多就不列出

友元函数

友元分为3种:

  • 友元函数
  • 友元类
  • 友元成员函数

引入友元函数,为了让非成员函数可以访问类的私有变量
友元函数必须在类内声明,才能标识该友元函数是这个类的友元
一个函数头例子:

friend Time operator*(double m, const Time & t);

因为这个函数并不是成员函数,所以就不用加上类作用域Time::,二期在实现函数体的时候是不需要加上friend关键字的。
我们在很多情况下涉及到,一个运算符的第一个操作数并不是类对象,而是其他类型,这时我们不能通过类的重载的运算符来调用函数,而是要通过定义友元函数去调用

常见的 << 和 >> 的重载:

给一个常见的函数定义示例:

ostream & operator << (ostream & os, const Time & t) {    os << t.hours << "hours, " << t.minutes << "minutes";}

这个函数有两个点要说:

  • 参数列表

之前我一直有一个困惑第一个参数os是干什么用的,今天看到这里才发现cout是一个ostream的类对象;
这样比如

cout << time_1;

这句话里,cout这个ostream类对象就是 “<<” 这个运算符的左操作数

  • 返回值
    为什么返回值是一个ostream的引用,是为了解决连续使用这个运算符的情况,如下,在处理每个右操作数的时候,保证左操作数都是一个ostream的引用
    所以估计ostream自己在重载这个运算符的时候返回类型也是ostream的引用
cout << "This is time_1: " << time_1;

这样,” >> “操作符是不是类似了~只不过换成istream就好了

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

隐式转换:

  • 赋值运算符调用符合参数列表的构造函数

如果我们有一个构造函数声明如下:

Stonewt(double a);

那么如下操作是可以通过编译的:

Stonewt mycat;myCat = 19.6;

只针对构造函数的参数只有一个参数,或者有n个参数,但是其他n-1个参数都有默认值的情况;
防止意外的转换,我们在定义这类构造函数的时候可以用explicit关键字关闭这个特性:

explicit Stonewt(double a);

转换函数:

  • 用户自定义的强制转换
    我们想要实现如下的效果:
Stonewt wolfe(285.7);double host = (double)wolfe; 

那么我们就要在类里面重载C++自己的类型转换函数了

public:    operator double() const;

然后再去实现:

    Stonewt::operator double() const {        return pounds;          //this->pounds;pounds是Stonewt类型的一个double类型的成员变量    }

这里你看,我们在声明函数和实现函数的函数头都没有定义返回值类型,但是函数体里却return了一个double类型的值,这也是转换函数的一个特殊的地方
另外,我们在定义了这个转换函数之后,也可以直接使用隐式转换:

Stonewt wolfe(285.7);double host = wolfe; 

谨慎使用隐式转换!!!

再谈友元函数:

我们又要重载运算符+, 有两种方式:

//成员函数Stonewt Stonewt::operator + (const Stonewt & st) const {    double pds = pounds + st.pounds;    Stonewt sum(pds);    return sum;}//友元函数Stonewt operator + (const Stonewt & st1, const Stonewt & st2) {    double pds = st1.pounds + st2.pounds;    Stonewt sum(pds);    return sum;}

就算我们有了隐式转换,我们的隐式转换也不会应用于成员函数的调用者,也就是说,成员函数的左操作数必须一开始是Stonewt类型的成员,而不能是由double类型转换而来的;但是参数,和友元函数的参数都是可以接受隐式转换的结果。

哈哈,讲个笑话:

可以相互转换的混淆:
如果我在程序中定义了A, B两种类型,然后同时实现了A到B的隐式转换和B到A的隐式转换,那么如果我定义了一个函数参数有A类型,也有B类型,那转换会不会混淆呢?
结果应该是这种情况就不用转换了,该是谁就是谁

所以,还是尽量避免隐式转换,老老实实加一个explicit,会让自己分析代码的时候不至于晕过去

1 0