<< C程序设计语言第2版 >> 练习1-14 打印输入中各个字符出现频度的直方图

news2025/5/25 8:59:28

1. 前言

本篇文章是<< C程序设计语言第2版 >> 的第1章的编程练习1-14, 个人觉得还有点意思, 所以写一篇文章来记录下. 希望可以给初学C的同学一点参考. 尤其是自学的同学, 或者觉得以前学得不好, 需要自己补充学习的同学. 和我的很多其它文章一样, 不建议自己还没实现自己的版本, 一来就看别人的答案, 还是要自己先实现自己的版本, 实现了之后, 然后再看别人的. 这本书的权威性我无需多言, 它也有配套的习题解答, 不过, 我还没看过. 个人觉得还没有完全实现自己的版本前, 看了大师的答案也作用不大, 也吸收不了啥. 只有都自己实现过, 再看大师的版本, 你才知道你有哪些地方可以改进, 能学什么技巧. 就算自己实现的算法很烂, 效率不高, 那也没关系, 因为只有你自己实现过, 再看别人优秀的, 你才能明白自己的不足.

这道编程题原文如下:

练习 1 - 14  编写一个程序, 打印输入中各个字符出现频度的直方图.

2. 题目分析

首先目的是打印一个直方图, 是打印输入中字符出现频度的直方图. 但这个要求是很宽泛的, 大师并没有具体一些规则, 是打印水平直方图还是垂直直方图, 字符的出现个数最多统计多少个, 统计多少种字符, 这些题目并没有规定, 所以完成这道编程题很灵活, 有很多种实现. 当然大师写书时出这个题也只是为了让读者掌握基本的字符处理技巧, 所以规则也很灵活.

在这里,我的实现方式给自己加了一些规则来实现, 当然不同的人有不同的规则和实现, 灵活运用即可. 

a. 直方图为垂直直方图

b. Y轴高度即最多统计50个数量的字符, 一屏显示高度也有限, 所以要限制

c. X轴为输入中的各种字符, 但因每次输入不一样, 所以X轴字符应不固定, 随每次输入的字符的不同, 而列出不同的字符. 如果每次X轴展示的字符一样那没输入那些字符那X轴列出来也没意义, 所以X轴列出来的字符每次应不同

d. 因为屏幕宽也是有限, 所以X轴最多展示50种字符, 我们这里设定和Y轴一样, 但X轴虽然最多只展示50个字符宽, 但字符种类不局限在50个. 就是说X轴在最多50个字符宽的前提下, 展示最多97种字符, 这97个字符就是键盘主键区域的全部可见字符, 94个, 加上3个常见的不可见字符换行'\n', 制表'\t', 空格' '. 大家可以看ASCII表, 从编码 0 ~ 127, 一共就 128 个字符. 我们就统计其中的 97 个字符, 从编码 33 的 '!' 开始到编码 126 的 '~' 为止共94个. 另加3个换行制表和空格. 

3. 数据结构与算法设计思考

关于打印垂直直方图的算法的详细分析在我另外的文章中有比较完整的分析, 这里不再完整分析, 算法还和之前的一样. 即我垂直直方图从最高数量50个开始打印, 如果X轴的某个字符有当前数量行那么多,就打印直方图案, 否则打印空格符. Y轴数量最高从第50开始,依次到1为止.

这道编程题比较关键的是关于存字符数量的数据结构的思考. 

因为这道题不是打印单词长度的直方图案, 而是打印字符出现频度的直方图案, 第一反应我们还是用一个数组保存字符出现频度的数量就好, 但问题就在于在C语言中数组的下标必须是个整型或整型表达式, 统计单词的长度的时候当然单词长度可以作为数组下标, 数组的值就存放相应单词长度的数量. 那我统计字符出现频度, 数组的值可以保存字符出现的频度, 下标可以直接用字符吗? 

这本书中详细说明过关于数组的使用, 只说了数组下标必须是整型或整型表达式, 就是如 int i; str[i], 这个i就是整型, 或者 str[i + 1], 这种 i + 1 , 这里i + 1是整型表达式. 但我们不能忘了, 字符型 char 它也是个整型, 是个小整型, 它和整型是一家人. 我们从键盘输入的字符都有相应的字符编码, 字符编码它还是整型. 所以我这里考虑用两个数组来做为数据结构.

关键思考

一个数组存字符的频度数量, 可以直接用字符作为数组的下标访问数组然后存数量. 即用 str[ch]++ 这样的表达式来存字符ch的数量, 这里ch是char型, 因为数组下标不可为负, 编译器如果不指明 char类型, 可能是unsigned char可能是singed char, 为保险指定声明为 unsigned char为型. 所以就像这样统计 str['a']++, str['b']++, 这样我们就能保存各个字符自己的数量, 而且就算每次输入不一样, 我们只统计从33的'!'到126的'~'和另外3个空白符, 互相不影响, 各自存各自的. 这个存字符频度数量数组的大小的话 (就是确定数组下标范围), 虽然我们只存那97种字符, 但ASCII码表一共有128个, 所以数组只要大于128就完整覆盖. 由于为保险起见用了unsigned char, 它的整型取值范围是 0 ~ 255 , ASCII字符编码最大127, 所以unsigned char完全够用.

顺便提一下, 当然char类型它也不完全是只用于处理字符数据而设计, 因为它本身也是一种小整型, 所以很小的整型也是可以用char. 它只有 8 个 bit, 有符号就只能表示 -128到127, 无符号可以大一点从0到255. 我这里用来处理字符, 用它来覆盖ASCII编码范围有符号的就够了, 只是为防止负数下标的情况所以用无符号的char.

另一个数组的话就存每次输入的字符, 因为我加了规则每次输入不一样的字符, 展示的直方图的X轴所列出的字符也不应一样, 不然每次列一样的还展示个啥. 而且每次X轴只展示50个字符 (97种字符其中的50个), 所以这个数组每次输入不一样也存不一样的字符.

C语言强大之一也在它的字符处理能力, 尤其是和指针结合, 更是灵活多变, 功能强大. 所以用这两个数组就可以解决这个题目数据结构设计的问题.

4. 代码讲解

4.1 统计函数的说明

这个题目我想说明一下的是统计字符的函数实现:

/* 统计输入的各字符的个数 */
void countchs(char *pstr, int slen, unsigned int *pstrn, int snlen)
{
    int i;
    int ch;
    unsigned char c;       /* c的值为unsigned char可保证不为负数 */ 
    int counted;

    i = 0;    
    counted = 0;
    while ((ch = getchar()) != EOF) {
        if (ch >= '!' && ch <= '~') {  /* 先统计字符数量再统计种类 */
            if (pstrn[ch] < snlen) {   /* 最多50个字符数 */
                pstrn[ch]++; 
                counted = 1;
            }
        } else if (ch == ' ') {        /* 三个常见空白字符也统计 */
            pstrn[' ']++;
            counted = 1;
        } else if (ch == '\t') {
            pstrn['\t']++;
            counted = 1;
        } else if (ch == '\n') {
            pstrn['\n']++;
            counted = 1;
        }
        
        if (counted == 1 && i < slen) { /* 统计97种字符种类 */
            for (i = 0; pstr[i] != '\0' && pstr[i] != ch; i++) { ; }
            pstr[i] = ch; 
            counted = 0;
        } 
    }
}

while循环中就两个if块, 第1个if语句块统计我们要统计的97种字符, 从 '!'到'~' (这中间包括了大小写字母和数字字符), 另加3个换行制表空格符. 我们直接用字符或unsigned char作为下标存它自己的数量. 这里pstrn[' ']和pstrn['\t']因为字符编码仍然是ASCII码0~127整型所以合法, 而ch是unsigned char范围是0 ~ 255也是整型也合法.

后面那个数组pstr[i]我们存每次输入的字符, 如果在我们前面if语句中已统计过的97个字符范围内, 每获得1个新出现的还没存的字符就存到数组中的第1个空位处. 即pstr[i] == '\0'的位置处. i这里用来找存放的位置.并且i控制字符种类上限. 如果没出现过才存新字符. (其实这里不可能到上限97种字符,因为X轴最多打印50个)

4.2 X轴长度变化的说明

因为打印X轴最多50个字符所以我们用 i < snlen, 这个snlen就是50来控制. 如果不足50个字符, 就按实际有多少种输出X轴, 所以X轴打印结束条件有个pstr[i] != '\0'. 因为到存字符种类那个数组为空字符 '\0'时说明打完了.

    for (i = 0; pstr[i] != '\0' && i < snlen; i++) {
        putchar('-');
        putchar('-');
    }

好了本文章我想写的重点就在这两个数组处理的问题上. 打印垂直直方图的算法有兴趣的同学可以看看我的另几篇文章, 算法一样的. 仅供参考, 也有bug, 代码测试也不充分, 请见谅.

4.3 X轴字符显示空格制表换行符

因为空格制表和换行符是不可见字符,但又在统计中, 所以将空格打印为\s, 制表打印为\t, 换行打印为\n, 为可见方便查看.

5. 完整代码

/* C程序设计语言第2版 练习1-14 打印输入中各字符出现频度的直方图 */
#include <stdio.h>
#define MAXNUM 50   /* 最多只统计50个字符数量 */ 
#define MAXCH 97    /* 只统计94种可见字符和2个制表空格换行符 */
#define MAX 128     /* ASCII共128个字符 */

void initchs(char *pstr, int slen, unsigned int *pstrn, int snlen);
void countchs(char *pstr, int slen, unsigned int *pstrn, int snlen);
void printhtgm(char *pstr, int slen, unsigned int *pstrn, int snlen);
void putspaces(int n);
int main(void)
{
    char strs[MAXCH];                         /* 存放字符       */
    unsigned int strsnum[MAX];                /* 存放字符数量   */

    initchs(strs, MAXCH, strsnum, MAX);       /* 初始化字符数组 */    
    countchs(strs, MAXCH, strsnum, MAXNUM);   /* 统计字符       */
    printhtgm(strs, MAXCH, strsnum, MAXNUM);  /* 打印直方图     */


    return 0;
}

/* 初始化统计数组 */
void initchs(char *pstr, int slen, unsigned int *pstrn, int snlen)
{
    int i;

    for (i = 0; i < slen; i++) { 
        pstr[i] = '\0';
    }
    for (i = 0; i < snlen; i++) { 
        pstrn[i] = 0; 
    }
}

/* 统计输入的各字符的个数 */
void countchs(char *pstr, int slen, unsigned int *pstrn, int snlen)
{
    int i;
    int ch;
    unsigned char c;       /* c的值为unsigned char可保证不为负数 */ 
    int counted;

    i = 0;    
    counted = 0;
    while ((ch = getchar()) != EOF) {
        if (ch >= '!' && ch <= '~') {  /* 先统计字符数量再统计种类 */
            if (pstrn[ch] < snlen) {   /* 最多50个字符数 */
                pstrn[ch]++; 
                counted = 1;
            }
        } else if (ch == ' ') {        /* 三个常见空白字符也统计 */
            pstrn[' ']++;
            counted = 1;
        } else if (ch == '\t') {
            pstrn['\t']++;
            counted = 1;
        } else if (ch == '\n') {
            pstrn['\n']++;
            counted = 1;
        }
        
        if (counted == 1 && i < slen) { /* 统计97种字符种类 */
            for (i = 0; pstr[i] != '\0' && pstr[i] != ch; i++) { ; }
			if (pstr[i] == '\0') { pstr[i] = ch; } 
            counted = 0;
        } 
    }
}

/* 显示各字符出现频率的直方图 */
void printhtgm(char *pstr, int slen, unsigned int *pstrn, int snlen)
{
    int i, j;

    putchar('\n');
    putchar('\n');
    putspaces(4);
    putchar('Y');
    putchar('\n');
    putspaces(4);
    putchar('^');
    putchar('\n');

    for (j = snlen; j > 0; j--) {   /* 打印Y轴和直方图案 */
        putspaces(2);
        printf("%2d|", j); 
        for (i = 0; pstr[i] != '\0' && i < snlen; i++) {
            if (pstrn[pstr[i]] >= j) {
                putspaces(1);
                putchar('*');
            } else {
                putspaces(2);
            }
        }
        putchar('\n');
    }
        
    putspaces(4);                   /* 打印X轴,X轴是可变化长度不固定 */
    putchar('+');                  
    for (i = 0; pstr[i] != '\0' && i < snlen; i++) {
        putchar('-');
        putchar('-');
    }
    putchar('>');
    putspaces(1);
    putchar('X');
    putchar('\n');
    putspaces(5);

    /* X轴字符按实际字符数打印 */
    /* 最多50个字符 */
    for (i = 0; pstr[i] != '\0' && i < snlen; i++) { 
        if (pstr[i] == ' ') {         /* 打印不可见字符为可见字符 */
            putchar('\\');
            putchar('s');
        } else if (pstr[i] == '\t') {
            putchar('\\');
            putchar('t');
        } else if (pstr[i] == '\n') {
            putchar('\\');
            putchar('n');
        } else {                     /* 其余可见字符正常打印 */
            putspaces(1);                            
            putchar(pstr[i]);
        }
    }
    putchar('\n');
    putchar('\n');
}

void putspaces(int n)
{
    int i;
    for (i = 0; i < n; i++) { putchar(' '); }
}

6. 运行结果

输入 歌曲 << my love >> 部分歌词结果

输入歌曲 << my hear will go on >> 部分歌词结果

当然有兴趣的同学也可以改进把Y轴也可以变为不固定高度.

7. 结语

您的点赞和收藏是我创作的动力, 感谢.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2385236.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

黑马点评双拦截器和Threadlocal实现原理

文章目录 双拦截器ThreadLocal实现原理 双拦截器 实现登录状态刷新的原因&#xff1a; ​ 防止用户会话过期&#xff1a;通过动态刷新Token有效期&#xff0c;确保活跃用户不会因固定过期时间而被强制登出 ​ 提升用户体验&#xff1a;用户无需频繁重新登录&#xff0c;只要…

港股IPO市场火爆 没有港卡如何参与港股打新?

据Wind资讯数据统计&#xff0c;今年1月1日至5月20日&#xff0c;港股共有23家企业IPO&#xff0c;较去年同期增加6家&#xff1b;IPO融资规模达600亿港元&#xff0c;较去年同期增长626.54%&#xff0c;IPO融资规模重回全球首位。 港股IPO市场持续火爆&#xff0c;不少朋友没有…

RESTful API 在前后端交互中的作用与实践

一、RESTful API 概述 RESTful&#xff08;Representational State Transfer&#xff09;API 是一种基于 HTTP 协议、面向资源的架构风格&#xff0c;旨在实现前后端的松散耦合和高效通信。它通过定义统一的资源标识、操作方法以及数据传输格式&#xff0c;为前后端提供了一种…

python打卡训练营打卡记录day35

知识点回顾&#xff1a; 三种不同的模型可视化方法&#xff1a;推荐torchinfo打印summary权重分布可视化进度条功能&#xff1a;手动和自动写法&#xff0c;让打印结果更加美观推理的写法&#xff1a;评估模式 作业&#xff1a;调整模型定义时的超参数&#xff0c;对比下效果 1…

如何评价OpenRouter这样的大模型API聚合平台?

OpenRouter通过统一接口简化多模型访问与集成的复杂性,实现一站式调用。然而,这种便利性背后暗藏三重挑战:成本控制、服务稳定性、对第三方供应商的强依赖性。 现在AI大模型火得一塌糊涂,新模型层出不穷,各有各的长处。但是对于开发者来说,挨个去对接OpenAI、谷歌、Anthr…

5.2.4 wpf中MultiBinding的使用方法

在 WPF 中,MultiBinding 允许将多个绑定(Binding)组合成一个逻辑结果,并通过一个转换器(IMultiValueConverter)处理这些值,最终影响目标属性。以下是其核心用法和示例: 核心组件: MultiBinding:定义多个绑定源的集合。 IMultiValueConverter:实现逻…

技术服务业-首套运营商网络路由5G SA测试专网搭建完成并对外提供服务

为了更好的服务蜂窝无线技术及运营商测试认证相关业务&#xff0c;搭建了技术服务业少有的5G测试专网&#xff0c;可独立灵活配置、完整端到端5G&#xff08;含RedCap、LAN&#xff09;的网络架构。 通过走真正运营商网络路由的方式&#xff0c;使终端设备的测试和运营商网络兼…

仿腾讯会议——音频服务器部分

1、中介者定义处理音频帧函数 2、 中介者实现处理音频帧函数 3、绑定函数映射 4、服务器定义音频处理函数 5、 服务器实现音频处理函数

大文件上传,对接阿里oss采用前端分片技术。完成对应需求!

最近做了一个大文件分片上传的功能&#xff0c;记录下 1. 首先是安装阿里云 oss 扩展 composer require aliyuncs/oss-sdk-php 去阿里云 oss 获取配置文件 AccessKey ID *** AccessKey Secret *** Bucket名称 *** Endpoint *** 2. 前端上传&#xff0c;对文件进行分片…

【场景分析】基于概率距离快速削减法的风光场景生成与削减方法

目录 1 主要内容 场景消减步骤 2 部分代码 3 程序结果 1 主要内容 该程序参考文献《含风光水的虚拟电厂与配电公司协调调度模型》场景消减部分模型&#xff0c;程序对风电场景进行生成并采用概率距离方法进行消减&#xff0c;程序先随机生成200个风电出力场景&#xff0c;然…

【Java Web】3.SpringBootWeb请求响应

&#x1f4d8;博客主页&#xff1a;程序员葵安 &#x1faf6;感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb; 文章目录 一、请求 1.1 postman 1.2 简单参数 1.3 实体参数 1.4 数组集合参数 1.5 日期参数 1.6 JSON参数 1.7 路径参数 二、响应 2…

单片机中断系统工作原理及定时器中断应用

文件目录 main.c #include <REGX52.H> #include "TIMER0.H" #include "KEY.H" #include "DELAY.H"//void Timer0_Init() { // TMOD 0x01; // TL0 64536 % 256; // TH0 64536 / 256; // ET0 1; // EA 1; // TR0 1; //}unsigned char…

LangGraph-agent-天气助手

用于创建agent和多代理工作流 循环&#xff08;有迭代次数&#xff09;、可控、持久 安装langgraph包 conda create --name agent python3.12 conda activate agent pip install -U langgraph pip install langchain-openai设置 windows&#xff08;>结尾&#xff09; s…

深度学习——超参数调优

第一部分&#xff1a;什么是超参数&#xff1f;为什么要调优&#xff1f; 一、参数 vs 超参数&#xff08;Parameter vs Hyperparameter&#xff09; 类型定义举例是否通过训练自动学习&#xff1f;参数&#xff08;Parameter&#xff09;是模型在训练过程中通过反向传播自动…

创建型:建造者模式

目录 1、核心思想 2、实现方式 2.1 模式结构 2.2 工作流程 2.3 实现案例 2.4 变体&#xff1a;链式建造者&#xff08;常见于多参数对象&#xff0c;无需指挥者&#xff09; 3、优缺点分析 4、适用场景 1、核心思想 目的&#xff1a;将复杂对象的构建过程与其表示分离…

UE4游戏查找本地角色数据的方法-SDK

UE4中&#xff0c;玩家的表示通常涉及以下几个类&#xff1a; APlayerController: 代表玩家的控制逻辑&#xff0c;处理输入等。 APawn: 代表玩家在世界中的实体&#xff08;比如一个角色、一辆车&#xff09;。APlayerController 控制一个 APawn。 ACharacter: APawn 的一个…

Java 连接并操作 Redis 万字详解:从 Jedis 直连到 RedisTemplate 封装,5 种方式全解析

引言 在分布式系统和高并发场景中&#xff0c;Redis 作为高性能内存数据库的地位举足轻重。对于 Java 开发者而言&#xff0c;掌握 Redis 的连接与操作是进阶必备技能。然而&#xff0c;从基础的 Jedis 原生客户端到 Spring 封装的 RedisTemplate&#xff0c;不同连接方式的原…

python web 开发-Flask-Login使用详解

Flask-Login使用详解&#xff1a;轻松实现Flask用户认证 1. Flask-Login简介 Flask-Login是Flask框架的一个扩展&#xff0c;专门用于处理用户认证相关的功能。它提供了用户会话管理、登录/注销视图、记住我功能等常见认证需求&#xff0c;让开发者能够快速实现安全的用户认证…

快速排序算法的C++和C语言对比

快速排序算法简介&#xff1a; 快速排序(Quick Sort)是一种高效的排序算法&#xff0c;采用分治法策略。它的基本思想是&#xff1a; 1. 从数列中挑出一个元素作为"基准" 2. 重新排序数列&#xff0c;所有比基准值小的元素放在基准前面&#xff0c;所有比基准值大的…

分布式事务知识点整理

目录 分布式事务问题&#xff1f;问题场景引入分布式事务的理论标准BASE理论附CAP理论 Two-phase Commit&#xff0c;2PC2PC系统组件两阶段执行过程2PC缺点 Three-Phase Commit&#xff0c;3PC三阶段执行过程 TTC(Try-Confirm-Cancel)seata项目以及原理how to define a Distrib…