1 好多书籍都建议我们直接使用初始化列表为成员进行初始化,为什么呢?
我想原因有二:
①考虑到效率
②有些成员不得不这么做(const,引用)
为什么使用初始化列表就效率呢?
其实也不一定,
对于内置类型,在函数体内赋值和在初始化列表中初始化两者的效率是等同的。但是为了美观和一致性的问题,《Effective C++》作者建议我们还是用初始化列表进行。但是这也不是在所有情况下都是必须的。若一个类的构造函数很多的时候,这时每个构造函数都有自己的初始列,这样多份成员的初始列就会导致烦人的重复。这时我们可以选择那些“赋值表现得像初始化一样好”的成员变量,改用它们为赋值。然后把他们放到一个函数中,供所有的构造函数调用。
对于类类型,两者的效率就相差很多了:
初始化列表的执行发生在进入函数体之前,它是在调用构造函数是进行初始化的。而对于函数体内的赋值(非初始化,而是赋值),它是首相调用了类的默认构造函数来初始化成员变量,而后在函数体中调用赋值函数为成员变量赋值的。显然这个和初始化列表相比默认构造函数的调用就浪费了。效率低了很多。
有些成员变量必须用初始化列表
const 和引用 类型的成员变量必须得用初始化列表进行初始化。其实这没什么高明的,也并不是一个明文规定。而是为遵守我们另一个规则而必须这么做。这个规则就是”const和引用类型的变量必须初始化“。而我们通过上面也明白了,函数体内是赋值而非初始化,若我们将const和引用类型的成员变量放在构造函数的函数体内的话。那么const和引用类型的变量将会没有初值,即没被初始化,根据我们另一个规则,当然会发生错误,编译器不允许我们这么做的。故:我们在构造函数中必须选择初始化列表对const和引用类型的成员变量进行初始化。
2 初始化列表的初始化顺序
初始化列表的初始化顺序并不代表成员变量的初始化顺序,C++有着十分固定的”成员初始化顺序“,即:
①基类成员更早于派生类成员进行初始化
②类内部总是按成员变量的声明顺序依次进行初始化的。
由此我们是不是有一个想法啊?若我们定义了一个类:
- #include <iostream>
- using namespace std;
- class base
- {
- public:
- base():p(new int[size]),size(4) //注意这里
- {
- for (int i=0; i<4; ++i)
- {
- p[i] = i;
- }
- for (int i=0; i<4; ++i)
- {
- cout << p[i] << " ";
- }
- }
- private:
- int *p;
- size_t size;
- };
- int main()
- {
- base t;
- system("pause");
- return 0;
- }
我们用该类定义一个对象,然后运行。看下结果。结果什么也没有(VC2008)。。为什么呢?有些同学可能相到了。看下我们的初始化列表
- base():p(new int[size]),size(4)
我们是先为p申请的空间,此时size初始未初始化状态。当然运行不成功了。。好,那我们改过来:
- #include <iostream>
- using namespace std;
- class base
- {
- public:
- base():size(4),p(new int[size])//注意改过来了
- {
- for (int i=0; i<4; ++i)
- {
- p[i] = i;
- }
- for (int i=0; i<4; ++i)
- {
- cout << p[i] << " ";
- }
- }
- private:
- int *p;
- size_t size;
- };
- int main()
- {
- base t;
- system("pause");
- return 0;
- }
运行一下,还是没有,原因在那里呢?看下我们的声明顺序,
- private:
- int *p;
- size_t size;
呵呵,明白了吧。好我们改过来:
- #include <iostream>
- using namespace std;
- class base
- {
- public:
- base():size(4),p(new int[size])
- {
- for (int i=0; i<4; ++i)
- {
- p[i] = i;
- }
- for (int i=0; i<4; ++i)
- {
- cout << p[i] << " ";
- }
- }
- private:
- size_t size;//改过来啦
- int *p;
- };
- int main()
- {
- base t;
- system("pause");
- return 0;
- }
运行一下,结果出现了。。呵呵。此时,无论你如何改初始化列表都不影响。由此就可以证明我们上面的结论②了吧。
既然到这里了,我们来证明下柳涛老师一篇日志的内存问题吧:看下面代码:
- #include <iostream>
- using namespace std;
- int a = 4;
- int b = 5;
- int c = 6;
- int d = 7;
- class base
- {
- public:
- base():p(&a)//注意这里
- {
- for (int i=0; i<4; ++i)
- {
- p[i] = i;
- }
- for (int i=0; i<4; ++i)
- {
- cout << p[i] << " ";
- }
- cout << endl;
- cout << "a: " << a << endl;
- cout << "b: " << b << endl;
- cout << "c: " << c << endl;
- cout << "d: " << d << endl;
- }
- private:
- int *p;
- };
- int main()
- {
- base t;
- system("pause");
- return 0;
- }
我们看下输出结果:
- 0 1 2 3
- a: 0
- b: 1
- c: 2
- d: 3
为啥b c d的值都被改变了呢?我们紧紧是让p指向了a啊。这就是柳涛老师的讲的内存问题的。原文地址是http://student.youkuaiyun.com/space.php?uid=91288&do=thread&id=2351
对于a,b,c,d;首相操作系统为变量分配空间(从低地址开始分配):首相是a,然后是b,c,d,由于他们都是整型,故为他们分配的空间是挨着的。当我们把p指向a后,若我们操作p[0]就相当于操作a了,同样p[1] 就是b了。.....
若我们改变一下:
- int a = 4;
- char b = 5;//注意这里改了
- int c = 6;
- int d = 7;
再次输出结果,结果不变.为什么呢?
- struct test1
- {
- int a;
- char b;
- int c;
- int d;
- };
- struct test2
- {
- int a;
- int b;
- int c;
- int d;
- };
我们可以输出一下这两个结构体的大小:
是一样的,都是16。在内存分配时,讲究字节对齐。故第一个char b;后面补充了三个字节。即:我们申请了还是相当于4个整型的空间。
若我们改成以下这样:
- struct test
- {
- int a;
- char b;//注意这里
- char c;//注意这里
- int d;
- };
结果就变多了。。
- 0 1 2 3
- a: 0
- b: 1
- c:
- d: 2
这就是结果.此时我们相当于申请了三个整型的空间,b,c占一个。故在我们移动指针p的时候第一个指向a,a = 0;第二个就到b,c那个整型空间,第三个就到d了,故d = 2;在b,c所占的整型空间里我们放进了整型值1.因为b处在该空间的开始位置,故通过b我们输出了1.c初始第二个字节开始的位置,从此开始将什么也输不出来。呵呵。关于内存空间分配的问题有太多东西了。。更多的东西都需要我们自己慢慢去摸索了。。加油大家,一起努力学习!
以上所有代码均在VC++ 2008中编译通过!