【第一部分 第一章】The C Programming Language 程序研究 【持续更新】

来源:互联网 发布:wt网络语言什么意思啊 编辑:程序博客网 时间:2024/05/21 10:27

The C Programming Language程序研究

 

前言

    C语言之父创作的The C Programming Language一书对于所有学习C语言的人来说都可以算作一部圣经。它不到200页的篇幅却几近完整地阐述了C语言的各个方面,不仅如此,书中的每个例程和练习都展现了作者丰富的C编程经验,对于初学者来说这些例程和练习并不是那么简单,即使是对于有些经验的C语言工作者来说,也有很多值得称道的地方。在此,我特意精选了The C Programming Language (second edition)中几个方面的编程例程和练习作深入的探讨和研究,一方面可以帮助自己巩固已学的知识、提高编程能力,另一方面也可以帮助其他C初学者更好地阅读K&R这部圣经。由于博主自己接触编程不久,因此文章中难免会有错误,还请大家不吝赐教,多多批评指正,谢谢。

 

声明

    1、此博文版权归断絮所有,如需转载请注明出处http://blog.csdn.net/duanxu_yzc/article/details/12029803,谢谢。

    2、在未经博主断絮允许的情况下,任何个人和机构都不得以任何理由出版此文章,否则必将追究法律责任!

 

 

第一部分 字符和字符串

    K&R这部书中的例程和练习大多都是和字符以及字符串有关的,博主遴选了几类典型的字符(串)处理问题,还对其中的某些进行了修改和扩展。

第一章 字符(串)统计

一、单词计数(p14)

问题描述:统计输入文本中的单词数目。(单词:任何不包含空白字符的连续字符。)

输入:This is a C programme.

输出:5

 

 

 

 

 

 

    分析

    标准输入情况:(用□代表空格,其它不可见字符使用其转义字符表示)

    This□is□a

    □This□is□a

    第一次出现非空白字符时表示已经进入了第一个新单词,程序开始计数,直到遇到空白字符,此时已经读到一个单词之外了,当再次由单词之外进入新单词——出现非空白字符时,再次计数,如此循环,直至文本结束。最后输出统计得到的单词数目。

    编码

/* * 单词计数 */#include <stdio.h>#include <ctype.h>#define IN 1//已经进入一个单词的标志#define OUT 0//已经退出一个单词的标志int main ( void ){long int ct;//单词数int state;//是否进入单词的状态int ch;//读到的字符ct = 0L;//初始化单词数为0state = OUT;//初始状态为不在一个单词中//读取文本,直到EOFwhile ( ( ch = getchar() ) != EOF ){if ( isspace ( ch ) )//切勿使用isblank()函数state = OUT;else if ( state == OUT )//在读到一个非空白字符时,如果前一状态为OUT,则表示进入了一个新单词{state = IN;//在新单词内部时,要保持状态为INct ++;}}printf ( "%ld\n", ct );return 0;}


    关键:如何识别已经进入了一个新单词?

      在读到一个非空字符时将状态设置为外(OUT),在一个单词内部时,保持状态为内(IN)。

 

    意外:在编码时,在判断读取是否为空白字符时,意外地使用了错误的isblank()函数,出现了错误的结果。这个函数在C99中有定义,int isblank ( int c );当参数为空格或者水平制表符时,函数返回1,否则返回0;随即测试此函数如下:


/* * 测试isblank()函数 */#include <stdio.h>#include <ctype.h>int main ( void ){int ch;int i;char str[] = " \v\f\r\t\n";i = 0;while ( ch = str[i] ){if ( isblank ( ch ) )printf ( "%d\n", i );i++;}return 0;}/* * 输出为 0 4 * 表明只有空格和水平制表符是isblank */


    扩展:使用枚举类型代替#define常量

/* * 使用枚举类型替代#include */#include <stdio.h>#include <ctype.h>int main ( void ){enum state { out, in };//使用枚举类型int state;//可以与枚举标签使用同样的名字,这样可以使表达更清楚int ch;long int ct;state = out;ct = 0L;while ( ( ch = getchar () ) != EOF ){if ( isspace ( ch ) )state = out;else if ( state == out ){state = in;ct++;}}printf ( "%ld\n", ct );return 0;}


    小结:此程序的关键是如何判断程序已经读到了一个新单词,另外不要在程序中使用表意不明的“幻数”,可以使用#define常量或者是枚举来代替。

 

 

 

    二、统计单词长度并绘制直方图

 

问题描述:统计输入文本中单词的长度,并绘制水平和垂直直方图表示长度的分布情况。

输入:This is a C programme

输出:

(水平)

 1:   **

 2:   *

 4:   *

 9:   *

(垂直)

  *

  *  *  *  *

  1  2  4  9

 

 

    分析

    统计不同长度单词的个数,实际需要统计两类数据——每个单词的字符数和各长度的单词数目,这与前一节中只统计单词数目不同。

    我们首先考虑如何获取各单词中字符的数目,单词还是不包含空白字符的连续的字符序列,我们仍旧以空白符为分界点,在出现空白符时就结束字符的计数,重置计数器;在遇到非空白符时增加计数器的数值。

    在得到了各个单词的长度之后,如何统计不同长度单词的个数呢?我们不难发现,单词的长度L和这种长度的单词数目C是以数对(L,C)的形式出现的,长度的唯一性确定了这个数对的唯一性,也就是说,每个长度只能有一个数对或者单词数目,但是每个单词数目并不一定只有一种长度。这类似于一维数组,每个下标只能有一个对应元素,而同一个元素值可能对应于不同的下标值。我们利用数组的这个特性来模拟L和C之间的关系:将L作为数组的下标,C作为数组的元素。这样就可以实用一个数组来统计不同长度单词的数目。具体操作如下:首先将所有长度的单词数目(数组的所有元素)置为0,然后在得到了具体长度(下标值)的一个单词之后,使这个长度对应的单词数目(下标对应的元素)增1。

    具体程序如下:

/* * 统计文本中单词长度分布 * 单纯统计数量信息 */#include <stdio.h>#include <ctype.h>int len[31];//外部数组自动初始化为0int main ( void ){int ch;int length;int i;length = 0;while ( ( ch = getchar () ) != EOF ){if ( isspace ( ch ) ){len[length] ++;//长度所对应的元素自增1length = 0;//恢复长度为0,以便下一单词的长度计数}elselength++;//遇到非空就计数}if ( length != 0 )//处理非空白后直接跟EOF的情况len[length] ++;for ( i=1; i<31; i++ )//输出if ( len[i] != 0 )printf ( "length: %2d   count: %3d\n", i, len[i] );return 0;}


 

    水平直方图

    至此我们已经得到了单词长度的统计数据,现在要考虑如何将这个一维数组转换为水平和垂直直方图。首先看看比较简单的水平直方图。

    我们可以直接利用得到的数据,按照各长度的数目,打印出相应数目的图案(*)。具体方案如下:


for ( i=1; i<31; i++ )//输出:将一维数组中元素的大小,转化为相应数目的星号,直接输出if ( len[i] != 0 ){printf ( "length %2d:   " , i );for ( j=0; j<len[i]; j++ )putchar ( '*' );putchar ( '\n' );}


    垂直直方图

    水平直方图很容易绘制,原因在于打印是按行进行的,也就是说,每行打印完成之后才能打印下一行,这与水平直方图的信息连续性方向刚好吻合,但是垂直直方图的分布并不是这样的,它的信息连续性是按列分布的,而打印只能按行进行,这就产生了矛盾,因此,我们不能直接使用得到的一维数组的数据进行输出,我们可以先看看最终想得到的输出的形式(*的数量代表单词的个数,□表示空格):

 

 □ 

 □ 

 * 

 □ 

 □ 

 □ 

 □ 

 □ 

 □ 

 □ 

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

*

          

    我们将上面的表格转化一下,用1代表*,用0代表□:

 

R0

 0 

 0 

 1 

 0 

 0 

 0 

 0 

 0 

 0 

 0 

R1

1

0

1

0

0

0

0

0

0

0

R2

1

0

1

0

0

0

0

0

0

0

...

1

0

1

1

0

0

1

0

0

0

...

1

1

1

1

0

1

1

1

0

0

...

1

1

1

1

1

1

1

1

0

0

...

1

1

1

1

1

1

1

1

1

0

  r_max-1  

1

1

1

1

1

1

1

1

1

1

    这样我们就得到了一个二维数组,按照这个二维数组就可以输出得到最终的分布图,现在的问题就是如何通过已经得到的一维数组生成这个二维数组。

    将一维数组转换为二维数组并不难,只需要将一维数组中每个元素的值转换为对应个数的1即可。观察这个二维数组,发现1的分布都是从最下面一行开始的,在转换的过程中应该从最后一行开始。还有一点需要注意,输出数组的行数由含有1最多的列决定(如图中被框住的列),因此我们需要找到包含有最多单词数的长度。最终思路如下:

    初始化二维数组→找出一维数组中的最大元素→将一维数组转化为二维数组→按照二维数组输出结果

    完整的代码如下:


/* * 统计文本中单词长度分布 * 垂直直方图 */#include <stdio.h>#include <ctype.h>#define R_MAX 1000int len[31];//外部数组自动初始化为0int len_2d[R_MAX][31];//不确定最多有多少行,预估为R_MAX行int main ( void ){int ch;int length;int i;int j;int r_max;length = 0;while ( ( ch = getchar () ) != EOF ){if ( isspace ( ch ) ){len[length] ++;//长度所对应的元素自增1length = 0;//恢复长度为0,以便下一单词的长度计数}elselength++;//遇到非空就计数}if ( length != 0 )//处理非空白后直接跟EOF的情况len[length] ++;/* * 获取实际的最大行 */for ( r_max=len[1], i=1; i<31; i++ )if ( len[i] > r_max )r_max = len[i];/* * 转化为二维垂直数组 */for ( j=1; j<31; j++ )if ( len[j] != 0 )for ( i=r_max-1; i>=r_max-len[j]; i-- )len_2d[i][j] = 1;/* * 输出 */for ( i=0; i<r_max; i++ ){for ( j=1; j<31; j++ )if ( len_2d[r_max-1][j] != 0 )//len_2d[r_max-1][j] 不输出空列(即没有单词的长度){if ( len_2d[i][j] != 0 ){putchar ( ' ' );//空格美化间隔putchar ( ' ' );putchar ( '*' );}else{putchar ( ' ' );//空格美化间隔putchar ( ' ' );putchar ( ' ' );}}putchar ( '\n' );}for ( j=1; j<31; j++ )//打印长度数if ( len_2d[r_max-1][j] != 0 )printf ( "%3d", j );return 0;}


    小结

    1、在遇到问题时要善于分解,然后各个击破。比如这个题目中需要绘制单词长度分布的直方图,可以分解为3个子问题:统计各单词包含字符数、统计各长度包含单词数和绘制图像,当然,这些子问题还可以进一步分解。

    2、使用数组下标和元素的关系模拟各种二元数对。

    3、逆序分析法:从结果入手一步步往前推理,直到找到可以解决的那一步。比如本节中绘制垂直直方图中,从最终的图形入手分析,一直到已经得到的一维数组,解决问题。

    4、一维数组转换为二维数组的方法。

 

 

    本章中探讨了字符和字符串的统计问题,其中也涉及到了图形绘制以及一维数组和二维数组之间的转换,但没有深入展开讨论,这些内容将在后续的章节里面专门介绍。