数组(Array)
1、数组的定义
1.1 何为数组
数组是相同类型数据的有序集合。数据描述的是相同类型得到数据按照一定先后次序排列组合而成。每一个数据成为一个元素,每个元素可以通过下标的方式访问它们。数组的定义的一般方式为:“数组类型 数组名[常量表达式]”,需要注意的是,常量表达式用来表示数组长度,它的下标是从0开始的。而数组名表示数组首地址,是地址常量,数组再被编译时分配连续内存,它的大小是这样计算的: 内存字节数=数组长度*sizeof(数据类型)
1.2 数组的特点
1、长度是确定的。数组一旦被创建,它的大小就是不可以改变的。
2、其元素类型必须是相同类型,不允许出现混合类型。
3、数组可以存储任何存储类型,包括基本类型和引用类型。
4、数组变量属于引用类型,数组也是对象。
5、数组必须先定义,后使用。
6、只能逐个引用数组,不能一次引用整个数组。
2、数组的初始化
2.1 一维数组的创建
数组的创建方式:
type_t arr_name [const_n];
//type_t 是指数组的元素类型
//arr_name 是数组的名字
//const_n 是一个常量表达式,用来指定数组的大小
数组创建的实例:
//代码1
int arr1[10];
char arr2[10];
float arr3[1];
double arr4[20];
//代码2
//用宏定义的方式
#define X 3
int arr5[X];
//代码3
//错误使用
//数组创建,[]要给一个常量才可以,不能使用变量。可以使用常量,或者直接使用宏定义
int count = 10;
int arr6[count];
2.1 一维数组的初始化
数组的初始化是指,在创建数组的同时同时给数组的内容一些合理初值。
1、数组个数和数值个数一致
int arr1[5] = {5, 8, 6, 9, 3};
2、数组大小大于初始元素数目,超过初始元素部分自动补0
int arr2[6] = {5, 6, 7};
3、不指定数组大小
int arr3[] = {5, 6, 4, 3};
5、存储字符串时,注意数组大小和最后一项的差别
(1)字符数组存储字符串
char arr4[] = "abcdef";//当不指定数组大小时,最后一位默认补0
(2)字符数组的大小和字符串字符个数一致
char arr5[6] = "abcdef";
注:这样初始化时存在问题,因为无法正常读取字符串结束标志('\0'),导致字符串的长度和内容不能得知。
(3)字符数组的大小大于字符串的字符数
char arr6[6] = "zxc"; //不足数组长度部分自动补0
说明:
1、数组是具有相同元素的集合,数组的大小由元素的个数乘以元素的大小
2、数组只能够整体初始化,不能被整体赋值,只能使用循环从第一个元素逐个遍历赋值。
3、初始化时,数组的维度或者元素个数可以忽略,编译器会根据花括号中元素的个数初始化数组元素的个数。
4、当花括号中用于初始化值的个数不足数组元素的大小时,数组剩下的元素依次用0进行初始化。
5、字符串数组在计算机内部用的是对应的ASCII码值进行存储的。
6、一般“”内的字符串,不用数组保存时,一般直接编译到字符变量区,并且不可被修改。
在内存中存储
2.3 一维数组的使用
下标引用操作符[],它其实就是数组访问的操作符,e.g.
#include <stdio.h>
int main() {
int arr[10] = {0}; //数组的不完全初始化
int sz = sizeof(arr) / sizeof[0]; //计算数组中元素的个数
//下面对数组元素进行赋值,数组元素是使用下标来访问的,下标从0开始
int i = 0; //做下标此时可以是变量
for (i = 0; i<10; i++) {
arr[i] = i;
}
//输出数组的内容
for (i = 0; i<10; i++) {
printf("%d", arr[i]);
}
renturn 0;
}
说明:
1、sizeof()操作符用于取长度,以字节为单位。sizeof(数组名)即求的整个数组的大小。sizeof(首元素)即求整个元素的大小。用0下标,因为数组至少存在一个元素,所以0下标一定存在。
2、数组是使用下标访问的,数组下标从0开始。
3、数组大小可以通过计算得到,一般计算方式为 sizeof(arr)/sizeof[0]
2.4一维数组在内存中的存储
#include <stdio.h>
int main() {
int arr[0] = {10};
int size = sizeof(arr) / sizeof(arr[0]);
for (i = 0; i<size; i++) {
printf("&arr[%d] = %p\n", i, &arr[i]);
}
return 0;
}
说明:
1、随着下标的增长,元素的地址也在有规律的增长,进而可以推算出数组中的元素在内存中是连续存储的。
2、在C语言中,任何变量(基本变量、指针变量、结构体变量和数组变量)的空间都是整体开辟的,但任何变量的起始地址一定是开辟字节中最小的。
3、二维数组及多维数组
3.1二维数组的创建和初始化
(1)二维数组的创建
//数组创建
int arr[3][4];
char [][5];
double arr[2][4];
二维数组创建时,行数可以不写,并且所有维度的第一个方括号的内容可以忽略。
(2)二维数组的初始化
//数组初始化
int arr[3][4] = {1, 2, 3, 4};
int arr[3][4] = {{1,2},{4,5}};
int arr[][4] = {{2,3},{4,5}};
说明:花括号中的一个花括号代表一维数组的初始化,当里面无花括号分组时,按照元素的顺序逐个进行初始化,预先未赋值的位置用0初始化。
3.2二维数组的使用
二维数组的使用也是通过下标的方式,用双重循环嵌套来使用。
include <stdio.h>
int main() {
int arr[3][4] = {0};
for (int i = 0; i<3; i++) {
for (int j = 0; j<=4; j++) {
a[i][j] = 4*i + j;
}
}
for (int i = 0; i<3; i++) {
for (int j = 0; j<=4; j++) {
printf("%d", a[i][j]);
}
}
return 0;
}
如果尝试打印一下上述代码中的二维数组的每个元素。
include <stdio.h>
int main() {
int arr[3][4] = {0};
for (int i = 0; i<3; i++) {
for (int j = 0; j<=4; j++) {
printf("arr[%d][%d] = %p\n", i, j, &a[i][j]);
}
}
return 0;
}
说明:二维数组在内存上也是线性连续存储的。
4、数组作为函数参数传递
1、调用函数传输函数组时,应注意减少函数传输数组时的成本问题。因为传参时,需要临时拷贝,如果数组过大会浪费资源,严重的话可能会导致栈溢出。
2、数组元素降维成指向数组内部元素的指针。
3、对指针加一,等于指针所指向的类型的大小。
4.1 一维数组
#include <stdio.h>
void base(int arr[]) {
printf("a = %d\n", sizeof(arr));//数组降维成指针后的指针大小,在32位系统下指针为4字节
printf("b = %d\n", sizeof(arr[0]));//数组首元素的大小
printf("sz = a/b = %d\n", sizeof(arr) / sizeof(arr[1])); //大小为1
printf("arr = %p\n", arr); //数组首元素地址
printf("&arr = %p\n", &arr); //指针的地址
printf("arr + 1 = %p\n", arr + 1); //下一个元素的地址
printf("&arr + 1 = %p\n", &arr + 1); //指针下一项的地址
}
int main(void) {
int shuzu[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
printf("a = %d\n", sizeof(shuzu)); //数组总大小
printf("b = %d\n", sizeof(shuzu[0]));//数组首元素的大小
printf("sz = a/b = %d\n", sizeof(shuzu) / sizeof(shuzu[0])); //数组元素个数
printf("shuzu = %p\n", shuzu); //数组首元素地址
printf("&shuzu = %p\n", &shuzu); //代表整个数组,但是地址仍是首元素地址
printf("shuzu + 1 = %p\n", shuzu + 1); //下一个元素的地址
printf("&shuzu + 1 = %p\n", &shuzu + 1); //跳过整个数组后紧挨着的地址,此时改地址减去
//首元素地址等于数组大小
printf("\n\n");
base(shuzu);
return 0;
}
说明:
1、sizeof(数组名),计算整个数组的大小,sizeof()内部单独存放一个数组,数组名表示整个数组。
2、&数组名,取出的是数组的地址。&数组名,数组名表示整个数组。
3、形参格式,例如int arr[]或者int *arr,两者等价
4、形参个数可以被忽略,否则有可能改变实参的大小,最好是忽略,亦或是填写比实参元素个数大的值。
5、用sizeof()求数组元素个数时,尽量在数组定义是求,否则传参后数组会降维成指针。
4.2 二维数组
数组元素降维会变成指向内部元素类型的指针,二维int整型数组内部的元素不再是int类型,而是具有四个整型的一维数组。对指针加一,就是加上指针所指向的类型的大小。对二维数组指针加一,加上的值为内部一维数组的大小。
#include <stdio.h>
void base(int arr[][4]) {
printf("a = %d\n", sizeof(arr));//数组降维成指针后的指针大小,在32位系统下指针为4字节
printf("b = %d\n", sizeof(arr[0][0]));//数组首元素的大小
printf("sz = a/b = %d\n", sizeof(arr) / sizeof(arr[0][0])); //大小为1
printf("arr = %p\n", arr); //数组首元素地址
printf("&arr = %p\n", &arr); //指针的地址
printf("arr + 1 = %p\n", arr + 1); //下一个元素的地址
printf("&arr + 1 = %p\n", &arr + 1); //指针下一项的地址
}
int main(void) {
int shuzu[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
printf("a = %d\n", sizeof(shuzu)); //数组总大小
printf("b = %d\n", sizeof(shuzu[0][0]));//数组首元素的大小
printf("sz = a/b = %d\n", sizeof(shuzu) / sizeof(shuzu[0][0])); //数组元素个数
printf("shuzu = %p\n", shuzu); //数组首元素地址
printf("&shuzu = %p\n", &shuzu); //代表整个数组,但是地址仍是首元素地址
printf("shuzu + 1 = %p\n", shuzu+1); //下一个元素的地址,这是其内部数组的一维数组
printf("&shuzu + 1 = %p\n", &shuzu); //跳过整个数组后紧挨着的地址,此时改地址减去
//首元素地址等于数组大小
printf("\n\n");
base(shuzu);
return 0;
}
说明:1、形参格式 int arr[][4] 或者 int (*arr)[4],这里为指向具有四个整型元素的一维数组的数组指针。除了第一个中括号的内容可以省略,后面中括号的内容不能省略,因为下标是数组类型的一部分,省略后会导致数组类型不明确。
2、其实所有数组,本质上都是一维数组,只不过内部元素不一样。例如:三维数组内部为二维数组,二维数组内部为一维数组。
5、数组指针和指针数组
数组指针:是指针,指向数组。e.g. int (*arr)[10]
指针数组:是数组,数组内容存放的是指针。e.g. int *arr[10]
其中,不同运算符之间有一个运算顺序:()>[]>*
所以,对于 (*p)[n]:根据优先级,先看括号内,则p是一个指针,这个指针指向一维数组,数组长度为n,这是“数组的指针”,即数组指针,*p[n]:根据优先级,先看[],则p是一个数组,再结合*,这个数组的类型是指针类型,共n个元素,这是“指针的数组”,即数组指针。
5.1指针数组
#include <stdio.h>
int main(){
int *p[4];
int arr1[3] = {1, 2, 3};
int arr2[4] = {2, 4, 6, 8};
int arr3[5] = {0};
int arr4[2] = {2, 2};
p[0] = arr1;
p[1] = arr2;
p[2] = arr3;
p[3] = arr4;
printf("%d\n", *(p[0] + 1));
printf("%d\n", *(p[1] + 1));
printf("%d\n", *(p[2] + 1));
printf("%d\n", *(p[3] + 1));
return 0;
}
对于语句int*p[4],因为[]的优先级要比*要高,所以p先于[]结合,构成一个数组的定义,数组名为p,而int*修饰的数组的内容,即数组的每个元素。也就是说,该数组包含4个指向int类型的指针,如图所示,因此它是一个指针数组。
5.2数组指针
#include <stdio.h>
int main(void) {
int shuzu[3][4] = { 0,1,2,3,4,5,6,7,8,9,0,0};
int(*arr)[4] = shuzu;
for (int i = 0; i<3;i++) {
for (int j = 0; j<4; j++) {
printf("arr[%d][%d] = %d",i, j,arr[i][j]);
}
printf("\n");
}
return 0;
}
其次,对于语句int(*arr)[4],“()”的优先级比[]高,*和arr构成一个指针的定义,指针变量名为arr,而int修饰的是数组的内容,即数组中的每个元素。也就是说,arr是一个指针。它指向一个包含4个int类型数据的指针,如图,很显然,它是一个数组指针。
6、程序举例
例1: 读10个整数存入数组,找出其中最大值和最小值
/*例1*/
#include <stdio.h>
#define SIZE 10
int main {
int x[SIZE], i, max, min;
printf("Enter 10 intrgers: \n");
for (i = 0; i<SIZE; ++i) {
printf("%d", i+1);
scanf("%d", &i);
}
max = min = x[0];
for (i=1; i<SIZE; i++) {
if (max < x[i]) max = x[i];
if (min > x[i]) min = x[i];
}
printf("Maxinum value in %d\n",max);
printf("Minimum value in %d\n",min);
}
例2 对10个元素实现冒泡排序(升序)
/*例2*/
#include <stdio.h>
int main() {
int a[11], i, j, t;
for (i = 1; i<11; i++) {
scanf("&d", &a[i]);
}
printf("\n");
for (j = 1; j<=9; j++) {
for (i = 1; i<=10-j; i++) {
if (a[i]<a[i+1]) {
t=a[i+1];
a[i+1]=a[i];
a[i]=t;
}
}
}
printf("The sorted numbers:\n");
for (i = 1; i<11; i++) {
printf("%d\n", a[i]);
}
}
例3:用简单选择法对10个数进行排序 (升序)
/*例3*/
#include <stdio.h>
int main() {
int a[11], i, j, t,k;
for (i = 1; i<11; i++) {
scanf("&d", &a[i]);
}
printf("\n");
for (j = 1; j<=9; j++) {
k = i;
for (i = ; i<=10-j; i++) {
if (a[j] < a[k]) k = j;
}
if (i!=k) {
t=a[i+1];
a[i+1]=a[i];
a[i]=t;
}
}
printf("The sorted numbers:\n");
for (i = 1; i<11; i++) {
printf("%d\n", a[i]);
}
}
7、参考文献
王道2025数据结构考研复习指导