【C++基础七】类和对象—下(拷贝构造和运算符重载)

1.拷贝构造函数

使用一个已经存在的对象,初始化一个正在定义的对象就是拷贝构造

Date d1(2025,3,13);
Date d2(d1);//用d1初始化d2

拷贝构造特点:

  • 拷贝构造是构造函数的一个重载形式
  • 拷贝构造函数只能有一个参数且必须是类类型对象的引用
class Date
{
    
    
public:
	 Date(int year = 2025, int month = 3, int day = 13)//默认构造函数
	 {
    
    
	 	_year = year;
	 	_month = month;
	 	_day = day;
	 }
	 Date(const Date& d)//拷贝构造函数
     {
    
    
	 	_year = d._year;
	 	_month = d._month;
	 	_day = d._day;
 	 }
private:
	 int _year;
	 int _month;
	 int _day;
};

int main()
{
    
    
	Date d1(1999,9,9);
	Date d2(d1);//拷贝构造
	return 0;
}

2.拷贝构造引用传递的意义

class Date
{
    
    
public:
Date(Date d)//错误的拷贝构造
     {
    
    
	 	_year = d._year;
	 	_month = d._month;
	 	_day = d._day;
 	 }
private:
	int _year;
	int _month;
	int _day;
}; 	 

int main()
{
    
    
	Date d1(1999,9,9);
	Date d2(d1);//拷贝构造
	return 0;
}

类的对象d2实例化需要先调用构造函数,所以需要先传参数,把d1传给拷贝构造中d的这个过程属于传值调用,若两者为内置类型(int ,char ,double)会直接进行拷贝,但d1和d都是自定义类型date,所以会发生拷贝构造,date d(d1)又是一个拷贝构造,又会发生重复上述过程,从而导致无线循环下去

class Date
{
    
    
public:
Date(const Date& d)//拷贝构造函数
{
    
    
	 _year = d._year;
     _month = d._month;
	 _day = d._day;
}
private:
	int _year;
	int _month;
	int _day;
}; 	 

int main()
{
    
    
	Date d1(1999,9,9);
	Date d2(d1);//拷贝构造
	return 0;
}

由于拷贝中的d变成了d1的别名,d相当于d1本身,所以参数d1传给d的过程中, 不会发生拷贝构造

3.默认拷贝构造函数

如果用户不显示写拷贝构造函数,编译器会自动生成一个默认构造函数
默认构造函数会对内置类型完成值拷贝(浅拷贝)
默认构造函数会去调用自定义类型的构造函数完成拷贝

对于内置类型来说,浅拷贝可能会有问题

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 int& data)//插入函数
 {
    
    
	 _array[_size] = data;
	 _size++;
 }
 
 ~Stack()//析构函数
 {
    
    
	 if (_array)
	 {
    
    
		 free(_array);
		 _array = nullptr;
		 _capacity = 0;
		 _size = 0;
	 }
 }
private:
	 int *_array;
	 size_t _size;
	 size_t _capacity;
};

int main()
{
    
    
	Stack s1(10);
	s1.push(1);`在这里插入代码片`
	s1.push(2);
	s3.push(3);

	Stack s2(s1);
	retrn 0;
}

stack类没有显示写拷贝构造函数,编译器会自动生成默认拷贝构造函数
请添加图片描述

默认构造函数是按照字节方式直接拷贝的,array指向的空间是动态开辟在堆区存储的,使用值拷贝的方式,s1和s2中的array指向的空间相同

当s1和s2生命周期结束时,会分别调用它们各自的析构函数,然而两个对象中的指针指向的空间相同,析构函数会调用两个free释放空间,同一份空间释放两次就会出错

对拷贝构造函数的总结:

  • 拷贝构造函数的参数必须是引用
  • 若类中没有涉及到空间申请,则默认拷贝构造就够用了
  • 下面这两种写法都是在拷贝构造:
Date d1(2025,7,30);

Date d2(d1);
Date d3 = d1;

拷贝构造的典型应用场景:

  • 使用已存在对象创建新对象
  • 函数参数类型为自定义类型对象
  • 函数返回值类型为自定义类型对象

后面两种情况实际上是因为在传值和传 返回值时会自动拷贝

4.运算符重载

在日期类中定义一个对象:

Date d1(2023,3,13);
Date d2 = d1 + ![请添加图片描述](https://i-blog.csdnimg.cn/direct/b06d24ff9818499ba4c631e2c494f5a9.jpeg)
100; //普通的加号不能实现此功能

如果想要计算100天后是几月几号,直接使用+无法实现这个功能
普通的加号只适用于内置类型,对于自定义类型需要的有特定功能的运算符需要用户自己写,而自己实现的运算符就是函数重载

C++为了增强代码的可读性,引入了运算符重载
运算符重载是具有特殊函数名的函数,也具有函数名字,返回值类型以及参数列表,其返回值类型和参数列表与普通函数类似

关键字:operator

举例说明实现日期类+号的重载:

Date operator+(Date d1, int x);

请添加图片描述
运算符重载是针对自定义类型的,所以函数参数中必须有一个自定义类型参数
但是,如果运算符重载写在类外,它就不能访问类中的私有成员
所以我们常把运算符重载写在类中

class Date
{
    
     
public:
	Date(int year = 1999, int month = 9, int day = 9)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
}
bool operator==(const Date& d2)//有隐藏的this指针,&d1传过来有this接收
{
    
    
	return _year == d2._year //由于隐藏的this指针的存在,取第一个参数d1的地址传过去被this指针接收,_year等价于d1._year
	&& _month == d2._month 
	&& _day == d2._day;
}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
    
    
	Date d1(2022,12,21);
	Date d2(2022,12,22);
	cout << (d1 == d2) << endl;
	//d1 == d2等价于d1.operator == (d2);
	return 0;
}

5.赋值运算符

赋值运算符十分特殊,若我们不显示写,编译器会自动生成一个
所以赋值运算符只能写在类中,如果显示写在了类外,有可能会和编译器自动生成的一个赋值函数冲突了

class Date
{
    
     
public:
	Date& operator=(const Date& d)//返回值类型是引用,传*this的引用作为变量的别名,相当于传*this本身,不会消耗一块空间,自然在return*this 返回时,不会进入拷贝构造中,而是直接返回
 	{
    
    
 		//为了防止自己给自己赋值的事情发生,如:d1=d1
 		if(this != &d)
 		{
    
    
     		_year = d._year;
     		_month = d._month;
     		_day = d._day;
 		}
 		return *this;//this是类对象(被赋值的)的地址,*this就是类对象本身
 	}
private:
	int _year;
	int _month;
	int _day;
};

赋值运算符返回类对象的原因是:多次连续赋值

Date d1(2025,3,13);
Date d2;
Date d3;
d2 = d3 = d1;//连续赋值
//d3 = d1 等价于 d3.operator=(d1);

6.前置++和后置++

C++为了区分前置和后置++,在后置++的函数中多加一个int类型的参数来区别前置++
虽然后置++多了一个参数,但是这个参数完全没用,只用于区别前置++

前置++:

Date& operator++();

后置++:

Date& operator++(int);

使用:
虽然后置++多了一个参数,但是不需要我们显示传参,编译器会自动帮助我们传参

Date d1(2023,7,31);
//前置++
++d1;
//后置++
d1++;

7.const成员

7.1对象调用const成员函数

将&a传过去由默认构造函数中隐藏的this指针接收, a指针类型为 const date* ,this指针类型为 date* ,将const date * 传给date * 属于权限的放大,报错

class Date
{
    
    
public:
	void print()
	{
    
    
		cout << _a << endl;
	}
private:
	int _a;
};

int main()
{
    
    
	const date a;
	a.print();//权限放大,报错
	return 0;
}

在括号外面加上const,使this指针类型改成 const date*

class Date
{
    
    
public:
	void print()const //this指针类型变为const date*
	{
    
    
		cout << _a << endl;
	}
private:
	int _a;
};

int main()
{
    
    
	const Date a;
	a.print();//正确 权限保持
	return 0;
}

const 在*左右对于指针的影响:

  • const date* p1 : 修饰的是* p1, const在 * 左面 指针所指向的内容只读
  • date const * p2 : 修饰的是* p2,const在* 左面 指针所指向的内容只读
  • date * const p3 : 修饰的是 p3,const在 * 右面,指针本身只读

注意:

class Date
{
    
    
public:
	void print()const //this指针类型变为const date*
	{
    
    
	   _a = 10;//报错
		cout << _a << endl;
	}
private:
	int _a;
};

int main()
{
    
    
	const Date a;
	a.print();
	return 0;
}

若在print函数内部修改成员变量_a的值就会报错,因为_a是this->_a,但是由于 this指针当前类型为 const date *,使this指向的内容只读,所以报错

7.2成员函数调用const成员函数

  1. 在temp1中时,this指针的类型为 date*,进入 temp2后, this指针 转换为 const date *,属于权限缩小 ,可以实现
class Date
{
    
    
public:
	void temp1()
	{
    
    
		temp2();
	}
	void temp2()const
	{
    
    
	}
private:
	int _a;
};

int main()
{
    
    
	Date a;
	a.temp1();
	return 0;
}
  1. temp4中 this指针类型为 const date*,temp3中 this指针类型为 date*,权限放大了,不可以实现
class date
{
    
    
public:
	void temp3()
	{
    
    
		
	}
	void temp4()const
	{
    
    
		temp3();
	}
private:
	int _a;
};

int main()
{
    
    
	date a;
	a.temp4();
	return 0;
}

7.3取地址及const取地址操作符重载

class date
{
    
    
public:
	date* operator&()
	{
    
    
		return this;
	}
	const date* operator&()const
	{
    
    
		return this;
	}
};
int main()
{
    
    
	date d;
	cout << &d << endl;
	return 0;
}