您的位置:首页 > 理论基础 > 计算机网络

【制表符和空格的转换】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-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

/*

 * 将文本中的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编辑器中的效果图)为:



输出文件截图(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-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

/*

 * 尽可能地将文本中的空格转换为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、在未经博主断絮允许的情况下,任何个人和机构都不得以任何理由出版此文章,否则必将追究法律责任!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐