第五章 运算符重载(类对象)
一、友元
-
友元是单向的,不能传递,也不能继承。
-
友元函数能够提高程序的运行效率,但是其破坏了类的封装性和隐藏性。
-
友元函数不是类的成员函数,只是在类内声明。
1.概念
一般来说,类的私有成员只能在类的内部访问,类之外是不能访问它们的。但如果将其他类或函数设置为类的友元(friend),就可以访问了。用这个比喻形容友元可能比较恰当:将类比作一个家庭,类的private 成员相当于家庭的秘密,一般的外人是不允许探听这些秘密的,只有 friend (朋友)才有能力探听这些秘密。
友元的形式可以分为友元函数和友元类。
class 类名
{ //...
friend 函数原型;
friend class 类名;
//...
}
2.友元函数
我们学过的函数形式有两种:全局函数(自由函数/普通函数)和成员函数。接下来,我们分别进行讨论。
①友元函数之全局函数
②友元函数之成员函数
class Point; //类的前向声明
//友元是单向的,友元是没有传递性的(A是B的友元,B是C的友元,A不是C的友元),不能被继承
//友元对于函数重载,每个重载的函数需要设置自己的友元函数
class Point
{
public:
Point(int ix = 0, int iy = 0)
: _ix(ix)
, _iy(iy)
{
}
void print() const
{
cout << "(" << _ix
<< "," << _iy
<< ")"; }
//友元的声明是不受 public/protected/private 关键字限制的
friend float distance(const Point & lhs, const Point & rhs);
friend float Line::distance(const Point & lhs, const Point & rhs);
private:
int _ix;
int _iy;
};
//友元函数之全局函数 (全局函数需要访问Point类里面的成员,所以需要设置为友元函数)
float distance(const Point & lhs, const Point & rhs)
{
return sqrt((lhs._ix - rhs._ix) * (lhs._ix - rhs._ix) +
(lhs._iy - rhs._iy) * (lhs._iy - rhs._iy));
//return hypot(lhs._ix - rhs._ix,lhs._iy - rhs._iy);
}
//友元函数之成员函数 (Line类里面的distance函数需要访问Point类里面的成员,所以需要在Point类里面声明Line类的distance函数,并设置有友元函数)
class Line
{
public:
float distance(const Point & lhs, const Point & rhs)
{
return sqrt((lhs._ix - rhs._ix) * (lhs._ix - rhs._ix) +
(lhs._iy - rhs._iy) * (lhs._iy - rhs._iy));
}
};
void test()
{
Point pt1(1,2);
Point pt2(4,6);
pt1.print();
cout << "--->";
pt2.print();
cout << "之间的距离为:" << Line().distance(pt1,pt2) << endl;
//Line line;
//cout << "之间的距离为:" << line.distance(pt1,pt2) << endl;
}
3.友元之友元类
假设类Line
中不止有一个 distance
成员函数,还有其他成员函数,它们都需要访问Point
的私有成员,如果还像上面的方式一个一个设置友元,就比较繁琐了,可以直接将Line
类设置 为Point
的友元。
class Point
{
//...
friend class Line;
//...
};
二、运算符重载(类对象)
1.为什么需要对运算符进行重载?
C++
预定义中的运算符的操作对象只局限于基本的内置数据类型,但是对于我们自定义的类型是没有办法操作的。但是大多时候我们需要对我们定义的类型进行类似的运算,这个时候就需要我们对这么运算符进行重新定义,赋予其新的功能,以满足自身的需求。例如:
class Complex
{
public:
Complex(double real = 0, double image = 0)
: _real(real)
, _image(image)
{
}
private:
double _real;
double _image;
};
void test()
{
Complex c1(1, 2), c2(3, 4);
Complex c3 = c1 + c2; //编译出错
}
2.运算符重载格式
为了使对用户自定义数据类型的数据的操作与内置的数据类型的数据的操作形式一致, C++ 提供了运算符的重载,通过把 C++ 中预定义的运算符重载为类的成员函数或者友元函数,使得对用户的自定义数据类型的数据(对象)的操作形式与 C++ 内部定义的类型的数据一致。
运算符重载的实质就是函数重载或函数多态。运算符重载是一种形式的C++
多态。目的在于让人能够用同名的函数来完成不同的基本操作。要重载运算符,需要使用被称为运算符函数的特殊函数形式,运算符函数形式:
返回类型 operator 运算符(参数表)
{
//...
}
3.运算符重载规则
运算符是一种通俗、直观的函数,比如: int x = 2 + 3;语句中的 “+” 操作符,系统本身就提供了很多个重载版本:
int operator+(int,int);
double operator+(double,double);
可以重载的运算符有:
不可以重载的运算符: .
(成员访问符) .*
(成员指针) ?:
(条件运算符) ::
(域运算符) sizeof
(长度运算符)
运算符重载还具有以下规则:
- 为了防止用户对标准类型进行运算符重载, C++ 规定重载的运算符的操作对象必须至少有一个是自定义类型或枚举类型
- 重载运算符之后,其优先级和结合性还是固定不变的。
- 重载不会改变运算符的用法,原来有几个操作数、操作数在左边还是在右边,这些都不会改变。
- 重载运算符函数不能有默认参数,否则就改变了运算符操作数的个数。
- 重载逻辑运算符( && , || )后,不再具备短路求值特性。
- 不能臆造一个并不存在的运算符,如@、$等
4.运算符重载的形式
运算符重载的形式有三种:
- 采用普通函数的重载形式
- 采用成员函数的重载形式
- 采用友元函数的重载形式
①以普通函数形式重载运算符
class Complex
{
public:
//...
double getReal() const
{
return _real;
}
double getImage() const
{
return _image;
}
//...
private:
double _real;
double _image;
};
//以普通函数进行重载,必须去设置get函数,用来获取对象成员
Complex operator+(const Complex & lhs, const Complex & rhs)
{ return
Complex(lhs.getReal() + rhs.getReal(),lhs.getImage() + rhs.getImage());
}
void test0()
{
Complex c1(1, 2), c2(3, 4);
Complex c3 = c1 + c2;//编译通过
}
②以成员函数形式重载运算符
既可以在类定义的同时,定义运算符函数使其成为inline
型,也可以在类定义之外定义运算符函数, 但要使用作用域限定符 “::”
注意:用成员函数重载双目运算符时,左操作数无须用参数输入,而是通过隐含的this
指针传入。
回到 Complex 的例子,如果以成员函数形式进行重载,则不需要定义 getter 函数:
class Complex
{
public:
//...
//运算符重载之成员函数
//非静态的成员函数的第一参数是隐藏的this指针(左操作数)
Complex operator+(const Complex & rhs)
{
return Complex(_real + rhs._real, _image + rhs._image);
}
};
③以友元函数形式重载运算符
class Complex
{
//...
friend Complex operator+(const Complex & lhs, const Complex & rhs);
};
//运算符重载之友元函数
Complex operator+(const Complex & lhs, const Complex & rhs)
{
return Complex(lhs._real + rhs._real, lhs._image + rhs._image);
}
5.特殊运算符的重载
①自增自减运算符 ++ – 的重载
C++
根据参数的个数来区分前置和后置形式。如果按照通常的方法(成员函数不带参数)来重载 ++/–运算符,那么重载的就是前置版本。要对后置形式进行重载,就必须为重载函数再增加一个 int 类型的参数,该参数仅仅用来告诉编译器这是一个运算符后置形式,在实际调用时不需要传递实参。
//前置++与后置++的区别?
//前置++的效率高一点,后置++的效率低一点
//&(++a) //返回值是左值 能取地址
//&(a++) //右值 不能取地址 (返回的是临时对象)
class Complex
{
public:
//...
//前置形式
Complex & operator++()
{
++_real;
++_image;
return *this;
}
//后置形式
Complex operator++(int) //int作为标记,并不传递参数
{
Complex tmp(*this);
++_real;
++_image;
return tmp; (返回临时对象)
}
};
②赋值运算符 = 的重载
对于赋值运算符 =
,只能以成员函数形式进行重载,我们已经在类和对象中讲过了,就不再赘述,大家可以翻看前面的内容。
③函数调用运算符 () 的重载
C++
引入了函数调用运算符。函数调用运算符的重载形式只能是成员函数形式。
重载了函数调用运算符的类创建的对象称为**函数对象**
//重载了函数调用运算符的类创建的对象称为函数对象
class FunctionObject
{
public:
FunctionObject(int count = 0) //构造函数
: _count(count)
{
}
void operator()(int x)
{
cout << " x = " << x << endl;
}
int operator()(int x, int y)
{
++_count;
return x + y;
}
private:
int _count; //函数对象的状态
};
int func(int x, int y)
{
return x + y;
}
//typedef 返回类型(*新类型)(参数表)
//typedef的功能是定义新的类型。第一句就是定义了一种Function的类型,并定义这种类型为指向某种函数的指针,这种函数以两个int为参数并返回int类型。后面就可以像使用int,char一样使用Function了。
typedef int (*Function)(int ,int); //函数指针
Function f = func;
f(a,b);
//函数指针与函数对象的区别:
//1、定义的方式不一样,使用方式一样
//2、函数对象可以携带状态信息,但是函数指针没有
void test()
{
//重载了函数调用运算符的类创建的对象称为函数对象
FunctionObject foo; //创建对象
int a = 3, b = 4;
foo(a); //与43行效果相同
//cout << foo.operator()(a,b) <<endl;
cout << foo(a, b) << endl; //函数对象 形式与函数类似
cout << "func(a,b)" << func(a,b) << endl;
}
④下标访问运算符 [] 的重载
下标访问运算符[]
通常用于访问数组元素,它是一个二元运算符,如arr[idx]
可以理解成arr
是左操作数, idx
是右操作数。对下标访问运算符进行重载时,只能以成员函数形式进行,如果从函数的观点来看,语句 arr[idx];可以解释为arr.operator[](idx);
,因此下标访问运算符的重载形式如下:
class CharArray
{
public:
CharArray(int size) //构造函数
: _size(size)
, _array(new char[_size]())
{
cout << "CharArray(int)" << endl;
}
// vector/string (vec[idx] str[idx]) 都是重载了下标访问运算符
//重载下标访问运算符的优点:
//1、增加了安全性
//2、返回值是个引用,可以作为左值
//为何返回值使用引用
//1、对于连续赋值这种情况(输入输出流 cout << "hello" << endl;)
//2、防止返回对象的时候调用拷贝构造函数
char & operator[](int idx)
{
if(idx >=0 && idx < _size)
{
return _array[idx];
}
else
{
static char nullchar = '\0';
cerr << "下标越界!\n";
return nullchar;
}
}
const char & operator[](int idx) const
{
return _array[idx];
}
size_t size() const
{
return _size;
}
~CharArray() //析构函数
{
cout << "~CharArray()" << endl;
if(_data)
{
delete [] _array;
_data = nullptr;
}
}
private:
int _size;
//size_t _size size_t类型变量的默认大于0
char * _array;
};
void test()
{
const char *pstr = "hello,world";
CharArray ca(strlen(pstr) + 1);
for(size_t idx = 0; idx != ca.size(); ++idx)
{
ca[idx] = pstr[idx];
}
for(size_t idx = 0; idx != ca.size(); ++idx)
{
cout << ca[idx] <<endl;
}
}
⑤成员访问运算符 -> * 的重载
class Data //第一层
{
public:
Data(int data = 0)
: _data(data)
{
cout << "Data()" << endl;
}
int getData() const
{
return _data;
}
private:
int _data;
};
class MiddleLayer //第二层
{
public:
MiddleLayer(Data * pdata)
: _pdata(pdata)
{
cout << "MiddleLayer(Data *)" << endl;
}
//返回值是一个指针
//重载箭头运算符 ->
Data * operator->()
{
return _pdata;
}
//重载解引用运算符 *
Data & operator*()
{
return *_pdata;
}
~MiddleLayer()
{
if(_pdata)
{
delete _pdata;
_pdata = nullptr;
}
}
private:
Data * _pdata;
};
class ThirdLayer //第三层
{
public:
ThirdLayer(MiddleLayer * ml)
: _ml(ml)
{
cout << "ThirdLayer(MiddleLayer *)" << endl;
}
//返回一个重载了箭头运算符的对象
MiddleLayer & operator->()
{
return *_ml;
}
~ThirdLayer()
{
if(_ml)
{
delete _ml;
_ml = nullptr;
}
}
private:
MiddleLayer * _ml;
};
void test()
{
MiddleLayer ml(new Data());
cout << ml->getData() << endl;
//解析:
cout << (ml.operator->())->getData() << endl;
cout << (*ml).getData() << endl;
ThirdLayer tl(new MiddleLayer(new Data()));
cout << tl->getData() << endl;
//解析:
cout << ((tl.operator->()).operator->())->getData() << endl;
}
⑥输入输出流运算符 >> << 的重载
//非静态的成员函数的第一参数是隐藏的this指针
//输入输出流要求第一个参数是流对象,所以不能把输入输出流运算符以成员函数进行重载
class Point {
public:
//...
friend std::ostream & operator<<(std::ostream & os, const Point & rhs);
friend std::istream & operator>>(std::istream & is, Point & rhs);
private:
int _x;
int _y;
};
void readInt(std::istream &is ,double &number) //输入流的状态判断
{
while(is >> number ,!is.eof())
{
if(is.bad())
{
ceer << "istream is bad" << endl;
return;
}
else if(is.fail())
{
is.clear();
is.ignore(std::numeric_limits<std::streamsize>::max(),"\n");
cout << "please input a valid int number : " << endl;
continue;
}
cout << "number = " << number << endl;
cout << number << endl;
break;
}
}
std::ostream & operator<<(std::ostream & os, const Point & rhs)
{
os << "(" << rhs._x
<< "," << rhs._y
<< ")";
return os;
}
istd::stream & operator>>(std::istream & is, Point & rhs)
{
//is >> rhs._x >> rhs._y; //不太健壮,没有判断输入的流的状态
readInt(is,rhs._x);
readInt(is,rhs._y);
return is;
}
void test()
{
Point c1(1,2);
cout << "c1 = " << c1 << endl;
//解析:
operator<<(operator<<(cout,"c1 = "),c1).operator<<(endl); //链式编程
}
6.运算符重载选取函数的建议
- 所有的一元运算符,建议以成员函数重载
- 运算符 = () [] -> ->* ,必须以成员函数重载
- 运算符 += -= /= *= %= ^= &= != >>= <<= 建议以成员函数形式重载
- 其它二元运算符,建议以非成员函数重载
三、类对象的类型转换
前面介绍过对普通变量的类型转换,比如说 int
型转换为long
型, double
型转换为int
型,接下来我们要讨论下类对象与其他类型的转换。转换的方向有:
- 由其他类型向自定义类型转换
- 由自定义类型向其他类型转换
1.由其他类型向自定义类型转换
由其他类型向定义类型转换是由构造函数来实现的,只有当类中定义了合适的构造函数时,转换才能通过。这种转换,一般称为隐式转换。
①explicit 禁止隐式转换
禁止隐式转换:需要在相应构造函数前面加上explicit
关键字。
class Point
{
public:
Point(int x = 0, int y = 0)
: _x(x)
, _y(y)
{
}
//...
friend std::ostream & operator<<(std::ostream & os, const Point & rhs);
private:
int _x;
int _y;
};
std::ostream & operator<<(std::ostream & os, const Point & rhs)
{
os << "(" << rhs._x << "," << rhs._y << ")";
return os;
}
void test()
{
Point pt = 1;//隐式转换
cout << "pt = " << pt << endl;
}
2.由自定义类型向其他类型转换
由自定义类型向其他类型的转换是由类型转换函数完成的,这是一个特殊的成员函数。形式:
operator 目标类型()
{
//...
}
类型转换函数具有以下的特征:
- 必须是成员函数;
- 参数列表中没有参数;
- 没有返回值,但在函数体内必须以 return 语句返回一个目标类型的变量(对象)。
例子:
class Fraction
{
public:
Fraction(double numerator, double denominator)
: _numerator(numerator)
, _denominator(denominator)
{
}
//很不符合常规思维,建议少用!
operator double()
{
return _numerator / _denominator;
}
operator Point()
{
return Point(_numerator, _denominator);
}
private:
double _numerator;
double _denominator;
};
void test()
{
Fraction f(2, 4);
cout << "f = " << f << endl;
double x = f + 1.11;
cout << "x = " << x << endl;
double y = f;
}
。
例子:
class Fraction
{
public:
Fraction(double numerator, double denominator)
: _numerator(numerator)
, _denominator(denominator)
{
}
//很不符合常规思维,建议少用!
operator double()
{
return _numerator / _denominator;
}
operator Point()
{
return Point(_numerator, _denominator);
}
private:
double _numerator;
double _denominator;
};
void test()
{
Fraction f(2, 4);
cout << "f = " << f << endl;
double x = f + 1.11;
cout << "x = " << x << endl;
double y = f;
}