C++语言中类的成员变量初始化(转…

来源:互联网 发布:海岛奇兵攻略软件 编辑:程序博客网 时间:2024/06/05 20:17
C++中对类的成员变量进行初始化的方法通常有如下两种:
1.构造函数进行初始化
例如:
[cpp] view plaincopy
class MyClass{  
public:  
    MyClass(int nValue) {var = nValue; }  
private:  
    int var; 
}  
2.在构造函数的初始化列表中进行初始化(成员初始化列表)
[cpp] view plaincopy
class MyClass{  
public:  
    MyClass(intnValue):var(nValue){}  
private:  
    int var; 
}  

从技术上,第二种方法比较好,但是在大多数情况,两者实际上没有什么区别。
 
使用成员初始化列表主要有如下两个原因:
第一个原因:必要性。
例如,有一个类成员,它本身是一个类或者结构,而且只有一个带一个参数的构造函数
[cpp] view plaincopy
class CMember{  
public:  
    CMember(int x){...} 
}  
因为MyClass有一个显式声明的构造函数,编译器就不会再产生一个缺省的构造函数(不带参数),所以没有一个参数就无法创建MyClass的一个实例。
[cpp] view plaincopy
CMember* pm = new CMember; // Error!  
CMember* pm = new CMember(2); // OK!  
若MyClass是另一个类的成员,该怎么初始化它呢?答案就是必须使用成员初始化列表
[cpp] view plaincopy
class CMyClass{  
    CMember m_member; 
public:  
    CMyClass(); 
};  
[cpp] view plaincopy
//必须使用初始化列表来初始化成员m_member  
CMyClass::CMyClass():m_member(2){  
}  

没有其他办法将参数传递给m_member,如果成员是一个常量对象或者引用也是一样。根据C++规则,常量对象和引用不能被赋值,它们只能被初始化。
 
第二个原因:出于效率考虑。
当成员类具有一个缺省的构造函数和一个赋值操作符时,MFC的CString提供了一个完美的例子。假定你有一个类CMyClass具有一个CString类型的成员m_str,
你想把它初始化为”How are you?“, 此时有两种选择:
[cpp] view plaincopy
CMyClass::CMyClass() { // 使用赋值操作符   
   CString::operator=(LPCTSTR);  
    m_str = _T("How areyou.");   
}    
[cpp] view plaincopy
// 使用初始化列表和构造函数   
CString::CString(LPCTSTR) CMyClass::CMyClass() : m_str(_T("Howare you.")) { }    
     
在它们之间有什么不同吗?
是的。编译器总是确保所有成员对象在构造函数体执行之前被初始化,因此在第一个例子中编译的代码将调用CString::Cstring来初始化m_str,这在控制到达赋值语句前完成。
在第二个例子中编译器产生一个对CString:: CString(LPCTSTR)的调用并将"Hi,how areyou."传递给这个函数。

结果是在第一个例子中调用了两个CString函数(构造函数和赋值操作符),而在第二个例子中只调用了一个函数。在CString的例子里这是无所谓的,因为缺省构造函数是内联的,CString只是在需要时为字符串分配内存(即,当你实际赋值时)。

但是,一般而言,重复的函数调用是浪费资源的,尤其是当构造函数和赋值操作符分配内存的时候。在一些大的类里面,你可能拥有一个构造函数和一个赋值操作符都要调用同一个负责分配大量内存空间的Init函数。在这种情况下,你必须使用初始化列表,以避免不要的分配两次内存。在内建类型如ints或者longs或者其它没有构造函数的类型下,在初始化列表和在构造函数体内赋值这两种方法没有性能上的差别。

不管用那一种方法,都只会有一次赋值发生。有些程序员说你应该总是用初始化列表以保持良好习惯,但我从没有发现根据需要在这两种方法之间转换有什么困难。
在编程风格上,我倾向于在主体中使用赋值,因为有更多的空间用来格式化和添加注释,你可以写出这样的语句:
x=y=z=0;
或者
memset(this,0,sizeof(this));
注意第二个片断绝对是非面向对象的。     
当我考虑初始化列表的问题时,有一个奇怪的特性我应该警告你,它是关于C++初始化类成员的,它们是按照声明的顺序初始化的,而不是按照出现在初始化列表中的顺序。
[cpp] view plaincopy
class CMyClass {      
    CMyClass(int x, int y);      
    int m_x;      
    int m_y;  
};    
CMyClass::CMyClass(int i) : m_y(i), m_x(m_y) { }  
   
你可能以为上面的代码将会首先做m_y=i,然后做m_x=m_y,最后它们有相同的值。但是编译器先初始化m_x,然后是m_y,,因为它们是按这样的顺序声明的。结果是m_x将有一个不可预测的值。这个例子是故意这样设计来说明这一点的,然而这种bug会很自然地出现。

有两种方法避免它,
一个是总是按照你希望它们被初始化的顺序来声明成员,
第二个是,如果你决定使用初始化列表,总是按照它们声明的顺序罗列这些成员。

这将有助于消除混淆。 [引用]类的成员变量可以是多种多样的数据类型,比如基本的数据类型、结构、联合、对象、指针、引用、const常量和动态数组等。但是对于上面一些特殊数据类型的成员变量来说,对成员变量的初始化就有相应的特殊的要求了。比如,对于不同类型的成员变量,初始化的位置可能就不一样。
 一般,对公有类型成员变量的初始化,可以直接对其赋值。对于私有类型成员变量的初始化,可以通过公有函数接口来进行。 
 有时,构造函数也是一个公有函数接口:定义对象的时候,在对象后面括号中可以有设置初始值的参数列表,然后在构造函数的函数体中将参数列表中的数值赋值给成员变量。
 在C++语言中,将参数列表中的数值赋值给成员变量时,不仅可以在构造函数的函数体中进行,也可以在初始化行中进行。初始化行在构造函数的参数数据类型的圆括号后以冒号开始,并且在代码开头的左大括号“ {”处结束。 在初始化行处进行初始化的情况有:  
分层类的构造函数可以调用它的任一个子对象的构造函数,调用必须出现在初始化行上。
 对于常量(const)类型的成员变量的初始化必须在初始化行上。 
对于引用类型的成员变量的初始化必须在初始化行上。 

 所谓的分层类就是类的一些成员函数是其他类的对象,这与类的继承与派生是不一样的。
 构造函数在初始化时执行的顺序是:先执行初始化行部分,然后再执行构造函数的赋值部分(即构造函数体部分)。
 如果在初始化行中需要对多个成员变量进行初始化,可以在各成员变量之间用半角逗号隔开。 
[cpp] view plaincopy
#include   
#include   
   
class CPoint // 基类   
{        
public:          
    int x,y;          
    CPoint(int ax=0,int ay=0)           
    {            
       x = ax;             
       y = ay;             
       cout<<"CPoint初始化赋值部分。x:"<<x<<"y:"<<y<<endl;          
    
};   
  
class CRect // 派生类 {  
private:          
    CPoint low_right; //注意low_right与up_left定义的顺序          
    CPoint up_left;       
public:          
    int & CenterX;          
    const int CenterY;          
    CRect(int x1,int y1,intx2,int y2,int & x3,int y3)          
   :up_left(x1,y1),low_right(x2,y2),CenterX(x3),CenterY(y3) // 初始化行       
    {          
       cout<<"CRect初始化赋值部分"<<endl;       
    
};   
  
void main()   
{        
    int cx=5;       
    int cy=6;       
    CRect R1(1,2,3,4,cx,cy);       
    cout<<"Center:x="<<R1.CenterX<<"y="<<R1.CenterY<<endl;       
    getch();  
}   

 
运行结果: CPoint初始化赋值部分。
x:3 y:4 CPoint初始化赋值部分。
x:1 y:2 CRect初始化赋值部分 
Center: x=5 y=6
说明:
上面的例程对分层类的初始化、常量成员变量的初始化和引用成员变量的初始化分别进行了说明。在CPoint类和CRect类的构造函数中都有信息输出,可以看出初始化行中的代码较赋值部分先执行。
 对常量成员变量和引用成员变量进行初始化时,不要直接使用赋值符号,而是在变量后加一个括号,在其中指定参数。这类似于类的构造函数。 
 在对引用成员变量进行初始化的时候,构造函数参数列表中对应参数数据类型应该为引用类型。 
 初始化行中的顺序并不决定初始化的顺序。初始化的顺序是由类定义体中各变量和对象声明的顺序来决定的。

比如,上面low_right在up_left前声明,所以初始化时low_right在前,尽管up_left在初始化行中是在前面。 
原创粉丝点击