运算符重载

1.友元

如果将其他类、函数设置为类的友元,那么友元、函数就可以在前一个类的类定义之外访问其私有成员了,用friend关键字声明友元

友元的三种形式:普通函数,成员函数,友元类

2.友元—普通函数形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Point{
Point(int x=0,int y=0)
:_ix(x)
,_iy(y)
{}
friend
float distance(const Point & lhs,const Point &rhs);//在类中声明该函数是友元,那么该函数就可以访问类的私有成员了
private:
int _ix;
int _iy
};

float distance(const Point & lhs,const Point &rhs){
return sqrt(pow(lhs._ix - rhs._iy,2)+pow(rhs._ix - rhs._iy),2);
}//这里无法直接访问私有成员


3.友元—成员函数形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Point;

class Line{
public:
float distance(const Point & lhs,const Point &rhs);//这里无法直接访问别的类的私有成员
};

class Point{
Point(int x=0,int y=0)
:_ix(x)
,_iy(y)
{}
friend
float Line::distance(const Point & lhs,const Point &rhs);
private:
int _ix;
int _iy
};

float Line::distance(const Point & lhs,const Point &rhs){
return sqrt(pow(lhs._ix - rhs._iy,2)+pow(rhs._ix - rhs._iy),2);
}//写在line类里面会发现编译器不认识lhs._ix这些point里面的成员函数


4.友元类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Line{
public:
float distance(const Point & lhs,const Point &rhs){
return sqrt(pow(lhs._ix - rhs._iy,2)+pow(rhs._ix - rhs._iy),2);
}//这里无法直接访问别的类的私有成员
};

class Point{
Point(int x=0,int y=0)
:_ix(x)
,_iy(y)
{}
friend
class Line;//声明友元类
private:
int _ix;
int _iy
};

友元的声明要注意和函数的形式一一对应上

友元的特点

  1. 友元不受类中访问权限的限制——可访问私有成员
  2. 友元破坏了类的封装性
  3. 不能滥用友元 ,友元的使用受到限制
  4. 友元是单向的——A类是B类的友元类,则A类成员函数中可以访问B类私有成员;但并不代表B类是A类的友元类,如果A类中没有声明B类为友元类,此时B类的成员函数中并不能访问A类私有成员
  5. 友元不具备传递性——A是B的友元类,B是C的友元类,无法推断出A是C的友元类
  6. 友元不能被继承——因为友元破坏了类的封装性,为了降低影响,设计层面上友元不能被继承

运算符重载

运算符重载是希望自定义类类型在操作时与内置类型保持一致

不能重载的运算符包括:

. 成员访问运算符 .*成员指针访问运算符  ?:三目运算符   ::作用域限定符  sizeof长度运算符

运算符重载的规则与形式

  • 运算符重载有以下规则
  1. 运算符重载时 ,其操作数类型必须要有自定义类类型或枚举类型 ——不能全都是内置类型

  2. 其优先级和结合性还是固定不变的 a == b + c

  3. 操作符的操作数个数是保持不变的

  4. 运算符重载时 ,不能设置默认参数 ——如果设置了默认值,其实也就是改变了操作数的个数

  5. 逻辑与 && 逻辑或 || 就不再具备短路求值特性 ,进入函数体之前必须完成所有函数参数的计算, 不推荐重载

  6. 不能臆造一个并不存在的运算符 @ $ 、

  • 运算符重载的形式

运算符重载的形式有三种:

  1. 采用友元函数的重载形式
  2. 采用普通函数的重载形式
  3. 采用成员函数的重载形式

以加法运算符为例,认识这三种形式。

加法运算符重载

1.友元函数的重载形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//复数
class Complex{
public:
Complex(int real, int image)
:_real(real)
,_image(image)
{}
void print() const{
couot << _real << "+" << _image << "i" << endl;
}
//声明友元
frind Complex operator+(const Complex &lhs, const Complex &rhs);
private:
int _real;
int _image;
};

//对+进行重载,采用友元函数的重载形式
//一般定义一个普通函数operator+运算符,写明计算逻辑
Complex operator+(const Complex &lhs, const Complex &rhs){
return Complex(lhs._real + rhs._real,lhs_image + rhs._image);
}
void test(){
Complex cx1(1,2);
Complex cx2(3,4);
//Complex cx = operator+(cx1,cx2);
//本质
Complex cx3 = cx1 + cx2;
cx3.print();
}

2.采用成员函数的重载形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//复数
class Complex{
public:
Complex(int real, int image)
:_real(real)
,_image(image)
{}
void print() const{
couot << _real << "+" << _image << "i" << endl;
}
//声明友元
Complex operator+(const Complex &rhs){
return Complex(_real + rhs._real,_image + rhs._image);
}
private:
int _real;
int _image;
};
void test0(){
Complex cx1(1,2);
Complex cx2(3,4);
//Complex cx3 = cx1.operator+(cx2);
//本质
Complex cx3 = cx1 + cx2;
cx3.print();
}

重载形式的选择

  • 不会修改操作数的值的运算符,倾向于采用友元函数的方式重载
  • 会修改操作数的值的运算符,倾向于采用成员函数的方式重载
  • 赋值=、下标[ ]、调用()、成员访问->、成员指针访问-> 运算符必须是成员函数形式重载*
  • 与给定类型密切相关的运算符,如递增、递减和解引用运算符,通常应该是成员函数形式重载
  • 具有对称性的运算符可能转换任意一端的运算对象,例如相等性、位运算符等,通常应该是友元形式重载

对++运算符重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//复数
class Complex{
public:
Complex(int real, int image)
:_real(real)
,_image(image)
{}
void print() const{
couot << _real << "+" << _image << "i" << endl;
}
//前置运算符重载,前置运算符重载返回的是本身
Complex& operator++(){
++_real;
++_image;
return *this
}
//后置运算符重载,后置运算符重载返回的是一个副本
Complex operator++(int){//为了区分前置++后后置++,加一个int用以区分
Complex temp(*this);
++_real;
++_image;
return temp;

}
private:
int _real;
int _image;
};