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;
}
posted @ 2019-12-03 22:52  TheDa  阅读(452)  评论(0编辑  收藏  举报