C++ 数组初始值
在 C++ 中,数组是一系列相同类型元素的集合,存储在连续的内存空间中。数组的初始化是指在创建数组时为其元素赋予初始值的过程。数组元素的初始值取决于数组的存储期(storage duration)和初始化方式。
1. 存储期决定默认初始化行为(未显式初始化时):
-
静态存储期 (Static Storage Duration):
- 包括全局数组(在任何函数之外定义)和使用
static
关键字在函数内部或类中定义的数组。 - 如果没有显式提供初始化列表,这些数组的元素会被自动零初始化。
- “零初始化”意味着:
- 对于基本数值类型(
int
,float
,double
,char
等),元素被初始化为0
。 - 对于指针类型,元素被初始化为
nullptr
。 - 对于
bool
类型,元素被初始化为false
。 - 对于类类型的元素,会调用其默认构造函数;如果默认构造函数不存在或不可访问,则程序无法编译(对于聚合类型,会进行成员的零初始化)。
- 对于基本数值类型(
#include <iostream> int globalArr[5]; // 全局数组,静态存储期 void func() { static int staticLocalArr[3]; // 静态局部数组,静态存储期 std::cout << "Static local array elements: "; for (int i = 0; i < 3; ++i) { std::cout << staticLocalArr[i] << " "; // 输出: 0 0 0 } std::cout << std::endl; } int main() { std::cout << "Global array elements: "; for (int i = 0; i < 5; ++i) { std::cout << globalArr[i] << " "; // 输出: 0 0 0 0 0 } std::cout << std::endl; func(); return 0; }
- 包括全局数组(在任何函数之外定义)和使用
-
自动存储期 (Automatic Storage Duration):
- 指在函数内部定义的、未使用
static
关键字的局部数组。 - 如果没有显式提供初始化列表,这些数组的元素将拥有不确定的值(垃圾值)。读取这些未初始化的值会导致未定义行为(Undefined Behavior, UB)。
- 必须显式初始化局部数组才能保证其元素具有可预测的值。
#include <iostream> int main() { int localArr[5]; // 局部数组,自动存储期,未初始化 std::cout << "Local array elements (uninitialized): "; for (int i = 0; i < 5; ++i) { // 输出的值是不确定的(可能是任意随机值) // 严格来说,读取未初始化的值是未定义行为 std::cout << localArr[i] << " "; } std::cout << std::endl; // 程序的行为可能因编译器、优化级别和运行环境而异 return 0; }
- 指在函数内部定义的、未使用
-
动态存储期 (Dynamic Storage Duration):
- 指使用
new
操作符在堆上分配内存的数组。 - 如果使用
new T[N]
(没有括号),元素也是未初始化的(具有不确定值),除非T
是具有非平凡(non-trivial)默认构造函数的类类型,这种情况下会调用默认构造函数。 - 如果使用
new T[N]()
(带有括号),则元素会被值初始化(value-initialized)。- 对于基本类型和指针,这意味着零初始化。
- 对于类类型,会调用默认构造函数(如果存在且可访问),否则进行零初始化。
#include <iostream> int main() { int* dynamicArr1 = new int[5]; // 动态数组,未初始化 int* dynamicArr2 = new int[5](); // 动态数组,值初始化(全为0) std::cout << "Dynamic array 1 (uninitialized): "; for (int i = 0; i < 5; ++i) { std::cout << dynamicArr1[i] << " "; // 输出不确定值 (UB) } std::cout << std::endl; std::cout << "Dynamic array 2 (value-initialized): "; for (int i = 0; i < 5; ++i) { std::cout << dynamicArr2[i] << " "; // 输出: 0 0 0 0 0 } std::cout << std::endl; delete[] dynamicArr1; delete[] dynamicArr2; return 0; }
- 指使用
2. 显式初始化方式:
无论数组具有何种存储期,都可以使用初始化列表 {}
来显式指定元素的初始值。
-
完全初始化: 提供与数组大小相同数量的初始值。
int arr[5] = {1, 2, 3, 4, 5}; // 所有元素都被显式初始化 char name[6] = {'H', 'e', 'l', 'l', 'o', '\0'}; // C风格字符串
-
部分初始化: 提供的初始值数量少于数组大小。
- 对于具有静态或动态存储期的数组(以及使用
{}
初始化的自动存储期数组),未被显式初始化的剩余元素会被自动零初始化。
int arr[5] = {1, 2}; // 初始化为 {1, 2, 0, 0, 0} float fArr[4] = {3.14f}; // 初始化为 {3.14f, 0.0f, 0.0f, 0.0f}
- 对于具有静态或动态存储期的数组(以及使用
-
全部零初始化: 使用
{0}
或(自 C++11 起)空初始化列表{}
。int arr1[5] = {0}; // 所有元素初始化为 0 int arr2[5] = {}; // C++11及以后: 所有元素初始化为 0 (推荐) double dArr[3] = {}; // 所有元素初始化为 0.0
注意:
{}
对于局部数组(自动存储期)尤其重要,因为它可以确保所有元素都被初始化为零,避免了垃圾值。 -
省略数组大小: 如果提供了初始化列表,可以省略数组的大小,编译器会自动根据初始化列表中的元素数量推断数组大小。
int arr[] = {10, 20, 30}; // 编译器推断 arr 的大小为 3
-
字符串字面量初始化
char
数组:char str1[6] = "Hello"; // 自动包含末尾的空字符 '\0'。数组大小必须足够容纳字符串和'\0'。初始化为 {'H','e','l','l','o','\0'} char str2[] = "World"; // 编译器推断大小为 6 (包括 '\0')。初始化为 {'W','o','r','l','d','\0'} // char str3[3] = "TooLong"; // 错误!数组大小不足
总结:
- 全局和
static
数组默认零初始化。 - 局部(非
static
)数组默认不初始化(包含垃圾值)。强烈建议总是显式初始化局部数组。 - 动态数组
new T[N]
默认不初始化(除非 T 有非平凡默认构造函数);new T[N]()
进行值初始化(通常是零初始化)。 - 使用初始化列表
{}
是最通用的显式初始化方法,部分初始化时剩余元素会被零初始化。 -
{}
(C++11) 或{0}
是将数组所有元素初始化为零的简洁方法。
想象一下,你有一个玩具箱,这个玩具箱很特别,里面有很多固定的小格子,每个格子只能放一种类型的玩具,比如都放积木,或者都放小汽车。这个有很多小格子的玩具箱,就像我们电脑里的“数组” (Array
)。
现在,我们来看看拿到一个新的“小格子玩具箱”时,里面可能是什么情况:
情况一:在公共区域的玩具箱 (像全局/静态数组)
- 想象一下,在幼儿园的公共游戏区,老师放了一个新的“小格子玩具箱”。为了安全和整齐,老师在还没让小朋友玩之前,会把每个小格子都检查一遍,如果里面是空的或者乱七八糟,老师会把它清理干净,可能暂时放一个表示“空”的小牌子(比如数字 0)在里面。
- 所以: 这种放在公共区域、大家都能看到的“玩具箱”,一开始每个格子里都是干净的、空的(或者说里面是 0)。
情况二:你自己房间里的玩具箱 (像局部数组)
- 现在想象一下,你自己的房间里,妈妈给了你一个空的“小格子玩具箱”。但因为是你自己的,妈妈可能比较忙,没有提前帮你整理。
- 你打开一看,里面可能乱七八糟!也许上一个用这个箱子的人留下了一些碎纸片、灰尘,或者根本不知道是什么的东西(我们叫它“垃圾值”)。
- 所以: 你自己房间里的这个“玩具箱”,如果你不主动整理,一开始每个格子里可能装着不知道是什么的“垃圾”!直接拿来用可能会搞混。
怎么办呢?主动整理!(显式初始化)
无论是在公共区还是你自己的房间,你都可以主动决定一开始在格子里放什么:
-
全部放满指定玩具: 你可以决定从一开始,就把每个格子里都放上你喜欢的特定积木。比如第一个格子放红色,第二个放蓝色……
玩具箱 = {红色积木, 蓝色积木, 绿色积木}
-
放一部分,剩下留空(放 0): 你可以只在前面几个格子里放上积木,然后告诉电脑(或者你自己决定),剩下的格子都暂时保持“空”(放个 0)。
-
玩具箱 = {红色积木, 蓝色积木, 0, 0, 0}
(假设有5个格子)
-
-
全部留空(放 0): 你可以决定,一开始所有的格子都保持“空”(放个 0),等以后想玩了再放具体的玩具。
-
玩具箱 = {0, 0, 0, 0, 0}
(全部放 0)
-
重点: 特别是你自己房间里的“玩具箱”(局部数组),最好一开始就主动整理一下(把它全部填满 0,或者放上你想好的东西),这样就不会被里面的“垃圾”搞糊涂啦!
现在我们把刚才的玩具箱画出来:
-
公共区的玩具箱(未主动整理,但默认干净):
[ 0 ] [ 0 ] [ 0 ] [ 0 ] [ 0 ] <-- 每个格子都是 0 (干净/空) (全局/静态数组,默认零初始化)
-
你自己房间的玩具箱(未主动整理):
[ ? ] [ ? ] [ ? ] [ ? ] [ ? ] <-- 每个格子里是啥?不知道!(垃圾值) (局部数组,未初始化)
- 这里的
?
代表“不知道是什么,可能是任何乱七八糟的东西”。
- 这里的
-
主动整理的玩具箱:
-
放满特定玩具:
[ 红 ] [ 蓝 ] [ 绿 ] [ 黄 ] [ 紫 ] <-- 放了指定的颜色积木 (int arr[] = {1, 2, 3, 4, 5};)
-
放一部分,剩下是 0:
[ 红 ] [ 蓝 ] [ 0 ] [ 0 ] [ 0 ] <-- 前面放了玩具,后面是 0 (空) (int arr[5] = {1, 2};)
-
全部放 0:
[ 0 ] [ 0 ] [ 0 ] [ 0 ] [ 0 ] <-- 所有格子都是 0 (空) (int arr[5] = {0}; 或 int arr[5] = {};)
-
放满特定玩具:
看图理解: 是不是画出来就很清楚了?那些带 ?
的格子看起来就很不靠谱,对吧?所以我们最好让格子里一开始就有明确的东西,哪怕是 0
。
好了,现在我们来总结一下电脑是怎么想的:
- 电脑里有一种东西叫“数组”(Array),就像我们说的有很多小格子的“玩具箱”。
- 每个“格子”里可以放一个数字或者其他信息。
- 重要的事情: 电脑需要知道每个格子里一开始放的是什么数字。
- 如果这个“数组”放在一个公共的地方(全局作用域或用了
static
),电脑比较负责,会自动把所有格子都填上 0。就像幼儿园老师会清理公共玩具箱一样。 - 但如果这个“数组”是你临时在程序的一个小部分里用(函数局部作用域,非
static
),电脑觉得这是你的“私人地盘”,它就不管了!格子里可能残留着之前程序运行留下的“数字垃圾”(不确定的值)。 - 所以,为了让电脑程序不出错,特别是对于临时的“数组”,我们最好主动告诉电脑:
- “嘿,电脑,把我这个数组的所有格子都填满 0!” (
= {0};
或= {};
) - 或者,“电脑,把我这个数组的格子按顺序填上 1、2、3...” (
= {1, 2, 3, ...};
) - 或者,“电脑,前几个格子填上 1、2,剩下的都填 0!” (
= {1, 2};
)
- “嘿,电脑,把我这个数组的所有格子都填满 0!” (
- 这样做(初始化)之后,电脑就能准确地知道每个格子里是什么,运行程序时就不会因为拿到“数字垃圾”而算错了!
记住: 就像你玩玩具前先把玩具箱整理好一样,让电脑用“数组”前,先告诉它里面应该放什么,这是一个非常好的习惯!特别是那些“你自己房间里的玩具箱”(局部数组),一定要记得整理哦!