完整版第6章数组
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
第6章数组
6.1怎样定义和引用一维数组
6.1.1怎样定义一维数组
一般格式:
类型数组名[常量表达式]; 如: int a[10];
说明:
(1) 数组名的命名规则遵循C 语言的标识符;
(2) 常量表达式,用来指定该数组中元素的个数,也就是该数组的长度。
该长度必须在这里是一个常量表达式(数字常量、符号常量) ,不能是变量。
由 于前面已经指定了类型,指定了元素个数后,该数组一共占用的空间大小就确定 了,^口:上例,a 是int 型,每个int 型在VC 中占4字节,而后面又定义了 10 个元素,所以,a 共占用了 4*10=40个字节的空间;
(3) 可以使用sizeof 运算来求出某个数组占用了多少空间; 特别强调,其中常量表达式不能是变量,如:
int n=10;
int a[n];
//错误,因为n 是变量 #defi ne N 10 int b[N]; //正确,因为N 是符号常量 int c[N+3]; //正确的,因为N+3是常量表
达式
对于以下定义: int a[10];
那么,系统就会在内存中划出一片存储空间,女口 右图:
显然,每个元素在内存中都是连续的,从而可以 根据上一个元素的地址,来计算出下一个元素的地址, 假设a 的第0个元素存在1000地址上,那么,a[5]的 地址:Add (a[0])+5*4=1020。
而对 a[i]的地址:
Add (a[i])=Add (a[0]+i*4)
(4) 数组定义后,元素的排列是从a[0]开始的,因此定义的int a[10] 中, 只有a[0]~a[9],并不存在a[10]这个元素。
(5) 数组定义后,其中的每个元素的值都是不确定的(就是不知道值为多 少,也可以说是随机的),如果想要在定义时就有初值,可以有两种方法:
方法一:用static 来修饰,那么,该数组中的每个元素都会被初始化为 0: static int b[10]; //数组b 中的10个元素每个值都是0
1000
1004 1008
1020
1036
int a[10]; //数组a中的10个元素值不确定
方法二:在定义时,同时给出值,如:
int c[10]={1,2,3}; // 给出了3 个值
如果所给的值比数组元素少,那么后面的每个元素都会自动初始化成0,如上面的 c 数组,后面的c[3]~c[9] 都是0
(6)前面第 4 点说了,定义的a[10] 的元素是从a[0]~a[9] ,没有a[10] 这个元素,如果在程序中引用了a[10],实际上是一种“越界错误”,但是在VC中,一般还发现不了。
6.1.2 怎样引用一维数组元素
引用一维数组元素最常用的方法就是:数组名[ 下标]
如上面定义的:
int a[10]={1,2,3}; 现在要引用第2 个数:a[2]=123;
还可以有别的引用方式:
(1)引用时,下标可以是变量:for(i=0;i<10;i++)
a[i]=2*i;
其中的a[i] 就是引用;
(2)可以仿照后面的指针进行引用,如要引用a[5] 元素:*(a+5)=345;
6.1.3 一维数组的初始化
一维数组的通常初始化方式:
( 1 )在定义数组时,全部数组元素赋予初值,如:
int a[10]={1,2,3,4,5,6,7,8,9,10};
如果给出了所有的元素的值,那么,在定义时元素的个数可以省略,如上面的a,后面已经给了10个元素的值,那么可以省略10:
int a[ ]={1,2,3,4,5,6,7,8,9,10};
(2)可以只给数组中的一部分元素赋初值,如:
int a[10]={1,2,3};
只给了 3 个元素的值,那么后面所有的元素全部取为0
(3)如果想给数组每个元素都取为0, 可以写为:
int a[10]={0};
6.1.4 一维数组应用举例
Eg6.1 :通常引用一维数组的方式,就是用一个for 循环,如:
int a[10];
int i;
// 要对a 进行赋初值,可以用一个for 循环进行:for(i=0;i<10;i++)//i 是从
0 开始,到9 结束,用的是<10,不能用<=10
{
a[i]=2*i;
}
//如果要用户输入10个元素进行赋初值,可以用scanf
for(i=0;i<10;i++)
{
scan f("%d",&a[i]);
}
//也可以从后往前进行赋初值:
for(i=9;i>=0;i--)
{
a[i]=2*i;
}
//要处理数组a,实际上只能对a中的元素一个一个进行,无法进行整体操作,如:
for(i=0;i<10;i++)
{
a[i]=a[i]*2;
}
//输出数组,也是一个元素一个元素进行输出,通常也是用一个for :
for(i=0;i<10;i++)
{
printf("%5d",a[i]); //每个元素占5个位置,从而在屏幕上对齐}
Eg6.2 :已知有一个数组a如下:
int a[10]={1,2,3,4,5,6,7,8,9,10};
现在要将a数组逆置,就是原来a[0]的值变成a[9]的值等。
方法一:再定义一个数组b,其大小与a相同,将a中的元素反过来赋值给b,从而b就达到了a的逆置,再将a拷贝回去即可。
如下图所示:
0123456789
0123456789
b
那么b[0]的值来自于a[9],而b[3]的值来自于a[6],……,a[i]的值来自于b[9-i],这样,可以编程如下:
for(i=0;i<10;i++)
{
b[i]=a[9-i];
}
再将b拷回去:
for(i=0;i<10;i++)
{
a[i]=b[i];
}
方法二:不需要数组b,直接在a中进行,实际上只需要让a[0]与a[9]进行交换,a[1]与a[8]进行交换,......... ,a[i]与a[9-i]进行交换便可,注意这个
交换只能交换一次,不能重复交换,也就是说,要控制循环的次数,对于10个元素来说,只要交换5对元素,对于有n个元素来说,要交换n/2对元素:
t=a[i]; a[i]=a[9-i];
a[9-i]=t;
}
Eg6.3 :插入:向数组中指定位置插入一个元素,如,原来的数组a:
int a[20]={49,27,36,58,64,19,72,86,31};
现在要向a中的第5个位置插入元素88,效果如下:
a[20]={49,27,36,58,64,19,72,86,31};
01 23456789 19
49 27 36 58 64 88 19 72 86 31
显然,需要挪动的元素个数为:9-5 (原来数组中元素个数-要插入的位置),这些元素应该是:a[5]、a[6]、a[7]、a[8]这4个元素,且应该是倒过来移,就是先将
a[8]移动到a[9],再将a[7]移动到a[8] 上,以此类推,可得如下的程序段:for(j=8;j>=5;j--) a[j+1]=a[j];
用变量表示,n表示原来数组a中的元素个数,k表示要插入的元素位置,可得:for(j=n-1;j>=k;j--) a[j+1]=a[j];
最后一步,就是将元素x插入到a[k]位置上去:
a[k]=x;
完整的代码如下:
void mai n()
{
int a[20]={49,27,36,58,64,19,72,86,31};
int n=9; //数组a中原来有9个数
int k=5; //向第k个位置中插入元素x
int x=99; //欲插入的元素值
int i,j;
printf("\n 插入前,数组a:");
for(i=0;i <n ;i++)
prin tf("%5d",a[i]);
for(j=n-1;j>=k;j--) //从a[k]这个元素开始,每个元素向后挪动
49 27 36 58 64 19 72 86 31
01 23456789 19
a
int t;
for(i=0;i<10/2;i++)
{
//注意交换次数
④ ③ ② ①
一个位置,注意要从后往前开始挪动
a[j+1]=a[j];
a[k]=x; //元素x插入到a[k]中。
n++; //元素个数加1
printf("\n 插入手,数组a:");
for(i=0;i <n ;i++)
prin tf("%5d",a[i]);
putchar('\n');
}
Eg6.4 :删除:从数组中指定位置删除一个元素,如,原来的数组a:
int a[20]={49,27,36,58,64,19,72,86,31};
现在要删除a中的第5个位置的元素19,效果如下:
a[20]={49,27,36,58,64,19,72,86,31};
01
01
显然,需要挪动的元素个数为:9-5-1 (原来数组中元素个数-要插入的位置-1 ),这些元素应该是:a[6]、a[7]、a[8]这3个元素,就是先将a[6]移动到a[5],再将a[7]移动到a[6]上,以此类推,a[j]应该移动到a[j-1]个位置上去,可得如下的程序段:
for(j=k+1;j <n ;j++)
a[j-1]=a[j];
n--;
Eg6.5 :用数组求Fibonacci数列问题,打印出前20个元素,并求和
Fibonacci 数列是:1,1,2,3,5,8, ……
显然,可以让a[0]=1,a[1]=1 ,从a[2]开始,每个元素都为前面两个元素的和,也就是说,a[i]=a[i-1]+a[i-2]; 这个i应该从2开始到19结束。
可编程如下:
int a[30];
int i,sum=0;
a[0]=1;
a[1]=1;
sum+=a[0]+a[1];
for(i=2;i<20;i++) a[i]=a[i-1]+a[i-2]; sum+=a[i];
}
printf(”前20 个Fibonacci 数列为:\n");
for(i=0;i<20;i++)
{
prin tf("%1Od",a[i]);
if((i+1)%5==0)
putchar('\n');
}
prin tf("\n 它们的和为:%d\n",sum);
Eg6.6 :查找从数组a中查找指定的元素:
int a[20]={49,27,36,58,64,19,72,86,31}
19
可查找以下四种元素
(1)最大值:可得86,位置为7
(2)最小值:可得19,位置为5
(3)与某个存在的元素相等的第一个元素:x=58,可得位置为3
(4)不存在的某个元素:x=100,不存在
对于(1)和(2),可假定max=a[0]就是最大值(或最小值),然后从a[1] 开始,每个元素与max相比较,如果发现一个元素比max更大,则说明找到了一个可能是最大的数,让max=这个数,这样直到最后,就能找到最大值。
可编程如下:
Eg6.7:排序:
6.2怎样定义和引用二维数组
6.1.1怎样定义二维数组
类型数组名[行号常量表达式][列号常量表达式];
如:
int a[5][10];
其中第一维通常称为行,第二维通常称为列,如上面的a就有5行10列,
就是说,每行有10个元素,它们是如下排列的:
a[0][0],a[0][1],a[0][2], ……,a[0][9]
a[1][0],a[1][1],a[1][2], ……,a[1][9]
a[4][0],a[4][1],a[4][2], ……,a[4][9]
显然,一共有5*10 = 50个元素。
且这50个元素也是一个接一个地顺序存放在内存中,从而,也可以像一维数组一个快速计算其地址,如上例,假设a[0][0]
的地址为1000,那么a[2][7]的地址Add(a[2][7])=1000+(2*10+7)*4 ,也就是说,对于a[i][j] 的地址Add(a[i][j])=1000+(i*10+j)*4
上面的计算是C语言的方式,这种方式有一个前提:按行存放,就是优先满足一行,再下一行,有些编译器会按列序存放,就是优先满足一列,再存到下一列中,如:
int a[5][10];
如果按列序存放,则其存放的顺序为:
a[0][0]->a[1][0]->a[2][0]->a[3][0]->a[4][0]->a[1][1]->a[2][1] ……->a[3][9]->a[4][9]
那么在这种存放方式下,a[2][7]的地址=1000+(7*5+2)*4 也就是说,a[i][j] 的地址Add(a[i][j])=1000+(j*5+i)*4
6.1.2 怎样引用二维数组
引用二维数组需要给出行下标和列下标来,如:
a[2][3]=123;
6.1.3 二维数组的初始化
(1 )在定义时进行:
int a[3][5]={
{1,2,3,4,5}, {6,7,8,9,10}, {11,12,13,14,15}
};
(2)在定义时,也可以只用一个大括号:
int a[3][5]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}
(3)也可以在定义时,只给某些元素赋初值,如:
int a[3][5]={
{1,2,},
{6},
{11,12,13,14}
};
那么,像a[0][2] 、a[1][3] 等元素的初值系统会自动取为0
(4)还可以在定义时,只给某些行取初值:
int a[3][5]={
{1,2,},
{},
{11,12,13,14}
};
则第 1 行中的所有元素全部取为0
(5)如果给初值时,已经明确给出了几行,那么定义时行号可以省略,如:int a[ ][5]={
{1,2,},
{6}, {11,12,13,14}
};
已经明确给了3行的值,那么行数3可以省略
(6)如果将所有的元素全部给了值,且不是用若干个{} 来表示行,也可以省略行号,如:
int a[ ][5]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}
因为已经给了15 个元素,且每行有 5 个元素,那么系统会自动取 3 行,但是,如果给的不是15个元素,而是17个元素,那么系统知道,应该取 4 行,且后面的 3 个元素自动取为0.
(7)如果在定义时,没有取初值,那么可以在程序用一般用一个二重循环进行赋值,如:
int a[5][10];
int i,j;
for(i=0;i<5;i++) //0~4 行
{
for(j=0;j<10;j++) //0~9 列
{
a[i][j]=i+j;
}
}
(8)在输出一个二维数组时,通常会每行占一行,就是说,需要控制输出格式:
for(i=0;i<5;i++) //0~4 行
{
for(j=0;j<10;j++) //0~9 列
{
printf("%5d",a[i][j]);
}
putchar('\n'); // 换行
}
6.1.4 二维数组应用举例
Eg6.8 用二维数组输出杨辉三角,所谓杨辉三角,就是每个元素都等于其上一行的对应元素及其前一元素的和:
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
可以用二维数组实现,第0行不用,且每行的第0列也不用,从第1行开始,第 1 列取为 1 ,然后后面每个元素均为前一行的对应元素的和,如:a[i][j]=a[i-1][j]+a[i-1][j-1]
且第i 行应该有i 个元素,可得下图:
int a[20][20]={0}; // 全部初始化为0
int i,j;
a[1][1]=1;
for(i=2;i<=10;i++) {
for(j=1;j<=i;j++) //第i 行,只需要计算i 个元素
{
a[i][j]=a[i-1][j]+a[i-1][j-1]; }
}
//下面开始打印这个杨辉三角 for(i=1;i<=10;i++) {
for(j=1;j<=i;j++) {
prin tf("%7d",a[i][j]); }
putchar('\n'); }
Eg 6.9用二维数组计算矩阵: (1) 矩阵的查找 (2) 矩阵的和 (3) 矩阵的积 (4) 行列互换
先看查找,从一个二维数组中,查找一个指定的值(最大值、最小值、指定 的元素值等),如有如下的二维数组:
int a[3][4]={49,56,23,64,78,61,19,41,23,38,88,97}; 则最大值为97,其位置为:第2行的第3个 最小值为19,位置为第1行的第2个
仍然可以使用二维数组,先假定 a[0][0]是最大值max
0 0 0
0 1 0
0 1 1 0
0 1 2 1 0
0 1
3
3
1
8
0 1 2 3 6 4 5 7 0 1 2 3 4 5
6
7 8
9
int max,l_max_i,l_max_j; // 分别表示最大值及其位置
max=a[0][0];
l_max_i=0;
l_max_j=0;
for(i=0;i<3;i++)
{
for(j=0;j<4;j++)
{
if(a[i][j]>max)
{
max=a[i][j]; l_max_i=i; l_max_j=j;
}
}
}
如果要找x=78:
int l_x_i,l_x_j;
int x=78;
int flag=0; // 先假定没找到
for(i=0;i<3;i++)
{
for(j=0;j<4;j++)
{
if(a[i][j==x)
{
l_x_i=i;
l_x_j=j;
flag=1;
break;
}
}
if(flag==1)
break; // 已经找到,直接退出该循环
}
if(flag==1) // 找到了,输出相应的信息
{
}else // 没找到,此时的l_x_i 和l_x_j 没有意义
{
}
6.3 字符数组
在定义一维数组时,如果类型为char 型,那么这个一维数组就是是字符数组,如:
int a[10];
char s[10];
字符数组与int型数组没有本质的区别,与int型数组类似,可以进行上面的操
作。
但是,字符数组如果在末尾加一个'\0'之后,该字符数组就有更多的应用。
如:
char s1[20]="Chi nese";
char s2[20]={C,'h','i',' n','e','s','e'};
char s3[ ]= {'C','h','i',' n','e','s','e'};
char s4[7]= {C,'h','i',' n','e','s','e'};
char s5[8]= {C,'h','i',' n','e','s','e'};
char s6[ ]="Chi nese";
其中s3没有指定数组的大小,系统会自动去数后面的数(7个),那么,S3 就取成7个char型大小,所以s3与s4是等价的。
对于s5来说,后面只有7个字符,但是前面大小为8,则有一个a[7]没有初值,系统会自动取为0,所以s5相当于:
char s5[8]= {'C','h','i',' n','e','s','e',0};
对于s2来说,大小为20个元素,显然,后面有13个0:
char
s2[20]={'C','h','i',' n','e','s','e',0,0,0,0,0,0,0,0,0,0,0,0,0};
如果某个字符数组后面至少有一个0 (0是它的ASCII码,如果用字符表示
这个0号字符,可用'\0'),那么这个字符数组就称为字符串。
如上面的s2、s5后面都至少有一个'\0',所以它们是字符串
而S3、s4后面没有'\0',它们不是字符串
如果要定义一个字符串的一维数组,还可以用""来括起来,如上面的s1,
后面的"Chinese"是一个用""括起来的字符串,,且s1的大小为20个元素。
而s6没有指定大小,那么系统会去数,其中Chines占7个char,系统还
会自动在后面加一个\0,故s6的大小为8个字节空间。
其中的一个字符串末尾的\0就称为字符串的结束标志。
有了这个标志,字符串的编程就与int型的不同,可以用这个标志来判断是否已经到了末尾。
如下图:
由于有了这个标志,一般可以用while循环来进行。
Eg 6.10 输出s2:
方法一:用for循环,输出该数组的所有的元素:
int i;
char s2[20]={C,'h','i',' n','e','s','e'};
for(i=0;i<20;i++)
putchar(s2[i]);
方法二:用while循环,直到遇到\0就结束输出:
i=0;
while( s2[i]!='\0')
putchar(s2[i]);
i++;
}
方法三:如果确定该字符数组是字符串(就是一定有那个'\0' ),printf
的%s格式输出:
printf("%s",s2); 方法四:方法三:如果确定该字符数组是字符串(就是一定有那个还可以用puts(s2) 进行输出:
puts(s2);
Eg 6.11 将字符串s2 中的所有元素大小写互换:
原来:char s2[20]={'C','h','i','n','e','s','e'};
互换后s2 中的内容为:"cHINESE"
实际上思想就是从s2[0] 开始,直到\0 前面,一个一个进行处理,
字符是大写的字母,则将其+32 就变成小写,如果是小写字母,就将其果不是字母,则直接跳过。
i=0;
while(s2[i]!='\0')
{
if(s2[i]>='A' && s2[i]<='Z') // 大写字母
s2[i]=s2[i]+32;
else if(s2[i]>='a' && s2[i]<='z') // 小写字母
s2[i]=s2[i]-32;
i++;
}
其中的这个循环:
i=0;
while(s2[i]!='\0')
{
// 操作s2
i++
}
是字符串编程中万能的循环。
Eg 6.12 求字符串s2 字符的个数,就是\0 前面的个数:
i=0;
while(s2[i]!='\0')
{
i++
}
最后,i的值就是s2的字符个数
课后练习:
教材P168的第1~15题。
还可以用'\0' ),
如果这个-32,如。