避免编译码
A. 编码与译码不对等
摘要 不对等的解决办法
B. 有线鼠标改无线鼠标为什么要编译码啊
原理使触点导通或断开。在实际应用中机械开头的结构形式很多,最常用的是交叉接触式。它的优点是结实耐用, 缺点是不防水。敲击比较费力,打字速度快时容易漏字。不过现在比较好的机械键盘都增加了Click功能, click功能实际上就是从机械结构上进行了改进,加大了缓存,防止快速打字时漏掉字符。它的使用寿命5000万到一亿次左右,普通用户10年大约键盘敲击20万次左右。所以一款好的机械键盘够用一辈子了。
塑料薄膜式键盘
塑料薄膜式键盘内有四层,塑料薄膜一层有凸起的导电橡胶,当中一层为隔离层,上下两层有触点。通过按键使橡胶凸起按下,使其上下两层触点接触,输出编码。这种键盘无机械磨损,可靠性较高,目前在市场占相当大的比重,不过很多JS也将这种成本相对较低的键盘当成电容式键盘。它最大的特点就是低价格, 低噪音,低成本。
导电橡胶式键盘
导电橡胶式键盘触点的接触是通过导电的橡胶接通。其结构是有一层带有凸起的导电橡胶,凸起部分导电,而这部分对准每个按键,互相连接的平面部分不导电,当键帽按下去时,由于凸起部分导电,把下面的触点按通,不按时,凸起部分会弹起。目前使用的也较多。
电容式键盘
电容式键盘它是一种类似电容式开关的原理,通过按键改变电极间的距离而产生电容量的变化,暂时形成震荡脉冲允许通过的条件。我们知道,电容的容量是由介质,两极的距离及两极的面积来决定的。所以当键帽按下时,两极的距离发生变化,这就引起电容容量发生改变,当参数设计合适时,按键时就有输出,而不按键就无输出,这个输出再经过整形放大,去驱动编码器。由于电容器无接触,所以这种键在工作过程中不存在磨损、接触不良等问题,耐久性、灵敏度和稳定性都比较好。为了避免电极间进入灰尘,电容式按键开关采用了密封组装。1000万到3000万次寿命。但目前市场上真正的电容式键盘并不多,大部分是前面两种键盘,一款真正的电容键盘价格是比较高的。
无线键盘
当然最先进的就是无线键盘,顾名思义这种键盘与电脑间没有直接的物理连线,通过红外线或无线电波将输入信息传送给特制的接收器。接收器的连接与普通键盘基本相同,也只需简单地连接到PS/2或COM口、USB口等上,购买时必须注意区别,一般无线的键盘在标识后有"RF"后缀(radio frequency),表示支持无线电波传输。现在大部分产品频点都在900 MHz,455 MHz, 330MHz。左右。
无线键盘需要使用干电池供电,对于红外线型的无线键盘具有较严格的方向性,尤其是水平位置的关系更为敏感,由于接收器接收角度有限(中心直线范围内6公尺)在键盘距离接收器太近时,会出现失灵的情况,同时灵敏度低时不能快速敲键,否则肯定会漏字符。而采用无线电的键盘要灵活得多,考虑到无线电是辐射状传播的,为了避免在近距离内有同类型(同频率)的键盘工作,导致互相干扰,一般都备有4个以上的频道,如遇干扰可以手动转频。无线键盘为了配合移动的需要,一般体积较小巧并集成有鼠标的功能,注意接收器和主机连接有两个接口,一个是PS2、一个是COM口,把这两个接口一一对应都接在主机上就可以了,但如果你不想使用键盘上的鼠标,那就只需把接收器的PS2口接在主机上就可以了,COM不接!接收器不需要外接电源,而键盘里内置的3号碱性电池可以正常使用3个月。
键盘的发展趋势
就键盘的发展来看,键盘的键位是逐渐的增多(但不是无限制的增加毕竟键盘的面积是有限的),而且是向着多功能多媒体的方向发展。从早期推出的电脑采用83键键盘,随后又推出了84键的设计标准,该标准将键盘分为三个区,即功能区、打字键区、负责光标控制和编辑的副键盘区。其中功能键区的光标键与数字键作为双功能符号键使用,使用一个"Numlock"键来控制这两种功能的切换。虽然两种规格的键盘现在已经不多见了,但是键盘主要区域的划分仍然沿用当时的标准,至今没有什么变化。直到1986年IBM公司推出了101键键盘,才在功能上实现了进一步的扩充,除了添加了F11、F12两个功能键之外,还在键盘的中部多加了一组专用的光标控制和编辑的键,在微软推出WIN95操作系统之后,出现Windows启动键,时至今日大量带各种附加功能键的键盘出现在我们的面前。例如Fn键、快捷键、带鼠标和手写板的键盘等等。
常用的键盘的接口有AT接口、PS/2接口和USB接口,现在绝大部分主板都是提供PS/2键盘接口,也称为"小口"。而兼容机尤其是较老的主板常常提供AT接口也被称为"大口",所幸的是市场上有一种大小口键盘转换连接器,售价只有区区几元钱,它一举解决了两种接口键盘的兼容性问题。一些公司还推出了USB接口的键盘。根据最新公布的 PC2001规范,以后所有通过ISA 总线工作的接口都会随着ISA总线的消亡而被USB取代。USB 允许同时将其他一些设备接入,相当于集成了一个HUB,比如可以将鼠标接入,这实际上节约了主板的COM或PS/2口。有的键盘甚至本身就集成了PS/2 转USB的电路,这样就更方便了。目前阻碍其普及的原因还是价格太高。集成USB HUB的键盘,这类键盘大多采用USB接口,由于外设使用USB的机会增加,为了使用更多的USB设备,需要添加一种USB HUB的装置扩展USB接口数量,但是专业的USB HUB价格比较昂贵,所以人们尝试将USB HUB集成到键盘或显示器中并得到成功。集成USB HUB的键盘往往自身占用一个USB接口,用以保持键盘信号与主机的传输,同时提供2到4个USB接口供其他设备连结,简单地说是一进多出,价格上要比专业的USB HUB便宜得多。
在单片机系统中,经常使用的键盘都是专用键盘。这类键盘都是单独设计制作的,成本高,连线多,且可靠性不高。这些问题在那些要求键盘按键较多的应用系统中显得更加突出。与此相比,在PC系统中广泛使用的PS/2键盘具有价格低、通用可靠,且使用的连线少(仅使用2根信号线)的特点,并可满足多数系统的要求。因此,在单片机系统中应用PS/2键盘是一种很好的选择。
本文在分析PS/2协议和PS/2键盘工作原理与特点的基础上,给出在AT89C51单片机上实现对PS/2键盘支持的硬件连接方法以及驱动程序的设计实现。
1PS/2协议
现在PC机广泛采用的PS/2接口为miniDIN 6引脚的连接器。其引脚如图1所示。
1—数据线(DATA);2—未用;3—电源地(GND);
4—电源(+5 V);5—时钟(CLK);6—未用。
图1PS/2连接器PS/2设备有主从之分,主设备采用female插座,从设备采用male插座。现在广泛使用的PS/2键盘鼠标均工作在从设备方式下。PS/2接口的时钟与数据线都是集电极开路结构的,必须外接上拉电阻。一般上拉电阻设置在主设备中。主从设备之间数据通信采用双向同步串行方式传输,时钟信号由从设备产生。
(1) 从设备到主设备的通信
当从设备向主设备发送数据时,首先会检查时钟线,以确认时钟线是否是高电平。如果是高电平,从设备就可以开始传输数据;否则,从设备要等待获得总线的控制权,才能开始传输数据。传输的每一帧由11位组成,发送时序及每一位的含义如图2所示。
图2从设备到主设备的通信每一帧数据中开始位总是为0,数据校验采用奇校验方式,停止位始终为1。从设备到主设备通信时,从设备总是在时钟线为高时改变数据线状态,主设备在时钟下降沿读入数据线状态。
(2) 主设备到从设备的通信
主设备与从设备进行通信时,主设备首先会把时钟线和数据线设置为“请求发送”状态。具体方式为:首先下拉时钟线至少100 μs来抑制通信,然后下拉数据线“请求发送”,最后释放时钟线。在此过程中,从设备在不超过10 μs的间隔内就要检查这个状态。当设备检测到这个状态时,将开始产生时钟信号。
此时数据传输的每一帧由12位构成,其时序和每一位含义如图3所示。
图3主设备到从设备的通信与从设备到主设备通信相比,其每帧数据多了一个ACK位。这是从设备应答接收到的字节的应答位,由从设备通过拉低数据线产生,应答位ACK总是为0。主设备到从设备通信过程中,主设备总是在时钟为低电平时改变数据线的状态,从设备在时钟的上升沿读入数据线状态。
2PS/2键盘的编码与命令集
(1) PS/2键盘的编码
现在PC机使用的PS/2键盘都默认采用第二套扫描码集。该扫描码集可参考文献\[1\]。扫描码有两种不同的类型:通码(make code)和断码(break code)。当一个键被按下或持续按住时,键盘会将该键的通码发送给主机;而当一个键被释放时,键盘会将该键的断码发送给主机。
根据键盘按键扫描码的不同,在此可将按键分为如下几类:
第一类按键,通码为1字节,断码为0xF0+通码形式。如A键,其通码为0x1C,断码为0xF0 0x1C。
第二类按键,通码为2字节0xE0+0xXX形式,断码为0xE0+0xF0+0xXX形式。如right ctrl键,其通码为0xE0 0x14,断码为0xE0 0xF0 0x14。
第三类特殊按键有两个,print screen键通码为0xE0 0x12 0xE0 0x7C,断码为0xE0 0xF0 0x7C 0xE0 0xF0 0x12; pause键通码为0x E1 0x14 0x77 0xE1 0xF0 0x14 0xF0 0x77,断码为空。
组合按键的扫描码发送按照按键发生的次序,如以下面顺序按左SHIFT+A键:1按下左SHIFT键,2按下A键,3释放A键,4释放左SHIFT键,那么计算机上接收到的一串数据为0x12 0x1C 0xF0 0x1C 0xF0 0x12。
在驱动程序设计中,就是根据这样的分类来对不同的按键进行不同处理的。
(2) PS/2键盘的命令集
主机可以通过向PS/2键盘发送命令来对键盘进行设置或者获得键盘的状态等操作。每发送一个字节,主机都会从键盘获得一个应答0xFA(“重发 resend”和“回应echo”命令例外)。下面简要介绍驱动程序在键盘初始化过程中所用的指令(详细键盘命令集见参考文献\[1\]):
0xED主机在本命令后跟随发送一个参数字节,用于指示键盘上num lock, caps lock, scroll lock led的状态;
0xF3主机在这条命令后跟随发送一个字节参数来定义键盘机打的速率和延时;
0xF4用于在当主机发送0xF5禁止键盘后,重新使能键盘。
3PS/2键盘与单片机的连接电路
PS/2键盘与AT89C51单片机的连接方式如图4所示。P1.0接PS/2数据线,P3.2(INT0)接PS/2时钟线。因为单片机的P1、P3口内部是带上拉电阻的,所以PS/2的时钟线和数据线可以直接与单片机的P1、P3相连接。
4驱动程序设计
驱动程序使用Keil C51语言,Keil uVision2编程环境。PS/2 104键盘驱动程序的主要任务,是实现单片机与键盘间PS/2通信,以及将接收到的按键扫描码转换为该按键的键值KeyVal,提供给系统上层软件使用。
(1) 单片机与键盘间PS/2通信的程序设计
在PS/2通信过程中,主设备(单片机)是在时钟信号为低时发送和接收数据信号的。因为单片机到键盘发送的是指令,需要键盘回应,所以这部分程序采用查询方式;而单片机接收键盘数据时,数据线上的信号在时钟为低时已经稳定,所以这部分程序采用中断方式,且不需要在程序中加入延时程序。单片机的键盘发送接口程序见本刊网站。
(2) 键盘扫描码转换程序设计
由于键盘扫描码无规律可循,因此由键盘扫描码获得相应按键的键值(字符键为其ASCII值,控制键如F1、CTRL等为自定义值),只能通过查表的方式。由于按键的三种类型及部分按键对应着两个键值(如A键的键值根据CAPS和SHIFT键状态有0x41(A)和0x61(a)两种),因此综合考虑查表转换速度和资源消耗,设计中使用4个键盘表:键盘扫描码转换基本集和切换集kb_plain_map\[NR_KEYS\]与 kb_shift_map\[NR_KEYS\];包含E0前缀的键盘扫描码转换基本集和切换集kbe0_plain_map\[NR_KEYS\]与 kbe0_shift_map\[NR_KEYS\]。PS/2 104键盘按键扫描码最大值为0x83,所以设置NR_KEYS为132。所有四个键盘表的定义均为如下形式:KB_MAP\[MAKE CODE\]=KEYVAL,如果扫描码对应的按键为空,如KB_MAP\[0x00\],则定义相应键值为NULL_KEY(0x00)。以下是键盘扫描码基本集的部分代码实例:kb_plain_map\[NR_KEYS\]={……
NULL_KEY;0x2C;0x6B;0x69;0x6F;0x30;0x39;NULL_KEY;// 扫描码0x40~0x47
file://对应按键空,逗号,K,I,O,0,9,空
file://对应键值 0x00,’,’,’k’,’i’,’o’,’0’,’9’,0x00
……};图4硬件连接电路如此设计键盘转换表的另一个好处在于,以后如需扩展支持有ACPI、Windows多媒体按键键盘时,只需要将键表中相应处修改即可。如ACPI power按键通码为0xE0 0x37,修改kbe0_plain_map\[0x37\]=KB_ACPI_PWR即可。
特殊按键PAUSE使用单独程序处理,如果接收到0xE1就转入这段程序;而print screen键则将其看作是两个通码分别为0xE0 0x12和0xE0 0x7C的“虚键”的组合键来处理。
在驱动程序中声明如下全局变量:led_status其bit0-scroll lock led关0、开1;bit1-num lock led关为0,开为1;bit2-caps lock led关为0,开为1;bit3~bit7总是0;agcs_status记录左右shift ctrl gui alt状态,bit0-左shift键,bit1-左ctrl键,bit2-左gui键,bit3-左alt键,bit4-右shift键,bit5-右 ctrl键,bit6-右gui键,bit7-右alt键,相应键按下则对应位为1,释放为0。E0_FLAG接到0xE0置1;E1_FLAG接收到 0xE1置1;F0_FLAG接收到0xF0置1。按键键值通过KeyVal提供给上层使用。
PS/2键盘扫描码键值转换程序ps2_codetrans()流程如图5所示。
图5扫描码键值转换程序流程第一类按键的扫描码键值转换程序代码:if (F0_FLAG) {//接收扫描码为断码
switch (mcu_revchar){//处理控制键
case 0x11: agcs_status&=0xF7;break;//左alt释放
case 0x12: agcs_status&=0xFE;break;//左shift释放
case 0x14: agcs_status&=0xFD;break;//左ctrl释放
case 0x58: if(led_status&0x04)
led_status&=0x03;//caps lock键
else led_status =0x04;
ps2_ledchange();
break;
case 0x59: agcs_status&=0xEF;break;//右shift释放
case 0x77: if(led_status&0x02)
led_status&=0x05;//num lock键
else led_status =0x02;
ps2_ledchange();
break;
case 0x7E: if(led_status&0x01)
led_status&=0x06;//scroll lock键
else led_status =0x01;
ps2_ledchange();
break;
default:break;
}
F0_FLAG = 0;
}
else {//接收扫描码为通码
if (led_status & 0x04) caps_flag = 1; else caps_flag = 0;
if (led_status & 0x02) num_flag = 1; else num_flag = 0;
if (scga_status & 0x11) shift_flag = 1; else shift_flag = 0;
file://扫描码键值转换
if ((caps_flag == shift_flag) (!num_flag)) KeyVal=kb_plain_map\[mcu_revchar\];
else KeyVal=kb_shift_map\[mcu_revchar\];
switch(mcu_revchar){//处理控制键或状态键
case 0x11: agcs_status = 0x08;//左alt按下
case 0x12: agcs_status = 0x01;//左shift按下
case 0x14: agcs_status = 0x02;//左ctrl按下
case 0x59: agcs_status = 0x10;//右shift按下
default: break;
}
}第二类按键的扫描码键值转换程序与上相似。要注意的是在退出该程序段时对E0_FLAG和F0_FLAG标志的清0。
PAUSE键的处理程序:如果接收到0xE1,置E1_FLAG=1,然后顺次将后续接收到的7个字节数据和PAUSE的通码后7个字节比较,一致则返回KeyVal=KB_PAUSE。在比较完所有7个字节后清除E1_FLAG标志。
键盘初始化程序kb_init()流程:
① 上电后,接收键盘上电自检通过信号0xAA,或者自检出错信号0xFC。单片机接收为0xAA,进入下一步,否则,进行出错处理。
② 关LED指示,单片机发送0xED,然后接收键盘回应0xFA,接着发送送0x00接收0xFA。
③ 设置机打延时和速率。 单片机发送0xF3,接收0xFA,发送0x00(250ms,2.0cps),接收0xFA。
④ 检查LED,发送0xED,接收0xFA,发送0x07(开所有LED),接收0xFA。发送0xED,接收0xFA,发送0x00(关LED),接收0xFA。
⑤ 允许键盘发送0xF4,接收0xFA。
键盘LED改变ps2_ledchange()函数流程:发送0xED→接收0xFA→发送led_status→接收0xFA。
结语
该驱动程序经Keil uVision2编译,在AT89C51单片机上运行通过,实现了对PS/2 104键盘的支持,以及对字符按键大小写切换,num lock切换,控制键及组合按键的支持。该程序对其他嵌入式或单片机系统中PS/2键盘的应用也有借鉴意义。
参考文献
1Adam Chapweske. The ATPS/2 Keyboard Interface.
2Adam Chapweske. PS/2 Mouse/Keyboard Protocol.
3Network Technologies Incorporated. PS/2 Keyboard & Mouse Protocols.
4 Linux 2.4.10内核程序 defkeymap.c dn_keyb.c kbd.c keybdev.c keyboard.c kbd_kern.h kd.h keyboard.h
PS/2帧的第一位是起始位,为0,然后是8位数据位,发送键盘扫描码的一个字节(扫描码为1-4个字节),然后是奇偶校验位,最后是停止位,为1。这些是在数据线(即1号引脚线)上发送的。无键按下时,数据线和始终线都保持为1。当有键按下时,时钟线CLOCK送出脉冲,同时数据线送出数据。主机(此处是89c51 MCU)在始终脉冲的下降沿对数据线采样获得数据。键盘扫描码包括通码和断码,当键按下时发送通码,抬起时发送断码。更详细的内容可参考所附的《PS/2 技术参考》。
根据上述原理,我们可以将键盘的脉冲线接至89c51的外部中断输入口(INT0或INT1),当键按下和抬起时有脉冲产生,此脉冲引发MCU 中断。将键盘的DATA线连至89c51的输入口(如P1.0)。在中断处理程序中,从输入口读入数据,然后通过循环移位对读进的数据位进行处理,1(起始位)、10(奇偶校验)、11(停止位)可抛弃,如不嫌麻烦也可将奇偶校验位加以应用。当一个数据帧收完后,将处理后剩下的2-9位(即扫描码)通过串口发至PC机,通过PC机的串口监视软件(如“串口调试助手”)来查看。硬件连线和源码如下:
源码:
ORG 0000H
AJMP MAIN;转入主程序
ORG 0003H ;外部中断P3.2脚INT0入口地址
AJMP INT ;转入外部中断服务子程序
;以下为主程序进行CPU中断方式设置
MAIN:MOV SCON,#50H;设置成串口1方式
MOV TMOD,#20H;波特率发生器T1工作在模式2上
MOV PCON,#80H;波特率翻倍为2400x2=4800BPS
MOV TH1,#0F3H;预置初值(按照波特率2400BPS预置初值)
MOV TL1,#0F3H;预置初值(按照波特率2400BPS预置初值)
SETB EA ;打开CPU总中断请求
SETB IT0 ;设定INT0的触发方式为脉冲负边沿触发
SETB EX0 ;打开INT0中断请求
SJMP $
INT: CLR EA ;暂时关闭CPU的所有中断请求
CJNE R0,#0,L1
L3: INC R0
SJMP L5
L1: CJNE R0,#9,L2
SJMP L3
L2: CJNE R0,#10,L4
SETB TR1;启动定时器T1
MOV SBUF,A
MOV R0,#0
L5: SETB EA ;允许中断
RETI ;退出子程序
L4: MOV C,P1.0
RRC A
SJMP L3
END
搞定后,当按下和释放键时,会在PC机上显示其扫描码。
通电时键盘会自检,此时键盘上三个灯全亮,自检完成后熄灭,并向主机发送十六进制字符AA.。
C. 编码和译码的区别有哪些
如两个人面对面地交谈,讲者把信息变成适于空气传递的声信号,变成语言,就得经过大脑用字词组成句子。这个过程就是编码。听者的耳朵接收到声音信号,经过大脑的理解,明白话音、句子所包含的意思。这个过程就是译码。
D. 调制 解调 编码 译码的关系与区别
调制解调,不同信号转换,数字音频转换,和红灯停绿灯行差不多编码译码,数字信号间的转换,和加密解密差不多
E. 简述一下生活中的编译码现象。很急!
超市条形码,QQ二维码
F. 生活中遇到的编译码现象
电话,手机,电脑上图片的显示,电信号转换为音响音信号
G. 纠错编译码器的研制
还不会,你能等吗?
我一个月左右可能可以学会,不过还是没看懂你的(7,4)汉明码的纠错编译码器的意思
H. 何谓编码何谓译码
编码 通俗来讲就是 把特定意义的信息按照特定规律转换成另一种信息
译码 就是把信息按照某种规律翻译出本来意义的信息
I. 为什么要对信息进行编码
编码是将源对象内容按照一种标准转换为一种标准格式内容。
解码是和编码对应的,它使用和编码相同的标准将编码内容还原为最初的对象内容。
编解码的目的最初是为了加密信息,经过加密的内容不知道编码标准的人很难识别,已经有数千年历史了。
而现在编解码种类非常多,主要目的则是为了信息交换。
除了加密,目前程序中常见的如字符编解码,HTML编解码,URL编解码,邮件编解码,多媒体编解码等。编码是为了符合传输的要求,解码是为了还原成我们能识别的信息。
例如字符编解码,字符编码在一系列数字与人们将文本输入到计算机中时希望看到的字符之间提供映射。因为世界上有不同的语言和文字,所以需要将不同的文字编码以通过计算机处理和传输。
再比如多媒体编解码,因为有多种不同格式的图像声音,所以它们各自有自己编解码标准。
J. 哈夫曼编、译码器
这个是我课程设计弄的,也是哈弗曼编码译码器
#include<iostream>
#include<stdlib.h>
#include<string.h>
using namespace std;
typedef struct{
int weight;
int parent,lchild,rchild;
int asc;
}HTNode,*HuffmanTree; //定义赫夫曼存储结构
struct node{
int ASCII;
int n;
};
struct node a[127];
struct node w[127];
//一些全局变量
int count;
HTNode H_T[250];
char ** HC;
char filename[20];
//函数声明
void menu1(); //菜单1
HuffmanTree HeapSort(HuffmanTree HT,int length); //堆排序
void MinHeapify(HuffmanTree HT, int k,int len); //调整成一个小顶堆
void buildMinHeap(HuffmanTree HT,int len); //建一个小顶堆
void swap(HTNode &t1,HTNode &t2); //交换两结构体
void savefile(); //把输入的英文文章保存到文件
void loadfile(); //从文件中读取文章
HuffmanTree CreateHuffman(HuffmanTree &HT,struct node *w,int n); //建立赫夫曼数并存入文件
void BianMa(HuffmanTree HT,int n ); //字符编码
void BianMa_all(HuffmanTree HT,char**HC,char *filename); //整篇文章编码
int loadfile2(); //从文件读入文章
void JieMa(); //解码
//主函数
int main()
{
char s;
while(s!='0')
{
system("cls");
cout<<"\n\n\n";
cout<<"\t\t\t\t赫夫曼编码/译码器"<<endl<<endl<<endl<<endl<<endl;
cout<<"\t\t\t\t 1.编码"<<endl<<endl<<endl<<endl;
cout<<"\t\t\t\t 2.译码"<<endl<<endl<<endl<<endl;
cout<<"\t\t\t\t 0.退出"<<endl<<endl<<endl<<endl;
cout<<"\t请输入0—2进行选择,按回车确认";
cin>>s;
switch(s)
{
case '1': menu1(); break;
case '2':
{
system("cls");
JieMa();
system("pause");
break;
}
}
}
}
//菜单1
void menu1(){
char s;
int i;
int a;
char c;
char fpname[20]="article.txt";
HuffmanTree HT;
while(s!='0'){
system("cls");
cout<<"\n\t\t\t\t编码界面";
cout<<"\n\n\n\t\t\t\t1.输入英文文章"<<endl;
cout<<"\n\n\t\t\t\t2.从文件中读入文章"<<endl;
cout<<"\n\n\t\t\t\t0.返回上一层"<<endl;
cout<<"\n\t请输入0—2进行选择,按回车确认";
cin>>s;
switch(s){
case'1':
system("cls");
savefile();
loadfile();
CreateHuffman(HT,w,count);
BianMa(HT,count);
BianMa_all(HT,HC,fpname);
system("cls");
cout<<"出现字符种类共计:";
cout<<count<<endl;
for(i=1;i<=count;i++)
{ a=HT[i].asc;
c=(char)a;
cout<<"________________________________________________________________________________"<<endl;
cout<<"\t\t\t字符:";
cout<<c<<endl;
cout<<"\t\t\tASCII码:";
cout<<a<<endl;
cout<<"\t\t\t频数:";
cout<<HT[i].weight<<endl;
cout<<"\t\t\t赫夫曼编码:";
cout<<HC[i]<<endl;
}
cout<<"________________________________________________________________________________";
cout<<"\n\t\t整篇文章的编码已存入文件“赫夫曼编码.txt”"<<endl;
system("pause");
break;
case'2':
system("cls");
if(loadfile2())
{ system("pause");
return;}
CreateHuffman(HT,w,count);
BianMa(HT,count);
BianMa_all(HT,HC,filename);
system("cls");
cout<<"出现字符种类共计:";
cout<<count<<endl;
for(i=1;i<=count;i++)
{ a=HT[i].asc;
c=(char)a;
cout<<"________________________________________________________________________________"<<endl;
cout<<"\t\t\t字符:";
cout<<c<<endl;
cout<<"\t\t\tASCII码:";
cout<<a<<endl;
cout<<"\t\t\t频数:";
cout<<HT[i].weight<<endl;
cout<<"\t\t\t赫夫曼编码:";
cout<<HC[i]<<endl;
}
cout<<"________________________________________________________________________________";
cout<<"\n\t\t整篇文章的编码已存入文件“赫夫曼编码.txt”"<<endl;
system("pause");
break;
}
}
}
//交换结构体
void swap(HTNode &t1,HTNode &t2)
{
HTNode m;
m = t1;
t1 = t2;
t2 = m;
}
//从键盘输入文章并保存
void savefile(){
FILE *fp;
char article;
if((fp=fopen("article.txt","w"))==NULL){
printf("打开文件不成功!");
exit(0);
}
cout<<"请输入英文文章,以#结束:";
getchar();
article=getchar();
while(article!='#'){
fputc(article,fp);
article=getchar();
}
fclose(fp);
}
//从文件读取文章,并统计字符出现频数
void loadfile(){
FILE *fp;
char ch;
int i,k,j=0;
count=0;
for(i=0;i<=127;i++) //把所有字符的ascii码存在数组
{ a[i].ASCII=i;
a[i].n=0;
}
if((fp=fopen("article.txt","r"))==NULL){
printf("打开文件不成功!");
exit(0);
}
ch=fgetc(fp);
k=(int)ch;
a[k].n++;
while(!feof(fp)){
ch=fgetc(fp);
k=(int)ch;
a[k].n++;
}
fclose(fp);
for(i=0;i<=127;i++) //统计字符种类总数
{
ch=(char)i;
if(a[i].n){
count++;
}
}
for(i=0;i<=127;i++)
{
for(;j<count;)
{
if(a[i].n)
{
w[j].n=a[i].n;
w[j].ASCII=a[i].ASCII;
j++;
break;
}
else break;
}
}
}
//调整为小顶堆
void MinHeapify(HuffmanTree HT, int k,int len)
{
int left=k*2;
int right=k*2+1;
int large;
int l=len;
large = k;
if (left<=l&&HT[left].weight<HT[large].weight)
large = left;
if (right<=l&& HT[right].weight<HT[large].weight)
large=right;
if (large!=k)
{
swap(HT[k],HT[large]);
MinHeapify(HT,large,l);
}
}
//建立小顶堆
void buildMinHeap(HuffmanTree HT,int len)
{
int i;
for (i=len/2;i>=1;i--)
{
MinHeapify(HT,i,len);
}
}
//堆排序
HuffmanTree HeapSort(HuffmanTree HT,int length)
{
int i;
int l=length;
buildMinHeap(HT,length);
for (i=length;i>= 2;i--)
{
swap(HT[1],HT[i]);
length--;
MinHeapify(HT,1,length);
}
return HT;
}
//建立赫夫曼数
HuffmanTree CreateHuffman(HuffmanTree &HT,struct node *w,int n)
{
int i,m,s1,s2,k1,k2,j,x,a;
FILE *fp,*fp2;
if(n<=1) return HT;
m=2*n-1;
HT=(HuffmanTree)malloc((m+1)*sizeof(HTNode));//0不用
for(i=1,j=0;i<=n;i++,j++)
{ HT[i].asc=w[j].ASCII;
HT[i].weight=w[j].n;
HT[i].parent=0;
HT[i].lchild=0;
HT[i].rchild=0;
}
for(;i<=m;i++)
{ a=250+i;
HT[i].asc=a;//父亲节点的asc可以是大于127的任意值
HT[i].weight=0;
HT[i].parent=0;
HT[i].lchild=0;
HT[i].rchild=0;
}
for(i=1;i<=n;i++){
H_T[i].asc=HT[i].asc;
H_T[i].parent=HT[i].parent;
H_T[i].lchild=HT[i].lchild;
H_T[i].rchild=HT[i].rchild;
H_T[i].weight=HT[i].weight;
}
for(i=n+1,x=n;i<=m;i++,x--)
{
HeapSort(H_T,x);
k1=H_T[x].asc;
k2=H_T[x-1].asc;
for(j=1;j<=127;j++)
{
if(HT[j].asc==k1)
}
for(j=1;j<=127;j++)
{
if(HT[j].asc==k2)
}
HT[s2].parent=i;
HT[s1].parent=i;
HT[i].lchild=s1;
HT[i].rchild=s2;
HT[i].weight=HT[s1].weight+HT[s2].weight;
H_T[x-1].asc=HT[i].asc;
H_T[x-1].lchild=HT[i].lchild;
H_T[x-1].parent=HT[i].parent;
H_T[x-1].rchild=HT[i].rchild;
H_T[x-1].weight=HT[i].weight;
}
if((fp2=fopen("count.txt","w"))==NULL) //保存赫夫曼树
{
cout<<"文件打开不成功!"<<endl;
exit(0);
}
fputc(count,fp2);
if((fp=fopen("HuffmanTree.dat","wb"))==NULL)
{ cout<<"文件打开不成功!"<<endl;
exit(0);
}
for(i=1;i<=(2*count-1);i++){
fwrite(&HT[i],sizeof(HTNode),1,fp);
}
fclose(fp);
fclose(fp2);
return HT;
}
//逆向编码
void BianMa(HuffmanTree HT,int n){
char *cd,temp;
int c,f,i,j,len,p,q;
cd=(char *)malloc(n*sizeof(char));
HC=(char * *)malloc(n*sizeof(char*));
for(i=1;i<=n;i++){
for(c=i,f=HT[i].parent,j=0;f!=0;c=f,f=HT[f].parent,j++)
{ if(HT[f].lchild==c) cd[j]='0';
else cd[j]='1';
if(HT[f].parent==0)
cd[j+1]='\0';
}
len=strlen(cd);
for(p=0,q=len-1;p<=q;p++,q--)
{
temp=cd[q];
cd[q]=cd[p];
cd[p]=temp;
}
cd[len]='\0';
HC[i]=(char*)malloc((len+10)*sizeof(char));
strcpy(HC[i],cd);
}
}
//整篇文章编码,并存入文件
void BianMa_all(HuffmanTree HT,char**HC,char *filename){
char ch;
int k,i;
FILE *fp,*fp2;
char code[100];
if((fp=fopen(filename,"r"))==NULL){
printf("打开文件不成功!");
exit(0);
}
if((fp2=fopen("赫夫曼编码.txt","w"))==NULL){
printf("打开文件不成功!");
exit(0);
}
ch=fgetc(fp);
k=(int)ch;
while(!feof(fp))
{
for(i=1;i<=count;i++)
{
if(k==HT[i].asc)
strcpy(code,HC[i]);
}
fputs(code,fp2);
ch=fgetc(fp);
k=(int)ch;
}
fclose(fp);
fclose(fp2);
}
void JieMa(){
int i,k,a,t,n=0;
FILE *fp1,*fp2,*fp3;
char ch,c;
HuffmanTree ht;
if((fp3=fopen("count.txt","r"))==NULL) //从文件读出字符总数
{
printf("打开文件不成功!");
exit(0);
}
n=fgetc(fp3);
ht=(HuffmanTree)malloc(2*n*sizeof(HTNode));
if((fp1=fopen("赫夫曼编码.txt","r"))==NULL)
{
printf("打开文件不成功!");
exit(0);
}
if((fp2=fopen("HuffmanTree.dat","rb"))==NULL)
{ cout<<"文件打开不成功!"<<endl;
exit(0);
}
for(i=1;i<=2*n-1;i++)
fread(&ht[i],sizeof(HTNode),1,fp2);
for(i=1;i<=2*n-1;i++)
{
if(ht[i].parent==0)
}
ch=fgetc(fp1);
while(!feof(fp1)){
if(ch=='0')
{
k=ht[k].lchild;
if(ht[k].lchild==0)
{a=ht[k].asc;
c=(char)a;
printf("%c",c);;
k=t;
}
}
if(ch=='1')
{
k=ht[k].rchild;
if(ht[k].lchild==0)
{ a=ht[k].asc;
c=(char)a;
printf("%c",c);
k=t;
}
}
ch=fgetc(fp1);
}
fclose(fp1);
fclose(fp2);
}
//读取文件中的文章,可自己选择文件
int loadfile2(){
FILE *fp;
char ch;
int i,k,j=0;
count=0;
for(i=0;i<=127;i++)
{ a[i].ASCII=i;
a[i].n=0;
}
cout<<"\n\n\n\t\t\t请输入你要打开的文件名:";
cin>>filename;
if((fp=fopen(filename,"r"))==NULL){
printf("打开文件不成功!");
return 1;
}
ch=fgetc(fp);
k=(int)ch;
a[k].n++;
while(!feof(fp)){
ch=fgetc(fp);
k=(int)ch;
a[k].n++;
}
fclose(fp);
for(i=0;i<=127;i++){
ch=(char)i;
if(a[i].n){
count++;
}
}
for(i=0;i<=127;i++)
{
for(;j<count;)
{
if(a[i].n)
{
w[j].n=a[i].n;
w[j].ASCII=a[i].ASCII;
j++;
break;
}
else break;
}
}
return 0;
}