所谓数组,就是相同数据类型的元素按一定顺序排列的集合,就是把有限个类型相同的变量用一个名字命名,然后用编号区分他们的变量的集合,这个名字称为数组名,编号称为下标。组成数组的各个变量称为数组的分量,也称为数组的元素。数组是在程序设计中,为了处理方便,把具有相同类型的若干变量按有序的形式组织起来的一种形式。这些按序排列的同类数据元素的集合称为数组。在C语言中字符串有着类似的一些特性,故在本章一并讨论。
数组
1、什么是数组
在程序设计中,为了处理方便,把具有相同类型的若干变量按有序的形式组织起来。这些按序排列的同类数据元素的集合称为数组。
在C语言中,数组属于构造数据类型。一个数组可以分解为多个数组元素,这些数组元素可以是基本数据类型或是构造类型。因此按数组元素的类型不同,数组又可分为数值数组、字符数组、指针数组、结构数组等各种类别。
每一个变量对应一块内存空间,
如果是整型int,则需要 4个字节的内存空间
如果是字符型char,则需要 1个字节的内存空间
数组是一组具有相同数据类型的元素的集合
假设现在我需要10个int型数据,那么就需要10个int的内存空间
下面的定义:
int a[10];
就在内存中分配了10个int的空间,共有10*4 = 40个字节,并为它们起了一个名字叫a
我们把这样的一组数据的集合称为数组(Array),它所包含的每一个数据叫做数组元素(Element),所包含的数据的个数称为数组长度(Length)。
例如int a[10];就定义了一个长度为10的整型数组,名字是a。
数组中的每个元素都有一个序号,这个序号从0开始,而不是从我们熟悉的1开始,称为下标(Index)。使用数组元素时,指明下标即可,形式为:
arrayName[index]
例如:int a[10] 对于这个数组,
数组下标为 a[0]、a[1]、a[2]、a[3]、a[4]、a[5]、a[6]、a[7]、a[8]、a[9] 共10个,a[9]是最后一个,没有a[10]
a[0] = 20;
a[4] = 10;
a[9] = 30;
2、一维数组的定义与使用
所谓一维数组就是指数组定义的时候数组名后面只有一个下标的数组,这里只有一个下标的意思是说数组名后面只有一个[], 比如 int a[10],而像int a[2][3]有多个[]的就不叫一维数组了。
2.1一维数组的定义方式:
dataType arrayName[length];
dataType 为数据类型,arrayName 为数组名称,length 为数组长度。例如:
float m[12]; // 定义了一个有12个元素的浮点型数组,每个元素都是浮点型
char ch[9]; // 定义了一个有9个元素的字符型数组,每个元素都是字符型
需要注意的是:
1) 数组中每个元素的数据类型必须相同,对于int a[10];,每个元素都必须为 int。
2) 数组长度 length 最好是整数或者常量表达式,不能是变量。例如 10、20*4 等,这样在所有编译器下都能运行通过;如果 length 中包含了变量,例如 n、4*m等,在某些编译器下就会报错。
3) 访问数组元素时,下标的取值范围为 0 ≤ index < length,过大或过小都会越界,导致数组溢出,发生不可预测的情况。
4) 数组是一个整体,它的内存是连续的,下面是inta[10];的内存示意图:
a[0] | a[1] | a[2] | a[3] | a[4] | a[5] | a[6] | a[7] | a[8] | a[9] |
a
数组名a代表的是整个数组,数组中的元素是通过下标来获取的
5)可以通过sizeof(数组名)来获取数组所占空间大小,通过sizeof(数组名)/sizeof(数组类型)获得数组的长度
printf("%d\n",sizeof(a)); printf("%d\n",sizeof(a)/sizeof(int)); |
2.2一维数组的初始化
数组元素的使用:
//给数组元素赋值 for(i=0;i<10;i++) { a[i]=i; //a[0]=0;a[1]=1;a[2]=2; .... a[9]=9 }
//打印数组 for(i=0;i<10;i++) { printf("%d\n",a[i]); } |
一维数组初始化:
全部初始化: inta[10]={20, 1, 2, 3, 4, 5, 6, 7, 8, 9};//定义数组的同时对数组元素进行赋值,以逗号进行分割 inta[]={1,2,3}; //给全部元素赋值,那么在数组定义时可以不给出数组的长度 |
部分初始化: inta[10]={2,5,6};//a[0]=2,a[1]=5,a[2]=6,剩余的元素值都是0 |
初始化为0: inta[10]={0}; //将数组所有元素都设为0
inti; for(i=0;i<10;i++) { a[i]=0;//通过循环遍历数组的元素,将每个元素的值置为0 } |
通过外部对数组元素赋值: for(i=0;i<10;i++) { scanf("%d",&a[i]); //使用数组元素要用下标,通过循环对每一个元素赋值 } |
2.3 一维数组的使用
2.3.1求数组中最大元素的值
inta[10]={0,3,4,89,8,23,44,7,11,10};
inti; intmax=a[0]; for(i=1;i<10;i++) { if(max<a[i]) { max=a[i]; } } printf("%d\n",max); |
思考:求最小元素的值和其对应的下标
2.3.2求数组元素的和
inta[10]={0,3,4,89,8,23,44,7,11,10};
intsum=0; inti; for(i=0;i<10;i++) { sum+=a[i]; }
printf("%d\n",sum); |
思考:
1、 打印数组中大于平均值的所有元素的值和对应的下标
2、 求数组中所有奇数元素的和
3、 求数组中所有偶数元素的和
2.3.3数组逆序
使用for循环: inta[10]={0,3,4,89,8,23,44,7,11,10};
inti; for(i=0;i<10/2;i++) { inttmp=a[i]; a[i]=a[10-i-1]; a[10-i-1]=tmp; } |
使用while循环: intmin=0;//数组最笑下标 intmax=9;//数组最大下标
while(min<max) { inttmp=a[min]; a[min]=a[max]; a[max]=tmp; min++; max--; } |
2.3.4分别统计0~n有多少个0~9
inta[10]={0};
intn=100; inti; a[0]=1; for(i=1;i<=n;i++) { intnum=i; while(num) { a[num%10]++; num=num/10; } } |
2.3.5数组排序:冒泡与选择排序
1、冒泡排序过程:
(1)比较第一个数与第二个数,若为逆序a[0]>a[1],则交换;然后比较第二个数与第三个数;依次类推,直至第n-1个数和第n个数比较为止——第一趟冒泡排序,结果最大的数被安置在最后一个元素位置上
(2)对前n-1个数进行第二趟冒泡排序,结果使次大的数被安置在第n-1个元素位置
(3)重复上述过程,共经过n-1趟冒泡排序后,排序结束
inta[10]={0,3,4,89,8,23,44,7,11,10}; inti,j; //外层循环控制排序的次数 for(i=0;i<9;i++) { //内存循环进行每一次排序的内部数据比较 for(j=0;j<9-i;j++) { if(a[j]>a[j+1]) { inttmp=a[j]; a[j]=a[j+1]; a[j+1]=tmp; } } } for(i=0;i<10;i++) { printf("%d\n",a[i]); } } |
2、选择排序过程:
(1)首先通过n-1次比较,从n个数中找出最小的, 将它与第一个数交换—第一趟选择排序,结果最小的数被安置在第一个元素位置上
(2)再通过n-2次比较,从剩余的n-1个数中找出关键字次小的记录,将它与第二个数交换—第二趟选择排序
(3)重复上述过程,共经过n-1趟排序后,排序结束
//外层循环控制排序的次数 for(i=0;i<9;i++) { //找出这一轮比较中数值最小的元素的下标 intmin=i; for(j=i+1;j<10;j++) { if(a[j]<a[min]) { min=j; } }
//如果下标与原来的下标不一样,表示有一个数比数组第i个元素要小,需要进行交换 if(min!=i) { inttmp=a[i]; a[i]=a[min]; a[min]=tmp; } } |
3、二维数组
一维数组可以看作是一行连续的数据,只有一个下标,称为一维数组。在实际问题中有很多数据是二维的或多维的,因此C语言允许构造多维数组。多维数组元素有多个下标,以确定它在数组中的位置
3.1二维数组的定义方式
二维数组定义的一般形式是:
dataType arrayName[length1][length2];
其中,dataType 为数据类型,arrayName 为数组名,length1 为第一维下标的长度,length2 为第二维下标的长度。例如:
int a[3][4];
定义了一个3行4列的数组,共有3×4=12个元素,数组名为a,即:
a[0][0], a[0][1], a[0][2], a[0][3] ==> a[0]
a[1][0], a[1][1], a[1][2], a[1][3] ==> a[1]
a[2][0], a[2][1], a[2][2], a[2][3] ==> a[2]
在二维数组中,要定位一个元素,必须给出一维下标和二维下标,就像在一个平面中确定一个点,要知道x坐标和y坐标。例如,a[3][4] 表示a数组第4行第5列的元素。
二维数组在概念上是二维的,但在内存中地址是连续的,也就是说各个元素是相互挨着的。那么,如何在线性内存中存放二维数组呢?有两种方式:一种是按行排列,即放完一行之后再放入第二行。另一种是按列排列, 即放完一列之后再放入第二列。
在C语言中,二维数组是按行排列的。也就是先存放a[0]行,再存放a[1]行,最后存放a[2]行;每行中的四个元素也是依次存放。数组a为int类型,每个元素占用4个字节,整个数组共占用4×(3×4)=48个字节。
3.2二维数组的初始化
1、二维数组的初始化可以按行分段赋值,也可按行连续赋值。
例如对数组a[5][3],按行分段赋值可写为:
int a[5][3]={ {80,75,92}, {61,65,71}, {59,63,70}, {85,87,90}, {76,77,85}};
按行连续赋值可写为:
int a[5][3]={80, 75, 92, 61, 65, 71, 59, 63, 70, 85, 87, 90, 76, 77, 85};
这两种赋初值的结果是完全相同的。
2、可以只对部分元素赋初值,未赋初值的元素自动取0值。例如:
int a[3][3]={{1},{2},{3}};
是对每一行的第一列元素赋值,未赋值的元素取0值。 赋值后各元素的值为:
1 0 0
2 0 0
3 0 0
3、如对全部元素赋初值,则第一维的长度可以不给出。例如:
int a[3][3]={1,2,3,4,5,6,7,8,9};
可以写为:
int a[][3]={1,2,3,4,5,6,7,8,9};
4、 二维数组可以看作是由一维数组嵌套而成的,把一维数组的每个元素看作一个数组,就组成了二维数组。当然,前提是各元素类型必须相同。根据这样的分析,一个二维数组也可以分解为多个一维数组,C语言允许这种分解。
如二维数组a[3][4],可分解为三个一维数组,其数组名分别为:a[0]、a[1]、a[2]。
对这三个一维数组不需另作说明即可使用。这三个一维数组都有4个元素,例如:一维数组a[0]的元素为a[0][0], a[0][1], a[0][2], a[0][3]。必须强调的是,a[0], a[1], a[2]不能当作下标变量使用,它们是数组名,不是一个单纯的下标变量。
3.3 二维数组的使用
1、赋值与输出
//二维数组的赋值 for(i=0;i<2;i++) { for(j=0;j<3;j++) { a[i][j]=i*3+j; } }
//二维数组的打印 for(i=0;i<2;i++) { for(j=0;j<3;j++) { printf("%4d",a[i][j]); } printf("\n"); } |
2、求二维数组的行列和,先输出每一行的值,在输出每一列的值
inta[3][5]={{1,2,3,4,5},{23,56,33,55,78},{11,16,78,54,89}};
//先输出每一行的和 inti,j;
//外层循环遍历有多少行 for(i=0;i<3;i++) { intsum=0; //内层循环计算每一行的值 for(j=0;j<5;j++) { sum+=a[i][j]; }
printf("%8d",sum); //打印每一行的和 }
printf("\n");
//输出每一列的和 //外层循环遍历有多少列 for(j=0;j<5;j++) { intsum=0; //内层循环计算每一列的值 for(i=0;i<3;i++) { sum+=a[i][j]; }
printf("%8d",sum); //打印每一行的和 } |
4、字符数组
用来存放字符的数组称为字符数组
4.1 字符数组定义
chara[10]; //一维字符数组 charb[5][10]; //二维字符数组 |
字符数组实际上是一系列字符的集合,也就是字符串(String)。在C语言中,没有专门的字符串变量,没有string类型,通常就用一个字符数组来存放一个字符串。
4.2 字符数组初始化
C语言规定,可以将字符串直接赋值给字符数组,例如:
charstr[30]={"helloworld"}; charstr[30]="helloworld"; //这种形式更加简洁,实际开发中常用 |
在C语言中,字符串总是以'\0'作为串的结束符。上面的两个字符串,编译器已经在末尾自动添加了'\0'。
'\0'是ASCII码表中的第0个字符,用NULL表示,称为空字符。该字符既不能显示,也不是控制字符,输出该字符不会有任何效果,它在C语言中仅作为字符串的结束标志。
4.3字符串的输入输出
puts 和 printf 在输出字符串时会逐个扫描字符,直到遇见 '\0' 才结束输出
printf 用 %s 输出字符串
inti; charstr1[30]="hello"; charstr2[]="abcdef"; charstr3[30]="Youareagood\0boy!"; printf("str1:%s\n",str1); printf("str2:%s\n",str2); printf("str3:%s\n",str3); return0; |
用scanf用%s进行字符串的输入, 遇空格或回车结束
chars[10]={0}; scanf("%s",s);//"%s"的作用就是输入一个字符串的,scanf是以回车键作为输入完成标示的,但回车键本身并不会作为字符串的一部分 //如果scanf参数中的数组长度小于用户在键盘输入的长度,那么scanf就会缓冲区溢出,导致程序崩溃 inti; for(i=0;i<10;i++) { printf("%d\n",s[i]); } printf("----------------------------------\n"); printf("%s\n",s); return0; |
4.4字符数组的使用
4.4.1、将字符串逆置
charstr[100]="helloworld"; intlen=0; while(str[len++]);//求出字符串长度,这里的长度包含了\0比实际字符串长度大1 intmin=0; intmax=len-2; while(min<max) { chartmp=str[min]; str[min++]=str[max]; str[max--]=tmp; } printf("%s\n",str);
|
4.4.2、中文字符串的逆置
charstr[100]="你好世界";
intlen=0; while(str[len++]);//求出字符串长度,这里的长度包含了\0比实际字符串长度大1
intmin=0; intmax=len-2; while(min<max) { chartmp=str[min]; str[min]=str[max-2]; str[max-2]=tmp;
tmp=str[min+1]; str[min+1]=str[max-1]; str[max-1]=tmp;
tmp=str[min+2]; str[min+2]=str[max]; str[max]=tmp;
min+=3; max-=3; } |
4.4.3、统计中英混合字符串长度:
charstr[]="abc你好世界"; intlen=0; inti=0; while(str[i]) { if(str[i]<0) i+=2; i++; len++; }
|
4.4.4、去掉字符串右边的空格
charstr[100]="helloworld "; intlen=0; while(str[len++]); len--; inti; //字符串从后往前比较,找到第一个非空格的字符,将其后面的字符置为\0 for(i=len-1;i>=0;i--) { if(str[i]!='') { str[i+1]='\0'; break; } } printf("<%s>\n",str);
|
4.4.5去掉字符串左边的空格
charstr[100]=" helloworld";
intlen=0; while(str[len++]==''); //得到字符串前面有多少个空格 len--;
//将字符串往前移动len个位置 inti=len; while(str[i]) { str[i-len]=str[i]; i++; } str[i-len]='\0'; //给字符串添加结束标志
printf("<%s>\n",str); |
4.5、字符串处理函数
4.5.1 gets函数
gets()函数和scanf一样可以从键盘获取字符串,但是gets函数只认为回车是输入结束标志,空格不会作为结束标志
chars[100]={0}; gets(s);//gets认为回车是输入结束标示,空格不是输入结束标示,所以用gets这个函数就可以实现输入带空格的字符串 //gets和scanf一样存在缓冲区溢出的问题
inti; for(i=0;i<10;i++) { printf("%d\n",s[i]); } printf("----------------------------------\n"); printf("%s\n",s); |
gets不能用类似“%s”或者“%d”之类的字符转义,只能接受字符串的输入
4.5.2 fgets函数
gets函数不检查预留缓冲区是否能够容纳用户实际输入的数据。多出来的字符会导致内存溢出,fgets函数改进了这个问题。
由于fgets函数是为读取文件设计的,所以读取键盘时没有gets那么方便
chars[10]={0}; //第一个参数是char的数组,第二个参数是数组的大小,单位:字节, //第三个参数stdin代表标准输入的意思 //fgets是安全的,不存在缓冲区溢出的问题,只要保证第二个参数小于等于数组实际的大小,就能避免缓冲区溢出问题 // fgets会将回车键读到数组里 fgets(s,sizeof(s)/sizeof(char),stdin);
printf("%s\n",s); |
4.5..3 puts函数
puts函数打印字符串,与printf不同,puts会在最后自动添加一个’\n’
char s[] = "hello world"; puts(s); |
4.5.4 fputs函数
fputs是puts的文件操作版本,
char s[] = "hello world"; fputs(s, stdout); |
4.5.5 strlen函数 得到字符串长度
size_t strlen(const char * _Str);
返回不包含字符串结尾’\0’的字符串长度
char s[100] = "hello world"; int len = strlen(s);//得到字符串长度,返回一个字符串中有效字符的数量(不包含字符串结尾的0) printf("len = %d\n", len); return 0; |
4.5.6 strcat字符串追加
size_t strcat(char * _Str1, const char * _Str2);
将参数_Str2追加到_Str1后尾
char s[1024] = "hello world"; int len = strlen(s);//得到字符串长度,返回一个字符串中有效字符的数量(不包含字符串结尾的0) printf("len = %d\n", len); char s1[100] = "abc123456789"; strcat(s, s1);//将两个字符串合并,结果放入第一个参数里面,strcat也存在缓冲区溢出的问题 printf("%s\n", s); |
4.5.7 strncat字符串有限追加
size_t strncat(char * _Str1, const char * _Str2, size_tlen);
4.5.8 strcmp字符串比较
int strcmp(const char * _Str1, const char * _Str2);
比较两个字符串是否相等,相等返回0,不等返回非0
4.5.9 strncmp字符串有限比较
4.5.10 strcpy 字符串拷贝
char*strcpy(char *_Str1, const char * _Str2);
将参数_Str2拷贝到参数_Str1中
4.5.11 strncpy字符串有限拷
4.5.12 sprintf,格式化字符串
和printf函数功能类似,printf函数将格式化结果输出到屏幕,sprintf将格式化结果输出到字符串
4.5.13 sscanf函数
sscanf类似于scanf函数,,scanf从键盘读取用户输入,scanf从指定格式化字符串读取输入
4.5.14 strchr查找字符
char * strchr(char * _Str, int _Ch);
在参数_str中查找参数_Ch指定字符,找到返回字符_Ch在_Str中所在位置,没有找到返回NULL;
4.5.15 strstr查找子串
char * strstr(char * _Str, const char * _SubStr)
在参数_str中查找参数_SubStr指定子串,找到返回子串在_Str中所在位置,没有找到返回NULL;
4.5.16 strtok分割字符串
字符在第一次调用时strtok()必需给予参数s字符串,往后的调用则将参数s设置成NULL每次调用成功则返回指向被分割出片段的指针
char buf[] = "abc@defg@igk"; char *p = strtok(buf, "@"); while (p) { printf("%s\n", p); p = strtok(NULL, "@"); } |
4.5.17 atoi转化为int
需要包含头文件stdlib.h