C数组和字符串
数组
“线性表”(Linear List)是数学应用在计算机科学中的一种相当简单与基本的数据结构。简单地说,线性表是n个元素的有限序列(n≥0),26个英文字母的字母表A,B,C,D,E,…,Z就是一个典型的线性表。
线性表的应用在计算机科学领域中相当广泛。例如,C语言中的数组或字符串结构就是一种典型线性表的应用,在计算机中属于内存中的静态数据结构(Static Data Structure),特性是使用连续存储空间(Contiguous Allocation)存储,内存分配在编译时必须分配给相关变量。
在程序设计语言中,数组(Array)可以看作是一群相同名称与数据类型的集合,并且在内存中占有一块连续的内存空间。在不同的程序设计语言中,数组结构类型的声明也有所不同,通常必须包含以下5种属性。
- 起始地址:表示数组名(或数组第一个元素)所在内存中的起始地址。
- 维数:代表此数组为几维数组,如一维数组、二维数组、三维数组等。
- 下标上下限:指在此数组中,元素内存存储位置的上标与下标。
- 数组元素个数:索引上限与索引下限的差加1。
- 数组类型:声明此数组的类型,用于决定数组元素在内存中所占空间的大小。
只要具备数组5种属性且计算机内存足够理想,任何程序设计语言中的数组表示法(Representation of Arrays)都容许n维数组的存在,通常数组可以分为一维数组、二维数组与多维数组等,基本的工作原理也大致相同。
认识C语言的数组
在C语言中,要存取数组中的数据,就要配合下标值(index)寻找数据在数组中的位置。
一般变量能帮我们存储一份数据,但如果数据过多,用变量存储就会非常麻烦。例如,班上有50位学生,要存储学生的数据就要声明50个变量。这时使用数组存储数据就可以有效改善上述问题。
一维数组
一维数组(One-Dimensional Array)是最基本的数组结构,使用一个下标值就可存放多个相同类型的数据。数组也和变量一样,必须事先声明,这样编译时才能分配到连续的存储空间。在C语言中,一维数组的语法声明如下:
数据类型 数组名[数组长度];
当然也可以在声明时直接设置初始值:
数据类型 数组名[数组大小]={初始值1,初始值2,…};
在此声明格式中,数据类型表示该数组存放元素的共同数据类型,例如C语言的基本的数据类型(如int,float,char等)。数组名则是数组中所有数据的共同名称,命名规则与变量相同。
所谓元素个数,是指数组可存放的数据个数。例如在C语言中定义如下一维数组
int Score[5];
在C语言中,数组的下标值是从0开始的,对于定义好的数组,可以通过指定下标值存取数组中的数据。声明数组后,可以像将值赋给变量一样给数组内的每一个元素赋值,例如:
Score[0]=65;
Score[1]=80;
如果这样的数组代表两位学生的成绩,在程序中需要输出第2位学生的成绩,可以如下表示:
printf("第2位学生的成绩:%d",Score[1]); /* 下标值为1 */
下面列举几个一维数组的声明实例:
int a[5];/*声明一个int类型的数组a,数组a中可以存放5个整数*/
long b[3];/*声明一个long类型的数组b,数组b可以存放3个长整数*/
float c[10];/*声明一个float类型的数组c,数组c可以存放10个单精度浮点数*/
此外,两个数组间不可以直接用“=”运算符互相赋值,只有数组元素之间才能互相赋值。例如:
int Score1[5],Score2[5];
Score1=Score2; /* 错误的语法 */
Score1[0]=Score2[0]; /* 正确 */
在定义一维数组时,如果没有指定数组元素的个数,那么编译程序会根据初始值的个数来自动决定数组的长度。例如下面定义数组arr设置初值时,元素个数会自动设置成3:
int arr[]={1, 2, 3};
以下程序是一个声明数组与存取数组元素数据的简单范例,使用一维数组记录5位学生的分数,使用for循环打印每位学生的成绩并计算总分和平均分。
01 #include <stdio.h>
02 #include <stdlib.h>
03
04 int main()
05 {
06 int Score[5]={ 87,66,90,65,70 };
07 /* 定义整数数组 Score[5],并设置5个成绩 */
08 int i=0;
09 float Total=0;
10
11 for (i=0;i< 5; i++) /* 执行 for 循环输出学生成绩 */
12 {
13 printf("第 %d 位学生的分数:%d\n",i+1,Score[i]);
14 Total+=Score[i]; /* 计算总成绩 */
15 }
16 printf("----------------------------------\n");
17 printf("总分:%.1f 平均分:%.1f\n", Total,Total/5);
18 /* 输出成绩总分和平均分 */
19
20 system("pause");
21 return 0;
22 }
- 第6行:声明整数数组Score,同时设置5位学生成绩的初始值。
- 第11行:通过for循环设置i变量从0开始计算,并作为数组的下标值,计算5位学生的总分Total。
- 第17行:输出成绩总分及平均分。
下面的范例程序用于示范一维数值数组的特性,数组arr的初值设置为数字1~10,并进行数值累加。
01 #include <stdio.h>
02 #include <stdlib.h>
03
04 int main()
05 {
06
07 int arr[10]={1,2,3,4,5,6,7,8,9,10};
08 int i,sum=0;
09
10 for (i=0;i<10;i++)
11 {
12 if(i==0)
13 printf(" "); /*如果i等于0就输出空格*/
14 else
15 printf("+"); /*如果i不等于0就输出+号*/
16 printf("%d",i+1);
17 sum = sum + arr[i]; /*将数组中的每个元素累加到sum*/
18 printf(" = %d\n",sum); /*输出累加后的结果 */
19 }
20 system("pause");
21 return 0;
22 }
- 第7行:声明一个整数数组arr,并设置初始值为1~10。
- 第13行:如果i等于0就输出空格。
- 第15行:如果i不等于0就输出+号。
- 第17行:将整数数组arr内的值累加到变量sum中。
- 第18行:输出累加后的结果。
冒泡法
接下来补充一维数组在排序(Sorting)上的应用。“排序”在程序设计领域中是一种很普遍的技巧,是指将一组数据按特定规则调换位置使数据具有某种次序关系(递增或递减)。排序的方法有许多种,在此我们介绍最为普遍的“冒泡法”(Bubble Sort)。
冒泡法的排序原理是逐次比较两个相邻的记录,如果大小顺序有误就立即对调,扫描一遍后一定有一个记录被置于正确的位置,仿佛气泡逐渐从水底冒升到水面上。
下面的范例程序运用冒泡排序算法,使用一维数组与for循环来将以下数列从小到大排序,这些数列的数据值将存放在一维数组中:
01 #include <stdio.h>
02 #include <stdlib.h>
03
04 int main()
05 {
06 int i,j,tmp;
07 int data[8]={16,25,39,27,12,8,45,63}; /* 原始数据 */
08
09 printf("冒泡排序法:\n原始数据为:");
10 for (i=0;i<8;i++)
11 printf("%3d",data[i]);
12 printf("\n");
13
14 for (i=7;i>0;i--) /* 扫描次数 */
15 {
16 for (j=0;j<i;j++)/*比较、交换次数*/
17 {
18 if (data[j]>data[j+1])/*比较相邻两数,如果第一个数较大就交换*/
19 {
20 tmp=data[j];
21 data[j]=data[j+1];
22 data[j+1]=tmp;
23 }
24 }
25 }
26 printf("排序后的结果为:");
27 for (i=0;i<8;i++)
28 printf("%3d",data[i]);
29 printf("\n");
30
31 system("pause");
32 return 0;
33 }
- 第7行:声明一个一维数组data并将此数列的数据值作为数组data的初始值。
- 第10~11行:输出此一维数组的所有元素值。
- 第18行:比较相邻两数,如果第一个数较大就交换两数位置。
- 第20~22行:直接进行数组元素的移动与交换操作。
- 第27~29:输出最后排序的结果。
二维数组
一维数组可以扩展到二维或多维数组,在使用上和一维数组相似,都是处理相同数据类型的数据,差别只在于维数的声明。例如,一个含有2×4个元素的C语言二维数组A[4][4]
,各个元素在直观平面上的排列方式所示。
在C语言中,二维数组的声明格式如下:
数据类型 数组名 [行数] [列数];
例如,声明数组arr的行数是3、列数是5,那么所有元素个数为15。语法格式如下:
int arr[3] [5];
arr为一个3行5列的二维数组,也可以视为3×5的矩阵。在存取二维数组中的数据时,使用的下标值仍然是从0开始。图8-10以矩阵图形来说明这个二维数组中每个元素的下标值与存储空间对应的关系。
当我们给二维数组设置初始值时,为了便于分隔行与列以及增加可读性,除了最外层的{}外,最好以{}包括住每一行元素初始值,并以“,”分隔每个数组元素,例如: ` int A[2][3]={ {1,2,3},{2,3,4} };` 还有一点要说明,C语言对于多维数组下标的设置,只允许第一维(第一个下标)省略不予定义,其他维数的下标都必须清晰定义出长度。例如以下声明范例:
int a[2][3] = { {1,2,3},{4,5,6} }; /*合法的声明*/
char b[ ][2] = { {'a','b'}, /*合法的声明,省略第一维元素个数的声明方法*/
{'c','d'},
{'e','f'} };
long c[2][2] = {0}; /*将各个元素的初值都设为0*/
double d[3][3] = { {0.5,2.7},
{3.1,2.5,6.9},/*合法的声明*/
{1.5} };
int A[2][ ]={ {1,2,3},{2,3,4} }; /*不合法的声明*/
在二维数组中,以大括号所包围的部分表示同一行的初值设置。与一维数组相同,如果设置初始值的个数少于数组元素,其余未设置初值的元素就会自动被设置为0。例如下面的情况:
int A[2][5]={ {77, 85, 73}, {68, 89, 79, 94} };
由于数组中的A[0][3]、A[0][4]、A[1][4]
都未设置初始值,因此初始值都会设置为0。下面的方式会将二维数组所有的值设置为0(常用在整数数组的初始化中):
int A[2][5]={ 0 };
以上声明只用一个大括号包括,表示把二维数组A视为一长串数组。因为初始值的个数少于数组元素,所以数组A中所有元素的值都被设置为0。再来看一个例子,按照以下方式声明:
int A[2][5]={ 5 };
这样声明的结果并不是二维数组A的所有元素都是5,而是只有A[0][0]=5
,其余数组元素都是0。
多维数组
在程序设计语言中,凡是二维以上的数组都可以称作多维数组。只要内存空间可用,就可以声明更多维数组来存取数据。在C语言中,要提高数组的维数,多加一组括号与下标值即可。定义语法如下:
数据类型 数组名[元素个数] [元素个数] [元素个数]… [元素个数];
下面引举几个C语言中声明多维数组的实例:
int Three_dim[2][3][4]; /*三维数组 */
int Four_dim[2][3][4][5]; /* 四维数组 */
基本上,三维数组和二维数组一样,可视为一维数组的扩展。例如,下面的程序片断中声明了一个2×2×2的三维数组,使用大括号可将其分为两个2×2的二维数组,同时设置初始值,并将数组中的所有元素使用循环输出:
int A[2][2][2]={ { {1,2},{5,6} },{ {3,4},{7,8} } };
int i,j,k;
for(i=0;i<2;i++) /* 外层循环 */
for(j=0;j<2;j++) /* 中层循环 */
for(k=0;k<2;k++) /* 内层循环 */
printf("A[%d][%d][%d]=%d\n",i,j,k,A[i][j][k]);
例如,声明一个单精度浮点数的三维数组:
float arr[2][3][4];
在设置初始值时,大家可以想象成要初始化两个3×4的二维数组,借助大括号将会更加清楚:
int arr[2][3][4]={ { {1,3,5,6}, /* 第一个3×4的二维数组 */
{2,3,4,5},
{3,3,3,3}
},
{ {2,3,3,54}, /* 第二个3×4的二维数组 */
{3,5,3,1},
{5 ,6,3,6}
}
};
下面的范例程序是为了加强大家对C语言中多维数组的应用与了解,请计算以下arr三维数组中所有元素值的总和,并将数据值为负数的元素都替换为0,再输出新数组的所有内容:
int arr[4][3][3]={ { {1,-2,3},{4,5,-6},{8,9,2} },
{ {7,-8,9},{10,11,12},{0.8,3,2} },
{ {-13,14,15},{16,17,18},{3,6,7} },
{ {19,20,21},{-22,23,24},(-6,9,12)} };
01 #include <stdio.h>
02 #include <stdlib.h>
03
04 int main()
05 {
06 int i,j,k,sum=0;
07
08 int arr[4][3][3]={ { {1,-2,3},{4,5,-6},{8,9,2} },
09 { {7,-8,9},{10,11,12},{0.8,3,2} },
10 { {-13,14,15},{16,17,18},{3,6,7} },
11 { {19,20,21},{-22,23,24},(-6,9,12)} };/* 声明并设置数组元素值 */
12
13 for(i=0;i<4;i++)
14 {
15 for(j=0;j<3;j++)
16 {
17 for(k=0;k<3;k++)
18 {
19 sum+=arr[i][j][k];
20 if (arr[i][j][k]<0)
21 arr[i][j][k]=0;/* 如果元素值为负数,就归零 */
22 printf("%d\t",arr[i][j][k]);
23 }
24 printf("\n");
25 }
26 printf("\n");
27 }
28 printf("---------------------------\n");
29 printf("原数组的所有元素值总和=%d\n",sum);
30 printf("---------------------------\n");
31
32 system("pause");
33 return 0;
34 }
- 第8~11行:声明并设置arr数组元素值。
- 第13、15、17行:由3层for循环进行运算。
- 第19行:将所有元素值累加到sum变量中。
- 第20~21行:如果元素值为负数,就将数据值重新设置为零。
- 第29行:输出所有元素值的总和。
字符串简介
在C语言中并没有字符串基本数据类型。与其他程序设计语言相比(如Visual Basic),C语言在字符串处理方面较为复杂。在C程序中存储字符串可以使用字符数组的方式,不过最后一个字符必须以空字符(\0)作为结尾。
字符串的使用
字符串声明的第一个重点就是必须使用空字符(\0)代表每一个字符串的结束,以下是C语言中常用的两种字符串声明方式:
方式1:char 字符串变量[字符串长度]="初始字符串";
方式2:char 字符串变量[字符串长度]={'字符1', '字符2', ...... ,'字符n', '\0'};
判断以下4种字符串声明方式是否合法:
char Str_1[6]="Hello";
char Str_2[6]={ 'H', 'e', 'l', 'l', 'o' , '\0'};
char Str_3[ ]="Hello";
char Str_4[ ]={ 'H', 'e', 'l', 'l', 'o', '!' };
其中,第一、二、三种方式中都是合法的字符串声明。虽然Hello只有5个字符,但是因为编译程序还必须加上\0字符,所以数组长度需声明为6,如果声明的长度不足,就可能造成编译上的错误。当然也可以选择不填入数组大小,让编译程序来自动分配内存空间(如第三种方式)。但是Str_4并不是字符串,因为最后一个字符并不是\0。
小技巧:在给字符串变量设置初始值时,如果字符串内容太长而无法在一行中设置完成,就可以将字符串拆成两个都用双引号括住的独立字符串,或者使用反斜杠(\)来连接两边的断点。例如:
char ch1[]="Could you tell me where the Tourist Information "
"Office is located";
/* 分成多个字符串,中间的空格与换行将不会影响字符串的连接*/
char ch2[]="Could you tell me where the Tourist Information\
Office is located";
/* 以反斜杠连接断点的两边 */
下面的范例程序将介绍4种字符串声明的方式,大家可以实际运行并比较其中的不同之处。
01 #include <stdio.h>
02 #include <stdlib.h>
03
04 int main()
05 {
06
07 /* 4种字符串声明与设置初值的方式 */
08 char Str_1[6]="Hello";
09 char Str_2[6]={ 'H', 'e', 'l', 'l','o','\0'};
10 char Str_3[ ]="Hello";
11 char Str_4[ ]={ 'H', 'e', 'l', 'l', 'o', '!' };
12
13
14 printf("%s\n",Str_1);
15 printf("%s\n",Str_2);
16 printf("%s\n",Str_3);
17 printf("%s\n",Str_4);
18
19 system("pause");
20 return 0;
21 }
- 第8~10行:合法的字符串声明方式。
- 第11行:声明的仅是一种字符数组,因为没有结尾字符(\0),不能算是字符串。
- 第17行:输出数据时,屏幕上会出现奇怪的符号。
字符串数组
由于字符串是以一维字符数组存储的,如果有许多关系相近的字符串集合,就称为字符串数组,并可以使用二维字符数组来表示。例如,一个班级中所有学生的姓名、每个姓名都是由许多字符所组成的字符串,这时就可以使用字符串数组存储。字符串数组声明方式如下:
char 字符串数组名[字符串数][字符数];
上式中字符串数用来表示字符串的个数,而字符数表示每个字符串的最大可存放字符数,并且包含(\0)结尾字符。当然也可以在声明时就设置初始值,不过要记得每个字符串元素都必须包含在双引号内,而且每个字符串间要以逗号(,)分开。语法格式如下:
char 字符串数组名[字符串数][字符数]={ "字符串1", "字符串2", "字符串3"…};
例如,声明名字为Name的字符串数组包含5个字符串,每个字符串都包括\0字符,字符串长度为10个字节:
char Name[5][10]={ "John",
"Mary",
"Wilson",
"Candy",
"Allen"
};
字符串数组虽然是二维字符数组,但是当我们要输出此Name数组中第二个字符串时,可以直接以printf(“%s”,Name[1])的方式打印输出一维数组。而要输出第二个字符串中的第一个字符时,仍然必须使用二维数组的输出方式,例如printf(“%s”,Name[1][0])。
使用字符串数组存储的坏处是每个字符串长度不会完全相同,而数组又属于静态内存分配,必须事先声明字符串中的最大长度,这样就可能造成内存的浪费。
下面的范例程序介绍字符串数组的应用,用来存储由用户输入的3位学生的姓名及每一位学生的3科成绩,并以横列方式输出每位学生的姓名、3科成绩及总分。
01 #include <stdio.h>
02 #include <stdlib.h>
03
04 int main()
05 {
06 char name[3][10],score[3][3];/* 声明存储姓名与成绩的数组 */
07 int i,total;
08
09 for(i=0;i<3;i++)
10 {
11 printf("请输入姓名及三科成绩:");
12 scanf("%s",&name[i]);/* 输入每一位学生的姓名 */
13 scanf("%d %d %d",&score[i][0],&score[i][1],&score[i][2]);
14 /* 输入3科成绩 */
15 }
16 printf("-------------------------------------\n");
17
18 for(i=0;i<3;i++)
19 {
20 printf("%s\t%d\t%d\t%d",name[i],score[i][0],score[i][1],score[i][2]);
21 total=score[i][0]+score[i][1]+score[i][2];/* 计算3科的总分 */
22 printf("\t%d\n",total);/* 输出3科的总分 */
23 }
24 printf("-------------------------------------\n");
25
26 system("pause");
27 return 0;
28 }
- 第6行:声明存储姓名与成绩的两个数组。
- 第12、13行:以scanf()函数输入每一位学生的姓名字符串与3科成绩。
- 第20行:直接以一维数组name[i]输出每位学生的名字。
- 第21行:计算3科成绩的总分。
字符串处理功能
由于字符串不是C语言的基本数据类型,因此如果大家要对字符串进行运算处理,就必须有一些特殊技巧。接下来介绍一些字符串的基本处理方法,包括计算字符串长度、复制、连接和搜索等方法,通过这些功能操作让大家更清楚C语言中字符串的相关应用。
下面的范例程序是计算一个输入字符串的长度,使用while循环从字符串中一个一个地取出字符来累加,直到遇到字符串的结尾字符(\0)才停止,最后输出累加结果。
01 #include<stdio.h>
02 #include<stdlib.h>
03
04 int main()
05 {
06 int length;/*用于计算字符串的长度*/
07 char str[30];/* 声明此字符串最多可存储30个字符*/
08
09 printf("请输入字符串:");
10 /*输入字符串*/
11 gets(str);
12 printf("输入的字符串为:%s\n",str);
13 length=0;
14 while (str[length]!='\0')
15 length++;
16 printf("此字符串有%d个英文字符\n",length);
17
18 system("pause");
19 return 0;
20 }
- 第6行:length变量用于计算字符串的长度。
- 第7行:声明此字符串最多可存储30个字符。
- 第11行:以gets()函数输入字符串,字符串中可以有空格。
- 第13行:声明length=0。
- 第14、15行:使用while循环,若当前字符不是结尾字符,则length变量累加1。
- 第16行:输出这个字符串的字符数。
下面的范例程序用于示范两个字符串的连接功能,也就是将S2字符串接到S1字符串后面。在这个程序中,我们使用record整数变量作为S3字符数组的下标值,再使用复制字符串的方法将S1、S2字符串复制到S3,得到新连接好的字符串。
01 #include <stdio.h>
02 #include <stdlib.h>
03
04 int main()
05 {
06 char S1[30];
07 char S2[30];
08 char S3[60];
09 int count,record;
10
11 printf("字符串 S1 的内容:");
12 gets(S1);
13 printf("字符串 S2 的内容:");
14 gets(S2);
15
16 record=0; /* 把整数变量 record 归 0,用来记录 S3 所指向的数组元素 */
17
18 for (count=0; S1[count] != '\0'; count++, record++) /* 将 S1 字符串复制到 S3 */
19 S3[record]=S1[count];
20
21 for (count=0; S2[count] != '\0'; count++, record++) /* 将 S2 字符串复制到 S3 */
22 S3[record]=S2[count];
23
24 S3[record]='\0';/* 字符串最后要加上 NULL 字符 */
25
26 printf("连接后的字符串 S3:%s", S3);/* 显示字符串连接后的结果 */
27 printf("\n"); /* 换行 */
28
29 system("pause");
30 return 0;
31 }
- 第8行:声明连接后的新字符串数组,首先要注意声明字符串数组的大小,如果串接后超过字符串声明的大小,编译程序就会自动清除后面连接的字符串。
- 第16行:把整数变量record的初始值设为0,用来记录S3所设置数组元素的下标值。
- 第18行:将S1字符串复制到S3。
- 第21行:将S2字符串复制到S3。
- 第24行:要在字符串最后加上NULL字符。
- 第26行:显示字符串连接后的结果。
字符串处理函数
字符串的处理功能除了可以自行设计外,其实在C语言的函数库中已经提供了相当多的字符串处理函数,只要我们在程序代码中包含string.h头文件,即可充分运用各种字符串函数的功能,本节中列举了一些实用的函数范例说明。
下面的范例程序使用strlwr()函数将字符串中的大写字母全部转换成小写字母,使用strcat()函数将str2字符串连接到str1字符串相关的使用方式如下:
strlwr(str);
strcat(str1,str2);
01 #include <stdio.h>
02 #include <stdlib.h>
03 #include <string.h>/* 包含 <string.h> 头文件 */
04
05 int main()
06 {
07
08 char str1[40];
09 char str2[40];
10
11 printf("请输入第一个字符串:");
12 gets(str1);
13 printf("请输入第二个字符串:");
14 gets(str2);
15
16 strcat(str1,str2);/* 将两个字符串连接起来 */
17 printf("%s\n",strlwr(str1));/* 将字符串内的大写字母转为小写字母 */
18
19 system("pause");
20 return 0;
21 }
- 第3行:包含
头文件,因此能够使用C语言的字符串相关函数。 - 第16行:使用strcat()函数将str2字符串连接到str1字符串。
- 第17行:将字符串内的大写字母转为小写字母。
下面的范例程序使用gets()函数与strlen()函数将所输入的字符串反向打印输出,建议大家使用字符数组的方式来处理,从最后一个元素往前逐一输出。strlen()函数的功能是输出字符串str的长度,使用方式如下:
strlen(str);
01 #include <stdio.h>
02 #include <stdlib.h>
03 #include <string.h>
04
05 int main()
06 {
07 char Word[40];/* 声明字符数组 */
08 int i,j=0;
09
10 printf("请输入字符串:");
11 gets(Word);
12
13 for(i=strlen(Word)-1;i>=0;i--)/*使用strlen()函数*/
14 printf("%c",Word[i]);/* 反向打印字符串 */
15
16 printf("\n");
17
18 system("pause");
19 return 0;
20 }
- 第7行:声明输入字符串变量最大长度的数组。
- 第11行:使用gets()函数,允许所输入的字符串中含有空格符。
- 第13、14行:使用C语言的库函数strlen()逐一反向打印输出字符。