资源描述
课 程 设 计 任 务 书
学院
信息科学与工程
专业
电子信息科学与技术
学生姓名
学号
设计题目
1.分数类的设计与实现 2.生命游戏
1.分数类的设计与实现
建立用于完成分数形式运算的类RationalNumber。编写一个测试该类的程序。用整数变量表示类的私有数据(即分子和分母)。给类提供一个能够对所声明的对象初始化的构造函数。为了能够在不提供初始化值的情况下也能对对象初始化,构造函数中应该包含默认的值。构造函数还应该以最简分数的形式存储数据,即2/4应该在对象中存储成分子为1、分母为2的形式。公有成员函数应该有以下功能:
1.两个有理数相加,以最简形式保存结果;
2.两个有理数相减,以最简形式保存结果;
3.两个有理数相乘,以最简形式保存结果;
4.两个有理数相除,以最简形式保存结果;
5.以a/b的形式输出有理数(a是分子,b是分母);
6.以浮点形式输出有理数。
2.生命游戏
生命游戏其实是一个零玩家游戏。它包括一个二维矩形世界,这个世界中的每个方格居住着一个活着的或死了的细胞。一个细胞在下一个时刻生死取决于相邻八个方格中活着的或死了的细胞的数量。如果相邻方格活着的细胞数量过多,这个细胞会因为资源匮乏而在下一个时刻死去;相反,如果周围活细胞过少,这个细胞会因太孤单而死去。
1.如果一个细胞周围有3个细胞为生(一个细胞周围共有8个细胞),则该细胞为生(即该细胞若原先为死,则转为生,若原先为生,则保持不变) 。
2.如果一个细胞周围有2个细胞为生,则该细胞的生死状态保持不变;
3.其它情况下,该细胞为死(即该细胞若原先为生,则转为死,若原先为死,则保持不变)
进度安排:
第17周:分析题目,查阅课题相关资料,进行类设计、算法设计;
第18周:上机调试程序,程序测试与分析,撰写课程设计报告,准备答辩。
指导教师(签字):
年 月 日
学院院长(签字)
年 月 日
目 录
PART I 1
1 需求分析 1
2 算法基本原理 1
2.1 加法 1
2.2 减法 2
2.3 乘法 2
2.4 除法 2
3 类设计 2
4 详细设计 3
4.1 类的接口设计 3
4.2 类的实现 4
4.3 主函数设计 9
5 运行结果与分析 10
5.1 程序运行结果 10
5.2运行结果分析 12
PART II 12
6 基于MFC的图形界面程序开发 12
6.1 基于MFC的图形界面程序设计 12
(1)界面设计 12
(2)代码设计 14
6.2 测试程序 22
6.3 MFC程序编写总结 23
7 参考文献 24
PART I
1 需求分析
(1) 有理数可分为整数和分数也可分为三种,一;正数,二;0,三;负数。除了无限不循环小数以外的实数统称有理数。在数学上有理数的运用相当广泛,所以一款简易的有理数计算器可以大大节约时间,而且不会因为人为误差导致计算结果出错。
(2) 依照它们的序列,有理数具有一个序拓扑。有理数是实数的(稠密)子集,因此它同时具有一个子空间拓扑。采用度量,有理数构成一个度量空间,这是它的第三个拓扑。幸运的是,所有三个拓扑一致并将有理数转化到一个拓扑域。有理数是非局部紧致空间的一个重要的实例。这个空间也是完全不连通的。有理数不构成完备的度量空间;实数是它的完备集。
(3)程序测试数据选择若干种有代表性的数据来进行测试,通过临界数据的测试来判断系统的正确性。
2 算法基本原理
2.1 加法
有理数的加法与小学的加法大有不同,小学的加法不涉及到符号的问题,而有理数的加法运算总是涉及到两个问题:一是确定结果的符号;二是求结果的绝对值。 在进行有理数加法运算时,首先判断两个加数的符号:是同号还是异号,是否有0。从而确定用那一条法则。在应用过程中,一定要牢记"先符号,后绝对值",熟练以后就不会出错了。 多个有理数的加法,可以从左向右计算,也可以用加法的运算定律计算。
法则
1.同号相加,取相同符号,并把绝对值相加。
2.绝对值不等的异号加减,取绝对值较大的加数符号,并用较大的绝对值减去较小的绝对值。互为相反数的两个数相加得0。
3.一个数同0相加,仍得这个数。
定律
Ⅰ.同号相加,取相同符号,并把绝对值相加。
Ⅱ.绝对值不相等的异号两数加减,取绝对值较大的符号,并用较大的绝对值减去较小的绝对值。互为相反数的两个数相加得0。
Ⅲ.一个数同0相加,仍得这个数。
Ⅳ.相反数相加结果一定得0。
2.2 减法
有理数减法法则:减去一个数,等于加上这个数的相反数。其中:两变:减法运算变加法运算,减数变成它的相反数。一不变:被减数不变。可以表示成: a-b=a+(-b)。
2.3 乘法
1.两数相乘,同号为正,异号为负,并把绝对值相乘。例;(-5)×(-3)=15 (-6)×4=-24
2.任何数字同0相乘,都得0. 例;0×1=0
3.几个不等于0的数字相乘,积的符号由负因数的个数决定。当负因数有奇数个数时,积为负;当负因数有偶数个数时,积为正。并把其绝对值相乘。例;(-10)×〔-5〕×(-0.1)×(-6)=积为正数,而(-4)×(-7)×(-25)=积为负数
4.几个数相乘,有一个因数为0时,积为0. 例;3×(-2)×0=0
5.乘积为1的两个有理数互为倒数(reciprocal)。例如,—3与—1/3,—3/8与—8/3。
2.4 除法
1.除以一个数等于乘以这个数的倒数。(注意:0没有倒数)
2.两数相除,同号为正,异号为负,并把绝对值相除。
3.0除以任何一个不等于0的数,都等于0。
4.0在任何条件下都不能做除数。
3 类设计
从上面的算法分析可以看到,本设计面临的计算问题的关键是设计一个有理数运算类。这个类必须能将用户给定的2个整数表达成有理数,同时必须进行合法性检测。同时需要为类设计4个方法,分别来运算加法,减法,乘法和除法,为了使有理数运算看起来更符合自然,所以必须利用C++的重载技术,来对相应的4种运算符进行重载,并且还需要设计一个方法来满足有理数分数形式和小数形式的转换。要从用户输入创建一个有理数,为类设计了一个带有默认参数的构造函数来满足需求。根据以上的分析,设计出CRationalNumber类。如图3.1所示。
图3.1 CRationalNumber类UML图形表示
4 详细设计
整个程序分为三个独立的文档,RationalNumber.h文件中为有理数类CRationalNumber的申明,RationalNumber.cpp文件中包括有理数类的成员函数实现文件;main.cpp文件包括程序的入口函数和测试数据。
4.1 类的接口设计
//RationalNumber.h文件,实现类的声明
class CRationalNumber
{
public:
CRationalNumber (int nt=0,int dt=1);
//带有默认参数的构造函数声明
virtual ~CRationalNumber();
public:
int numtor; //分子
int dentor; //分母
CRationalNumber operator+(CRationalNumber &rn);
//加号运算符重载函数声明
CRationalNumber operator-(CRationalNumber &rn);
//减号运算符重载函数声明
CRationalNumber operator*(CRationalNumber &rn);
//乘号运算符重载函数声明
CRationalNumber operator/(CRationalNumber &rn);
//除号运算符重载函数声明
float ConvertFloat(); //将有理数转换成浮点数
};
ostream& operator << (ostream &,RationalNumber &);
//重载流插入运算符,使分数以(a/b)形式输出函数声明
类CRationalNumber设计了一个带有默认构造参数的构造函数,方便用户构造有理数。同时设计了4个重载函数,可以让使用者像使用普通整数那样对有理数运算进行操作,大大的提高了使用简洁性。
4.2 类的实现
// RationalNumber.cpp文件,类实现
#include "stdafx.h"
#include "RationalCompute.h"
#include "RationalNumber.h"
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CRationalNumber::CRationalNumber(int nt,int dt)
//带有默参数构造函数的定义
{
int temp,num,n,d;
n=nt;
d=dt;
if(n<d)
{temp=n;
n=d;
d=temp;}
while(d!=0) //求分子和分母的最大公约数
{num=n%d;
n=d;
d=num;
}
numtor=nt/n;
//构造函数以最简分数的形式存储数据
dentor=dt/n;
}
CRationalNumber::~CRationalNumber()
{
}
CRationalNumber CRationalNumber::operator+(CRationalNumber &rn) //加号运算符重载函数的定义
{
CRationalNumber temp;
temp.numtor=this->numtor*rn.dentor+rn.numtor*this->dentor;
temp.dentor=this->dentor*rn.dentor;
int temp1,num,n,d;
n=temp.numtor;
d=temp.dentor;
if(n<d) //求分子和分母的最大公约数
{temp1=n;
n=d;
d=temp1;
}
while(d!=0)
{num=n%d;
n=d;
d=num;
}
temp.numtor=temp.numtor/n;
temp.dentor=temp.dentor/n;
return (temp);
}
CRationalNumber CRationalNumber::operator-(CRationalNumber &rn) //减号运算符重载函数的定义
{
CRationalNumber temp;
temp.numtor=this->numtor*rn.dentor-rn.numtor*this->dentor;
temp.dentor=this->dentor*rn.dentor;
int temp1,num,n,d;
n=temp.numtor;
d=temp.dentor;
if(n<d) //求分子和分母的最大公约数
{temp1=n;
n=d;
d=temp1;}
while(d!=0)
{num=n%d;
n=d;
d=num;
}
temp.numtor=temp.numtor/n;
temp.dentor=temp.dentor/n;
return (temp);
}
CRationalNumber CRationalNumber::operator*(CRationalNumber &rn) //乘号运算符重载函数的定义
{ CRationalNumber temp;
temp.numtor=this->numtor*rn.numtor;
temp.dentor=this->dentor*rn.dentor;
int temp1,num,n,d;
n=temp.numtor;
d=temp.dentor;
if(n<d) //求分子和分母的最大公约数
{temp1=n;
n=d;
d=temp1;}
while(d!=0)
{num=n%d;
n=d;
d=num;
}
temp.numtor=temp.numtor/n;
temp.dentor=temp.dentor/n;
return (temp);
}
CRationalNumber CRationalNumber::operator/(CRationalNumber &rn) //除号运算符重载函数的定义
{
CRationalNumber temp;
temp.numtor=this->numtor*rn.dentor;
temp.dentor=this->dentor*rn.numtor;
int temp1,num,n,d;
n=temp.numtor;
d=temp.dentor;
if(n<d) //求分子和分母的最大公约数
{temp1=n;
n=d;
d=temp1;}
while(d!=0)
{num=n%d;
n=d;
d=num;
}
temp.numtor=temp.numtor/n;
temp.dentor=temp.dentor/n;
return (temp);
}
//将有理数转换成浮点数
float CRationalNumber:: ConvertFloat()
{
// 分母为0,返回-1。表示错误。
if ( dentor==0 )
return -1;
float temp;
temp=float(this->numtor)/float(this->dentor);
return (temp);
}
ostream& operator << (ostream &output,RationalNumber &rn) //重载流插入运算符的定义,使分数以(a/b)形式输出
{
if(rn.dentor==0) //分母是0输出error
cout<<"error!"<<endl;
else{
if(rn.numtor>0 && rn.dentor<0)
output<<-rn.numtor<<"/"<<-rn.dentor;
else
output<<rn.numtor<<"/"<<rn.dentor;
return output;
}
}
在类的构造函数中,对用户输入的数据进行了求最大公约数处理,使输入数据看起来最简洁化。而且在所有的运算操作中都对错误的数据(如分母为0)的情况进行了处理。由于运算符<<比较特殊,所以没有将<<重载符放入类的成员函数中,而是作为全局函数来处理,避免与cout<<的冲突。
4.3 主函数设计
//main.cpp主函数
#include " RationalNumber. h"
int main()
{
cout<<" .oO欢迎使用有理数计算器Oo. 请根据提示输入运算数据! "<<endl;
int n1,n2,m1,m2;
cout<<"\n\n请输入第一个分数的分子与分母:"<<endl;
cin>>n1>>n2;
cout<<"请输入第二个分数的分子与分母:"<<endl;
cin>>m1>>m2;
RationalNumber rn1(n1,n2);
//定义类的对象rn1并指定分子与分母值
RationalNumber rn2(m1,m2);
//定义类的对象rn2并指定分子与分母值
RationalNumber rn3,rn4,rn5,rn6;
//定义类的对象rn3,rn4,rn5,rn6不指定分子与分母,用默认参数对其初始化
//调用以浮点形式输出分数函数
rn3=rn1+rn2; //调用加号重载函数
rn4=rn1-rn2; //调用减号重载函数
rn5=rn1*rn2; //调用乘号重载函数
rn6=rn1/rn2; //调用除号重载函数
cout<<rn1<<" 加 "<<rn2<<" 等于: "<<rn3<<" 或者 ";
//调用流插入运算符,使分数以(a/b)形式输出
rn3.showfloat();
//调用以浮点形式输出分数函数
cout<<rn1<<" 减 "<<rn2<<" 等于: "<<rn4<<" 或者 ";
//调用流插入运算符,使分数以(a/b)形式输出
rn4.showfloat();
//调用以浮点形式输出分数函数
cout<<rn1<<" 乘 "<<rn2<<" 等于: "<<rn5<<" 或者 ";
//调用流插入运算符,使分数以(a/b)形式输出
rn5.showfloat();
//调用以浮点形式输出分数函数
cout<<rn1<<" 除 "<<rn2<<" 等于: "<<rn6<<" 或者 ";
//调用流插入运算符,使分数以(a/b)形式输出
rn6.showfloat();
//调用以浮点形式输出分数函数
cout<<"--------计算完成,请按任意键退出系统!"<<endl;
getchar();
getchar();
return 0;
}
在程序的主函数部分,选择了让用户输入操作数据的方式来互动,当用户根据提示来输入相应的数据后,程序会自动运算出每组数据的所有操作结果,如果用户输入数据有错误,则会显示出错误信息。
5 运行结果与分析
5.1 程序运行结果
当输入正确无误的数据后,程序运行结果如图5.1所示。
图5.1程序运行结果
从图2中可以看出,当输入正确的数据后,系统能正确无误的计算出有理数相加,相减,相乘,相除的结果,并能正确转换成小数形式。 当输入有误的数据,如分母为0的时候,程序运行结果如图5.2所示。
图5.2 程序运行结果(错误数据源)
从图5.2中可以看出,当输入错误的数据后,程序会检测出错误数据源,证明了程序的健壮性。当输入的数据分子和分母最大公约数不为1的情况下,程序运行结果如图5.3所示:
图5.3 程序运行结果(有公约数)
5.2运行结果分析
首先当向程序输入正确的数据源后,程序能准确快速的计算出4则运算的结果,当向程序输入分母为0的错误的数据后,程序也同样能够识别。当向程序中输入的分子分母有最大公约数,并且最大公约数不为1的情况下,说明分数可以化简,程序也能很好的识别并给出正确的结果。从运行结果来分析,设计的有理数类完全满足于需求
PART II
6 基于MFC的图形界面程序开发
6.1 基于MFC的图形界面程序设计
MFC的图形界面程序设计可在上述类设计的基础上进行改造,MFC的图形界面程序与DOS界面程序的主要不同点是:MFC图形界面程序与DOS界面程序的输入输出方式不同,DOS界面程序采用字符交互式实现数据输入输出,主要通过cin,cout等I/O流实现,而MFC的图形程序界面采用标准Windows窗口和控件实现输入输出,因此必须在MFC类的框架下加入上面所设计的矩阵和方程组类,并通过图形界面的输入输出改造来完成。
(1)界面设计
首先在VC中建立MFC AppWizard(exe)工程,名称为RationalCompute,并在向导的Step1中选择Dialog based,即建立基于对话框的应用程序,如下图6.1所示
图6.1 建立MFC AppWizard(exe)工程
在弹出的MFC AppWizard对话框中,选择单文档应用程序,其他使用默认值如图6.2,点击完成。
图6.2 选择文档类型
建立应用程序工程骨架,点击确定。如图6.3
图6.3建立工程骨架
此程序已经具备了常见的程序外观,如标题栏,菜单栏,工具栏,状态栏和视图区,程序的框架已经构建起来了。
可以在VC++环境中看到App Wizard已经生成5个类,如图6.4
图6.4,应用程序类视图
(2)代码设计
在CMyView类视图里添加画笔,画出生命游戏中的游戏界面,定义时间函数,定义更新速度以及生命游戏的游戏规则,代码如下:
void CMyView::paint(CDC *p)
{
int a,b,i,j;
for(i=0;i<20;i++)
for(j=0;j<20;j++)
{
p->Ellipse(i*30,j*30,i*30+30,j*30+30);
}
CBrush c;
c.CreateSolidBrush(RGB(0,255,0));
p->SelectObject(c);
srand((int)time(NULL));
for(int x=0;x<=200;x++)
{
a=(int)(20.0*rand()/(RAND_MAX+1.0));
b=(int)(20.0*rand()/(RAND_MAX+1.0));
m[a+1][b+1]=1;
p->Ellipse(a*30,b*30,a*30+30,b*30+30);
}
float time2;
char str[20];
time2=(float)(time1)/1000.0;
sprintf(str,"更新速度:%.2f秒",time2);
p->TextOut(20,630,str,strlen(str));
DeleteObject(c);
}
void CMyView::paint1(CDC *p)
{
int i,j;
for(i=0;i<20;i++)
{for(j=0;j<20;j++)
{
int num=0;
for(int i1=0;i1<3;i1++)
{for(int j1=0;j1<3;j1++)
{
if(num1==1)
{if(m[i+i1][j+j1]==1)
num++;else;}
else
{if(m1[i+i1][j+j1]==1)
num++;else;}
}
}
if(num1==1)
{if(m[i+1][j+1]==1)
num--;else;}
else
{if(m1[i+1][j+1]==1)
num--;else;}
if(num==3)
{
CBrush c;
c.CreateSolidBrush(RGB(0,255,0));
p->SelectObject(c);
p->Ellipse(i*30,j*30,i*30+30,j*30+30);
if(num1==1)
{m1[i+1][j+1]=1;
}
else
{m[i+1][j+1]=1;
}
DeleteObject(c);
}
else if(num==2)
{
if(num1==1)
{m1[i+1][j+1]=m[i+1][j+1];
}
else
{m[i+1][j+1]=m1[i+1][j+1];
}
/*HBRUSH c;
if(m[i+1][j+1]==1)
{
c=CreateSolidBrush(RGB(0,255,0));
::SelectObject(hdc,c);
}
else
{
c=CreateSolidBrush(RGB(255,255,255));
::SelectObject(hdc,c);
}
::Ellipse(hdc,i*30,j*30,i*30+30,j*30+30);
::DeleteObject(c);*/
}
else
{
CBrush c;
c.CreateSolidBrush(RGB(255,255,255));
p->SelectObject(c);
p->Ellipse(i*30,j*30,i*30+30,j*30+30);
if(num1==1)
{m1[i+1][j+1]=0;
}
else
{m[i+1][j+1]=0;
}
DeleteObject(c);
}
}
}
if(num1==1)
num1=0;
else
num1=1;
}
void CMyView::OnDraw(CDC* pDC)
{
CMyDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
paint(pDC);
// TODO: add draw code for native data here
}
/////////////////////////////////////////////////////////////////////////////
// CMyView diagnostics
#ifdef _DEBUG
void CMyView::AssertValid() const
{
CView::AssertValid();
}
void CMyView::Dump(CDumpContext& dc) const
{
CView::Dump(dc);
}
CMyDoc* CMyView::GetDocument() // non-debug version is inline
{
ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMyDoc)));
return (CMyDoc*)m_pDocument;
}
#endif //_DEBUG
/////////////////////////////////////////////////////////////////////////////
// CMyView message handlers
void CMyView::OnTimer(UINT nIDEvent)
{
CDC *p;
p=GetDC();
paint1(p);
CView::OnTimer(nIDEvent);
}
void CMyView::OnFileNew()
{
SetTimer(1,time1,NULL);
}
void CMyView::OnFileOpen()
{
KillTimer(1);
}
void CMyView::OnM1()
{
CDC *p;
p=GetDC();
KillTimer(1);
time1=time1/10;
float time2;
char str[20];
time2=(float)(time1)/1000.0;
sprintf(str,"更新速度:%.2f秒",time2);
p->TextOut(20,630,str,strlen(str));
SetTimer(1,time1,NULL);
}
void CMyView::OnM2()
{
CDC *p;
p=GetDC();
KillTimer(1);
time1=time1*10;
float time2;
char str[20];
time2=(float)(time1)/1000.0;
sprintf(str,"更新速度:%.2f秒",time2);
p->TextOut(20,630,str,strlen(str));
SetTimer(1,time1,NULL);
}
6.2 测试程序
游戏界面如下图6.5,此时游戏未开始,点击开始游戏,则游戏开始,玩家可通过暂停,加速,减速参与游戏,游戏界面上的细胞根据运动规律每秒更新一次,则形成了一幅时刻变化的图。变化中的生命游戏图如图6.6,6.7.
图6.5 游戏开始界面
图6.6 图6.7
6.3 MFC程序编写总结
MFC程序与控制台程序最大的区别就是界面,由于MFC是一个微软公司提供的类库(class libraries),以C++类的形式封装了Windows的API,并且包含一个应用程序框架。其中包含的类包含大量Windows句柄封装类和很多Windows的内建控件和组件的封装类。所以传统的控制台程序只需要学习C++就能编写,但MFC程序必须要先熟悉windows编程,这也是初学者最大的挑战。不熟悉windows编程可以说是对MFC程序无从下手,本系统采用的是MFC的对话框架构,相比于文本文档模式来讲,要简单易懂些。MFC提供的对话框模板大大提高了用户体验的满意度和友好度,所以MFC必将为windows程序员必学的类库。
通过本次课程设计的开发,使我初步掌握了windows程序设计的窍门,也了解了与传统控制台程序之前的差别,可以说C++是基础,会windows编程才能写出更友好实用的应用软件来。通过面向对象的封装与设计实践,是我更进一步理解了什么是面向对象编程。由于本课程设计要求的主要功能比较简单,所以我相信这只是我学习中的一个小台阶,我将以此为基石,在今后的学习过程中不断总结和进步,写希望老师能给予批评指正。
7 参考文献
[1]徐士良. C常用算法程序集. 北京:清华大学出版社,1995
[2]郑莉,董渊,张瑞丰. C++语言程序设计(第3版). 北京:清华大学出版社,2007
[3]钱能. C++程序设计教程(第二版). 北京:清华大学出版社,2007
[4]陈志泊,王春玲. 面向对象的程序设计语言—C++. 北京:人民邮电出版社,2002
[5]李庆扬,王能超,易大义. 数值分析. 湖北:华中理工大学出版社,1986
[6]李爱华,程磊 面向对象程序设计C++语言 清华大学出版社,2010
[7]马秀丽,刘志妩,李筠 C语言程序设计 清华大学出版社,2008
展开阅读全文