【C++类和对象之拷贝构造、赋值运算符重载】

简介: 【C++类和对象之拷贝构造、赋值运算符重载】

拷贝构造函数

拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。


拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型 对象创建新对象时由编译器自动调用。


特性

拷贝函数也是特殊的成员函数,其特征有:

  1. 拷贝构造函数是构造函数的一个重载形式。
  2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用
class Date {
public:
  void print()
  {
    cout << _year << "-" << _month << "-" << _day << endl;
  }
  Date(int year = 2023, int month = 5, int day = 5)
  {
    _year = year;
    _month = month;
    _day = day;
  }
  Date(const Date& date)
  {
    _year = date._year;
    _month = date._month;
    _day = date._day;
  }
private:
  int _year;
  int _month;
  int _day;
};
int main()
{
  Date d1;
  Date d2(d1);
  d1.print();
  d2.print();
  return 0;
}

6a551cfcbcc64e2d9c7bda4f0fd41ce3.png

如果不传引用就会发生无限调用>

7d4151f677b64675a3605b2ef4e594b7.png


C++中规定自定义类型传值传参会调自动用他的拷贝构造。

class Date {
public:
  void print()
  {
    cout << _year << "-" << _month << "-" << _day << endl;
  }
  Date(int year = 2023, int month = 5, int day = 5)
  {
    _year = year;
    _month = month;
    _day = day;
  }
  Date(const Date& date)
  {
    cout << "Date(const Date& date)" << endl;
    _year = date._year;
    _month = date._month;
    _day = date._day;
  }
private:
  int _year;
  int _month;
  int _day;
};
void func(Date d)
{
}
int main()
{
  Date d1;
  func(d1);
  return 0;
}

5c30cae38000416c906a453be01af1d6.png


可以看到我们调用func函数的时候他自动调用了Date类中的拷贝构造函数。

e0344a0b4f144efe9549081b6dcc9edd.png



  1. 当然编译器会为我们自动检查,假如出现这种问题,程序就会在调用的时候无限调用下去。
    因此我们传惨的时候要用引用或者指针来接收。
  2. 如我们没有自己定义实现,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝


e35791d7ec714814b29f507f5f5be12c.png


4.编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝,如果没有涉及到资源申请的时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝

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

930034886fb04bb096c2c0258156b860.png


  1. 此时上面代码的问题有:
    会析构两次、一个改变会影响另一个
  2. 拷贝构造函数典型调用场景:
    使用已存在对象创建新对象
    函数参数类型为类类型对象
    函数返回值类型为类类型对象

class Date {
public:
    Date(int year, int minute, int day)
    {
        cout << "Date(int,int,int):" << this << endl;
    }
    Date(const Date& d)
    {
        cout << "Date(const Date& d):" << this << endl;
    }
    ~Date()
    {
        cout << "~Date():" << this << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};
Date Test(Date d)
{
    Date temp(d);
    return temp;
}
int main()
{
    Date d1(2022, 1, 13);
    Test(d1);
    return 0;
}


fe0378c1e58d42e2a5bb64ea0e406d3d.png


赋值运算符重载

重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。


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

函数名字为:关键字operator后面接需要重载的运算符符号。

函数原型:返回值类型 operator操作符(参数列表)。


注意:


不能通过连接其他符号来创建新的操作符:比如operator@

重载操作符必须有一个类类型参数

用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义

作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this

.*   ::   sizeof   ?:   .   注意以上5个运算符不能重载。这个经常在笔试选择题中出现。


赋值运算符重载格式

  • 参数类型:const T&,传递引用可以提高传参效率
  • 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  • 检测是否自己给自己赋值
  • 返回*this :要复合连续赋值的含义


class Date
{
public:
  Date(int year = 1900, int month = 1, int day = 1)
  {
    _year = year;
    _month = month;
    _day = day;
  }
  Date(const Date& d)
  {
    _year = d._year;
    _month = d._month;
    _day = d._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;
};

【注意】:赋值运算符只能重载成类的成员函数不能重载成全局函数
其原因是:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的 赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的 成员函数。

08567440ecb84847be6de2669ecdb9cf.png



当用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝

但需要注意的是:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。

我们再来看这段代码>

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;
  s2 = s1;
  return 0;
}


运行上面代码我们可以发现程序崩溃了。

f613f94ab7bd4b92b58e5d9266a4cad6.png


我们来画图解释一下>

8f8f2c3ce1b04c6fb391fd57c5bf8ebe.png



🍀小结🍀

今天我们学习了拷贝构造函数和赋值运算符重载相关的内容相信大家看完有一定的收获。

相关文章
|
24天前
|
编译器 C++
C++之类与对象(完结撒花篇)(上)
C++之类与对象(完结撒花篇)(上)
32 0
|
19天前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
21 4
|
19天前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
18 4
|
20天前
|
存储 编译器 C语言
【C++打怪之路Lv3】-- 类和对象(上)
【C++打怪之路Lv3】-- 类和对象(上)
15 0
|
24天前
|
编译器 C++ 数据库管理
C++之类与对象(完结撒花篇)(下)
C++之类与对象(完结撒花篇)(下)
28 0
|
19天前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
17 1
|
29天前
|
存储 编译器 C++
【C++类和对象(下)】——我与C++的不解之缘(五)
【C++类和对象(下)】——我与C++的不解之缘(五)
|
29天前
|
编译器 C++
【C++类和对象(中)】—— 我与C++的不解之缘(四)
【C++类和对象(中)】—— 我与C++的不解之缘(四)
|
1月前
|
编译器 C语言 C++
C++入门3——类与对象2-2(类的6个默认成员函数)
C++入门3——类与对象2-2(类的6个默认成员函数)
23 3
|
30天前
|
C++
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
51 1