初始化(1)

Updated: 2018-12-29

入门

C++中数组分静态和动态两种。静态数组大小固定,编译时(compile-time)就分配好了,存在 stack 中:

int a[5] = {1, 2, 3, 4, 5};
for (int i = 0; i < 5; i++) {
  cout << a[i] << " ";
}

// 1 2 3 4 5

动态数组则是运行时(run-time)才分配,存在heap中,大小可变,但切记要用后删除否则可能会内存泄漏:

int *a = new int[5];
for (int i = 0; i < 5; i++) {
  cout << a[i] << " ";
}
delete[] a;

// 0 0 0 0 0

C++11 之后可以方便的初始化一个动态数组:

int *a = new int[5]{1, 2, 3, 4, 5};
for (int i = 0; i < 5; i++) {
  cout << a[i] << " ";
}
delete[] a;

// 编译时可能需要加上 --std c++11
// $ clang++ --std c++11 tmp.cpp; ./a.out
// 1 2 3 4 5

Java 的语法跟 C++很像,但完全不需要担心内存的管理:

jshell> int[] a = new int[5]
a ==> int[5] { 0, 0, 0, 0, 0 }

坑!

1 号坑

貌似 Java 自动将数组元素全部初始化为 0,那如果不初始化 C++的数组会怎样?

int a[5];
for (int i = 0; i < 5; i++) {
  cout << a[i] << " ";
}

结果却是...

// 4196528 0 4196160 0 -258455680

2 号坑

数组越界的时候,Java 非常省心的抛了一个错:

jshell> int a[] = new int[5]
a ==> int[5] { 0, 0, 0, 0, 0 }

jshell> a[10]
|  Exception java.lang.ArrayIndexOutOfBoundsException: Index 10 out of bounds for length 5
...

C++却有返回值:

int a[5] = {0};
cout << a[10];

// -1195833

3 号坑

听说这样就可以将静态数组全部初始化为 0:

int a[5] = {0};

那是不是这样就可以全部初始化为 1?

int a[5] = {1}

结果是...

1 0 0 0 0

4 号坑

类比 Java 的数组,生成一个大小为 5 的ArrayList是不是应该这样:

jshell> List<Integer> a = new ArrayList<>(5);

但读取的时候缺产生了这样的错误:

jshell> a.get(0)
|  Exception java.lang.IndexOutOfBoundsException: Index 0 out of bounds for length 0
|        at Preconditions.outOfBounds (Preconditions.java:64)
...

进阶

C/C++是比Java更底层一点的语言,它可以直接操纵内存,而Java是跑在JVM虚拟机里。C/C++如果不对数组进行初始化,声明了数组只是知道了数组起始的内存地址。由于这段内存可能之前被用在别的程序,所以直接打印出来不全是0也不足为奇;而由于地址是连续的,a[10]只是说从起始地址位移十个单位,所以它是可以读取到内存里的值的。是不是感觉C++在内存管理这方面略微可怕?

int a[5] = {1}的意思是把第一个初始化为1,其他没注明的就为0。

new ArrayList<>(5);里的5设的是初始容量(capacity),这是区别于数组大小(size)的: capacity是当前数组可能的最大长度,而size是实际上ArrayList里有多少个元素,当size要超过capacity的时候,ArrayList会悄悄扩大它下面的数组。

分步来看,首先a的size为0,加入一个数后变为1

jshell> List<Integer> a = new ArrayList<>(5);
a ==> []

jshell> a.size()
$1 ==> 0

jshell> a.add(1)
$2 ==> true

jshell> a.size()
$3 ==> 1

Capacity是不直接可见的,但通过reflection可以看到,当前为5,是初始化时设置的:

jshell> import java.lang.reflect.Field;

jshell> Field field = ArrayList.class.getDeclaredField("elementData");
field ==> transient java.lang.Object[] java.util.ArrayList.elementData

jshell> field.setAccessible(true);

jshell> field.get(a)
$7 ==> Object[5] { 1, null, null, null, null }

jshell> int capacity = ((Object[])field.get(a)).length
capacity ==> 5

继续添加元素,发现capacity自动从5升到了7,而数组的size为6:

jshell> a.addAll(Arrays.asList(2, 3, 4, 5, 6))

jshell> field.get(a)
$10 ==> Object[7] { 1, 2, 3, 4, 5, 6, null }

jshell> int capacity = ((Object[])field.get(a)).length
capacity ==> 7

jshell> int size = a.size()
size ==> 6