【制表符和空格的转换】The C Programming Language 程序研究 第一部分第二章
2013-10-27 19:59
453 查看
第二章 字符和字符串的转换
一、文本中制表符和空格的相互转换
(一)制表符转换为空格
问题描述: |
要解决这个问题,首先要弄清楚文本中tab的性质,我们以vim编辑器为例进行探究。
说明:在每个tab后面都会紧跟一个2,以此来标记2前面是一个tab,另外为了发现tab的规律,特在文本中插入几行用来标记列号的数字下面就是tab在vim文本编辑器中的效果:
每个数字2前面都有一个tab,很容易观察到,每个2都会出现在列号为1的位置,也就是说输入一个tab后,会让光标出现在某些固定的列,而且这些固定的列会是第9列、第17列、第25列……那么,如果在第8列后(此时光标位于第9列)再输入一个tab(输入后tab出现在第9列)会怎样呢?光标会停留在第9列,还是会跳转到第17列呢?我们观察这一情况:
答案是:光标跳转到第17列。也就是说,tab一定是占位符,在输入tab后不会出现光标位置不变的情况。
我们还可以观察到:
如果tab出现在第1~8列,光标会跳转到第9列
如果tab出现在第9~16列,光标会跳转到第17列
如果tab出现在第n列,光标会跳转到第((n-1)/8+1)*8+1列
……
因此,如果要用空格代替tab,并且不改变文本的布局,就要知道tab所处的列,然后将tab替换为相应数目的空格,使光标处于正确的位置即可。那么,如何得到tab所在列呢?当然,我们首先想到的应该是字符计数,但是,单纯的字符计数能够知道tab所处的列吗?我们假设输入的文本(tab用\t表示)是11\t21\t1,此时,如果使用单纯的字符计数,第一个tab位于第3列,第二个tab位于第6列,根据得到的规律,它们都将光标移至第9列,这显然是不正确的。正确计算列号的方式应该是:第一个tab位于第3列,为了将光标移至第9列,应该把这个tab换为6个空格,然后,将这些用于替换的空格也算入字符计数中,这时,第二个tab就会位于第11列,为了将光标移至第17列,应该把tab换为6个空格。
这样我们便得到了解决这个问题的方案:
逐个读取字符,如果读到tab,根据此时的字符数(列号)n,将这个tab替换为(n/8+1)*8-n个空格输出,并将此时的字符数记为:(n/8+1)*8;如果是非tab就直接输出字符,并计数。依此思路编写程序,便可以解决问题。 (另外,注意换行符对程序的影响:如果换行,则需要重新计算字符的列号)
下面是代码和测试结果:
C++ Code By DuanXu-yzc1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | /* * 将文本中的tab转换为等效的空格,并且不改变文本原先的布局 */ #include <stdio.h> #define INTERVAL 8 int main ( void ) { int count; int ch; int i; int space_num; count = 0; while ( (ch=getchar()) != EOF ) { if ( '\t' == ch ) { space_num = (count/INTERVAL+1)*INTERVAL-count; for ( i=0; i<space_num; i++ ) putchar ( ' ' ); count = (count/INTERVAL+1)*INTERVAL; } else { count++; putchar ( ch ); } if ( '\n' == ch ) count = 0; } return 0; } |
在上面的方法中,计算空格数目以及字符数比较复杂,我们再次观察:
可以发现:用于替换tab的空格数目仅仅取决于两个相邻tab(也就是两个相邻的列号为8的列)之间的非tab字符数目,如果tab是该行的第一个tab,那么就取决于这个tab之前的字符数目。如果这个字符数目为n,那么需要的空格数为n-n%8。依据这个规律我们采取如下的方案:
逐个读取字符,如果是tab,打印出n-n%8个空格,并将计数器n置为0;如果为非空格,那么原样打印,并计数(遇到换行符时也应该将计数器置0)。由此编写如下程序并测试:
C++ Code By DuanXu-yzc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | /* * 将输入中的tab字符转换为适当数目的空格,并且不改变原输入的格局 */ #include <stdio.h> #define INTERVAL 8 int main ( void ) { int count; int ch; int i; count = 0; while ( ( ch = getchar () ) != EOF ) { if ( '\t' == ch ) { for ( i=0; i<INTERVAL-count%INTERVAL; i++ ) putchar ( ' ' ); count = 0; } else { count++; putchar ( ch ); } if ( '\n' == ch ) count = 0; } return 0; } |
为了更好地测试这个程序,我们将它改为了文件版(从文件输入文本,而不是从标准输入),并测试了一个比较复杂的文本文件:
程序如下:
C++ Code By DuanXu-yzc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | /* * 将文件中所有的tab位替换为等效数目的空格,并且不改变原来文件的格局 */ #include <stdio.h> #include <stdlib.h> #define INTERVAL 8 #define MAXLEN 150 int main ( void ) { FILE *in; FILE *out; int ch; int i, j; int count = 0; char line[MAXLEN]; if ( NULL == ( in = fopen("detab.c","r") ) ) { fprintf ( stderr, "Can't open file \"detab.c\"\n" ); exit ( EXIT_FAILURE ); } if ( NULL == ( out = fopen("detab_out.c","w") ) ) { fprintf ( stderr, "Can't create file \"detab_out.c\"\n" ); exit ( EXIT_FAILURE ); } while ( fgets(line, MAXLEN, in) != NULL ) { for ( i=0; (ch=line[i])!='\0'; ++i ) { if ( '\t' == ch ) { for ( j=0; j<INTERVAL-count%INTERVAL; j++ ) fputc ( ' ', out ); count = 0; } else { count++; fputc ( ch, out ); } if ( '\n' == ch ) count = 0; } } if ( fclose(in) != 0 || fclose(out) != 0 ) fprintf ( stderr, "Error in closing files\n" ); return 0; } |
输出文件截图(vim编辑器中的效果图)为:
至此,我们已经解决了如何将tab转换为空格的问题,并通过了相对残酷的测试,下面我们讨论这种转换的逆过程,即如何将文本中的空格转换为tab。
(二)空格转换为制表符
问题描述:(尽可能地将文本中的空格转换为tab,并且不改变原文本的布局) |
要正确地将空格转化为制表符,首先要考虑是不是所有的空格都可以转换为tab,我们看看下面的例子:
第一个例子中,在第3列有一个空格,我们尝试将其替换为tab,结果就改变了文本的布局;第二个例子中,在第3列到第8列有6个连续的空格,我们尝试在第3列将空格替换为tab,结果符合要求,由此看来,并不是所有的空格都可以替换为tab。
在上一节中,我们探究了vim编辑器中tab的性质:
如果tab出现在第1~8列,光标会跳转到第9列
如果tab出现在第9~16列,光标会跳转到第17列
如果tab出现在第n列,光标会跳转到第((n-1)/8+1)*8+1列
也就是说,tab的输出会使光标跳转到某些固定的列,所以,出现在这些固定的列之前的空格,一定可以被替换为tab,上面的第二个例子也说明了这一点。又由于我们需要尽可能地将空格转换为tab,所以,要将尽可能多的连续空格转换为tab。由此,我们可以得到基本的思路:
逐个读取字符,如果不是空格,则直接输出,如果是空格,则先不输出,并记下它们的index,接着读取下面的字符,如果下面的空格能够一直延续到index%8==0,就将这些连续的空格替换为tab输出,如果连续的空格不能延续到index%8==0,就按原样输出连续的空格。
这种方法是行不通的:
1、对于读到了空格所采取的的措施太复杂,对于空格的操作的不确定性太大2、不能简单地通过index(或者count)来判断空格是否可以被替换为tab,而应该使用列号(col),比如:(为了方便与index比较,让列号从0开始)
如果依据index来判断是否应该被替换,那么上面的字符串中index为7的空格之前的所有连续空格都可以被替换,替换后的结果为:
显然,替换后改变了原文本的布局。正确的方法应该是根据列号来替换,如果有列号为7、15、23等的空格,那么就可以将此列号以及之前的连续空格替换为一个tab
对于以上两方面的问题,我们采取如下措施:
1、对于所有的字符,均不采用边读边输出的方式,无论读到的字符是否为空格,均不输出(这样就省去了区分空格和非空格的步骤),除非读到了可以替换为tab的空格,此时就输出一直未输出的所有字符(包括那些不能被替换的空格,这样就不用判断所有读到的空格是否该输出),并且替换连续的空格为tab输出,如此一直进行下去,直到所有字符读取完毕,最后输出所有未输出的字符。2、使用列号col而不是下标index来作为可以替换的条件。
根据以上的改进我们得到了具体的操作:
将字符读取到字符数组(为什么不直接从输入中进行处理?请读者自行思考),遍历整个数组,对于下标为index,行号为col的元素,如果(col+1)%8==0,而且str[index]==空格,就往前查看连续的空格,输出上次打印的终止位置到连续空格之前的所有字符,然后输出一个tab,如此一直进行下去,直到数组中所有字符都遍历完毕,输出上次打印的终止位置之后的所有字符。
编写程序如下:
C++ Code By DuanXu-yzc1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | /* * 尽可能地将文本中的空格转换为tab,并且不改变原文本的布局 */ #include <stdio.h> #include <string.h> #define INTERVAL 8 #define MAXLEN 150 int main ( void ) { int i,j,k; // i遍历字符数组,j寻找连续空格,k输出字符 int col; // 列号 int len; // 字符串长度 int start; // 下一次打印的起始位置(或本次打印的截止位置) char str[MAXLEN]; while ( fgets ( str, MAXLEN, stdin ) != NULL ) { col = 0; start = 0; len = strlen ( str ); for ( i=0; i<len; i++ ) // 遍历整个字符串 { if ( (col+1) % INTERVAL == 0 && str[i] == ' ' ) // 可替的换条件 { for ( j=i; str[j] == ' '; j-- ) // 查找连续空格 ; for ( k=start; k<j+1; k++ ) // 打印出上次打印的截止位置到此次连续空格之前的字符 putchar ( str[k] ); putchar ( '\t' ); // 打印替换的tab start = i+1; // 重置下次打印的起始位置(或本次打印的截止位置) } if ( '\t' == str[i] ) // 遇到tab时调整列号 col = ( col/INTERVAL + 1 ) * INTERVAL; else // 非tab时列号加1 col++; } for ( k=start; str[k] != '\0'; k++ ) // 打印出上次打印的截止位置到字符串末尾的字符 putchar ( str[k] ); } return 0; } |
测试:
在此提供文件版,供读者进行更严酷的测试:
C++ Code By DuanXu-yzc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | /* * 尽可能地将文件中的空格转换为tab,并且不改变原文件的布局 */ #include <stdio.h> #include <stdlib.h> #include <string.h> #define INTERVAL 8 #define MAXLEN 150 int main ( void ) { int i,j,k; // i遍历字符数组,j寻找连续空格,k输出字符 int col; // 列号 int len; // 字符串长度 int start; // 下一次打印的起始位置(或本次打印的截止位置) FILE *in; FILE *out; char str[MAXLEN]; if ( NULL == ( in = fopen("detab_out.c","r") ) ) { fprintf ( stderr, "Can't open file \"detab_out.c\"\n" ); exit ( EXIT_FAILURE ); } if ( NULL == ( out = fopen("entab_out.c","w") ) ) { fprintf ( stderr, "Can't create file \"entab_out.c\"\n" ); exit ( EXIT_FAILURE ); } while ( fgets ( str, MAXLEN, in ) != NULL ) { col = 0; start = 0; len = strlen ( str ); for ( i=0; i<len; i++ ) // 遍历整个字符串 { if ( (col+1) % INTERVAL == 0 && str[i] == ' ' ) // 可替的换条件 { for ( j=i; str[j] == ' '; j-- ) // 查找连续空格 ; for ( k=start; k<j+1; k++ ) // 打印出 上次打印的截止位置 到 此次连续空格之前 的字符 fputc ( str[k], out ); fputc ( '\t', out ); // 打印替换的tab start = i+1; // 重置下次打印的起始位置(或本次打印的截止位置) } if ( '\t' == str[i] ) // 遇到tab时调整列号 col = ( col/INTERVAL + 1 ) * INTERVAL; else // 非tab时列号加1 col++; } for ( k=start; str[k] != '\0'; k++ ) // 打印出 上次打印的截止位置 到 字符串末尾 的字符 fputc ( str[k], out ); } if ( fclose(in) != 0 || fclose(out) != 0 ) fprintf ( stderr, "Error in closing files\n" ); return 0; } |
声明
1、此博文版权归断絮所有,如需转载请注明出处http://blog.csdn.net/duanxu_yzc/article/details/13175207,谢谢。
2、在未经博主断絮允许的情况下,任何个人和机构都不得以任何理由出版此文章,否则必将追究法律责任!
相关文章推荐
- 【第一部分 第一章】The C Programming Language 程序研究 【持续更新】
- [置顶] 【其他部分 第一章 矩阵】The C Programming Language 程序研究 【持续更新】
- 做C Programming Language的习题,突发奇想研究如何消除任意多空格或\t,并把格式转换为每个单词占一行
- 【其他部分 第一章 矩阵】The C Programming Language 程序研究 【持续更新】
- The C programming language --第二章 类型、运算符与表达式 读书笔记
- 关于编写一个函数invert(x,p,n),返回x循环右移n位后得到的值,the c programming language 第二章练习2-8
- 再读《The C Programming Language》 第二章 2.4 练习汇总
- 《The C++ programming language》读书笔记(2)——第二章:C++ 概览
- 再读《The C Programming Language》 第二章 2.2 运算符
- TCPL(The C Programming Language)读书笔记 第二章 类型、运算符与表达式
- 再读《The C Programming Language》 第二章 2.3 表达式
- The C++ Programming Language第二章
- 再读《The C Programming Language》 第二章 2.1 变量类型
- TCPL(The C Programming Language)读书笔记 第四章 函数与程序结构
- The Swift Programming Language--语言指南--类型转换
- The C++ Programming Language 第二章
- The C++ Programming Language Special 3rd Edition学习笔记-[3]第二章 C++概览
- 编写函数itob,将整数n转换为以b为底的数(The c programming language 练习3-5)
- The C Programming Language 第四章函数与程序结构 读书笔记
- the c programing language 练习1-21 将空格字符替换为最少数量的制表符和空格