数组简介
我们经常需要在程序中存储某种类型的大量数据值。例如编写一个程序,追踪一只篮球队的成绩,就要存储一个赛季各场分数和各个球员得分。我们可以使用前面学过的知识为每个分数使用不同的变量。然而,如果一个赛季有非常多的赛事,这会非常频繁,因为有球赛的每个球员都需要许多变量。这显然不理想。
理想情况应该把这些变量集中在一个名称下面,这样就不需要定义多个变量了。
计算平均分的问题
我们通过一个例子来了解数组。这个例子是计算某班学生的平均分数。假设该班有十个学生。计算一组数字的平均值,要将它们全加起来,再除以数字的个数。
#include <stdio.h>
int main(void)
{
int number = 0; /* 存储输入的分数*/
int count = 10; /* 学生的个数 */
long sum = 0L; /* 总分 */
float average = 0.0f; /* 平均分*/
/* 读十个数的平均数 */
for(int i = 0; i < count; i ++)
{
printf("输入一个学生的成绩: ");
scanf("%d", &number); /* 读取一个数 */
sum += number; /* 累加到sum*/
}
average = (float)sum / count; /* 计算平均数 */
printf("\n输入的十个数字的平均值是: %f\n", average);
return 0;
}Code language: PHP (php)
这个程序只对平均值感兴趣,所以累加输入的数然后输出平均值。如果要将其开发为更复杂的程序,需要输入一些数值,再输出每个人的分数,最后输出平均分,上面的代码就无法实现了,因为 number 每次都被覆盖了,不能再次使用。
如何存储所有的分数,可以声明十个整数,但是这样不能用 for 循环输入,而且必须添加代码逐个读入,太麻烦了。
#include <stdio.h>
int main(void)
{
int number0 = 0, number1 = 0, number2 = 0, number3 = 0, number4 = 0;
int number5 = 0, number6 = 0, number7 = 0, number8 = 0, number9 = 0;
long sum = 0L; /* Sum of the numbers */
float average = 0.0f; /* Average of the numbers */
/* Read the ten numbers to be averaged */
printf("输入一到五个数字,\n");
printf("每个数字用空格隔开,输入完成按回车键.\n");
scanf("%d%d%d%d%d", &number0, &number1, &number2, &number3, &number4);
printf("输入后面五个数字,\n");
printf("每个数字用空格隔开,输入完成按回车键.\n");
scanf("%d%d%d%d%d", &number5, &number6, &number7, &number8, &number9);
/* Now we have the ten numbers, we can calculate the average */
sum = number0 + number1 + number2 + number3 + number4 +
number5 + number6 + number7 + number8 + number9;
average = (float)sum / 10.0f;
printf("\n平均数是: %f\n", average);
return 0;
}Code language: PHP (php)
数组的概念
数组是一组数目固定、类型相同的数据项,数组中的数据项称为元素。数组中的元素都是 int、long 或者其他类型。
下面是数组声明,非常类似于声明一个含有单一数值的正常变量,但要在名称后面的方括号中放置一个数:
long numbers[10];Code language: CSS (css)
方括号中的数字定义了要存放在数组中的元素个数,称为数组维。存储在数组中的每个数据项都用相同的名称访问,在这个例子中,该名称就是 numbers。要选择某个元素,可以在数组名称后的方括号内使用索引值。索引值从 0 开始,最后一个是 9。所以第一个元素就是 numbers[0],第二个是 numbers[1],最后一个是 numbers[9]。
索引值从 0 开始,不是 1。这一点大家一定要牢记。很多初学者在这个地方犯错。
如果访问的时候索引超过这个数组的合法范围,比如上面如果访问 numbers[10],这个时候就可能程序不能正常运行。编译器检查不出这种错误,所以程序仍可以编译,但是执行有问题。有些时候甚至会覆盖重要信息,导致电脑锁死,需要重启计算机。所以大家要注意避免这个问题。
此外,定义的时候 long numbers[10] 这个地方的 10 并不是索引的意思,而是表示这个数组有十个元素。这点与直接访问比如 numbers[10] 的 10 是不同的概念。long numbers[10] 是声明,numbers[10] 是操作某个元素,当然在这里会超出了范围。
#include <stdio.h>
int main(void)
{
long numbers[10];
printf("%ld", numbers[10]); //访问了第十一个元素 可能会访问到一些垃圾数据
return 0;
}Code language: PHP (php)
下面使用数组知识来解决平均分的问题:
#include <stdio.h>
int main(void)
{
int numbers[10]; /* 声明有十个元素的数组*/
int count = 10; /* 读取数据数量 */
long sum = 0L; /* 存储总数 */
float average = 0.0f; /* 平均数*/
printf("\n输入十个数:\n"); /* 提示输入 */
/* 读取十个数 */
for(int i = 0; i < count; i ++)
{
printf("%2d> ", i + 1);
scanf("%d", &numbers[i]); /* 读取十个数到数组里的十个元素 */
sum += numbers[i]; /* 累加 */
}
for(int i = 0; i < count; i++)
{
printf("\n序号:%d 分数:%d", i + 1, numbers[i]);
}
average = (float)sum / count; /* 计算平均数*/
printf("\n十个数的平均数是: %f\n", average);
return 0;
}Code language: PHP (php)
内存地址与寻址运算符
寻址运算符 & 输出其操作数的内存地址。前面使用了寻址运算符 &,它广泛用在 scanf() 函数。& 放在变量前面,scanf 需要用到这个变量的地址,scanf 内部可以利用这个地址去修改对应的变量的值。
#include <stdio.h>
int main(void)
{
/* 定义一些整数变量 */
long a = 1L;
long b = 2L;
long c = 3L;
/* 声明一些浮点变量 */
double d = 4.0;
double e = 5.0;
double f = 6.0;
printf("long类型的变量占用%u个字节。", sizeof(long));
printf("\n这是一些long类型变量的地址:");
printf("\na的地址是:%p,\nb的地址是:%p", &a, &b);
printf("\nc的地址是:%p", &c);
printf("\n\ndouble类型的变量占用%d字节。", sizeof(double));
printf("\n这是一些double类型变量的地址:");
printf("\nd的地址是:%p,\ne的地址是:%p", &d, &e);
printf("\nf的地址是:%p\n", &f);
return 0;
}Code language: PHP (php)
%u 是显示 sizeof 生成的值,它是无符号的整数。%p 是一个新的格式说明符,用来输出一个内存地址,其值为十六进制。内存地址一般是 32 位或者 64 位,地址的大小取决于引用的最大内存量。
声明一个包含 4 个元素的数组:
long number[4];Code language: CSS (css)
获取数组元素地址的方式类似普通变量:
#include <stdio.h>
int main(void)
{
int value = 0;
printf("\n%p", &value);
long number[4];
printf("\n%p\n", &number[2]);
int data[5];
for(int i = 0; i < 5; i++)
{
data[i] = 12 * (i + 1);
printf("data[%d] 地址是:%p 值是:%d\n", i, &data[i], data[i]);
}
return 0;
}Code language: PHP (php)
数组初始化
为了安全起见,预先确定元素的初始值。可以使用大括号并且用逗号隔开,来给数组的元素初始化:
double values[5] = {1.5, 2.5, 3.5, 4.5, 5.5};
前三个元素的值就是 1.5、2.5、3.5,后面两个元素初始化值为 0。
如果没有给元素提供初值,编译器会给它们提供初值 0,所以初值提供了一种把整个数组初始化为 0 的简单方式,只需要给一个元素提供 0:
double values[5] = {0.0};
这样整个数组都初始化为 0.0。
如果初始值超过元素个数,有些编译器就会报错。
可以不指定数组的大小,让编译器从该列值中推断出元素的个数:
int primes[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
以上数组的长度就是 10。
#include <stdio.h>
int main(void)
{
double numbers[] = {0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5};
printf("%.2lf\n", numbers[3]);
return 0;
}Code language: PHP (php)
sizeof 运算符
sizeof 可以计算指定类型的变量所占的字节数:
printf("%zu", sizeof(long));Code language: JavaScript (javascript)
sizeof 也可以用于数组:
double values[5] = {1.5, 2.5, 3.5, 4.5, 5.5};
printf("%zu", sizeof values);Code language: JavaScript (javascript)
也可以使用表达式 sizeof values[0] 来计算数组中一个元素所占的字节数。可以通过如下运算来求出数组中的元素的数目:
size_t elementCount = sizeof values / sizeof values[0];
在使用循环处理中的所有元素时,可以使用 sizeof:
#include <stdio.h>
int main(void)
{
printf("%zu\n", sizeof(double));
double numbers[] = {0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5};
printf("%zu\n", sizeof numbers);
printf("数组的元素个数:%u", sizeof numbers / sizeof(double));
for(int i = 0; i < sizeof numbers / sizeof(double); i++)
{
printf("\n%.1lf", numbers[i]);
}
return 0;
}Code language: PHP (php)
多维数组
一个数组里面可以存储多个相同类型的数据,这个我们叫做一维数组。比如 float arr[5]。下面介绍二维数组:
float carrots[25][50];
float numbers[3][5];Code language: CSS (css)
虽然在内存中存储实际上还是顺序存储在内存中,但是我们可以把它想象成三行五列的表格。
二维数组的初始化类似于一维数组,区别是把每一行的初始值放在大括号中 {},再把所有行放在一堆大括号中。如果指定的初始值少于一行的元素,这些值会从每行的第一个元素开始,依序赋予各个元素,剩下未指定初值的元素则初始化为 0。
int numbers[3][4] = {0};
三维数组可以理解为我们有四块地,每块地 10 行 20 列:
double beans[4][10][20];Code language: CSS (css)
要处理多维数组中的所有元素,需要一个嵌套循环,嵌套的层数就是数组的维数:
#include <stdio.h>
int main(void)
{
int numbers3[3][3][5] =
{
{
{1, 2, 3, 4, 5},
{1, 2, 3, 4, 5},
{1, 2, 3, 4, 5}
},
{
{1, 2, 3, 4, 5},
{1, 2, 3, 4, 5},
{1, 2, 3, 4, 5}
},
{
{1, 2, 3, 4, 5},
{1, 2, 3, 4, 5},
{1, 2, 3, 4, 5}
}
};
int sum = 0;
for(int i = 0; i < 3; i++)
{
for(int j = 0; j < 3; j++)
{
for(int k = 0; k < 5; k++)
{
sum += numbers3[i][j][k];
}
}
}
printf("%d", sum);
return 0;
}Code language: PHP (php)
使用 sizeof 可以确定多维数组中每一维的元素个数:
#include <stdio.h>
int main(void)
{
int numbers3[3][3][5] =
{
{
{1, 2, 3, 4, 5},
{1, 2, 3, 4, 5},
{1, 2, 3, 4, 5}
},
{
{1, 2, 3, 4, 5},
{1, 2, 3, 4, 5},
{1, 2, 3, 4, 5}
},
{
{1, 2, 3, 4, 5},
{1, 2, 3, 4, 5},
{1, 2, 3, 4, 5}
}
};
int sum = 0;
for(int i = 0; i < sizeof(numbers3) / sizeof(numbers3[0]); i++)
{
for(int j = 0; j < sizeof(numbers3[0]) / sizeof(numbers3[0][0]); j++)
{
for(int k = 0; k < sizeof(numbers3[0][0]) / sizeof(numbers3[0][0][0]); k++)
{
sum += numbers3[i][j][k];
}
}
}
printf("%d", sum);
return 0;
}Code language: PHP (php)
变长数组
前面的所有数组都在代码中指定了固定的长度,也可以定义其长度在程序运行期间确定。
#include <stdio.h>
int main(void)
{
size_t len = 0;
printf("请输入数组长度:");
scanf("%zd", &len);
float numbers[len];
numbers[3] = 1.2f;
printf("%f", numbers[3]);
return 0;
}Code language: PHP (php)
也可以在执行期间确定二维或多维数组:
#include <stdio.h>
int main(void)
{
size_t len = 0;
printf("请输入数组长度:");
scanf("%zd", &len);
size_t len2 = 0;
printf("请输入数组长度:");
scanf("%zd", &len2);
float numbers[len][len2];
numbers[3][2] = 1.2f;
printf("%f", numbers[3][2]);
return 0;
}Code language: PHP (php)
下面的代码可以检查编译器是否支持变长数组:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
#ifdef __STDC_NO_VLA__
printf("不支持");
exit(1);
#endif
return 0;
}Code language: PHP (php)
使用变长数组计算平均分:
#include <stdio.h>
int main(void)
{
size_t len = 0;
printf("请输入学生个数:");
scanf("%zd", &len);
int grades[len];
long sum = 0L;
float average = 0.0f;
printf("输入学生分数\n");
for(size_t i = 0; i < len; ++i)
{
printf("%2zd>", i + 1);
scanf("%d", &grades[i]);
sum += grades[i];
}
printf("分数\n");
for(size_t i = 0; i < len; ++i)
{
printf("grades[%2zd]=%3d", i, grades[i]);
if((i + 1) % 5 == 0)
printf("\n");
}
average = (float)sum / len;
printf("\n %zd 个学生的平均分是 : %.2f\n", len, average);
return 0;
}Code language: PHP (php)