带你重新复习数组

简介: 任何数组,不论是静态声明的还是动态创建的,其所有元素在内存中都是连续的字节存放的,也就是说保存在一大块连续的内存中。

数组的本质

任何数组,不论是静态声明的还是动态创建的,其所有元素在内存中都是连续的字节存放的,也就是说保存在一大块连续的内存中。

D0191141-6252-4F20-8E3C-6D2AE71ECC4D.png

 

例如:

a[3]= 100;

编译器计算地址a+3* sizeof (int),得到 0x004284FC,并返回该地址所指对象的引用而不是返回 “45”这个值。

其实,当你使用“[]”来引用数组元素的时候,编译器必须把它转换为同类型的指针表示形式,然后再进行编译.

例如:

1. a[3]=100;
2. 转换为*(a+3)=100

这是因为C++/C 数组本身不会保存下标值与元素对象之间的对应关系,因此无法直接通过下标索引来定位真正的数组元素对象。如果在程序中直接使用指针方式,虽然可以缩短编译时间,但是会损害程序的清晰性,所以要使用“[]”。

数组名字本身就是一个指针,是一个指针常量,即a 等价于 int * const a, 因此你不能试图修改数组名的值。数组名的值就是数组第一个元素的内存单元首地址,即 a==&a[0],这样,我们就可以通过同类型的指针迭代(++/--)来遍历整个数组,但是你不能妄想仅通过数组名(不使用下标和选代)就想达到访问整个数组的目的,除非它是带有”\0"结束符的字符数组(即宇符串)。基于这个原因,任何两个数组之间不能直接赋值,即使是同类型数组。

数组的声明

一般有如下三种方式:

(1)明确地指出它的元素个数,编译器会按照给定的元素个数来分配存储空间

(2)不指定元素个数而直接初始化,编译器会根据你提供的初始值的个数来确定数组的元素个数

(3)同时指定元素个数并且初始化。但是不允许既不指定元素个数,又不初始化,因为编译器不知道到底该为数组分配多少存储空间

注:编译器在给数组分配内存空间的时候总是以用户指定的元素个数为准。如果初始值的个数多于指定的个数,则报错;如果用户没有指定元素个数,则编译器计算初始值的个数作为数组的元素个数;如果初始值个数小于指定的数组元素个数,则后面的元素全部自动初始化为 0。

值得注意的是不存在元素为0的数组

标准 C++/C 不会对用户访问数组是否越界进行任何检查,无论是静态的(编译时)检查还是动态的(运行时)检查。

虽然数组自己知道它有多少个元素,但是由于可以使用整型变量及其表达式作为数组的下标,而变量的值在编译时是无法确定的,所以语言无法执行静态检查。

因此,你可以访问超出数组范围的元素。换句话说,你可以把数组存储空间以外的内存单元当做这个数组的元素来访问,例如 a[15]=100;系统可能不会报错,那是因为 a[15]可能恰好是位于一块空闲内存中,但是由此造成的后果要由你的程序来承担

二维数组

二维数组在 C++/C中都是以“行序优先” 来存储元素的

F7F3D812-C0BD-428B-8DE9-E9C185917DF7.jpeg

当我们访问数组元素 a[4][1]时,实际上编译器转换为*((a+4*3)+1),其中3就是该数组的列数,而4 和1是用户提供的下标值。在访问二维数组中的某个元素时,编译器为了计算其地址必须知道该数组的列数,但并不需要知道这个数组总共有多少行。

注:数组实际上也是一个可以递归定义的概念:任何维数的数组都可以看做是由比它少一维的数组组成的一维数组。例如 int a[3][4][5]可以看做是由二维数组int b[4][5]组成的一维数组,其长度为3;而b[4][5]又可以看做是由一维数组 int c[5][组成的一维数组,其长度为4;c    [5]则是int 元素的数组,其长度为5。

数组和指针之间存在如下的等价关系

(1):一维数组等价于元素的指针,例如:

int a[10]<<==>>int * const a;

(2):二维数组等价于指向一维数组的指针,例如:

int b[3][4]<<==>>int (* const b)[4];

(3)三维数组等价于指向二维数组的指针,例如:

int c[3][4][5]<<==>>int(* const c)[4][5];

多为数组的概念与二维数组的相似

注:数组名实际上就是该数组的首地址。把数组作为参数传递给函数的时候并非把整个数组的内容传递进去,此时数组退化为一个同类型的指针。也就是说,output 函数原型中声明的数组并非真正的数组,编译器会把它改写为 void output (const int * const a, int size)。当你在函数内部使用下标来访问数组元素的时候,编译器把它转换为*(a+i)。

C++/C 为什么要把数组传递改写为指针传递呢?主要原因如下:

(1):数组在内存中是连续字节存放的,因此编译器可以通过地址计算来引用数组中的元素

(2):出于性能考虑。如果把整个数组中的元素全部传递进去,不仅需要大量时间来拷贝数组,而且这个拷贝还会占用大量的堆栈空间

注:对于多维数组传递,情况有所不同,你必须指出除第一维之外的所有维的长度(元素个数)。

对于多维数组,C++/C 并不像一维数组那样可以简单地转换为同类型指针,而是转换为与其等价的数组指针例如int a[m][n]就转换为 int (*a)[n],就是说a是一个指向一维数组的指针,而该一维数组具有n个元素,a是指向原来数组的第一行的指针,a[1]就是指向第二行的指针,依次类推。

下面的4种表达方法是等价的:

(1) a[i][j]

(2) *(a[i]+ j)

(3) (*(a + i))[j]//值得注意的是,这里的‘*’并不是解引用

(4) *(*(a + i) + j)

要注意,上述4个表达式中的a是指向一维数组的指针,而不是单纯地指向 int 类型元素的指针,因此(*(a+i)+j)的值实际上是((a +i*sizeof(int) * n) +j* sizeof(int)。

相关文章
|
3月前
指针复习
指针复习
14 1
|
5月前
|
C语言
数组知识点总结
数组知识点总结
31 0
|
9月前
|
算法
剑指offer(数组相关面试题)
剑指offer(数组相关面试题)
|
10月前
|
存储 搜索推荐 算法
数组掌握秘籍:Java数组进阶指南
数组掌握秘籍:Java数组进阶指南
|
10月前
|
存储 编译器 C语言
|
10月前
|
算法 编译器 C语言
数组知识点(下)
数组知识点(下)
|
11月前
|
存储
数组知识点
数组知识点
|
12月前
|
存储 C语言
初阶C语言 第三章-------《数组》(一维数组,二维数组,数组越界.....) 知识点+思维导图+基本练习题+超详细+通俗易懂(建议收藏)
初阶C语言 第三章-------《数组》(一维数组,二维数组,数组越界.....) 知识点+思维导图+基本练习题+超详细+通俗易懂(建议收藏)
|
算法 程序员 编译器
【C】带你复习有趣的函数
【C】带你复习有趣的函数
|
算法 Linux C语言
几道数组相关的面试题
几道数组相关的面试题
64 0
几道数组相关的面试题