08. 数组的使用
一、数组的概述
在程序设计中,为了方便处理数据把具有相同类型的若干变量按有序形式组织起来就称为 数组。数据 就是在 内存 中 连续 的 相同类型 的变量空间。同一个数组所有的成员都是相同的数据类型,同时所有的成员在内存中是连续的。数组属于构造类型。
二、一维数组的使用
2.1、一维数组的定义
一维数组用于存储一维数列中数据的集合,它的定义格式如下:
数据类型 数组名[数组长度];
- 数据类型 表示数组中的所有元素的类型;
- 数组名 表示该数组类型变量的名称,命名规则遵循标识符的命名规范;
- 数组长度 定义了数组中存放的数据元素的个数,它可以是 整型常量、整型表达式 或 标识符常量;
#include <stdio.h>
int main(void)
{
// 定义一维数组
int array2[3];
return 0;
}
数组是多个相同类型数据的组合,一个数组一旦声明/定义了,其长度是固定的,不能动态变化;
C99 之后,数组的长度可以是变量,为了支持变长数组,这种数组不能初始化
2.2、一维数组的初始化
在定义一维数组时,我们可以直接对数组元素进行赋值。这种赋值方式也被称为数组的 静态初始化。如果我们只给一部分元素赋值,未赋值的部分元素值为 0 。它的初始化格式如下:
数据类型 数组名[数据的长度] = {元素1, 元素2, ...};
在对全部数组元素赋初值时可以不指定数据长度。它的简化格式如下:
数据类型 数组名[] = {元素1, 元素2, ...};
我们还可以为指定的索引位置赋值,其它位置的索引自动赋值为的 0 。
数据类型 数组名[数据的长度] = {[索引1]=元素1, [索引2]=元素2, ...};
数据类型 数组名[] = {[索引1]=元素1, [索引2]=元素2, ...};
#include <stdio.h>
int main(void)
{
// 静态初始化:数组的初始化和赋值操作同时进行
// 数组一旦初始化完成,其长度就去确定了。
int array0[5] = {1001, 1002, 1003};
// 静态初始化的简化格式
int array1[] = {1001, 1002, 1003, 1004, 1005};
// 未指定的索引位置赋值,其它默认为初始化值
int array3[5] = {[0]=1001, 1002, 1003, [4]=1005};
int array4[] = {[0]=1001, 1002, 1003, [4]=1005};
return 0;
}
数组初始化时,数组的长度(方括号里的数字)必须是 常量 或者 常量表达式,不能是变量。
数组静态初始化时,我们可以只给一部分元素赋值,剩余元素默认为 0。
如果不初始化数组,数组元素和未初始化的普通变量一样,其中储存的都是垃圾值;
在给数据进行静态初始化时,我们可以省略数组的长度。
2.3、一维数组的使用
声明数组后,我们可以通过 下标(或索引) 的方式给一维数组的数组元素赋值或者获取一维数组指定位置的元素;数组的下标是从 0 开始的,也就是说下标为 0 表示的是第一个数组元素;C 不允许把数组作为一个单元赋给另一个数组,除初始化以外也不允许使用花括号列表的形式赋值。
#include <stdio.h>
int main(void)
{
int array[3];
// 数组的角标(索引)从0开始,到数组的长度-1结束
array[0] = 1001;
array[1] = 1002;
array[2] = 1003;
// 数组下标越界
// id[3] = 1004;
printf("array[0]: %d\n",array[0]);
printf("array[1]: %d\n",array[1]);
printf("array[2]: %d\n",array[2]);
return 0;
}
2.4、一维数组的数组名
一维数组的数组名是一个地址的常量,代表数组中首元素的地址。但是有两个例外:
sizeof(数组名)
,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节;&数组名
,这里的数组名表示整个数组,取出的是整个数组的地址;
#include <stdio.h>
int main(void)
{
int array[3] = {1001,1002,1003};
// 一维数组的数组名是一个地址常量,代表数组中首元素的地址
printf("一维数组首元素的地址:%p\n", array);
printf("一维数组首元素的地址加1:%p\n", array+1);
printf("一维数组第一个元素的地址:%p\n", &array[0]);
printf("一维数组第二个元素的地址:%p\n", &array[1]);
printf("一维数组整个数组的地址:%p\n", &array);
printf("一维数组整个数组的地址加1:%p\n", &array+1);
printf("一维数组的大小:%zu byte\n",sizeof(array));
return 0;
}
C 语言规定,数据一旦声明,数组名执行的地址就不可更改。因为声明数组时,编译器会自动为数组分配内存,这个地址与数组名绑定,不可更改;
2.5、获取一维数组的长度
我们可以使用 sizeof 运算符计算数组的长度。
#include <stdio.h>
int main(void)
{
int array[3] = {1001,1002,1003};
// 数组占内存的大小 = 数据类型*元素个数
printf("%d\n",sizeof(array));
// 数组元素大小
printf("%d\n",sizeof(array[0]));
// 数组元素个数
printf("%d\n",sizeof(array)/sizeof(array[0]));
return 0;
}
2.6、遍历一维数组
数组的遍历指的是取出数据的过程,不要局限的理解为,遍历就是打印。
#include <stdio.h>
int main(void)
{
int array[3] = {1001,1002,1003};
int i = 0;
int size = sizeof(array)/sizeof(array[0]);
for(i = 0; i < size; i++)
{
printf("array[%d]: %d\n",i,array[i]);
}
return 0;
}
三、字符数组
3.1、字符数组的定义
数组中元素类型为字符型元素的数组称为字符数组。字符数组中的每一个元素都可以存放一个字符。字符数组的定义和使用与其它类型的数组类似。字符数组的定义格式如下:
char 数组名[数组长度];
#include <stdio.h>
int main()
{
char array[5];
return 0;
}
3.2、字符数组的初始化
在定义字符数组时,我们可以逐个字符赋给数组中的各元素。每一个元素的字符都是使用一对单引号("")表示的。字符数组部分赋值时,未赋值的部分用 '\0' 来填充。它的完整格式如下:
char 数组名[数据的长度] = {元素1,元素2,...};
如果在定义字符数组时进行初始化,可以省略数组的长度。它的简化格式如下:
char 数组名[] = {元素1,元素2,...};
我们还可以用字符串来给字符数组赋初值。通常我们会采用一个字符数组来存放一个字符串。
char 数组名[] = "字符串";
我们还可以为指定的索引位置赋值,其它位置的索引自动赋值为的 0 。
char 数组名[数据的长度] = {[索引1]=元素1, [索引2]=元素2, ...};
char 数组名[] = {[索引1]=元素1, [索引2]=元素2, ...};
#include <stdio.h>
int main()
{
char array1[] = {'h','e','l','l','o'};
char array2[] = "hello";
char array3[] = {[0]='a', 'b', [3]='d', 'e'};
return 0;
}
3.3、字符数组的结束标志
在 C 语言中,字符串总是以 '\0' 作为字符串的结束标志,因此把一个字符串存入字符数组时,编译器会自动把结束符 '\0' 存入到字符数组中,并以此为该字符串是否结束的标志。因此,用字符串赋值比用组个字节赋值要多占一个字节,多占的这个字节用于存放字符串结束标志 '\0'。
#include <stdio.h>
int main()
{
char array1[] = {'h','e','l','l','o'};
// 等价于:array2[] = {'h','e','l','l','o','\0'}
char array2[] = "hello";
printf("使用字符逐个赋值的长度:%d\n",sizeof(array1));
printf("使用字符串赋值的长度:%d\n",sizeof(array2));
return 0;
}
字符数组并不要求最后一个字符为 '\0';
3.4、输出字符数组
我们可以使用格式符 "%c" 实现字符数组中字符逐个输入与输出。它的格式如下:
printf("%c",数组名[数组下标]);
我们也可以使用格式符 "%s" 将整个字符串依次输入或输出。它的格式如下:
printf("%s",数组名);
- 当我们使用 "%s" 格式符输出字符串时,输出字符不包含结束符 '\0'。
- 用 "%s" 格式符输出字符串时,printf() 函数中的输出项时字符数组名,而不是数组中的某一个具体元素。
- 如果数组长度大于字符串实际长度,则也只能输出到 '\0' 为止。
- 如果一个字符数组中包含多个 '\0' 结束字符,则在遇到第一个 '\0' 时就结束。
#include <stdio.h>
int main()
{
char array1[6] = {'h','e','l','l','o'};
char array2[] = "hello";
printf("%s\n",array1);
printf("%s\n",array2);
return 0;
}
#include <stdio.h>
int main()
{
char array1[6] = {'h','e','l','l','o'};
char array2[] = "hello";
int i = 0;
while(array1[i])
{
printf("%c",array1[i]);
i++;
}
printf("\n");
for(i = 0; array2[i]; i++)
{
printf("%c",array2[i]);
}
return 0;
}
在定义字符数组时,我们只给一部分元素赋值,未赋值的元素值为 0
数字0 等同于 '\0',但不等同于 '0';
四、二维数组的使用
4.1、二维数组的定义
数组属于引用数据类型,数组的元素也可以是引用数据类型;对于二维数组的理解,可以看成一维数组 array1 作为另一个一维数组 array2 的元素而存在;从数组底层的运行机制来看,其实没有多维数组;二维数组根本意义就是一维数组的数组,二维数组的第一维就是数据的起始地址,第二维就是基于某个数组中的某个值。
在内存中并不存在二维数组,二维数组实际的硬件存储器是连续编址的,也就是说内存中只有一维数组,即放完一行之后顺次放入第二行,和一维数组存放方式是一样的。它的定义格式如下:
数据类型 数组名[常量表达式1][常量表达式2];
#include <stdio.h>
int main(void)
{
// 定义二维数组
int array[2][3];
return 0;
}
4.2、二维数组的初始化
二维数组和一维数组一样,也可以在声明时对其进行初始化。
我们可以将所有数据写在同一个大括号内,按照数组元素排列顺序对元素赋值。如果大括号内的数据少于数组元素的个数,则系统默认将后面未赋值的元素值设置为 0。
数据类型 数组名[常量表达式1][常量表达式2] = {元素1,元素2,元素3,...};
我们也可以分行给元素赋值,在分行赋值时,可以只对部分元素赋值。
数据类型 数组名[常量表达式1][常量表达式2] = {{元素1,元素2,元素3,...},{元素4,元素5,...},...};
在为所有元素赋初值时,可以省略行下标,但是不能省略列下标。
数据类型 数组名[][常量表达式] = {元素1,元素2,元素3,元素4,元素5,...};
我们还可以为指定的索引位置赋值,其它位置的索引自动赋值为的 0 。
数据类型 数组名[数据的长度1][数据的长度2] = {[索引1][索引2]=元素1, [索引3][索引4]=元素2, ...};
数据类型 数组名[] = {[索引1][索引2]=元素1, [索引3][索引4]=元素2, ...};
#include <stdio.h>
int main(void)
{
// 二维数组静态初始化
int array0[2][3] = {{1, 2, 3},{4, 5}};
// 二维数组静态初始化简化形式
int array1[][3] = {1, 2, 3, 4, 5};
// 为二维数组部分索引赋值
int array2[2][3] = {[0][0]=1, 2, [1][1]=5};
int array3[][3] = {[0][0]=1, 2, [1][1]=5};
return 0;
}
数组静态初始化时,我们可以只给一部分元素赋值,剩余元素默认为 0;
如果对全部元素赋值,那么第一维的长度可以不给出;
二维数组可以看作是由一维数组嵌套而成的,如果一个数据的每个元素又是一个数组,那么它是二维数组;
定义多维数组的格式与二维数组类似,
数据类型 数组名[n1][n2]...[n3]
;
4.3、二维数组的使用
我们同样可以通过 下标(索引)的方式来为二维数组的元素赋值或者获取二维数组指定位置的元素;行下标的取值范围从 0 到 n-1,列下标的取值范围从 0 到 m-1。二维数组最大的下标元素时 array[n-1][m-1]。
#include <stdio.h>
int main(void)
{
int array[2][3] = {{1,2,3},{4,5,6}};
// array[0]:二维数组中的第一个一维数组
// array[0][0]:获取第一个一维数组中0索引的元素
printf("%d\n",array[0][0]);
return 0;
}
4.4、二维数组的数组名
二维数组的数组名也是一个地址的常量,代表数组中首元素(第一行)的地址。但是有两个例外:
sizeof(数组名)
,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节;&数组名
,这里的数组名表示整个数组,取出的是整个数组的地址;
#include <stdio.h>
int main(void)
{
int array[2][3] = {{1,2,3},{4,5,6}};
// 二维数组的数组名是一个地址常量,代表数组中首元素的地址
printf("二维数组首元素的地址:%p\n", array);
printf("二维数组首元素的地址加1:%p\n", array+1);
printf("二维数组第一行元素的地址:%p\n", array[0]);
printf("二维数组第二行元素的地址:%p\n", array[1]);
printf("二维数组第一个元素的地址:%p\n", &array[0][0]);
printf("二维数组第二个元素的地址:%p\n", &array[0][1]);
printf("二维数组整个数组的地址:%p\n", &array);
printf("二维数组整个数组的地址加1:%p\n", &array+1);
printf("二维数组的大小:%zu byte\n",sizeof(array));
return 0;
}
4.5、获取二维数组数据的长度
#include <stdio.h>
int main(void)
{
int array[2][3] = {{1,2,3},{4,5,6}};
// 行*列*数据类型
printf("二维数组大小:%d\n",sizeof(array));
// 二维数组一行大小
printf("二维数组一行大小:%d\n",sizeof(array[0]));
// 二维数组元素大小
printf("二维数组元素大小:%d\n",sizeof(array[0][0]));
// 二维数组行数
printf("二维数组行数:%d\n",sizeof(array)/sizeof(array[0]));
// 二维数组列数
printf("二维数组列数:%d\n",sizeof(array[0])/sizeof(array[0][0]));
return 0;
}
4.6、遍历二维数组
#include <stdio.h>
int main()
{
int i = 0, j = 0;
int array[2][3] = {{1,2,3},{4,5,6}};
for(i = 0;i < sizeof(array)/sizeof(array[0]); i++)
{
for(j = 0; j < sizeof(array[0])/sizeof(array[0][0]); j++)
{
printf("%d\t",array[i][j]);
}
printf("\n");
}
return 0;
}
五、多维数组
多维数组的定义和二维数组相同,只是下标更多,一般形式如下:
数据类型 数组名[常量表达式1][常量表达式2]...[常量表达式n];
由于数组元素的位置都可以同可以通过偏移量计算,因此对于一个三维数组 array[m][n][p] 来说,元素 array[a][i][k] 所在的地址是从 array[0][0][0] 算起到 \((i*n*p + j*p + k)\) 个单位的位置。
六、数组的常见算法
6.1、求特征值
#include <stdio.h>
int main(void)
{
int array[] = {12, 43,11, 56, 34, 45, 23, 90, 16, 59};
size_t length = sizeof(array) / sizeof(array[0]);
int max = array[0];
int min = array[0];
double avg = 0;
int sum = 0;
for(int i = 0; i < length; i++)
{
if(max < array[i])
max = array[i];
if(min > array[i])
min = array[i];
sum += array[i];
}
avg = sum * 1.0 / length;
printf("max: %d, min: %d, sum: %d, avg: %.2lf", max, min, sum, avg);
return 0;
}
6.2、数组的反转
#include <stdio.h>
int main()
{
int array[] = {1001,1002,1003,1004,1005};
int i = 0;
size_t length= sizeof(array) / sizeof(array[0]);
int temp = 0;
for(i = 0; i < length / 2; i++)
{
temp = array[i];
array[i] = array[length-i-1];
array[length-i-1] = temp;
}
for(i = 0; i < length; i++)
{
printf("%d\t",array[i]);
}
return 0;
}
6.3、数组的线性查找
#include <stdio.h>
int main()
{
int array[] = {43, 32, 76, -98, 0, 64, 33, -21, 32, 99};
int i = 0;
int target = 33;
int flag = 1;
size_t length = sizeof(array) / sizeof(array[0]);
for(i = 0; i < length - 1; i++)
{
if(target == array[i])
{
printf("Find the target, index is %d\n", i);
flag = 0;
break;
}
}
if(flag)
{
printf("Not found the target!\n");
}
return 0;
}
6.4、数组的二分查找
#include <stdio.h>
int main()
{
// 二分法查找
// 前提:所要查找的数组必须有序
int array[] = {-98,-34,2,34,54,66,79,105,210,333};
int i = 0;
int target = 66; // 要查找的数据
int flag = 1;
size_t length= sizeof(array) / sizeof(array[0]);
int head = 0; // 初始的头索引
int end = length - 1; // 初始的末索引
int middle = 0;
while(head <= end)
{
middle = (head + end) / 2;
if(target == array[middle])
{
printf("Find the target, index is %d\n", middle);
flag = 0;
break;
}
else if(array[middle] > target)
{
end = middle - 1;
}
else{
head = middle + 1;
}
}
if(flag)
{
printf("Not found the target!\n");
}
return 0;
}
6.5、冒泡排序
冒泡排序的基本思想: 通过对待排序序列前后,一次比较相邻元素的排序码,若发现逆序则交换,使排序码较大的元素逐渐从前部移向后部;
#include <stdio.h>
int main()
{
int array[] = {43, 32, 76, -98, 0, 64, 33, -21, 32, 99};
int i = 0,j = 0;
int length = sizeof(array) / sizeof(array[0]);
int temp = 0;
for(i = 0; i < length - 1; i++)
{
for(j = 0; j < length - 1 - i; j++)
{
if (array[j] > array[j + 1]) {
temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
for(i = 0; i < length; i++)
{
printf("%d\t",array[i]);
}
return 0;
}