第二章 字符和字符串的转换
一、文本中制表符和空格的相互转换
(一)制表符转换为空格
问题描述:
|
要解决这个问题,首先要弄清楚文本中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就直接输出字符,并计数。依此思路编写程序,便可以解决问题。 (另外,注意换行符对程序的影响:如果换行,则需要重新计算字符的列号)
下面是代码和测试结果:
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 ) count = 0; if ( '\n' == ch ) return 0; |
在上面的方法中,计算空格数目以及字符数比较复杂,我们再次观察:
可以发现:用于替换tab的空格数目仅仅取决于两个相邻tab(也就是两个相邻的列号为8的列)之间的非tab字符数目,如果tab是该行的第一个tab,那么就取决于这个tab之前的字符数目。如果这个字符数目为n,那么需要的空格数为n-n%8。依据这个规律我们采取如下的方案:
逐个读取字符,如果是tab,打印出n-n%8个空格,并将计数器n置为0;如果为非空格,那么原样打印,并计数(遇到换行符时也应该将计数器置0)。由此编写如下程序并测试:
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 ) count = 0; if ( '\n' == ch ) return 0; |
为了更好地测试这个程序,我们将它改为了文件版(从文件输入文本,而不是从标准输入),并测试了一个比较复杂的文本文件:
程序如下:
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 ) char line[MAXLEN]; if ( NULL == ( in = fopen("detab.c","r") ) ) if ( NULL == ( out = fopen("detab_out.c","w") ) ) while ( fgets(line, MAXLEN, in) != NULL ) if ( '\n' == ch ) if ( fclose(in) != 0 || fclose(out) != 0 ) return 0; |
测试文件截图(vim编辑器中的效果图)为:
输出文件截图(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,如此一直进行下去,直到数组中所有字符都遍历完毕,输出上次打印的终止位置之后的所有字符。
编写程序如下:
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 int main ( void ) while ( fgets ( str, MAXLEN, stdin ) != NULL ) for ( i=0; i<len; i++ ) // 遍历整个字符串 for ( k=start; k<j+1; k++ ) // 打印出上次打印的截止位置到此次连续空格之前的字符 putchar ( '\t' ); // 打印替换的tab if ( '\t' == str[i] ) // 遇到tab时调整列号 return 0; |
测试:
在此提供文件版,供读者进行更严酷的测试:
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 int main ( void ) char str[MAXLEN]; if ( NULL == ( in = fopen("detab_out.c","r") ) ) if ( NULL == ( out = fopen("entab_out.c","w") ) ) while ( fgets ( str, MAXLEN, in ) != NULL ) for ( i=0; i<len; i++ ) // 遍历整个字符串 for ( k=start; k<j+1; k++ ) // 打印出 上次打印的截止位置 到 此次连续空格之前 的字符 if ( '\t' == str[i] ) // 遇到tab时调整列号 if ( fclose(in) != 0 || fclose(out) != 0 ) return 0; |
【未完待续,敬请期待!】
声明
1、此博文版权归断絮所有,如需转载请注明出处http://blog.csdn.net/duanxu_yzc/article/details/13175207,谢谢。
2、在未经博主断絮允许的情况下,任何个人和机构都不得以任何理由出版此文章,否则必将追究法律责任!