前言
最近发现在 C++
的代码中还有不少是 C
语言的代码,于是想要把 C
的写法改成 C++
的写法。
其中碰到了 C++
初始化的问题,查阅资料后发现有坑,于是便有了此文。
Java 变量初始化
先从简单的开始, Java
中的初始化相对而言简单许多。
局部变量
1
2
3
4
5
6
|
class Test {
public static void main(String[] args) {
int a;
System.out.println(a);
}
}
|
结果如下
1
2
3
|
Test.java:4: error: variable a might not have been initialized
System.out.println(a);
^
|
在 Java
中,局部变量没有初始化是不能使用的。
成员变量
1
2
3
4
5
6
7
8
9
10
|
class Test
{
int a;
public static void main(String[] args)
{
Test test = new Test();
System.out.println(test.a);
}
}
|
结果如下
可以看到 int
类型的成员变量默认为 0
。为什么类的成员变量就有默认值呢?
这就要说到 JVM
的类加载机制了,这里简单说一下
- 类的加载过程主要包括装载、链接、初始化
- 装载和链接就不说了,给成员变量赋值是在初始化的过程中,会对成员变量赋零值
数据类型的零值如下:
数据类型 |
零值 |
int |
0 |
long |
0L |
short |
(short)0 |
char |
‘\u0000’ |
byte |
(byte)0 |
boolean |
false |
float |
0.0f |
double |
0.0d |
reference |
null |
所以这里看到 0
是正常的。
全局静态变量
1
2
3
4
5
6
|
class Test {
static int a;
public static void main(String[] args) {
System.out.println(a);
}
}
|
结果如下
全局静态变量和成员变量一样在初始化的阶段进行赋值,有一种情况比较特殊的就是常量会在链接阶段的准备环节就赋值。具体的可以看Java 类的加载过程
C++ 变量初始化
局部变量
1
2
3
4
5
6
7
|
using namespace std;
int main(int argc, char *argv[])
{
int a;
cout << a << endl;
return 0;
}
|
结果如下
C++
输出的是 0
好像很开心啊,不过别高兴太早了。
我把代码稍微改一下,如下所示
1
2
3
4
5
6
7
8
|
using namespace std;
int main(int argc, char *argv[])
{
string str("dkfjakfjaja");
int a;
cout << a << endl;
return 0;
}
|
结果如下
结果就不一样了,这个才是正常情况下应该有的,不要抱侥幸心理,以为本应该是 0
。
那么这个值到底应该是多少呢?
我也不知道,这个值取决于你的内存中是什么,它就会是什么。其实这是个脏数据,你从里面读取的数据是之前留下的。至于是多少,不知道。
成员变量
1
2
3
4
5
6
7
8
9
10
11
|
using namespace std;
class Test {
public:
int a;
};
int main(int argc, char *argv[])
{
Test test;
cout << test.a << endl;
return 0;
}
|
成员变量和局部变量一样。
全局变量和静态变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
using namespace std;
class Test {
public:
int a;
};
int g_a;
Test g_test;
int main(int argc, char *argv[])
{
static int s_a;
cout << s_a << endl;
cout << g_a << endl;
cout << g_test.a << endl;
return 0;
}
|
结果如下
静态变量和全局变量都是 0
,这又是为什么?
因为未初始化的全局变量/初始化为 0
的全局变量和静态变量存放在 BSS
段,而 BSS
段在程序执行前会自动清零。
C++中成员变量应该怎么初始化
在构造函数的方法体中初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
using namespace std;
class A{
public:
A () {
cout << "A Construct" << endl;
}
A& operator=(const A& a) {
cout << "A Copy Assignment Construct" << endl;
return *this;
}
};
class B {
public:
B() {
cout << "B Construct" << endl;
a = A();
}
private:
A a;
};
int main(int argc, char *argv[])
{
B b;
return 0;
}
|
结果如下
1
2
3
4
|
A Construct
B Construct
A Construct
A Copy Assignment Construct
|
在构造函数体中进行的不是初始化,而是赋值操作。成员变量的初始化动作发生在进入构造函数之前。
所以在执行 B
的构造函数的时候,会先触发成员变量 a
的初始化,调用了一次构造函数。
接着在构造函数体里面又调用了一次 A
的构造方法,创建了一个对象,然后把新创建的 A
对象赋值给成员变量 a
,又会调用一次拷贝赋值构造函数。
所以你会看到两次 A
的构造函数被调用,一次拷贝赋值函数的调用。
这么多次调用对性能其实有一定的损耗,所以推荐在初始化列表中进行初始化。
在初始化列表中初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
using namespace std;
class A{
public:
A () {
cout << "A Construct" << endl;
}
A& operator=(const A& a) {
cout << "A Copy Assignment Construct" << endl;
return *this;
}
};
class B {
public:
// 这里显示调用 a 的构造方法,是出于调试的原因,如果没有参数,完全可以不写,效果是一样的
B() : a() {
cout << "B Construct" << endl;
}
private:
A a;
};
int main(int argc, char *argv[])
{
B b;
return 0;
}
|
结果如下
1
2
|
A Construct
B Construct
|
可以看到 A
和 B
的构造方法只调用了一次,比在函数体里初始化少了两次调用。
总结
本文对比了 Java
和 C++
变量的初始化,可以看到 C++
明显复杂了许多。
在 C++
中我们推荐在初始化列表中对成员变量进行初始化。
参考