K&R 练习 1-9:输出输入,用一个空格替换多个空格
K&R Exercise 1-9: output the input, replacing multiple blanks by a single blank
我一直在研究一些关于 C 的书籍,试图让我的 C 腿(海腿!明白了吗?!)。我刚刚完成了 K&R 书中的练习 1-9,供参考 "write a program to copy its input to its output, replacing each string of one or more blanks by a single blank." 不过我对我的代码发生了什么有疑问--
#include <stdio.h>
//Copy input to output. Replace each string of multiple spaces with one single space
int main(int argc, char *argv[]){
int ch, lch; // Variables to hold the current and last characters, respectively
/* This loop should 'put' the current char, then store the current char in lc,
* loop back, 'get' a new char and check if current and previous chars are both spaces.
* If both are spaces, do nothing. Otherwise, 'put' the current char
*/
for(ch = getchar(); (ch = getchar()) != EOF; lch = ch){
if(ch == ' ' && lch == ' ')
;
else putchar(ch);
}
return 0;
}
除了第一个字符输入外,这大部分都有效。例如,如果第一行输入是
"This is a test"
我的代码输出
"his is a test".
在删除第一个字符输入后,程序始终如一地满足练习的要求。
谁能告诉我我在循环中犯的错误导致了这个问题?也欢迎任何其他建议。
问题是循环的第一次迭代调用 getchar
两次 - 一次是在初始化 ch
变量时,另一次是在检查 ch
与 [=14= 时].
删除 ch = getchar()
将解决此问题:
for( lch = '?' ; (ch = getchar()) != EOF; lch = ch) {
...
}
请注意,您需要使用 space 以外的任何值初始化 lch
。
在 for 循环语句中,您遇到了错误。
for(ch = getchar(); (ch = getchar()) != EOF; lch = ch){...}
在这里,您将第一个字符存储在 ch 中,然后通过再次读取字符输入再次测试是否 (ch!=EOF)。
从初始化语句中删除ch=getchar()
;让它在第二部分。
for(;(ch = getchar()) != EOF; lch = ch){...}
此外,您必须在创建 lch 之前对其进行初始化 运行,因为在循环的第一次迭代中进行比较之前,lch 中不会存储任何值。所以,让lch=0
先初始化。
for(lch = 0; (ch = getchar()) != EOF; lch = ch){...}
考虑在您的编译器中启用警告,它可能会检测并警告此问题,因此您可以修复它。
以上内容可以解决您的问题。
(感谢蓝月亮和hyde帮我修改答案。)
您在循环开始前调用 getchar()
一次,然后在 for
条件下每次迭代调用一次。因此,您检索到的第一个字符将被丢弃。
您还需要在循环之前初始化 lch
,然后再进行比较。当字符串的第一个字符是 space :
时,取决于你想做什么
- 将其设置为
' '
将 trim 领先 space "pre-matching"。
- 将其设置为任何其他值将正常处理前导 space。
你的循环头变成(在第二种情况下):
for(lch = 'a' /*arbitrary*/; (ch = getchar()) != EOF; lch = ch)
感谢 shekar suman 对未初始化 lch
的提醒。
你在循环初始化中调用了两次getchar:
for(ch = getchar(); (ch = getchar()) != EOF; lch = ch)
相反,您应该在初始化时调用它一次(获取第一个字符),然后在迭代结束时调用它(获取下一个字符):
int ch, lch = 0; // avoid using uninitialized variable
for(ch = getchar(); ch != EOF; lch = ch)
{
if(ch == ' ' && lch == ' ')
;
else putchar(ch);
ch = getchar();
}
UPD:感谢 Blue Moon 和 shekhar suman 指出 lch 的问题
是的,发生了什么事,当你声明你的 for 语句时,首先你用
初始化 ch
for( ch= getchar();
所以此时你得到了第一个字符 (T) 并且指针前进了一个位置到下一个字符 (h)
然后你又得到了 (ch = getchar()) !=EOF;
的字符
尝试更改 for (ch= getchar();
并改用 for (ch= '' ;
。
希望修复它。
改变这个循环
for(ch = getchar(); (ch = getchar()) != EOF; lch = ch){
if(ch == ' ' && lch == ' ')
;
else putchar(ch);
}
以下方式
for( lch = EOF; ( ch = getchar() ) != EOF; lch = ch )
{
if ( ch != ' ' || lch != ' ' ) putchar( ch );
}
否则在循环开始时你读了一个字符两次。
我还认为作业描述了另一项任务
"write a program to copy its input to its output, replacing each
string of one or more blanks by a single blank."
您应该用一个空格替换每一整行空格。:)
上面显示的循环不执行此任务。
除非任务是使用 for 循环来完成,否则如果您尝试获得更清晰的代码,那么学习这门语言会更好。只需告诉自己代码的作用,例如比较等效的 while 循环和 for 循环:
//initialize lch to prevent undefined behaviour
//if the first character is a space, it will be printed
lch = 'A';
// as long as you can read characters
while((ch = getchar()) != EOF) {
// if either the current character or the previous one is not a space
if(ch!=' ' || lch!=' ') {
//print it
putchar(ch);
}
// remember the current for the next round
lch = ch;
}
一旦理解了 while 结构,您也可以将其转换为 hacky for 循环,但为什么要这样做呢? while 更容易阅读,编译器不关心,因为它会以相同的方式编译。 (大概)
虽然有很多正确的答案,但让我给你一个提示,你可以如何使用调试器(这里是 gdb)自己跟踪这个问题:
首先将代码更改为如下所示(每行一条语句!):
...
for(ch = getchar();
(ch = getchar()) != EOF;
lch = ch){
...
现在使用符号编译它(-g
用于 gcc),然后 运行 使用调试器编译代码:
gdb ./a.out
在 main()
:
处设置一个断点
(gdb) break main
启动程序:
(gdb) run
看到它在 main()
停止:
Breakpoint 1, main (argc=1, argv=0x7fffffffe448) at main.c:15
15 for(ch = getchar();
(gdb)
单步执行代码:
(gdb) step
在 gbd 命令行中使用 print ch
在 "running" 代码的各个阶段检查有趣的变量(此处为 ch
),同时单步执行它。
有关如何引导 gbd 的更多详细信息:http://beej.us/guide/bggdb/
for
语句包含三个部分:初始化、条件和增量。这些部分由两个分号分隔。
当 for
语句的条件部分有副作用时,这会让人非常困惑。副作用属于增量部分:
for (ch = getchar(); ch != EOF; lch = ch, ch = getchar())
并且,正如其他人指出的那样,lch
必须进行初始化,因此:
int lch = 'a';
最后,虽然这不会影响程序的正确性,但我会反转 if
测试:
if (ch != ' ' || lch != ' ')
putchar(ch);
这对我有用
#include <stdio.h>
int main(int arg, char *argv[]){
char c = 0;
long blank = 0;
long tab = 0;
while((c=getchar())!= EOF){
if(c == ' '){
++blank;
}
if(c != ' '){
if(blank>1){
printf("%c", ' ');
blank = 0;
printf("%c", c);
}
else{
printf("%c", c);
}
}
} //end of while
return 0;
}
@elessar 有一个小的变化。第 12 行必须从 (blank>1) 更改为 (blank>=1) 因为前一个不会打印单个空白。
#include <stdio.h>
int main(int arg, char *argv[]){
char c = 0;
long blank = 0;
long tab = 0;
while((c=getchar())!= EOF){
if(c == ' '){
++blank;
}
if(c != ' '){
if(blank>=1){
printf("%c", ' ');
blank = 0;
printf("%c", c);
}
else{
printf("%c", c);
}
}
} //end of while
return 0;
}
另一个流产:
#include <stdio.h>
int main()
{
int charac;
// Variable declared for verifying consecutive whitespaces
bool blank = false;
// As long as you did not input EOF (Ctrl + Z on Windows, Ctrl + D on linux, macOS)
while ((charac = getchar()) != EOF){
// Current char is whitespace, the one before was also whitespace => go to next iteration
if((charac == ' ') && (blank == true)){
continue;
}
// If current char is whitespace, keep this in mind(blank = true) and output the whitespace
else if(charac == ' ')
{
blank = true;
putchar(charac);
continue;
}
// If current character is not whitespace, output it and reset the blank boolean
putchar(charac);
blank = false;
}
return 0;
}
#include <stdio.h>
#include <ctype.h>
/* replace each string of one or more blanks by a single blank */
int main() {
int c, s1;
s1 = 0;
while ((c = getchar()) != EOF) {
if (isspace(c)) {
++s1;
} else {
s1 = 0;
}
if (s1 > 1) {
continue;
}
putchar(c);
}
return 0;
}
我也在读这本书学习 C,我设法想出了这种方法,我希望得到一些反馈以改进。
为了不浪费内存,我尽量不声明太多变量space。
我最终定义了毯子 space 以便稍后打印它,因为我想将多个选项卡和 space 视为一个案例。
#include <stdio.h>
/* space char was defined so I can treat ' ' and '\t' on the same case */
#define BLANK ' '
int main(){
int c;
while((c = getchar()) != EOF){
/* if char is either ' ' or '\t' */
if((c == ' ') || (c == '\t')){
/* print a blank */
putchar(BLANK);
/* read next char */
c = getchar();
/* while after the ' ' or '\t' the char is again ' ' or '\t' ... */
/* I'm not going to bother with it and I'm going to read the next char */
while((c == ' ') || (c == '\t')){
c=getchar();
}
/* print the char */
putchar(c);
}
/* another char */
else {
putchar(c);
}
}
}
我一直在研究一些关于 C 的书籍,试图让我的 C 腿(海腿!明白了吗?!)。我刚刚完成了 K&R 书中的练习 1-9,供参考 "write a program to copy its input to its output, replacing each string of one or more blanks by a single blank." 不过我对我的代码发生了什么有疑问--
#include <stdio.h>
//Copy input to output. Replace each string of multiple spaces with one single space
int main(int argc, char *argv[]){
int ch, lch; // Variables to hold the current and last characters, respectively
/* This loop should 'put' the current char, then store the current char in lc,
* loop back, 'get' a new char and check if current and previous chars are both spaces.
* If both are spaces, do nothing. Otherwise, 'put' the current char
*/
for(ch = getchar(); (ch = getchar()) != EOF; lch = ch){
if(ch == ' ' && lch == ' ')
;
else putchar(ch);
}
return 0;
}
除了第一个字符输入外,这大部分都有效。例如,如果第一行输入是
"This is a test"
我的代码输出
"his is a test".
在删除第一个字符输入后,程序始终如一地满足练习的要求。
谁能告诉我我在循环中犯的错误导致了这个问题?也欢迎任何其他建议。
问题是循环的第一次迭代调用 getchar
两次 - 一次是在初始化 ch
变量时,另一次是在检查 ch
与 [=14= 时].
删除 ch = getchar()
将解决此问题:
for( lch = '?' ; (ch = getchar()) != EOF; lch = ch) {
...
}
请注意,您需要使用 space 以外的任何值初始化 lch
。
在 for 循环语句中,您遇到了错误。
for(ch = getchar(); (ch = getchar()) != EOF; lch = ch){...}
在这里,您将第一个字符存储在 ch 中,然后通过再次读取字符输入再次测试是否 (ch!=EOF)。
从初始化语句中删除ch=getchar()
;让它在第二部分。
for(;(ch = getchar()) != EOF; lch = ch){...}
此外,您必须在创建 lch 之前对其进行初始化 运行,因为在循环的第一次迭代中进行比较之前,lch 中不会存储任何值。所以,让lch=0
先初始化。
for(lch = 0; (ch = getchar()) != EOF; lch = ch){...}
考虑在您的编译器中启用警告,它可能会检测并警告此问题,因此您可以修复它。
以上内容可以解决您的问题。
(感谢蓝月亮和hyde帮我修改答案。)
您在循环开始前调用 getchar()
一次,然后在 for
条件下每次迭代调用一次。因此,您检索到的第一个字符将被丢弃。
您还需要在循环之前初始化 lch
,然后再进行比较。当字符串的第一个字符是 space :
- 将其设置为
' '
将 trim 领先 space "pre-matching"。 - 将其设置为任何其他值将正常处理前导 space。
你的循环头变成(在第二种情况下):
for(lch = 'a' /*arbitrary*/; (ch = getchar()) != EOF; lch = ch)
感谢 shekar suman 对未初始化 lch
的提醒。
你在循环初始化中调用了两次getchar:
for(ch = getchar(); (ch = getchar()) != EOF; lch = ch)
相反,您应该在初始化时调用它一次(获取第一个字符),然后在迭代结束时调用它(获取下一个字符):
int ch, lch = 0; // avoid using uninitialized variable
for(ch = getchar(); ch != EOF; lch = ch)
{
if(ch == ' ' && lch == ' ')
;
else putchar(ch);
ch = getchar();
}
UPD:感谢 Blue Moon 和 shekhar suman 指出 lch 的问题
是的,发生了什么事,当你声明你的 for 语句时,首先你用
初始化 chfor( ch= getchar();
所以此时你得到了第一个字符 (T) 并且指针前进了一个位置到下一个字符 (h)
然后你又得到了 (ch = getchar()) !=EOF;
尝试更改 for (ch= getchar();
并改用 for (ch= '' ;
。
希望修复它。
改变这个循环
for(ch = getchar(); (ch = getchar()) != EOF; lch = ch){
if(ch == ' ' && lch == ' ')
;
else putchar(ch);
}
以下方式
for( lch = EOF; ( ch = getchar() ) != EOF; lch = ch )
{
if ( ch != ' ' || lch != ' ' ) putchar( ch );
}
否则在循环开始时你读了一个字符两次。
我还认为作业描述了另一项任务
"write a program to copy its input to its output, replacing each string of one or more blanks by a single blank."
您应该用一个空格替换每一整行空格。:) 上面显示的循环不执行此任务。
除非任务是使用 for 循环来完成,否则如果您尝试获得更清晰的代码,那么学习这门语言会更好。只需告诉自己代码的作用,例如比较等效的 while 循环和 for 循环:
//initialize lch to prevent undefined behaviour
//if the first character is a space, it will be printed
lch = 'A';
// as long as you can read characters
while((ch = getchar()) != EOF) {
// if either the current character or the previous one is not a space
if(ch!=' ' || lch!=' ') {
//print it
putchar(ch);
}
// remember the current for the next round
lch = ch;
}
一旦理解了 while 结构,您也可以将其转换为 hacky for 循环,但为什么要这样做呢? while 更容易阅读,编译器不关心,因为它会以相同的方式编译。 (大概)
虽然有很多正确的答案,但让我给你一个提示,你可以如何使用调试器(这里是 gdb)自己跟踪这个问题:
首先将代码更改为如下所示(每行一条语句!):
...
for(ch = getchar();
(ch = getchar()) != EOF;
lch = ch){
...
现在使用符号编译它(-g
用于 gcc),然后 运行 使用调试器编译代码:
gdb ./a.out
在 main()
:
(gdb) break main
启动程序:
(gdb) run
看到它在 main()
停止:
Breakpoint 1, main (argc=1, argv=0x7fffffffe448) at main.c:15
15 for(ch = getchar();
(gdb)
单步执行代码:
(gdb) step
在 gbd 命令行中使用 print ch
在 "running" 代码的各个阶段检查有趣的变量(此处为 ch
),同时单步执行它。
有关如何引导 gbd 的更多详细信息:http://beej.us/guide/bggdb/
for
语句包含三个部分:初始化、条件和增量。这些部分由两个分号分隔。
当 for
语句的条件部分有副作用时,这会让人非常困惑。副作用属于增量部分:
for (ch = getchar(); ch != EOF; lch = ch, ch = getchar())
并且,正如其他人指出的那样,lch
必须进行初始化,因此:
int lch = 'a';
最后,虽然这不会影响程序的正确性,但我会反转 if
测试:
if (ch != ' ' || lch != ' ')
putchar(ch);
这对我有用
#include <stdio.h>
int main(int arg, char *argv[]){
char c = 0;
long blank = 0;
long tab = 0;
while((c=getchar())!= EOF){
if(c == ' '){
++blank;
}
if(c != ' '){
if(blank>1){
printf("%c", ' ');
blank = 0;
printf("%c", c);
}
else{
printf("%c", c);
}
}
} //end of while
return 0;
}
@elessar 有一个小的变化。第 12 行必须从 (blank>1) 更改为 (blank>=1) 因为前一个不会打印单个空白。
#include <stdio.h>
int main(int arg, char *argv[]){
char c = 0;
long blank = 0;
long tab = 0;
while((c=getchar())!= EOF){
if(c == ' '){
++blank;
}
if(c != ' '){
if(blank>=1){
printf("%c", ' ');
blank = 0;
printf("%c", c);
}
else{
printf("%c", c);
}
}
} //end of while
return 0;
}
另一个流产:
#include <stdio.h>
int main()
{
int charac;
// Variable declared for verifying consecutive whitespaces
bool blank = false;
// As long as you did not input EOF (Ctrl + Z on Windows, Ctrl + D on linux, macOS)
while ((charac = getchar()) != EOF){
// Current char is whitespace, the one before was also whitespace => go to next iteration
if((charac == ' ') && (blank == true)){
continue;
}
// If current char is whitespace, keep this in mind(blank = true) and output the whitespace
else if(charac == ' ')
{
blank = true;
putchar(charac);
continue;
}
// If current character is not whitespace, output it and reset the blank boolean
putchar(charac);
blank = false;
}
return 0;
}
#include <stdio.h>
#include <ctype.h>
/* replace each string of one or more blanks by a single blank */
int main() {
int c, s1;
s1 = 0;
while ((c = getchar()) != EOF) {
if (isspace(c)) {
++s1;
} else {
s1 = 0;
}
if (s1 > 1) {
continue;
}
putchar(c);
}
return 0;
}
我也在读这本书学习 C,我设法想出了这种方法,我希望得到一些反馈以改进。 为了不浪费内存,我尽量不声明太多变量space。 我最终定义了毯子 space 以便稍后打印它,因为我想将多个选项卡和 space 视为一个案例。
#include <stdio.h>
/* space char was defined so I can treat ' ' and '\t' on the same case */
#define BLANK ' '
int main(){
int c;
while((c = getchar()) != EOF){
/* if char is either ' ' or '\t' */
if((c == ' ') || (c == '\t')){
/* print a blank */
putchar(BLANK);
/* read next char */
c = getchar();
/* while after the ' ' or '\t' the char is again ' ' or '\t' ... */
/* I'm not going to bother with it and I'm going to read the next char */
while((c == ' ') || (c == '\t')){
c=getchar();
}
/* print the char */
putchar(c);
}
/* another char */
else {
putchar(c);
}
}
}