C++核心编程 4 类和对象 - 运算符重载(5)

运算符重载 : 对已有的运算符重新进行定义, 赋予其另一种功能, 以适应不同的数据类型 

4.5.1 加号运算符重载

 作用: 实现两个自定义数据类型相加的运算

class Person

{

public:

  int m_A;

  int m_B;  

}

Person p1;

p1.m_A = 10;

p1.m_B = 10;

 

Person p2;

p2.m_A = 10;

p2.m_B = 10;

 

Person p3 = p1 + p2;

此时若想创建一个p3为p1+p2,编译器是不支持p1与p2相加的, 只能通过自己构建一个成员函数实现两个对象相加属性后返回新的对象

Person PersonAddPerson(Person &p)       //返回值为Person的成员函数PersonAddPerson传进来一个&p

{

  Person temp;

  temp.m_A = this ->  m_A + p.m_A;

  temp.m_B = this ->  m_B + p.m_B;

  return temp;

}

上述成员函数名为开发者自定义的, 可以随便起名字, 太复杂了, 于是编译器给起了个统一的名字 operator+, 通过成员函数重载+号

Person operator+(Person &p)       

{

  Person temp;

  temp.m_A = this ->  m_A + p.m_A;

  temp.m_B = this ->  m_B + p.m_B;

  return temp;

}

于是第一段代码里的 Person p3 = p1 + p2;    ====等价于== >>   Person p3 = p1.operator + (p2);   可以简化为   Person p3 = p1 + p2;

通过全局函数重载:
Person operator+(Person &p1, Person &p2)

{

  Person temp;

  temp.m_A = p1.m_A + p2.m_A;

  temp.m_B = p1.m_B + p2.m_B;

  return temp;

}

Person p3 = operator + (p1, p2);  

简化为:

Person p3 = p1 + p2;

//加号运算符重载
class Person
{
public:
    //1 成员函数重载"+"
    Person operator+(Person& p)
    {
        Person temp;
        temp.m_A = this->m_A + p.m_A;
        temp.m_B = this->m_B + p.m_B;
        return temp;
    }

    int m_A;
    int m_B;
};

////2 全局函数重载"+"
//Person operator+(Person& p1, Person& p2)
//{
//    Person temp;
//    temp.m_A = p1.m_A + p2.m_A;
//    temp.m_B = p1.m_B + p2.m_B;
//    return temp;
//}

//函数重载版本
Person operator+(Person& p1, int a)
{
    Person temp;
    temp.m_A = p1.m_A + a;
    temp.m_B = p1.m_B + a;
    return temp;
}
void test01()
{
    Person p1;
    p1.m_A = 10;
    p1.m_B = 10;

    Person p2;
    p2.m_A = 10;
    p2.m_B = 10;

    //1 成员函数重载本质调用
    //Person p3 = p1.operator+(p2);
    //2 全局函数重载本质调用
    //Person p3 = operator+(p1, p2);
    Person p3 = p1 + p2;
    //运算符重载也可以发生函数重载
    Person p4 = p1 + 100;   //Person + int
    cout << "p3.m_A = " << p3.m_A << endl;
    cout << "p3.m_B = " << p3.m_B << endl;
    cout << "p4.m_A = " << p4.m_A << endl;
    cout << "p4.m_B = " << p4.m_B << endl;

}

int main()
{

    test01();
    system("pause");
    return 0;
}

 总结1: 对于内置的数据类型的表达式的运算符是不可能改变的(Person是自定义的数据类型可以改变, 但是像1+1 不能改变成1-1, 这种内置的数据类型的表达式的运算符是不可能改变的)

总结2: 不要滥用运算符重载(比如两个数相加的函数,但是用代码实现了两个数相减,别人看不懂)

4.5.2 左移运算符重载

cout <<  

int a = 10;

cout << a << endl;

 

Person p;

p.m_A = 10;

p.m_B = 10;

cout << p << endl;    //此时若想直接输出p就打印出m_A和m_B是不可以的,若想输出p就打印出p的所有属性,就得重载左移运算符;

//左移运算符重载
class Person
{
    friend ostream& operator<<(ostream& Cout, Person& p);
public:
    Person(int a, int b)
    {
        m_A = a;
        m_B = b;
    }
private:
    //1 利用成员函数重载 左移运算符  p.operator<<(cout)  简化版本 p << cout
    //不会利用成员函数重载<<运算符,因为无法实现cout在左侧的预期效果
    //void operator<<(cout) {  }
    
    int m_A;
    int m_B;

};
//2 只能利用全局函数重载左移运算符
ostream & operator<<(ostream &Cout, Person &p)    //本质operator<<(Cout, p)    简化为Cout << p    //cout属于ostream数据类型 可右键转到定义查看
                            //引用其实就是起别名 上行中的Cout 是cout的自定义名字
{
    Cout << "m_A = " << p.m_A << "m_B = " << p.m_B;
    return Cout;      //链式编程思想,如果返回值为void 而不是ostream &(因为传入的参数里Cout是ostream & 引用) 则在第30行的代码后面不能继续追加endl换行符
}
void test1()
{

    Person p(10,10);
    //p.m_A = 10;
    //p.m_B = 10;
    //cout << p.m_A << endl;
    //cout << p.m_B << endl;
    cout << "重载的p = " << p << endl;
}
int main()
{
    test1();
    system("pause");
    return 0;
}

总结:重载左移运算符配合友元可以实现输出自定义数据类型

4.5.3 递增运算符重载

 int a = 10;

cout << ++a << endl;   //11

cout << a << endl;    //11

int b = 10;

cout << b++ << endl;  //10

cout << b << endl;   //11

 

class MyInteger    //我自己的整型

{

public:

  MyInteger()

  {

    m_Num = 0;

  }

private:

  int m_Num;

}

MyInteger myint;

cout << myint << endl;    //0

cout << ++myint << endl;    //1

cout << myint++ << endl;    //1

cout << myint << endl;        //2

#include <iostream>
#include <string>
using namespace std;

//递增运算符重载
class MyInteger
{
    friend ostream& operator<<(ostream& cout, MyInteger myint);
public:
    MyInteger()
    {
        m_num = 0;
    }
    //重载前置++运算符    
    MyInteger& operator++()   //返回引用是为了一直对一个数据进行递增  简单来说 加&返回的还是原来的对象,不加&返回的是一个拷贝出来的新对象
    {
        m_num++;
        
        return *this;     //thhis是指向类本身的指针,解引用相当于类本身
        //为了可以实现cout << ++myint << endl,所以此函数应该返回myint类型的返回值,利用指向自身的this指针,再利用解引用用把它的自身作为一个返回
    }
    //重载后置++运算符
    //void operator++(int)    //此处的int是占位参数,用于区分前置与后置,加个占位符避免与前置重定义。待会等后置函数体写好就可删掉
    MyInteger operator++(int)
    {
        //先返回结果 -》先记录当时的结果
        MyInteger temp = *this;
        //后进行递增操作
        m_num++;
        //最后将记录的结果做返回
        return temp;
    }

private:
    int m_num;
};

//重载 << 运算符
ostream & operator<<(ostream& cout, MyInteger myint)
{
    cout << myint.m_num;
    return cout;
}
void test1()  //
{
    MyInteger myint;

    cout << ++(++myint) << endl;
    cout << myint << endl;
}

void test2()  //
{
    MyInteger myint;
    cout << myint++ << endl;
    cout << myint << endl;
}

int main()
{
    //test1();
    test2();
    system("pause");
    return 0;
}

-------------------------------- 举一反三-----------------------------

//重载递减运算符
class myInteage
{
    friend ostream& operator<<(ostream& cout, myInteage myint);
public:
    myInteage()
    {
        m_num = 0;
    }
    //重载前置--
    myInteage & operator--()
    {
        m_num--;
        return *this;
    }
    //重载后置--
    myInteage operator--(int)
    {
        myInteage temp = *this;
        m_num--;
        return temp;
    }
private:
    int m_num;
};
//重载 << 运算符
ostream& operator<<(ostream& cout, myInteage myint)
{
    cout << myint.m_num;
    return cout;
};

void test1()    //前置递减
{
    myInteage myint;
    cout << --myint << endl;
    cout << myint << endl;
}

void test2()    //后置递减
{
    myInteage myint;
    cout << myint-- << endl;
    cout << myint << endl;
}

int main()
{
    test1();
    test2();
    system("pause");
    return 0;
}

 

4.5.4 赋值运算符重载

 c++编译器至少给一个类添加4个函数(实际是6个)

1、默认构造函数(无参,函数体为空)

2、默认析构函数(无参,函数体为空)

3、默认拷贝构造函数,对属性进行值拷贝

4、赋值运算符operator=,对属性进行值拷贝

 

如果类中有属性指向堆区。做赋值操作时也会出现深浅拷贝问题

 

class Person
{
public:
    Person(int age)
    {
        m_Age = new int(age);
    }

    ~Person()   //加入析构函数后,运行会崩
    {
        if (m_Age != NULL)
        {
            delete m_Age;
            m_Age = NULL;
        }
    }

    int* m_Age;
};

void test1()
{
    Person p1(18);
    Person p2(20);

    p2 = p1;  //赋值操作

    cout << "p1的年龄为:" << *p1.m_Age << endl;  //不加*,打印出来的是指针 加*解引用打印出来的是18岁
    cout << "p2的年龄为:" << *p2.m_Age << endl;

}

int main()
{
    test1();
    system("pause");
    return 0;
}

以上程序运行的时候会崩,因为一个堆区内存释放了两次

 

 

解决方案:利用深拷贝解决浅拷贝的问题    //重载 = 使p2 = p1时,重新申请一块内存,各自释放各自的内存;

 

 

class Person
{
public:
    Person(int age)
    {
        //将指针开辟到堆区
        m_Age = new int(age);
    }

    ~Person()   //加入析构函数后,运行会崩
    {
        if (m_Age != NULL)
        {
            delete m_Age;
            m_Age = NULL;
        }
    }
    //重载 赋值运算符
    Person& operator=(Person &p)     //下面调用时 是p2 = p1,所以 小括号里得传入参数p1,所以小括号里得传入一个Person的数据类型
    {
        //编译器提供的是类似于 m_Age = p.m_Age 这样的代码 指针之间直接等号赋值的浅拷贝
        //而我们 应该先判断是否有属性在堆区,如果有属性在,应先释放干净,然后再深拷贝
        if (m_Age != NULL)
        {
            delete m_Age;
            m_Age = NULL;
        }
        //深拷贝
        m_Age = new int(*p.m_Age);

        //返回对象本身
        return *this;
    }
    int* m_Age;
};

void test1()
{
    Person p1(18);
    Person p2(20);

    Person p3(30);

    p3 = p2 = p1;  //赋值操作

    cout << "p1的年龄为:" << *p1.m_Age << endl;  //不加*,打印出来的是指针 加*解引用打印出来的是18岁
    cout << "p2的年龄为:" << *p2.m_Age << endl;
    cout << "p3的年龄为:" << *p3.m_Age << endl;
}

int main()
{
    test1();

    //int a = 10;
    //int b = 20;
    //int c = 30;

    //c = b = a;

    //cout << "a=" << a << endl;
    //cout << "b=" << b << endl;
    //cout << "c=" << c << endl;

    system("pause");
    return 0;
}

 

4.5.5 关系运算符重载

 对比自定义数据类型时使用

//重载关系运算符
class Person
{
public:
    Person(string name, int age)
    {
        m_Name = name;
        m_Age = age;
    }
    
    //重载 == 号
    bool operator==(Person &p)
    {
        if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
        {
            return true;
        }
        return false;
    }


    //重载 != 号
    bool operator!=(Person& p)
    {
        if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
        {
            return false;
        }
        return true;
    }

    string m_Name;
    int m_Age;

};

void test1()
{
    Person p1("Tom", 11);
    Person p2("Tom", 11);
    if (p1 == p2)
    {
        cout << "p1和p2是相等的! " << endl;
    }
    else
    {
        cout << "p1和p2是不相等的! " << endl;
    }
}

void test2()
{
    Person p1("Tom", 11);
    Person p2("Tom", 11);
    if (p1 != p2)
    {
        cout << "p1和p2是不相等的! " << endl;
    }
    else
    {
        cout << "p1和p2是相等的! " << endl;
    }

}

int main()
{
    test1();
    test2();

    system("pause");
    return 0;
}

 

4.5.6 函数调用运算符重载

 1、函数调用运算符()也可以重载

2、由于重载后使用的方式非常像函数的调用,因此称为仿函数

3、仿函数没有固定写法,非常灵活

//重载函数调用运算符
//打印输出类
class Myprint
{
public:
    //重载函数调用运算符
    void operator()(string test)
    {
        cout << test << endl;
    }
};

void MyPrint2(string test)
{
    cout << test << endl;
}


void test1()
{
    Myprint myPrint;
    myPrint("Hello world");     //重载函数调用    由于使用起来跟函数调用十分类似,因此称为仿函数
    MyPrint2("Hello world");    //函数调用

}

//仿函数十分灵活,无固定写法
//加法类
class MyAdd
{
public:
    int operator()(int num1 , int num2)      
    {
        return num1 + num2;
    }
};

void test2()
{
    MyAdd myAdd;
    int ret = myAdd(100, 100);
    cout << "ret=" << ret << endl;

    //匿名函数对象
    cout << MyAdd()(100, 100) << endl;      //类名加小括号 ->  创建匿名对象       因为是重载调用函数 所以叫匿名函数对象
}

int main()
{
    //test1();
    test2();
    system("pause");
    return 0;
}

 

posted @ 2021-12-10 18:02  大白不会敲代码  阅读(76)  评论(0)    收藏  举报