前言

最近发现在 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);
    }

}

结果如下

1
0

可以看到 int 类型的成员变量默认为 0 。为什么类的成员变量就有默认值呢?

这就要说到 JVM 的类加载机制了,这里简单说一下

  1. 类的加载过程主要包括装载、链接、初始化
  2. 装载和链接就不说了,给成员变量赋值是在初始化的过程中,会对成员变量赋零值

数据类型的零值如下:

数据类型 零值
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);
    }
}

结果如下

1
0

全局静态变量和成员变量一样在初始化的阶段进行赋值,有一种情况比较特殊的就是常量会在链接阶段的准备环节就赋值。具体的可以看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;
}

结果如下

1
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;
}

结果如下

1
22094

结果就不一样了,这个才是正常情况下应该有的,不要抱侥幸心理,以为本应该是 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
1320706850

成员变量和局部变量一样。

全局变量和静态变量

 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;
}

结果如下

1
2
3
0
0
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

可以看到 AB 的构造方法只调用了一次,比在函数体里初始化少了两次调用。

总结

本文对比了 JavaC++ 变量的初始化,可以看到 C++ 明显复杂了许多。

C++ 中我们推荐在初始化列表中对成员变量进行初始化。

参考