C语言 数组
数组: 一组相同类型元素的集合
一、一维数组
1.1 一维数组的声明
元素类型 数组名[元素个数]
说明:元素个数一般为常量表达式,c99标准支持可变长数组,即元素个数可以使用变量表示
int arr[10]; //声明一个整形数组,数组有10个元素,数组名为arr
/*
可变长数组
int n = 10;
int arr[n];
*/
1.2 一维数组的初始化
初始化:在数组声明时为数组赋值
int arr1[5] = {1,2,3};
int arr2[5] = {1,2,3,4,5};
int arr3[] = {1,2,3};
char arr4[] = "abc" //等同于 arr4[] = {'a','b','c','\0'};
说明:
1.在数组声明时为数组赋值,可省略数组元素个数,此时元素个数为初始值个数
int arr3[] = {1,2,3}; //元素个数为:3
2.如果初始值个数小于元素个数时,剩余数组元素值被初始化为0
int arr1[5] = {1,2,3}; // 等同于 int arr1[5] = {1,2,3,0,0};
3.只能在数组声明时为数组赋值,其他时候只能为数组元素赋值,不能为数组赋值
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]);
函数内此公式不适用
#include <stdio.h>
//打印数组每个元素值
int main()
{
int arr[5] = { 0 };
int size = sizeof(arr) / sizeof(arr[0]); //size为数组元素个数
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 2 3 4 5
1.4 一维数组在内存中存储
一维数组在内存中是连续存放的,地址使用是从低地址到高地址
#include <stdio.h>
// 32位环境测试
int main()
{
int arr[5] = { 1,2,3,4,5 };
int size = sizeof(arr) / sizeof(arr[0]); //size为数组元素个数
int i = 0;
for (i = 0; i < size; i++)
{
printf("%p\n", &arr[i]); //打印对应元素在内存中地址
}
return 0;
}
输出
010FFD80
010FFD84
010FFD88
010FFD8C
010FFD90
二、二维数组
2.1 二维数组声明
元素类型 数组名[行数][列数]
数组元素个数=行数*列数,在声明时行数可以省略(但必须初始化)
int arr[3][2]; //声明一个二维数组,数组名为arr,数组有3行2列,元素个数为3*2,元素类型为int
2.2 二维数组的初始化
初始化:在数组声明的时候为其赋值
int arr1[3][3] = {1,2,3};
int arr2[2][2] = { {1},{2,3} };
int arr3[][2] = { {1,2},{3,4} };
说明:
1.初始值个数少于行数与列数乘积,剩余数组元素被初始化为0
int arr1[3][3] = {1,2,3}; // 等同于 int arr1[3][3] = { {1,2,3},{0,0,0},{0,0,0} };
//或等同于 int arr1[3][3] = {1,2,3,0,0,0,0,0,0};
2 只能在数组声明时为数组赋值,其他时候只能为数组元素赋值
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]
#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 二维数组在内存中存储
二维数组在内存中是连续存放的,地址使用是从低地址到高地址
#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;
}
输出
&arr[0][0] = 00B8FE6C
&arr[0][1] = 00B8FE70
&arr[0][2] = 00B8FE74
&arr[1][0] = 00B8FE78
&arr[1][1] = 00B8FE7C
&arr[1][2] = 00B8FE80
三、多维数组
多维数组:维数大于1个称为多维数组,如:二维数组、三维数组、四位数组等
3.1 多维数组声明
int arr[3][4][5];//声明1个三维数组,数组名为arr,数组为3排、4行、5列,元素个数=3*4*5,元素类型为int
3.2 多维数组初始化
int arr1[2][2][2 = { { {1,2},{3,4} },{ {5,6},{7,8} } };
说明:多维数组中,只有第一维才能根据初始化列表缺省的提供,剩余的几维必修显式地写出
int arr1[][2][2 = { { {1,2},{3,4} },{ {5,6},{7,8} } }; //第一维度省略不写,通过计算子数组的个数,得到第一维度为2
四、数组下标越界
数组的下标是有范围的,下标范围为[0,元素个数-1],当下标不在此范围时就超出数组合法空间的访问,即数组下标越界。C语言标准并不规定数组下标越界检查,下标越界检查涉及开销比想象的多(涉及到指针指向空间是否在数组空间内),所以大部分编译器不做下标越界检查
4.1 下标越界访问
#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;
}
输出
arr[-1] = -858993460
arr[5] = -858993460
大部分编译器对越界访问只会报警告,程序仍旧可以运行
4.2 下标越界修改
#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
#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]);
//arr++; 此代码错误,因为arr是数组首元素地址,是一个指针常量,常量不能更改
return 0;
}
输出
p = 008FFE64
&arr[0] = 008FFE64
例2
#include <stdio.h>
int main()
{
int arr1[5] = { 1,2,3,4,5 };
int arr2[5];
arr2 = arr1;//此代码错误,因为arr2是arr2数组首元素地址,一个指针常量,不能被修改
return 0;
}
5.1.2 数组名两个例外
只有在两种情况下,数组名不是数组首元素地址
例外1:sizeof(数组名)
#include <stdio.h>
//32位环境测试
int main()
{
int arr[5] = { 1,2,3,4,5 };
/*
在sizeof内部单独放一个数组名,数组名表示整个数组大小,int类型大小为4,元素个数为5,数组大小为20字节
而不是当作一个地址大小计算(32位平台地址大小为4字节)
*/
printf("%d\n", sizeof(arr));
return 0;
}
输出
20
例外2:&数组名
#include <stdio.h>
//32位环境测试
int main()
{
int arr[5] = { 1,2,3,4,5 };
printf("&arr = %p\n", &arr); // &数组名,取出的是数组地址
printf("&arr[0] = %p\n", &arr[0]); // &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;
}
输出
&arr = 004FF924
&arr[0] = 004FF924
arr = 004FF924
---------------
&arr+1 = 004FF938
&arr[0]+1 = 004FF928
arr+1 = 004FF928
5.2 下标引用与指针表达式
我们之前访问(修改)数组元素时是使用下标引用,但我们知道数组名一般情况下就是数组首元素的地址,是一个指针常量,所以本质上下标引用是指针表达式的伪装。
array[subscript ]
等同于 *(array + subscript )
#include <stdio.h>
//32位环境测试
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; //整形指针变量p存放arr数组首元素地址
printf("\n使用指针表达式方式打印元素值\n");
for (i = 0; i < 5; i++)
{
printf("%d ", *(p+i)); // *(p+i) 等同于 *(arr+i)
}
return 0;
}
输出
使用下标引用方式打印元素值
1 2 3 4 5
使用指针表达式方式打印元素值
1 2 3 4 5
当指针在表达式左边时,修改指针指向空间存放的值
当指针在表达式右边时,访问指针指向空间存放的值
#include <stdio.h>
int main()
{
int arr[5] = { 1,2,3,4,5 };
int* p = arr;
*p = 10; //指针在表达式左边,修改p指向空间存放值,等同于 arr[0] = 10;
int i = *p; //指针在表达式右边,访问p指向空间存放值,等同于 int i = arr[0]
printf("*p = %d\n", *p);
printf("i = %d\n", i);
printf("arr[0] = %d\n", arr[0]);
return 0;
}
输出
*p = 10
i = 10
arr[0] = 10
说明:
1.下标引用相对于指针表达式可读性更强
2.下标引用绝不会比指针更有效率,但指针有时会比下标引用更有效率
5.3 数组与指针区别
数组和指针并不是相等的
- 声明一个数组时,编译器根据声明所指定的元素个数为数组分配内存空间,然后再创建数组名,它的值是一个常量,指向这片空间的起始位置。
- 声明一个指针变量时,编译器只为指针本身分配内存空间,如果指针变量是一个全局变量则默认初始化为NULL,如果是局部变量不会被初始化,存放的是一个随机地址值
六、数组与函数
6.1 一维数组传参的函数设计
一位数组数组名是数组首元素地址
当在一个函数内部使用外部某个一维数组时,需要将该一维数组传入函数内,我们之前一般设计为数组形式
例1:
#include <stdio.h>
//32位环境测试
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])
求元素个数,这是一种错误设计方法
#include <stdio.h>
//32位环境测试
/*
传入实参arr是数组名,本质是数组首元素地址,是一个指针类型,形参应该设计为int* p,但也可以写成int arr[]
[]内元素个数可以不指定,因为编译器都会当作指针来处理
*/
void print2(int arr[])
{
int i = 0;
/*
arr是一个指针,sizeof(arr)是求指针大小,32位指针大小为4字节,64位为8字节,本机是32位环境
所以sizeof(arr) = 4。
sizeof(arr[0]) 由于arr[0]是int类型,所以sizeof(arr[0])等同于sizeof(int) = 4。
即sizeof(arr) / sizeof(arr[0]) 等同于 4 /4 =1,即 int size = 1;
*/
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我们知道,如果在函数内部需要知道外部数组元素个数,可通过一个显式的参数传递给函数,指明数组元素个数
#include <stdio.h>
//32位环境测试
void print3(int arr[],int size) //形式参数设计为数组形式,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
我们知道一维数组的数组名为数组首元素地址,所以函数参数设计时应该用指针类型
#include <stdio.h>
//32位环境测试
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 二维数组传参的函数设计
二维数组的数组名是首元素地址,首元素是一个一维数组,即数组指针
int arr[3][4];
int (*p)[4] = arr; //p存放数组arr首元素地址
// 下标引用运算符优先级高于间接访问运算符,但是由于小括号存在,p首先与*匹配,即p是指针。接下来与
// 下标引用运算符匹配,表明p指向某种类型的数组,最后与int匹配,所以p是一个指向数组的指针,数组有10个
// 元素,每个元素为int类型
当在一个函数内部使用外部某个二维数组时,需要将该二维数组传入函数内,我们之前一般设计为数组形式
例1:
#include <stdio.h>
//32位环境测试
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个显式的参数传递给函数,指明行和列
#include <stdio.h>
//32位环境测试
/*
二维数组的数组名是一维数组的地址,本质是一个指针,使用数组形式传参,列的大小无法省略,这一点只能写死
*/
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个一维数组的地址,所以函数参数设计时应该用指针类型
#include <stdio.h>
//32位环境测试
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,在设计这个函数时候我们可能并不知道这个二维数组的行数,只有调用者最清楚该二维数组行数,由于多维数组每个元素在内存中是连续存储,我们可以将二维数组作为一个一维数组使用
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;
}