静态数组

一、一维数组的定义与初始化

1.一维数组的定义

一维数组也称向量,它用以组织具有一维顺序关系一组同类型的数据

一维数组的定义方式:

数据类型 数组名[常量表达式]

类型说明符表示数组中所有元素的类型;常量表达式指数组的长度(即数组中存放元素的个数)

例如:int array[5];

上述代码 int 表示数组元素的类型,array 是数组的名称,5是指数组的长度。

数组占据的内存空间是连续的,所以很容易计算出数组占据的内存大小( 数组长度*sizeof(数据类型))和每个元素所对应的内存首地址。

上述数组array占据的内存大小为:5*sizeof(int)=20字节

完成数组的定义后,编译器根据数组定义语句中提供的数据类型和数组长度给数组变量分配适当的内存空间。如果想要使用数组操作数据,还需要对数组进行初始化。

2.一维数组的初始化

三种常见的数组初始化元素的方式:

(1)直接对数组中所有元素赋初始值。

int a[4]={1,2,3,4};

表示定义了一个长度为4的整型数组a,a数组中的元素依次是1,2,3,4.

(2)只对数组一部分元素进行赋值

int b[5]={1,2,3};

表示定义了一个长度为5的整型数组b,数组的前三个元素依次为1,2,3,其他的元素的值会被默认设置为0.

(3)对数组全部元素赋值,但不指定长度

int c[]={1,2,3,4};

系统会根据赋值号右边的初始值列表中给出的初值个数自动设置数组的长度。

【注意】

  • 数组下标是用方括号括起来的,而不是圆括号
  • 数据类型可以是基本类型,也可以是指针、结构体等
  • 数组名的命名规则和变量名的命名规则相同

二、一维数组的引用

当需要访问数组中的元素时,需要通过数组名和下标来引用数组元素,一维数组的元素引用方式如下:

数组名[下标]

下标是指元素在数组中的位置,元素的下标从0开始,也就是一个长度为6的数组,下标为0~5;

访问数组元素时,下标不能超出这个范围(0~(数组长度-1)),否则程序会报错。


三、 数组的非法操作

(1)用一个已初始化的数组为另一个数组赋值

int x[3]={1,2,3};

int y[3];

y=x;//错误操作

(2)对数组进行整体输入输出

printf()和scanf()仅仅支持字符数组整体的输入和输出,不支持其他类型的数组整体输入输出。对于不能整体输入输出的数组,必须以元素为单位进行操作。

int a[3]={1,2,3};

printf(“%d”,a);//错误操作

printf(“%d %d %d”,a[0],a[1],a[2]);//正确操作

(3)数组与数组之间不能进行比较

int a[3]={1,2,3};

int b[3]={4,5,6};

if(a>b)…//错误操作

(4)数组与数组之间不能进行运算

int a[3]={1,2,3};

int b[3]={4,5,6};

a+=b;//错误操作


四、二维数组的定义和初始化

1.二维数组的定义

二维数组的定义方式如下:

类型说明符 数组名[常量表达式1] [常量表达式2]

常量表达式1称为“行下标”,常量表达式2称为“列下标”

int a[3][4];

表示定义一个3行4列的二维整型数组a.

二维数组a的元素分布情况: 第一天:数组与遍历 - 图1

多维数组的元素存储顺序按照右边的下标率先变化的原则,称为行主序

2.二维数组的初始化

三种常见的二维数组初始化方式:

(1)按行给二维数组赋初值

int a[2][3]={{1,2,3},{4,5,6}};

(2)将所有的数组元素按行顺序写在一个大括号内

int a[2][3]={1,2,3,4,5,6};

(3)对部分数组元素进行赋值

int a[3][3]={{1},{2,3},{4,5,6}};

对于没有赋初始值的元素,系统自动赋值为0.

【注意】 二维数组的行下标可以省略,但列下标不能省略

int a[2][3]={1,2,3,4,5,6};

可以写成int a[][3]={1,2,3,4,5,6};

系统会根据固定的列数,将后面的数值进行划分,自动将行数定位2.


五、二维数组的引用

二维数组的引用方式同一维数组是一样的,通过数组名和下标进行数组元素的引用

数组名 [行下标][列下标];

访问时也不能超过数组的范围。

int a[3][4];

a[3][4]=3;//错误操作,行下标范围0~2,列下标范围0~3


六、数组的深入探讨

(1)一维数组的数组名

C语言中,在几乎所有使用数组名的表达式中,数组名的值是一个指针常量,也就是数组第一个元素的地址。

它的类型取决于数组元素的类型:如果数据元素类型是int,那么数组名的类型就是“指向int的常量指针”,其他类型同理。

只有当数组名在表达式中使用时,编译器才会为它产生一个指针常量(注意这个值是一个指针常量,而不是指针变量)。

两种场合数组名不用指针常量来表示:

①当数组名作为sizeof()的操作符

sizeof返回整个数组的长度,而不是指向数组的指针的长度。

②数组名作为单目操作符&的操作数

对数组名取址产生的是一个指向数组的指针,而不是一个指向某个指针常量的指针。

(2)一维数组的下标引用

表达式*(b+3)含义:b是一个指向整型的指针,加法运算结果仍是一个指向整型的指针,该指针指向的是数组第一个元素后3个整型长度的位置,然后,间接访问这个位置(取这个位置的值)。

这与下标引用b[3]的过程完全一样。所以除了优先级外,下标引用和间接访问完全相同。

即:array[下标]和*(array+(下标))等同

在使用下标引用的地方可以使用对等的指针表达式来代替。

案例分析:

int array[10];

int *ap=array+2

第一天:数组与遍历 - 图2

用array的对等表达式表示ap的表达式

ap: array+2或 &(array[2])

*ap: array[2]或 *(array+2)

ap[0]:array[2](这是一个正确的表达,因为C语言的下标引用和间接访问是一样的,所以ap[0]和*(ap+0)是对等的,所以结果和上一个表达式一致。)

ap+6: array[8]或 &(array[8])

*ap+6: array[2]+6(因为间接访问符*优先级要高于运算符+)

*(ap+6):array[8]

ap[-1]: array[1](还是这个道理:下标引用就是间接访问:*(ap+(-1))

(3)二维数组的数组名

int matrix[3][5];

matrix 类型是一个指向一个包含5个整型元素的数组的指针。

第一天:数组与遍历 - 图3

它的值是:它指向包含5个整型元素的第一个子数组。

(4)二维数组的下标引用

matrix[1][2]访问的元素如下:

第一天:数组与遍历 - 图4

表达式:matrix+1(1是根据包含5个元素的数组的调整,所以它指向matrix的下一行)

第一天:数组与遍历 - 图5

表达式:*(matrix+1)

实际上标识了一个包含5个整型元素的子数组。数组名的值是一个常量指针,指向数组的第一个元素。

第一天:数组与遍历 - 图6

表达式:*(matrix+1)+3

前一个表达式是一个指向整型值的指针,所以5是根据整型的长度进行调整,结果还是一个指针,指向刚才位置的后面的第3个位置。

第一天:数组与遍历 - 图7

表达式:*(*(matrix+1)+3)

该表达式就是间接访问那个位置的值。

根据间接访问可以用下标表示*(matrix+1)等同于matrix[1], *(matrix[1]+3)继续替换则变成了matrix[1][3]。

动态数组

一、C++的动态数组(Dynamically-allocated Single-Dimension arrays)

当无法使用静态分配的数组时 — 因为你在编译时不知道合适的大小,或该数组太大而无法合理地放入运行时堆栈,或该数组的生命周期比创建它的函数更长。动态分配数组涉及到使用new运算符,类似于其他对象的动态分配,不同之处在于数组需要我们另外指定大小。

动态分配数组是使用new表达式完成的,该表达式会动态分配足够的内存来存储整个数组,然后返回指向数组第一个单元格的指针。

  1. int* a = new int[10];

表达式 new int[10] 在堆上分配一个足够大的内存块来存储 10 个整数,并返回一个指向第一个的指针。第二个的位置直接跟在第一个之后,第三个直接跟在第二个之后,依此类推,所有的单元格都是相同的类型。所以给定一个指向第一个单元格的指针和一个索引,在幕后,可以使用以下计算来计算任何给定单元格的索引:

  1. 单元格 i 的地址 = (单元格 0 的地址) + (sizeof(int) * i)

注意,计算具有大索引的单元格的地址并不比使用小索引更消耗性能。这个计算只是一个乘法和加法;如果我们假设给定地址的内存访问需要恒定时间,那么访问数组中的任何单元都需要恒定时间。(在实践中,内存访问时间可能会因缓存等影响而有所不同,尽管你可以合理地认为主内存访问花费恒定时间。)

有趣的是,返回的指针类型并未指定有关数组的任何内容。当我们动态分配一个int数组时,我们得到的是一个int,即一个指向int的指针。数组在一般情况下,实现为指针,以他们的第一个单元,它是由我们来了解是否特定int指向一个单一的int或int数组。

一旦有了指向数组的指针,就可以像访问静态分配的数组一样访问它的每一个数字:

  1. int* a = new int[10];
  2. a[3] = 4;
  3. std::cout << a[3] << std::endl;

a[3]相当于假想表达*q,其中q是一个指向int占用三个单位的一个点。换句话说,a[3]给你提供的是这个单元格中的int(从理论上讲,是对它的引用),而不是指向这个单元格的指针。

当完成动态分配的数组时,需要释放它,就像您处理任何其他动态分配的对象一样。但是,重要的是要注意你要使用不同的运算符delete[]来执行此操作。像delete一样,你给delete[]一个指向数组的指针,它会为你释放整个数组(以及它所有单元格中的所有对象)。

  1. delete[] a;

由你决定哪些指针指向数组,并在需要时使用delete[]。在指向数组的指针上使用delete而不是delete[ ],或在指向单个值的指针上使用delete[]而不是delete,会导致“undefined behavior”。就像我们之前看到的使用delete一样,指针a不受影响,尽管它现在指向未分配的内存,因此它现在就不会被再使用。

二、C++动态分配二维数组-使用常规方式(即不使用除iostream外的内置库)

动态二维数组的分配

  1. int** test = new int*[rows];
  2. for(int i = 0; i < rows; i++)
  3. test[i] = new int[columns];

释放动态分配二维数组占用的空间

  1. for(int i = 0; i < rows; i++)
  2. delete []test[i];
  3. delete []test;

三、C++动态分配二维数组-使用Vector(需要额外的库vector)

注意使用这种方法你需要在文件头

  1. #include <vector>

使用vector动态分配二维数组

  1. std::vector<std::vector<int>> test(rows);
  2. for (i = 0; i < test.size(); i++)
  3. test[i].resize(columns);

使用swap()方法清空vector占用的内存空间

  1. // vector的clear方法无法清空其占用的内存
  2. //可以使用swap来使一个空的vector来替换掉原来test里有的内容。
  3. vector<T>().swap(test);