第8题  在字符串最前面插入n个#号

在本题程序MODI1.C中,函数fun通过字符指针形参s接收一个字符串、通过整型形参n接收一个整数,在字符串s最前面插入n个#号,得到一个新字符串。

例如:若接收字符串为″clanguageisok″、n为5,则在最前面插入5个#后,所得结果字符串为″#####clanguageisok″

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

MODI1.C:

MODI8-01

参考答案:

MODI8-02

运行结果:

MODI8-03

程序解析:

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

/********found********/
s = p;

函数fun的形参s的声明char s[ ]等价于char ∗s,其意义是:定义s为字符指针。由于p和s都是字符指针,所以,语句s = p;在语法上是正确的,其意义是:将指针变量p的当前值赋给指针变量s。不过,在执行语句s = p;之前,函数fun的指针s通过参数传递已经指向main中的数组s的首字符,而指针p并未初始化,其值无意义,仅反映新分配内存的当前随机性物理状态。这样,执行语句s = p;就是用一个没有确定意义的指针p的值去改写具有确定意义的指针s,显然是没道理、不合适的。反之,若将语句s = p;改成p = s;,就是用一个具有确定意义的指针s的值去改写一个没有确定意义的指针p,即使得指针p也指向main中的数组s的首字符,从而通过指针p也能访问main中的数组s,这种意图很自然,为随后的操作做好准备。再看第2个错误:

do {
­      a[i] = ∗p;
­      i++;
­      /********found********/
} while (∗p++)

这是一个do_while语句。在C语言中,语句要么以右花括号}结尾,要么以分号;结尾,不存在第3种可能。因此,此处错误是:漏写了分号;。要理解程序,请继续读下去。

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

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

char s[N];
int n;

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

MODI8-04

首先,执行下列语句:

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

printf(″请输入前插#号的个数: ″);
scanf(″%d″, &n);

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

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

在函数fun中,声明:

char a[N], ∗p;
int i;

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

MODI8-06

在函数fun中,首先执行语句p = s;,即使得指针p也指向main中的数组s的首字符,为随后的操作做好准备,至此,当前存储状态如下图所示: MODI8-07接着,执行下列语句:

for (i = 0; i < n; i++)
­        a[i] = ′#′;

由于n的当前值为5,循环索引变量i依次取值0、1、2、3、4、5,当i取5时,i < n不成立,故for循环终止。当i依次取0、1、2、3、4时,i < n均成立,故重复执行a[i] = ′#′;共5次,从下标0开始,为字符数组a填写5个#号,至此,当前存储状态如下图所示:

MODI8-08

注意:上面for循环终止后,i的值为5,指示第5个#后的空闲位置。接着,执行语句:

do {
­      a[i] = ∗p;
­      i++;
} while (∗p++);

do_while循环的执行过程是: 一上来,直接执行循环体语句a[i] = ∗p;将p所指字符∗p即′c′赋值给a[i]即a[5],和i++;令i加1指向下一空闲位置,再判别循环条件∗p++,由于指针间接引用操作符∗与后缀加1操作符++的优先级相同、且服从自右而左结合,故循环条件∗p++等价于∗(p++),因此,在求得当前循环条件∗p++的值也就是∗p即字符′c′的值(非0、逻辑真)并确定重复执行循环体之后、再令p加1指向下一字符′l′,于是,第2次执行循环体语句a[i] = ∗p;将p所指字符∗p即′l′赋值给a[i]即a[6],和i++;令i加1指向下一空闲位置,p加1指向下一字符′a′,如此重复,直到将串尾标记字符′\0′赋值给a[18]后,求得当前循环条件∗p++的值也就是∗p即字符′\0′的值(为0、逻辑假),故do_while循环到此终止。至此,当前存储状态如下图所示:

MODI8-09

接着,执行语句strcpy(s, a);调用库函数strcpy将字符数组a中的字符串(包括′\0′)复制于字符数组s之中,至此,当前存储状态如下图所示:

MODI8-10

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

MODI8-11

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

printf(″前插%d个#号后的结果串:\n″, n);
puts (s);

将前插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,否则返回一个非负值。

⑨ 库函数char  ∗strcpy(char  ∗s, const  char  ∗ct),需要头文件string.h支持。

拷贝字符串ct(包括′\0′)于字符数组s之中,并返回s。

练习题:

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

返回