第6题  统计字符串中字母a、b、c、d、e的出现次数

在本题程序MODI1.C中,函数fun通过字符指针形参s接收一个字符串、通过整型指针形参num接收一个长度为5的整型数组,统计字符串中字母a、b、c、d、e(不区分大小写)的出现次数,并分别存储于num[0]、num[1]、num[2]、num[3]、num[4]中。

例如:若接收文本行为″TheCLanguageIsGood″,字母a、b、c、d、e(不区分大小写)的出现次数分别为2、0、1、1、2。

请改正程序中的错误,使之得出正确结果。                                                                注意:不许改动 main函数,不许增行或删行,也不许更改程序结构。

MODI1.C:

MODI6-01

参考答案:

MODI6-02

运行结果:

MODI6-03

程序解析:

解答此题也不难,不必特别细读程序。我们明确知道错误位置,一定在标志行/********found********/下面紧邻的行内,先看第1个错误:

for (k = 0; k < i; ++k)
­      /********found********/
­­      num[i] = 0;

如果能够看出:在for循环过程中,k是循环索引变量,i的值为5且保持不变,就应该知道该循环的功能是对数组num进行清0,据此可知,应将num[i] = 0;中的i改为k。如果不作改变,就是对num[5]重复清零5次,是很怪异的。再看第2个错误:

for ( ; ∗s; ++s) {
­      /********found********/
­      switch (s) {

如果能明白:for循环是指针s对字符串的扫描过程,那么switch语句应根据指针s所指字符而非指针值本身进行跳转,因此应将switch (s) { 中的s改为∗s。因为s是指针,∗s才是s所指向的字符。要理解程序,请继续读下去。

编译预处理指令#include <stdio.h>让预处理器用头文件stdio.h的内容替换#include <stdio.h>这一行,这叫做文件包含,用来包含标准函数库的有关信息,以支持printf、scanf等一族库函数的调用。程序要调用某个库函数,就必须包含与之对应的头文件。文件包含指令通常位于源程序开头处,而且要单独占一行、结尾无标点。

程序从函数main开始执行。声明:

char s[N];
int  i, num[5];

定义长度为N即100的字符数组s,s用于接收输入的字符串;定义整型变量i,用作循环索引变量;定义整型数组num,用作计数5个字母出现次数的计数器。对于这些变量,均分配存储,未作初始化。凡未经初始化的变量,其初值无意义,仅反映新分配内存的当前随机性物理状态。至此,当前存储状态如下图所示,变量的无意义垃圾值以空白表示。注意:数组名被定义为指向数组首元素的常量指针,常量指针只供引用但不能被赋值,其存储空间是在有关表达式计值过程中临时自动分配的,并不是数组存储块的组成部分。因此s是字符指针,num是整型指针。由于机器内存是一维连续字节序列的本性,数组元素顺次占据一段内存。数组下标从0开始,例如:s和num都是指向第0元素的指针,通过s[i]或∗(s+i)可访问数组s的第i元素,通过num[i]或∗(num+i)可访问数组num的第i个元素。若p为字符指针,则通过∗p可访问p所指向的字符。

MODI6-04

首先,执行下列语句:

printf(″请输入一串字符:\n″);
gets(s);

printf语句在标准输出设备(通常是显示器)给出提示,gets语句从标准输入设备(通常是键盘)读取一行字符(以换行符′\n′结尾)、存储于字符数组s中、并以空字符′\0′取代换行符。至此,当前存储状态如下图所示。

MODI6-05

然后,执行函数调用语句fun(s, num);,以数组名s(自动转换为字符指针)和数组名num(自动转换为整型指针)为实参调用函数fun,于是,main的执行被挂起(暂停),函数fun开始执行。首先,为形参变量s和num分配存储,完成参数传递:将main中的数组s和num的第0元素的地址值(即指向第0个元素的指针)分别传值(拷贝)于fun的形参s和num,即让fun的指针s和num分别指向main中的数组s和num的第0元素,以便通过fun中的指针s和num来访问main中的数组s和num。

在函数fun中,声明:

int k, i = 5;

定义整型变量k,分配存储,不作初始化,k用作循环索引变量。定义整型变量i,分配存储,初始化为5,表示共有5个被统计字母。至此,当前存储状态如下图所示。形参s和num也是fun的局部变量。这里有两个变量s、两个变量num、两个变量i,且同名同型,但不会冲突,因为它们分属不同的函数fun和main、分别存在于不同的存储空间、各有自己的作用域。局部变量只能在其所属函数中访问。

MODI6-06

在函数fun中,首先执行下列语句:

for (k = 0; k < i; ++k)
­       num[k] = 0;

将数组num的5个元素清0,为计数5个字母的出现次数作准备,执行效果如下图所示:

MODI6-07

接着,执行下面的for语句:

for ( ; ∗s; ++s) {
­      switch (∗s) {
­­            case ′a′: case ′A′: i = 0;  break;
­            case ′b′: case ′B′: i = 1;  break;
­            case ′c′case ′C′: i = 2;  break;
­            case ′d′: case ′D′: i = 3;  break;
­            case ′e′: case ′E′: i = 4;  break;
­            default :               i = −1; break;
­      }
­      if (i != −1)
­            ++num[i];
}

for语句下辖两个并列语句:switch语句和if语句。在for循环过程中,指针s从头至尾扫描整个字符串,直至扫到串尾标记空字符′\0′时循环终止。for语句的第2表达式,即重复执行循环体的条件为∗s,等价于∗s != 0,还等价于∗s != ′\0′。在机器字符集中,′\0′的码值为0,而其它字符的码值均不为0。for语句的第1表达式缺省,无需一次性准备工作,这是允许的。在指针s从头至尾扫描整个字符串的过程中,switch语句根据s所指字符∗s的值,跳转至匹配情形执行相应语句、赋于i不同的值。例如:当∗s为字母′b′′B′时,执行i = 1;,而后执行break语句使switch终止。当∗s不是被统计字母时,执行 i = −1。因此,当switch终止后,执行if语句,根据i的取值,令num的对应元素num[i]加1,也就是令对应的被统计字母的出现次数加1。当for循环终止后,num[0]、num[1]、num[2]、num[3]、num[4]的值分别就是字母a、b、c、d、e(不区分大小写)在整个字符串中的出现次数。至此,当前存储状态如下图所示:

MODI6-08

由于这是函数fun的最后语句,函数fun随即返回其调用者main,但无返回值,同时收回fun的存储空间。函数fun返回后,main从挂起点即语句fun(s, num);恢复继续执行,函数fun无返回值,但其执行效果留在数组num中。至此,当前存储状态如下图所示:

MODI6-09

在函数main中,接着执行下列语句:

printf(″  a  b  c  d  e\n″);
for (i = 0; i < 5; ++i)
printf(″%3d″, num[i]);
printf (″\n″);

将各元音字母的出现次数在标准输出设备(通常是显示器)上输出。由于这是main的最后语句,随即函数main执行结束、同时收回其存储空间。

知识点:

① 组成C程序的各个函数自己的局部变量(含形参变量)在函数进入后、函数的语句开始执行前,自动分配存储空间,并以″传值″方式(即拷贝复制)完成参数传递,这些局部变量的存储空间在函数返回的同时自动撤销回收。不同函数的局部变量可以同名,不会冲突,驻留各自的存储空间,只能被本函数自己的语句访问。函数的局部变量,只要未被显示初始化,则其初值无意义,只反映新分配物理内存的当前随机状态。

② 字符数组与字符串的关系。字符数组是用来存储字符数据的,每个元素可以存放机器字符集的任一字符。长度为n的字符串是长度为n+1的字符数组,最后一个字符必须是空字符′\0′,空字符′\0′不计入字符串长度,这是C语言的规定,供程序检测是否到达串尾之用。长度为n的字符数组,可以容纳n个任意字符,因此可以存储长度小于或等于n−1的任意字符串。

③  在C语言中,逻辑表达式的结果类型为int,结果值只取0或1,结果取0表示逻辑假,结果取1表示逻辑真。充当whileforif等语句的条件表达式,可以是任何数值型表达式,包括字符型、整型、实型,其值为0视为逻辑假,其值非0视为逻辑真。

④ 设有字符指针p,且已指向字符数组内某字符元素,那么,++p使p指向下一字符元素,而−−p使p指向上一字符元素。

⑤  设有字符指针变量p,且已指向某字符变量,则可通过∗p间接访问p所指的字符。MODI3-08在这种情形下,可通过∗p间接访问p所指的字符′A′,即字符变量∗p的值是′A′,注意:p和∗p是两个不同类型的变量,p的类型是字符指针类型,即char ∗类型,而∗p的类型是字符类型,即char类型。

⑥ 凡字符串处理程序,通常采用字符指针扫描字符串的方法解决问题。扫描终止的标志就是遇到串尾标记空字符′\0′,而空字符′\0′的机内码值的确为0。像′\0′′\n′′a′等字符常量以及其它字符变量,当出现在表达式中时,自动转换为int类型。

⑦  库函数char  ∗gets(char  ∗s),需要头文件stdio.h支持。

从标准输入设备(通常是键盘),读取下一输入文本行于字符数组s中,并用空字符′\0′替代换行符′\n′,返回s。若遇文件尾或发生错误,则返回NULL。

⑧ 库函数int  puts(const  char  ∗s),需要头文件stdio.h支持。

向标准输出设备(通常是显示器),输出字符串s的内容之后,再输出换行符′\n′。若发生错误,则返回EOF,否则返回一个非负值。

⑨ 常用switch语句的格式及执行:

格式:

­      switch (控制表达式) {
­­            case 常量表达式:  语句;  break;
­            case 常量表达式:  语句;  break;
­            ……
­            case 常量表达式:  语句;  break;
­            default :                 语句;  break;
­      }

控制表达式和常量表达式的值必须是整型,任意两个常量表达式的值均不得相同,至多存在一个default标号。先计算控制表达式的值,再计算各常量表达式的值,转向匹配的case标号后面的语句执行,如果没有匹配的标号,则转向default标号后面的语句执行,如果也不存在default标号,则不执行任何语句。break语句终止switch语句。

例如:

­      switch (∗s) {
­­            case ′a′: case ′A′: i = 0;  break;
­            case ′b′: case ′B′: i = 1;  break;
­            case ′c′case ′C′: i = 2;  break;
­            case ′d′: case ′D′: i = 3;  break;
­            case ′e′: case ′E′: i = 4;  break;
­            default :               i = −1; break;
­      }

­根据字符∗s是否被统计字母,转向相应语句执行,赋于变量i不同的值,而后终止。

练习题:

试任意改变程序,为自己命新题,举一反三。

 返回