2024/02/26

目录

1. 运算符重载

1.1 加号运算符重载

1.1.1 利用成员函数重载

1.1.2 利用全局函数重载

1.1.3 运算符重载可以发生函数重载

1.2 左移运算符重载

1.2.1 利用成员函数重载(通常不用)

1.2.2 利用全局函数重载

1.2.3 利用友元访问私有属性

1.3 递增运算符重载

1.3.1 前置递增(类内实现)

1.3.2 后置递增(类内实现)

1.4 赋值运算符重载(深拷贝浅拷贝)

2. 前置++和后置++区别

2.1 本质:

2.2 常见问题

3. 深拷贝和浅拷贝

3.1 区别

3.2 浅拷贝的问题

3.3 深拷贝的不同以及如何解决浅拷贝的问题

3.3.1 自定义拷贝构造函数

3.3.2 赋值运算符重载


1. 运算符重载

operator关键字

1.1 加号运算符重载

实现两个类直接相加。

1.1.1 利用成员函数重载
class Person
{
public:
	int m_A;
	int m_B;
	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 main()
{
    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.operator+(p2)
	cout<<p3.m_A<<endl;
    cout<<p3.m_B<<endl;
}
1.1.2 利用全局函数重载
class Person
{
public:
	int m_A;
	int m_B;
};

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;
}

int main()
{
	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 = operator+(p1,p2)
	cout<<"p3 m_A is "<<p3.m_A<<endl;
    cout<<"p3 m_B is "<<p3.m_B<<endl;
}

1.1.3 运算符重载可以发生函数重载
class Person
{
public:
	int m_A;
	int m_B;
};

Person operator+ (Person &p, int num)
{
	Person temp;
	temp.m_A = p.m_A + num;
	temp.m_B = p.m_B + num;
	return temp
}

void test2()
{
	Person p4;
	p4 = p1+20;
}

1.2 左移运算符重载

实现直接利用cout<<p;打印类p中的成员变量。

1.2.1 利用成员函数重载(通常不用)
class Person
{
public:
	int m_A;
	int m_B;
	
	void operator<< (Person& p)
	{}
};

注意,对于operator<<,函数必须传参,不传参则自动报错。

1.2.2 利用全局函数重载
#include <iostream>
using namespace std;

class Person
{
public:
    int m_A;
    int m_B;
};

void operator<< (ostream & cout, Person& p)
{
    cout<<"m_A: "<<p.m_A<<" m_B: "<<p.m_B<<endl;
}

int main(int argc, char const *argv[])
{
    Person p;
    p.m_A = 10;
    p.m_B = 20;
    cout<<p;
    
    return 0;
}

Q1: 为什么重载函数的ostream & cout的参数必须是引用?

因为不引用的话,相当于传值,也就是要拷贝一份cout对象;但是ostream里面的拷贝构造函数是protected的,无法拷贝。

上面的例子还不完整,需要考虑链式编程,才可以实现继续输出换行符。

ostream& operator<< (ostream & cout, Person& p)
{
    cout<<"m_A: "<<p.m_A<<" m_B: "<<p.m_B<<endl;
    return cout;
}

int main(int argc, char const *argv[])
{
    Person p;
    p.m_A = 10;
    p.m_B = 20;
    cout<<p<<endl;
    
    return 0;
}

1.2.3 利用友元访问私有属性

如果类中元素是私有的,需要利用友元。

class Person
{
    friend ostream& operator<< (ostream & cout, Person& p);
public:
    Person(int m_A, int m_B):m_A(m_A),m_B(m_B){}
private:
    int m_A;
    int m_B;
};

ostream& operator<< (ostream & cout, Person& p)
{
    cout<<"m_A: "<<p.m_A<<" m_B: "<<p.m_B<<endl;
    return cout;
}

int main(int argc, char const *argv[])
{
    Person p(10,20);
    cout<<p<<endl;
    
    return 0;
}

1.3 递增运算符重载

1.3.1 前置递增(类内实现)
class Person
{
    friend ostream& operator<< (ostream& cout, Person& p);
public:
	Person(int age):age(age){}
	Person& operator++()
	{
        age = age+1;
        return *this;
	}
private:
    int age;
};

ostream& operator<< (ostream& cout, Person& p)
{
    cout<<"m_A: "<<p.age;
    return cout;
}

int main(int argc, char const *argv[])
{
    /* code */
    Person p(10);
    ++p;    
    cout<<(++p)<<endl;    //12
    return 0;
}

需要先利用<<运算符重载输出类中的成员变量。

注意,前置递增中,重载函数没有参数。且返回值是当前类的引用类型。

1.3.2 后置递增(类内实现)

为了实现后置递增的特性:先进行所有运算之后再递增,返回值需要是一个新的类对象。

class Person
{
    friend ostream& operator<< (ostream& cout, const Person& p);
public:
	Person(int age):age(age){}
	Person& operator++()
	{
        age = age+1;
        return *this;
	}
    Person operator++(int)
	{
        //先记录当前的结果
        Person temp = *this;
        //后递增
        age = age+1;
        //返回记录的结果
        return temp;
	}
private:
    int age;
};

ostream& operator<< (ostream& cout, const Person& p)
{
    cout<<"m_A: "<<p.age;
    return cout;
}

int main(int argc, char const *argv[])
{
    /* code */
    Person p(10);
    cout<<p++<<endl;    //10
    cout<<p<<endl;      //11
    cout<<++p<<endl;    //12

    return 0;
}

注意,函数传参必须改成是一个const对象,因为我们返回了一个临时值。

1.4 赋值运算符重载(深拷贝浅拷贝)

可以利用赋值运算符重载解决浅拷贝的问题。

如果下面的代码不用赋值运算符重载,则会报错:double free

#include <iostream>
using namespace std;

class Person
{
public:
    Person()
    {
        m_Age = NULL;
    }
	Person(int age){
		m_Age = new int (age);
	}
	
	int* m_Age;
	
	~Person()
	{
		if(m_Age != NULL)
		{
			delete m_Age;
			m_Age = NULL;
		}
	}
	Person& operator =(Person &p)
	{
		if(m_Age != NULL)
		{
			delete m_Age;
			m_Age = NULL;
		}
		
		m_Age = new int(*p.m_Age);
		return *this;
	}
};

int main()
{
	Person p1(18);    //有参构造
	Person p2;        //无参构造
	p2 = p1;            //运算符重载
	
	cout<<*p1.m_Age<<endl;
	cout<<*p2.m_Age<<endl;
}

2. 前置++和后置++区别

2.1 本质:

self &operator++() {
    node = (linktype)((node).next);
    return *this;
}
const self operator++(int) {
    self tmp = *this;
    ++*this;
    return tmp;
}

为了区分前置和后置,重载函数以参数类型来区分。用int作为参数,编译器会给它指定一个0,说明是后置++。

2.2 常见问题

1. 为什么后置返回对象,而不是引用?

因为后置为了与内置行为一致(返回旧值)而创建了一个临时对象,在函数结束后就会被销毁,所以不能返回引用。

2. 为什么后置前面也要加const?

其实可以不加,但是为了防止使用i++++(使用两次后置++只会把结果累计加一次),所以我们手动禁止其合法化,在前面加const。

3. 最好用哪一种?

最好前置++,因为不会创建临时对象,不会造成额外开销。

3. 深拷贝和浅拷贝

3.1 区别

浅拷贝:1)简单的拷贝构造,或者2)编译器等号直接赋值

深拷贝:堆区重新申请分配空间,再进行拷贝。

3.2 浅拷贝的问题

当类中成员变量是开辟在堆区时:

#include <iostream>
using namespace std;

class Person
{
public:
	Person(int age, int height)
    {
        this->age = age;
        this->height=new int(height);
    }
    ~Person()
    {
        if(this->height != NULL)
        {
            delete this->height;
            this->height = NULL;
        }
    }

    int age;
    int* height;
};

int main(int argc, char const *argv[])
{
    /* code */
    Person p1(10,170);
    Person p2(p1);        //or Person p3 = p1;

    cout<<"p1 age is: "<<p1.age<<endl;
    cout<<"p1 height is: "<<*p1.height<<endl;
    cout<<"p2 age is: "<<p2.age<<endl;
    cout<<"p2 height is: "<<*p2.height<<endl;
    cout<<"p1 height is: "<<*p1.height<<endl;

    return 0;
}

会出现double free的错误,原因如下:

由于p2先调用析构函数,堆区内容被释放;之后p1再调用析构函数,然而此时堆区内容已经被释放了,所以造成double free。

3.3 深拷贝的不同以及如何解决浅拷贝的问题

解决方法:

  1. 自定义拷贝构造函数
  2. 赋值运算符重载
3.3.1 自定义拷贝构造函数
#include <iostream>
using namespace std;

class Person
{
public:
	Person(int age, int height)
    {
        this->age = age;
        this->height=new int(height);
    }

    //自定义拷贝构造
    Person(Person& p)
    {
        this->age = p.age;
        this->height = new int(*p.height); //再开辟一块内存
    }

    ~Person()
    {
        if(this->height != NULL)
        {
            delete this->height;
            this->height = NULL;
        }
    }

    int age;
    int* height;
};

int main(int argc, char const *argv[])
{
    /* code */
    Person p1(10,170);
    Person p2(p1);        //or Person p2 = p1;

    cout<<"p1 age is: "<<p1.age<<endl;
    cout<<"p1 height is: "<<*p1.height<<endl;
    cout<<"p2 age is: "<<p2.age<<endl;
    cout<<"p2 height is: "<<*p2.height<<endl;
    cout<<"p1 height is: "<<*p1.height<<endl;

    return 0;
}

3.3.2 赋值运算符重载

见上面代码。但要注意区分一点:

MyStr str2;

str2 = str1;

MyStr str3 = str2;

是不一样的。前者MyStr str2;是str2的声明加定义,调用无参构造函数。所以str2 = str1;是在str2已经存在的情况下,用str1来为str2赋值,调用的是拷贝赋值运算符重载函数;而后者,是用str2来初始化str3,调用的是拷贝构造函数。

而赋值运算符重载针对的时第一种情况。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值