第七章 数组 前面各章所使用的数据都属于基本数据类型(整型、实型、字符型),C语言除了提供基本数据类型外,还提供了构造类型的数据,它们是数组类型、结构体类型、共同体类型等。构造数据类型是由基本数据类型的数据按照一定的规则组成,所以也称为“导出类型”。 下面简单介绍一下数组概念: 1、数组:一组具有相同数据类型的数据的有序的集合。 2、数组元素:构成数组的数据。数组中的每一个数组元素具有相同的名称,不同的下标,可以作为单个变量使用,所以也称为下标变量。 3、数组的下标:是数组元素的位置的一个索引或指示。 4、数组的维数:数组元素下标的个数。根据数组的维数可以将数组分为一维、二维、三维、多维数组。 7.1 一维数组 数组是一组有序数据的集合,数组中每一个元素的类型相同。用数组名和下标来唯一确定数组中的元素。 7.1.1 一维数组的定义(先定义后使用) 定义方式: 类型说明符 数组名[整型常量表达式] 例: int a[10] int a[10] 表示如下信息: 定义一个数组,数组名a,有10个元素,每个元素的类型均为int。这10个元素分别是:a[0]、a[1]、a[2]、a[3]、a[4]、....、a[8]、a[9]。各个数组元素是排成一行的一组下标变量,用一个统一的数组名来标识,用一个下标来指示其在数组中的位置。一维数组通常和一重循环相配合,对数组元素进行处理。 注意: 1)下标从0开始。如:数组a的第1个元素是a[0],数组的第8个元素是a[7] 注意: 2)C语言不允许对数组的大小做动态定义,如: int n; scanf("%d",&n); int a[n]; 因为在编译时,C编译器根据已知数组大小分配内存。 说明: 1)数组名:按标识符规则。本例a就是数组名。 2)整型常量表达式:表示数组元素个数(数组的长度)。可以是整型常量或符号常量,不允许用变量。整型常量表达式在说明数组元素个数的同时也确定了数组元素下标的范围,下标从0开始~整型常量表达式-1(注意不是1~整型常量表达式)。 C语言不检查数组下标越界,但是使用时,一般不能越界使用,否则结果难以预料(覆盖程序区-程序飞出,覆盖数据区-数据覆盖破坏,操作系统被破坏,系统崩溃)。本例数组元素个数是10个,下标从0-9。 3)C编译程序为数组分配了一片连续的空间。 4)类型说明:指的是数据元素的类型,可以是基本数据类型,也可以是构造数据类型。类型说明确定了每个数据占用的内存字节数。 一维数组: float mark[100];
7.1.2 一维数组的初始化 初始化:在定义时指定初始值,编译器把初值赋给数组变量。赋值:使用赋值语句,在程序运行时把值赋给数组变量,如a[0] = 2。 初始化格式: 数据类型 数组名[常量表达式]={初值表} 1、一般初始化,例: static int a[10]= { 0,1,2,3,4,5,6,7,8,9} int array[10] = {1,2,3,4,5,6,7,8,9,10}; 2、部分元素初始化,其余元素均为零。 例:static int a[10] = {0,1,2,3,4}; 仅前5个元素赋初值,后5个元素自动赋为0。 3、全部元素均初始化为0,不允许简写。 static int a[10] = {0,0,0,0,0,0,0,0,0,0}; 不能写成:int a[10]={0*10}; 不能简写为:static int a[10] = {0*10}; 注意:当程序不给数组指定初始值时,编译器作如下处理: (1)编译器自动把静态数组的各元素初始化为0。 (2)编译器不为动态数组自动指定初始值。 4、如果全部元素均指定初值,定义中可以省略元素的个数,例: static int a[5] = {1,2,3,4,5}; 可以写为: static int a[ ] = {1,2,3,4,5}; 7.1.3 数组元素的引用 C语言规定,不能引用整个数组,只能逐个引用元素,元素引用方式: 数组名[下标表达式] 例:a[0] = a[5] + a[7] - a[2*3] “下标表达式”可以是任何非负整型数据,取值范围是0 ~ (元素个数-1)。 特别强调:在运行C语言程序过程中,系统并不自动检验数组元素的下标是否越界。因此在编写程序时,保证数组下标不越界是十分重要的。 1个数组元素,实质上就是1个变量,它具有和相同类型单个变量一样的属性,可以对它进行赋值和参与各种运算。 在C语言中,数组作为1个整体,不能参加数据运算,只能对单个的元素进行处理。 7.1.4 一维数组的应用 [例7.3] 输入10个数,用“冒泡法”对10个数排序(由小到大)。 冒泡法的基本思想:通过相邻两个数之间的比较和交换,使排序码(数值)较小的数逐渐从底部移向顶部,排序码较大的数逐渐从顶部移向底部。就像水底的气泡一样逐渐向上冒,故而得名。 “冒泡法”算法:以六个数9、8、5、4、2、0为例。 第1趟比较(下图1) 第2趟比较(下图2)
第1趟比较后,剩5个数未排好序;两两比较5次 第2趟比较后,剩4个数未排好序;两两比较4次 第3趟比较后,剩3个数未排好序;两两比较3次 第4趟比较后,剩2个数未排好序;两两比较2次 第5趟比较后,全部排好序;两两比较1次 算法结论:对于n个数的排序,需进行n-1趟比较,第j趟比较需进行n-j次两两比较。 程序流程图:(用两层嵌套循环实现)
程序:设需排序的数有10个,定义数组大小为11,使用a[1]~a[10]存放10个数,a[0]不用。 main() { int a[11]; /* 用a[1]~a[10], a[0]不用*/ int i,j,t; /* i,j作循环变量,t作临变*/ printf("input 10 numbers:\n"); for(i=1;i<11;i++) scanf("%d",&a[i]); /* 输入10个整数 */ printf("\n"); for(j=1;j<=9;j++) /* 第j趟比较 */ for(i=1;i<=10-j; i++) /* 第j趟中两两比较10-j次 */ if (a[i] > a[i+1]) /* 交换大小 */ { t = a[i]; a[i] = a[i+1]; a[i+1] = t; } printf("the sorted numbers:\n"); for(i=1;i<11;i++) printf("%d",a[i]); }
7.2 二维数组 二维数组:数组元素是双下标变量的数组。 二维数组的数组元素可以看作是排列为行列的形式(矩阵)。二维数组也用统一的数组名来标识,第一个下标表示行,第二个下标表示列。下标从0开始。 7.2.1 二维数组的定义 类型说明符 数组名1[行常量表达式1][列常量表达式1], 数组名2[行常量表达式2][列常量表达式2]……; 例:int a[3][4]; a为3×4(3行4列)的数组 float b[5][10];b为5×10(5行10列)的数组 二维数组的理解: 二维数组a[3][4]理解为: 有三个元素a[0]、a[1]、a[2],每一个元素是一个包含4个元素的数组。
二维数组的元素在内存中的存放顺序: 按行存放,即:先顺序存放第一行的元素,再存放第二行的元素。(最右边的下标变化最快,第一维的下标变化最慢)。
例如:整型数组 b[3][3]={ {1,2,3}, {4,5,6}, {7,8,9} };
再一次说明: (1)二维数组中的每个数组元素都有两个下标,且必须分别放在单独的“[ ]”内。如:a[3,4 ] (2)二维数组定义中的第1个下标表示该数组具有的行数,第2个下标表示该数组具有的列数,两个下标之积是该数组具有的数组元素的个数。 (3)二维数组中的每个数组元素的数据类型均相同。二维数组的存放规律是“按行排列”。 (4)二维数组可以看作是数组元素为一维数组的数组。 7.2.2 二维数组的初始化 1、分行赋值,如: static int a[3][4] ={{1,2,3,4},{5,6,7,8},{9,10,11,12}}; 2、全部数据写在一个大括号内,如: static int a[3][4]= {1,2,3,4,5,6,7,8,9,10,11,12}; 3、部分元素赋值,如: static int a[3][4] = {{1},{5},{9}}; 仅对a[0][0]、a[1][0]、a[2][0]赋值,其余元素未赋值(编译器自动为未赋值元素指定初值0。 教材p137还列举了一些部分元素赋初值的例子,请自行阅读。 4、如果对全部元素赋初值,则第一维的长度可以不指定,但必须指定第二维的长度。 例: static int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12}; 与下面定义等价: static int a[ ][4]={1,2,3,4,5,6,7,8,9,10,11,12}; 7.2.2 二维数组元素的引用 用数组名和下标引用元素。例: float a[2][3]; 有6个元素,按如下方式引用各元素: a[0][0]、a[0][1]、a[0][2]、a[1][0]、a[1][1]、a[1][2] (下标从0始) 注意:数组float a[2][3]中无元素a[2][3]。 7.2.3 二维数组应用举例 二维数组的遍历访问(扫描),一般都采用双重循环处理(行循环,列循环)。 [例7.4] 将一个二维数组行和列交换,存到另一个二维数组中。例如:
算法: b[j][i] = a[i][j] 程序: main() { static int a[2][3] = {{1,2,3},{4,5,6}}; static int b[3][2], i,j; printf("array a:\n"); for(i=0;i<=1;i++)/* 0~1行 */ { for(j=0;j<=2;j++) /* 0~2列 */ { printf("%5d",a[i][j]); b[j][i] = a[i][j];/* 行、列交换 */ } printf("\n");/*输出一行后换行 */ } printf("array b:\n"); for(i=0;i<=2;i++) { for(j=0;j<=1;j++) printf("%5d",b[i][j]); printf("\n"); /*输出一行后换行 */ } }
[例7.5] 有一个3×4的矩阵,要求编程序以求出其中值最大的那个元素的值及其所在的行号和列号。 解题思路:首先把第一个元素a[0][0]作为临时最大值max,然后把临时最大值max与每一个元素a[i][j]进行比较,若a[i][j]>max,把a[i][j]作为新的临时最大值,并记录下其下标i和j。当全部元素比较完后,max是整个矩阵全部元素的最大值。
main() { int i,j,row=0,colum=0,max; static int a[3][4]={{1,2,3,4},{9,8,7,6},{-10,10,-5,2}}; max = a[0][0]; for(i=0; i<=2; i++) /* 用两重循环遍历全部元素 */ for(j=0; j<=3; j++) if (a[i][j] > max ) { max = a[i][j]; row = i;colum = j; } printf("max=%d, row=%d, colum=%d\n",max,row,colum); } 注意:本例中得到的行列值从0始。 7.2.5 多维数组 1. 当数组元素的下标在2个或2个以上时,该数组称为多维数组。其中以2维数组最常用。 2. 定义多维数组:类型说明 数组名[整型常数1] [整型常数2]… [整型常数k]; 3. 例如:int a[2][3][3]; 4. 定义了一个三维数组a,其中每个数组元素为整型。总共有2x3x3=18个元素。 说明: 1) 对于三维数组,整型常数1,整型常数2,整型常数3可以分别看作“深”维(或:“页”维)、“行”维、“列”维。可以将三维数组看作一个元素为二维数组的一维数组。三维数组在内存中先按页、再按行、最后按列存放。 2) 多维数组在三维空间中不能用形象的图形表示。多维数组在内存中排列顺序的规律是:第一维的下标变化最慢,最右边的下标变化最快。 3) 多维数组的数组元素的引用:数组名[下标1] [下标2]… [下标k]。多维数组的数组元素可以在任何相同类型变量可以使用的位置引用。只是同样要注意不要越界。 三维数组的元素排列顺序
7.3 字符数组 字符数组:存放字符数据的数组,每一个元素存放一个字符。 7.3.1程序中定义字符数组 例、char c[10]; /* 定义c为字符数组,包含10个元素 */ c[0]='I'; c[1]=' '; c[2]='a'; c[3]='m'; c[4]=' ';c[5]='h'; c[6]='a'; c[7]='p'; c[8]='p'; c[9]='y';
注意:字符型与整型可以通用,但有区别: char c[10]; /* 在内存中占10字节 */ int c[10]; /* 在内存中占20字节 */ 7.3.2字符数组的初始化 1、逐个元素初始化 static char c[10] = {'I',' ','a','m',' ','h','a','p','p','y'}; 2、初始化数据少于数组长度,多余元素自动为“空”('\0',二进制0)。 static char c[10] = {'c','','p','r','o','g','r','a','m'};/* 9 */ 3、指定初值时,若未指定数组长度,则长度等于初值个数(不加’\0’)。 static char c[ ] = {'I',' ','a','m',' ','h','a','p','p','y'}; '}; /* 10 */ 7.3.3字符数组的引用 引用一个元素,得到一个字符。 [例7.2.1] 输出一个字符串。 main() { static char c[10]={'I',' ','a','m',' ','a',' ','b','o','y'}; int i; for(i=0;i<10;i++) printf("%c",c[i]); printf("\n"); } 输出结果: I am a boy [例6.7] 输出一个钻石图形。 main() { static char diamond[ ][5]={{' ',' ','*'}, {' ','*',' ','*'}, {'*',' ',' ',' ','*'}, {' ','*',' ','*'}, {' ',' ','*'} }; int i,j; for(i=0;i<5;i++) { for(j=0;j<5;j++) printf("%c",diamond[i][j]); printf("\n"); } } 输出结果:
7.3.4字符串 字符串例子:"I am a boy" 1、C语言中,字符串作为字符数组处理。字符数组可以用字符串来初始化,例: static char c[] = {"I am happy"}; 也可以这样初始化:(不要大括号) static char c[] = "I am happy"; 2、字符串在存储时,系统自动在其后加上结束标志‘\0’(占一字节,其值为二进制0)。但字符数组并不要求其最后一个元素必须是'\0',例:static char c[] = {"China"};
static char c[5] = {"China"};
static char c[10] = {"China"};
7.3.5字符数组的输入输出 两种方法: 1、用“%c”格式符逐个输入输出。 2、用“%s”格式符按字符串输入输出。 例: static char c[6]; scanf("%s",c); printf("%s",c); 注意: 1)输出时,遇'\0'结束,且输出字符中不包含'\0'。 2)“%s”格式输出字符串时,printf()函数的输出项是字符数组名,而不是元素名。 static char c[6] = "China"; printf("%s",c); printf("%c",c[0]); printf("%s",c[0]); 3)“%s”格式输出时,即使数组长度大于字符串长度,遇‘\0’也结束。 例:static char c[10] = {"China"}; printf(“%s”,c); /*只输出5个字符 */ 4)“%s”格式输出时,若数组中包含一个以上'\0',遇第一个'\0'时结束。 5)输入时,遇回车键结束,但获得的字符中不包含回车键本身,而是在字符串末尾添‘\0’。因此,定义的字符数组必须有足够的长度,以容纳所输入的字符。如:输入5个字符,定义的字符数组至少应有6个元素。 6)一个scanf函数输入多个字符串,输入时以“空格”键作为字符串间的分隔。例:static char str1[5],str2[5],str3[5]; scanf("%s%s%s",str1,str2,str3); 输入数据:How are you? str1、str2、str3获得的数据见下图。
例:static char str[13]; scanf("%s",str); 输入:How are you? 结果:仅“How”被输入数组str 如要想str获得全部输入(包含空格及其以后的字符),程序应设计为: static char c[13]; int i; for(i=0;i<13;i++) c[i] = getchar(); 7) C语言中,数组名代表该数组的起始地址,因此,scanf()函数中不需要地址运算符&。 例:static char str[13] ;scanf("%s",str); scanf("%s",&str); 8)不能采用赋值语句将一个字符串直接赋给一个数组。 例:char c[10];c[]=“good”; 但是可以初始化:如:char c[ ]= “good”; 7.3.6字符串处理函数 在C的函数库中,提供了一些字符串处理函数。字符串输入、输出函数: 在调用字符串输入、输出函数时,在程序前面通常应设置一个相关的文件包含预处理命令(有关预处理命令将在第9章介绍),即 #include <stdio.h> 1、puts()函数:输出字符串(以‘\0’结尾)。 例、static char c[6]=“China”; printf、puts均以‘\0’结尾. printf("%s\n",c); printf需要格式控制符%s puts(c); puts不需要格式控制符,且自动换行 2、gets()函数:输入字符串到数组。 例: static char str[12]; gets(str); 注意:gets()、puts()一次只能输入输出一个字符串。而scanf()、printf()可以输入输出几个字符串。 3、strcat():连接字符串。 strcat(字符串1,字符串2); 功能:把“字符串2”连接到“字符串1”的后面。从str1原来的’\0’(字符串结束标志)处开始连接。 注意: 字符串1一般为字符数组,要有足够的空间,以确保连接字符串后不越界; 字符串2可以是字符数组名,字符串常量或指向字符串的字符指针(地址)。 4、strcpy():字符串拷贝。 strcpy(字符串1,字符串2); 功能:将“字符串2”为首地址的字符串复制到“字符串1”为首地址的字符数组中。即把“字符串2”的值拷贝到“字符串1”中。 例如: static char str1[10],str2[ ]={“china”}; strcpy(str1,str2); 执行后str1的状态为:
注意: str1-一般为字符数组,要有足够的空间,以确保复制字符串后不越界; str2-可以是字符数组名,字符串常量或指向字符串的字符指针(地址)。 字符串(字符数组)之间不能赋值,但是通过此函数,可以间接达到赋值的效果。 5、strcmp():字符串比较。 int strcmp(字符串1,字符串2); 比较“字符串1”、“字符串2”, 例: strcmp(str1,str2); strcmp("China", "Korea"); strcmp(str1, "Beijing"); 比较规则:逐个字符比较ASCII码,直到遇到不同字符或‘\0’,比较结果是该函数的返回值。 字符串1 < 字符串2, strcmp()返回值<0 字符串1 == 字符串2, strcmp()返回值==0 字符串2 > 字符串2, strcmp()返回值>0 长度不同的字符串也可以进行比较,比较结果当然是“不同”。 长度不同的字符串也可以进行比较,比较结果当然是“不同”。 注意:字符串只能用strcmp函数比较,不能用关系运算符“==”比较。 例:if (strcmp(str1,str2)== 0) printf("yes"); if (str1 == str2) printf("yes"); 6、求字符串的长度函数strlen(str) 功能:统计str为起始地址的字符串的长度(不包括“字符串结束标志”),并将其作为函数值返回。 注意:在调用字符串处理函数时,在程序前面应设置一个相关的文件包含预处理命令,即#include <string.h> 7.3.4 字符数组应用举例 [例7.8] 输入一行字符,统计其中有多少个单词(单词间以空格分隔)。 比如,输入“ I am a boy.",有4个单词。 算法:单词的数目由空格出现的次数决定(连续出现的空格记为出现一次;一行开头的空格不算。)。应逐个检测每一个字符是否为空格。 用num表示单词数(初值为0)。word=0表示前一字符为空格,word=1表示前一字符不是空格,word初值为0。如果前一字符是空格,当前字符不是空格,说明出现新单词,num加1。 #include "stdio.h" /* gets()函数在该头文件定义 */ main() { char string[81] ; int i, num = 0, word = 0; char c; gets(string); for(i=0; (c=string[i]) != '\0';i++) if (c==' ') word = 0; else if (word == 0) { word = 1; num++; } printf("There are %d words in the line\n",num); } [例7.9] 输入三个字符串,并找出其中最大者。 分析:用strcmp()函数比较字符串的大小。首先比较前两个,把较大者拷贝给字符数组变量string(用strcpy()函数拷贝),再比较string和第三个字符串。 程序:设字符串最长为19个字符。 #include "string.h" /* strcmp、strcpy函数均在string.h中定义 */ Int main() { char string[20]; /* 存最大字符串 */ char str[3][20]; /* 三个字符串 */ int i; for(i=0;i<3;i++) gets(str[i]); /* 输入三个字符串 */ if (strcmp(str[0],str[1]) > 0) strcpy(string,str[0]); else strcpy(string,str[1]); if (strcmp(str[2],string) > 0) strcpy(string,str[2]); printf("\nthe largest string is: \n%s\n",string); } 小结: 数组是程序设计的常用数据结构,字符串(字符数组)在现代程序中也用得相当普遍,应掌握它们的意义和用法。 1、数组(一维和二维,多维)名、数组元素的概念。一维数组常与一重循环配合、二维数组常与二重循环配合,对数组元素依次进行处理。 2、数组必须先定义后使用,数组的初始化方法。 3、数组元素在内存中的存储顺序。 4、数据排序算法。(冒泡排序法) 5、字符串的特点(作为字符数组处理、最后一字节加'\0')。 6、字符串处理函数的应用。
|
|
来自: running_to_you > 《第七章 数组》