C语言支持数组数据结构,它可以存储一个固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据,但它往往被认为是一系列相同类型的变量。
数组的声明并不是一个个单独的变量,比如number0、number1、…、number99,而是声明一个数组变量,比如numbers,然后使用number[0]、numbers[1]、…、numbers[99]来代表一个个单独的变量。数组中特定元素可以通过索引访问。
所有的数组都是有连续的内存位置组成。最低的地址对应第一个元素,最高的地址对应最后一个元素。
在C中要声明一个数组,需要指定元素的类型和元素的数量,如下所示:
type arrayName[arraySize];
这叫做一位数组。arraySize必须是一个大于零的整数常量,type可以是任意有效的C数据类型。例如,要声明一个类型为double的包含10个元素的数组balance,声明语句如下:
double balance[10];
现在balance是一个可用的数组,可以容纳10个类型为double的数字。
在C中,可以逐个初始化数组,也可以使用一个初始化语句,如下所示:
double balance[5]={1000.0,2.0,3.4,7.0,50.0};
大括号 { } 之间的值得数目不能大于我们在数组声明时方括号 [ ] 中指定的元素数目。如果省略掉数组的大小,数组的大小则为初始化时元素的个数。因此,如果:
double balance[] = {1000.0, 2.0, 3.4, 7.0, 50.0};
将创建一个数组,它与前一个实例中所创建的数组是完全相同的。下面是一个为数组中某个元素赋值的实例:
balance[4] = 50.0;
上述语句把数组中第五个元素的值赋为50.0。所有的数组都是以0作为他们第一个元素的索引,也被称为基索引,数组的最后一个索引是数组的总大小减去1。以下是上面所讨论的数组的图像表示:
数组元素可以通过数组名称加索引进行访问。元素的索引是放在放括号内,跟在数组名称的后边。例如:
double salary = balance[9];
上面的语句将把数组中第10个元素的值赋给salary变量。下面的实例使用了上述的三个概念,即,声明数组、数组赋值、访问数组:
实例:
#include
int main ()
{
int n[ 10 ]; /* n 是一个包含 10 个整数的数组 */
int i,j;
/* 初始化数组元素 */
for ( i = 0; i < 10; i++ )
{
n[ i ] = i + 100; /* 设置元素 i 为 i + 100 */
}
/* 输出数组中每个元素的值 */
for (j = 0; j < 10; j++ )
{
printf("Element[%d] = %d\n", j, n[j] );
}
return 0;
}
当上面的代码被编译和执行时,会产生下列结果:
Element[0] = 100
Element[1] = 101
Element[2] = 102
Element[3] = 103
Element[4] = 104
Element[5] = 105
Element[6] = 106
Element[7] = 107
Element[8] = 108
Element[9] = 109
在C中,数组是非常重要的,需要了解更多关于数组的细节。下面列出了必须清楚的一些与数组相关的重要概念:
概念 | 描述 |
---|---|
多维数组 | C支持多维数组。多维数组最简单的形式就是二维数组 |
传递数组给函数 | 可以通过指定不带索引的数组名称来给函数传递一个指向数组的指针 |
从函数返回数组 | C允许从函数返回数组 |
指向数组的指针 | 可以通过不带索引的数组名称来生成一个指向数组中第一个元素的指针 |
C语言支持多为数组。多维数组声明的一般形式如下:
type name[size1][size2]...[sizeN];
例如,下面的声明创建了一个三维整型数组:
int threedim[5][10][4];
多维数组的最贱但形势就是二维数组。一个二维数组,在本质上,是一个一位数组的列表。声明一个x行y列的二维整型数组,形式如下:
type arrayName [ x ][ y ];
其中,type可以是任意有效的C数据类型,arrayName是一个有效的C标识符。一个二维数组可以被认为是一个带有x行和y列的表格。下面是一个二维数组,包含3行和4列:
int x[3][4];
因此,数组中的每个元素是使用形式为a[i,j]的元素名称来标识的,其中a是数组名称,i和j是唯一a中每个元素的下标。
初始化二维数组
多为数组可以通过在括号内为每行指定值来进行初始化。下面是一个带有3行4列的数组。
int a[3][4] = {
{0, 1, 2, 3} , /* 初始化索引号为 0 的行 */
{4, 5, 6, 7} , /* 初始化索引号为 1 的行 */
{8, 9, 10, 11} /* 初始化索引号为 2 的行 */
};
内部嵌套括号时可选的,下面的初始化与上面是等同的:
int a[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};
访问二维数组元素
二维数组中的元素是通过使用下标(即数组的行索引和列索引)来访问的。例如:
int val = a[2][3];
上面的语句将获取数组中第3行第4个元素。可以通过上面的示意图来进行验证,下面将使用嵌套循环来处理二维数组:
实例:
#include
int main ()
{
/* 一个带有 5 行 2 列的数组 */
int a[5][2] = { {0,0}, {1,2}, {2,4}, {3,6},{4,8}};
int i, j;
/* 输出数组中每个元素的值 */
for ( i = 0; i < 5; i++ )
{
for ( j = 0; j < 2; j++ )
{
printf("a[%d][%d] = %d\n", i,j, a[i][j] );
}
}
return 0;
}
当上面代码被编译和执行时,会产生下列结果:
a[0][0] = 0
a[0][1] = 0
a[1][0] = 1
a[1][1] = 2
a[2][0] = 2
a[2][1] = 4
a[3][0] = 3
a[3][1] = 6
a[4][0] = 4
a[4][1] = 8
如上所述,可以创建任意维度的数组,但是一般情况下,我们创建的数组是一维数组和二维数组。
二维数组存放字符串,读取时当做一维数组使用。比如:
#include
int main(){
int i;
char names[6][50]={"马超","关平","赵云","张飞","关羽","刘备"};
for(i=0;i<6;i++) {
printf("悍将名称:%s\n",names[i]);
}
return 0;
}
输出结果为:
悍将名称:马超
悍将名称:关平
悍将名称:赵云
悍将名称:张飞
悍将名称:关羽
悍将名称:刘备
二维数组在逻辑上是方针,由行和列组成。
但是二维数组在物理上是线性的,按行来一次进行存放,内存是连续的。
二维数组名的补偿是一行的长度,比如以下例子中:
age + 1 address is 00EFFC04
age + 2 address is 00EFFC14
因为每一行有4个元素,每个int类型的元素占四个字节,一行有16个字节,所以数组名age加1后地址增加了16个字节说明数组名的步长为一行的长度。
具体到每一个元素加1的时候地址增加的是一个元素所占的字节的大小,因此元素的步长即为元素本身的大小,例如:
age[2][0] + 0 address is 00EFFC14
age[2][0] + 1 address is 00EFFC18
实例:
#include
int main()
{
int age[6][4];
for (int i = 0; i < sizeof(age)/sizeof(age[0]) ; i++)
{
printf("age + %d address is %p\n",i, age + i);
}
for (int i = 0; i < sizeof(age) / sizeof(age[0]); i++)
{
for (int j = 0; j < sizeof(age[0]) / sizeof(int); j++)
{
printf("age[%d][0] + %d address is %p\n",i,j,&age[i][0]+j);
}
}
}
输出结果为:
age + 0 address is 0x7fffd98b9400
age + 1 address is 0x7fffd98b9410
age + 2 address is 0x7fffd98b9420
age + 3 address is 0x7fffd98b9430
age + 4 address is 0x7fffd98b9440
age + 5 address is 0x7fffd98b9450
age[0][0] + 0 address is 0x7fffd98b9400
age[0][0] + 1 address is 0x7fffd98b9404
age[0][0] + 2 address is 0x7fffd98b9408
age[0][0] + 3 address is 0x7fffd98b940c
age[1][0] + 0 address is 0x7fffd98b9410
age[1][0] + 1 address is 0x7fffd98b9414
age[1][0] + 2 address is 0x7fffd98b9418
age[1][0] + 3 address is 0x7fffd98b941c
age[2][0] + 0 address is 0x7fffd98b9420
age[2][0] + 1 address is 0x7fffd98b9424
age[2][0] + 2 address is 0x7fffd98b9428
age[2][0] + 3 address is 0x7fffd98b942c
age[3][0] + 0 address is 0x7fffd98b9430
age[3][0] + 1 address is 0x7fffd98b9434
age[3][0] + 2 address is 0x7fffd98b9438
age[3][0] + 3 address is 0x7fffd98b943c
age[4][0] + 0 address is 0x7fffd98b9440
age[4][0] + 1 address is 0x7fffd98b9444
age[4][0] + 2 address is 0x7fffd98b9448
age[4][0] + 3 address is 0x7fffd98b944c
age[5][0] + 0 address is 0x7fffd98b9450
age[5][0] + 1 address is 0x7fffd98b9454
age[5][0] + 2 address is 0x7fffd98b9458
age[5][0] + 3 address is 0x7fffd98b945c
可以看到每一个元素占了四个字节的大小,并且这些元素的地址是连续的。
将二维数组当做参数时,必须指明所有维数大小或者省略第一维的,但是不能省略第二维或者更高维的大小,这是由编译器原理限制的。事实上,编译器是这样处理数组的:
设有数组int a[m][n],如果要访问a[i][j]的值,编译器的寻址方式为:
&a[i][j]=&a[0][0]+i*sizeof(int)*n+j*sizeof(int); // 注意 n 为第二维的维数
因此可以省略第一维的维数,不能省略其他维的维数。
在定义二维数组的时候对其进行初始化,也可以省略第一维,编译器会根据你的初始化语句自动决定第一维度。
实例:
#include
#include
#include
int main()
{
char a[10][10];
memset(a,0,sizeof(a));
printf("%lu\n",sizeof(a));
for(int i=0;i<10;i++)
{
for(int j=0;j< 10;j++)
printf("%d ",a[i][j]);
}
//system("PAUSE");
return 0;
}
执行输出结果:
100
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
如果要在函数中传递一个一维数组作为参数,必须以下面三种方式来声明函数形式参数,这三种声明方式的结果是一样的,因为每种方式都会告诉编译器将要接收一个整型指针。同样的,也可以传递一个多维数组作为形式参数。
方式1:
形式参数是一个指针:
void myFunction(int *param)
{
.
.
.
}
方式2:
形式参数是一个已定义大小的数组:
void myFunction(int param[10])
{
.
.
.
}
方式3:
形式参数是一个未定义大小的数组:
void myFunction(int param[])
{
.
.
.
}
实例:
下面的函数,把数组作为参数,同时还传递了另一种参数,根据所传的参数,会返回数组中各元素的平均值:
double getAverage(int arr[], int size)
{
int i;
double avg;
double sum;
for (i = 0; i < size; ++i)
{
sum += arr[i];
}
avg = sum / size;
return avg;
}
现在,调用上面的函数,如下所示:
#include
/* 函数声明 */
double getAverage(int arr[], int size);
int main ()
{
/* 带有 5 个元素的整型数组 */
int balance[5] = {1000, 2, 3, 17, 50};
double avg;
/* 传递一个指向数组的指针作为参数 */
avg = getAverage( balance, 5 ) ;
/* 输出返回值 */
printf( "平均值是: %f ", avg );
return 0;
}
double getAverage(int arr[], int size)
{
int i;
double avg;
double sum=0;
for (i = 0; i < size; ++i)
{
sum += arr[i];
}
avg = sum / size;
return avg;
}
当上面的代码被编译和执行时,会产生下列结果:
平均值是: 214.400000
就函数而言,数组的长度是无关紧要的,因为C不会对形式参数执行边界检查。
数组名是一个指向数组中第一个元素的常量指针。因此,在下面的声明中:
double balance[50];
balance是一个指向&balance[0]的指针,即数组balance的第一个元素的地址。因此,下面的程序片段把p赋值为balance的第一个元素的地址:
double *p;
double balance[10];
p = balance;
使用数组名作为常量指针是合法的,反之亦然。因此, * (balance+4)是一种访问balance[4]数据的合法方式。
一旦把第一个元素的地址存储在p中,就可以使用 p、(p+1)、*(p+2) 等来访问数组元素。下面实例就演示了上面的概念:
实例:
#include
int main ()
{
/* 带有 5 个元素的整型数组 */
double balance[5] = {1000.0, 2.0, 3.4, 17.0, 50.0};
double *p;
int i;
p = balance;
/* 输出数组中每个元素的值 */
printf( "使用指针的数组值\n");
for ( i = 0; i < 5; i++ )
{
printf("*(p + %d) : %f\n", i, *(p + i) );
}
printf( "使用 balance 作为地址的数组值\n");
for ( i = 0; i < 5; i++ )
{
printf("*(balance + %d) : %f\n", i, *(balance + i) );
}
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
使用指针的数组值
*(p + 0) : 1000.000000
*(p + 1) : 2.000000
*(p + 2) : 3.400000
*(p + 3) : 17.000000
*(p + 4) : 50.000000
使用 balance 作为地址的数组值
*(balance + 0) : 1000.000000
*(balance + 1) : 2.000000
*(balance + 2) : 3.400000
*(balance + 3) : 17.000000
*(balance + 4) : 50.000000
在上面的实例中,p是一个指向double型的指针,这意味着它可以存储一个double类型的变量。一旦有了p中的地址,*p将给出存储在p中相应地址的值。
在没有明确数组的元素个数时,在程序中想知道数组单元个数可以使用sizeof(a)/sizeof(a[0]),sizeof(a) 是得到数组a的大小,sizeof(a[0]) 是得到数组a中单个元素的大小。
实例:
#include
int main(int argc,char *grgv[])
{
int a[]={1,2,3,4,5};
int b;
b=sizeof(a)/sizeof(a[0]);
printf("数组元素个数为:%d",b);
return 0;
}
实例:
给定n个学生的成绩,要求有多少学生超过了平均分,代码如下:
#include //导如输入输出头文件
int main(){
//主函数
int cj[100]={0};//定义数组
int n;
int count=0;//定义计数器,统计有多少人达到平均分
scanf("%d",&n);
int i=0;
//读入数据
for(i=0;iarg){
count++;
}
}
//输出平均分和人数
printf("平均分为:%0.2f\n超过平均分的人有:%d个\n",arg,count);
return 0;
}
实例:
#include
int main()
{
int a[2] = {1,2};
printf("a = %d\n",a[0]);
printf("*(a+0) = %d\n",*(a + 0));
printf("a[1] = %d\n",a[1]);
printf("*a = %d\n",*a);
printf("*(a+1) = %d\n",*(a + 1));
printf("\n");
printf("a 的地址:%p\n",a);
printf("(a+0)的地址:%p\n",(a + 0));
printf("(a+1)的地址:%p\n",(a + 1));
// %p 读入一个指针
printf("\n");
return 0;
}
输出结果:
a = 1
*(a+0) = 1
a[1] = 2
*a = 1
*(a+1) = 2
a 的地址:0x7ffe9e227634
(a+0)的地址:0x7ffe9e227634
(a+1)的地址:0x7ffe9e227638
事实上 a[0] 、a[1]…a[i] 代表的都是值,a、(a+0)、(a+1)、(a+i) 代表的是地址;另外这里的 a 代表整个数组的首地址,相当于 a[0] 的地址,而这里 (a+1) 就代表的是 a[0+1] 的地址。
正如文章中提到的:所有的数组都是由连续的内存位置组成。最低的地址对应第一个元素,最高的地址对应最后一个元素,即是说 (a+i) 就代表的是 a[0+i] 的地址。
指针与数组名的区别
指针:也是一个变量,存储的数据是地址。
数组名:代表的是该数组最开始的一个元素的地址。
int a[10];
int *p;
p = &a[0] // 可以写成 p = a;