C++:拷贝构造函数和赋值运算符重载

发布于:2024-04-30 ⋅ 阅读:(33) ⋅ 点赞:(0)

目录

一、拷贝构造函数

1.1概念

1.2特征

二、赋值运算符重载

2.1运算符重载

2.2赋值运算符重载

2.2.1赋值运算符重载格式 

2.2.2赋值运算符重载要求

2.2.3默认生成的赋值运算符重载

2.3前置++和后置++重载


一、拷贝构造函数

1.1概念

只有一个形参,这个形参是对类类型对象的引用,在用存在的类类型对象创建时由编译器自动调用。

1.2特征

1.拷贝构造函数是创建与已经存在对象一模一样的新对象。

2.拷贝构造函数一般用const修饰。

3.拷贝构造函数是构造函数的一个重载形式。

4.拷贝构造函数的参数必须是类类型对象的引用,且参数只能有一个。

5.如果用户未定义,编译器会生成默认的拷贝构造函数。默认拷贝构造函数对内置类型对象按内存存储字节序完成浅拷贝,自定义类型是调用其自身的构造函数完成浅拷贝。

class Time
{
public:
	Time()
	{
		_hour = 1;
		_minute = 1;
		_second = 1;
	}
	Time(const Time& t)
	{
		_hour = t._hour;
		_minute = t._minute;
		_second = t._second;
		cout << "Time::Time(const Time&)" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};
int main()
{
	Date d1;
	// 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数
	// 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构
	//造函数
	Date d2(d1);
	return 0;
}

6. 虽然编译器会生成默认的拷贝构造函数,但仅仅会完成浅拷贝,如果拷贝的函数涉及空间申请(如stack),则会出现错误(两个函数会申请到同一块空间,销毁时会出现同一块空间释放2次,导致程序崩溃)。

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 10)
	{
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	void Push(const DataType& data)
		{
			// CheckCapacity();
			_array[_size] = data;
			_size++;
		}
		~Stack()
		{
			if (_array)
			{
				free(_array);
				_array = nullptr;
				_capacity = 0;
				_size = 0;
			}
		}
private:
	DataType* _array;
	size_t _size;
	size_t _capacity;
};
int main()
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);
	Stack s2(s1);
	return 0;
} 

 上述代码中s2和s1申请了同一块空间,释放时导致程序崩溃。  

二、赋值运算符重载

2.1运算符重载

1.C++为了增强代码的可读性,增加了运算符重载的概念,支持自定义函数的操作符运算。

2.运算符重载是一个有着特殊函数名的函数,有返回值,有参数、函数名等。

3.函数名为:operator加上要重载的操作符

4.标准模板:

返回值类型 operator操作符(参数)

: 

1.operator后的操作符不能自己创建,必须是已含的操作符。

	bool operator@(const Date & d2)//非已含的操作符
	{//报错
		return _year == d2._year
		  && _month == d2._month
			&& _day == d2._day;
	}

2.重载操作符必须有一个类类型的参数。

	bool operator==()//参数过少
	{
		return _year == d2._year
		  && _month == d2._month
			&& _day == d2._day;
	}

3.用于内置类型的运算符,不会随运算符重载改变其本身含义。

// 定义一个复数类
class Complex 
{
public:
    // 成员变量:实部和虚部
    double real;
    double imag;

    // 构造函数
    Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) 
    {

    }

    // 重载加法运算符,使得两个Complex对象可以相加
    Complex operator+(const Complex& other) const 
    {
        return Complex(real - other.real, imag - other.imag);//将减法重载成加法
    }

    // 为了方便打印,重载<<运算符
    friend ostream& operator<<(std::ostream& os, const Complex& c) 
    {
        os << c.real << " + " << c.imag << "i";
        return os;
    }
};

int main() 
{
    Complex num1(3, 4);  // 创建复数3 + 4i
    Complex num2(1, -1); // 创建复数1 - 1i

    Complex sum = num1 + num2; // 使用重载的+运算符
    int a = 2;
    int b = 3 + a;
    cout << "Sum: " << sum << endl; // 输出相加结果
    cout << "b: " << b << endl;
    return 0;
}

 

上图中,我故意将减法重载成加法,Sum执行的+是我重载的减法,但b执行的还是加法,说明内置类型的运算符不受重载运算符的影响。 

4.类的成员函数重载时,形参比操作数少1,因为成员函数的第一个参数是隐藏的this(C++:类的对象模型和this指针中有介绍)。

bool operator==( Date & d2)//只有一个参数但有两个操作数
{
	return _year == d2._year
	  && _month == d2._month
		&& _day == d2._day;
}

上面图片的代码在编译器眼中是下图这样 

	bool operator==( Date & d2)
	{
		return this->_year == d2._year
		  && this->_month == d2._month
			&& this->_day == d2._day;
	}

5.   (.*)  (::)   (sizeof)  (?:)  (.)  前面括号内的5个操作符不能重载。   

2.2赋值运算符重载

2.2.1赋值运算符重载格式 

1.参数类型:const T&,传递引用可以提高传参效率。

2.返回值:*this,返回引用可以提高返回的效率,有返回值是为了支持连续的赋值

3.要检验是否会自己给自己赋值

Date& operator=(const Date& d)//参数类型含有const,防止改变参数
{
	if (this != &d)//检验是否会自己给自己赋值
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	return *this;//返回*this方便连续赋值
}

2.2.2赋值运算符重载要求

 赋值运算符只能重载成类的成员函数不能重载成全局函数。用户如果不写赋值运算符重载,编译器会自动生成一个默认的赋值运算符重载,与全局的运算符重载冲突。

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	int _year;
	int _month;
	int _day;
};
Date& operator=(Date& left, const Date& right)//全局函数
{
	if (&left != &right)
	{
	left._year = right._year;
	left._month = right._month;
	left._day = right._day;
	}
	return left;
}

2.2.3默认生成的赋值运算符重载

 编译器默认生成的赋值运算符重载以值的方式逐字节拷贝。对于内置类型成员变量是直接赋值的(浅拷贝),对于自定义类型成员变量需要调用对应类的赋值运算符完成赋值。

: 编译器自动生成的赋值运算符重载是浅拷贝,对于无空间申请的函数可以(如Date),对于有空间申请的函数就不可以(如Stack),必须自己写。

class Time
{
public:
	Time()
	{
		_hour = 1;
		_minute = 1;
		_second = 1;
	}
	Time& operator=(const Time& t)
	{
		if (this != &t)
		{
			_hour = t._hour;
			_minute = t._minute;
			_second = t._second;
		}
		return *this;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};
int main()
{
	Date d1;
	Date d2;
	d1 = d2;
	return 0;
}

2.3前置++和后置++重载

前置++:返回+1之后的结果
后置++:返回+1之前的结果

前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载。
 C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器
自动传递。
 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存
一份,然后给this + 1(存在额外空间的使用)。

故更推荐使用前置++。


网站公告

今日签到

点亮在社区的每一天
去签到