数组:一组相同类型元素的集合
一、一维数组 1.1 一维数组的声明 元素类型 数组名[元素个数] 说明:元素个数一般为常量表达式 ,c99标准支持可变长数组,即元素个数可以使用变量表示
1.2 一维数组的初始化 初始化:在数组声明时为数组赋值
1 2 3 4 int arr1[5 ] = {1 ,2 ,3 };int arr2[5 ] = {1 ,2 ,3 ,4 ,5 };int arr3[] = {1 ,2 ,3 };char arr4[] = "abc"
说明: 1.在数组声明时为数组赋值,可省略数组元素个数,此时元素个数为初始值个数
2.如果初始值个数小于元素个数时,剩余数组元素值被初始化为0
3.只能在数组声明时为数组 赋值,其他时候只能为数组元素 赋值,不能为数组赋值
1 2 3 4 5 int arr1[5 ];arr = {1 ,2 ,3 ,4 ,5 }; int arr2[] = {1 ,2 ,3 };int arr3[3 ];arr3 = arr2;
1.3 引用数组元素 数组名[下标] 说明: 1.数组元素通过下标来访问 2.数组下标从0开始,下标范围为[0,size-1] 3.size = sizeof(arr) / sizeof(arr[0]); 函数内此公式不适用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <stdio.h> int main () { int arr[5 ] = { 0 }; int size = sizeof (arr) / sizeof (arr[0 ]); int i = 0 ; for (i = 0 ; i < size; i++) { arr[i] = i; } for (i = 0 ; i < size; i++) { printf ("%d " ,arr[i]); } return 0 ; }
输出
1.4 一维数组在内存中存储 一维数组在内存中是连续存放的,地址使用是从低地址到高地址
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <stdio.h> int main () { int arr[5 ] = { 1 ,2 ,3 ,4 ,5 }; int size = sizeof (arr) / sizeof (arr[0 ]); int i = 0 ; for (i = 0 ; i < size; i++) { printf ("%p\n" , &arr[i]); } return 0 ; }
输出
1 2 3 4 5 010F FD80010F FD84010F FD88010F FD8C010F FD90
二、二维数组 2.1 二维数组声明 元素类型 数组名[行数][列数] 数组元素个数=行数*列数,在声明时行数可以省略(但必须初始化)
2.2 二维数组的初始化 初始化:在数组声明的时候为其赋值
1 2 3 int arr1[3 ][3 ] = {1 ,2 ,3 };int arr2[2 ][2 ] = { {1 },{2 ,3 } };int arr3[][2 ] = { {1 ,2 },{3 ,4 } };
说明: 1.初始值个数少于行数与列数乘积,剩余数组元素被初始化为0
1 2 int arr1[3 ][3 ] = {1 ,2 ,3 };
2 只能在数组声明时为数组赋值 ,其他时候只能为数组元素赋值
1 2 3 4 5 int arr1[2 ][3 ] = { {1 ,2 ,3 },{4 ,5 ,6 } };int arr2[2 ][3 ];arr2 = arr1; arr2[0 ] = arr1[0 ] arr2[0 ][0 ] = arr1[1 ][1 ];
2.3 引用数组元素 数组名[行下标][列下标] 行下标:范围为 [0,行号-1] 列下标:范围为 [0,列号-1]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <stdio.h> int main () { int arr[2 ][3 ] = { 0 }; int i = 0 ; int j = 0 ; int flag = 0 ; for (i = 0 ; i < 2 ; i++) { for (j = 0 ; j < 3 ; j++) { arr[i][j] = flag; flag++; } } for (i = 0 ; i < 2 ; i++) { for (j = 0 ; j < 3 ; j++) { printf ("arr[%d][%d] = %d " , i, j, arr[i][j]); } printf ("\n" ); } return 0 ; }
2.4 二维数组在内存中存储 二维数组在内存中是连续存放的,地址使用是从低地址到高地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <stdio.h> int main () { int arr[2 ][3 ] = { 0 }; int i = 0 ; int j = 0 ; for (i = 0 ; i < 2 ; i++) { for (j = 0 ; j < 3 ; j++) { printf ("&arr[%d][%d] = %p\n" ,i,j,&arr[i][j]); } } return 0 ; }
输出
1 2 3 4 5 6 &arr[0 ][0 ] = 00B 8FE6C &arr[0 ][1 ] = 00B 8FE70 &arr[0 ][2 ] = 00B 8FE74 &arr[1 ][0 ] = 00B 8FE78 &arr[1 ][1 ] = 00B 8FE7C &arr[1 ][2 ] = 00B 8FE80
三、多维数组 多维数组:维数大于1个称为多维数组,如:二维数组、三维数组、四位数组等
3.1 多维数组声明
3.2 多维数组初始化 1 int arr1[2 ][2 ][2 = { { {1 ,2 },{3 ,4 } },{ {5 ,6 },{7 ,8 } } };
说明:多维数组中,只有第一维才能根据初始化列表缺省的提供,剩余的几维必修显式地写出
1 int arr1[][2 ][2 = { { {1 ,2 },{3 ,4 } },{ {5 ,6 },{7 ,8 } } };
四、数组下标越界 数组的下标是有范围的,下标范围为[0,元素个数-1 ],当下标不在此范围时就超出数组合法空间的访问,即数组下标越界。C语言标准并不规定数组下标越界检查 ,下标越界检查涉及开销比想象的多(涉及到指针指向空间是否在数组空间内),所以大部分编译器不做下标越界检查
4.1 下标越界访问 1 2 3 4 5 6 7 8 9 #include <stdio.h> int main () { int arr[5 ] = { 1 ,2 ,3 ,4 ,5 }; printf ("arr[%d] = %d\n" , -1 , arr[-1 ]); printf ("arr[%d] = %d\n" , 5 , arr[5 ]); return 0 ; }
输出
1 2 arr[-1 ] = -858993460 arr[5 ] = -858993460
大部分编译器对越界访问只会报警告,程序仍旧可以运行
4.2 下标越界修改 1 2 3 4 5 6 7 8 9 #include <stdio.h> int main () { int arr[5 ] = { 1 ,2 ,3 ,4 ,5 }; arr[-1 ] = 2 ; arr[5 ] = 3 ; return 0 ; }
程序错误,Run-Time Check Failure #2 - Stack around the variable ‘arr’ was corrupted.
结论:我们要自己对数组下标是否越界做检查,不要指望编译器
五、数组与指针 5.1 数组名 5.1.1数组名是什么 在C中,几乎所有使用数组名的表达式,数组名是数组首元素地址,是一个指针常量
例1
1 2 3 4 5 6 7 8 9 10 #include <stdio.h> int main () { int arr[5 ] = { 1 ,2 ,3 ,4 ,5 }; printf ("p = %p\n" , arr); printf ("&arr[0] = %p\n" , &arr[0 ]); return 0 ; }
输出
1 2 p = 008F FE64 &arr[0 ] = 008F FE64
例2
1 2 3 4 5 6 7 8 9 #include <stdio.h> int main () { int arr1[5 ] = { 1 ,2 ,3 ,4 ,5 }; int arr2[5 ]; arr2 = arr1; return 0 ; }
5.1.2 数组名两个例外 只有在两种情况下,数组名不是数组首元素地址例外1:sizeof(数组名)
1 2 3 4 5 6 7 8 9 10 11 12 #include <stdio.h> int main () { int arr[5 ] = { 1 ,2 ,3 ,4 ,5 }; printf ("%d\n" , sizeof (arr)); return 0 ; }
输出
例外2:&数组名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <stdio.h> int main () { int arr[5 ] = { 1 ,2 ,3 ,4 ,5 }; printf ("&arr = %p\n" , &arr); printf ("&arr[0] = %p\n" , &arr[0 ]); printf ("arr = %p\n" , arr); printf ("---------------\n" ); printf ("&arr+1 = %p\n" , &arr+1 ); printf ("&arr[0]+1 = %p\n" , &arr[0 ]+1 ); printf ("arr+1 = %p\n" , arr+1 ); return 0 ; }
输出
1 2 3 4 5 6 7 &arr = 004F F924 &arr[0 ] = 004F F924 arr = 004F F924 --------------- &arr+1 = 004F F938 &arr[0 ]+1 = 004F F928 arr+1 = 004F F928
5.2 下标引用与指针表达式 我们之前访问(修改)数组元素时是使用下标引用,但我们知道数组名一般情况下就是数组首元素的地址,是一个指针常量,所以本质上下标引用是指针表达式的伪装 。
array[subscript ] 等同于 *(array + subscript )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <stdio.h> int main () { int arr[5 ] = { 1 ,2 ,3 ,4 ,5 }; int i = 0 ; printf ("使用下标引用方式打印元素值\n" ); for (i = 0 ; i < 5 ; i++) { printf ("%d " , arr[i]); } int * p = arr; printf ("\n使用指针表达式方式打印元素值\n" ); for (i = 0 ; i < 5 ; i++) { printf ("%d " , *(p+i)); } return 0 ; }
输出
1 2 3 4 使用下标引用方式打印元素值 1 2 3 4 5 使用指针表达式方式打印元素值 1 2 3 4 5
当指针在表达式左边时,修改指针指向空间存放的值 当指针在表达式右边时,访问指针指向空间存放的值
1 2 3 4 5 6 7 8 9 10 11 12 #include <stdio.h> int main () { int arr[5 ] = { 1 ,2 ,3 ,4 ,5 }; int * p = arr; *p = 10 ; int i = *p; printf ("*p = %d\n" , *p); printf ("i = %d\n" , i); printf ("arr[0] = %d\n" , arr[0 ]); return 0 ; }
输出
1 2 3 *p = 10 i = 10 arr[0 ] = 10
说明:1.下标引用相对于指针表达式可读性更强 2.下标引用绝不会比指针更有效率,但指针有时会比下标引用更有效率
5.3 数组与指针区别 数组和指针并不是相等的
声明一个数组时,编译器根据声明所指定的元素个数为数组分配内存空间 ,然后再创建数组名,它的值是一个常量,指向这片空间的起始位置。
声明一个指针变量时,编译器只为指针本身分配内存空间 ,如果指针变量是一个全局变量则默认初始化为NULL,如果是局部变量不会被初始化,存放的是一个随机地址值
六、数组与函数 6.1 一维数组传参的函数设计 一位数组数组名是数组首元素地址 当在一个函数内部使用外部某个一维数组时,需要将该一维数组传入函数内,我们之前一般设计为数组形式
例1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <stdio.h> void print1 (int arr[5 ]) { int i = 0 ; for (i = 0 ; i < 5 ; i++) { printf ("%d " , arr[i]); } } int main () { int arr[5 ] = { 1 ,2 ,3 ,4 ,5 }; print1(arr); return 0 ; }
例2 : 我们发现例1设计时,函数写死了,数组元素个数只能为5,当外部数组元素个数不是5的时候,函数需要修改,有些人会想到在函数内部通过 sizeof(arr) / sizeof(arr[0])求元素个数,这是一种错误设计方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <stdio.h> void print2 (int arr[]) { int i = 0 ; int size = sizeof (arr) / sizeof (arr[0 ]); for (i = 0 ; i < size; i++) { printf ("%d " , arr[i]); } } int main () { int arr[5 ] = { 1 ,2 ,3 ,4 ,5 }; print2(arr); return 0 ; }
例3: 通过例2我们知道,如果在函数内部需要知道外部数组元素个数,可通过一个显式的参数传递给函数,指明数组元素个数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <stdio.h> void print3 (int arr[],int size) { int i = 0 ; for (i = 0 ; i < size; i++) { printf ("%d " , arr[i]); } } int main () { int arr[5 ] = { 1 ,2 ,3 ,4 ,5 }; int size = sizeof (arr) / sizeof (arr[0 ]); print3(arr,size); return 0 ; }
例4 我们知道一维数组的数组名为数组首元素地址,所以函数参数设计时应该用指针类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <stdio.h> void print4 (int * p,int size) { int i = 0 ; for (i = 0 ; i < size; i++) { printf ("%d " , *(p+i)); } } int main () { int arr[5 ] = { 1 ,2 ,3 ,4 ,5 }; int size = sizeof (arr) / sizeof (arr[0 ]); print4(arr,size); return 0 ; }
结论: 1.一维数组传参传递的是数组首元素地址,本质是指针 2.我们推荐使用例3、例4的方式进行函数设计,尤其是例4
6.2 二维数组传参的函数设计 二维数组的数组名是首元素地址,首元素是一个一维数组,即数组指针
1 2 3 4 5 int arr[3 ][4 ];int (*p)[4 ] = arr;
当在一个函数内部使用外部某个二维数组时,需要将该二维数组传入函数内,我们之前一般设计为数组形式 例1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <stdio.h> void print1 (int arr[2 ][2 ]) { int i = 0 ; int j = 0 ; for (i = 0 ; i < 2 ; i++) { for (j = 0 ; j < 2 ; j++) { printf ("arr[%d][%d] = %d " , i, j, arr[i][j]); } printf ("\n" ); } } int main () { int arr[2 ][2 ] = { {1 ,2 },{3 ,4 } }; print1(arr); return 0 ; }
例2: 我们发现例1设计时,函数写死了,数组的行和列只能为2,其他情况函数需要修改。可通过2个显式的参数传递给函数,指明行和列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <stdio.h> void print2 (int arr[][2 ],int row,int col) { int i = 0 ; int j = 0 ; for (i = 0 ; i < row; i++) { for (j = 0 ; j < col; j++) { printf ("arr[%d][%d] = %d " , i, j, arr[i][j]); } printf ("\n" ); } } int main () { int arr[2 ][2 ] = { {1 ,2 },{3 ,4 } }; int row = sizeof (arr) / sizeof (arr[0 ]); int col = sizeof (arr[0 ]) / sizeof (arr[0 ][0 ]); print2(arr,row,col); return 0 ; }
例3 我们知道二维数组的数组名为数组首元素地址,即1个一维数组的地址,所以函数参数设计时应该用指针类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <stdio.h> void print3 (int (*p)[2 ], int row, int col) { int i = 0 ; int j = 0 ; for (i = 0 ; i < row; i++) { for (j = 0 ; j < col; j++) { printf ("arr[%d][%d] = %d " , i, j, *(*(p+i)+j)); } printf ("\n" ); } } int main () { int arr[2 ][2 ] = { {1 ,2 },{3 ,4 } }; int row = sizeof (arr) / sizeof (arr[0 ]); int col = sizeof (arr[0 ]) / sizeof (arr[0 ][0 ]); print3(arr,row,col); return 0 ; }
例4 前三种方式列的个数只能为2,在设计这个函数时候我们可能并不知道这个二维数组的行数,只有调用者最清楚该二维数组行数,由于多维数组每个元素在内存中是连续存储,我们可以将二维数组作为一个一维数组使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 void print4 (int * p,int row,int col) { for (int i = 0 ;i<row;i++) { for (int j = 0 ;j<col;j++) { printf ("%d " ,p[i*row+j]); } printf ("\n" ); } } int main () { int arr[3 ][4 ] = {1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10 ,11 ,12 }; int row = sizeof (arr) / sizeof (arr[0 ]); int col = sizeof (arr[0 ]) / sizeof (arr[0 ][0 ]); print4((int *)arr,3 ,4 ); return 0 ; }