深入运算符重载(2)

深入运算符重载(2)

1、运算符重载时,什么时候以引用返回,什么时候以值返回?

以值返回:

 Int operator+(const Int& it)
 {
     this->value += it.value;
     
     return Int(this->value + it.value);
 }

以引用返回:

Int &operator+=(const Int &it)
{
    this->value += it.value;
    
    return *this;
}

a += b;

总结:凡是运算符返回自身时候,以引用返回。但是以将亡值返回的时候,需要使用值返回。

2、构造函数的三个作用

class Int
{
private:
	int value;
public:
    //1 2 3
    Int(int x = 0):value(x)
    {
        cout << "Int()" << this << endl;
    }
    
    Int(const Int &it):value(it.value) 
    {
        cout << "Copy Int:" << this << endl;
    }
    
    Int &operator=(const Int &it)
    {
        if(this != &it)
        {
            value = it.value;
        }
       	cout << this << " = " << &it << endl;
        
        return *this;
    }

}

int main()
{
    Int a = 10;			//自定义类型
   	//Int a(10);
    
    int b = 100;		//内置类型
    
    a = b;
}

构造函数的三个作用:

  1. 构建对象
  2. 初始化对象
  3. 类型转换(仅限单参数的构造函数)

3、什么是类型转换

我们看下面这个例子

问题:如上main函数所示,能否将b赋值给a?

回答:构造函数的第三个功能,类型转化

首先我们观察,一个是int 类型,一个是自定义的Int类型。如果将b给a赋值,显然是不能通过的,但是编译器并没有报错,这是怎么一回事?在这里插入图片描述

我们将b进行转化,转化为自定义类型。然后将转化后的类型给a赋值。

通过调用我们可以发现:
在这里插入图片描述

我们创建出一个临时对象地址为012FF690,然后将临时对象赋值给a对象。

再来看一下析构过程:

~Int()
{
    cout << "Destroy Int: " << this << endl;
}

在这里插入图片描述
可以发现临时对象在赋值完毕后就会被销毁。

3.1 此处可以类型转换的构造函数必须是单参数的。

//构造函数
Int(int x,int y):value()
{
    cout << "Int()" << this << endl;
}

int main()
{
    Int a(10,20);
    int b = 100;
    
    a = b;		//error:必须只含有一个参数
}

在这里插入图片描述

如果给参数初始值的化也可以以当作是一个参数:

Int(int x,int y = 0):value()
{
    cout << "Int()" << this << endl;
}

int main()
{
    Int a(10);
    int b = 100;
    
    a = b;				//right :赋了默认值
}

问题:a的值是多少呢?

Int(int x,int y = 0):value(x + y)
{
    cout << "Int()" << this << endl;
}

int main()
{
    Int a(1,2);
    int b = 100;
    //a = b;
    //a = b,20;		//逗号运算符,按最右的为准。
    
    //a = (Int)(b,20);	//也是逗号表达式,取20,也就是(Int) 20;
    //a = Int(b,20);	//调用两个参数的构造函数,创建一个无名对象,将20给y,b给x,结果是120; 
}

最后两者都会产生临时对象,第一种按照类型转换方式来调用构造函数,第二种产生一个无名对象,接收两个参数。

接上面的问题,那如果给两个参数,还不给默认初始化呢?

Int(int x,int y):value(x + y)
{
    cout << "Int()" << this << endl;
}

int main()
{
    Int a(10,20);
    int b = 100;
    
    a = (Int)(b,100);	//error:强转形式创建无名对象
    a = Int(b,100);		//right:调动构造函数创建无名对象

}

对于类型强转类型的对象,我们只能给一个有效参数,给多了error

3.2 如果不允许隐式进行构造函数:构造函数前添加explicit关键字。

...
explicit Int(int x = 0):value(x)
{
    cout << "Int()" << this << endl;
}
...
    
int main()
{
    Int a(10);
    int b = 100;
    
    a = b;				//error,不能隐式的类型转换
    
    a = (Int) (200);	//right,强制转换后可以
}

3.3 我们能否将变量给对象赋值呢?

explicit Int(int x):value(x)
{
    cout << "Create Int:" << this << endl;
}

int main()
{
    Int a(10);
    int b = 100;
    
    a = (Int)b;
    
    b = a;			//error
    
    b = (int)a;		//尝试类型转换:error
    
}

不可以这样操作,我们没有强制转换类型的缺省函数。我们需要自己写。返回类型是强转的类型(系统给出)

operator int()const 
{
    return value;	
    //返回类型是强转的类型
}

int operator int()const 
{
    return value;
}
//如果是这种情况则刚好:
// b = (int)a;

//如果是这种情况:b = (float)a;
//不可能再写一个float类型的重载函数
//上述代码的main函数
int main()
{
    Int a(10);
    int b = 100;
    
    a = (Int)b;
    
    b = a;
    //调用强转函数
    //相当于:b = a.operator int();
    //相当于:b = operator int(&a);
    b = (int)a;		
    
}

总结:

  1. 当需要将内置类型赋值给对象的时候,我们可以通过强制转换,通过单参构造函数来构建无名对象;
  2. 当需要将对象赋值给内置类型的时候,可以通过运算符重载函数来实现。

3.4 mutable关键字和仿函数

有如下类:

class Add
{
   mutable int value;
   //即使
public:
    Add(int x = 0) : value(x) {}
  	
    int operator()(int a,int b) const 
    //添加const value将无法被修改,可以给value添加 mutable 关键字
    {
        value = a + b;
        return value;
    }
};

如下main函数:

int main()
{
    int a = 10,int b = 20, c = 0;
    Add add;
    
    c = add(a,b);	//仿函数
}

仿函数概念:仿函数即仿照的函数,表示它功能与函数类似但并不是真正的函数,仿函数又叫函数对象。在C++中它通过重载函数调用运算符即 ()运算符。

问题:赋值那句add对象是如何调用构造函数的?

add不会调用构造函数,会调用仿函数

c = add(a,b);		//我们重载了括号运算符

//相当于:
c = add.operator()(a,b);

更复杂的情况:

int main()
{
    int a = 10, b = 20, c = 0;
    c = Add()(a,b);
}

类型名加 () 调用构造函数,产生一个将亡值对象,然后调用自己的

() 运算符重载,将a和b相加,最后赋值给c。

总结:

  1. 被mutable修饰的成员变量,即使在成员方法中为this指针添加const 修饰也可以被修改。
  2. 凡是重载了括号的运算符,我们就叫做仿函数。当碰见对象( )的时候要注意,可能是对象构造,可能是调用仿函数。

4、构造函数使用初始化列表和赋值的区别

思考下面代码的输出方式:

#include<iostream>
using namespace std;

class Int
{
private:
	int value;
public:
    Int(int x = 0):value(x)
    {
        cout << "Int()" << this << endl;
    }
    
    Int(const Int &it):value(it.value) 
    {
        cout << "Copy Int:" << this << endl;
    }
    
    Int &operator=(const Int &it)
    {
        if(this != &it)
        {
            value = it.value;
        }
       	cout << this << " = " << &it << endl;
        
        return *this;
    }
	
    ~Int()
    {
        cout << "Destory Int:" << this << endl;
    }
};


class Object
{
    int num;
    Int val;			//Int类型对象
public:
    Object(int x,int y)
    {
        num = x;
        val = y;

        cout << "create Objcet:" << this << endl;
    }

    ~Object()
    {
        cout << "destroy Object:"<< this << endl;
    }
};
int main()
{
    Object obj(1,2);
}

过程分析:
在这里插入图片描述在这里插入图片描述在这里插入图片描述

输出结果:在这里插入图片描述

初始化列表和在构造函数里面赋值等同的效果(仅限内置类型

Object(int x,int y):num(x)	//等价赋值语句
{
    cout << "Create Object:"<< this << endl;
    //num = x;		//等价于初始化列表
    val = y;
}

但是自定义类型就不一样,不需要构造临时对象

Object(int x,int y):num(x),val(y)
{
    cout << "Create Object:"<< this << endl;
    //num = x;		//等价于初始化列表
    //val = y;
}

在这里插入图片描述

总结:对于内置类型,效率相同;对于自定义类型的初始化,尽量使用初始化列表的方式进行,节省空间。

5、类数据成员的构造顺序

按照设计顺序进行构建

#include<iostream>
using namespace std;

class Object
{
    int num;
    int sum;		//与定义数据相关
public:
    Object(int x = 0):sum(x),num(sum)
    {
        
    }
    
    void Print()const 
    {
        cout << num << " " << sum << endl;
    }
};

C++中类数据成员的构造与初始化列表中成员的顺序无关,与类数据成员再类中定义的数据有关。

过程分析:

  1. 首先构造的是num,其次是sum。根据编译器会将所有的数据成员先初始化为0,再赋值。
  2. num先被构造,将sum的值赋给num。但是此时sum未被构造,是0或是随机数,将这个值赋给了num。
  3. 后来将x的值赋给sum。

6、对象生存期管理方式

class Object
{
    Int *ip;
public:
    Object(Int *s = NULL):ip(s) {}
    ~Object()
    {
        if(ip != NULL)
        {
            delete ip;
        }
        ip = NULL;
    }
};


int main()
{
    Object obj(new Int(10));
    //new 的三个作用
    //1、从堆区申请空间
    //2、调用构造函数,构造对象
    //3、返回对象的地址
}

过程分析:

  1. new有三个作用,首先会从堆区开辟一块空间,接着调用构造函数来构建对象,最后会返回对象的地址。将10存放到开辟的空间中,指针s来指向这个空间,在构造函数中还会将s来赋值给ip。
  2. 对象结束后,如果ip为空,则删除ip。将ip赋值为空。

这样做有什么好处呢?

可以将 Int 对象的生存期进行自动管理,我们将上下两个代码进行对比。

手动管理:

int fun()
{
    Int *ip = new Int(10);
    
    ...
        
    delete ip;		//如果没有这一步,会发生内存泄漏,由我们手动进行
}

int main()
{
    fun();
}

自动管理:

int fun()
{
    Object obj(new Int(10));
    //在堆区申请的空间,如果fun函数结束,obj局部对象会被销毁,会调用它的析构函数。而它的析构函数中有delete,由系统自动进行
}

int main()
{
    fun();
}

7、*运算符和->运算符重载后返回对象的区别

分析下面代码:

class Int
{
private:
	int value;
public:
    Int(int x = 0):value(x)
    {
        cout << "Int()" << this << endl;
    }
    
    Int(const Int &it):value(it.value) 
    {
        cout << "Copy Int:" << this << endl;
    }
    
    Int &operator=(const Int &it)
    {
        if(this != &it)
        {
            value = it.value;
        }
       	cout << this << " = " << &it << endl;
        
        return *this;
    }
	
    ~Int()
    {
        cout << "Destory Int:" << this << endl;
    }

    int &Value()
    {
        return value;
    }

    const int &Value()const 
    {
        return value;
    }

};

class Object
{
    Int *ip;
public:
    Object(Int *s = NULL):ip(s) {}
    ~Object()
    {
        if(ip != NULL)
        {
            delete ip;
        }
        ip = NULL;
    }

    Int &operator*()
    {
        return *ip;
    }

    const Int& operator*() const  
    {
        return *ip;
    }

    Int *operator->()
    {
        return ip;
    }

    const Int* operator->() const
    {
        return ip;
    }
};

int main()
{
    Object obj(new Int(10));
    Int *ip = new Int(10);
    (*ip).Value();
    (*obj).Value();
    obj->Value();
    ip->Value();
}

重载*返回的是对象本身,重载指向符返回的是对象的指向。

为什么呢,我们看一下组合的概念:

组合:

  1. 值类型
  2. 指针类型
  3. 引用类型(最复杂)
    在这里插入图片描述

第一种组合最常见,val 对象是整个Object 类的一部分。

第二种指针类型的组合,ip并不是Object类的一部分,是一种弱关联,指针指向的对象可以被改变。

如果使用对ip进行访问,返回对象本身。如果通过*->进行访问,得到的是堆区的地址**。
在这里插入图片描述

第三中组合通过引用来修饰类内对象,是强关联,修改val对象会影响到。

明白了这个我们就可以将->进行进一步的修改:

Int * opearotr->()	//重载->运算符
{
    return &**this;
    //return ip;			//返回的是对象的地址
}

const Int *opearotr->()const 
{
    return &**this;
    //return ip;
}

问题:&**this是如何理解的呢?

this指针指向当前的对象,*this的意思就是对象本身。

再来一个就是 * * this,他会调用重载函数,返回的是所指向的对象的别名(返回 * ip)。

这时加上&,与解引用相互抵消,就是ip的地址。

那么,返回最初的代码,为什么解引用加.能和->有同样的作用呢?

class Int
{
private:
	int value;
public:
    Int(int x = 0):value(x)
    {
        cout << "Int()" << this << endl;
    }
    
    Int(const Int &it):value(it.value) 
    {
        cout << "Copy Int:" << this << endl;
    }
    
    Int &operator=(const Int &it)
    {
        if(this != &it)
        {
            value = it.value;
        }
       	cout << this << " = " << &it << endl;
        
        return *this;
    }
	
    ~Int()
    {
        cout << "Destory Int:" << this << endl;
    }

    int &Value()
    {
        return value;
    }

    const int &Value()const 
    {
        return value;
    }

};

class Object
{
    Int *ip;
public:
    Object(Int *s = NULL):ip(s) {}
    ~Object()
    {
        if(ip != NULL)
        {
            delete ip;
        }
        ip = NULL;
    }

    Int &operator*()
    {
        return *ip;
    }

    const Int& operator*() const  
    {
        return *ip;
    }

    Int *operator->()
    {
        return ip;
    }

    const Int* operator->() const
    {
        return ip;
    }
};

int main()
{
    Object obj(new Int(10));
    Int *ip = new Int(10);
    (*ip).Value();
    (*obj).Value();
    obj->Value();
    ip->Value();
}

在这里插入图片描述

在创建对象之前,会为obj对象和p对象分配空间。obj对象中有着指针对象,所以占4个字节(32位);p对象就是传统的指针对象,也占4个字节。

当要构造obj对象的时候,会先将他的临时对象参数先构造出来,在堆区创建一个空间来保存无名对象,值为10。接着p对象也需要创建一个无名对象,由p直接指向它,值为1。

接着p访问的就是p所致空间的内容;而 * obj就需要调用运算符的重载函数,返回的是解引用后的ip,也就是值为10的临时对象临时对象,通过Value来取到它的值。

p->Value()没什么好说的。关键在于obj->Value(),首先会调用->重载运算符。*obj的含义是得到ip,**obj 就是 * ip,得到临时对象的内容。& *ip,& 和 * 相互抵消,就是ip。ip->Value(),与最普通的p->Value()类似。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值