• C++基础——默认成员函数讲解1


    目录

    一.类的六大特殊成员函数

    二.构造函数

    1.定义:

    2.其特征如下:

    3.构造函数的玩法1: 

    玩法2:提供多种构造函数,完成多种初始化

     玩法3:二义性调用

    玩法4:构造函数不仅能够完成对象成员变量的初始化,还可以做判断功能:

    4.C++实现栈的好处:

    C语言实现栈代码(部分):

    C++实现: 

    5.默认构造函数

    三.析构函数

    1.定义:

    2 特性:

    3.析构练习: 

    4.默认析构函数



    一.类的六大特殊成员函数

    如果一个类中什么成员都没有,简称为空类。

    空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员 函数。

    默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

    二.构造函数

    1.定义:

            构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任 务并不是开空间创建对象,而是初始化对象。

    2.其特征如下:

    1. 函数名与类名相同。

    2. 无返回值。

    3. 对象实例化时编译器自动调用对应的构造函数。

    4. 构造函数可以重载。

    3.构造函数的玩法1: 

    1. class Date
    2. {
    3. public:
    4. //构造函数——与类同名,且没有返回值
    5. Date(int year, int month, int day)
    6. {
    7. _year = year;
    8. _month = month;
    9. _day = day;
    10. }
    11. void Print() {
    12. cout << _year<<"-"<< _month << "-" << _day << endl;
    13. }
    14. private:
    15. int _year;
    16. int _month;
    17. int _day;
    18. };
    19. int main(){
    20. Date d1(2015, 1, 1);
    21. Date d2(202291);
    22. d1.Print();
    23. d2.Print();
    24. }

            当Date类创建d1,d2对象时,会自动调用构造函数,实参2015传给形参year,1传给month,1传给day......完成d1,d2对象的成员变量初始化赋值。 

    玩法2:提供多种构造函数,完成多种初始化

    1. class Date
    2. {
    3. public:
    4. //构造函数
    5. Date(int year, int month, int day)
    6. {
    7. _year = year;
    8. _month = month;
    9. _day = day;
    10. }
    11. //无参构造函数
    12. Date() {
    13. _year = 1999;
    14. _month = 10;
    15. _day = 13;
    16. }
    17. void Print() {
    18. cout << _year << "-" << _month << "-" << _day << endl;
    19. }
    20. private:
    21. int _year;
    22. int _month;
    23. int _day;
    24. };
    25. int main() {
    26. Date d1(2015, 1, 1);
    27. Date d2(2022, 9, 1);
    28. d1.Print();
    29. d2.Print();
    30. Date d3;
    31. Date d4;
    32. d3.Print();
    33. d4.Print();
    34. return 0;
    35. }

           注:创建d3,d4对象时,不带参数所以自动调用无参构造函数Date() 

     玩法3:二义性调用

    1. class Date
    2. {
    3. public:
    4. //全缺省构造函数
    5. Date(int year=1, int month=1, int day=1)
    6. {
    7. _year = year;
    8. _month = month;
    9. _day = day;
    10. }
    11. //无参构造函数
    12. Date() {
    13. _year = 1999;
    14. _month = 10;
    15. _day = 13;
    16. }
    17. void Print() {
    18. cout << _year << "-" << _month << "-" << _day << endl;
    19. }
    20. private:
    21. int _year;
    22. int _month;
    23. int _day;
    24. };
    25. int main() {
    26. Date d5;
    27. Date d6;
    28. d5.Print();
    29. d6.Print();
    30. return 0;
    31. }

            产生错误的原因:会引发二义性对于不带参数的对象,既可以调用全缺省构造,也可以调用无参构造,让编译器为难,报错对于这两种构造函数,可以同时存在,没有语法错误,但会产生歧义,不知道调用哪个。 

    玩法4:

    构造函数不仅能够完成对象成员变量的初始化,还可以做判断功能:

    1. class Date {
    2. public:
    3. Date(int year = 1199, int month = 12, int day = 15) {
    4. //可以对对象的实参做判断功能
    5. if(!(year >= 1 && (month >= 1 && month <= 12) && (day >= 1 && day <=
    6. GetMonthDay(year, month)))) {
    7. cout << "该日期为非法日期" << endl;
    8. }
    9. else {
    10. cout << "该日期为合法日期" << endl;
    11. }
    12. _year = year;
    13. _month = month;
    14. _day = day;
    15. cout << _year << "-" << _month << "-" << _day << endl;
    16. }
    17. //获得月份天数
    18. int GetMonthDay(int year, int month) {
    19. int MonthDay[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
    20. if ((month == 2) && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))) {
    21. return 29;
    22. }
    23. else {
    24. return MonthDay[month];
    25. }
    26. }
    27. private:
    28. int _year;
    29. int _month;
    30. int _day;
    31. };
    32. int main() {
    33. Date d1(2022, 2, 40);
    34. Date d2(-1999, 15, 30);
    35. Date d3(2022, 10, 15);
    36. }

    4.C++实现栈的好处:

    C语言实现栈代码(部分):

    1. #include
    2. #include
    3. typedef struct Stack
    4. {
    5. int* array;
    6. int capacity;
    7. int size;
    8. }Stack;
    9. //初始化函数
    10. void StackInit(Stack* ps,int capacity)
    11. {
    12. ps->array = (int*)malloc(sizeof(int) * 4);
    13. if (NULL == ps->array)
    14. {
    15. perror("malloc fail");
    16. return;
    17. }
    18. ps->capacity = 4;
    19. ps->size = 0;
    20. }
    21. //插入数据函数
    22. void StackPush(Stack* ps, int data)
    23. {
    24. //...扩容
    25. ps->array[ps->size] = data;
    26. ps->size++;
    27. }
    28. int main(){
    29. Stack st;
    30. StackInit(&st,4);
    31. StackPush(&st,1);
    32. StackPush(&st,2);
    33. StackPush(&st,3);
    34. return 0;
    35. }

    C++实现: 

    1. class Stack{
    2. public:
    3. //构造函数
    4. Stack(int capa=4){
    5. _array = (int*)malloc(sizeof(int) * capa);
    6. if (NULL == _array)
    7. {
    8. perror("malloc fail");
    9. return;
    10. _capacity = 4;
    11. _size = 0;
    12. }
    13. void StackPush(int data){
    14. //...扩容
    15. _array[_size] = data;
    16. _size++;
    17. }
    18. private:
    19. int* _array;
    20. int _capacity;
    21. int _size;
    22. };
    23. int main(){
    24. Stack st; //创建对象后自动调用构造函数——完成初始胡操作
    25. StackPush(1);
    26. StackPush(2);
    27. StackPush(3);
    28. return 0;
    29. }

            总结:C++就是针对于我们可能在实现代码的过程中忘记去写初始化构造函数,从而研发出构造函数,在后面介绍的析构函数也是如此。

    5.默认构造函数

            如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦 用户显式定义编译器将不再生成。

            默认构造函数有三种形式:

    1、无参构造(没有形参的构造函数)称之为默认构造函数

    2、全缺省构造(形参都有缺省值)也称之为默认构造函数

    3、程序员自己不写构造,编译器自动生成的默认构造函数

    重点介绍一下编译器自己生成的:

    1. class Date
    2. {
    3. public:
    4. //没有写构造函数
    5. void Print()
    6. {
    7. cout << _year << "-" << _month << "-" << _day << endl;
    8. }
    9. private:
    10. int _year;
    11. int _month;
    12. int _day;
    13. };
    14. int main()
    15. {
    16. Date d1;
    17. return 0;
    18. }

            当创建d1对象时,自动调用构造函数,因为我们没有亲自去写构造函数,所以编译器自动生成一个默认的无参构造函数,这种构造函数既不会让其成员变量置成零,也不会做其他任何事情,它生成的构造函数只为确保程序能够正常运行,赋值的事情不归它管,该类中的成员变量值经过构造后会变成随机值。 

            有的小伙伴会问了,系统生成的默认构造函数没什么用,毕竟这些成员变量并不能被赋我们想要的具体的值,最终还是得要我们自己去写它,那么创造出这个机制的意义在哪里?

            其实C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:int/char...,而自定义类型就是我们使用class/struct/union等自己定义的类型。对于内置类型的成员变量,编译器不会管,所以成员变量成为随机值(让其自生自灭),对于自定义类型的成员变量,编译器会调用其自定义类型的构造函数,也就是说编译器只处理自定义类型的成员变量。如下代码:

    案例1:

    1. class Time{
    2. public:
    3. //构造函数
    4. Time(){
    5. cout << "Time()构造函数" << endl;
    6. _hour = 0;
    7. _minute = 0;
    8. _second = 0;
    9. }
    10. private:
    11. int _hour;
    12. int _minute;
    13. int _second;
    14. };
    15. class Date{
    16. private:
    17. // 基本类型(内置类型)
    18. int _year;
    19. int _month;
    20. int _day;
    21. // 自定义类型
    22. Time _t;
    23. };
    24. int main(){
    25. Date d;
    26. return 0;
    27. }

    运行结果: 

            解析:_year,_month,_day都是随机值,而Time _t这个变量是class Time自定义类型,编译器会走到Time类中,调用该类的构造函数。 

    案例2:

    1. typedef int DataType;
    2. class Stack
    3. {
    4. public:
    5. Stack(size_t capa = 4)
    6. {
    7. _array = (DataType*)malloc(sizeof(DataType) * capa);
    8. if (NULL == _array)
    9. {
    10. perror("malloc申请空间失败!!!");
    11. return;
    12. }
    13. _capacity = capa;
    14. _size = 0;
    15. }
    16. void Push(DataType data)
    17. {
    18. // CheckCapacity();
    19. _array[_size] = data;
    20. _size++;
    21. }
    22. // 其他方法...
    23. //析构函数
    24. ~Stack()
    25. {
    26. if (_array)
    27. {
    28. free(_array);
    29. _array = NULL;
    30. _capacity = 0;
    31. _size = 0;
    32. }
    33. }
    34. private:
    35. DataType* _array;
    36. int _capacity;
    37. int _size;
    38. };
    39. class MyQue {
    40. private:
    41. //自定义类型
    42. Stack _pushST;
    43. Stack _popST;
    44. };
    45. int main() {
    46. MyQue q;
    47. return 0;
    48. }

     运行结果:

            对于MyQue类来说,它的成员变量全是自定义类型,编译会处理——Stack类中调用Stack的构造函数,从而得到上图右上角的各变量初始值,MyQue不需要自己写构造函数,全靠调用Stack类的即可。

            案例总结:Date类和Stack类的构造函数都需要自己去写,而MyQue类的构造使用默认构造即可,因为编译生成的构造函数并不能满足我们的需求。


    三.析构函数

            在之前,我写过很多用C语言实现链表、通讯录、栈、队列的代码,发现都需要用到数据结构的销毁函数Destory,于是析构函数因此诞生,即使我们忘记写了,系统也会帮助我们清理。

    1.定义:

            析构函数与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由 编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

    2 特性:

            析构函数是特殊的成员函数,其特征如下:

    1. 析构函数名是在类名前加上字符 ~。

    2. 无参数无返回值类型。

    3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构 函数不能重载。

    4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

    3.析构练习: 

    1. class Stack
    2. {
    3. public:
    4. //构造函数
    5. Stack(int capa = 4){
    6. _array = (DataType*)malloc(sizeof(DataType) * capa);
    7. if (NULL == _array)
    8. {
    9. perror("malloc申请空间失败!!!");
    10. return;
    11. }
    12. _capacity = capacity;
    13. _size = 0;
    14. }
    15. void Push(DataType data){
    16. // 扩容
    17. _array[_size] = data;
    18. _size++;
    19. }
    20. //析构函数
    21. ~Stack(){
    22. if (_array){
    23. free(_array);
    24. }
    25. _array = NULL;
    26. _capacity = 0;
    27. _size = 0;
    28. }
    29. int main(){
    30. Stack st; //创建对象后自动调用构造函数——完成初始化操作
    31. StackPush(1);
    32. StackPush(2);
    33. StackPush(3);
    34. return 0;
    35. }

            析构函数的调用是在执行return 0后,函数即将销毁时自动调用它,然后堆区空间就会被释放,且其他变量清零。 

    4.默认析构函数

            默认析构函数与默认构造函数同理,都是我们不生成时,编译器自动生成一个析构函数,但编译器生成的也只能处理一些简单类型的成员变量,例如日期类:

    1. class Date {
    2. public:
    3. Date(int year, int month, int day)
    4. {
    5. _year = year;
    6. _month = month;
    7. _day = day;
    8. }
    9. void Print() {
    10. cout << _year << "-" << _month << "-" << _day << endl;
    11. }
    12. //没有写析构函数
    13. private:
    14. int _year;
    15. int _month;
    16. int _day;
    17. };
    18. int main() {
    19. Date d1(2022, 10, 5);
    20. Date d2(2021, 9, 1);
    21. return 0;
    22. }

            对于复杂类型的成员变量,编译器自动生成的析构函数并不能完成最后的清理工作,比如:对于堆区空间的生成,使用fopen函数打开的文件,最后需要手动释放(fclose/free),可是默认析构函数并不能做到这么智能,所以还是得自己去写析构函数!!! 

            所以我总结出一个道理:若编译器默认生成就可以满足需求的话,就不用自己写,不满足需求就自己写。

  • 相关阅读:
    Spring Boot Thymeleaf(十一)
    JS判断数据类型
    英语写作中“合理的”“科学的”reasonable plausible scientific 的用法
    信息学奥赛一本通:1148:连续出现的字符
    手机号的正则表达式
    CANOE使用七:自动化测试Autosar网络管理(创建TestModule-搭配Panel界面及使用Capl识别配置文件TXT的自动化测试流程)
    ESP32网络开发实例-将数据保存到InfluxDB时序数据库
    Jdk8 时间和日期API介绍
    【CPP】函数重载、模版
    sql登录操作笔记
  • 原文地址:https://blog.csdn.net/weixin_69283129/article/details/127773069