第11题  从字符串中挑出所有数字字符组成一个新串

在本题程序MODI1.C中,函数fun通过字符指针形参s接收一个字符串,从中挑选所有数字字符,组成一个新串,并返回新串长度。

例如:若接收字符串为″as1d2f354hj9k67jw0″,则结果串为″123549670″,应返回9。

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

MODI1.C:

MODI11-01

参考答案:

MODI11-02

运行结果:

MODI11-03

程序解析:

解答此题需要读懂程序。

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

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

char s[N];
 int n;

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

MODI11-04

首先,执行下列语句:

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

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

MODI11-05

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

在函数fun中,声明:

int i, j;

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

MODI11-06

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

for (i = j = 0; s[i] != ′\0′; ++i)
­        if (s[i] >= ′0′ && s[i] <= ′9′)
­                s[j++] = s[i];

从语法上讲,这是一条for语句,其循环体是一条if语句、if下辖s[j++] = s[i];。 for语句的执行过程是:①执行i = j = 0,将i和j同时初始化为0。令i为0,目的在于从下标0起,依次逐个扫描s中的字符;令j为0,目的是将数字字符逐一转储于j所指示的空闲位置,即s[j]中,而后令j加1。执行效果如下图所示:

MODI11-07

②测试循环条件s[i] != ′\0′,若s[i] 不是′\0′,则执行循环体,即if语句,测试条件s[i] >= ′0′ && s[i] <= ′9′,若s[i]是数字字符,则执行s[j++] = s[i];,将当前数字字符s[i]转储于j所指示的空闲位置,即s[j]中,之后再令j加1,指示下一空闲位置。若s[i]不是数字字符,则不执行任何动作。③执行++i,令i加1,指示下一字符,即向后扫描。返回②重复执行。在②③的循环过程中,当某次测试循环条件s[i] != ′\0′为逻辑假,即s[i] 为′\0′时,for循环终止。当for循环终止后,i的值为18,指示′\0′,j的值为9,也是新串长度,指示′h′。注意:串尾字符′\0′尚未被转储。&&是逻辑与运算符,表示″同时成立″的意义。

MODI11-08

接着,执行语句s[j] =′\0′;,追加串尾标志字符′\0′于s[9],新串构造完成。s[10]至s[18]这些原来的字符对于新生串而言是无意义的。注意:新串的建立也在字符数组s中进行,这叫″就地″算法,可有效节省存储。本题之所以能就地实现,是因为i扫过的那些字符,即从s[0]至s[i]在后续操作中不再需要,且因只挑选数字能确保j的值不会超过i,故从s[0]至s[i]的空间可用于容纳新串,而且够用。至此,当前存储状态如下图所示:

MODI11-09

接着执行语句return j;将新串长度9返回其调用者main,同时收回fun的存储空间。函数fun返回后,main从挂起点即语句n = fun(s);恢复继续执行,将返回值9赋于变量n,fun的执行效果亦留在数组s中。至此,当前存储状态如下图所示:

MODI11-10

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

printf(″由其中全部数字字符组成的新串:\n″);
puts(s);
printf(″新串长度为: %d\n″, 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,否则返回一个非负值。

⑨ 在任何字符集中,数字字符0至9都是连续排列的,因此条件s[i] >= ′0′ && s[i] <= ′9′的成立,就表明字符s[i]是数字字符。&&是逻辑与运算符,若s[i] >= ′0′ 和 s[i] <= ′9′ 同时成立,则其结果为真。

练习题:

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

返回