c语言程序设计实验报告

时间:2024-06-28 19:57:56编辑:coo君

C语言程序设计实验报告完整版

这个题目很简单!

你可以把我这个程序的输出部分修改一下(不修改也可以)
程序如下:

#include

#define STU_NUM 10//学生数目
#define SCORE_NUM 3//课程数目

typedef struct /*定义结构体数组*/
{
char num[20]; /*学号*/
char name[20]; /*姓名*/
float score[SCORE_NUM]; /*成绩*/
float total;//总分
float average;//平均分
} Student;

Student stu[STU_NUM];

//输入学生信息
void input()
{
int i,j;

printf("请输入%d个学生的信息:\n",STU_NUM);
for(i=0;i<STU_NUM;i++)
{
printf("学号:");
scanf("%s",stu[i].num);
printf("姓名:");
scanf("%s",stu[i].name);
for(j=0;j<SCORE_NUM;j++)
{
printf("科目%d的成绩:",j+1);
scanf("%f",&stu[i].score[j]);
}
}
}

//输出学生信息
void output(void)
{
int i,j;

for(i=0;i<STU_NUM;i++)
{
printf("学生的信息如下:\n");
printf("学号:%s\n",stu[i].num);
printf("姓名:%s\n",stu[i].name);
for(j=0;j<SCORE_NUM;j++)
{
printf("科目%d的成绩:%f\n",j+1,stu[i].score[j]);
}
printf("总分:%f\n",stu[i].total);
printf("平均分:%f\n",stu[i].average);
}
}

//计算总分和平均分
void process()
{
int i,j;

for(i=0;i<STU_NUM;i++)
{
stu[i].total=0;
for(j=0;j<SCORE_NUM;j++)
{
stu[i].total+=stu[i].score[j];
}
stu[i].average=stu[i].total/SCORE_NUM;
}
}

//按总分进行排序(冒泡法)
void sort()
{
Student tStu;
int i,j;

for(i=0;i<STU_NUM;i++)
{
for(j=STU_NUM-1;j>i;j--)
{
if(stu[j].total>stu[j-1].total)
{
tStu=stu[j];
stu[j]=stu[i];
stu[i]=tStu;
}
}
}
}

main()
{
input();
process();
sort();
output();
}


C语言程序设计实验报告怎么写啊

只是个例子,不可照抄。

实验内容与要求:
[实验内容]
1、通过本试验初步培养计算机逻辑解题能力。熟练掌握赋值语句和if语句的应用;掌握switch多路分支语句和if嵌套语句的使用
2、将前期所学习到的基本数据类型、运算符和表达式等程序设计基础知识运用于具体的程序设计。
3、进一步熟练掌握输入输出函数scanf, printf和getchar的使用,熟悉math.h中常用数学函数的使用方法
4、掌握循环语句的应用方法。
5、了解随机数生成函数。

[实验要求]
在规定期限独立完成实验内容
1、提交实验报告(电子版)
2、提交相应源程序文件(文件名 EX6_x姓名.c, 如EX6_1彭健.c)
3、要求从简单到复杂,后面的要求均在前面的基础上进行修改,前六题,每题均需要保留各自的程序,六题以后,每题均在前一题基础上修改,保留最后一个程序即可(如做到第九题,则保留EX6_9姓名.c,做到第11题,则保留ex6_11姓名.c)

二、实验原理和设计方案:
1、函数头的选则,while循环语句,switch(case)语句,条件表达式,if else条件语句,自增运算符,设置复杂变量,输出随机操作数。
2、变量要有分数变量和等级变量,要有选择算法题数的变量和计算正确与否的变量,要有随机输出的两个操作数变量和自己按运算符号输入结果的变量,最后还有判断是否要进行的变量字符。中间结果有选择运算符的switch()和分数等级的switch()和错题对题的自增和选择运算符计算的自增。
3、问题的分析方法:先考虑设置整形变量和字符变量,考虑到要不断循环计算,选择用while语句来循环。在循环体中,将前面的输出提示运算符,和自行选择运算符、答案及输出随机操作数完成。再用switch语句对选择的运算符进行判断,并用变量进行自增运算,计算出错题于对题个数和选择了那种运算符号。在循环体最后用if else语句来判断是否继续执行还是跳出循环。最后根据自增计算的结果和公式进行分数计算,并用switch语句来是想等级的制定。

三、源代码
#include
#include
#include
void main()
{
..........
}

四、试验结果和启发
因为输入y要继续循环,所以选择用while语句。在while语句中要结合前面的按提示计算,并嵌套switch语句并用条件表达式,来计算结果正确与否,计算的题型是什么。最后再用switch语句来完成分数等级的判断。

五、实验体会:
描述自己在编程或程序编译运行中遇到的难点和问题及解决的办法。


编写一个函数primeNum(int x),功能是判别一个数是否为素数

#include#define scanf_s scanfint primeNum(int x){ int m; for (m = 2; m > 1 && m <= x; m++) if (x%m == 0) break; if(m==x) return 1; else return 0;}void main(){ int a, b; printf("Please input a number:"); scanf_s("%d", &a); b=primeNum(a); if (b == 1) printf("%d is a prime number.\n", a); else printf("%d is not a prime number.\n", a);}错误太多,就不一一指出了,直接附上改好的代码。

用C语言编写自定义函数prime(int x),判断x是否为素数。

#include "stdio.h"
#include "math.h"//判断一个整数是否为素数
bool prime(int x)
{
int i;
for(i=2;i<= sqrt(x);i++)
{
if(x%i==0)
return false;
}
return true;
} //判断一个整数(1位或多位)是否每一位都是素数
bool allPrime(int a)
{
if(prime(a%10)) //如果个位是素数
{
a=a/10;
return allPrime(a);
}
else
return false;
}// 打印出1-5000内满足题意的素数
void printPrime()
{
int i;
int a=0;
for( i=11;i<5000;) // 1-9 不用再判断了,只有一位。10和5000很明显也不用去判断了,不是素数
{
if(prime(i)) // 如果i是素数,判断个位是不是素数,如果个位不是,不满足第二个条件
{
if(prime(i%10)) //判断个位
{
a=i/10 ; //a就是去掉个位后的数
if(prime(a)) //判断a 是否是素数,如果是,再判断a每一位是否是素数
{
if(allPrime(a))
printf("%d\n",&i);
} }
}
i+=2;
}
}int main()
{
printPrime();
}


C语言实训总结

在初学C语言的一个学期后,我们进行了C语言实训阶段,尝试自己编写一个比较复杂的程序系统。在为期两周的时间中,我们同组的同学共同的感受是:C语言实训和平时上课所接触的程序是有很大不同的,所经受的考验和克服的困难是平时所无法比拟的。好在同组的搭档们精诚合作,分工明确,有问题共同解决,攻克了C语言实训的复杂程序。在这里,我作为其中的参与者,自然感触良多。







刚开始接触到C的时候,我已经学过一些有关VB的内容,这个在算法和思维上稍微有点帮助。回想本学期的学习,首先,最基本的,是C的数据格式,让我们知道整数,浮点数以及字符常量在C中的运用。然后,在学会了数据转化,以及熟练的可以对各种数据处理之后,我开始进行有关数据结构,像数组,结构体等的学习,因为有的东西从现有的知识来看都是非常简单的,还没有联系到指针等等一些复杂的概念。可是,仅仅学会这些是远远不够的,C语言中,还有很多更加经典、重要、实用的知识。



说说函数。虽说很多程序语言都有函数这一内容,但我觉得C语言的函数是最有魅力的了。学习函数的方法是比较简单的,只有两个字“牢记”,即:牢记函数的功能,牢记函数的用途以及如何输入输出。函数从本质上讲是一段通用程序,用它可以帮助我们节约很多编程的时间,学习C语言的“高人”都说,一个聪明的编程者在编写程序前往往总是先找自己所编写的程序中有多少是可以用函数来代替的。比如,大家可以作一个比较字符串的实验,用C语言中的strcmp()函数只要一句话,而自己编写的话,30句都很难实现,可想而知函数的实用和快捷。在我们C语言实训的代码中,函数更是得到了充分的应用,可以说,实训题目的复杂代码,就是用无数个函数的调用和嵌套积累出来的。



要注意的是,有的同学刚刚开始的时候,都是被一些大的程序激励的,所以当开始的时候看到繁琐的数据转化和简单的算法,都觉得很无聊,都想自己做几个自己满意的程序来看看,虽然这种想法很好,但是,我们说,没有基础,纯粹是搬照一些现成设计方法,是不足取的。要知道,程序设计讲究的是个人的思维的,假如刚开始就被一些现成的思想束缚住,以后就会觉得很无趣。
  我们知道,指针其实是C语言的灵魂,许多的数据结构在我们学到这里之前都可以说是精通了。所以我们的任务就是,让数据结构在指针中运行。当然,刚刚开始接触到这些新的东西,是一件非常痛苦的事情,所以我们一定要用非常形象的思维去看待指针,不能太固化。所以,新的东西,比如结构体在指针中的表现方法,数组及多维数组在结构体中的运用,都一点一点的加了进来,同时丰满了我们对原来C的数据机构,数据表示的理解。当我们完成了这三步的学习,我们已经可以自豪的说,我们的基础都扎实了,可以进一步的学习有关算法,设计概念等等深层次的东西了。
  但是,指针,结构体,这些太抽象的东西,在学习C语言的时候我们就有点“似懂非懂”,可是在眼下的C语言实训中,像这么重要的C语言知识,一定要达到能熟练掌握,实际运用的程度。在实训的大程序中,结构体在指针中的表现方法,数组及在结构体中的运用等具体的技术环节,都得到了体现,不会指针,我们的工作是没法展开的。所以,在实训期间,大家在巩固基本知识的基础上,逐块攻克实训课题,克服了困难,自信心得到了提高。







最后,谈谈我们组的程序软件。商店商品管理系统,是一个比较利于应用,解决实际问题,方便实际管理的程序。设计代码比较复杂,结构比较严谨。在程序编写的1周左右的时间里,组员们遇到了上述的困难,包括程序设计构思,甚至是指针等某些知识点的欠缺,导致的工作中出现的困难。但是,当大家一起团结协作,解决了这些困难之后,发现自己也可以编写复杂的、应用性的程序了,更发现自己对C语言这门学科的兴趣也提高了。

当然,我们编写的商店商品管理系统,还存在很多疏漏和不合理之处。比如,程序复杂冗长,如果时间充裕,我们将在不改变程序运行结果的基础上,简化程序,使每一句更加精辟,总体上更加简化。另外,在程序的外观上,我们由于时间问题,没有做更多的修饰,运行起来显得比较死板、枯燥乏味。如果增添一些色彩和其他效果,我们的程序也许会更加完美。

以上就是我的C语言实训个人总结


c语言实验报告心得

c语言实验心得:
1、只有频繁用到或对运算速度要求很高的变量才放到data区内,如for循环中的计数值。
2、其他不频繁调用到和对运算速度要求不高的变量都放到xdata区。
3、常量放到code区,如字库、修正系数。
4、逻辑标志变量可以定义到bdata中。
在51系列芯片中有16个字节位寻址区bdata,其中可以定义8*16=128个逻辑变量。这样可以大大降低内存占用空间。定义方法是: bdata bit LedState;但位类型不能用在数组和结构体中。
5、data区内最好放局部变量。
因为局部变量的空间是可以覆盖的(某个函数的局部变量空间在退出该函数是就释放,由别的函数的局部变量覆盖),可以提高内存利用率。当然静态局部变量除外,其内存使用方式与全局变量相同;
6、确保程序中没有未调用的函数。
在Keil C里遇到未调用函数,编译器就将其认为可能是中断函数。函数里用的局部变量的空间是不释放,也就是同全局变量一样处理。这一点Keil做得很愚蠢,但也没办法。
7、如果想节省data空间就必须用large模式。
将未定义内存位置的变量全放到xdata区。当然最好对所有变量都要指定内存类型。
8、使用指针时,要指定指针指向的内存类型。
在C51中未定义指向内存类型的通用指针占用3个字节;而指定指向data区的指针只占1个字节;指定指向xdata区的指针占2个字节。如指针p是指向data区,则应定义为: char data *p;。还可指定指针本身的存放内存类型,如:char data * xdata p;。其含义是指针p指向data区变量,而其本身存放在xdata区。

以前没搞过C51,大学时代跟单片机老师的时候也是捣鼓下汇编,现在重新搞单片机,因为手头资料不多,找到一些C51的程序,发现里面有这些关键字,不甚明了,没办法只好找了下,发现如下描述:

从数据存储类型来说,8051系列有片内、片外程序存储器,片内、片外数据存储器,片内程序存储器还分直接寻址区和间接寻址类型,分别对应code、data、xdata、idata以及根据51系列特点而设定的pdata类型,使用不同的存储器,将使程序执行效率不同,在编写C51程序时,最好指定变量的存储类型,这样将有利于提高程序执行效率(此问题将在后面专门讲述)。与ANSI-C稍有不同,它只分SAMLL、COMPACT、LARGE模式,各种不同的模式对应不同的实际硬件系统,也将有不同的编译结果。


在51系列中data,idata,xdata,pdata的区别

data:固定指前面0x00-0x7f的128个RAM,可以用acc直接读写的,速度最快,生成的代码也最小。

idata:固定指前面0x00-0xff的256个RAM,其中前128和data的128完全相同,只是因为访问的方式不同。idata是用类似C中的指针方式访问的。汇编中的语句为:mox ACC,@Rx.(不重要的补充:c中idata做指针式的访问效果很好)

xdata:外部扩展RAM,一般指外部0x0000-0xffff空间,用DPTR访问。

pdata:外部扩展RAM的低256个字节,地址出现在A0-A7的上时读写,用movx ACC,@Rx读写。这个比较特殊,而且C51好象有对此BUG,建议少用。但也有他的优点,具体用法属于中级问题,这里不提。




三、有关单片机ALE引脚的问题

"单片机不访问外部锁存器时ALE端有正脉冲信号输出,此频率约为时钟振荡频率的1/6.每当访问

外部数据存储器是,在两个机器周期中ALE只出现一次,即丢失一个ALE脉冲."这句话是不是有毛

病.我觉得按这种说法,应该丢失3个ALE脉冲才对,我一直想不通是怎么回事,希望大虾们帮帮我.

小弟感激涕零.

答:

其他所有指令每6个机器周期发出一个ALE,而MOVX指令占用12个机器周期只发出一个ALE

四、如何将一个INT型数据转换成2个CHAR型数据?

经keil优化后,char1=int1/256,char2=int1%256或char1=int1>>8,char2=int1&0x00ff效率是一样的。

五、在KEIL C51上仿真完了,怎样生成HEX文件去烧写??

右键点项目中Target 1,选第二个,在OUTPUT中选中CREAT HEX

六、typedef 和 #define 有何不同??

typedef 和 #define 有何不同》》》 如

typedef unsigned char UCHAR ;

#define unsigned char UCHAR ;



typedef命名一个新的数据类型,但实际上这个新的数据类型是已经存在的,只不过是定义了

一个新的名字.

#define只是一个标号的定义.

你举的例子两者没有区别,但是#define还可以这样用

#define MAX 100

#define FUN(x) 100-(x)

#define LABEL

等等,这些情况下是不能用typedef定义的

七、请问如何设定KELC51的仿真工作频(时钟)

用右键点击左边的的target 1,然后在xtal一栏输入

八、不同模块怎样共享sbit变量,extern不行?

把SBIT定义单独放到一个.H中,每个模块都包含这个.h文件

九、C51中对于Px.x的访问必须自己定义吗?

是的。

如sbit P17 = 0x97;即可定义对P1.7的访问

十、SWITCH( )语句中表达式不可以是位变量对吗?

可以用位变量:

#include

#include



void main()

{

bit flag;

flag=0;

switch(flag)

{

case '0':{printf("0\n");break;}

case '1':{printf("1\n");break;}

default:break;

}

}



bit 变量只有两种状态,if 语句足够啦,!!!

十一、const常数声明占不占内存???

const 只是用来定义“常量”,所占用空间与你的定义有关,如:

const code cstStr[] = {"abc"};

占用代码空间;而如:

const char data cstStr[] = {"abc"};

当然占用内存空间。

另外,#define 之定义似乎不占用空间。

十二、philips的单片机P89C51RD+的扩展RAM在C51中如何使用?

试一试将auxr.1清0,然后在c语言中直接声明xdata类型的变量

十三、BUG of Keil C51

程序中用如下语句:

const unsigned char strArr[] = {"数学"};

结果发现strArr[] 内容为 {0xCA,0xD1,0xA7},真奇怪!



凡是有0xfd,则会通通不见了,所以只能手工输入内码了,例如 uchar strArr[]=

{0xCA,0xfd,0xd1,0xa7}(用Ultraedit会很方便)。

十四、Keil C51中如何实现代码优化?

菜单Project下Option for target "Simulator"的C51.

看到Code optimization了吗?

十五、请教c的!和 ~ 符号有甚区别??

!是逻辑取反,~是按位取反。

十六、c51编程,读端口,还要不要先输出1?

我怎么看到有的要,有的不要,请高手给讲讲,到底咋回事?谢了

要输出1的,除非你能保证之前已经是1,而中间没有输出过其他值。

十七、当定时器1(T1)用于产生波特率时,P3^5还是否可以用作正常的I/O口呢?

p3.5完全可以当普通的io使用

十八、C51中 INT 转换为 2个CHAR?

各位高手:

C51中 INT 转换为 CHAR 如何转换诸如:

X = LOW(Z);

Y = HIGH(Z);

答:

x=(char)z;

y=(char)(z>>8);

十九、如果我想使2EH的第7位置1的话,用位操作可以吗?

现在对位操作指令我一些不太明白请各位多多指教:

如 SETB 07H 表示的是20H.7置1,对吗?(我在一本书上是这么看到的)

那么如果我想使2EH的第7位置1的话,象我举的这个例子怎么表示呢?谢谢!

SETB 77H

setb (2eh-20h)*8+7

20h-2fh每字节有8个可位操作(00h-7fh),其它RAM不可位直接操作

二十、char *addr=0xc000 和char xdata *addr=0xc000有何区别?

char *addr=0xc000;

char xdata *addr=0xc000;

除了在内存中占用的字节不同外,还有别的区别吗?



char *addr=0xc000; 是通用定义,指针变量 addr 可指向任何内存空间的值;

char xdata *addr=0xc000; 指定该指针变量只能指向 xdata 中的值;

后一种定义中该指针变量(addr)将少占用一个存储字节。





uchar xdata *addr=0xc000;指针指向外ram;

如果:data uchar xdata *addr=0xc000;指针指向外ram但指针本身存在于内ram(data)



以此类推可以idata uchar xdata *addr=0xc000;pdata uchar xdata *addr=0xc000;

data uchar idata *addr=0xa0;.........

二十一、while(p1_0)的执行时间?

假设,P1_0为单片机P1口的第一脚,请问,

while(P1_0)

{

P1_0=0;

}

while(!P1_0)

{

P1_0=1;

}

以上代码,在KEIL C中,需要多长时间,执行完。能具体说明while(P1_0)的执行时间吗?



仿真运行看看就知道了,

我仿真了试了一下,约14个周期

二十二、怎样编写C51的watchdog程序?

各位大虾,我用KEIL C51 编写了一个带外部开门狗的程序,可程序无法运行起来,经过查

找,发现程序在经过C51编译后,在MAIN()函数的前部增加了一端初始化程序,等到进入

主程序设置开门狗时,开门狗已经时间到,将我的程序复位了,请问我怎样才能修改这一端

初始花程序,使他一运行,就设置开门狗?



可以在startup.a51中加入看门狗刷新指令,当然用汇编,然后重新编译startup.a51

,将他和你的程序连接即可。新的startup.a51会自动代替系统默认的启动模块。

二十三、keil C51 怎样把修改的startup.a51 加到工程文件中

直接加入即可

注意不要改动?STACK,?C_START,?C_STARTUP等符号。startup.a51直接加入项目,不用修改也可。可在内面自己修改汇编的一些限制或堆栈指针。

二十四、关于波特率的设置

我在设定串口波特率时发现一个问题:在晶体震荡器为11.0592MHz时,若设9600BPS的话,

TH1=0XFD,TL1=0XFD,而要设19200BPS的话,TH1、TL1有否变化,如果没变,为什么?

如果变了,又为什么?(因为我看书上俩个是一样的),希望大家点拨。

答:

当电源控制寄存器(PCON)第BIT7(SMOD)为1时波特率加倍。

TH1和TL1的值不变.

二十五、如何在C中声明保留这部分RAM区不被C使用?

我不知道在C源程序中怎么控制这个,但在汇编程序中加入下面一段就行:

DSEG AT 20H

AA: DS 10

这样C51就不会占用20H--29H了

或者在c51里这样定义:



uchar data asm_buff[10] _at_ 0x20;

二十六、问浮点运算问题

我在用C51时发现它对传递浮点参数的个数有限制,请问:

1)参数是以全局变量的形式传递的,请问以全局变量的形式传递的参数也有限制吗?

2)这种传递浮点参数的限制有多少呢?

3)float*float的结果是float类型还是double类型?能否直接赋值给float类型的变量?

答:

由于KEIL C51的参数传递是通过R0-R7来传递的,所以会有限制。

不过KEIL提供了一个编译参数,可以支持更多参数的传递。具体

的内容见KEIL的PDF文档。

我建议你把多个要传递的参数定义到指针或结构体中去,传递参

数通过指针或结构进行,这样好一些。



第3个问题回答是YES,你自己试试不就知道了。

二十七、如何在某一个地址定义ram

用_at_ 命令,这样可以定位灵活一点的地址

uchar xdata dis_buff[16] _at_ 0x6020 ;//定位RAM

将dis_buff[16]定位在0x6020开始的16个字节

二十八、keil c中,用什么函数可以得到奇偶校验位?

例如32位数据,将四个字节相互异或后检查P即可,若耽心P被改变,可用内嵌汇编。

#include

unsigned char parity(unsigned char x){

x^=x;

if(P)return(1);

else return(0);

}



unsigned char parity2(unsigned int x){

#pragma asm

mov a,r7

xrl ar6,a

#pragma endasm

if(P)return(1);

else return(0);

}


要C语言程序设计试验报告的小结,谁有?

通过对这一课题的设计和实现,我对Micosoft Visual C++环境进行了深一步的了解,并逐渐开始熟练Micosoft Visual C++环境的工作界面,以及对每一个快捷键的熟悉。并认识到,熟悉这些快捷键,极为便捷编写程序,但是还要更加熟悉。 编程时要养成良好的风格,注意相同内容的缩进和对齐。这样做,可以使程序代码出错的情况下,可以快速并且便捷的查找到错误的行,利于很好的修改。 通过这次编程我们深深的感受到对代码的变量命名,代码内注释格式,甚至嵌套中行缩进的长度和函数间的空行数字都有明确规定,良好的编写习惯,不但有助于代码的移植和纠错,也有助于不同人员之间的协作。 这个程序设计主要涉及到了C语言中的结构体、指针及文件操作等内容,只有充分掌握了C语言中的结构体、指针及文件操作等内容,才有可能组织好这些代码,使之符合运算逻辑,得到理想的结果。 善于总结,也是学习能力的一种体现,每次完成一个编程任务,完成一段代码,都应当有目的的跟踪该程序的应用状况,随时总结,找到自己的不足,这样所编写的程序才能逐步提高,生活就是这样,汗水预示着结果也见证着收获。劳动是人类生存生活永恒不变的话题。通过实际动手做,我们才真正领略到“艰苦奋斗”这一词的真正含义,我们想说,编程确实有些辛苦,但苦中也有乐,在这个团队的任务中,一起的工作可以让我们有说有笑,相互帮助,配合默契。对我们而言,知识上的收获重要,精神上的丰收是可喜的。挫折是一份财富,经历是一份拥有。这次实际操作必将成为我们人生旅途上一个非常美好的回忆! 回顾起此次课程设计,至今仍感慨颇多,的确,自从拿到题目到完成整个编程,从理论到实践,在整整半个月的日子里,可以学到很多很多的东西,同时不仅可以巩固了以前所学过的知识,而且学到了很多在书本上所没有学到过的知识。通过这次课程设计使我懂得了理论与实际相结合是很重要的,只有理论知识是远远不够的,只有把所学的理论知识与实践相结合起来,从理论中得出结论,才能真正为社会服务,从而提高自己的实际动手能力和独立思考的能力。在设计的过程中遇到问题,可以说得是困难重重,这毕竟第一次做的,难免会遇到过各种各样的问题,同时在设计的过程中发现了自己的不足之处,对一些前面学过的知识理解得不够深刻,掌握得不够牢固,比如说结构体,指针……通过这次课程设计之后,我把前面所学过的知识又重新温故了一遍。 同时,在*老师的身上我学得到很多实用的知识,在此表示感谢!同时,对给过我帮助的所有同学和指导老师再次表示忠心的感谢!


C语言实验

哈夫曼编码(Huffman Coding)是一种编码方式,以哈夫曼树—即最优二叉树,带权路径长度最小的二叉树,经常应用于数据压缩。 在计算机信息处理中,“哈夫曼编码”是一种一致性编码法(又称"熵编码法"),用于数据的无损耗压缩。这一术语是指使用一张特殊的编码表将源字符(例如某文件中的一个符号)进行编码。这张编码表的特殊之处在于,它是根据每一个源字符出现的估算概率而建立起来的(出现概率高的字符使用较短的编码,反之出现概率低的则使用较长的编码,这便使编码之后的字符串的平均期望长度降低,从而达到无损压缩数据的目的)。这种方法是由David.A.Huffman发展起来的。 例如,在英文中,e的出现概率很高,而z的出现概率则最低。当利用哈夫曼编码对一篇英文进行压缩时,e极有可能用一个位(bit)来表示,而z则可能花去25个位(不是26)。用普通的表示方法时,每个英文字母均占用一个字节(byte),即8个位。二者相比,e使用了一般编码的1/8的长度,z则使用了3倍多。倘若我们能实现对于英文中各个字母出现概率的较准确的估算,就可以大幅度提高无损压缩的比例。

本文描述在网上能够找到的最简单,最快速的哈夫曼编码。本方法不使用任何扩展动态库,比如STL或者组件。只使用简单的C函数,比如:memset,memmove,qsort,malloc,realloc和memcpy。
因此,大家都会发现,理解甚至修改这个编码都是很容易的。

背景
哈夫曼压缩是个无损的压缩算法,一般用来压缩文本和程序文件。哈夫曼压缩属于可变代码长度算法一族。意思是个体符号(例如,文本文件中的字符)用一个特定长度的位序列替代。因此,在文件中出现频率高的符号,使用短的位序列,而那些很少出现的符号,则用较长的位序列。
编码使用
我用简单的C函数写这个编码是为了让它在任何地方使用都会比较方便。你可以将他们放到类中,或者直接使用这个函数。并且我使用了简单的格式,仅仅输入输出缓冲区,而不象其它文章中那样,输入输出文件。
bool CompressHuffman(BYTE *pSrc, int nSrcLen, BYTE *&pDes, int &nDesLen);
bool DecompressHuffman(BYTE *pSrc, int nSrcLen, BYTE *&pDes, int &nDesLen);
要点说明
速度
为了让它(huffman.cpp)快速运行,我花了很长时间。同时,我没有使用任何动态库,比如STL或者MFC。它压缩1M数据少于100ms(P3处理器,主频1G)。
压缩
压缩代码非常简单,首先用ASCII值初始化511个哈夫曼节点:
CHuffmanNode nodes[511];
for(int nCount = 0; nCount < 256; nCount++)
nodes[nCount].byAscii = nCount;
然后,计算在输入缓冲区数据中,每个ASCII码出现的频率:
for(nCount = 0; nCount < nSrcLen; nCount++)
nodes[pSrc[nCount]].nFrequency++;
然后,根据频率进行排序:
qsort(nodes, 256, sizeof(CHuffmanNode), frequencyCompare);
现在,构造哈夫曼树,获取每个ASCII码对应的位序列:
int nNodeCount = GetHuffmanTree(nodes);
构造哈夫曼树非常简单,将所有的节点放到一个队列中,用一个节点替换两个频率最低的节点,新节点的频率就是这两个节点的频率之和。这样,新节点就是两个被替换节点的父节点了。如此循环,直到队列中只剩一个节点(树根)。
// parent node
pNode = &nodes[nParentNode++];
// pop first child
pNode->pLeft = PopNode(pNodes, nBackNode--, false);
// pop second child
pNode->pRight = PopNode(pNodes, nBackNode--, true);
// adjust parent of the two poped nodes
pNode->pLeft->pParent = pNode->pRight->pParent = pNode;
// adjust parent frequency
pNode->nFrequency = pNode->pLeft->nFrequency + pNode->pRight->nFrequency;
这里我用了一个好的诀窍来避免使用任何队列组件。我先前就直到ASCII码只有256个,但我分配了511个(CHuffmanNode nodes[511]),前255个记录ASCII码,而用后255个记录哈夫曼树中的父节点。并且在构造树的时候只使用一个指针数组(ChuffmanNode *pNodes[256])来指向这些节点。同样使用两个变量来操作队列索引(int nParentNode = nNodeCount;nBackNode = nNodeCount –1)。
接着,压缩的最后一步是将每个ASCII编码写入输出缓冲区中:
int nDesIndex = 0;
// loop to write codes
for(nCount = 0; nCount < nSrcLen; nCount++)
{
*(DWORD*)(pDesPtr+(nDesIndex>>3)) |=
nodes[pSrc[nCount]].dwCode << (nDesIndex&7);
nDesIndex += nodes[pSrc[nCount]].nCodeLength;
}
(nDesIndex>>3): >>3 以8位为界限右移后到达右边字节的前面
(nDesIndex&7): &7 得到最高位.
注意:在压缩缓冲区中,我们必须保存哈夫曼树的节点以及位序列,这样我们才能在解压缩时重新构造哈夫曼树(只需保存ASCII值和对应的位序列)。
解压缩
解压缩比构造哈夫曼树要简单的多,将输入缓冲区中的每个编码用对应的ASCII码逐个替换就可以了。只要记住,这里的输入缓冲区是一个包含每个ASCII值的编码的位流。因此,为了用ASCII值替换编码,我们必须用位流搜索哈夫曼树,直到发现一个叶节点,然后将它的ASCII值添加到输出缓冲区中:
int nDesIndex = 0;
DWORD nCode;
while(nDesIndex < nDesLen)
{
nCode = (*(DWORD*)(pSrc+(nSrcIndex>>3)))>>(nSrcIndex&7);
pNode = pRoot;
while(pNode->pLeft)
{
pNode = (nCode&1) ? pNode->pRight : pNode->pLeft;
nCode >>= 1;
nSrcIndex++;
}
pDes[nDesIndex++] = pNode->byAscii;
}


《C语言程序设计》实验报告

哥哥你太浪漫了,这么难得题!

使用结构数组...短多啦
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#include
struct stuScore
{
char name[20];
int number;
float math;
float comp;
};
void main()
{int i;
float sum[5];
stuScore st[5];
printf("请输入5位学生的学号、姓名、数学成绩、计算机成绩\n");
for(i=0;i<5;i++)
scanf("%d%s%f%f",&st[i].number,st[i].name,&st[i].math,&st[i].comp);
printf("学号\t姓名\t数学\t计算机\t总分\n");
for(i=0;i<5;i++)
{sum[i]=st[i].math+st[i].comp;
printf("%d\t%s\t%6.2f\t%6.2f\t%6.2f\n",st[i].number,st[i].name,st[i].math,st[i].comp,sum[i]);
}
}

使用结构变量,没有使用结构数组...恶长...
#include
struct stuScore
{
char name[20];
int number;
float math;
float comp;
};
void main()
{float sum1,sum2,sum3,sum4,sum5;
stuScore st1,st2,st3,st4,st5;
printf("请输入5位学生的学号、姓名、数学成绩、计算机成绩\n");
scanf("%d%s%f%f",&st1.number,st1.name,&st1.math,&st1.comp);
scanf("%d%s%f%f",&st2.number,st2.name,&st2.math,&st2.comp);
scanf("%d%s%f%f",&st3.number,st3.name,&st3.math,&st3.comp);
scanf("%d%s%f%f",&st4.number,st4.name,&st4.math,&st4.comp);
scanf("%d%s%f%f",&st5.number,st5.name,&st5.math,&st5.comp);
sum1=st1.math+st1.comp;
sum2=st2.math+st2.comp;
sum3=st3.math+st3.comp;
sum4=st4.math+st4.comp;
sum5=st5.math+st5.comp;
printf("学号\t姓名\t数学\t计算机\t总分\n");
printf("%4d\t %s\t %6.2f\t %6.2f\t %6.2f\n",st1.number,st1.name,st1.math,st1.comp,sum1);
printf("%4d\t %s\t %6.2f\t %6.2f\t %6.2f\n",st2.number,st2.name,st2.math,st2.comp,sum2);
printf("%4d\t %s\t %6.2f\t %6.2f\t %6.2f\n",st3.number,st3.name,st3.math,st3.comp,sum3);
printf("%4d\t %s\t %6.2f\t %6.2f\t %6.2f\n",st4.number,st4.name,st4.math,st4.comp,sum4);
printf("%4d\t %s\t %6.2f\t %6.2f\t %6.2f\n",st5.number,st5.name,st5.math,st5.comp,sum5);
}


上一篇:黄褐斑的形成原因

下一篇:道德底线剧情介绍