• (笔记):构造函数之初始化列表


    一、为了更好的说明初始化列表,首先由一个错误的代码引入:

     1 #include<iostream>
     2  
     3 #include"string.h"
     4 using namespace std;
     5 
     6 class Student
     7 {
     8     private:
     9     int Num,Chinese,Maths,English;
    10     float Total;
    11     char Name[20];
    12     public:
    13         Student(){}//默认构造函数 
    14          Student(char name[20],int num,int chinese,int maths,int english);
    15 
    16 };
    17 Student::Student(char name[20],int num,int chinese,int maths,int english){
    18     int n;
    19     for(n=0;n<strlen(name);n++)
    20         Name[n]=name[n];
    21     Num=num;
    22     Chinese=chinese;
    23     English=english;
    24     Maths=maths;
    25     Total=maths+chinese+english;
    26 }
    27  
    28 int main()
    29 {
    30     int i,j;
    31     int num,chinese,maths,english;
    32     char name[20];
    33     Student std[5];//因为有了默认构造函数所以此时编译可以通过
    34         
    35     for(i=0;i<5;i++)
    36     {
    37         cin>>name[20]>>num>>chinese>>maths>>english;
    38         std[i](name[20],num,chinese,maths,english);
    39         for(j=0;j<20;j++)
    40         name[j]='';    
    41         
    42     }
    43 }
    View Code

    上面代码在编译的时候出现一个错误提示:
    [Error] no match for call to '(Student) (char&, int&, int&, int&, int&)'
    该错误定位到下面这行std[i](name[20],num,chinese,maths,english);该段代码错误的原因是误认为先定义一个Student 的数组,之后调用构造函数来进行赋值,实际上,构造函数是在Student std[5];这句定义的时候就调用默认构造函数初始化了 (如果读者不明白构造函数的执行过程请参考我的上篇笔记)。所有构造函数(可以是默认构造或者非平凡构造)都是在定义的时候就调用,定义以后的调用就不叫初始化了,而是赋值。写这个错误代码的人,明显是没有理解构造函数是怎么执行的,
    他的目的是通过循环依次调用构造函数来进行初始化。当然我们可以按照他的想法来进行"正确"的编写代码,将上面的错误行出修改为std[i].Student(name[20],num,chinese,maths,english);

    这样看起来才像是对象本身来调用构造函数进行初始化。遗憾的是,这样也是无法编译的,出现下面的错误信息:
    [Error] invalid use of 'Student::Student'

    出现这样的错误是因为,C++本身就要求在定义的时候系统自动调用构造函数,而对象是不能调用构造函数的。

    解决方案:

    1、可以在定义的时候直接初始化,为了方便的说明问题,我们把上面的Student std[5];这句代码中改为1个数组元素,for循环去掉,
    就变成下面的代码了(这段代码仅仅c++11支持):

     1 #include<iostream>
     2  
     3 #include"string.h"
     4 using namespace std;
     5 
     6 class Student
     7 {
     8     private:
     9     int Num,Chinese,Maths,English;
    10     float Total;
    11     char Name[20];
    12     public:
    13         Student(){}//默认构造函数 
    14          Student(char name[20],int num,int chinese,int maths,int english);
    15 
    16 };
    17 Student::Student(char name[20],int num,int chinese,int maths,int english){
    18     int n;
    19     for(n=0;n<strlen(name);n++)
    20         Name[n]=name[n];
    21     Num=num;
    22     Chinese=chinese;
    23     English=english;
    24     Maths=maths;
    25     Total=maths+chinese+english;
    26 }
    27  
    28 int main()
    29 {
    30     int i,j;
    31     int num,chinese,maths,english;
    32     char name[20];
    33     Student std[1] = { { name,num,chinese,maths,english} };
    34         
    35 
    36 }
    View Code

    2、还可以定义一系列指针,然后再每次构造的时候调用new来构造类对象。
    如:

    1 Student *std[5];
    2 for(i=0;i<5;i++)
    3 {
    4 cin>>name[20]>>num>>chinese>>maths>>english;
    5 std[i]=new Student(name,num,chinese,maths,english);
    6 */ 

    为了更好的看到new对象的时候,构造函数是怎么执行的,简单的更改一下上面的代码如下:

     1 #include<iostream>
     2  
     3 #include"string.h"
     4 using namespace std;
     5 
     6 class Student
     7 {
     8     private:
     9     int Num,Chinese,Maths,English ;
    10     float Total;
    11     public:
    12         Student(){}//默认构造函数 
    13          Student( int num );
    14 
    15 };
    16 Student::Student(int num ){
    17     
    18     cout<<"" << num <<"个构造函数" <<endl; 
    19     Num = num;
    20     cout<<"Num = "<<num<<endl;
    21 }
    22  
    23 int main()
    24 {
    25     int i;
    26     int num,chinese,maths,english;
    27 
    28     Student *std[5];
    29     for(i=0;i<5;i++)
    30     {
    31         std[i]=new Student( i );//每申请一块内存就自动调用构造函数     
    32     }
    33 }
    View Code

    结果如图所示:

    通过上面的错误实例,我们发现有些代码出现错误的原因就是没有理解好构造函数的执行过程。不知道你有没有发现
    我在上面的代码中总是在构造函数的内部,对Student类的成员变量进行赋值,这里严格来说并不是初始化(有关初始化概念请参考上篇笔记),
    这是一个赋值计算的过程,在定义一个变量的时候自动的执行构造函数里面的赋值代码。

    二、面引出初始化列表。

    形如下面的代码:在构造函数的后面有个 : 符号,后面紧跟着对成员变量的初始化。

     1 class Student
     2 {
     3     private:
     4     int m_num,m_maths ;
     5     public:
     6         Student(){}//默认构造函数 
     7         Student(int num,int maths  ):m_num(num),m_maths(maths)
     8         {
     9             
    10             cout<<"进入计算阶段" <<endl; 
    11             cout<<"m_num = "<<num<<endl;
    12         } 
    13 
    14 };

    为了更好的了解初始化列表,首先我们要知道,构造函数的执行可以分成两个阶段,初始化阶段和计算阶段,初始化阶段先于计算阶段。而初始化阶段就是对应着初始化列表那部分,而计算阶段就是构造函数的函数体部分。初始化阶段先于计算阶段执行,如下面程序:

     1 #include<iostream>
     2  
     3 #include"string.h"
     4 using namespace std;
     5 
     6 class Student
     7 {
     8     private:
     9     int m_num,m_maths ;
    10     public:
    11         Student(){}//默认构造函数 
    12          Student(int num,int maths );
    13 };
    14 Student::Student(int num,int maths  ):m_num(num),m_maths(maths)
    15 {
    16     cout<<"进入计算阶段前m_num = "<<m_num <<endl; 
    17     this->m_num = 2;
    18     cout<<"进入计算阶段后查看m_um = "<<m_num<<endl;
    19 }
    20  
    21 int main()
    22 {
    23     
    24     Student A(1,2); 
    25 }

    试验结果图如下:

    从图中可以看出,在进入计算阶段前我们看到了m_num的值变为了1,在计算阶段后m_num变为了2,所以说初始化阶段先于计算阶段完成。

    下面说明一下初始化列表的优点:它比在构造函数计算阶段(函数体内)进行赋值效率更高(仅仅针对类类型的变量)。下面有两段代码和相应的结果图片,代码2是在代码1的基础上更改了Student2的构造函数为初始化列表形式。

    代码1:

     1 #include<iostream>
     2  
     3 using namespace std;
     4 
     5 class Student1 {
     6     public:
     7         int a;
     8             
     9          Student1()                         // 无参构造函数
    10         { 
    11             cout << "默认构造函数Student1" << endl ;
    12         }
    13     
    14         Student1(const Student1& t1) // 拷贝构造函数
    15         {
    16             cout << "拷贝构造函数Student1" << endl ;
    17             this->a = t1.a ;
    18         }
    19     
    20         Student1& operator = (const Student1& t1) // 赋值运算符
    21         {
    22             cout << "赋值函数Student1" << endl ;
    23             this->a = t1.a ;
    24             return *this;
    25         }
    26  };
    27 class Student2
    28 {
    29     public:
    30      
    31     Student1 test ;
    32     Student2(Student1 &t1){            //图1结果 
    33         test  = t1 ;
    34     }
    35 };
    36 int main()
    37 {
    38     
    39     Student1 A;
    40     Student2 B(A);
    41 
    42 }
    View Code

    代码2:

     1 #include<iostream>
     2  
     3 using namespace std;
     4 
     5 class Student1 {
     6     public:
     7         int a;
     8             
     9          Student1()                         // 无参构造函数
    10         { 
    11             cout << "默认构造函数Student1" << endl ;
    12         }
    13     
    14         Student1(const Student1& t1) // 拷贝构造函数
    15         {
    16             cout << "拷贝构造函数Student1" << endl ;
    17             this->a = t1.a ;
    18         }
    19     
    20         Student1& operator = (const Student1& t1) // 赋值运算符
    21         {
    22             cout << "赋值函数Student1" << endl ;
    23             this->a = t1.a ;
    24             return *this;
    25         }
    26  };
    27 class Student2
    28 {
    29     public:
    30      
    31     Student1 test ;
    32      Student2(Student1 &t1):test(t1){}
    33 };
    34 int main()
    35 {
    36     
    37     Student1 A;
    38     Student2 B(A);
    39 
    40 } 
    View Code

    代码1图:

    代码2图:

    从图中可以看出第一段代码的计算结果调用了2次构造函数和1次赋值函数,而代码2结果,调用了1次构造函数和1次拷贝函数。
    当代码量比较多的时候会发现,代码2比代码1效率更高。

    欢迎大家关注我的微信公众号「佛系师兄」,里面有更多技术文章。

    比如

    反复研究好几遍,我才发现关于 CMake 变量还可以这样理解!

    更多好的文章会优先在里面不定期分享!打开微信客户端,扫描下方二维码即可关注!

  • 相关阅读:
    Java IO6:字符流进阶及BufferedWriter、BufferedReader
    Java IO5:字符流
    Java IO4:字符编码
    Java IO3:字节流
    Java IO2:RandomAccessFile
    Java IO1:IO和File
    Java异常
    Java语法糖4:内部类
    SharePoint JavaScript API 根据文件路径删除文件
    SharePoint PowerShell 批量删除遗弃视图
  • 原文地址:https://www.cnblogs.com/newneul/p/7643051.html
Copyright © 2020-2023  润新知