280 likes | 367 Views
实验七 字符串编程. 一、实验目的. 掌握字符串的概念 理解字符数组和字符指针的区别 熟悉常用的字符串库函数的用法 掌握字符指针变量和字符指针数组作函数参数的方法. 二、实验内容. 1 、字符串基本概念 在 C 语言中一串以 ’ ’ 结尾的字符被看作是字符串。当 C 语言编译器在程序中遇到长度为 n 的字符串字面量时,它会为字符串字面量分配长度为 n+1 的内存空间。这块内存空间将用来存储字符串字面量中的字符,以及一个额外的字符 —— 空字符。空字符用来标志字符串的末尾。空字符是 ASCII 字符集中真正的第一个字符,因此它用转义序列 来表示。.
E N D
一、实验目的 • 掌握字符串的概念 • 理解字符数组和字符指针的区别 • 熟悉常用的字符串库函数的用法 • 掌握字符指针变量和字符指针数组作函数参数的方法
二、实验内容 • 1、字符串基本概念 • 在C语言中一串以’\0’结尾的字符被看作是字符串。当C语言编译器在程序中遇到长度为 n 的字符串字面量时,它会为字符串字面量分配长度为 n+1 的内存空间。这块内存空间将用来存储字符串字面量中的字符,以及一个额外的字符——空字符。空字符用来标志字符串的末尾。空字符是 ASCII 字符集中真正的第一个字符,因此它用转义序列\0 来表示。
既然字符串字面量是作为数组来存储的,那么编译器会把它看作是char * 类型的指针。例如,printf函数和scanf函数都接收char *类型的值作为它们的第一个参数。思考下面的例子: printf("abc"); • 当调用 printf函数时,会传递"abc"的地址(即指向字母 a 存储单元的指针)。
a)字符串字面量与字符常量 • 只包含一个字符的字符串字面量不同于字符常量。字符串字面量"a"是用指针来表示的,这个指针指向存放字符"a"(以及随后的空字符)的内存单元。字符常量'a'是用整数(字符的ASCII 码)来表示的。 • 不要在需要字符串的时候使用字符(或者反之亦然)。 下面的函数调用是合法的: printf("\n"); • 因为 printf函数期望指针作为它的第一个参数。然而,下面的调用却是非法的: printf('\n'); /*** WRONG ***/
b)字符串变量 • 假设需要变量用来存储最多 80 个字符的字符串。既然字符串会在末尾处需要空字符,那么要声明的变量是含有 81 个字符的数组: #define STR_LEN 80 [惯用法] char str[STR_LEN+1]; • 注意这里把 STR_LEN定义为 80 而不是 81,因此强调的事实是 str 可以存储最多 80 个字符的字符串。对宏加一的这种方法是C程序员常用的方式。 • 当声明用于存放字符串的字符数组时,始终要保证数组的长度比字符串的长度多一个字符。这是因为 C 语言规定每个字符串都要以空字符结尾。如果没有给空字符预留位置,可能会导致程序运行时出现不可预知的结果,因为 C 函数库中的函数假设字符串都是以空字符结束的。
c)初始化字符串变量 • 字符串变量可以在声明时进行初始化: char date1[8] = "June 14"; • 编译器将把字符串"June 14"中的字符复制到数组 date1中,然后追加一个空字符从而使 date1可以作为字符串使用。虽然"June 14"看起来是字符串字面量,但其实不然。C编译器会把它看成是数组初始化式的缩写形式。实际上,我们可以写成: char date1[8] = {'J', 'u', 'n', 'e', ' ', '1', '4', '\0'}; • 相信大家都会认同原来的方式看起来更便于阅读。 • 如果初始化式比字符串变量长时又会怎样呢?这对字符串而言是非法的,就如同对数组是非法的一样。然而,C语言允许初始化式(不包括空字符)与变量有完全相同的长度: char date3[7] = "June 14"; • 编译器把初始化式中的字符简单地复制到date3 中,没有空间给空字符,所以编译器也不会试图存储一个空字符。
如果计划初始化用来放置字符串的字符数组,一定要确保数组的长度要长于初始化式的长度。否则,编译器将忽略空字符,这将使得数组无法作为字符串使用。如果计划初始化用来放置字符串的字符数组,一定要确保数组的长度要长于初始化式的长度。否则,编译器将忽略空字符,这将使得数组无法作为字符串使用。 • 字符串变量的声明可以忽略它的长度。这种情况下,编译器会自动计算长度: char date4[] = "June 14"; • 编译器为 date4 分配 8个字符的空间,这足够存储"June 14"中的字符和一个空字符。(事实是date4 的长度没有指明不意味着稍候可以改变数组的长度。一旦编译了程序,那么 date4的长度就固定是 8 了。)如果初始化式很长,那么忽略字符串变量的长度是特别有效的,因为手工计算长度很容易出错。
d)字符串字面量的操作 • 通常情况下可以在任何 C语言允许使用 char *指针的地方使用字符串字面量。例如,字符串字面量可以出现在赋值运算符的右边: char *p; p = "abc"; • 这个赋值操作不是复制"abc"中的字符,而仅仅是使 p 指向字符串的第一个字符。 • 允许改变字符串字面量中的字符,但是不推荐这么做: char *p = "abc"; *p = 'b'; /* string literal is now "bbc" */ • 对于一些编译器而言,改变字符串字面量可能会导致程序运行异常。
e)字符数组与字符指针 • 一起来比较一下下面两个声明: char date[] = "June 14"; • 它声明 date 是个字符数组。和这个声明相似的是下面这个声明: char *date = "June 14"; • 它声明 date 是个指向字符串字面量的指针。正因为有了数组和指针之间的紧密关系,才使上面两个声明中的 date都可以作为字符串。尤其是,任何期望传递字符数组或字符指针的函数都将接收这两种声明的date作为参数.
然而,需要注意,不能错误地认为上面两种 date可以互换。两者之间有着显著的差异: • 在声明为数组时,就像任意数组元素一样,可以修改存储在 date 中的字符。在声明为指针时,date 指向字符串字面量,从第4小节知是不可以修改字符串字面量的。 • 在声明为数组时,date 是数组名。在声明为指针时,date是变量,这个变量可以在程序执行期间指向其他字符串。
如果需要可以修改的字符串,那么就要建立字符数组用来存储字符串。这时声明指针变量是不够的。如果需要可以修改的字符串,那么就要建立字符数组用来存储字符串。这时声明指针变量是不够的。 • 下面的声明使编译器为指针变量分配了足够的内存空间: char *p; • 可惜的是,它不为字符串分配空间。(这怎么可能呢?因为我们没有指明字符串的长度。)在使用 p 作为字符串之前,必须把 p 指向字符数组。一种可能是把 p 指向已经存在的字符串变量: char str[STR_LEN+1], *p; p = str; • 现在 p 指向了 str 的第一个字符,所以可以把p 作为字符串使用了。
使用未初始化的指针变量作为字符串是非常严重的错误。考虑下面的例子,它试图创建字符串"abc": char *p; p[0]='a'; /*** WRONG ***/ p[1]='b'; /*** WRONG ***/ p[2]='c'; /*** WRONG ***/ p[3]='\0'; /*** WRONG ***/ • 因为 p 没有初始化,所以我们不知道它指向哪里。把字符 a、b、c 和\0 写入 p所指向的内存将会对程序产生无法预期的影响。程序可能没有错误继续运行,或者可能崩溃,或者可能行为异常。
2、字符串的读/写 • 使用printf函数或 puts 函数来写字符串是很容易的。读入字符串却有点麻烦,主要是因为输入的字符串可能比用来存储的字符串变量更长。为了一次性读入字符串,可以使用scanf函数或 gets函数,也可以以每次一个字符的方式读入字符串。
a)用 printf 函数和 puts 函数写字符串 • 转换说明%s 允许 printf函数写字符串。参考下面的例子: char str[] = "Are we having fun yet?" printf("Value of str: %s\n", str); • 输出会是 Value of str: Are we having fun yet? • printf函数会逐个写字符串中的字符直到遇到空字符才停止。(如果空字符丢失,printf函数会越过字符串的末尾继续写,直到最终在内存的某个地方找到空字符为止。) • 如果只显示字符串的一部分,可以使用转换说明%.ps。这里的 p是要显示的字符数量。语句 printf("%.6s\n", str); • 会显示出:Are we
就像数一样,字符串可以在指定域内显示。 转换说明%ms 会在大小为 m的域内显示字符串。(对于超过 m个字符的字符串,printf函数会显示出整个字符串,而不会截断。)如果字符串少于 m个字符,则会在域内右对齐输出。为了强制左对齐,可以在m前加一个减号。m值和p值可以组合使用:转换说明%m.ps 会使字符串的前 p 个字符在大小为 m的域内显示。 • printf函数不是唯一一个字符串输出函数。C函数库还提供 puts 函数。此函数可以按如下方式使用: puts(str); • puts 函数只有一个参数, 此参数就是需要显示的字符串, 参数中没有格式串。 在写完字符串后,puts 函数总会添加一个额外的换行符,因此显示会移至下一输出行的开始处。
输入并运行以下程序,观察程序运行结果。参考程序如下:输入并运行以下程序,观察程序运行结果。参考程序如下: #include <stdio.h> void main() { int i; char string1[5]={'A','B','C','D','E'}; char string2[6]={'A','B','C','D','E','\0'}; char string3[6]="ABCDE\0"; char string4[5]="ABCDE"; printf("string1= \"%s\"\n",string1); //第9行 printf("string2= \"%s\"\n",string2); printf("string3= \"%s\"\n",string3); printf("string4= \"%s\"\n",string4); } • 对于上面的程序,将第9行改为:for (i=0;i<5;i++) printf("c",string1[i]); 运行修改后的程序,观察显示的结果。试分析其中的原因。
b)逐个字符读字符串 • 对许多程序而言,因为 scanf函数和gets函数都有风险且不够灵活,C程序员经常会编写自己的输入函数。通过每次一个字符的方式来读入字符串,这类函数可以提供比标准输入函数更大程度的控制。 • 如果决定设计自己的输入函数,那么就需要考虑下面这些问题: • 在开始存储字符串之前,函数应该跳过空白字符吗? • 什么字符会导致函数停止读取:换行符、任意空白字符、还是其他一些字符?需要存储这类字符还是忽略掉? • 如果输入的字符串太长以致无法存储,那么程序应该做些什么:忽略额外的字符,还是把它们留给下一次的输入操作?
假定需要的函数不会跳过空白字符,在第一个换行符处(不把换行符存储到字符串中)停止读取,并且忽略额外的字符。函数将有如下原型: int read_line(char str[], int n); • str 表示用来存储输入的数组,而且 n 是最大读入字符的数量。如果输入行包含多于 n 个的字符,read_line 函数将忽略多余的字符。read_line 函数会返回实际存储在str中的字符数量(在 0到 n 之间的任意数)。不可能总是需要 read_line 函数的返回值,但是有这个返回值也没问题。
read_line函数主要由一个循环构成,只要str 留有空间,那么此循环就逐个读入字符并且把它们存储起来。在读入换行符时循环终止。下面是 read_line 函数的完整定义: int read_line(char str[], int n) { char ch; int i = 0; while ((ch = getchar()) ! = '\n') { if (i < n) { str[i++] = ch; } } str[i] = '\0' /* terminates string */ return i; /* number of characters stored */ }
返回之前,read_line 函数在字符串的末尾放置了一个空字符。就像 scanf函数和 gets函数一样,标准函数会自动在输入字符串的末尾放置一个空字符。然而,如果你正在写自己的输入函数,那么必须要考虑这一点。 • 3、常用的字符串处理函数包括: • 字符串连接函数strcat(str1,str2), • 字符串复制函数strcpy(str1,str2), • 字符串比较函数strcmp(str1,str2), • 测试字符串长度函数strlen(str)等等。
以下程序是用字符数组编程实现字符串的拷贝。参考程序如下:以下程序是用字符数组编程实现字符串的拷贝。参考程序如下: /******************************************** 函数功能:字符串拷贝 函数参数:字符型数组srcStr, 存储源字符串 字符型数组dstStr, 存储目的字符串 函数返回值:无 **********************************************/ void mystrcpy(char dstStr[ ], char srcStr[ ]) { int i=0; while(srcStr[i]!='\0') { dstStr[i]=srcStr[i]; i++; } dstStr[i]='\0'; } • 将上述函数改写成用字符指针变量实现。比较两者的区别。
4、录入并运行以下程序,查看运行结果: #include <stdio.h> void main() { int i; char as[]="I am happy"; //第5行:初始化字符数组 char *ps="I am happy"; //第6行:初始化字符指针 printf("The as string is:"); for(i=0;i<11;i++) { printf("%c",*(as+i)); //第9行:字符数组用指针法输出 } printf("\n"); printf("The ps string is:"); for(i=0;i<11;i++) { printf("%c",*(ps+i)); //第13行:字符指针用指针法输出 } printf("\n"); } • 若将第5行和第6行修改为:char *as="I am happy"; 程序是否还能运行?为什么? • char ps[]="I am happy"; • 若将第5行修改为:char as[]="I am happy"; 第9行改为:printf("%s",as); 程序运行后会出现什么结果?说明了什么?
5、录入并运行以下程序,查看运行结果。 #include <stdio.h> char *Strcpy(char *dst,char *scr) { char *p; p=dst; while(*(p++)=*(scr++)); return dst; } void main() { char str1[80],str2[80]; printf("\nPlease enter a string:"); gets(str1); printf("\nThe str2 is %s",Strcpy(str2,str1)); } • 运行后,该程序的结果是什么?这是一个返回指针值的函数,该程序的功能是什么?
6、观察下面的程序,其中有两处错误,请找出出错的行数和原因,并修改错误。6、观察下面的程序,其中有两处错误,请找出出错的行数和原因,并修改错误。 #include <stdio.h> void main() { char *name; char msq[10]; printf("What's your name?\n"); scanf("%s",&name); msq="Hello"; printf("%s %s \n",msq,name); }
三、编程题 • 1.主函数定义一个存放各种编程语言的字符指针数组char *language[],数组的长度定义为常量N,要求字符串按字典顺序排序。编写下列两个函数:数组排序函数sort(char *language[]),数组输出函数output(char *language[]),用字符指针变量作参数。字符串为”PASCAL”, “BASIC”, “C/C++”, ”Fortran”, “Turbo C”, “JAVA”。
2.有一篇文章,共有3行文字,每行有80个字符。要求分别统计出其中英文大写字母、小写字母、数字、空格以及其他字符的个数。
本节课完! 谢谢!