C++_狄泰总结03操作符重载

1)操作符重载(本质函数定义) ==>使用对象:类

c++的重载可以拓展操作符的功能
操作符的重载是以函数的方式进行
本质:用特殊形式的函数扩展操作符的功能

通过关键字operator 可以定义特殊的函数
operator 的本质:通过函数重载操作符
语法:

Type operator sign(const Type& p1, const Type& p2)
{	//sign是标准的 + -*/ ...
		type ret;
		return ret	
} 

特点:可以将操作符重载函数定义为类的成员函数,优点如下:

  1. 比全局操作符重载函数少一个参数(左操作数,用隐藏的this指针)
  2. 不需要依赖友元函数就可以完成操作符的重载
  3. 编译器优先在成员函数中寻找操作符重载函数
#include <stdio.h>

class Complex 
{
    int a;
    int b;
public:
    Complex(int a = 0, int b = 0)
    {
        this->a = a;
        this->b = b;
    }
    
    int getA()
    {
        return a;
    }
    
    int getB()
    {
        return b;
    }
    
    Complex operator + (const Complex& p)
    {
        Complex ret;
        printf("Complex operator + (const Complex& p)\n");
        ret.a = this->a + p.a;
        ret.b = this->b + p.b;
        
        return ret;
    }
    
    friend Complex operator + (const Complex& p1, const Complex& p2);
};

Complex operator + (const Complex& p1, const Complex& p2) //要用友元才能访问private对象
{//但这样破坏了C++的封装性
    Complex ret;
    printf("Complex operator + (const Complex& p1, const Complex& p2)\n");
    ret.a = p1.a + p2.a;
    ret.b = p1.b + p2.b;
    
    return ret;
}

int main()
{

    Complex c1(1, 2);
    Complex c2(3, 4);
    Complex c3 = c1 + c2; // c1.operator + (c2)这里编译器优先采用类的成员函数(操作符合重载函数)
    
    printf("c3.a = %d, c3.b = %d\n", c3.getA(), c3.getB());
    
    return 0;
}

操作符重载原则:
赋值操作符(=)只能重载为成员函数
操作符重载不能改变原操作符的优先等级
操作符重载不能改变操作数的个数
操作符重载不能改变操作符的原有语义

2)完善复数类:操作符重载

运算"+" "-" "* "/"
比较 "==" "!="
赋值 "=" //c++规定只能重载为成员函数
求模 "modulus"
**
1)复数的加减乘除是分别先算出实部和虚部,然后在创建对象,利用其带参构造函数
Complex ret(na, nb);
return ret;
2)复数的比较是返回 a b 的判断表达式
return (a == c.a) && (b == c.b);
return !(*this == c);
3)复数的赋值是先比较是否一样,后a b直接赋值

#ifndef _COMPLEX_H_
#define _COMPLEX_H_

class Complex
{
    double a;
    double b;
public:
    Complex(double a = 0, double b = 0);
    double getA();
    double getB();
    double getModulus();
    
    Complex operator + (const Complex& c);
    Complex operator - (const Complex& c);
    Complex operator * (const Complex& c);
    Complex operator / (const Complex& c);
    
    bool operator == (const Complex& c);
    bool operator != (const Complex& c);
    
    Complex& operator = (const Complex& c);
};

#endif

#include "Complex.h"
#include "math.h"

Complex::Complex(double a, double b)
{
    this->a = a;
    this->b = b;
}

double Complex::getA()
{
    return a;
}

double Complex::getB()
{
    return b;
}

double Complex::getModulus()
{
    return sqrt(a * a + b * b);
}

Complex Complex::operator + (const Complex& c)
{
    double na = a + c.a;
    double nb = b + c.b;
    Complex ret(na, nb);
    
    return ret;
}

Complex Complex::operator - (const Complex& c)
{
    double na = a - c.a;
    double nb = b - c.b;
    Complex ret(na, nb);
    
    return ret;
}

Complex Complex::operator * (const Complex& c)
{
    double na = a * c.a - b * c.b;
    double nb = a * c.b + b * c.a;
    Complex ret(na, nb);
    
    return ret;
}

Complex Complex::operator / (const Complex& c)
{
    double cm = c.a * c.a + c.b * c.b;
    double na = (a * c.a + b * c.b) / cm;
    double nb = (b * c.a - a * c.b) / cm;
    Complex ret(na, nb);
    
    return ret;
}
    
bool Complex::operator == (const Complex& c)
{
    return (a == c.a) && (b == c.b);
}

bool Complex::operator != (const Complex& c)
{
    return !(*this == c);
}
    
Complex& Complex::operator = (const Complex& c)
{
    if( this != &c )
    {
        a = c.a;
        b = c.b;
    }
    
    return *this;
}

**

3)"<<" 左移操作符合重载

**
操作符合<<的原生语义是按位左移,意义:将整数1按位左移2位
重载左移操作符,将变量或常量左移到一个对象中

Console& operator << (const char* s)	//输出到终端
{
    printf("%s", s);
    
    return *this;
}

在这里插入图片描述

#include <iostream>
#include <cmath>

using namespace std; //std命名空间里有c++标准库定义的类和对象


int main()
{
    cout << "Hello world!" << endl;		//这里采用了c++标准库,已经进行了左移操作符的重载了
    
    double a = 0;
    double b = 0;
    
    cout << "Input a: ";
    cin >> a;
    
    cout << "Input b: ";
    cin >> b;
    
    double c = sqrt(a * a + b * b);
    
    cout << "c = " << c << endl;
    
    return 0;
}

4)数组操作符重载( [ ] )

数组访问符是C/C++的内置操作符
数字访问符的原生意义是:数组访问和指针运算:
a[n] =(a+n) ==>(n+a) ==>n[a]

要求:
只能通过类的成员函数重载
重载函数能且只能使用一个参数
可以定义不同参数的多个重载函数

class Test
{
    int a[5];
public:
    int& operator [] (int i)	//操作符重载
    {
        return a[i];
    }
    
    int& operator [] (const string& s)
    {
        if( s == "1st" )
        {
            return a[0];
        }
        ....int main()
{
    Test t;
  	... 
    cout << t["5th"] << endl;	//a[4]
    cout << t["4th"] << endl;   //a[3]
 }

数组操作符号重载的意义:
1)可以用英文等其他形式调用a[0]等
2)让(数组)类可以向数组一样来使用

class IntArray
{
	....
public:
	int& IntArray::operator [] (int index);
    IntArray& self();
    ~IntArray();
};

Inarry.cpp
int& IntArray::operator [] (int index)
{
    return m_pointer[index];
}

IntArray& IntArray::self()
{
    return *this;
}

int main()
 Inarry* a =Inarry::NewInstance(9);   
    
    if( a != NULL )
    {
        Inarry& array = a->self();	//这一步的意义? array指向这个a的数组  
             cout << array[i] << endl;
//main.c:35:26: error: no match for ‘operator[]’ (operand types are ‘Inarry’ and ‘int’)	因为array不是一个数组而是一个数组类,所以才需要操作符重载
        
        cout << "array.length() = " << array.getLength() << endl;

5)函数对象–重载函数操作符号"()" (在工程中取代函数指针)

特性:
只能通过类的成员函数重载
可以定义不同参数的多个重载函数

实际案例

客户需求:
1)函数可以获得斐波拉契数列每项的值
2)每调用一次返回一个值
3)函数可根据需要重复使用
这个方案:利用静态局部变量,实现累加
缺点:
静态局部变量处于函数内部,外界无法改变
函数为全局函数,一旦开始,无法从头执行
无法制定某个具体的数列项作为初始项目

#include <iostream>
#include <string>

using namespace std;

int fib()
{
    static int a0 = 0;
    static int a1 = 1;
    
    int ret = a1;
    
    a1 = a0 + a1;
    a0 = ret;
    
    return ret;
}


int main()
{
    for(int i=0; i<10; i++)
    {
        cout << fib() << endl;	//从1到55
    }
    
    cout << endl;
    {
        cout << fib() << endl;
    }
    
    return 0;
}

问题优化:利用操作符号重载() ,可反复调用函数对象,并预设初值

#include <iostream>
#include <string>

using namespace std;

class Fib
{
    int a0;
    int a1;
public:
    Fib()
    {
        a0 = 0;
        a1 = 1;
    }
    
    Fib(int n)
    {
        a0 = 0;
        a1 = 1;
        
        for(int i=2; i<=n; i++)
        {
            int t = a1;
            
            a1 = a0 + a1;
            a0 = t;
        }
    }
    
    int operator () ()
    {
        int ret = a1;
    
        a1 = a0 + a1;
        a0 = ret;
        
        return ret;
    }
};

int main()
{
    Fib fib;
    
    for(int i=0; i<10; i++)
    {
        cout << fib() << endl;
    }
    
    cout << endl;
    
    for(int i=0; i<5; i++)		//可以接着继续
    {
        cout << fib() << endl;
    }
    
    cout << endl;
    
    Fib fib2(10);	//可以反复调用,且能确定从哪开始
    
    for(int i=0; i<5; i++)
    {
        cout << fib2() << endl;
    }
    
    return 0;
}

**

6)赋值操作符(=)重载   重看视频

**
当函数无赋值操作符重载时,编译器自动补充赋值操作符(=)重载函数,但这个只完成浅拷贝的操作,
当需要进行深拷贝时必须重载赋值操作符
赋值操作符与拷贝构造函数有相同的存在意义
原则:重载赋值操作符,必须要实现深拷贝??

为什么需要赋值操作符重载?

Test t2 = Test t1

==>由于编译器提供的赋值操作符只提供了浅拷贝的动作,,浅拷贝,t2跟t1的物理状态一样里面的m_point指向了同一块地址,

赋值操作符函数编写原则:
原则1: 返回类型是引用,为了能够连续赋值
原则2: 参数是const Test&
原则3:判断这个不是自赋值(i=i),用this指针跟参数的地址比较,自赋值是没有意义的,只是为了兼容C语言

原则4:将当前对象返回

Test& operator = (const Test& obj)		
    {																	
        if( this != &obj )										
        {
            delete m_pointer;			//这里删除原本的空间
            m_pointer = new int(*obj.m_pointer);	//m_point,重新申请一个空间,空间的内容是对象的内容
        }
        return *this;											
    }

**

6.1)实际例子:赋值操作浅拷贝导致系统奔溃

**

/*由于编译器提供的赋值操作符只提供了浅拷贝的动作,
浅拷贝,t2跟t1的物理状态一样里面的m_point指向了同一块地址,
main函数执行完,销毁对象,Test2释放了内存后,Test1想再次释放同一块内存,于是系统崩溃
*/
int main()
{
    Test t1 = 1;
    Test t2;   		
    /*当只进行浅拷贝(无自定义拷贝构造函数/赋值操作符时,浅拷贝,t2跟t1的物理状态一样)
    	里面的m_point指向了同一块地址
    */
    t2 = t1;	//t2 的m_point 从null 变成了跟 t1一样指向相同的内存空间
    
    t1.print();			//m_pointer = 0x202ac20

    t2.print();		//m_pointer = 0x202ac40
    
    return 0;
}

test.cpp
class Test
{
    int* m_pointer;
public:
    Test()
    {
        m_pointer = NULL;
    }
    Test(int i)
    {
        m_pointer = new int(i);
    }
 /*如果没有这两个,编译器会自动补充拷贝构造函数和赋值操作符重载函数,
 赋值操作符只进行浅拷贝动作,由于类里面的成员是m_point,他会使用到系统的堆内存 ,
 浅拷贝动作带来的是对象的物理状态保存一致,即都指向了同一块内存空间(指向的地址一样)
 */  
    Test(const Test& obj)						
    {
        m_pointer = new int(*obj.m_pointer);
    }
    Test& operator = (const Test& obj)		原则1: 返回类型是引用,为了能够连续赋值
    {										原则2: 参数是const Test& (const 引用类型)
        if( this != &obj )					原则3:判断是否不等于自身,用this指针跟参数的地址比较
        {
            delete m_pointer;			//这里删除原本的空间
            m_pointer = new int(*obj.m_pointer);	//m_point,重新申请一个空间,空间的内容是对象的内容
        }
     
        return *this;						原则4:返回 *this ,引用值
    }
  ===========================================
    void print()
    {
        cout << "m_pointer = " << hex << m_pointer << endl;
    }
    ~Test()
    {
        delete m_pointer;
    }

7)智能指针(本质:对象)-->操作符重载(->和*),利用一个对象来代替指针行为

解决问题:内存泄漏

  1. 动态申请堆空间,用完不还   -->长时间变卡,或出bug
  2. Java C#引入垃圾回收机制 ==> C++没有垃圾回收机制
  3. 指针无法控制所指堆空间的生命周期

==>引入智能指针(本质,动态申请没有归还)

智能指针需要解决的问题:

  1. 在指针生命周期结束时主动释放堆空间  ==>类对象里的析构函数释放指针
  2. 一片堆空间**最多只能有一个指针标识
    **=>避免多次释放==>如果两个指针指向同一片堆空间,有可能造成重复释放的bug
    ==>拷贝构造函数重载和赋值符合函数重载
    /*拷贝构造函数避免多个指针指向同一个内存的思路:
    1)释放类本身所指向的指针
    2)指针指向新对象的内存
    3)赋值对象的指针指向为空
    */
    3)杜绝指针运算和指针比较 ==> 避免野指针,指针越界
    ==>对象本来就不行,除非再重载 "++ " "-- " “==” “!=”

智能指针方案:用一个对象模拟指针的行为
0)需要一个特殊指针 —>对象模拟指针(外号:智能指针)
1)重载指针操作符 “->” 和 "*"
2)只能通过类的成员函数重载
3)重载函数不能使用参数 ==>只能定义一个重载函数

class Pointer		//先建一个类,才能有对象
{
    Test* mp;	//加入模板技术,可以使得point类指向各种类型
public:
    Pointer(Test* p = NULL)		//被堆空间上面的内存地址初始化,先给默认值,默认值为空
    {
        mp = p;
    }
    Pointer(const Pointer& obj)		
    {
    	/*拷贝构造函数避免多个指针指向同一个内存的思路:
			1)释放类本身所指向的指针
			2)指针指向新对象的内存	
			3)赋值对象的指针指向为空
		*/
		//delete mp; 这里本来应该像赋值操作符重载一样有这个,但由于main函数里的p2并没
        mp = obj.mp;
        const_cast<Pointer&>(obj).mp = NULL;	//由于obj是connst(只读)属性,所以需要解引用
    }
    Pointer& operator = (const Pointer& obj)	//由于采用了mp指针,深拷贝
    {
        if( this != &obj )		
        {
            delete mp;
            mp = obj.mp;
            const_cast<Pointer&>(obj).mp = NULL;
        }
        
        return *this;
    }
    Test* operator -> ()	//无参,只能定义一个
    {
        return mp;			//返回 mp这个成员指针
    }
    Test& operator * ()		// *的意义==>返回当前的变量或对象
    {
        return *mp;			//返回这个对象
    }
    bool isNull()			//判断当前指针是否为空
    {
        return (mp == NULL);
    }
    ~Pointer()	//析构函数,释放mp指针  //满足第一个需求:在指针生命周期结束时**主动释放堆空间**
    {
        delete mp;
    }
};
int main()
{
    Pointer p1 = new Test(0);		//这里创建一个对象(智能指针)
    
    cout << p1->value() << endl;	//用对象模拟指针	
    
    Pointer p2 = p1;
    
    cout << p1.isNull() << endl;
    
    cout << p2->value() << endl;
    
    return 0;
}

8)前置操作符(++i)与后置操作符(i++)重载,"–"操作符同理

编译器优化后 ++i 跟i++的语义是一样的
问题: 前置操作符(++i)与后置操作符(i++)可以重载吗 ?怎么区分前置还是后置?

==>全局函数和成员函数均可以重载 =>推荐用成员函数
==>前置++不需要参数, 后置++要一个占位参数表示
(前置++需要返回引用,因为重载自加运算符后可以返回对象的引用, 以方便在表达式中连续使用。而后置++返回的不是引用,所以不能进行连续使用。)

i++与++i有区别吗?
===>对于基本类型(int)的变量,前置++跟后置++效率一样(编译器会进行优化)
==>对于类类型的对象,前置++的效率高(后置++需要调用构造函数,析构函数,而且)

class Test
{
    int mValue;
public:
  Test& operator ++ ()		//++i ,加一之后,要返回自身 this指针指向的对象
    {
        ++mValue;
        
        return *this;
    }
    
    Test operator ++ (int)	//i++,返回自身,后++
    {
        Test ret(mValue);	//将当前对象的值保存下来
        
        mValue++;
        
        return ret;		//返回保存的当前对象
    }

9)逻辑操作符无法重载(重载后无法保持短路法则,违反语义原则)

工程开发不要重载逻辑操作符
通过重载比较操作符替换逻辑操作符重载
通过专用成员函数替换逻辑操作符重载

10)工程中不要重载逗号操作符(重载后无法从左向右计算表达式,违反语义原则)

重载:
进入函数体前必须完成所有参数的计算,但函数参数的计算次序是不定的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值