我们在使用数组编程时, 往往会对数组的访问范围格外留心. 一般地, 我们会认为只要数组越界访问, 程序就会报错——其实不然, 有时越界访问也不会造成程序的崩溃. 值得强调的是, 我并不是在鼓励朋友们越界访问数组, 而是想让大家正视这一问题, 并在模拟越界访问现场时理解数组访问操作的实质. 事实上, 越界访问数组绝不是一种好的编程习惯, 甚至可以说"糟糕", 因为合法范围外的存储单元中的数据是未知的, 这些存储单元的作用也是未知的, 如果我们随意修改这些数据, 其造成的后果是不可估量的.
下面这个程序给出了越界访问操作的实例: 从程序的运行结果来看, 编译器为变量i、a、c和数组b分配了连续的存储空间, 根据程序运行结果中的内存地址可画出下面的内存地址对应图. 程序在运行期间, 从0060FEE0存储单元开始, 顺序修改各内存单元存储的数值(int型变量占4个字节, 而内存按字节编址, 所以每个int型变量占4个存储单元). 在程序结束前, 从0060FEE0到0060FF00的9个存储块中的值都被修改过. 碰巧的是, 程序运行时对变量i的修改未影响循环的正确结束. 这里多想一步, 这次只是碰巧, 在一般情况下, 这种性质的操作极有可能给程序的运行带来负面的影响.
#include<stdio.h>
/* 此程序无任何实际意义 */
int main()
{
int i, a = 1, c = 2, b[5] = {0};
printf("%p %p %p %p\n", b, &c, &a, &i);
i = 0;
while(i <= 8)
{
b[i] = i;
printf("%d ", b[i]);
i ++;
}
printf("\n");
printf("c = %d, a = %d, i = %d\n", c, a, i);
/* */
return 0;
}
由程序的运行结果, 我们可以画出变量在内存中的分布情况.
内存地址 | 程序开始时 | 程序运行结束前 |
0060FEE0 (b[0]) | 0 | 0 |
0060FEE4 (b[1]) | 0 | 1 |
0060FEE8 (b[2]) | 0 | 2 |
0060FEEC (b[3]) | 0 | 3 |
0060FEF0 (b[4]) | 0 | 4 |
0060FEF4 (c) | 2 | 5 |
0060FEF8 (a) | 1 | 6 |
0060FEFC (i) | 0 | 7(最后变为9) |
0060FF00 (b[8]) | 未知 | 8 |
我想大多数朋友对二维数组的理解仅限于将其想象为一个存储矩阵, 其实这只是二维数组的逻辑存储结构, 其在内存中的物理存储结构并不真的是一个矩阵, 而是和一维数组一样的一维连续存储空间. C语言编译器将二维数组在内存中按行存储, 所以实际上二维数组和一维数组一样, 都是连续的存储在内存中.
我们一起来设计一个程序, 如果一个小组有4名同学, 每名同学都参加了期末考试, 而期末考试共考察了5门课的学习情况(微积分、C语言、电路分析基础、电子技术、信号与系统), 使用二维数组编程并求这4名同学总成绩的平均值和电子技术课程的平均分.
#include<stdio.h>
#define N 4
float Average1(unsigned short s[][5]);
float Average2(unsigned short s[][5]);
int main()
{
/* 共有N名同学, 5门课程: 微积分、C语言、电路分析基础、电子技术、信号与系统 */
unsigned short students[N][5] = {0};
short temp;
unsigned short i;
unsigned short j;
i = 0;
while(i < N)
{
j = 0;
printf("第%hu位同学的成绩 ", i + 1);
while(j < 5)
{
switch(j)
{
case 0 : printf("微积分: "); break;
case 1 : printf("C语言: "); break;
case 2 : printf("电路分析基础: "); break;
case 3 : printf("电子技术: "); break;
case 4 : printf("信号与系统: "); break;
}
/* 增加输入容错机制 */
while(scanf("%hd", &temp) != 1 || temp < 0 || temp > 100)
{
while(getchar() != '\n') ;
printf("请输入合法数据.\n");
}
students[i][j] = temp;
j ++;
}
i ++;
}
/* */
printf("%d名学生的总成绩平均值为%.4f.\n", N, Average1(students));
/* */
printf("%d名学生电子技术课程的平均分为%.4f.\n", N, Average2(students));
/* */
return 0;
}
/* 求N名同学的总成绩平均值 */
float Average1(unsigned short s[][5])
{
unsigned short i = 0;
unsigned short j;
unsigned long Sum = 0;
while(i < N)
{
j = 0;
while(j < 5)
{
Sum += s[i][j];
j ++;
}
i ++;
}
return (float)Sum / N;
}
/* 求N名学生电子技术课程的平均成绩 */
float Average2(unsigned short s[][5])
{
unsigned short i = 0;
unsigned int Sum = 0;
while(i < N)
{
Sum += s[i][3];
i ++;
}
return (float)Sum / N;
}
上面的程序在求解电子技术课程的平均值时, Average2函数的参数为一二维数组: 我们大胆地猜想一下, 可以用一个一维数组解决这个问题吗? 可以这样考虑, 将二维数组看作一个一维数组, 访问第一名同学的电子技术成绩存储单元后, 往后数5个存储单元就是第二名同学的电子技术成绩存储单元, 以此类推可访问所有同学的电子技术成绩存储单元.
/* 求N名学生电子技术课程的平均成绩 */
float Average3(unsigned short s[])
{
unsigned short i = 0;
unsigned int Sum = 0;
while(i < 5 * N)
{
Sum += s[i];
i += 5;
}
return (float)Sum / N;
}
下面给出完整的程序代码.
#include<stdio.h>
#define N 4
float Average1(unsigned short s[][5]);
float Average2(unsigned short s[][5]);
float Average3(unsigned short s[]);
int main()
{
/* 共有N名同学, 5门课程: 微积分、C语言、电路分析基础、电子技术、信号与系统 */
unsigned short students[N][5] = {0};
short temp;
unsigned short i;
unsigned short j;
i = 0;
while(i < N)
{
j = 0;
printf("第%hu位同学的成绩 ", i + 1);
while(j < 5)
{
switch(j)
{
case 0 : printf("微积分: "); break;
case 1 : printf("C语言: "); break;
case 2 : printf("电路分析基础: "); break;
case 3 : printf("电子技术: "); break;
case 4 : printf("信号与系统: "); break;
}
/* 增加输入容错机制 */
while(scanf("%hd", &temp) != 1 || temp < 0 || temp > 100)
{
while(getchar() != '\n') ;
printf("请输入合法数据.\n");
}
students[i][j] = temp;
j ++;
}
i ++;
}
/* */
printf("%d名学生的总成绩平均值为%.4f.\n", N, Average1(students));
/* */
printf("%d名学生电子技术课程的平均分为%.4f.\n", N, Average2(students));
/* */
printf("%d名学生电子技术课程的平均分为%.4f.\n", N, Average3(students[0] + 3));
/* */
return 0;
}
/* 求N名同学的总成绩平均值 */
float Average1(unsigned short s[][5])
{
unsigned short i = 0;
unsigned short j;
unsigned long Sum = 0;
while(i < N)
{
j = 0;
while(j < 5)
{
Sum += s[i][j];
j ++;
}
i ++;
}
return (float)Sum / N;
}
/* 求N名学生电子技术课程的平均成绩 */
float Average2(unsigned short s[][5])
{
unsigned short i = 0;
unsigned int Sum = 0;
while(i < N)
{
Sum += s[i][3];
i ++;
}
return (float)Sum / N;
}
/* 求N名学生电子技术课程的平均成绩 */
float Average3(unsigned short s[])
{
unsigned short i = 0;
unsigned int Sum = 0;
while(i < 5 * N)
{
Sum += s[i];
i += 5;
}
return (float)Sum / N;
}
虽然有时的确可以用一维数组代替二维数组解决问题, 但从编程的便利性来看, 使用二维数组的操作思想比使用一维数组的操作思想更易编写程序.