数组(Array)是有序的元素序列。 若将有限个类型相同的变量的集合命名,那么这个名称为数组名。组成数组的各个变量称为数组的分量,也称为数组的元素,有时也称为下标变量。用于区分数组的各个元素的数字编号称为下标。数组是在程序设计中,为了处理方便, 把具有相同类型的若干元素按有序的形式组织起来的一种形式。这些有序排列的同类数据元素的集合称为数组。
所以,数组是用于储存多个相同类型数据的集合。
int a;
int b[10];
我们可以确定a就是一个整型变量,那么b[10]就是一个整型类型的数组,b就是表示数组名,那么数组名的类型又是什么?在C语言中,数组名b实际上就是一个指针常量,即数组第一个元素的地址,类型取决于数组元素的类型;当然,在这里,不能把数组看作是指针,数组具有一些与指针完全不同的特征,数组在内存中是连续存放的,而指针只是一个标量。所以数组名表示的就是数组首元素的地址,但是,有两种情况是例外的:sizeof(数组名)和取地址&数组名,这里sizeof(数组名)表示整个数组的字节长度,而不是指针的字节长度;
int b[10];
printf(“%d”,sizeof(b));
结果:40
解释:整型数组中,一个元素表示4个字节,一个数组就是4*10=40个字节
int b[10];
printf(“%p\n”,b);
printf(“%p\n”,&b);
printf(“%p\n”,b+1);
printf(“%p\n”,&b+1);
结果:
000000000061FDF0
000000000061FDF0
000000000061FDF4
000000000061FE18
在数组中,我们可以通过引用数组元素下标来找到指定元素。C语言规定:数组下标从0开始。
例如,我们想找到第五个元素,那么我们就直接引用b[4]即可。实际上,在C语言中,b[4]是用解引用来调取的,即*(b+4),C语言用[]来表示,使表达上更加简洁了。
来看下面代码:
#include
int main()
{
int arr[10] = {0};//数组的不完全初始化
//计算数组的元素个数
int sz = sizeof(arr)/sizeof(arr[0]);
//对数组内容赋值,数组是使用下标来访问的,下标从0开始。所以:
int i = 0;//做下标
for(i=0; i<10; i++)
{
arr[i] = i;
}
//输出数组的内容
for(i=0; i<10; ++i)
{
printf("%d ", arr[i]);
}
return 0;
}
这就是对数组的下面引用,我们可以通过for循环来进行进行遍历数组,可以进行赋值,打印等一系列操作。
简单的来说,创建一个数组就是数组名加上[],花括号需要给出常量值。如:
int arr1[10];
接着我们看,
int count = 10;
int arr2[count];
这里可不可行呢?
数组创建,在C99标准之前, [] 中要给一个常量才可以,不能使用变量。在C99标准支持了变长数组的概念,数组的大小可以使用变量指定,但是数组不能初始化。
数组的初始化和变量的初始化意义是相同的,都是为了给出合理的初始值。
如:
int arr1[10] = {1,2,3};
int arr2[] = {1,2,3,4};
int arr3[5] = {1,2,3,4,5};
char arr4[3] = {'a',98, 'c'};
char arr5[] = {'a','b','c'};
char arr6[] = "abcdef";
arr1中已经确定了该数组有10个元素,我们可以用{}来对数组进行赋值,但是我们可以看到这里只是赋值了3个,C中规定,未被赋值的默认为0.所以前三个元素分别被赋值为1,2,3,其他为0.
arr2中,[]没有给出指定个数,但是在后面的花括号初始化中,赋值了4个元素,所以,就被默认为arr2中有4个元素,且4个元素分别被赋值。
arr3中就是对第一个元素到最后一个元素都赋值了。
arr4是一个字符数组,我们发现第二个元素赋值成98,那么对于字符来说,这个98代表的是ASCLL表中第98个值,为‘b’。
arr5等同于arr4
arr6我们发现是用一串字符串来初始化,对于字符数组来说,这是合理的。因为一个元素表示一个字符。所以,对于下面两个数组,其实是等价的。
char arr1[] = "abc";
char arr2[3] = {'a','b','c'};
上面我们说过,数组在内存中是连续存储的,我们来看以下代码:
#include
int main()
{
int arr[10] = {0};
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
for(i=0; i<sz; ++i)
{
printf("&arr[%d] = %p\n", i, &arr[i]);
}
return 0;
}
数组地址是从低地址到高地址的且相邻地址相差4,正好是一个整型字节。所以得出数组在内存中是连续存放的。
如果说一维数组是一条线的话,那么二维数组就是一个面。
二维数组的表示:
int arr[3][4];
char arr[3][5];
double arr[2][4];
我们以其中的整型二维数组来说明,arr是一个包含3个元素的向量,每个元素本身还是一个包含5个元素的向量。换句话说,arr是个一维数组的一维数组。也就是说,我们可以把第一个[]中常量看作行数,第二个[]中常量看作是列数来进行表示。
但要记住,这只是为了方便理解给用户自己看的,是一种伪装形式,在多维数组也是如此。
#include
int main()
{
int arr[3][4];
int i = 0;
for(i=0; i<3; i++)
{
int j = 0;
for(j=0; j<4; j++)
{
printf("&arr[%d][%d] = %p\n", i, j,&arr[i][j]);
}
}
return 0;
}
结果:
可以看出,在二维数组中,数组元素内存依旧是连续存放的。
int arr[3][4] = {1,2,3,4};
解释:arr数组中有三行四列,初始化中,只对第一行进行了赋值初始化,其他默认为0.
int arr[3][4] = {{1,2},{4,5}};
解释:arr中有多个花括号,在第二层花括号中,表示对该行数的某个元素进行初始化。
int arr[][4] = {{2,3},{4,5}};
解释:二维数组如果有初始化,行可以省略,列不能省略。
二维数组下标引用与一维数组是相同的。
看以下代码:
#include
int main()
{
int arr[3][4] = {0};
int i = 0;
for(i=0; i<3; i++)
{
int j = 0;
for(j=0; j<4; j++)
{
arr[i][j] = i*4+j;
}
}
for(i=0; i<3; i++)
{
int j = 0;
for(j=0; j<4; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}
结果:
解释:二维数组是从0 0下标开始的,到最后一个元素下标3 3,利用两个for循环嵌套遍历,得出结果。
当我们建好一个数组后,并对它进行了初始化,就表示它的空间内存就已经是确定的了,当在访问时,如果下标引用超过范围时,就是越界访问了。C语言本身是不做数组下标的越界检查,编译器也不一定报错,但是编译器不报错,并不意味着程序就是正确的,所以程序员写代码时要做好检查。
如:
#include
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int i = 0;
for(i=0; i<=10; i++)
{
printf("%d\n", arr[i]);//当i等于10的时候,越界访问了
}
return 0;
}
由上面我们知道,数组名是一个指针,当数组名作为参数传递给一个函数实际就是传递了首元素地址过去,然后在函数中对该指针执行间接访问操作实现对数据的访问。由于数组是在内存中连续存放的,所以在函数中调用数组的形参时,能跟在主函数一样调用数组。但是,正因为数组名是指针,在函数中用sizeof计算数组大小的时候,往往出现错误,因为此时调用过去的是首元素地址。所以解决方法经常在使用数组作为参数时,会带上一个数组元素个数的变量。
如:调用冒泡排序函数时,都会先在数组所在函数先计算好个数,再进行传参。
void bubble_sort(int arr[], int sz)//参数接收数组元素个数
{
int i = 0;
for(i=0; i<sz-1; i++)
{
int j = 0;
for(j=0; j<sz-i-1; j++)
{
if(arr[j] > arr[j+1])
{
int tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
}
}
}
}
int main()
{
int arr[] = {3,1,7,5,8,9,0,2,4,6};
int sz = sizeof(arr)/sizeof(arr[0]);//先计算好个数
bubble_sort(arr, sz);
for(i=0; i<sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
结果:0 1 2 3 4 5 6 7 8 9