【C++基础(六)】类和对象(中) --拷贝构造,运算符重载-LMLPHP


1. 前言

拷贝构造函数和运算符重载
是类和对象中六大默认成员函数
的其中两个

【C++基础(六)】类和对象(中) --拷贝构造,运算符重载-LMLPHP


2. 拷贝构造函数

我们在写代码的时候会遇见这种场景:

class Date
{
public:
	 Date(int year = 1900, int month = 1, int day = 1)
	 {
	 _year = year;
	 _month = month;
	 _day = day;
	 }
private:
	 int _year;
	 int _month;
	 int _day;
};
int main()
{
	 Date d1(2023,7,30);
	 Date d2(d1);//用d1初始化d2
	 return 0;
}

拷贝构造函数特征:

  • 拷贝构造是构造函数的一个重载形式
  • 拷贝构造函数只能有一个参数且必须
    是类类型对象的引用!
 Date(const Date& d)
 {
	 _year = d._year;
	 _month = d._month;
	 _day = d._day;
 }

2.1 对拷贝构造函数参数的思考

为什么拷贝构造函数的参数
一定要是:类类型对象的引用?


所以是类类型对象可以理解
但是为啥必须传引用?

【C++基础(六)】类和对象(中) --拷贝构造,运算符重载-LMLPHP
假如加上了引用,调用拷贝构造时
传对象的别名进函数,就不会有
形参拷贝一份实参的过程,就不会死递归!


2.2 默认拷贝构造函数

如果用户不显示写拷贝构造函数
编译器会自动生成一个默认构造函数

举个例子:

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);
	s3.push(4);
	s3.push(5);
	Stack s2(s1);
	retrn 0;
}

stack类没有显示写拷贝构造函数
编译器会自动生成默认拷贝构造函数

默认构造函数是按照字节方式直接拷贝的

【C++基础(六)】类和对象(中) --拷贝构造,运算符重载-LMLPHP

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


2.3 对拷贝构造函数的总结

  • 拷贝构造函数的参数必须是引用

  • 若类中没有涉及到空间申请
    则默认拷贝构造就够用了

  • 下面这两种写法都是在拷贝构造:

Date d1(2023,7,30);
Date d2(d1);
Date d3 = d1;

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

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

3. 运算符重载

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

Date d1(2023,7,31);
Date d2 = d1+100; //普通的加号不能实现此功能


要自己实现的运算符需要运算符重载!

使用关键字: operator


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

Date operator+(Date d1, int x);

【C++基础(六)】类和对象(中) --拷贝构造,运算符重载-LMLPHP


3.1 对运算符重载的思考

运算符重载是针对自定义类型的
所以函数参数中必须有一个类类型参数

所以常常将运算符重载写在类中!

比如:

class Date
{ 
public:
 Date(int year = 1900, int month = 1, int day = 1)
 {
        _year = year;
        _month = month;
        _day = day;
 }
 bool operator==(const Date& d2)
 {
	  return _year == d2._year;
	      && _month == d2._month
	      && _day == d2._day;
 }
private:
 int _year;
 int _month;
 int _day;
};

因为类中函数默认有this指针
指针this就代表了此类对象了!


3.2 特殊的赋值运算符

赋值运算符十分特殊
若我们不显示写,编译器会自动生成一个

Date& operator=(const Date& d)
 {
 if(this != &d)
 {
     _year = d._year;
     _month = d._month;
     _day = d._day;
 }
 return *this;
 }

注:this是类对象的地址
*this就是类对象本身


Date d1(2023,7,31);
Date d2;
Date d3;
d2 = d3 = d1;//连续赋值


如果类中未涉及到资源管理
赋值运算符是否实现都可以
一旦涉及到资源管理则必须要实现


3.3 前置++和后置++

对于++的运算符重载比较有争议
因为前置和后置是两种不同的函数!

比如:

前置++:

Date& operator++();

后置++:

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

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


3.4 运算符重载再理解

class Date
{ 
public:
 Date(int year = 1900, int month = 1, int day = 1)
 {
        _year = year;
        _month = month;
        _day = day;
 }
 bool operator==(const Date& d2)
 {
	  return _year == d2._year;
	      && _month == d2._month
	      && _day == d2._day;
 }
 Date& operator=(const Date& d)
 {
	 if(this != &d)
	 {
	     _year = d._year;
	     _month = d._month;
	     _day = d._day;
	 }
	 return *this;
 }
private:
 int _year;
 int _month;
 int _day;
};
Date d1(2023,7,31);
Date d2(2023,7,30);
if(d1==d2)
{
	cout<<"d1和d2相同";
}
Date d3;
d3 = d1;

上面的代码中调用了两个运算符重载函数
operator=operator==

【C++基础(六)】类和对象(中) --拷贝构造,运算符重载-LMLPHP

【C++基础(六)】类和对象(中) --拷贝构造,运算符重载-LMLPHP


4. 总结以及拓展

写运算符重载函数时,尽量使用引用传参

const Date& d

有几个操作符不能被运算符重载:

  • .*
  • ::
  • sizeof
  • ?:
  • .

C++中拷贝构造和赋值运算符容易混淆

Date d1(2023,7,31);
Date d2 = d1;
Date d3;
d3 = d1;

d2=d1是调用了拷贝构造函数
d3=d1是调用了赋值运算符重载

关于更多拷贝构造和赋值的关系

拷贝构造和赋值运算符重载


08-02 11:05