C++数组的存储|C++数组所占内存空间

我们以前说变量像箱子,数组像仓库。在这一节,我们要来深入探究一下,这些“箱子”和“仓库”在电脑内部是怎样摆放的。

内存和地址

我们知道变量和数组都是放在内存里的,我们有时候还能够听到内存地址(Address)这个词。那么地址究竟是什么意思呢?

其实在内存里,就像是许许多多的街道,每条街道有它的名字,而街道上的每幢房子又按顺序地编了号,于是街道名和房子在街道上的编号就能确定内存中唯一的一幢房子,我们在这里认为所有的数据在内存中都是放在房子里。电脑就是依照这个原理找到所要的访问或修改的数据的。街道名和房子在街道上的编号就称为这个房子的地址。我们通常把地址表示为一串十六进制的数。关于十六进制数我们在这里不作展开说明。

那么这些内存中的房子和我们所说的变量和数组是什么关系呢?在内存里的房子的大小是规定的,每幢房子只能存储一个字节(Byte)的数据。(一个字节相当于一个半角的英文字母,一个汉字需要占用两个字节。)有时候,一种类型的变量需要比较大的空间,比如一个浮点型的实数,一幢房子是放不下的,而是需要4幢房子的空间才能放得下。于是电脑就把连起来的4幢房子拼起来,每幢房子放这个实数的一部分数据。而这连起来的4幢房子,构成了一个能够存放浮点型实数的变量。

我们认为,内存中的“房子”是客观存在的,每幢房子的大小一样,没有任何区别;而不同类型的变量“箱子”则是由若干幢房子拼接而成,箱子在内存中是不存在的,只是我们为了方便理解而臆想出来的。右图就是一个浮点型变量在内存中的情况。(图7.2.1)

数组在内存中的存储情况

变量在内存中是由若干个相邻的“房子”拼接而成的,而数组在内存中则是由若干个相邻的数组元素按顺序拼接而成的。每个数组元素又相当于一个变量。左图是一个大小为3的短整型(short)数组在内存中的情况。(图7.2.2)

我们在上一节的最后说到可以省略数组的大小,但是这样一来我们就无法得知数组的大小了,这将可能造成越界访问。当我们了解了数组在内存中的存储情况后,我们就能够知道数组的大小了。在C++中,有一个名为sizeof的操作符,可以求出一个数组或一种数据类型在内存中占了多少“房子”,它的使用方法是:
    sizeof(数组名或数据类型);

通过左图我们可以理解,要求出数组的大小,应该是用整个数组占的“房子”数除以每一个数组元素占的“房子”数,即6除以2等于3。下面我们就来看一个求出数组大小的程序实例:(程序7.2.1)
#include "iostream.h"
int main()
{
int array[]={3,5,6,8,7,9};
int size=sizeof(array)/sizeof(int);
cout <<"size="<<size <<endl;
for (int i=0;i<size;i++)
cout <<array[i] <<" ";
cout <<endl;
return 0;
}
运行结果:
size=6
3 5 6 8 7 9

通过这个程序,可以成功地知道一个数组的大小,我们也不用为可能发生的越界访问而发愁了。

字符的存储情况

电脑是用电来计算和保存信息的。在电脑里,就好像有许许多多的开关,用导通(开)来表示1,用断开(关)来表示0。那么这些个“0”和“1”是怎么来表示一些字符的呢?

当只有一个开关的时候,这个开关能表示两种状态,即0和1;当有两个开关的时候,这两个开关可以表示四种状态,即00、01、10、11……如果你学过排列,就不难理解,当有8个开关的时候,可以表示28=256种状态,分别是0~255。在电脑中,就是用8个开关(0或1)来表示一个字节的,每一个开关(0或1)称为一个“位”(Bit),即8位组成一个字节。我们把一个字节所能表示的256种状态和256个字符按一定的顺序一一对应起来,一个字节就可以表示256种不同的字符。这种用8位二进制表示一个字符的编码称为ASCII码(念aski),它的全称是美国信息交换标准码(America Standard Code for Information Interchange)。我们需要记住的ASCII码有三个,数字0的ASCII码为十进制的48,大写字母A的ASCII码为十进制的65,小写字母a的ASCII码为十进制的97。

下面我们就来编写一段程序,输出ASCII码表的常用部分:(程序7.2.2)
#include "iostream.h"
#include "iomanip.h"
int main()
{
   char temp;
   for (int i=32;i<=127;i++)
   {
      temp=i;
      cout << setw(2) <<temp;
      if (i%16==15) //从0~15正好16个,所以余数为15的时候换行
      {
         cout <<endl; 
      }
   }
   return 0;
}
运行结果:

以上这段程序输出了96个常用的字符,从空格(ASCII码为十进制的32)一直到三角(ASCII码为十进制的127)。每行16个字符,共6行。有些人可能要问一个问题,上面这段程序中怎么能把整型变量i赋值给字符型变量temp呢?根据前面我们所说的字符的存储原理,不难发现字符的实质是一个0~255的整数,所以把一个在这个范围内的整数赋值给字符变量在C++中是允许的。

字符数组在内存中的存储情况

我们以前说过,字符和字符串是不同的:字符只能是一个,而字符串是由若干个字符连接而成。可是,’a’和”a”有区别吗?

其实字符和字符串的区别有点像单词和句子的区别。一句句子可能只有一个单词组成,但是句号却是必不可少的,否则就不能称为句子了。字符串在结尾处也会加上一个“句号”来表示字符串的结束,称为结尾符。在C++中用数组表示的字符串的结尾符是’\0’,它也是一个字符。所以字符串”a”实际上是两个字符,即字符’a’和结尾符’\0’。

在初始化一个字符数组的时候有两种初始化方式,一种是按字符串初始化,一种是按字符初始化。按字符串初始化就会在最后一个元素出现结尾符,而结尾符也要占用一个字符的空间,所以在声明数组的时候一定要注意空间是否足够。下面我们就来看一下这两种初始化方法:(程序7.2.3)
#include "iostream.h"
int main()
{
   char a[]={"Hello"};//按字符串初始化
   char b[]={'H','e','l','l','o'};//按字符初始化
   char c[]={'H','e','l','l','o','\0'};//按字符串初始化
   cout <<"Size of A=" <<sizeof(a) <<endl;
   cout <<"Size of B=" <<sizeof(b) <<endl;
   cout <<"Size of C=" <<sizeof(c) <<endl;
   cout <<a <<endl;
   cout <<b <<endl;
   cout <<c <<endl;
   return 0;
}
运行结果:
Size of A=6
Size of B=5
Size of C=6
Hello
Hello烫蘃ello
Hello

从数组a、b和c的大小,我们就能看出按字符串和按字符初始化的不同了。你可能还会发现,输出的数组a和c都是正常的,为什么输出的b却夹杂着乱码呢?这是因为a和c的属性都是字符串的字符数组,而b是普通字符数组。b数组没有结尾符,电脑在输出它的时候就会发生问题了。

数组a和b在内存中的存储情况如右上图所示(图7.2.3)