友元 - friend
- 友元不受访问权限的控制(public / protected / private)
- 友元函数可以重载,但是友元属性不会被继承
- 不能访问友元类内部的私有函数
- 友元是单向的,有缘关系不能传递(A -> B -> C) 推不出 (A -> C)
- 子类不会继承父类的友元属性
友元的形式
- 友元函数
- 友元类
class 类名
{
// ...
friend 函数原型;
friend class 类名;
// ....
};
函数的形式
- 成员函数
- 非成员函数(全局函数、自由函数)
友元函数类型
- 友元函数之普通函数
- 友元函数之成员函数
- 友元之友元类
模板代码
class Point
{
public:
Point(int ix = 0, int iy = 0)
: _ix(ix)
, _iy(iy)
{}
void point() = default;
void ~point() = default;
private:
int _ix;
int _iy;
}
1. 以普通函数的形式
class Point
{
// ...
friend float distance(const Point &lhs, const Point &rhs);
//...
}
float distance(const Pint &lhs, const Point &rhs)
{
return hypot(lhs._ix - rhs._ix, lhs._iy - rhs._iy);
}
2. 以成员函数的形式
// 类的前向声明
class Point;
class Line
{
public:
// 单纯声明
float distance(const Point &lhs, const Point &rhs);
/**
// Point 类还未定义
{
return hypot(lhs._ix - rhs._ix, lhs._iy - rhs._iy);
}
*/
}
class Point
{
// ...
freind Line::distance(const Point &lhs, const Point &rhs);
}
float Line::distance(const Point &lhs, const Point &rhs)
{
return hypot(lhs._ix - rhs._ix, lhs._iy - rhs._iy);
}
3. 以友元类的形式
class Line
{
// ...
}
class Point
{
// ...
friend class Line;
// ...
}
运算符重载
可以重载的运算符:
+ | - | * | / | % | ^ |
---|---|---|---|---|---|
& | | | ~ | ! | = | < |
> | += | -= | *= | /= | %= |
^= | &= | |= | >> | << | >>= |
<<= | == | != | >= | <= | && |
|| | ++ | – | ->* | -> | , |
[] | () | new | delete | new[] | delete[] |
不可重载的运算符:
.
.*
?:
::
sizeof
运算符重载的规则
- 为了防止用户对标准类型进行运算符重载,C++规定重载的运算符的操作对象必须至少有一个是自定义类型或枚举类型
- 重载运算符之后,其优先级和结合性还是固定不变的。
- 重载不会改变运算符的用法,原来有几个操作数、操作数在左边还是在右边,这些都不会改变。
- 重载运算符函数不能有默认参数,否则就改变了运算符操作数的个数。
- 重载逻辑运算符(&&, ||)后,不再具备短路求值特性。
- 不能臆造一个并不存在的运算符,如@、$等
运算符重载的形式
- 采用普通函数的重载形式
- 采用成员函数的重载形式
- 采用友元函数的重载形式
模板代码
class Complex
{
public:
Complex(double dreal = 0, double dimage = 0)
: _dreal(dreal)
, _dimage(dimage)
{}
private:
double _dreal;
double _dimage;
}
1. 以普通函数的形式
class Complex
{
// ...
public:
double getReal() const
{
return _dreal;
}
double getImag() const
{
return _dimage;
}
// ...
}
Complex operator+(const Complex &lhs, const Complex &rhs)
{
return Complex(lhs.getReal() + rhs.getReal()
, lhs.getImag() + rhs.getImag());
}
2. 以成员函数的形式
class Complex
{
// ...
public:
// 重载不会改变运算符的用法,原来有几个操作数、操作数在左边还是在右边,这些都不会改变
// 非静态成员函数,首位有个默认的隐含参数 *this
Complex operator+(const Complex &rhs)
{
return Complex(_dreal + rhs._dreal, _dimage + rhs._dimage);
}
// ...
}
3. 以友元函数的形式
tip:加减乘除等建议采用友元函数的重载形式:
1. 可以直接调用成员变量
2. 且函数相较采用成员函数的重载形式,更为直观(两个形参)
class Complex
{
// ...
friend Complex operator+(const Complex &lhs, const Complex &rhs);
// ...
}
Complex operator+(const Complex &lhs, const Complex &rhs)
{
return Complex(lhs._dreal + rhs._dreal, lhs._dimage + rhs._dimage);
}
特殊运算符的重载
- 所有的一元运算符,建议以成员函数重载
- 运算符
=
()
[]
->
->*
,
必须以成员函数重载 - 运算符
+=
-=
/=
*=
%=
^=
&=
!=
>>=
<<=
建议以成员函数形式重载 - 其它二元运算符,建议以非成员函数重载
01. 复合赋值运算符
主要形式有:+=, -+, *=, /=, %=, <<=, >>=, &=, ^=, |=
tip:由于对象本身发生了改变,推荐使用成员函数形式
实现代码
class Complex
{
// ...
public:
// 对于复合赋值运算符,由于对象本身发生了改变,推荐使用成员函数形式
Complex &operator+=(const Complex &rhs)
{
_dreal += rhs._dreal;
_dimag += rhs._dimag;
return *this;
}
// ...
}
02. 自增自减运算符
主要形式有:++, –
tips:
-
分为前置和后置,其中后置形式在参数列表中加入
int
类型的形参,但实际使用是不用传参 -
从语言实现角度考虑,前置形式的实现速度比后置形式的实现速度快(因为不用创建对象)
实现代码
class Complex
{
// ...
public:
// 当返回的为函数外的实体时,加上 & 引用,如:*this
// 当返回的为函数内的成员变量,或临时变量,不加引用
// 如:return Complex(); 等
// 前置形式
// 由于前置形式返回的为自增后的值,所以直接返回本体即可
Complex &operator++()
{
++_dreal;
++_dimage;
return *this;
}
// 后置形式
// 由于后置形式返回的为自增前的值,然后原实体值自增,所以需借用临时变量
Complex operator++(int) // 用 int 作为标记,并不传递参数
{
Complex tmp(*this);
++_dreal;
++_dimage;
return tmp;
}
// 由此可见,前置形式的执行效率高于后置形式
// ...
}
03. 赋值运算符
只能以成员函数形式进行重载,详情见 - 类和对象
实现代码
Point &Point::operator=(const Point &rhs)
{
if(this == &rhs)
return *this;
delete[] _contant;
_contant = nullptr;
_contant = new char[strlen(rhs._contant) + 1]();
strcpy(_contant, rhs._contant);
return *this;
}
04. 输入输出流运算符
tips:
- 为了能连续输入输出,所以返回类型为 ostream/istream
- 由于非静态成员函数必定包含隐藏 this 指针,所以不可以成员函数形式实现
- 由于传入 ostream 类型的实参,并返回该实参,所以用 & 来减少系统开销
实现代码
class Complex
{
// ...
friend ostream &operator<<(ostream &os, const Complex &rhs);
friend istream &operator>>(istream &is, Complex &rhs);
// ...
}
ostream &operator<<(ostream &os, const Complex &rhs)
{
os << // 输出形式
<< ;
return os;
}
istream &operator>>(istream &is, Complex &rhs)
{
is >> rhs._dreal >> rhs._dimage;
return is;
}
05. 函数调用运算符
由于临时对象/匿名函数在执行后会自动销毁,为了保留其值,重写函数调用符,将其以成员函数的形式重载,使其成为函数对象 - (Function Object)
- 泛型思考问题的方式。而当对象未被销毁时,其状态仍可保留下来。
实现代码
class FunctionObject
{
public:
FunctionObject(int count = 0)
: _count(count)
{}
void operator()(int x)
{
++_count;
cout << "x = " << x << endl;
}
// 与此题无关的 tip:
// 可用函数指针调用此方法
// 具体类型为:int (*pf)(int, int)
int operator()(int x, int y)
{
++_count;
return x + y;
}
int _count;
};
06. 下标访问运算符
实现下标访问类数组类型,如数组、字符串等
实现代码
class CharArry
{
public:
// size_t 为无符号整数
// 32 位系统内 = unsigned int,64 位系统内 = unsigned long
// 注意:size_t 不应与可能为负值的变量进行比较
// 负值与无符号数比较时,会转化为无符号数,预期效果可能有误
CharArray(size_t size = 10)
: _size(size)
, _array(new char[_size]())
{}
// 如果去掉 & 引用,那当作为右值时,无法赋值,因为返回的为左值,不可更改
char &operator[](size_t idx)
{
if(idx < _size)
return _array[idx];
else
{
// 之所以需要定义一个 static char 类型的变量并返回
// 主要原因是因为变量的生命周期问题
// 当返回值为引用类型时,不应当返回该函数的成员变量
static char nullchar = '\0';
return nullchar;
}
}
// 实现 const 对象的下标访问
// 虽然 const 的返回值不会被修改,但是 & 可以减少系统开销,所以依旧返回引用
const char &operator[](size_t idx) const
{
if(idx < _size)
return _array[idx];
else
{
static char nullchar = '\0';
return nullchar;
}
}
~CharArray()
{
delete[] _array;
_array = nullptr;
}
private:
size_t _size;
char *_array;
};
07. 成员访问运算符
成员访问运算符:->
、 *
实现代码
class Data
{
public:
int getData()
{
return _data;
}
private:
int _data;
};
class MiddleLayer
{
public:
MiddleLayer(Data *pdata)
: _pdata(pdata)
{}
Data *operator->()
{
return *_pdata;
}
// 重载解引用符
Data& operator*()
{
return *_pdata;
}
~MiddleLayer()
{
delete _data;
_data = nullptr;
}
private:
Data *_pdata;
};
class ThirdLayer
{
public:
ThirdLayer(MiddleLayer *ml)
: _ml(ml)
{}
// 返回一个重载了箭头运算的对象
MiddleLayer &operator->()
{
return *_ml;
}
~ThirdLayer()
{
delete _ml;
_ml = nullptr;
}
private:
MiddleLayeer *_ml;
};
int main()
{
MiddleLayer ml(new Data());
cout << "MiddleLayer:" << endl;
// 两者等价
cout << ml->getData() << endl;
cout << (ml.operator->())->getData() << endl;
// operator*()
cout << "\tMiddleLayer *:" << endl;
cout << (*ml).getData() << endl
<< endl;
ThirdLayer tl(new MiddleLayer(new Data()));
cout << "ThirdLayer:" << endl;
// 两者等价
cout << tl->getData() << endl;
cout << ((tl.operator->()).operator->())->getData() << endl; // 其实不用加括号,也是正确的
return 0;
}
类型转换
1. 由其他类型向自定义类型转换
tip:一般称为隐式转换
实现代码
class Point
{
public:
Point(int ix = 0, int iy = 0)
: _ix(ix)
, _iy(iy)
{}
// ...
friend ostream &operator<<(ostream &os, const Point &rhs);
private:
int _ix;
int _iy;
};
ostream &operator<<(ostream &os, const Point &rhs)
{
os << "(" << rhs._ix
<< ", " << rhs._iy
<< ")";
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 Poin()
{
return Point(_numerator, _denominator);
}
private:
double _numerator;
double_denominator;
};
void test()
{
Fraction f(2, 4);
cout << "f = " << f << endl;
double x = f + 1.1;
cout << "x = " << x << endl;
double y = f;
return 0;
}