C++类和对象——第二关

[复制链接]
发表于 2026-1-18 02:47:28 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
目次
类的默认成员函数:
(一)构造函数
(二)析构函数
(三)拷贝构造函数


类的默认成员函数:

类内里有6个特殊的成员函数分别包办差异的功能;

(一)构造函数

   分析:C++把范例分成内置范例(根本范例)和⾃界说范例。内置范例就是语⾔提供的原⽣数据范例,如:int/char/double/指针等,⾃界说范例就是我们使⽤class/struct等关键字⾃⼰界说的范例。
  构造函数的特点:
(1)主动调用,在类实例化对象的时间会主动的调用构造函数。
(2)构造函数没有返回值(啥都不写,而不是说写void),名字和当前类的名字雷同。
(3)构造函数可以分为三种,1.没有形参的构造,2.全缺省参数的构造,3.主动天生的构造,编译器默认天生的是无参构造。他们三个只能存在一个。如果类中没有显式界说构造函数,则C++编译器会⾃动⽣成⼀个⽆参的默认构造函数,⼀旦⽤⼾显式界说编译器将不再⽣成。
   由于在调用的时间不须要转达任何的参数,以是构造函数总结下来可以称之为0实参构造函数。
  本身写了构造函数,(编译器不再天生)。
  1. class Date {
  2. private:
  3.         int _year;
  4.         int _month;
  5.         int _day;
  6. public:
  7.         Date(int year = 1999, int month = 01, int day = 01) // 自己定义的全缺省构造函数
  8.         {
  9.                 _year = year;
  10.                 _month = month;
  11.                 _day = day;
  12.         }
  13.         void Print()
  14.         {
  15.                 cout << this->_year << "/" << this->_month << "/" << this->_day << endl;
  16.         }
  17. };
  18. int main()
  19. {
  20.         Date d1(2024, 9, 23);
  21.         Date d2;
  22.         cout << "传参了:" << endl;
  23.         d1.Print();
  24.         cout << "没有传参:" << endl;
  25.         d2.Print();
  26.         return 0;
  27. }
复制代码


去掉本身写的构造函数的时间(利用的是编译器的默认构造):
  1. class Date {
  2. private:
  3.         int _year;
  4.         int _month;
  5.         int _day;
  6. public:
  7.         //Date(int year = 1999, int month = 01, int day = 01) // 自己定义的全缺省构造函数
  8.         //{
  9.         //        _year = year;
  10.         //        _month = month;
  11.         //        _day = day;
  12.         //}
  13.         void Print()
  14.         {
  15.                 cout << this->_year << "/" << this->_month << "/" << this->_day << endl;
  16.         }
  17. };
  18. int main()
  19. {
  20.         //Date d1(2024, 9, 23);
  21.         Date d1;
  22.         Date d2;
  23.         cout << "传参了:" << endl;
  24.         d1.Print();
  25.         cout << "没有传参:" << endl;
  26.         d2.Print();
  27.         return 0;
  28. }
复制代码

这里可以看到,编译器初始化成员变量是随机值。
   由于c++没有明白规定构造函数的初始化规则,以是差异的编译器会有差异的初始化规则,在vs上大概会初始化为随机值,但是在其他编译器上大概就不一样,构造函数中编译器对内置范例是没有确定处理惩罚的。
  同样一段代码,我们放到devc++中实验的结果:编译器的初始化结果和vs是不一样的。

tips:正由于默认的构造函数初始化打击不确定的,以是大多数构造函数都是须要本身写的。
ps:想要redpanda dev编译器的可以私我给发。
(4)构造函数支持函数重载
我们可以写着两个构造函数,无参构造和全缺省构造函数,但是会产生调用歧义。
  1. class Date {
  2. private:
  3.         int _year;
  4.         int _month;
  5.         int _day;
  6. public:
  7.         Date(int year = 1999, int month = 01, int day = 01) // 自己定义的全缺省构造函数
  8.         {
  9.                 _year = year;
  10.                 _month = month;
  11.                 _day = day;
  12.         }
  13.          
  14.        
  15.         // 自己定义的无参数的默认构造函数
  16.         //Date()
  17.         //{
  18.         //        _year = 1999;
  19.         //        _month = 07;
  20.         //        _day = 12;
  21.         //}
  22.         // 自己定义的带参默认构造
  23.         //Date(int year, int month, int day)
  24.         //{
  25.         //        _year = year;
  26.         //        _month = month;
  27.         //        _day = day;
  28.         //}
  29.         void Print()
  30.         {
  31.                 cout << this->_year << "/" << this->_month << "/" << this->_day << endl;
  32.         }
  33. };
  34. int main()
  35. {
  36.         Date d1;
  37.         Date d2(2024,2,3);
  38.         cout << "传参了:" << endl;
  39.         cout << "没有传参:" << endl;
  40.         d2.Print();
  41.         return 0;
  42. }
复制代码
尚有就是:在创建对象调用的时间,是无参构作育不须要给对象传参了,是有参数构造的必须要传参,否则编译器会报错的。
(5)为了更深条理的明白构造函数,我们利用C++来实现一个栈的初始化和压栈利用:
  1. #include <stdlib.h>
  2. #include <iostream>
  3. using namespace std;
  4. class stack {
  5. private:
  6.         int _top;// 栈顶
  7.         int _capacity; // 数组空间的大小
  8.         int* _arr; // 使用动态数组来实现栈
  9. public:
  10.         stack(int n = 4) // 实现构造函数,和init函数差不多
  11.         {
  12.                 _top = 0;
  13.                 _capacity = 4;
  14.                 _arr = (int*)malloc(sizeof(int) * _capacity);//先预开辟4*4 = 16个字节的空间
  15.         }
  16.         // 压栈入数据
  17.         void push(int x)
  18.         {
  19.                 // 判断栈是否满了
  20.                 if (_top == _capacity) // 如果栈满了,就重新开空间
  21.                 {
  22.                         int newcapacity = _capacity * 2;
  23.                         int* tmp = (int*)realloc(_arr, sizeof(int) * newcapacity);
  24.                         if (tmp == nullptr)
  25.                         {
  26.                                 cout << "realloc fail!" << endl;
  27.                                 return;
  28.                         }
  29.                         _capacity = newcapacity;
  30.                         _arr = tmp;
  31.                 }
  32.                 _arr[_top] = x;
  33.                 _top++;
  34.         }
  35. };
  36. int main()
  37. {
  38.         stack st;
  39.         st.push(1);
  40.         st.push(2);
  41.         st.push(3);
  42.         st.push(4);
  43.         st.push(5);
  44.         return 0;
  45. }
复制代码
可以看到我们写的栈的初始化函数就是构造函数我们没有手动调用,它在步伐实验过程中主动调用了。




(二)析构函数

和构造函数相反对应的是析构函数,它们可以说是相辅相成。
   C++规定对象在烧毁时会⾃动调⽤析构函数,完成对象中资源的清算开释⼯作。
  析构函数的特性:
(1)函数名和对象名一样,没有返回值,没有参数,界说时在函数名前面加~:
(2)同构造函数一样,一个类只能存在一个析构函数,构造函数在创建对象时调用,析构函数在对象生命周期竣事的时间调用。
  1. class Date {
  2. private:
  3.         int _year;
  4.         int _month;
  5.         int _day;
  6. public:
  7.         Date(int year = 1999, int month = 01, int day = 01) // 自己定义的全缺省构造函数
  8.         {
  9.                 _year = year;
  10.                 _month = month;
  11.                 _day = day;
  12.         }
  13.        
  14.         // 自己写的析构函数
  15.         ~Date()
  16.         {
  17.                 cout << "这里调用了析构函数:~Date()" << endl;
  18.         }
  19.         void Print()
  20.         {
  21.                 cout << this->_year << "/" << this->_month << "/" << this->_day << endl;
  22.         }
  23. };
  24. int main()
  25. {
  26.         Date d1;
  27.         // 这里定义的两个对象没有显示的调用析构函数如:d1.Date().
  28.         Date d2;
  29.         return 0;
  30. }
复制代码
step1:d1和d2两个对象当代码实验到return 0;这句代码的时间生命周期竣事,我们在这句代码处打一个断点举行调试。

step2:我们接下来对代码举行渐渐调试,发当代码直接跳到了我们刚本身实现的析构函数的位置

step3:步伐继承实验,实验完析构函数内的步伐代码之后打印竣事,步伐竣事。

在调用析构函数的时间,有几个对象就析构频频,可以看到我们创建了两个析构函数,打印了两次。由于在main的函数栈帧中,实验规则和数据结构的栈的实验规则差不多,都是后进先出,以是在析构多个对象的时间,遵照先界说的后析构,也可以明白为生命周期先竣事的末了析构。
(3)跟构造函数雷同,编译器默认⽣成的析构函数对内置范例成员不做处理惩罚,自界说范例成员会调⽤他的析构函数。
(4)还须要留意的是我们显⽰写析构函数,对于⾃界说范例成员也会调⽤他的析构,也就是说⾃界说范例成员⽆论什么情况都会⾃动调⽤析构函数
   好比说:我们界说了一个类,内里存在别的一个类实例化的对象,我们在另一个类中实现了析构函数,在界说这个类实例化的对象的时间,这个实例化的对象生命周期竣事的时间会去调用另一个类的析构函数。
  (5)没有申请资源时,析构函数可以不写,有资源申请时,⼀定要⾃⼰写析构,否则会造成资源走漏。
        在上面我面我们界说了一个栈的类做构造函数的例子,我们在写的时间申请了空间,但是我们没有将空间开释掉,也没有写析构函数,而编译器本身默认天生的析构函数只会清算内置范例的空间,以是实在已经造成了内存走漏了,终极泄漏的这块内存由利用体系采取。
        我们给它加上析构函数将开发在堆上的空间开释掉。
  1. #include <cstdlib>
  2. #include <stdlib.h>
  3. #include <iostream>
  4. using namespace std;
  5. class stack {
  6. private:
  7.         int _top;// 栈顶
  8.         int _capacity; // 数组空间的大小
  9.         int* _arr; // 使用动态数组来实现栈
  10. public:
  11.         stack(int n = 4) // 实现构造函数,和init函数差不多
  12.         {
  13.                 _top = 0;
  14.                 _capacity = 4;
  15.                 _arr = (int*)malloc(sizeof(int) * _capacity);//先预开辟4*4 = 16个字节的空间
  16.         }
  17.         // 压栈入数据
  18.         void push(int x)
  19.         {
  20.                 // 判断栈是否满了
  21.                 if (_top == _capacity) // 如果栈满了,就重新开空间
  22.                 {
  23.                         int newcapacity = _capacity * 2;
  24.                         int* tmp = (int*)realloc(_arr, sizeof(int) * newcapacity);
  25.                         if (tmp == nullptr)
  26.                         {
  27.                                 cout << "realloc fail!" << endl;
  28.                                 return;
  29.                         }
  30.                         _capacity = newcapacity;
  31.                         _arr = tmp;
  32.                 }
  33.                 _arr[_top] = x;
  34.                 _top++;
  35.         }
  36.         // 自己写的析构函数
  37.         ~stack()
  38.         {
  39.                 free(_arr);
  40.                 _arr = nullptr;
  41.                 _capacity = _top = 0;
  42.         }
  43. };
  44. int main()
  45. {
  46.         stack st;
  47.         return 0;
  48. }
复制代码
固然,如果可以也可以在析构函数内里申请空间,除非你有病。
(三)拷贝构造函数

须要明白的几个点:
(1)拷贝构造是构造函数的重载,他和构造函数一样,都是为了初始化一个对象。构造函数是初始化当前对象,拷贝构造是利用另一个函数来初始化当前对象。
   (2)拷⻉构造函数的第⼀个参数必须是类范例对象的引⽤,使⽤传值⽅式编译器直接报错,由于语法逻辑上会引发⽆穷递归调⽤。拷⻉构造函数也可以多个参数,但是第⼀个参数必须是类范例对象的引⽤,后⾯的参数必须有缺省值。
  

假设不是类范例的对象引用——>引发无穷递归:
  1. class Date
  2. {
  3. public:
  4.         Date(int year = 1, int month = 1, int day = 1)
  5.         {
  6.                 _year = year;
  7.                 _month = month;
  8.                 _day = day;
  9.         }
  10.                 // 编译报错:error C2652 : “Date”: ⾮法的复制构造函数: 第⼀个参数不应是“Date”
  11.                 //Date(Date d)
  12.                 Date(const Date & d)
  13.                 {
  14.                         _year = d._year;
  15.                         _month = d._month;
  16.                         _day = d._day;
  17.                 }
  18.                 Date(Date  d) // 这里语法上是不允许的,只是为了举例。
  19.                 {
  20.                         _year = d._year;
  21.                         _month = d._month;
  22.                         _day = d._day;
  23.                 }
  24.                 void Print()
  25.                 {
  26.                         cout << _year << "-" << _month << "-" << _day << endl;
  27.                 }
  28. private:
  29.         int _year;
  30.         int _month;
  31.         int _day;
  32. };
  33. int main()
  34. {
  35.         Date d1;
  36.         Date d2(d1);
  37.         return 0;
  38. }
复制代码

tips:为了制止发生被拷贝对象成员被修改的情况,在拷贝构造函数的参数前面加一个const限定。如在上面的日期类的拷贝构造函数中:
  1. Date(const Date&  d) //这里加一个const修饰能够避免源对象(d1)被修改
  2. {
  3.     _year = d._year;
  4.     _month = d._month;
  5.         _day = d._day;
  6. }
  7. int main()
  8. {
  9.         Date d1;
  10.         Date d2(d1);
  11.         return 0;
  12. }
复制代码
  (3)C++规定⾃界说范例对象进⾏拷⻉⾏为必须调⽤拷⻉构造,⾃界说范例传值传参和传值返回都会调⽤拷⻉构造完成
  1. class Date
  2. {
  3. public:
  4.         Date(int year = 1, int month = 1, int day = 1)
  5.         {
  6.                 _year = year;
  7.                 _month = month;
  8.                 _day = day;
  9.         }
  10.                 Date(const Date&  d) //这里加一个const修饰能够避免源对象
  11.                 {
  12.                         _year = d._year;
  13.                         _month = d._month;
  14.                         _day = d._day;
  15.                 }
  16.                 void Print()
  17.                 {
  18.                         cout << _year << "-" << _month << "-" << _day << endl;
  19.                 }
  20. private:
  21.         int _year;
  22.         int _month;
  23.         int _day;
  24. };
  25. void func(Date d)
  26. {
  27.         d.Print();
  28. }
  29. int main()
  30. {
  31.         Date d1;
  32.         Date d2(d1);
  33.         func(d1); // 这里传值传参会调用拷贝构造
  34.         return 0;
  35. }
复制代码
对代码举行调试运行过程如图:


   (4)若未显式界说拷⻉构造,编译器会⽣成⾃动⽣成拷⻉构造函数。⾃动⽣成的拷⻉构造对内置范例成员变量会完成值拷⻉/浅拷⻉(⼀个字节⼀个字节的拷⻉)雷同于memcpy函数,对⾃界说范例成员变量会调⽤他的拷⻉构造
  (5)如果在界说对象(类)的时间产生了资源的申请,就须要本身写拷贝构造函数,同析构函数一样,是否有资源的申请决定是否须要本身写拷贝构造函数,一样平常须要本身写析构函数的话就须要本身写拷贝构造函数。
(4)(5)两点且看下面代码:
  1. class stack {
  2. private:
  3.         int _top;// 栈顶
  4.         int _capacity; // 数组空间的大小
  5.         int* _arr; // 使用动态数组来实现栈
  6. public:
  7.         stack(int n = 4) // 实现构造函数,和init函数差不多
  8.         {
  9.                 _top = 0;
  10.                 _capacity = 4;
  11.                 _arr = (int*)malloc(sizeof(int) * _capacity);//先预开辟4*4 = 16个字节的空间
  12.         }
  13.         // 压栈入数据
  14.         void push(int x)
  15.         {
  16.                 // 判断栈是否满了
  17.                 if (_top == _capacity) // 如果栈满了,就重新开空间
  18.                 {
  19.                         int newcapacity = _capacity * 2;
  20.                         int* tmp = (int*)realloc(_arr, sizeof(int) * newcapacity);
  21.                         if (tmp == nullptr)
  22.                         {
  23.                                 cout << "realloc fail!" << endl;
  24.                                 return;
  25.                         }
  26.                         _capacity = newcapacity;
  27.                         _arr = tmp;
  28.                 }
  29.                 _arr[_top] = x;
  30.                 _top++;
  31.         }
  32.         // 自己写的析构函数
  33.         ~stack()
  34.         {
  35.                 free(_arr);
  36.                 _arr = nullptr;
  37.                 _capacity = _top = 0;
  38.         }
  39. };
  40. int main()
  41. {
  42.         stack st1;
  43.         stack st2(st1);//使用st1来初始化st2
  44.         return 0;
  45. }
复制代码
当我们实验这段代码的时间会发生下列情况:

步伐挂掉了,为什么?
我们来调试看一下:

对于这种情况我们就须要本身写一个拷贝构造函数了。
  1. class stack {
  2. private:
  3.         int _top;// 栈顶
  4.         int _capacity; // 数组空间的大小
  5.         int* _arr; // 使用动态数组来实现栈
  6. public:
  7.         stack(int n = 4) // 实现构造函数,和init函数差不多
  8.         {
  9.                 _top = 0;
  10.                 _capacity = 4;
  11.                 _arr = (int*)malloc(sizeof(int) * _capacity);//先预开辟4*4 = 16个字节的空间
  12.         }
  13.         // 压栈入数据
  14.         void push(int x)
  15.         {
  16.                 // 判断栈是否满了
  17.                 if (_top == _capacity) // 如果栈满了,就重新开空间
  18.                 {
  19.                         int newcapacity = _capacity * 2;
  20.                         int* tmp = (int*)realloc(_arr, sizeof(int) * newcapacity);
  21.                         if (tmp == nullptr)
  22.                         {
  23.                                 cout << "realloc fail!" << endl;
  24.                                 return;
  25.                         }
  26.                         _capacity = newcapacity;
  27.                         _arr = tmp;
  28.                 }
  29.                 _arr[_top] = x;
  30.                 _top++;
  31.         }
  32.         // 自己写的析构函数
  33.         ~stack()
  34.         {
  35.                 free(_arr);
  36.                 _arr = nullptr;
  37.                 _capacity = _top = 0;
  38.         }
  39.         // 自己写的拷贝构造函数
  40.         stack(const stack& st)
  41.         {
  42.                 _capacity = st._capacity;
  43.                 _top = st._top;
  44.                 int new_capacity = st._capacity;
  45.                 int* tmp = (int*)malloc(sizeof(int) * new_capacity);//重新申请一片空间
  46.                 if (tmp == nullptr)
  47.                 {
  48.                         perror("malloc fail!");
  49.                         return;
  50.                 }
  51.                 _arr = tmp;
  52.         }
  53. };
复制代码
实验代码重新调试看一下:

地点不一样了,我们重新申请了空间。
        在上面的代码调试过程中,我们发现利用编译器默认天生的拷贝构造函数不会申请空间,它会一个个字节的拷贝,拷贝动态数组的时间它拷贝的值动态数组指针变量的地点,并没有申请空间这一利用,我们将这种拷贝方式称为浅拷贝。而我们本身实现的拷贝构造函数本身申请了空间,我们将这种拷贝称为深拷贝。
        也可以利用等号的方式调用拷贝构造函数初始化对象,在承接上面的代码,我们界说一个新的对象,如:
  1. int main()
  2. {
  3.         stack st1;
  4.         stack st2(st1);//使用st1来初始化st2
  5.         // 也可以使用等号的方式调用拷贝构造函数初始化对象
  6.         stack st3 = st1;
  7.         return 0;
  8. }
复制代码
如许写不会报错。
   (6) 传值返回会产⽣⼀个暂时对象调⽤拷⻉构造,产生拷贝,传值引⽤返回,返回的是返回对象的别名(引⽤),没有产⽣拷⻉。但是如果返回对象是⼀个当前函数局部域的局部对象,函数竣事就烧毁了,那么使⽤引⽤返回是有题目的,这时的引⽤相称于⼀个野引⽤,雷同⼀个野指针⼀样。传引⽤返回可以镌汰拷⻉,但是⼀定要确保返回对象,在当前函数竣过后还在,才气⽤引⽤返回。
  不能返回暂时对象,返回暂时对象是会烧毁的,内里的各种指针就成了野指针,返回的是一个被烧毁了的空间,以是引用这时间是这块被烧毁的空间的别名。
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表