C++ Tutorials: C++ Language: Compound data types: Arrays

C++官方参考链接:Arrays - C++ Tutorials (cplusplus.com)

数组
数组是放置在连续内存位置中的一系列相同类型的元素,可以通过向唯一标识符添加索引来单独引用这些元素。
这意味着,例如,5个int类型的值可以声明为一个数组,而不必声明5个不同的变量(每个变量都有自己的标识符)。相反,使用数组,5个int值存储在连续的内存位置,并且可以使用相同的标识符和适当的索引访问所有5个int值。
例如,一个包含5个int类型的整数值foo的数组可以表示为: 

其中每个空白面板代表数组的一个元素。在本例中,这些是int类型的值。这些元素编号从0到4,第一个是0,最后一个是4;在C++中,数组中的第一个元素总是用0(而不是1)编号,无论它的长度如何。
与普通变量一样,数组在使用之前必须声明。C++中数组的典型声明是: 
type name [elements];

其中type有效类型(如int、float…),name有效标识符elements字段(总是用方括号[]括起来)指定数组的长度,表示元素的数量。
因此,具有5个int类型元素的foo数组可以声明为:
int foo[5];
注意:方括号[]中的elements字段表示数组中元素的数量,必须是常量表达式,因为数组是静态内存块,其大小必须在程序运行之前的编译时确定。 

初始化数组
默认情况下,局部作用域的普通数组(例如,在函数中声明的数组)不进行初始化。这意味着它的任何元素都没有被设置为任何特定的值;它们的内容在声明数组时是不确定的。
但是在声明数组时,数组中的元素可以显式初始化为特定的值,方法是将初始值括在花括号{}中。
例如:
int foo [5] = { 16, 2, 77, 40, 12071 };
这个语句声明了一个数组,它可以像这样表示: 

花括号{}之间的值的数量不应大于数组中元素的数量。例如,在上面的例子中,foo被声明为有5个元素(由方括号[]中所包含的数字指定),而大括号{}正好包含5个值,每个元素一个值。如果用少于元素数量的声明,则将其余元素设置为它们的默认值(对于基本类型,这意味着它们用0填充)。例如:
int bar[5] = { 10, 20, 30 }; 
会创造像下面的数组:

初始化式甚至可以没有值,只有大括号:
int baz [5] = { };
这将创建一个由5个int值组成的数组,每个int值初始化为0:

当为数组提供值的初始化时,C++允许方括号为空[]。在这种情况下,编译器将自动假定数组的大小与括号{}之间包含的值的数量相匹配:
int foo [] = { 16, 2, 77, 40, 12071 };
在这个声明之后,数组foo将是5个int长度长,因为我们提供了5个初始化值。
最后,C++的发展使得数组也采用了统一初始化。因此,声明和初始化器之间不再需要等号。这两个语句是等价的: 
int foo[] = { 10, 20, 30 };
int foo[] { 10, 20, 30 };
静态数组和直接在命名空间中声明的数组(在任何函数之外)总是初始化的。如果没有指定显式初始化式,则所有元素都默认初始化(对于基本类型,使用零)。

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

在前面的例子中,foo有5个元素,每个元素的类型都是int,可以用来引用每个元素的名称如下:

例如,下面的语句将值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的第6个元素,因此实际上超过了数组的大小。
在C++中,超出数组索引的有效范围在语法上是正确的。这可能会产生问题,因为访问超出范围的元素不会在编译时导致错误,但会在运行时导致错误。允许这样做的原因将在后面的章节中介绍指针时看到。
此时,重要的是能够清楚地区分括号[]与数组相关的两种用法。它们执行两种不同的任务:一是在声明数组时指定数组的大小;第二个是在具体数组元素被访问时为它们指定索引。不要混淆括号[]和数组的这两种可能用法。
int foo[5];         // declaration of a new array
foo[2] = 75;        // access to an element of the array. 
主要的区别是,声明之前有元素的类型,而访问则没有。
其他一些有效的数组操作:
foo[0] = a;
foo[a] = 75;
b = foo[a+2];
foo[foo[a]] = foo[2] + 5;
例如:
// arrays example
#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;

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

Jimmy表示每个元素包含5个int类型元素的3个元素的二维数组。它的C++语法是:
int jimmy [3][5]; 
例如,在表达式中垂直引用第二个元素,水平引用第四个元素的方法是:
jimmy[1][3]

(请记住数组下标总是以0开头)。 
多维数组不局限于两个索引(即两个维度)。它们可以根据需要包含任意多的索引。不过要注意:数组所需的内存量会随着每个维度呈指数增长。
例如:
char century [100][365][24][60][60];
在一个世纪中,每秒钟声明一个具有char类型元素的数组。这相当于超过30亿个char!因此,这个声明将消耗超过3GB的内存! 
最后,多维数组对程序员来说只是一种抽象,因为通过乘以它的下标,一个简单的数组也可以得到同样的结果:
int jimmy [3][5];   // is equivalent to
int jimmy [15];     // (3 * 5 = 15) 
与多维数组不同的是,编译器会自动记住每个虚维的深度。以下两段代码产生了完全相同的结果,但其中一段使用了二维数组,而另一段使用了简单数组:

/* multidimensional array */
#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);
    }
}

/* pseudo-multidimensional array */
#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的内存块赋值:

注意,代码使用已定义的宽度和高度常量,而不是直接使用它们的数值。这为代码提供了更好的可读性,并允许在一个地方轻松地更改代码。

数组作为形参
在某些情况下,我们可能需要将数组作为形参传递给函数。在C++中,不可能将数组表示的整个内存块直接作为实参传递给函数。但是可以传递的是它的地址。在实践中,这几乎具有相同的效果,而且是一种更快和更有效的操作。
要接受数组作为函数的形参,可以将形参声明为数组类型,但要使用空括号,省略数组的实际大小。例如:
void procedure (int arg[])
这个函数接受一个名为arg的“int数组”类型的形参。为了传递给这个函数,声明的数组为:
int myarray[40];
写这样一个调用就足够了:
procedure (myarray);
下面是一个完整的例子:
// arrays as parameters
#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])
注意,第一个方括号[]是空的,而下面的方括号为各自的维度指定了大小。为了使编译器能够确定每个附加维度的深度,这是必要的。
在某种程度上,将数组作为实参传递总是会丢失一个维度。背后的原因是,由于历史原因,数组不能直接复制,因此真正传递的是一个指针。这是新手程序员的一个常见错误来源。尽管清楚地理解指针,将在下一章中解释,会有很大帮助。 

库array
上面解释的数组是作为继承自C语言的语言特性直接实现的。它们是一个很好的特性,但是由于限制了它的复制,很容易退化为指针,它们可能会受到过度优化的影响。
为了克服使用语言内置数组的这些问题,C++提供了一种可替代的array类型作为标准容器。它是在头文件<array>中定义的类型模板(实际上是类模板)。
容器是一个库特性,超出了本教程的范围,因此这里不详细解释该类。可以说,它们的操作方式与内置数组类似,只是它们允许被复制(复制整个内存块的实际代价很高的操作,因此要小心使用),并且只有在明确告知要这样做时(通过其成员data)才会衰减为指针。
作为一个例子,这是同一个例子的两个版本,使用本章描述的语言内置数组和库中的容器: 

/* language built-in array */
#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';
}

/* container library array */
#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]。除此之外,主要的区别在于数组的声明,以及为库array包含额外的头文件。还要注意访问库array的大小是多么容易。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

weixin_40186813

你的能量无可限量。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值