C++(四十七):数组的初始值

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)

情况二:你自己房间里的玩具箱 (像局部数组)

  • 现在想象一下,你自己的房间里,妈妈给了你一个空的“小格子玩具箱”。但因为是你自己的,妈妈可能比较忙,没有提前帮你整理
  • 你打开一看,里面可能乱七八糟!也许上一个用这个箱子的人留下了一些碎纸片、灰尘,或者根本不知道是什么的东西(我们叫它“垃圾值”)。
  • 所以: 你自己房间里的这个“玩具箱”,如果你不主动整理,一开始每个格子里可能装着不知道是什么的“垃圾”!直接拿来用可能会搞混。

怎么办呢?主动整理!(显式初始化)

无论是在公共区还是你自己的房间,你都可以主动决定一开始在格子里放什么:

  1. 全部放满指定玩具: 你可以决定从一开始,就把每个格子里都放上你喜欢的特定积木。比如第一个格子放红色,第二个放蓝色……

    • 玩具箱 = {红色积木, 蓝色积木, 绿色积木}
  2. 放一部分,剩下留空(放 0): 你可以只在前面几个格子里放上积木,然后告诉电脑(或者你自己决定),剩下的格子都暂时保持“空”(放个 0)

    • 玩具箱 = {红色积木, 蓝色积木, 0, 0, 0} (假设有5个格子)
  3. 全部留空(放 0): 你可以决定,一开始所有的格子都保持“空”(放个 0),等以后想玩了再放具体的玩具。

    • 玩具箱 = {0, 0, 0, 0, 0} (全部放 0)

重点: 特别是你自己房间里的“玩具箱”(局部数组),最好一开始就主动整理一下(把它全部填满 0,或者放上你想好的东西),这样就不会被里面的“垃圾”搞糊涂啦!


现在我们把刚才的玩具箱画出来:

  1. 公共区的玩具箱(未主动整理,但默认干净):

    [ 0 ] [ 0 ] [ 0 ] [ 0 ] [ 0 ]  <-- 每个格子都是 0 (干净/空)
    (全局/静态数组,默认零初始化)
    
  2. 你自己房间的玩具箱(未主动整理):

    [ ? ] [ ? ] [ ? ] [ ? ] [ ? ]  <-- 每个格子里是啥?不知道!(垃圾值)
    (局部数组,未初始化)
    
    • 这里的 ? 代表“不知道是什么,可能是任何乱七八糟的东西”。
  3. 主动整理的玩具箱:

    • 放满特定玩具:
      [ 红 ] [ 蓝 ] [ 绿 ] [ 黄 ] [ 紫 ]  <-- 放了指定的颜色积木
      (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};)
  • 这样做(初始化)之后,电脑就能准确地知道每个格子里是什么,运行程序时就不会因为拿到“数字垃圾”而算错了!

记住: 就像你玩玩具前先把玩具箱整理好一样,让电脑用“数组”前,先告诉它里面应该放什么,这是一个非常好的习惯!特别是那些“你自己房间里的玩具箱”(局部数组),一定要记得整理哦!

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容