题目:
编写一个程序,把较长的输入行“折”成短一些的两行或多行,折行的位置在输入行的第n列之前的最后一个非空格符之后。要保证程序能智能地处理输入行很长以及在指定的列前没有空格或制表符的情况。
补充(2021.10.26): 参考了书中的答案之后,这个题目的理解是:折行的位置在输入行的第n列之前的最后一个空格符之后,并且暗含着把输入行中的Tab扩展成空格去处理
自我解答:
题目理解:本题想要实现的是类似于Windows下txt中的自动换行功能,换行时不能分割一个完整的单词。具体行为可以在txt中打开自动换行功能,通过改变显示框的大小进行查看。(并非完全按照题目本来意思去理解)
举例如下:假设n设置为10,即每一行中最多有10个字符。下面例子中用_来表示空格
情形一:
abcdefghijklmnop 此时第10列之前没有空格,则在第10列位置即j后进行折行。自动折行后为:
abcdefghij
klmnop
情形二:
abcd_efghijk 此时第10列位置为i,此时i在单词内部,所以不能在i之后进行折行。这时的折行位置在e之前,效果为:
abcd_
efghijk
情形三:
abcd_ _ _ _ _ _ _ _efg 此时第10列是_,依然在_之后进行折行,效果为
abcd_ _ _ _ _ _
_ _efg
情形四:
abcdtefghijk,假设t占8个字符,第10列的字符是f,在单词内部,折行后效果为
abcdt
efghijk
情形五:
abcdefghttijk,第9、10列的字符都为t,此时在t之后换行,换行效果为:
abcdefght
t
ijk
#include#define TABSIZE 8 #define N 10 //line feed for every N characters void printLine(char s[], int len) { int counter; s[len] = 'n'; s[len + 1] = ' '; printf("%s", s); for(counter = 0; counter < N + 1; counter++) s[counter] = ' '; } int main() { int c, tempchar; int pos; char line[N + 2] = {' '}; int currentLen = 0; int j = 0; int spacePos; int counter; char wordchar[N] = {' '}; for(pos = 1; (c = getchar()) != EOF; pos++) { line[j++] = c; currentLen++; if(c == 'n') { printLine(line, j-1); j = 0; currentLen = 0; pos = 0; continue; } else if(c == 't') { currentLen = currentLen + TABSIZE - (pos - 1) % TABSIZE - 1; pos = pos + TABSIZE - (pos - 1) % TABSIZE - 1; } if(currentLen >= N) { tempchar = getchar(); pos++; if(tempchar == EOF) break; else if(tempchar == 'n') { printLine(line, j); j = 0; currentLen = 0; pos = 0; continue; } else if(tempchar == ' ') { printLine(line, j); j = 0; line[j++] = tempchar; currentLen = 1; } else if(tempchar == 't') { printLine(line, j); j = 0; line[j++] = tempchar; currentLen = TABSIZE; pos = pos + TABSIZE - (pos - 1) % TABSIZE - 1; } else { if(c == ' ' || c == 't') { printLine(line, j); j = 0; line[j++] = tempchar; currentLen = 1; } else { for(spacePos = 0; spacePos < j; spacePos++) { if(line[j - spacePos - 1] == ' ' || line[j - spacePos - 1] == 't') break; } // printf("spacePos is %dn", spacePos); if(spacePos == j) { printLine(line, j); j = 0; line[j++] = tempchar; currentLen = 1; } else { for(counter = 0; counter < spacePos; counter++) { wordchar[counter] = line[j - spacePos + counter]; } printLine(line, j - spacePos); for(counter = 0; counter < spacePos; counter++) line[counter] = wordchar[counter]; j = spacePos; line[j++] = tempchar; currentLen = j; if(currentLen == N) { printLine(line, j); j = 0; currentLen = 0; } } } } } } if(currentLen > 0) printLine(line, j); return 0; }
update in 2021.10.25
编程思路:
1. 定义符号常量N为折行的位置(列数),并定义一个printLine函数用于打印这一行,printLine函数会自动进行换行,其入参s[]不包含'n'
2. 定义一个长度为line[N+2]的字符数组,用于存放从输入获取的字符,长度之所以要多2是因为调用printLine函数时可能会自动填充'n'和' '。定义一个变量currentLen用以纪录当前获取的字符数组的长度,这里的字符宽度包含了制表符所占的宽度。
2. 每向line中输入一个字符,会计算当前的currentLen,然后和N比较判断是否进行折行。
3. 折行时,需要考虑当前字符和紧跟后面的字符。首先判断下个字符是否是输入结束,如结束直接跳出循环,并打印剩余line中的字符。如果下个字符是换行,打印line中的字符,并复位line及其长度,但此时的'n'不能保存到下一次的line中;如果下个字符是空格,打印line中的字符,并复位line,把这个字符保存到line的开头。如果下个字符是Tab,打印line中的字符,并复位line,把这个字符保存到line的开头,由于是Tab,所以line的长度置为TABSIZE;如果下个字符不是EOF,n,t,则说明这个字符是单词的开头或在单词中,所以此时需要判断当前字符是否为空格或者Tab,如果是空格或Tab,则说明line的最后字符不在单词中,直接打印line后复位,并保存这一字符到line的开头;如果当前字符不是空格或Tab,则说明此时正处于单词中,此时需要向前搜索line中的空格和t,如果找不到,则强制进行截断打印,如果找到则需要把line中这个空格之后的部分取出后打印,并把取出的部分保存到下一次的line中,最后注意的是,当取出的这部分和下一个字符组合到一起后有可能已经达到N,此时需要打印一次line。
参考答案:
#include#define MAXCOL 10 #define TABINC 8 char line[MAXCOL]; int exptab(int pos); int findblnk(int pos); int newpos(int pos); void printl(int pos); int main() { int c, pos; pos = 0; while((c = getchar()) != EOF) { line[pos] = c; if(c == 't') pos = exptab(pos); else if(c == 'n') { printl(pos); pos = 0; } else if(++pos >= MAXCOL) { pos = findblnk(pos); printl(pos); pos = newpos(pos); } } } void printl(int pos) { int i; for(i = 0; i < pos; ++i) putchar(line[i]); if(pos > 0) putchar('n'); } int exptab(int pos) { line[pos] = ' '; for(++pos; pos < MAXCOL && pos % TABINC != 0; ++pos) line[pos] = ' '; if(pos < MAXCOL) return pos; else { printl(pos); return 0; } } int findblnk(int pos) { while(pos > 0 && line[pos] != ' ') --pos; if(pos == 0) return MAXCOL; else return pos + 1; } int newpos(int pos) { int i, j; if(pos <= 0 || pos >= MAXCOL) return 0; else { i = 0; for(j = pos; j < MAXCOL; ++j) { line[i] = line[j]; ++i; } return i; } }
MAXCOL是一个符号常量,它给出了输入行的折行位置,即输入行的第n列。整形变量pos是程序在文本行中的当前位置。程序将输入行的每一处第n列之前对改输入行折行。
这个程序将把制表符扩展为空格;每遇到一个换行符就把此前的输入文本打印出来;每当变量pos的值达到MAXCOL时,就会对输入行进行折叠。
函数findblnk从输入行的pos处开始倒退着寻找一个空格(目的是保持折行位置的单词的完整)。如果找到了一个空格符,它就返回紧跟在该空格符后面的那个位置的下标;如果没有找到空格,它就返回MAXCOL。
函数printl打印输出从位置零到位置pos-1之间的字符。
函数newpos调整输入行,它将把从位置pos开始的字符复制到下一个输出行的开始,然后再返回变量pos的新值。
补充:
对比自我解答和参考答案,参考答案中思路更加清晰,表达更为简洁。参考答案得以简洁实现的原因有:1. 定义了一个全局变量数组line,用于存放取到的字符。2. 定义了一个printl函数用于打印数组line,并用pos指定line中的打印位置。3. Tab的处理,把Tab扩展成了空格处理 4. 简单的思路,并没有考虑一些特殊情况
参考答案中没有考虑的情况有:
1. 行尾没有n,而直接EOF。这种情况不会打印最后一次的line中的字符。例如输入为:
abcd (d之后直接是EOF), 此时没有输出
2. 当line数组满时,直接向前寻找空格,并在最后一个空格符之后折行打印。例如输入为:
abcd efghi jkn (d和e之间有一个空格) 输出为
abcd_ (_表示一个空格)
efghi jk
之所以有这样的输出是因为当line满时没有判断其后的字符类型,即没有判断当前字符是不是单词结尾的情况。
而参照txt的折行规则,由于到i时恰为10个字符,且i之后为一个空格符,即代表单词已经结尾,所以应该在i后折行。
回看对题目的理解:
折行的位置在输入行的第n列之前的最后一个非空格符之后
这句和参考答案的实现明显是冲突的,所以正确的表述应该是:
折行的位置在输入行的第n列之前的最后一个空格符之后。
这样表述也潜藏着把Tab转换成空格的理解。



