二维数组,C语言二维数组完全攻略
数学中的行列矩阵,通常使用二维数组来描述,即用二维数组的第一维表示行,第二维表示列;生活中凡是能抽象为对象及对象的若干同类型属性的问题,一般用二维数组来描述。
例如,若表示一个班级学生的语文、数学、外语、C 语言等 4 门课的成绩数据。该问题可把每个学生看成一个对象,用二维数组的第一维来表示,如果有 50 个学生,则可设定二维数组第一维的大小为 50;成绩可看成每个对象的属性,且均可使用整型表示,可用二维数组的第二维来表示,每个对象(学生)含 4 个属性(4 门课程),故第二维大小可设为 4。
再比如,某公司若统计某产品的某个月份的销量数据,该问题可以把一周当成一个对象,一个月含 4 周,故 4 个对象,二维数组第一维可设为 4;日销售量可看成每个对象的属性,可用二维数组的第二维表示,对象(每周)含有 7 个属性(7 天的日销售量),故二维数组的第二维可设为 7。
静态二维数组定义的一般格式为:
例如:
如下二维数组的定义形式均是错误的。
每个对象(元素)sc[i] 又是一个包含 4 个属性(4 门成绩)的一维数组,4 个属性分别 为:sc[i][0](语文)、sc[i][1](数学)、sc[i][2](外语)、sc[i][3](C 语言)。每一行表示一个学生,每一列表示一门课程,形成如下所示的行列矩阵形式。
二维数组名 sc 是首对象(第一个学生)sc[0] 的地址,即 sc=&sc[0]。二维数组名加 1 表示跳过一个对象,即 sc+1 为第二个对象(学生)sc[1] 的地址,sc+2 为第三个对象(学生)sc[2] 的地址。
从行列式角度分析,二维数组名即首行的地址,C 语言中的地址一般均是空间首地址。故二维数组名是首行首地址,该数组名加 1 表示跳过一整行,到达第二行的首地址,以此类推。
【例 1】以下二维数组 sc 用于保存 3 个学生的 4 门课程(语数外及 C 语言)成绩。根据程序的运行结果,分析二维数组名的含义。
sc=0060FEE0
&sc[0]=0060FEE0
sc+1=0060FEF0
sc+2=0060FF00
程序分析:
1) 本例中定义的二维数组为 3 行 4 列,含 3 个学生对象,对应 3 行,每个学生对象包括 4 个属性,对应 4 列。
2) 二维数组名 sc 相当于首对象即第一个学生 sc[0] 的地址,即 sc==&sc[0]。输出地址一般使用格式控制符 %p 或 %x 或 %08x。由某次运行结果可知,sc 和 &sc[0] 的值均为十六进制数 0060FEE0。
由此可得:二维数组名为首对象或首行的地址。
3) 二维数组名加 1 表示跳过一个对象(一行)的空间,为下一个对象(下一行)的地址。即跳过一个对象所有属性(一行中所有列元素)对应的空间,到达下一个对象(下一行)的起始位置。
而本例中一个对象有 4 个属性,每个属性均为整型变量,在 VC++6.0 中占 4 字节,故一个对象(一行)共占 4X4=16 个字节。故 sc、sc+1、sc+2 均相差 16 个字节,转换为十六进制为 0x10。如运行结果所示。
4) 程序每次运行为某变量空间分配的起始地址可能有差别。
若 M 和 N 均为已定义的正整数:
列下标j的范围为 0〜N-1。
称第几个时,习惯上是从第 1 个开始,第 2 个,第 3 个,…,而不从第 0 个开始。但是二维数组的行下标及列下标均是从 0 开始的,为统一起见,本书中对数组元素的引用,采用行列序号来描述,如 a[i][j] 为 i 行 j 列元素,而不说成第 i 行第 j 列元素。例如:
程序分析:
例题涉及对二维数组的赋值,一般使用双重循环,外层循环控制行下标,内层循环控制列下标。二维数组的每个元素 a[i][j] 像普通变量一样使用,使用 scanf 函数输入时,一定要加 &。输入时,可以输入完 6 个数据后按一次回车键。
本题要求每输出一行后换行,即输出完一行中的所有列数据(内层循环结束)后换行。
实现代码:
Input 6 integers:1 2 3 4 5 6
1 2 3
4 5 6
s=21
二维数组的初始化方式通常有以下几种。
1) 分行给出初始化数据,且每行的初始化数据个数等于列数。例如:
该初始化语句相当于如下 6 条赋值语句。
如果初始化数据的个数是列数的整数倍,即:
如 int a[3][4]; 其逻辑结构是 3 行 4 列的矩阵形式,如图 1) 所示,而其存储结构是连续的(线性的),如图 2) 所示。
问题分析:该问题可把每个学生当成一个对象,而每个对象(学生)有 5 个属性:4 门课程成绩及平均分。故该问题可使用二维数组来处理数据,该二维数组含有 N 个对象,故第一维的大小为 N,每个对象有 5 个属性,故第二维的大小为 5。为便于验证,本例中学生个数 N 设为 3 个。
实现代码:
输入3个学生信息(语、数、外、C语言成绩):
NO1:82 91 88 93
NO2:83 84 80 91
NO3:73 79 86 81
学号 语文 数学 外语 C语言 平均成绩
NO1: 82.0 91.0 88.0 93.0 88.5
NO2: 83.0 84.0 80.0 91.0 84.5
NO3: 73.0 79.0 86.0 81.0 79.8
例如,若表示一个班级学生的语文、数学、外语、C 语言等 4 门课的成绩数据。该问题可把每个学生看成一个对象,用二维数组的第一维来表示,如果有 50 个学生,则可设定二维数组第一维的大小为 50;成绩可看成每个对象的属性,且均可使用整型表示,可用二维数组的第二维来表示,每个对象(学生)含 4 个属性(4 门课程),故第二维大小可设为 4。
再比如,某公司若统计某产品的某个月份的销量数据,该问题可以把一周当成一个对象,一个月含 4 周,故 4 个对象,二维数组第一维可设为 4;日销售量可看成每个对象的属性,可用二维数组的第二维表示,对象(每周)含有 7 个属性(7 天的日销售量),故二维数组的第二维可设为 7。
二维数组的定义
同一维数组一样,既支持 C89 标准的二维静态数组,又支持 C99 标准的二维动态数组或变长数组。某些 C 编译器还没更新到支持 C99 标准的语法,故可能在一些编译器中变长数组会报错。如无特殊说明,教程中所指二维数组,均默认为静态数组。静态二维数组定义的一般格式为:
类型 数组名[第一维大小][第二维大小];
其中,第一、二维的大小一般均为常量表达式。例如:
int a[4][5];定义了一个 4 行 5 列的 int 型二维数组 a。
float sc[3][4];定义了一个 3 行 4 列的 float 型二维数组 sc。
如下二维数组的定义形式均是错误的。
int a[][3];//错误。编译器无法确定所需空间 int a[2][];//错误。缺少列下标,编译器无法确定所需空间动态数组例子如下(仅做了解)。
int n=2; int a[n][3];//动态数组,正确的C99语法。但在某些编译器中可能报错 int a[2][n];//动态数组,正确的C99语法定义时未初始化的数组,其数据元素的值一般为无意义的随机值,如:
int a[2][3];//该数组的6个元素均为随机值可以把二维数组看成一个特殊的一维数组,它的每个元素又是一个一维数组。例如,定义一个表示 3 个学生 4 门课程成绩的二维数组:
int sc[3][4];定义了一个 3 行 4 列的二维数组 sc,该二维数组可表示 3 个对象(学生),从这个角度看,该二维数组可以看成含 3 个对象(学生)的一维数组,3 个对象(元素)分别为:sc[0]、sc[1]、sc[2],其中 sc 为该一维数组名。
每个对象(元素)sc[i] 又是一个包含 4 个属性(4 门成绩)的一维数组,4 个属性分别 为:sc[i][0](语文)、sc[i][1](数学)、sc[i][2](外语)、sc[i][3](C 语言)。每一行表示一个学生,每一列表示一门课程,形成如下所示的行列矩阵形式。
语文 数学 外语 c语言 sc[0](第一个学生)-----sc[0][0] sc[0][1] sc[0][2] sc[0][3] sc[1](第二个学生)-----sc[1][0] sc[1][l] sc[1][2] sc[1][3] sc[2](第三个学生)-----sc[2][0] sc[2][1] sc[2][2] sc[2][3]
二维数组名 sc 是首对象(第一个学生)sc[0] 的地址,即 sc=&sc[0]。二维数组名加 1 表示跳过一个对象,即 sc+1 为第二个对象(学生)sc[1] 的地址,sc+2 为第三个对象(学生)sc[2] 的地址。
从行列式角度分析,二维数组名即首行的地址,C 语言中的地址一般均是空间首地址。故二维数组名是首行首地址,该数组名加 1 表示跳过一整行,到达第二行的首地址,以此类推。
【例 1】以下二维数组 sc 用于保存 3 个学生的 4 门课程(语数外及 C 语言)成绩。根据程序的运行结果,分析二维数组名的含义。
#include<stdio.h> int main (void) { int sc[3][4]; printf("sc=%p\n",sc); printf ("&sc[0]=%p\n",&sc[0]); printf("sc+1=%p\n",sc+1); printf("sc+2=%p\n",sc+2); return 0; }程序某次运行的结果:
sc=0060FEE0
&sc[0]=0060FEE0
sc+1=0060FEF0
sc+2=0060FF00
程序分析:
1) 本例中定义的二维数组为 3 行 4 列,含 3 个学生对象,对应 3 行,每个学生对象包括 4 个属性,对应 4 列。
2) 二维数组名 sc 相当于首对象即第一个学生 sc[0] 的地址,即 sc==&sc[0]。输出地址一般使用格式控制符 %p 或 %x 或 %08x。由某次运行结果可知,sc 和 &sc[0] 的值均为十六进制数 0060FEE0。
由此可得:二维数组名为首对象或首行的地址。
3) 二维数组名加 1 表示跳过一个对象(一行)的空间,为下一个对象(下一行)的地址。即跳过一个对象所有属性(一行中所有列元素)对应的空间,到达下一个对象(下一行)的起始位置。
而本例中一个对象有 4 个属性,每个属性均为整型变量,在 VC++6.0 中占 4 字节,故一个对象(一行)共占 4X4=16 个字节。故 sc、sc+1、sc+2 均相差 16 个字节,转换为十六进制为 0x10。如运行结果所示。
4) 程序每次运行为某变量空间分配的起始地址可能有差别。
二维数组的引用
二维数组的引用格式为:数组名[行下标][列下标];
注意引用数组元素时不能加类型,行下标、列下标均从 0 开始。且行下标和列下标的形式可以为常量、变量或表达式。若 M 和 N 均为已定义的正整数:
int i,j; //i和j分别表示行下标和列下标. int a[M][N]; //定义了一个M行N列的二维整型数组a行下标i的范围为 0〜M-1。
列下标j的范围为 0〜N-1。
称第几个时,习惯上是从第 1 个开始,第 2 个,第 3 个,…,而不从第 0 个开始。但是二维数组的行下标及列下标均是从 0 开始的,为统一起见,本书中对数组元素的引用,采用行列序号来描述,如 a[i][j] 为 i 行 j 列元素,而不说成第 i 行第 j 列元素。例如:
int a[3][4]; a[0][0]; //为0行0列元素 a[2][1]; //为2行1列元素 a[1][1+2]; //为1行3列元素例如:
int n=4, i, j ; int a[3][4]; //定义了一个3行4列的二维数组a对该数组的引用形式为 a[i][j],其中,行下标 i 的范围为 0〜2,列下标 j 的范围为 0〜3。以下对该二维数组的引用均是正确的。
a[2][n-1]=a[0][1+1] +a[2][2-1]; //a[0][2]、a[2][1]相加后赋给a[2][3] a[3-1][n-1];//引用2行3列元素以下对该二维数组的引用形式是错误的。
a[-2][1]; //行下标-2越界,应为0〜2 a[3][2]; //行下标3越界,应为0〜2 a[2][n]; //列下标n=4越界,应为0〜3 a[2,3]; //引用形式错误,应为a[2][3] a[2][]; //缺少列下标 int a[1][n-2]; //引用不能加类型,若为定义,第二维含有变量n,错误【例 2】定义一个 2 行 3 列的二维数组,从键盘上输入 6 个数值,依次给该二维数组的每个元素赋值,按每行三个元素输出该二维数组及所有元素的和。
程序分析:
例题涉及对二维数组的赋值,一般使用双重循环,外层循环控制行下标,内层循环控制列下标。二维数组的每个元素 a[i][j] 像普通变量一样使用,使用 scanf 函数输入时,一定要加 &。输入时,可以输入完 6 个数据后按一次回车键。
本题要求每输出一行后换行,即输出完一行中的所有列数据(内层循环结束)后换行。
实现代码:
#include<stdio.h> int main (void) { int a[2][3]; //先定义,后赋值 int i,j,s=0; printf("Input 6 integers:"); for(i=0;i<2;i++) { for (j=0; j<3; j++) { scanf ("%d",&a[i][j]); //勿忘 & s+=a[i][j] ; //与上一条语句不能颠倒 } } for(i=0;i<2;i++) { for (j=0; j<3; j++) printf("%d\t",a[i][j]); //使用\t 的作用 printf ("\n"); //注意该输出换行符的位置 } printf("s=%d\n", s); return 0; }运行结果为:
Input 6 integers:1 2 3 4 5 6
1 2 3
4 5 6
s=21
二维数组的初始化
二维数组可以先定义,后赋值,在显式赋值之前,二维数组的各数据元素是随机值。也可以在定义二维数组的同时,采用初始化列表的形式对其元素赋初值,称为二维数组的初始化。二维数组的初始化方式通常有以下几种。
1) 分行给出初始化数据,且每行的初始化数据个数等于列数。例如:
int a[2][3]={{1,2,3},{4,5,6}};该初始化列表给出了两行数据,每一行数据用一对大括号
{}
括起来,一行中的数据及行与行之间均用逗号隔开。这是一种较常用的二维数组的初始化方式。该初始化语句相当于如下 6 条赋值语句。
a[0][0]=1; a[0][1]=2; a[0][2]=3; a[1][0]=4; a[1][1]=5; a[1][2]=6;由于初始化列表中明确给出了两行数据,故定义该数组时,其第一维的大小可省略,编译器能间接算出该数组的行数为 2,故依然可以确定其空间大小,因此,在对二维数组进行初始化时,其第一维的大小可以省略,即写成如下形式:
int a[][3]={{l,2,3},{4,5,6}};注意:第二维的大小一定不能省略!如下初始化均是错误的。
int a[2][] = {{l,2,3},{4,5,6}}; //错误。不能省略第二维大小 int a[][] = {{l,2,3}, {4,5,6}}; //错误。不能省略第二维大小 int a[][3]; //错误。没有提供初始化列表时,两维的大小都必须显式给出 int a[2][3] = {{l,2,3},{4,5,6},{7,8,9}}; //错误。初始行数多于数组行数如果把上面一条初始化语句改为省略第一维的大小,便是正确的,即:
int a[][3] = {{1,2,3}, {4,5,6}, {7,8,9}}; //正确。间接得知该数组为三行2) 分行给出初始化数据,但每行的初始化数据个数少于列数。例如:
int a[2][3]={{l,2},{4}};该初始化列表给出了两行数据,第一行给出两个数据,少一个,对 int 型默认为补 0;第二行仅给出一个数据,少两个,补两个 0。所以上述初始化语句相当于:
int a[2][3]={{1,2,0},{4,0,0}};同理:
int a[][3]={{0},{0}};相当于:
int a[][3] = {{0,0,0},{0,0,0}}; //2行3列3) 初始化数据没有分行,容易产生混乱,不推荐这种方式。
如果初始化数据的个数是列数的整数倍,即:
int a[2][3]={l,2,3,4,5,6};初始化数据以列数三个为一组,共分为两组,且每组数据个数恰好等于列数 3,故第一组赋值给第 1 行,第二组赋值给第 2 行。初始化后,数组中各元素为:
1 2 3
4 5 6
int a[2][3]={1,2,3,4};由于该二维数组列数为 3,初始化数据以三个为一组,共分为两组,但第二组仅一个数据,少于列数 3,对 int 型数组用 0 补齐。故第一组数据 1,2,3 赋值给第一行,第二组补齐为三个数据 4,0,0 后,赋值给第二行。相当于 int a[][3]={1,2,3,4,0,0}; 初始化后, 数组中各元素为:
1 2 3
4 0 0
int a[][3] = {1,2,3,4,5,6,7,8}; //正确,可间接得知该数组为三行,不推荐第三行不够三个数据,用 0 补齐。故该语句相当于:
int 3[][3] = {1,2,3,4,5,6,7,8,0};初始化后,数组中各元素为:
1 2 3
4 5 6
7 8 0
int a[2][3] = {1,2,3,4,5,6,7,8};//错误。初始数据个数8多于数组总个数6
二维数组的存储
二维数组在逻辑(表现形式)上可理解为矩阵形式(分行分列),但其物理存储形式却是连续的,即存完第一行,在其后面接着存储第二行,第三行,…,如无特殊说明,本书中涉及对二维数组的表述一般指的是其逻辑形式即矩阵形式。如 int a[3][4]; 其逻辑结构是 3 行 4 列的矩阵形式,如图 1) 所示,而其存储结构是连续的(线性的),如图 2) 所示。
二维教组的应用举例
【例 3】一个班级有 N 名学生,每个学生有 4 门课程(语文、数学、外语、C 语言),计算每个学生的平均分。编写程序实现该功能需求。问题分析:该问题可把每个学生当成一个对象,而每个对象(学生)有 5 个属性:4 门课程成绩及平均分。故该问题可使用二维数组来处理数据,该二维数组含有 N 个对象,故第一维的大小为 N,每个对象有 5 个属性,故第二维的大小为 5。为便于验证,本例中学生个数 N 设为 3 个。
实现代码:
#include<stdio.h> #define N 3 int main (void) { float a[N][5],sum; //sum用来累加每个学生4门课的总成绩 int i, j ; printf ("输入%d个学生信息(语、数、外、C语言成绩):\n",N); for(i=0;i<N;i++) { sum=0.0; //对每个学生sum均初始为0 printf ("NO%d:",i+1); for (j=0; j<4; j++) //每个学生仅输人4门课成绩,注意j<4 { scanf("%f",&a[i][j]); sum+=a[i][j]; } a[i][4]=sum/4; //每个学生的平均成绩是计算出来的 } printf ("\n学号\t语文\t数学\t外语\tC语言\t平均成绩\n"); for(i=0;i<N;i++) { printf("NO%d:\t", i+1); for(j=0;j<5;j++) printf ("%.1f\t",a[i][ j]) ; //保留一位小数,注意格式%.1f printf ("\n"); } return 0; }运行结果为:
输入3个学生信息(语、数、外、C语言成绩):
NO1:82 91 88 93
NO2:83 84 80 91
NO3:73 79 86 81
学号 语文 数学 外语 C语言 平均成绩
NO1: 82.0 91.0 88.0 93.0 88.5
NO2: 83.0 84.0 80.0 91.0 84.5
NO3: 73.0 79.0 86.0 81.0 79.8