day9 cpp:运算符重载
1.友元
什么是友元?
一般来说,类的私有成员只能在类的内部访问,类之外不能访问,但如果将其他类/函数设置为此类的友元,那么友元类/函数就可以在前一个类对的类定义之外访问其私有成员了,用friend关键字声明友元
友元的三种形式:普通函数,成员函数,友元类
普通函数形式
class Point {
public:Point(int x,int y=0):_x(x),_y(y){}void print() const {cout << "(" << this->_x << endl;cout << "(" << this->_y << endl;}//友元的第一种形式//普通 函数声明 为一个类的友元函数//那么在这个函数中可以访问类的私有成员friendfloat distance(const Point& a, const Point& b);
private:int _x;int _y;
};
//建立一个观念
//如果函数的参数为对象
//形参首先想到const引用的方式
float distance(const Point& a, const Point& b) {return sqrt(pow(a._x - b._x, 2) + pow(a._y - b._y, 2));
}
成员函数形式 (不常用)
class Point {
public:Point(int x,int y=0):_x(x),_y(y){}void print() const {cout << "(" << this->_x << endl;cout << "(" << this->_y << endl;}friendfloat distance(const Point& a, const Point& b);
private:int _x;int _y;
};
class line {
public:float distance(const Point& a, const Point& b) {//报错_x,_y是私有成员return sqrt(pow(a._x - b._x, 2) + pow(a._y - b._y, 2));}
};
之前distance是普通函数,现在distance是类line的成员函数,在声明要加上作用域限定
class Point {
public:Point(int x,int y=0):_x(x),_y(y){}void print() const {cout << "(" << this->_x << endl;cout << "(" << this->_y << endl;}friendfloat line::distance(const Point& a, const Point& b);
private:int _x;int _y;
};
class line {
public:float distance(const Point& a, const Point& b) {return sqrt(pow(a._x - b._x, 2) + pow(a._y - b._y, 2));}
};
但不仅_x,_y处私有成员变量报错,line::distance声明也报找不到line错误 ,将line类放在Point类前
class line {
public:float distance(const Point& a, const Point& b) {return sqrt(pow(a._x - b._x, 2) + pow(a._y - b._y, 2));}
};
class Point {
public:Point(int x,int y=0):_x(x),_y(y){}void print() const {cout << "(" << this->_x << endl;cout << "(" << this->_y << endl;}friendfloat line::distance(const Point& a, const Point& b);
private:int _x;int _y;
};
但是distance的实现中又找不到Point对象 ,在类line前进行Point类的前向声明
class Point;
class line {
public:float distance(const Point& a, const Point& b) {return sqrt(pow(a._x - b._x, 2) + pow(a._y - b._y, 2));}
};
class Point {
public:Point(int x,int y=0):_x(x),_y(y){}void print() const {cout << "(" << this->_x << endl;cout << "(" << this->_y << endl;}friendfloat line::distance(const Point& a, const Point& b);
private:int _x;int _y;
};
但line类中的distance实现可以创建Point对象,可具体的Point类的成员变量访问不到,
Point类的前向声明:将distance的函数体写在line类中,编译器只知道有一个Point类,但不知道Point类具体有什么成员
t类的前向声明的用处:进行了前向声明的类,可以引用或指针的形式作为函数的参数,只要不涉及对该类对象具体成员的访问,编译器可以通过
所以将line类中distance实现变为声明,将其实现放到后面,并加上作用域限定
class Point;
class line {
public:float distance(const Point& a, const Point& b);
};
class Point {
public:Point(int x,int y=0):_x(x),_y(y){}void print() const {cout << "(" << this->_x << endl;cout << "(" << this->_y << endl;}//友元的第二种形式//将另一个类成员函数声明为友元函数friendfloat line::distance(const Point& a, const Point& b);
private:int _x;int _y;
};
//这种方式割裂了代码
//使用较少,但是仍要掌握
float line::distance(const Point& a, const Point& b){return sqrt(pow(a._x - b._x, 2) + pow(a._y - b._y, 2));
}
注:缺省参数必须放在声明上(如果分开),因为调用一定在声明之后,可能在实现之前
友元类形式
如上,假设类line中不只一个distance成员函数,还有其他成员函数,他们都需要访问Point私有成员,如上设置成一个一个友元成员函数很麻烦,可以将line类设置为Point的友元类,相比于成员函数形式更常用
class Point {
public:Point(int x,int y):_x(x),_y(y){}//友元的第三种形式友元类//将line类声明为Point友元类//那么line中所有成员函数都可以访问Point的私有成员friendclass line;
private:int _x;int _y;
};
class line {
public:float distance(const Point& a, const Point& b) {return sqrt(pow(a._x - b._x, 2) + pow(a._y - b._y, 2));}void setX(Point & a,int x) {a._x = x;}
};
友元的特点
1.友元不受类中访问权限的限制:
可访问私有成员
2.友元破坏了类的封装性
外面的小子都可以访问私有成员了
3.不能滥用友元,友元的使用受到限制
4.友元是单向的
A类是B类的友元,则A类成员函数中可以访问B类私有成员,但B类不是A类的友元
5.友元不具备传递性
A是B的友元类,B是C的友元类,但A不是B的友元类
6.友元不能被继 承
因为友元破坏了类的封装性,为了降低影响,设计层面上友元不能被继承
2.运算符重载
运算符重载的介绍
为了c++中自定义类型与内置类型保持一致
运算符重载的规则与形式(重点)
运算符重载有以下规则
运算符重载形式
1.采用友元函数的重载形式
2.采用普通函数的重载形式
3.采用成员函数的重载形式
+运算符重载
实现一个复数类,复数分为实部和虚部,重载+运算符,使其能够处理两个复数之间的加法运算
友元函数形式
class Complex {
public:Complex(int real, int image) :_real(real),_image(image){}void print() {cout << _real << "+" << _image << "i" << endl;}friendComplex operator+(const Complex& lhs, const Complex& rhs);
private:int _real;int _image;
};
//运算符重载的第一种方式--友元函数形式
//定义一个普通函数operator+
//将这个函数声明为Complex类的友元
Complex operator+(const Complex& lhs, const Complex& rhs) {return Complex(lhs._real + rhs._real, lhs._image + rhs.image);
}
void test() {int a = 1, b = 2;//&(a+b) errorint c = a + b;Complex cx1(3, 7);Complex cx2(4, 5);Complex cx3 = cx1 + cx2;//本质Complex cx3=operator+(cx1,cx2); Complex cx3.operator=(operator+(cx1,cx2))cx3.print();//7+12i
}
Complex cx3=operator+(cx1,cx2)解读:
1.operator+(cx1,cx2)是一个匿名的临时对象
2.cx3=临时对象 赋值
注意:
class T{....};
T t;
T a;
t.operator=(a);//xx()=常量;则xx()一定是左值,int a=xx()中xx()随意
普通函数形式(几乎不用)
类外访问私有成员,一是用友元函数,二是提供公有的成员函数访问私有成员
class Complex {
public:Complex(int real, int image):_real(real), _image(image){}void print() {cout << _real << "+" << _image << "i" << endl;}int getReal() const { return _real; }int getImage() const { return _image; }
private:int _real;int _image;
};
//运算符重载的第二种方式---普通函数
//在Complex类中提供一系列公有的get函数
//在这个函数中调用这些get函数能间接的获取到Complex私有成员
//很少使用这种方式,因为几乎完全破坏了对私有成员的隐藏性
Complex operator+(const Complex& lhs, const Complex& rhs) {return Complex(lhs.getReal() + rhs.getReal(), lhs.getImage() + rhs.getImage());
}
成员函数形式
class Complex {
public:Complex(int real, int image):_real(real), _image(image){}void print() {cout << _real << "+" << _image << "i" << endl;}//成员函数方式进行重载//会有一个默认的this指针作为第一个参数Complex operator+(const Complex& rhs) {return Complex(_real + rhs._real,_image + rhs._image);}
private:int _real;int _image;
};
在C++中,
+
运算符重载函数通常不返回引用,主要基于以下设计原则和实际需求://cx1.operator+(cx2)
一、语义一致性
数学加法特性
+
运算本质是生成新值而非修改原操作数,返回新对象符合数学加法语义。若返回引用,会误导使用者认为原对象被修改。与内置类型行为对齐
内置类型(如int
)的+
运算返回临时结果,自定义类型应保持相同行为以符合用户预期。二、对象生命周期问题
临时对象必要性
+
运算结果通常是临时对象,若返回引用会绑定到即将销毁的局部对象(如函数栈内对象),导致悬垂引用。
// 错误示例:返回局部对象引用
const Vector& operator+(const Vector& v) {
Vector tmp(v.x + 1, v.y + 1);
return tmp; // tmp析构后引用失效
}无法返回成员对象引用
若返回*this
或其他成员引用,会破坏加法运算的交换律(如a+b
与b+a
行为不一致)三、性能与拷贝优化
返回值优化(RVO)
现代编译器可优化临时对象的拷贝,直接构造返回值到目标位置,避免额外开销。与赋值运算符对比
赋值运算符(=
)返回引用是为了支持链式赋值(如a=b=c
),而+
运算无此需求。
+=运算符重载
像+=这一类会修改操作数的值的运算符,倾向于采用成员函数的方式重载
实现定义运算符重载函数步骤:
0.确定用什么方式重载
1.确定函数返回值
2.再写上函数名(operator运算符)
3.再补充参数列表(友元的普通函数---运算需要多少操作数就准备多少参数;成员函数---考虑第一个操作数是this指针所指向对象)
4.最后完成函数体的内容
class Complex {
public:Complex(int real, int image):_real(real), _image(image){}void print() {cout << _real << "+" << _image << "i" << endl;}//成员函数方式进行重载//会有一个默认的this指针作为第一个参数Complex operator+(const Complex& rhs) {return Complex(_real + rhs._real,_image + rhs._image);}Complex& operator+=(const Complex& rhs) {_real += rhs._real;_image += rhs._image;return *this;}
private:int _real;int _image;
};
int main(void){Complex cx1(3, 7);Complex cx2(4, 5);cx1 += cx2;//本质cx1.operator+=(cx2);返回引用绑定的对象本身cx1.print();//7+12ireturn 0;
}
重载形式的选择(重要)
不会修改操作数值的运算符,倾向于采用友元函数的方式重载
会修改操作数值的运算符,倾向于采用成员函数方式重载
具有对称性的运算符可能转换任意一端的运算对象,例如a+b(b+a一样),相等性,位运算符等,通常是友元
与给定类型密切相关的运算符,如递增,递减和解引用运算符,通常是成员函数形式重载
赋值=,下标[],调用(),成员访问->,成员指针访问->*运算符必须是成员函数形式重载
3.作业
编写一个Base类使输出为1
class Base {
public:Base(int base):_base(base){}friendBase operator+(const Base& a, const Base& b);friendint operator==(const Base& a, int x);
private:int _base;
};
Base operator+(const Base& a, const Base& b) {return Base(b._base - a._base);
}
int operator==(const Base& a, int x) {return a._base == x;
}
int main(void) {int i = 2;int j = 7;Base x(i);Base y(j);cout << (x + y == j - i) << endl;return 0;
}