C++成员变量初始化列表和变量初始化顺序

一、成员变量初始化列表
与其他函数不同,构造函数除了有名字,参数列表和函数体之外,还可以有初始化列表,初始化列表以冒号开头,后跟一系列以逗号分隔的初始化字段。
class foo
{
	public:
	foo(string s, int i):name(s), id(i){} ; // 初始化列表
	private:
		string name ;int id ;
};
其中foo(string s, int i):name(s), id(i){}; 即为成员初始化列表。

成员变量初始化顺序
1、成员变量在使用 初始化列表初始化时,与构造函数中 初始化成员列表的顺序无关,只与 定义成员变量的顺序有关。因为成员变量的初始化次序是根据变量在 内存中次序有关,而内存中的排列顺序早在编译期就 根据变量的定义次序决定了。这点在EffectiveC++中有详细介绍。
2、如果不使用初始化列表初始化,在 构造函数内初始化时,此时与 成员变量在构造函数中的位置有关。
3、注意:类成员在 定义时,是不能初始化的(静态常量成员除外)
4、注意:类中 const成员常量和reference 成员变量 必须在 构造函数初始化列表 中初始化
5、注意:类中 static成员变量,必须在 类外初始化
                  类中的 static const 静态常量成员可以在定义时被初始化

6、静态变量进行初始化顺序是 基类的静态变量先初始化,然后是 它的派生类。直到 所有的静态变量都被初始化。这里需要注意 全局变量和静态变量的初始化是不分次序的。这也不难理解,其实静态变量和全局变量都被放在公共内存区。可以把静态变量理解为带有“ 作用域”的全局变量。在一切初始化工作结束后,main函数会被调用,如果某个类的构造函数被执行,那么首先基类的成员变量会被初始化。 

变量的初始化顺序就应该是:
基类的静态变量或全局变量
2 派生类的静态变量或全局变量
基类的成员变量
4 派生类的成员变量



成员变量初始化时构造函数赋值与初始化列表的区别
一般我们进行成员变量初始化用两种方法
第一种是通过在构造函数内赋值
class Point
{
public:
Point(){ _x = 0; _y = 0;};
Point( int x, int y ){ _x = 0; _y = 0; }
private:
int _x, _y;
};

第二种是使用初始化列表
class Point
{
public:
Point():_x(0),_y(0){};
Point( int x, int y ):_x(x),_y(y){}
private:
int _x, _y;
};
这两种用法是有区别的
一、在有些情况下,必须使用初始化列表。特别是const和引用数据成员被初始化时。
class Point
{
// 这个类的定义就要求使用初始化成员列表,因为const成员只能被初始化,不能被赋值
public:
Point():_x(0),_y(0){};
Point( int x, int y ):_x(x),_y(y){}
//Point(){ _x = 0; _y = 0;}
//Point( int x, int y ){ _x = 0; _y = 0; }
private:
const int _x, _y;
};
二、是从效率方面来说的,对于内置类型或复合类型,差异不会太大,但对于非内置数据类型,差异还是很明显的
如我们再给Point类添加一个新的string类型的成员变量
class Point
{
const int _x, _y;
string _name;
}; 
构造函数内赋值进行初始化
Point( int x, int y, string name ){ _x = 0; _y = 0; _name = name; }
_name = name 这个表达式会调用string类的缺省构造函数一次,再调用Operator=函数进行赋值一次。所以需调用两次函数一次构造,一次赋值


用初始化列表进行初始化
Point( int x, int y, string name ):_x(x),_y(y), _name(name){}
_name会通过拷贝构造函数仅以一个函数调用的代码完成初始化

即使是一个很简单的string类型,不必要的函数调用也会造成很高的代价。随着类越来越大,越来越复杂,它们的构造函数也越来越大而复杂,那么对象创建的代价也越来越高,所以一般情况下建议使用初始化列表进行初始化,不但可以满足const和引用成员的初始化要求,还可以避免低效的初始化数据成员。

使用初始化列表有两个原因:
原因1.必须这样做:

《C++ Primer》中提到在以下三种情况下需要使用初始化成员列表:
一、需要初始化的数据成员是对象的情况(这里包含了继承情况下,通过显示调用父类的构造函数对父类数据成员进行初始化);
二、需要初始化const修饰的类成员;
三、需要初始化引用成员数据;

即:例一、数据成员是对象,切对象只有含参数的构造函数;

        如果我们有一个类成员,它本身是一个类或者是一个结构,而且这个成员它只有一个带参数的构造函数,而没有默认构造函数,这时要对这个类成员进行初始化,就必须调用这个类成员的带参数的构造函数,如果没有初始化列表,那么他将无法完成第一步,就会报错。

class Test
{
public:
         Test(int x,int y,int z);
private:
         int a;
         int b;
         int c;
};
class MyTest
{
public:
        MyTest():test(1,2,3){}        //初始化,初始化列表在构造函数执行前执行(这个可以测试,对同一个变量在初始化列表和构造函数中分别初始化,首先执行参数列表,后在函数体内赋值,后者会覆盖前者)。
private:
        Test test;            //声明
};
        因为Test有了显示的带参数的构造函数,那么他是无法依靠编译器生成无参构造函数的,所以没有三个int型数据,就无法创建Test的对象。
         Test类对象是MyTest的成员,想要初始化这个对象test, 那就只能用成员初始化列表 ,没有其他办法将参数传递给Test类构造函数

例二、对象引用或者cosnt修饰的数据成员
        另一种情况是这样的:类成员中含有一个const对象时,或者是一个引用时,他们也必须要通过成员初始化列表进行初始化因为这两种对象要在声明后马上初始化,而在构造函数中,做的是对他们的赋值,这样是不被允许的

即:

class Test
{
     priate:
          const int a;               //const成员声明
      public:
          Test():a(10){}           //初始化
};

class Test
{
    private:
         int &a;                        //声明
    public:
         Test(int a):a(a){}        //初始化
}

例三、子类初始化父类的私有成员,需要在(并且也只能在)参数初始化列表中显示调用父类的构造函数,如下:

比如说:
       

 class child : public foo
     {
     };

       foo里面的构造函数是这样写的:

foo (int x)
    {
       a = x;
    }.

       而在child里面写child(int x){ foo(x); }是通过不了编译的。

只有把子类构造函数写作child (int x) : foo(x){}才可以。



原因2.效率要求这样做:
       类对象的构造顺序显示,进入构造函数体后,进行的是计算,是对成员变量的赋值操作,显然,赋值和初始化是不同的,这样就体现出了效率差异,如果不用成员初始化类表,那么类对自己的类成员分别进行的是一次隐式的默认构造函数的调用,和一次赋值操作符的调用,如果是类对象,这样做效率就得不到保障。

注意:构造函数需要初始化的数据成员,不论是否显式的出现在构造函数的成员初始化列表中,都会在该处完成初始化,并且初始化的顺序和其在类中声明时的顺序是一致的,与列表的先后顺序无关所以要特别注意,保证两者顺序一致才能真正保证其效率和准确性。初始化数据表赋值后构造函数会再赋值一次,所以要保证二者的一致性。

 

 
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值