第11章 使用类
思维导图
1.运算符重载
如下是一种实现加法运算的方式:
1.1 不应该返回对临时对象的引用
Time Time::Sum(const Time &t) const
{
Time sum;
sum.minutes = minutes + t.minutes;
sum.hours = hours + t.hours + sum.minutes/60;
sum.minutes %= 60;
return sum;
}
如果返回类型为Time类型的对象,在调用结束后,返回对象是sum
的副本,使得调用函数可以使用它。
然而如果返回类型是引用,也就是对局部变量sum
的引用,由于函数调用结束后局部变量将会被删除,因此引用将会指向一个不存在的对象。
1.2 加法运算符的重载
//方法声明
Time operator+(const Time & t) const;
//方法定义
Time Time::operator+(const Time & t) const
{
...
}
//方法调用
total = coding.operator+(fixing);
total = coding + fixing;
方法调用时,运算符左侧的对象是调用对象(被隐式使用),运算符右侧的对象是作为参数被传递的对象(被显式使用)。
注意重载后的运算符至少有一个操作数是用户定义的数据类型。
重载后缀++运算符:
CTimer operator++(int); //后缀++
1.3 在运算符重载方法定义中使用构造函数
如果方法通过计算要得到一个新对象,可以考虑使用构造函数来完成工作。使用构造函数可以避免麻烦,并确保新的对象是按照正确的方式创建的。
Vector Vector::operator+(const Vector &b) const
{
return Vector(x + b.x, y + b.y);
}
2. 友元
友元函数是非成员函数,但有访问类成员数据的权限,一般处理对象不是第一个操作数的运算符重载问题。
友元函数可以视作一部分类的扩展接口。
2.1 友元的必要性
假设A和B是某个用户定义类型的对象,实现了加法运算符的重载,那么:
A = B + 2.75
,相当于A = B.operator+(B)
但是例如A = 2.75 + B
却无法通过类成员函数实现,所以需要一种非成员函数,它使用的所有值都是显式参数。
2.2 友元的简单创建与使用
1.声明
友元函数在类声明中声明,但它不是成员函数,不能使用成员运算符调用。
//将原型放在类声明中
friend Time operator*(double m, const Time & t);
2.定义
友元函数的定义中不要出现限定符Time::
和关键字friend
。
Time operator*(double m, const Time & t)
{
...
}
友元函数和成员函数有着相同的访问权限,都可以访问类的私有成员。
但是友元函数在访问时需要加上成员运算符或作用域解析运算符,如果Vector
类中存在枚举enum Mode {RECT, POL};
由于友元函数不在类作用域内,因此必须使用Vector::RECT
,而不能使用RECT
。
3.友元函数的替换
不使用友元函数,可以实现同样的功能。但是也可以将其设置为友元函数。
Time operator*(double m, const Time & t)
{
return t*m; //调用类方法
}
2.3 重载输入输出运算符
#include <iostream>
...
friend std::ostream & operator<<(std::ostream & os, const Time & t);
该函数需要访问Time
对象的数据成员,是Time
的友元函数;将ostream
对象作为一个整体来使用,不访问它的数据成员,因此不是ostream
的友元函数。
由于需要考虑cout << "Trip time:" << trip;
的情况,因此operator<<
函数的返回值应该为指向ostream
对象的引用。
std::ostream & operator<<(std::ostream & os, const Time & t)
{
os << t.hours << t.minutes;
return os;
}
重载输入运算符:
friend std::istream& operator>>(std::istream& is, CTimer &t);
3.类的自动转换和强制类型转换
3.1 C++对内置类型的转换方式
1.自动转换
如果类型兼容,C++自动将这个值转换为接收变量的类型;
比如double time = 11;
,自动将int
类型转换为double
。
2.强制类型转换
如果类型不兼容,可以使用强制类型转换。
比如int *p = (int *) 10;
,这里必须使用强制类型转换。
3.2 构造函数用作自动类型转换函数
1.构造函数的条件
在C++中,接受一个参数的构造函数可以作为自动类型转换函数。从而实现从参数类型到类类型的转换。
//构造函数声明
Stonewt(double lbs);
//自动类型转换
Stonewt myCat;
myCat = 19.6;
2.自动类型转换的过程
程序将先使用该构造函数创建一个临时对象,用19.6对临时对象进行初始化,然后将临时对象逐成员复制到myCat
中,最后临时对象被删除。
3.可自动转换的类型
当转换不存在二义性时,Stonewt(double lbs)
也可以用于转换其它数值类型,例如Stonewt Jumbo(700);
和Jumbo = 700;
都会先将int
转换为double
,然后在调用Stonewt(double lbs)
构造函数。
4.关闭自动类型转换
使用explicit
关键字用于关闭这种自动特性。
//构造函数声明
explicit Stonewt(double lbs);
//强制类型转换
Stonewt myCat;
myCat = Stonewt(19.6);
5.自动类型转换的发生时机
将Stonewt
对象初始化为double
值时;
将double
值赋值给Stonewt
对象时;
将double
值传递给接收Stonewt
参数的函数时;
返回值被声明为Stonewt
类型的函数试图返回double
值时。
3.3 转换函数
构造函数用于从某种类型到类类型的转换。如果要做类类型到其它类型的转换,需要用到转换函数。
转换函数的形式:
operator typeName();
转换函数的特点:
1.必须是类方法
2.不能指定返回类型
3.不能有参数
转换函数的声明和定义:
operator int() const;
Stonewt::operator int() const
{
return int(pounds + 0.5); //基本类型间的显式类型转换
}
转换函数的使用方法:
Stonewt wolfe(285.7);
int host = int(wolfe);
可以使用explicit
避免自动的隐式类型转换:
explicit operator int() const;
3.4 友元函数、运算符重载、转换函数
使double
类型的值与Stonewt
类型的值相加的方法:
1.使用友元函数
friend Stonewt operator+(const Stonewt &, const Stonewt &);
优点是程序简短,缺点是每次都需要调用转换函数,增加了时间和内存开销。
2.重载加法运算符,显式使用double类型的参数
Stonewt operator+(double x);
friend Stonewt operator+(double x, Stonewt & s);
优点是不需要调用转换函数,运行速度较快;缺点是程序较长。
4.课后题
1.友元函数和成员函数之间的区别是什么?
从函数调用方式和访问对象成员的方法两个角度:
成员函数是类定义的一部分,通过特定的对象来调用;
成员函数可以隐式访问对象的成员,而无需使用成员运算符。
友元函数不是类的组成部分,被称为直接函数调用;
友元函数不能隐式访问对象的成员,必须对作为参数传递的对象使用成员运算符。
2.非成员函数必须是友元才能访问类成员吗?
如果访问类的私有成员,必须是友元;
如果访问类的公有成员,可以不是友元。
3.哪些运算符不能重载?
sizeof
、.
、.*
、::
、?:
4.在重载运算符=、()、[]和->时,有什么限制?
只能使用类成员函数进行重载,不能使用友元函数进行重载。