定义
数组是用来存储一系列相同类型数据的集合
静态数组
静态数组内存分配是连续的(即地址连续),最低的地址对应首元素,最高的地址对应末尾元素。在栈内存上分配了固定大小,在运行时这个大小不能改变。在函数执行完以后,系统自动销毁;
int a[5] = {1,2,3,4,5};
或 int a[] = {1,2,3,4,5};//定义时地址分配完毕,数组长度固定
定义一个静态数组实例
# include <stdio.h>
int main(void)
{
int a[5] = {1, 2, 3, 4, 5};
int i;
for (i = 0; i < 5; i++ )
{
printf("%d\n", a[i]);
}
return 0;
}
动态数组
动态数组的内存空间是从堆(heap)上分配(即动态分配)的,当程序执行到分配内存语句时,才为其分配存储空间。运行结束后,程序员需要free自行销毁释放分配的空间。
定义一个动态数组实例
#include <stdio.h>
#include <stdlib.h> //使用malloc函数需要调用此头文件
int main()
{
int Len; //数组长度
int *array; //数组指针
int i; //数组下标
//获取数组长度
printf("请输入数组长度:");
scanf("%d", &Len);
//动态分配内存空间,如果失败就退出程序
array = (int*)malloc( Len * sizeof(int) );
if(array == null)
{
printf("内存分配失败!\n");
exit(0);
}
//向数组占用的内存写入数据
for(i = 0; i < Len; i++ )
{
array[i] = i + 1;
}
// 遍历数组
for(i = 0; i < Len; i++)
{
printf("%d ", array[i]);
}
printf("\n");
free(array); //释放资源
return 0;
}
一维数组的定义和初始化
1、一维数组定义的一般形式:类型名 数组名[数组长度];
(1)类型名:指定数组中每个元素的类型
(2)数组名:数组变量的名称,是一个合法的标识符
(3)数组长度:一个整型常量表达式,设定数组的大小
如:
int a[10];//定义了一个有10个整型元素的数组a
float b[5];//定义了一个有5个单精度浮点型元素的数组b
2、一维数组的初始化:
(1)其一般形式为:类型名 数组名[数组长度] = {初值表}; 如:int arr[10] = {1,2,3,4,5,6,7,8,9,10};
(2)自动计算数组的长度:如果声明中未给出数组长度,编译器会把数组长度设置为刚好能容纳初始值的长度。
如:int arr[ ] = {1,2,3,4,5};等价于int arr[5] = {1,2,3,4,5};
(3)数组的初始化也可以只针对部分元素,如:int arr[5] = {1,2,3};那么数组的前三个元素分别为1,2,3,剩余的元素被初始化为0。
(4)如果初始化列表的项数>数组元素个数,编译器会视其为错误,如:int arr[5] = {1,2,3,4,5,6);//error
(5)指定初始化器:初始化指定元素。
#include <stdio.h>
int main()
{
int days[8] = { 31, 28, [4] = 31, 30, 31, [1] = 29};
int i;
for(i = 0; i < 8; i++)
{
printf("%d %d\n",i + 1, days[i]);
}
return 0;
}
编译并运行该代码,输出如下:
1 31
2 29
3 0
4 0
5 31
6 30
7 31
8 0
3、指定初始化器的两个重要特性:
如果指定初始化器后面有更多的值,那么这些值将被初始化指定元素后面的元素,即days[4]被初始化为31后,days[5]和days[6]将分别被初始化为30,31
如果再次初始化指定元素,那么最后的初始化将会取代之前的初始化,即days[1]本来被初始化为28,但是days[1]又被后面指定初始化为29.
如果没有指定元素大小,编译器会把数组的大小设置为足够装得下初始化的值,如:int arr[ ] = {1,[6] = 23}; arr数组有7个元素。
一维数组的使用:
1.[ ]为下标引用操作符。数组元素的引用要指定下标,其形式为:数组名[下标]。注意:数组下标从0开始,其取值范围为[0,数组长度-1],下标不能越界。用于识别数组元素的数字被称为下标、索引或者偏移量。所以a[0]的意思就是数组a中的第1个元素(生活中都是从1开始计数的),但是其实在计算机中应该中描述其为第0个元素。
2.看以下代码:
#include <stdio.h>
int main()
{
int arr[10] = {0}; //数组的不完全初始化
int i; //作为数组的下标
int sz = sizeof(arr)/sizeof(arr[0]);//计算数组的元素个数
//对数组内容进行赋值
for(i=0; i<10; i++)
{
arr[i] = i;
}
//输出数组的内容
for(i=0; i<10; ++i)
{
printf("%d ", arr[i]);
}
return 0;
}
编译并运行该代码,输出如下:
0 1 2 3 4 5 6 7 8 9
(1)数组的大小是可以通过计算得到的。sizeof(arr)是整个数组的大小,sizeof(arr[0])是数组中一个元素的大小,两者相除得到的就是数组元素的个数。
(2)这段代码使用了一个循环给数组赋值,注意for循环中的第二条表达式(test)中不能写成i <= 10,这样会造成数组下标越界。C语言本身是不做数组下标的越界检查,编译器也不一定报错,但是编译器不报错,并不意味着程序就是正确的,所以程序员写代码时,最好自己做越界的检查。
(3)另外,C不允许把数组作为一个单元赋给另一个数组,如:
#include <stdio.h>
int main()
{
int oxen[5] = {1, 2, 3, 4, 5};
int yaks[5];
yaks = oxen;//error
return 0;
}
一维数组在内存中的存储
看以下代码:
#include <stdio.h>
int main()
{
int arr[5] = {0};
int i;
for(i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i)
{
printf("&arr[%d] = %p\n", i, &arr[i]);
}
return 0;
}
编译并运行该代码,输出如下:

随着数组下标的增长,元素的地址,也在有规律的递增。因为arr数组的类型为int,所以每个地址之间相差4个字节。由此可以得出结论:数组在内存中是连续存放的。
二维数组的定义和初始化
C语言支持多维数组,最常见的多维数组为二维数组。二维数组本质上是以数组作为数组元素的数组,即“数组的数组”。二维数组的定义形式为:类型名 数组名[行长度][列长度];如:int arr[3] [2];//定义了一个二维数组,3行2列,共6个元素。
二维数组的初始化
(1)分行赋初值:一般形式为:类型名 数组名[行长度][列长度]={{初值表0},{初值表1},...,{初值表n}};
如:
int arr[2][2] = { { 1, 2} , {3, 4} };
此时arr数组中各元素为:
1 2
3 4
二维数组的初始化,也可以只针对部分元素,如:int arr[2][2] = { {1, 2},{} };此时arr数组的第0行的元素的值分别为1,2;而第一行的元素的初值默认为0。
(2)顺序赋初值:一般形式为:类型名 数组名[行长度][列长度]={初值表};
如:
int arr[2][2] = { 1, 2, 3, 4 };等价于int arr[2][2] = { { 1, 2} , {3, 4} };
int arr[2][2] = { 1, 2, 0, 0 };等价于int arr[2][2] = { {1, 2},{} };
(3)注意:二维数组初始化时,如果对全部元素都赋了初值,或分行赋初值时,在初值表中列出了全部行,就可以省略行长度。
如:
int arr[ ][3] = {1,2,3,4,5,6,7,8,9};等价于int arr[3][3] = {{1,2,3},{4,5,6},{7,8,9}};
不可以省略列的长度!!!
二维数组的使用
1.引用二维数组的元素要指定两个下标,即行下标和列下标,形式为:数组名[行下标][列下标]。行下标的合理取值范围为[0,行长度-1],列下标的合理取值范围为[0,列长度-1]。比如a[0][0]的意思是第1行第1列的元素(生活中从1开始计数),但是其实在计算机中应该中描述其为第0行第0列的元素。
用两重循环来给二维数组赋值:
#include <stdio.h>
int main()
{
int arr[2][2] = {0};
int i, j;
for(i = 0; i < 2; i++)
{
for(j = 0; j < 2; j++)
{
arr[i][j] = i * 4 + j;
}
}
for(i = 0; i < 2; i++)
{
for(j = 0; j < 2; j++)
{
printf("%d ", arr[i][j]);
}
}
return 0;
}
编译并运行代码,输出如下:
0 1 4 5
二维数组在内存中的存储
看以下代码:
#include <stdio.h>
int main()
{
int arr[2][2];
int i, j;
for(i = 0; i < 2; i++)
{
for(j = 0; j < 2; j++)
{
printf("&arr[%d][%d] = %p\n", i, j,&arr[i][j]);
}
}
return 0;
}
编译并运行该代码,输出如下:

由此,我们可以分析,二维数组在内存中也是连续存储的。所以,二维数组可以整体看成一个一维数组,如图:

数组作为函数参数
1.数组名是数组首元素的地址,数组名是一个地址常量。
2.两个例外:
(1)sizeof(数组名),计算整个数组的大小,sizeof内部单独放一个数组名,数组名表示整个数组。
(2)&数组名,取出的是数组的地址。&数组名,数组名表示整个数组
除了这 两种情况之外,其它所有的数组名都表示数组首元素的地址,如:*数组名等等
看以下代码以及结果:

test()中打印的是长度为4,更加印证了数组作为函数参数时,没有把整个数组都传过去,而是把数组中首元素的地址传过去了,此处的地址是用十六进制表示的整型常量,所以大小为4byte。
main()中打印的长度为40,是因为sizeof(数组名)计算的是整个数组的大小,数组中有10个int型的元素,所以10*4=40
函数定义头中的形参int arr[]里面的[],它的里面可以什么都不填,也可以填一个任意数,因为它没有意义,比如说形参写成int arr[20]也是可以的,但是为了代码的可读性更强,我们不建议这样写。
其实,test()的形参还可以这样写:
void test(int* arr)
{
printf("%d\n",sizeof(arr));
}
因为数组名是数组首元素的地址,我们用指针来接收arr的首元素地址更好。注意:只有在函数原型或函数定义头中,才可以用int arr[]代替int* arr。
在函数原型中可以省略参数名,所以下面这4种写法等效:
void test(int* arr);
void test(int arr[]);
void test(int*);
void test(int []);