数组(Arrays)

重要的事情说三遍:

强烈建议按照目录结构中的顺序学习!!!点我查看教程目录结构

强烈建议按照目录结构中的顺序学习!!!点我查看教程目录结构

强烈建议按照目录结构中的顺序学习!!!点我查看教程目录结构

数组是一系列相同类型的元素,这些元素放置在连续的内存位置中,可以通过在唯一标识符上添加索引来单独引用。

这意味着,例如,五个 int 类型的值可以声明为一个数组,而不需要声明 5 个不同的变量(每个变量都有自己的标识符)。使用数组,这五个 int 值存储在连续的内存位置中,所有五个值都可以使用相同的标识符和适当的索引进行访问。

例如,一个包含 5 个 int 类型整数值的数组 foo 可以表示为:

arrays1.jpg

其中每个空白面板代表数组的一个元素。在这种情况下,这些都是 int 类型的值。这些元素从 0 到 4 进行编号,0 是第一个,4 是最后一个。在 C++ 中,数组的第一个元素总是编号为 0(而不是 1),无论其长度如何。

像普通变量一样,数组在使用前必须声明。C++ 中数组的典型声明是:

type name [elements];

其中 type 是有效类型(例如 intfloat),name 是有效标识符,而 elements 字段(总是用方括号 [] 括起来)指定数组的长度,以元素的数量表示。

因此,具有五个 int 类型元素的 foo 数组可以声明为:

int foo [5];

注意:方括号 [] 内的 elements 字段表示数组中的元素数量,必须是一个常量表达式,因为数组是静态内存块,其大小必须在编译时确定,在程序运行之前。

初始化数组(Initializing arrays)

默认情况下,本地作用域(例如,在函数内声明)的普通数组未初始化。这意味着在数组声明时,其元素没有设置为任何特定值;它们的内容是不确定的。

但是,可以在声明时通过大括号 {} 显式地将数组元素初始化为特定值。例如:

int foo [5] = { 16, 2, 77, 40, 12071 };

这个语句声明了一个数组,可以表示为:

arrays2.jpg

数组中的值不能超过数组元素的数量。例如,上述例子中的 foo 被声明为具有 5 个元素(由方括号 [] 内的数字指定),大括号 {} 中包含了 5 个值,每个元素一个值。如果声明的值较少,剩余的元素将被设置为默认值(对于基本类型来说,默认值为 0)。例如:

int bar [5] = { 10, 20, 30 };

将创建一个如下的数组:

arrays3.jpg

初始化器甚至可以没有值,只有大括号:

int baz [5] = { };

这将创建一个包含五个 int 值的数组,每个值初始化为 0:

arrays4.jpg

当为数组提供初始化值时,C++ 允许方括号 [] 为空。在这种情况下,编译器会自动假定数组的大小与大括号 {} 中包含的值数量匹配:

int foo [] = { 16, 2, 77, 40, 12071 };

在这个声明之后,数组 foo 的长度为 5,因为我们提供了 5 个初始化值。

最后,C++ 的演变也导致了数组的统一初始化。因此,不再需要在声明和初始化器之间使用等号。这两种语句是等价的:

int foo[] = { 10, 20, 30 };
int foo[] { 10, 20, 30 };

静态数组和直接在命名空间中声明的数组(在任何函数外部)总是被初始化的。如果没有指定显式初始化器,所有元素都将默认初始化(对于基本类型来说,初始化为零)。

访问数组的值

数组中任何元素的值可以像相同类型的普通变量一样访问。语法是:

name[index]

根据前面的示例,foo 有 5 个元素,每个元素的类型为 int,可以用以下名称来引用每个元素:

arrays5.jpg

例如,以下语句将值 75 存储在 foo 的第三个元素中:

foo [2] = 75;

例如,以下语句将 foo 的第三个元素的值复制到名为 x 的变量中:

x = foo[2];

因此,表达式 foo[2] 本身就是一个 int 类型的变量。

请注意,foo 的第三个元素表示为 foo[2],因为第一个是 foo[0],第二个是 foo[1],因此第三个是 foo[2]。同理,最后一个元素是 foo[4]。因此,如果我们写 foo[5],我们将访问 foo 的第六个元素,从而实际上超出了数组的大小。

在 C++ 中,语法上可以超出数组的有效索引范围。这可能会产生问题,因为访问超出范围的元素不会在编译时引发错误,但可能会在运行时引发错误。允许这样做的原因将在引入指针的章节中看到。

此时,重要的是要清楚地区分方括号 [] 与数组相关的两种用途。它们执行两项不同的任务:一个是声明数组时指定数组的大小;另一个是访问具体数组元素时指定索引。不要将这两种方括号 [] 的可能用途与数组混淆。

int foo[5];         // 声明一个新数组
foo[2] = 75;        // 访问数组的一个元素

主要区别在于,声明之前有元素的类型,而访问时没有。

一些其他有效的数组操作:

foo[0] = a;
foo[a] = 75;
b = foo [a+2];
foo[foo[a]] = foo[2] + 5;

例如:

// 数组示例
#include <iostream>
using namespace std;

int foo [] = {16, 2, 77, 40, 12071};
int n, result=0;

int main ()
{
  for ( n=0 ; n<5 ; ++n )
  {
    result += foo[n];
  }
  cout << result;
  return 0;
}

多维数组(Multidimensional arrays)

多维数组可以描述为“数组的数组”。例如,一个二维数组可以被想象为由相同数据类型的元素组成的二维表。

bidimensional_arrays1.jpg

jimmy 表示一个 3 行 5 列的 int 类型的二维数组。C++ 的语法是:

int jimmy [3][5];

例如,引用垂直第二行和水平第四列的元素的方式是:

jimmy[1][3]

bidimensional_arrays2.jpg

(记住数组索引总是从零开始)。

多维数组不限于两个索引(即两个维度)。它们可以包含任意多个索引。但要小心:数组所需的内存量随着每个维度的增加而呈指数增长。例如:

char century [100][365][24][60][60];

声明了一个 char 类型的数组,每秒一个元素。这相当于超过 30 亿个 char!所以这个声明将消耗超过 3GB 的内存!

最终,多维数组对于程序员来说只是一个抽象,因为通过乘以其索引可以用一个简单的数组实现相同的结果:

int jimmy [3][5];   // 相当于
int jimmy [15];     // (3 * 5 = 15)

唯一的区别是,使用多维数组时,编译器会自动记住每个假想维度的深度。以下两段代码产生完全相同的结果,但一个使用二维数组,另一个使用简单数组:

// 多维数组

#define WIDTH 5  
#define HEIGHT 3  
  
int jimmy [HEIGHT][WIDTH];  
int n,m;  
  
int main ()  
{  
  for (n=0; n<HEIGHT; n++)  
    for (m=0; m<WIDTH; m++)  
    {  
      jimmy[n][m]=(n+1)*(m+1);  
    }  
}

// 伪多维数组

#define WIDTH 5  
#define HEIGHT 3  
  
int jimmy [HEIGHT * WIDTH];  
int n,m;  
  
int main ()  
{  
  for (n=0; n<HEIGHT; n++)  
    for (m=0; m<WIDTH; m++)  
    {  
      jimmy[n*WIDTH+m]=(n+1)*(m+1);  
    }  
}

以上两段代码均不会在屏幕上产生任何输出,但都以如下方式为名为 jimmy 的内存块赋值:

bidimensional_arrays3.jpg

注意,代码使用定义的常量来表示宽度和高度,而不是直接使用它们的数值。这使得代码更具可读性,并且可以在一个地方轻松进行更改。

作为参数的数组

在某些时候,我们可能需要将数组作为参数传递给函数。在 C++ 中,不能将数组代表的整个内存块直接作为参数传递给函数。但可以传递它的地址。实际上,这几乎具有相同的效果,而且操作更快、更高效。

要接受一个数组作为函数参数,可以将参数声明为数组类型,但使用空括号,省略数组的实际大小。例如:

void procedure (int arg[])

这个函数接受一个类型为“int 数组”的参数 arg。为了将一个声明为:

int myarray [40];

数组传递给这个函数,只需这样调用:

procedure (myarray);

这里有一个完整的示例:

// 作为参数的数组
#include <iostream>
using namespace std;

void printarray (int arg[], int length) {
  for (int n=0; n<length; ++n)
    cout << arg[n] << ' ';
  cout << '\n';
}

int main ()
{
  int firstarray[] = {5, 10, 15};
  int secondarray[] = {2, 4, 6, 8, 10};
  printarray (firstarray,3);
  printarray (secondarray,5);
}

在上面的代码中,第一个参数 (int arg[]) 接受任何元素为 int 的数组,无论其长度。因此,我们包括了第二个参数,该参数告诉函数我们传递给它的每个数组的长度。这样可以使打印数组的 for 循环知道要在传递的数组中迭代的范围,而不会超出范围。

在函数声明中,还可以包含多维数组。三维数组参数的格式是:

base_type[][depth][depth]

例如,一个以多维数组作为参数的函数可以是:

void procedure (int myarray[][3][4])

注意,第一个括号 [] 留空,而后面的括号指定其各自维度的大小。这是必要的,以便编译器能够确定每个附加维度的深度。

某种程度上,作为参数传递数组总是会丢失一个维度。其背后的原因是,由于历史原因,数组不能直接复制,因此实际上传递的是指针。这是新手程序员常犯的错误来源。尽管清楚理解指针(在后续章节中解释)会有很大帮助。

库数组(Library arrays)

上述数组是直接作为语言特性实现的,继承自 C 语言。它们是一个很好的特性,但由于其复制限制和容易退化为指针,可能过于优化。

为了解决内置数组的某些问题,C++ 提供了一个替代的数组类型作为标准容器。它是头文件 <array> 中定义的类型模板(实际上是类模板)。

容器是库特性,不在本教程范围内,因此这里不会详细解释类。只需说它们的操作方式与内置数组相似,不同的是它们允许被复制(实际上是一个昂贵的操作,复制整个内存块,因此要谨慎使用),并且只有在明确要求时才会退化为指针(通过其成员 data)。

仅举例说明,这里有两个版本的相同示例,一个使用本章中描述的语言内置数组,另一个使用库中的容器:

// 语言内置数组
#include <iostream>
using namespace std;

int main()
{
  int myarray[3] = {10, 20, 30};
  
  for (int i = 0; i < 3; ++i)
    ++myarray[i];
  
  for (int elem : myarray)
    cout << elem << '\n';
}

// 库数组
#include <iostream>
#include <array>
using namespace std;

int main()
{
  array<int, 3> myarray {10, 20, 30};
  
  for (int i = 0; i < myarray.size(); ++i)
    ++myarray[i];
  
  for (int elem : myarray)
    cout << elem << '\n';
}

如你所见,这两种数组都使用相同的语法来访问其元素:myarray[i]。除此之外,主要区别在于数组的声明,以及为库数组包含了一个额外的头文件。注意到,库数组访问大小也很方便。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值