关于我们

质量为本、客户为根、勇于拼搏、务实创新

< 返回新闻公共列表

【C++】类和对象(中)(万字详解)(二)

发布时间:2023-06-29 12:00:56


4.拷贝构造函数

1.概念

首先要知道,拷贝构造函数也是构造函数的重载。


那在创建对象时,可否创建一个与已存在对象一模一样的新对象呢?


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


举例:日期类:

class Date { public:  //构造函数  Date(int year = 1900, int month = 1, int day = 1)  {  _year = year;  _month = month;  _day = day;  }   void print()  {  cout << _year << " " << _month << " " << _day << endl;  } private:  int _year;  int _month;  int _day; };  int main() {  Date d1(2022, 10, 10);  Date d2(d1); //拷贝d1  d1.print();  d2.print();  return 0; }

   

2.特性

拷贝构造函数也是特殊的成员函数,其特征如下:

1. 拷贝构造函数是构造函数的一个重载形式。函数名相同,参数不同。

2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。

发生递归的原因:

Date(Date d)  //去掉引用,就是传值调用

{

   _year = d._year;

   _month = d._month;

   _day = d._day;

}

传值调用:当调用拷贝构造函数时,形参是实参的一份临时拷贝

调用拷贝构造Date d2(d1);,先得传参,传参调用完拷贝构造以后,本身又要调用拷贝构造,而后又要传参,然后一直自己调用自己,导致一直递归下去......

那么传地址可不可以?可以,但不就麻烦了吗?

Date(Date *d)  //去掉引用,就是传值调用

{

   _year = d->_year;

   _month = d->_month;

   _day = d->_day;

}


所以还是引用是最好的方式!


3.拷贝构造函数只有单个形参,该形参是对本类类型对象的引用,一般常用const修饰


为什么要加const呢?


Date(const  Date & d)  //去掉引用,就是传值调用

{

   _year = d._year;

   d._month=_month ; //有时候会写反,而改变了原本对象的值。

   _day = d._day;

}


Date d2(d1); //拷贝d1


所以加了const以后,就不能改变d1原对象了


4. 若未显示定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按

字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定

义类型是调用其拷贝构造函数完成拷贝的。所以内置类型处理,自定义类型不处理,自定义类型可能复杂多变,需要自己写拷贝函数,这样就叫 深拷贝。

浅拷贝: 自己没有写,使用默认的  (日期类)

class Date { public:  //构造函数  Date(int year = 1900, int month = 1, int day = 1)  {  cout << "构造函数" << endl; //验证调用所需  _year = year;  _month = month;  _day = day;  }  private:  int _year;  int _month;  int _day; };  int main() {  Date d1(2022, 10, 10);  Date d2(d1); //拷贝d1  return 0; }

   

是可以实现我们想要的功能的,所以不需要我们自己写。

举例:(栈类)

class Stack { public:  Stack(int capacity = 4)  {  cout << "Stack(int capacity = 4)" << endl;   _a = (int*)malloc(sizeof(int)*capacity);  if (_a == nullptr)  {  perror("malloc fail");  exit(-1);  }   _top = 0;  _capacity = capacity;  }  //析构函数  ~Stack()  {  cout << "~Stack()" << endl;   free(_a);  _a = nullptr;  _top = _capacity = 0;  }   void Push(int x)  {  // ....  // 扩容  _a[_top++] = x;  }  private:  int* _a;  int _top;  int _capacity; };  int main() {  Stack s1;  s1.Push(1);  s1.Push(2);  Stack s2(s1); }

   

当使用默认拷贝函数时,是否能实现我们想要的需求:

 这是怎么了??编译器自己提供的默认拷贝构造函数居然把s1拷贝到s2类中时,两个类中的指针变量指向了同一块空间!!


那么,栈的特性就是先进后出,所以s2先调用析构函数,把空间清理了,然后s1再去调用析构函数,又去清理,这不是瞎搞嘛,造成内存重复释放!

所以默认的拷贝构造函数解决不了我们的需求,需要我们自己写。

//st2(st1)拷贝构造  Stack(const Stack& st)  {  cout << "Stack(const Stack& st)" << endl;   _a = (int*)malloc(sizeof(int)*st._capacity);  if (_a == nullptr)  {  perror("malloc fail");  exit(-1);  }  memcpy(_a, st._a, sizeof(int)*st._top);  _top = st._top;  _capacity = st._capacity;  }

   

 即解决了指针重复指向一块空间,又把已经创建好的s1对象和其中的内容都拷贝到了s2。


总结:需要写析构函数的类,都需要写深拷贝的拷贝构造,如:Stack


不需要写析构函数的类,默认生成的浅拷贝的拷贝构造就可以用 。如:Date/MyQueue


类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请

时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

5.运算符重载

1.为什么要重载运算符

为了让自定义类型可以使用 运算符


对于日期类,我们会经常用到,某个日期加多少天来得到另一个日期。


所以日期需要加减,也需要比较,那么使用+  =   -  ,在C++中,就需要运算符重载,与函数重载没有关系。


2.赋值运算符重载格式

返回值+operator+运算符 +(所需参数),参数个数就是参与运算符的参数个数

class Data { public:  Data(int year=1,int month=1,int day=1)  {  _year = year;  _month = month;  _day = day;  } private:  int _year;  int _month;  int _day; };  bool operator==(Data& d1,Data& d2)  {  return d1._year == d2._year  && d1._month == d2._month  && d1._day == d2._day;  }  int main() {  Data d1(2022, 10, 11);  Data d2(2023, 1, 1);  d1==d2;//可以显式调用 operator==(d1,d2);  cout << (d1 == d2) << endl;  cout << (d1 > d2) << endl; }

   

1.定义运算符重载时,参数为引用的好处是,不需要调用拷贝函数,若是传值,参数为对象,则需要拷贝对象,调用拷贝函数。


2.定义在类外,需要将类的成员变量设置为公开,会造正麻烦,那么我们直接就定义到了类的内部。


3.若直接放入类的内部定义运算符重载,则会出现问题:

class Data { public:  Data(int year=1,int month=1,int day=1)  {  _year = year;  _month = month;  _day = day;  }   bool operator==(Data& d)  {  return this->_year == d2._year  && this->_month == d2._month  && this->_day == d2._day;  }  private:  int _year;  int _month;  int _day; };   int main() {  Data d1(2022, 10, 11);  Data d2(2023, 1, 1);  d1==d2;//可以显式调用 operator==(d1,d2);  cout << (d1 == d2) << endl;  cout << (d1 > d2) << endl; }

   

那为什么在类中,显示的是一个参数呢?

     bool operator==(Data& d)

   {

       return this->_year == d2._year

           && this->_month == d2._month

           && this->_day == d2._day;

   }



/template/Home/leiyu/PC/Static