C++运算符重载
一 运算符重载的本质
运算符重载:本质上是函数重载
C++中所有预定义的运算符都是通过运算符函数来 实现的。
例如: i +j 编译器自动解析为 operator+(i, j)
标准C++语言中已经为各种基本数据类型重载了运 算符函数op () erator+()。
这些重载形式如:
operator+(int, int)
operator+(float, float)
operator+(double, double)
根据函数重载原则,系统会用operator+(int, int)与 表达式“i+j”匹配。
二 运算符重载的规则
“=”运算符重载不能被派生类继承
C++中可重载的运算符
不可重载运算符. (成员引用运算符) *(成员指针运算符):: ? : sizeof
重载的运算符要保持原运算符的意义。例如,单目运算 符不能重载为双目运算符;
只能对已有的运算符重载,不能增加新的运算符;
重载的运算符不会改变优先级别和结合性;
以下运算符只允许用成员函数重载:= ( ) [ ] new delete
三 一元运算符重载的方法
运算符重载时,运算符函数只能定义为两种方式: 类的成员函数 友元函数
这两种方式非常相似,关键区别在于 成 员函数具有this 指针, 友元函数没有 this 指针
一元运算符,不论是前缀还是后缀,都需要一个操作数:
aa @ 及 @ aa
其中@:重载的运算符 aa:重载运算符的类的对象
可以解释为:aa . operator @ ( )
所需操作数在参数表由对象隐式提供
这时,运算 符函数用类的成员函数表示
或者可以解释为:operator @ (aa)
所需操作数在参数表由对象显式提供
这时,运算 符函数用类的友元函数表示
四 二元运算符重载的方法
同样分为成员函数和友元函数两种实现方案
aa @ bb
友元函数operator(aa,bb)
成员函数aa.operator(bb)
五 成员函数重载运算符
示例 三维坐标重载
// 成员函数重载运算符,范例程序 # include < iostream > using namespace std; class three_d { int x, y, z; // 3 d di t // 3_d coordinates public: three_d operator+(three_d t); // 重载“+” three_d operator=(three_d t);// 重载“=” three_d operator++(); // 重载“++” void show(); void assign(int mx, int my, int mz); }; three_d three_d :: operator+(three_d t) // 重载“+” { three_d temp; temp.x = x + t.x; temp.y = y + t.y; temp.z = z + t.z; return temp; } three_d three_d :: operator=(three_d t)// 重载“=” { x = t.x; y = t.y; z = t.z; return *this; } three_d three_d :: operator++() // 重载一元运算符“++” { x++; y++; z++; return *this; } void three_d::show() // 显示 xyz , , 坐标 { cout << x << "," << y << "," << z << "\n"; } void three_d::assign(int mx, int my, int mz) // 指定坐标 { x = mx; y = my; z = mz; } int main() { three_d a, b, c; a.assign(1, 2, 3); b.assign(10, 10, 10); a.show(); b.show(); a . operator+(b); c = a + b; // 将 a 和 b 加在一起 c.show(); c = a + b + c; // 将 a 、b、c 加在 起一 c.show(); c = b = a; // 多重赋值 c.show(); b.show(); ++c; // 对 c 递加 c.show(); cin.ignore(); return 0; } /* 1,2,3 10,10,10 11,12,13 22,24,26 1,2,3 1,2,3 2,3,4 */
复数的重载运算 即二元运算示例
#include<iostream> using namespace std; class Complex { private: double real, image; public: Complex(double x = 0.0, double y = 0.0) { real = x; image = y; } Complex operator-(const Complex &c); // 减法重载,双元 bool operator==(const Complex &c); Complex operator-(); //取反运算符重载,单元 Complex &operator+=(const Complex &c); void Print(); }; void Complex::Print() { cout << real << " + " << image << "i" << endl; } Complex Complex::operator-(const Complex &c) // 减法重载 { Complex temp(real - c.real, image - c.image);//为什么这里可以直接访问c的私有数据成员?因为C++的封装针对的是类而不是对象。
//感觉怪怪的,本来a-b,a.operator-(b),b自己就有访问自己的私有成员的权限啊 return temp; } bool Complex:: operator==(const Complex &c) { return (real == c.real && image == c.image); } Complex Complex:: operator-() //取反运算符重载,单元 { return Complex(-real, -image); } Complex & Complex::operator+= (const Complex &c) { real += c.real; image += c.image; return *this; } int main() { Complex c1(2, 7), c2(4, 2), c3; c3 = c1 - c2; c3.Print(); if (c3 == c1) cout << "c3 equals to c1" << endl; else cout << " c3 doesn t’ equal to c1 equal to c1 endl; " << endl; c3 = -c2; c3.Print(); c3 += c2; c3.Print(); cin.ignore(); return 0; } /* -2 + 5i c3 doesn t’ equal to c1 equal to c1 endl; -4 + -2i 0 + 0i */
只是这个输出有点龙鸣
注意引用作为返回值的用法,详细见类和结构的constructing那些构造函数和析构函数的辨析
即不会产生返回值副本。
六 友元函数重载运算符
首先举个例子,居然还有这么骚的操作
这里系统通过构造函数将27强制类型转化为复数类型的变量
#include<iostream> using namespace std; class Complex { private: double real, image; public: Complex(int a) { real = a; image = 0; } Complex(double x = 0.0, double y = 0.0) { real = x; image = y; } Complex operator+(const Complex &c); Complex operator-(const Complex &c); // 减法重载,双元 bool operator==(const Complex &c); Complex operator-(); //取反运算符重载,单元 Complex &operator+=(const Complex &c); void Print(); }; void Complex::Print() { cout << real << " + " << image << "i" << endl; } Complex Complex::operator+(const Complex &c) { Complex temp(real + c.real, image + c.image); return temp; } Complex Complex::operator-(const Complex &c) // 减法重载 { Complex temp(real - c.real, image - c.image);//为什么这里可以直接访问c的私有数据成员?因为C++的封装针对的是类而不是对象。 //感觉怪怪的,本来a-b,a.operator-(b),b自己就有访问自己的私有成员的权限啊 return temp; } bool Complex:: operator==(const Complex &c) { return (real == c.real && image == c.image); } Complex Complex:: operator-() //取反运算符重载,单元 { return Complex(-real, -image); } Complex & Complex::operator+= (const Complex &c) { real += c.real; image += c.image; return *this; } int main() { Complex c1(2, 7), c2(4, 2), c3; c3 = c1 + 27; c3.Print(); return 0; }
/*
29 + 7i
*/
但是与此同时,27+c1这样的操作就不行了
因为27不是复数类型的变量,所以没有 27.operator+(c1)这样的说法
所以出现了友元函数
在第一个参数需要隐式转换的情形下,使用友元 函数重载算符是正确的选择。
示例代码,重载复数
#include<iostream> using namespace std; class complex { public: complex(double r = 0 , double i = 0); void print() const; friend complex operator+(const complex & c1 , const complex & c2); friend complex operator-(const complex & c1 , const complex & c2); friend complex operator-(const complex & c); private: double real , imag; }; complex::complex(double r, double i) { real = r; imag = i; } complex operator + (const complex & c1, const complex & c2) { double r = c1.real + c2.real; double i = c1.imag + c2.imag; return complex(r, i); } complex operator - (const complex & c1, const complex & c2) { double r = c1.real - c2.real; double i = c1.imag - c2.imag; return complex(r, i); } complex operator - (const complex&c) { return complex(-c.real, -c.imag); } void complex::print() const { cout << '(' << real << "," << imag << ')' << endl; } int main() { complex c1(2.5, 3.7), c2(4.2, 6.5); complex c; c = c1 - c2; // c = operator - ( c1, c2 ) c.print(); c = c1 + c2; // c = operator + ( c1, c2 ) c.print(); c = -c1; // c = operator operator - (c1) c.print(); c = 2 + c1; c.print(); cin.ignore(); return 0; } /* (-1.7,-2.8) (6.7,10.2) (-2.5,-3.7) (4.5,3.7) */
其实如果理解为函数返回值的话
-减号取反的操作就很好理解了
使用友元函数重载单目运算符
// 例题7.3:使用友元函数重载单目运算符 #include<iostream> using namespace std; class Point { public: Point(int i = 0, int j = 0) { x = i; y = j; } void Show() { cout << "(" << x << ", " << y << ")" << endl; } friend Point operator-(const Point &p1); //数值取反 friend bool operator!(const Point &p1); //原点判断 private: int x, y; }; Point operator-(const Point &p1) //数值取反 { Point tmp(-p1.x, -p1.y); return tmp; } bool operator!(const Point &p1) //原点判断 { return (p1.x == 0 && p1.y == 0); } int main() { Point p1(20, 40), p2; p1.Show(); p2.Show(); cout << !p1 << endl; cout << !p2 << endl; p2 = -p1; p2.Show(); cout << !p2<< endl; cin.ignore(); return 0; } /* (20, 40) (0, 0) 0 1 (-20, -40) 0 */
使用友元函数的可能的问题
three_d operator + + ( three_d opl ) { opl . x + + ; opl . y + + ; opl . z + + ; return opl ; }
++的时候,友元函数显然是传值,不会改变自身
但是如果这样的话
three_d operator + + ( three_d * opl ) { opl -> x + + ; opl -> y + + ; opl -> z + + ; return *opl ; }
three d ob ( 1 , 2 , 3 ) ;
& ob + + ;这句代码有二义性
对 ob 的地址进行自增?还是ob自增?
three_d operator + + ( three_d & opl ) { opl . x + + ; opl . y + + ; opl . z + + ; return opl ; }
three d ob ( 1 2 3 ) ; three_d ob ( 1 , 2 , 3 ) ; ob + + ; // ok
这样用引用才是正确的
举例子
# include < iostream > using namespace std; class three_d { int x, y, z; // 3 d di t // 3_d coordinates public: three_d(int x, int y, int z) { this->x = x; this->y = y; this->z = z; } friend three_d operator++(three_d & t); // 重载“+” void show(); }; three_d operator++(three_d &t) // 重载一元运算符“++” { t.x++; t.y++; t.z++; return t; } void three_d::show() // 显示 xyz , , 坐标 { cout << x << "," << y << "," << z << "\n"; } int main() { three_d ob1(1, 2, 3); ++ob1; //++ob1; ob1.show(); } /* 234 */
如果不加引用的话就是123
顺便,这里只能识别++ob1,而不能识别ob1++
具体看下面
一些总结
如果运算符的操作需要修改类对象状态时,则它应 该是友元函数。需要左值操作数的运算符(如 =,*=, ++)应该用成员函数重载。 定义成友元函数时要用引用参数。
如果运算符的操作数 如果运算符的操作数(尤其是第一个操作数 尤其是第一个操作数)希望有 隐式转换,则重载算符时必须用友元函数
不能用友元函数重载的运算符是 = () [] —>
七 重载++和--
就如同上面所说的那样
重载++和--有前后两种方法
前缀方式++i,--i
成员aa . operator + + ( ) ;
友元operator + + ( X & aa )
后缀方式i++,i--
成员aa . operator + + ( int ) ;
友元operator + + ( X & aa , int )
其实就是二元的设置方法,将第二个参数默认为0
如上面的改成这样就可以了
# include < iostream >
using namespace std;
class three_d
{
int x, y, z; // 3 d di t // 3_d coordinates
public:
three_d(int x, int y, int z)
{
this->x = x;
this->y = y;
this->z = z;
}
friend three_d operator++(three_d & t); // 重载“+”
friend three_d operator++(three_d & t,int); // 重载“+”
void show();
};
three_d operator++(three_d &t) // 重载一元运算符“++”
{
t.x++;
t.y++;
t.z++;
return t;
}
three_d operator++(three_d &t,int) // 重载一元运算符“++”
{
t.x++;
t.y++;
t.z++;
return t;
}
void three_d::show() // 显示 xyz , , 坐标
{
cout << x << "," << y << "," << z << "\n";
}
int main()
{
three_d ob1(1, 2, 3);
++ob1;
ob1.show();
ob1++;
ob1.show();
}
/*
2,3,4
3,4,5
*/
八 重载赋值运算符=
如果不重载赋值运算符的话,=就直接调用拷贝构造函数了吧,然后还有什么析构函数神马的
X & X :: operator = ( const X & from) { // 复制 X 的成员 }
1.返回类类型的引用是为了与 C++ 原有赋值号的语义 原有赋值号的语义 相匹配
还有一个原因,那就是不引用的话,返回的时候会创建临时对象,调用拷贝构造函数,
但是万一由于没有定义拷贝构造函数 ,就会调用默认的拷贝构造函数。
我们知道调用默认的拷贝构造函数时当在类中有指针时就会出错(浅拷贝)。
详见浅拷贝。
2.用 const 修饰参数,保证函数体内不修改实际参数
其他注意事项
1.重载的运算符函数 operator = 不能被继承
2.重载的运算符函数 operator = 必须是成员函数
3.如果用户没有为类重载赋值运算符函数,编辑程序将 生成一个 缺省的赋值运算符。
4.拷贝函数用于创建一个新对象,赋值运算符是改变一 个已存在的对象的值
为什么重载的运算符函数 operator = 必须是成员函数? 默认赋值运算符有可能会转换为调用有参数构造函数。
指针悬挂问题
感觉有点像前面说的的浅拷贝问题。
就是c++默认的赋值函数,在处理类成员指针变量的时候的一些不足之处。
如下代码
# include < iostream > using namespace std; class String { char *p; int size; public: String(int sz) { p = new char[size = sz]; } ~String() { delete []p; } }; void f()//其实作用只是生成一个代码块 { String s1(10); String s2(10); s1 = s2; } int main() { f(); return 0; }
s1指向s1的内存块
s2指向s2的内存块
然后s1的指针值变为s2
代码块结束的时候,s2的指针调用两次析构函数,而s1的原本指向的内存就变得无法使用了。
正确做法是重载赋值运算符
代码如下
# include < iostream > using namespace std; class String { char *p; int size; public: String(int sz) { p = new char[size = sz]; } ~String() { delete []p; } void operator=(String &t); }; void String::operator=(String &t) { delete []p; p = new char[size = t.size]; strcpy(p, t.p); } void f()//其实作用只是生成一个代码块 { String s1(10); String s2(10); s1 = s2; } int main() { f(); return 0; }
不适用void的话,就需要注意使用引用类型作为返回值
防止浅拷贝的错误
String &operator=(String &t);
String &String::operator=(String &t)
{
delete []p;
p = new char[size = t.size];
strcpy(p, t.p);
return *this;
}
九 重载( )和[ ]
只能用成员函数重载,不能用友元函数重载
# include < iostream > using namespace std; class F { public : double ff ( double x ,double y ,double z) ; } ; double F :: ff ( double x ,double y ,double z) { return (x * y + 5 )*z; } int main ( ) { F fobj ; cout << fobj . ff ( 5.2 , 2.5,3.0 ) << endl ; return 0; }
18
照葫芦画瓢吧
设 x 是类 X 的 个对象 一 ,则表达式 x [ y ] 可被解释为 x . operator [ ] ( y ) 重载时,只能显式地声明一个参数
这大概就是STL的操作吧
# include < iostream > using namespace std; class vector { public: vector(int sz); ~vector(); int &operator[](int i); private: int *v; }; vector::vector(int sz) { v = new int[sz]; } vector::~vector() { delete[]v; } int &vector::operator[](int i) { return v[i]; } int main() { vector a(5); a[2] = 12; cout << a[2] << endl; return 0; }