添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

数组是相同类型的对象序列,它们占据一块连续的内存区。 传统的 C 样式数组是许多 bug 的根源,但至今仍很常用,尤其是在较旧的代码库中。 在新式 C++ 中,我们强烈建议使用 std::vector std::array ,而不是本部分所述的 C 样式数组。 这两种标准库类型都将其元素存储为连续的内存块。 但是,它们提供更高的类型安全性,并支持保证指向序列中有效位置的迭代器。 有关详细信息,请参阅 容器

在 C++ 数组声明中,数组大小在变量名称之后指定,而不是像在其他某些语言中那样的在类型名称之后指定。 以下示例声明了要在堆栈上分配的 1000 个双精度值的数组。 元素数量必须以整数文本或常量表达式的形式提供。 这是因为,编译器必须知道要分配多少堆栈空间;它不能使用在运行时计算的值。 为数组中的每个元素分配默认值 0。 如果你不指定默认值,则每个元素最初包含恰好位于该内存位置的任意随机值。

    constexpr size_t size = 1000;
    // Declare an array of doubles to be allocated on the stack
    double numbers[size] {0};
    // Assign a new value to the first element
    numbers[0] = 1;
    // Assign a value to each subsequent element
    // (numbers[1] is the second element in the array.)
    for (size_t i = 1; i < size; i++)
        numbers[i] = numbers[i-1] * 1.1;
    // Access each element
    for (size_t i = 0; i < size; i++)
        std::cout << numbers[i] << " ";

数组中的第一个元素是第 0 个元素。 最后一个元素是 (n-1) 元素,其中 n 是数组可以包含的元素数量。 声明中的元素数量必须是整数类型,且必须大于 0。 你需要负责确保程序永远不会将大于 (size - 1) 的值传递给下标运算符。

仅当数组是 structunion 中的最后一个字段并启用了 Microsoft 扩展(未设置 /Za/permissive-)时,零大小的数组才是合法的。

分配和访问基于堆栈的数组的速度比基于堆的数组更快。 但是,堆栈空间是有限的。 数组元素的数量不能太大,以免占用过多的堆栈内存。 多大的数量算作“太大”取决于程序。 可以使用分析工具来确定数组是否太大。

你可能需要一个太大的、以致无法在堆栈上分配的数组,或者需要一个其大小在编译时未知的数组。 可以使用 new[] 表达式在堆上分配此数组。 运算符返回指向第一个元素的指针。 下标运算符处理指针变量的方式与处理基于堆栈的数组的方式相同。 还可以使用指针算术将指针移到数组中的任意元素。 你需要负责确保:

  • 始终保留原始指针地址的副本,以便可以在不再需要数组时删除内存。
  • 不会将指针地址递增或递减至超过数组边界。
  • 以下示例演示在运行时如何在堆上定义一个数组。 其中演示了如何使用下标运算符和指针算术来访问数组元素:

    void do_something(size_t size)
        // Declare an array of doubles to be allocated on the heap
        double* numbers = new double[size]{ 0 };
        // Assign a new value to the first element
        numbers[0] = 1;
        // Assign a value to each subsequent element
        // (numbers[1] is the second element in the array.)
        for (size_t i = 1; i < size; i++)
            numbers[i] = numbers[i - 1] * 1.1;
        // Access each element with subscript operator
        for (size_t i = 0; i < size; i++)
            std::cout << numbers[i] << " ";
        // Access each element with pointer arithmetic
        // Use a copy of the pointer for iterating
        double* p = numbers;
        for (size_t i = 0; i < size; i++)
            // Dereference the pointer, then increment it
            std::cout << *p++ << " ";
        // Alternate method:
        // Reset p to numbers[0]:
        p = numbers;
        // Use address of pointer to compute bounds.
        // The compiler computes size as the number
        // of elements * (bytes per element).
        while (p < (numbers + size))
            // Dereference the pointer, then increment it
            std::cout << *p++ << " ";
        delete[] numbers; // don't forget to do this!
    int main()
        do_something(108);
    

    初始化数组

    可以在循环中、以每次一个元素的方式或者在单个语句中初始化数组。 以下两个数组的内容是相同的:

        int a[10];
        for (int i = 0; i < 10; ++i)
            a[i] = i + 1;
        int b[10]{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    

    将数组传递给函数

    将数组传递给函数时,该数组将作为指向第一个元素的指针传递,无论它是基于堆栈的数组还是基于堆的数组。 指针不包含任何其他大小或类型信息。 此行为称为指针衰减。 将数组传递给函数时,始终必须在单独的参数中指定元素数量。 此行为还意味着将数组传递给函数时不会复制数组元素。 若要防止函数修改元素,请将参数指定为指向 const 元素的指针。

    以下示例演示了一个接受数组和长度的函数。 指针指向原始数组而不是副本。 由于参数不是 const,因此该函数可以修改数组元素。

    void process(double *p, const size_t len)
        std::cout << "process:\n";
        for (size_t i = 0; i < len; ++i)
            // do something with p[i]
    

    将数组参数 p 声明并定义为 const,使其在函数块中为只读:

    void process(const double *p, const size_t len);
    

    也可以用这些方式声明相同的函数,而无需改变行为。 数组仍作为指向第一个元素的指针传递:

    // Unsized array
    void process(const double p[], const size_t len);
    // Fixed-size array. Length must still be specified explicitly.
    void process(const double p[1000], const size_t len);
    

    从其他数组构造的数组是多维数组。 通过按顺序放置多个括起来的常数表达式来指定这些多维数组。 例如,考虑此声明:

    int i2[5][7];
    

    它指定类型为 int 的数组,从概念上以五行七列的二维矩阵排列,如下图所示:

    该图像是一个 7 个单元格宽、5 个单元格高的网格。 每个单元格都包含单元格的索引。 第一个单元格索引标记为 0,0。 该行中的下一个单元格是 0,1,依此类推,该行中的最后一个单元格是 0,6。 下一行以索引 1,0 开始。 之后单元格的索引为 1,1。 该行最后一个单元格的索引为 1,6。 此模式会重复,直到最后一行(从索引 4,0 开始)。 最后一行的最后一个单元格索引为 4,6。 :::image-end

    可以声明具有初始化表达式列表的多维数组(如初始化表达式中所述)。 在这些声明中,可以省略指定第一维的边界的常数表达式。 例如:

    // arrays2.cpp
    // compile with: /c
    const int cMarkets = 4;
    // Declare a float that represents the transportation costs.
    double TransportCosts[][cMarkets] = {
       { 32.19, 47.29, 31.99, 19.11 },
       { 11.29, 22.49, 33.47, 17.29 },
       { 41.97, 22.09,  9.76, 22.55 }
    

    前面的声明定义四列三行的数组。 行表示工厂,列表示从工厂装运到的市场。 值是从工厂运输到市场的成本。 忽略数组的第一个维度,但编译器会通过检查该初始值设定项来填充它。

    对 n 维数组类型使用间接寻址运算符 (*) 将生成 n-1 维数组。 如果 n 为 1,则将生成标量(或数组元素)。

    C++ 数组按行优先顺序存储。 行优先顺序意味着最后一个下标变化最快。

    还可以在函数声明中省略多维数组第一个维的边界规范,如下所示:

    // multidimensional_arrays.cpp
    // compile with: /EHsc
    // arguments: 3
    #include <limits>   // Includes DBL_MAX
    #include <iostream>
    const int cMkts = 4, cFacts = 2;
    // Declare a float that represents the transportation costs
    double TransportCosts[][cMkts] = {
       { 32.19, 47.29, 31.99, 19.11 },
       { 11.29, 22.49, 33.47, 17.29 },
       { 41.97, 22.09,  9.76, 22.55 }
    // Calculate size of unspecified dimension
    const int cFactories = sizeof TransportCosts /
                      sizeof( double[cMkts] );
    double FindMinToMkt( int Mkt, double myTransportCosts[][cMkts], int mycFacts);
    using namespace std;
    int main( int argc, char *argv[] ) {
       double MinCost;
       if (argv[1] == 0) {
          cout << "You must specify the number of markets." << endl;
          exit(0);
       MinCost = FindMinToMkt( *argv[1] - '0', TransportCosts, cFacts);
       cout << "The minimum cost to Market " << argv[1] << " is: "
           << MinCost << "\n";
    double FindMinToMkt(int Mkt, double myTransportCosts[][cMkts], int mycFacts) {
       double MinCost = DBL_MAX;
       for( size_t i = 0; i < cFacts; ++i )
          MinCost = (MinCost < TransportCosts[i][Mkt]) ?
             MinCost : TransportCosts[i][Mkt];
       return MinCost;
    
    The minimum cost to Market 3 is: 17.29
    

    编写函数 FindMinToMkt,以便添加不需要更改任何代码而仅需重新编译的新工厂。

    初始化数组

    具有类构造函数的对象数组由构造函数初始化。 如果初始化表达式列表中的项少于数组中的元素,则默认的构造函数将用于剩余元素。 如果没有为类定义默认构造函数,初始化表达式列表必须完整,即数组中的每个元素都必须有一个初始化表达式

    考虑定义了两个构造函数的Point 类:

    // initializing_arrays1.cpp
    class Point
    public:
       Point()   // Default constructor.
       Point( int, int )   // Construct from two ints
    // An array of Point objects can be declared as follows:
    Point aPoint[3] = {
       Point( 3, 3 )     // Use int, int constructor.
    int main()
    

    aPoint 的第一个元素是使用构造函数 Point( int, int ) 构造的;剩余的两个元素是使用默认构造函数构造的。

    静态成员数组(是否为 const)可在其定义中进行初始化(类声明的外部)。 例如:

    // initializing_arrays2.cpp
    class WindowColors
    public:
        static const char *rgszWindowPartList[7];
    const char *WindowColors::rgszWindowPartList[7] = {
        "Active Title Bar", "Inactive Title Bar", "Title Bar Text",
        "Menu Bar", "Menu Bar Text", "Window Background", "Frame"   };
    int main()
    

    访问数组元素

    您可以使用数组下标运算符 ([ ]) 访问数组的各个元素。 如果使用不带下标的一维数组的名称,它将评估为指向数组第一个元素的指针。

    // using_arrays.cpp
    int main() {
       char chArray[10];
       char *pch = chArray;   // Evaluates to a pointer to the first element.
       char   ch = chArray[0];   // Evaluates to the value of the first element.
       ch = chArray[3];   // Evaluates to the value of the fourth element.
    

    使用多维数组时,可以在表达式中使用各种组合。

    // using_arrays_2.cpp
    // compile with: /EHsc /W1
    #include <iostream>
    using namespace std;
    int main() {
       double multi[4][4][3];   // Declare the array.
       double (*p2multi)[3];
       double (*p1multi);
       cout << multi[3][2][2] << "\n";   // C4700 Use three subscripts.
       p2multi = multi[3];               // Make p2multi point to
                                         // fourth "plane" of multi.
       p1multi = multi[3][2];            // Make p1multi point to
                                         // fourth plane, third row
                                         // of multi.
    

    在前面的代码中,multidouble 类型的三维数组。 p2multi 指针指向大小为三的 double 类型的数组。 在此示例中,该数组与一个、两个和三个下标一起使用。 尽管更为常见的是指定所有下标(如在 cout 语句中),但有时选择数组元素的特定子集会很有用,如 cout 后面的语句中所示。

    重载下标运算符

    与其他运算符相似,下标运算符 ([]) 也可由用户重新定义。 如果没有重载下标运算符,下标运算符的默认行为是使用以下方法组合数组名称和下标:

    *((array_name) + (subscript))

    像涉及指针类型的所有加法中一样,缩放将自动执行以调整类型的大小。 结果值不是来自 array_name 的 n 个字节,而是数组的第 n 个元素。 有关此转换的详细信息,请参阅加法运算符

    同样,对于多维数组,将使用以下方法获取地址:

    ((array_name) + (subscript1 * max2 * max3 * ... * maxn) + (subscript2 * max3 * ... * maxn) + ... + subscriptn))

    表达式中的数组

    当数组类型的标识符出现在 sizeof、address-of (&) 或引用的初始化以外的表达式中时,该标识符将转换为指向第一个数组元素的指针。 例如:

    char szError1[] = "Error: Disk drive not ready.";
    char *psz = szError1;
    

    指针 psz 指向数组 szError1 的第一个元素。 与指针不同,数组不是可修改的左值。 因此,以下赋值是非法的:

    szError1 = psz;
    

    std::array