CPP课程设计三角形
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
《C++程序设计》课程设计
一、目的
在《C++程序设计》课程学习和课程实验的基础上,利用Visual C++6.0 MFC编程,实现一个交互绘图程序。
通过一定代码量的实际编程,巩固、加深C++概念的理解,提高面向对象编程技术的水平和熟练度,检验《C++程序设计》课程的学习效果,为后续专业课程的学习奠定程序设计基础。
二、任务与要求
利用Visual C++6.0 MFC编程创建单文档多视应用程序框架,用鼠标点击屏幕交互生成三角形,并绘制三角形的外接圆和内切圆,程序运行界面参见下图。
要求:
(1) 用链表记录生成的三角形;
(2) 三角形类中包含顶点、边长、外接圆半径、内切圆半径及圆心等数据成员;
(3) 生成的三角形可以存盘/读出。
程序的基本代码量约为500行,在此基础上可进一步发挥个人的想象力、创造力,为程序添加新的功能,使程序的总量大于1000行。
供参考的功能有:
(1) 图形的放大与缩小;
(2) 图形的移动/旋转/拷贝;
(3) 三角形质量的统计分析(内切圆半径与最长边之比);
(4) 三角形信息的列表显示;
(5) 修改图形的线型/线宽/颜色等。
三、步骤与参考资料
(1)创建单文档、多视应用程序框架;
(2)设计点、线段、三角形类(CPoint2D、CLine、CTriangle代码参见附录);
(3)设计菜单:在WorkSpace中选ResourceV iew的“Menu”,双击IDR_MAINFRAME,就会出现主菜单,在其中添加如图所示的菜单项。
(4)菜单命令映射:给每个子菜单取一个合适的ID号,如:ID_MENU_DRA W_TRIANGLE、ID_MENU_DISPLAY、ID_MENU_SA VE_TRIANGLE、ID_MENU_READ_TRIANGLE,在子菜单上击鼠标右键,选ClassWizard,选菜单的ID号,再选Command,最后点击Add Function
按钮,添加消息映射函数。
依次添加所有菜单命令的消息映射函数。
本例中画三角形子菜单的ID
(5)鼠标消息映射:在具体实现各个子菜单的消息映射函数前,本例需要解决生成三角形的问题。
用鼠标在视口中交互绘制三角形需要映射鼠标动作的消息响应函数,在ClassWizard中选择视口类添加鼠标左键、右键、移动WM_LBUTTONDOWN、WM_RBUTTONDOWN、WM_MOUSEMOVE的响应函数,其中要自己写入交互画三角形的代码,重点是鼠标移动过程中的异或;鼠标消息是标准的,直接从对话框右列表框中选。
注意
(6)响应鼠标动作交互画三角形的代码:
void CTriangleView::OnLButtonDown(UINT nFlags, CPoint point)
{ //以下均为自己添加的程序代码
CPoint2D p;
CDC *pDC=GetDC();
CPen newpen(PS_SOLID,1,RGB(0,255,255));
CPen *pOldpen=pDC->SelectObject(&newpen);
pDC->SetROP2(R2_XORPEN); //异或状态画图
if(drawflag==1)//表示进入画图状态
{
p.x=point.x; p.y=point.y;
if(Pointnum%3==0)
pTriangle=new CTriangle;
//画十字
pDC->MoveTo(point.x,point.y-5); pDC->LineTo(point.x,point.y+5);
pDC->MoveTo(point.x-5,point.y); pDC->LineTo(point.x+5,point.y);
if(Pointnum==0) //第一次点击鼠标左键
{
pTriangle->setTriV ertax(p);
Pointnum++;
pDC->MoveTo(pTriangle->p1.x,pTriangle->p1.y);
pTriangle->p2.x=point.x; pTriangle->p2.y=point.y;
pDC->LineTo(pTriangle->p2.x,pTriangle->p2.y);
}
else if(Pointnum==1) //第二次点击鼠标左键
{
pTriangle->p2.x=point.x; pTriangle->p2.y=point.y;
Pointnum++;
}
else//第三次点击鼠标左键
{
pTriangle->setTriV ertax(p);
//把十字异或
pDC->MoveTo(pTriangle->p1.x,pTriangle->p1.y-5); pDC->LineTo(pTriangle->p1.x,pTriangle->p1.y+5);
pDC->MoveTo(pTriangle->p1.x-5,pTriangle->p1.y); pDC->LineTo(pTriangle->p1.x+5,pTriangle->p1.y);
pDC->MoveTo(pTriangle->p2.x,pTriangle->p2.y-5); pDC->LineTo(pTriangle->p2.x,pTriangle->p2.y+5);
pDC->MoveTo(pTriangle->p2.x-5,pTriangle->p2.y); pDC->LineTo(pTriangle->p2.x+5,pTriangle->p2.y);
pDC->MoveTo(pTriangle->p3.x,pTriangle->p3.y-5); pDC->LineTo(pTriangle->p3.x,pTriangle->p3.y+5);
pDC->MoveTo(pTriangle->p3.x-5,pTriangle->p3.y); pDC->LineTo(pTriangle->p3.x+5,pTriangle->p3.y);
pTriangle->draw(); //画三角形
m_pdlg=new CDlgTriangle(this);
CString str1,str2,str3,str4,str5,str6,str7,str8,str9,str10;
str1.Format("%.2f",pTriangle->a); m_pdlg->m_a=str1; str2.Format("%.2f",pTriangle->b); m_pdlg->m_b=str2;
str3.Format("%.2f",pTriangle->c); m_pdlg->m_c=str3;
str4.Format("%.2f",pTriangle->RcenPoint.x); m_pdlg->m_Rx=str4;
str5.Format("%.2f",pTriangle->RcenPoint.y); m_pdlg->m_Ry=str5; str6.Format("%.2f",pTriangle->R); m_pdlg->m_R=str6; str7.Format("%.2f",pTriangle->rcenPoint.x); m_pdlg->m_rx=str7; str8.Format("%.2f",pTriangle->rcenPoint.y); m_pdlg->m_ry=str8; str9.Format("%.2f",pTriangle->r); m_pdlg->m_r=str9; m_pdlg->m_x1=pTriangle->p1.x; m_pdlg->m_y1=pTriangle->p1.y; m_pdlg->m_x2=pTriangle->p2.x; m_pdlg->m_y2=pTriangle->p2.y; m_pdlg->m_x3=pTriangle->p3.x; m_pdlg->m_y3=pTriangle->p3.y; str10.Format("%.2f",pTriangle->triArea()); m_pdlg->m_s=str8; m_pdlg->Create(IDD_TRIANGLE,this); CTriangleDoc *pDoc=GetDocument(); if(pDoc->ptrHead==NULL) {
pDoc->ptrHead=pTriangle; pTriangle->last=pTriangle; } else { pDoc->ptrHead->last->next=pTriangle; pTriangle->last=pDoc->ptrHead->last; pDoc->ptrHead->last=pTriangle; } pTriangle->next=pDoc->ptrHead; Pointnum=0; } } pDC->SelectObject(pOldpen); CView::OnLButtonDown(nFlags, point); }
///右击之后不再画图drawflag 变为0
void CTriangleView::OnRButtonDown(UINT nFlags, CPoint point) { if(drawflag) { drawflag=0; Pointnum=0; } CView::OnRButtonDown(nFlags, point); }
void CTriangleView::OnMouseMove(UINT nFlags, CPoint point)
{//鼠标移动过程中,总是先异或前次画的图,再画新位置的图,交替更换就形成动态if(drawflag==1&&(Pointnum==1||Pointnum==2))
{
CClientDC dc(this);
CPen pen(PS_SOLID,1,RGB(0,255,255));
CPen *pOldpen=dc.SelectObject(&pen);
dc.SetROP2(R2_XORPEN);
if(Pointnum==1)
{
dc.MoveTo(pTriangle->p1.x,pTriangle->p1.y);
dc.LineTo(pTriangle->p2.x,pTriangle->p2.y);
pTriangle->p2.x=point.x; pTriangle->p2.y=point.y;
dc.MoveTo(pTriangle->p1.x,pTriangle->p1.y);
dc.LineTo(point.x,point.y);
}
else
{
dc.MoveTo(pTriangle->p2.x,pTriangle->p2.y);
dc.LineTo(pTriangle->p3.x,pTriangle->p3.y);
dc.MoveTo(pTriangle->p1.x,pTriangle->p1.y);
dc.LineTo(pTriangle->p3.x,pTriangle->p3.y);
pTriangle->p3.x=point.x; pTriangle->p3.y=point.y;
dc.MoveTo(pTriangle->p2.x,pTriangle->p2.y);
dc.LineTo(pTriangle->p3.x,pTriangle->p3.y);
dc.MoveTo(pTriangle->p1.x,pTriangle->p1.y);
dc.LineTo(pTriangle->p3.x,pTriangle->p3.y);
}
dc.SelectObject(pOldpen);
}
CView::OnMouseMove(nFlags, point);
}
(7)对话框显示生成三角形的信息
void CTriangleV iew::OnDisplay()
{
m_pdlg->ShowWindow(SW_SHOW); //本例用的是无模式对话框,可用有模式对话框//自己写}
(8)链表保存与读出
void CTriangleV iew::OnFilesave()
{
CString filename;
CFileDialog dlg( FALSE,"txt",NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, "txt 文件(*.txt)|*.txt|All Files(*.*)|*.*||");
if(dlg.DoModal() == IDOK)
{
filename = dlg.GetPathName();
}
else
return;
//此处自己添加写链表数据的代码
}
void CTriangleV iew::OnRead()
{
CString filename;
CFileDialog dlg( TRUE,"txt",NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, "txt 文件(*.txt)|*.txt|All Files(*.*)|*.*||");
if(dlg.DoModal() == IDOK)
{
filename = dlg.GetPathName();
}
else
return;
//此处自己添加读链表数据的代码
CClientDC dc(this);
this->OnDraw(&dc);
}
四、课程设计评估
五、供参考的资料
(备注:下列代码仅供参考,不一致或不合理之处可在课程设计当中加以改进)
链表节点类的设计
class EsLinkNode
{
public:
EsLinkNode *next,*last;
public:
EsLinkNode();
virtual ~EsLinkNode();
EsLinkNode * GetNext();
EsLinkNode * GetLast();
void LinkAfter(EsLinkNode *newNode);
void LinkBefore(EsLinkNode *newNode);
//virtual EsLinkNode *NewNode();
friend class EsLink;
public:
virtual BOOL Read(FILE *pfile);
virtual BOOL Write(FILE *pfile);
};
链表节点类的实现
EsLinkNode::EsLinkNode()
{
next=(EsLinkNode *)NULL;
last=(EsLinkNode *)NULL;
//LinkNode specific initialization goes here
}
//destruct function
EsLinkNode::~EsLinkNode()
{
next=(EsLinkNode *)NULL;
last=(EsLinkNode *)NULL;
}
EsLinkNode *EsLinkNode::GetNext()
{
return next;
}
EsLinkNode *EsLinkNode::GetLast()
{
return last;
}
void EsLinkNode::LinkAfter(EsLinkNode *newNode) {
newNode->next=next;
next=newNode;
newNode->last=this;
newNode->next->last=newNode;
}
void EsLinkNode::LinkBefore(EsLinkNode *newNode) {
newNode->last=last;
last=newNode;
newNode->next=this;
newNode->last->next=newNode;
}
BOOL EsLinkNode::Read(FILE *pfile)
{
return TRUE;
}
BOOL EsLinkNode::Write(FILE *pfile)
{ return TRUE;
}
CPoint2D类的设计
class CPoint2D:public CObject
{
public:
double x,y;
public:
CPoint2D(){ x=-1e10; y=-1e10;}
CPoint2D(double x,double y){this->x=x; this->y=y;}
~CPoint2D(){}
void operator=(CPoint2D &p){ x=p.x; y=p.y; }
};
CLine类的设计
class CLine:public CObject
{
public:
double A,B,C; //直线方程系数
public:
CLine(){ A=0; B=0; C=0; }
CLine(double NA,double NB,double NC) {A=NA; B=NB; C=NC;}
~CLine(){}
};
CTriangle类的设计
class CTriangle:public CObject,public EsLinkNode
{
public:
CPoint2D p1,p2,p3; //三角形顶点
double a,b,c; //三角形边长
double R,r; //三角形外接圆半径和内切圆半径
CPoint2D RcenPoint,rcenPoint; //三角形外接圆和内切圆的圆心
public:
CTriangle();
CTriangle(CPoint2D p1,CPoint2D p2,CPoint2D p3);
CTriangle(double x1,double y1,double x2,double y2,double x3,double y3);
~CTriangle(){}
private:
bool InterOfTwoLine(CLine &line1,CLine &line2,CPoint2D &intPoint); public:
void setTriV ertax(CPoint2D &p); //设置顶点
void triEdgeLength(); //计算边长
double triArea(); //计算面积
void Circum(); //计算外接圆圆心
void InscribedCircle(); //计算内切圆圆心
void draw();
BOOL Read(FILE *pfile);
BOOL Write(FILE *pfile);
};
///计算两条直线的交点
bool CTriangle::InterOfTwoLine(CLine &line1,CLine &line2,CPoint2D &intPoint) {
if(line1.A*line2.B==line1.B*line2.A)
{
AfxMessageBox("两直线平行,求交不成功!");
return false;
}
double temp=line1.B*line2.A-line1.A*line2.B;
intPoint.x=(line1.C*line2.B-line1.B*line2.C)/temp;
intPoint.y=(line1.A*line2.C-line1.C*line2.A)/temp;
return true;
}
void CTriangle::Circum()///计算外接圆圆心公式
{ ///计算外接圆半径
if(a==-1e10)
triEdgeLength();
double temp=triArea();
R=a*b*c/(4.0*temp);
CLine L1,L2;
L1.A=p2.x-p1.x; L1.B=p2.y-p1.y; L1.C=(p1.y*p1.y-p2.y*p2.y+p1.x*p1.x-p2.x*p2.x)*0.5000;
L2.A=p2.x-p3.x; L2.B=p2.y-p3.y; L2.C=(p3.y*p3.y-p2.y*p2.y+p3.x*p3.x-p2.x*p2.x)*0.5000;
InterOfTwoLine(L1,L2,RcenPoint);
}
void CTriangle::InscribedCircle()///计算内切圆圆心公式
{ ///计算内切圆半径
if(a==-1e10)
triEdgeLength();
double p;
p=(a+b+c)*0.5;
r=sqrt((p-a)*(p-b)*(p-c)/p);
double temp;
temp=a+b+c;
rcenPoint.x=(a*p3.x+b*p1.x+c*p2.x)/temp;
rcenPoint.y=(a*p3.y+b*p1.y+c*p2.y)/temp;
}
void CTriangle::draw()
{
Circum();
InscribedCircle();
CMDIFrameWnd *pFrame =(CMDIFrameWnd *) AfxGetApp()->m_pMainWnd;
CMDIChildWnd *pChild =(CMDIChildWnd *) pFrame->GetActiveFrame();
CTriangleView *pV iew = (CTriangleV iew *) pChild->GetActiveV iew();
CDC *pDC=pV iew->GetDC();
pDC->MoveTo(p1.x,p1.y);
pDC->LineTo(p2.x,p2.y);
pDC->LineTo(p3.x,p3.y);
pDC->LineTo(p1.x,p1.y);
CBrush *pBrush=CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));
CBrush *pOldBrush=pDC->SelectObject(pBrush);
pDC->Ellipse(int(RcenPoint.x-R+0.5),int(RcenPoint.y-R+0.5),int(RcenPoint.x+R+0.5),int(RcenPoint.y+R+0.5));
pDC->Ellipse(int(rcenPoint.x-r+0.5),int(rcenPoint.y-r+0.5),int(rcenPoint.x+r+0.5),int(rcenPoint.y+r+0.5));
pDC->SelectObject(pOldBrush);
pV iew->ReleaseDC(pDC);
}
BOOL CTriangle::Read(FILE *pfile)
{
if(pfile)
{
// fscanf(pfile,"%f %f %f %f %f %f\n",&p1.x,&p1.y,&p2.x,&p2.y,&p3.x,&p3.y);
float x1=0,y1=0,x2=0,y2=0,x3=0,y3=0,
RR=0,RcenPoint_x,RcenPoint_y,
rr=0,rcenPoint_x,rcenPoint_y;
int flag = fscanf(pfile,"%f %f %f %f %f %f %f %f %f %f %f %f", &x1,&y1,&x2,&y2,&x3,&y3,
&RcenPoint_x,&RcenPoint_y,&RR,
&rcenPoint_x,&rcenPoint_y,&rr
);
if(flag != -1)
{
p1.x = x1; p1.y = y1;
p2.x = x2; p2.y = y2;
p3.x = x3; p3.y = y3;
RcenPoint.x = RcenPoint_x;
RcenPoint.y = RcenPoint_y;
R = RR;
rcenPoint.x = rcenPoint_x;
rcenPoint.y = rcenPoint_y;
r = rr;
return 1;
}
return -1;
}
else
return 0;
}
BOOL CTriangle::Write(FILE *pfile)
{
if(pfile)
{
fprintf(pfile,"%f %f %f %f %f %f %f %f %f %f %f %f\n",
p1.x,p1.y,p2.x,p2.y,p3.x,p3.y,
RcenPoint.x,RcenPoint.y,R,
rcenPoint.x,rcenPoint.y,r
);
return 1;
}
else
return 0;
}
void CTriangleV iew::OnDisplay()
{
m_pdlg->ShowWindow(SW_SHOW);
}
void CTriangleV iew::OnFilesave()
{
/* if(drawflag==0)
{
int numnote=oblist.GetCount();
ofstream outfile;
outfile.open("out.txt",ios::app);
if(!outfile)
MessageBox("Cannot open the file!");
else
{
outfile<<numnote<<endl;
POSITION pos;
pos=oblist.GetHeadPosition();
for(int i=1;i<=numnote;i++,pos++)
{
outfile<<((CTriangle*)(oblist.GetAt(pos)))->p1.x<<" ";
outfile<<((CTriangle*)(oblist.GetAt(pos)))->p1.y<<" ";
outfile<<endl;
}
}
outfile.close();
}*/
CString filename;
CFileDialog dlg( FALSE,"txt",NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, "txt 文件(*.txt)|*.txt|All Files(*.*)|*.*||");
if(dlg.DoModal() == IDOK)
{
filename = dlg.GetPathName();
}
else
return;
FILE *fp;
fp=fopen(filename,"w");
if(fp==NULL)
{
AfxMessageBox("Open File to write Error!");
return;
}
else
{
CTriangleDoc *pDoc=GetDocument();
CTriangle *head=pDoc->ptrHead;
if(head)
{
do
{
head->Write(fp);
head=(CTriangle *)head->next;
}while(head!=pDoc->ptrHead);
}
fclose(fp);
}
}
void CTriangleV iew::OnRead()
{
CString filename;
CFileDialog dlg( TRUE,"txt",NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, "txt 文件(*.txt)|*.txt|All Files(*.*)|*.*||");
if(dlg.DoModal() == IDOK)
{
filename = dlg.GetPathName();
}
else
return;
FILE *fp;
fp=fopen(filename,"r");
if(fp==NULL)
{
AfxMessageBox("Open File to read Error!");
return;
}
else
{
CTriangleDoc *pDoc=GetDocument();
do
{
CTriangle* node=new CTriangle;
if(node==NULL)
break;
int ret = node->Read(fp);
if(-1 == ret)
{
delete node;
break;
}
if(pDoc->ptrHead==NULL)
{
pDoc->ptrHead=node;
node->last=node;
}
else
{
pDoc->ptrHead->last->next=node;
node->last=pDoc->ptrHead->last;
pDoc->ptrHead->last=node;
}
node->next=pDoc->ptrHead;
}while(!feof(fp));
}
fclose(fp);
CClientDC dc(this);
this->OnDraw(&dc);
}
(C++程序设计课程设计报告样式及说明)
C++程序设计
课程设计报告
学生姓名:吴杰晨学号: 041106225 系 (院):信息工程学院计算机系
专业:计算机科学与技术
设计(论文)题目: 学生成绩管理系统
完成日期: 2005年12月30日
指导教师: 徐永安田怀凤
二○○五年十二月
目录
1.设计目的 (1)
2.需求分析 (1)
3.概要设计 (1)
4.测试数据 (2)
5.详细设计
5.1功能设计…………………………………………………………2
5.2类设计 (6)
7.
8.
9.
1、设计目的
1)在具体编程过程中进一步去了解和熟悉面向对象的程序设计思想,努力使自己能够在程序中体
现面向对象的程序设计方法,使自己对面向对象程序语言的三大特性:继承性、封装性、多态性,有更深刻的了解。
2)理解所需完成的任务,会进行初步的需求分析,从用户使用的角度去构思程序的各项功能及与
用户的交互界面,从感性的角度去理解"用户友好"的设计思想,尽量使得用户能以最方便的方式操作自己所编写的程序系统,用MFC尝试基本的交互界面的制作.
3)在设计中进一步熟悉链表这种数据结构,熟练掌握其各项功能的实现,并能够通过MFC中的List
Control控件将相应的结果反馈出来。
4)陪养调试中大型程序的能力,学会利用编译系统中的各种调试功能去协助完成自己的编程与调
试过程,总结编程中常犯的错误,为以后的进一步深入积累实际经验。
2、需求分析
要求实现基本的数据添加、删除、保存、读取等功能。
对MFC的界面分析如下:
1)添加:
初步考虑是弹出一个Dialog来获得用户所输入的数据。
2)删除:
方法一:添加一个删除按钮,当点击后删除选定的项目;
方法二:在所需删除的项目上点击右击,选择删除,也可;
3)保存、读取:
单击相应按钮后通过CFileDialog的对象实现相应的功能。
除此之外,对数据结构的设计将会在类的设计中详细给出。
3、概要设计
具体的实现应分为三个部分,界面部分,功能部分,界面与功能的连接部分。
这是几乎所有Windows程序设计的标准三大部分,图示如下:
有时也许各部分的界限区分并不是很明显,但无论怎样,抓住这三个部分,是有利于理清思路的。
对于以上三个部分,可以打一个比方,一个程序好比一盒软包装的饮料,功能部分是我们想要的,是里面的果汁,但我们不可能说凭空地得到它们,也不可能说是可以直接把整盒饮料连着包装塞进嘴里。
商家为我们设计得很好,饮料的包装盒上已经为我们开好了一个锡纸蒙着的洞,这就是我们程序的界面,也就是接口,英文就是Interface.我们可以通过它来和程序进行交互。
但是,如果锡纸不被吸管捅破,你仍然你得不到你想要的饮料,所以商家想得很周到,往往会为我们提供一根吸管。
吸管即是盒上小洞和里面饮料的连接渠道。
同样道理,我们需要将界面与功能连接起来,于是我们需要有第三个部分,即界面与功能相连接的部分。
4、测试数据
041106243 蠢才 9 9
041106245 庸才 56 44
041106240 全才 88 55
041106250 人才 60 60
041106246 鬼才 44 4
041106244 奇才 88 66
041106225 天才 1 99
041106242 栋才 100 100
以上数据的按排,为以后一些功能的测试留下了方便,在程序的整个编写过程中都将使用这组数据。
为了简便起见,设课程仅为两门。
5、详细设计
5.1功能设计
程序中的各个功能往往被设计成一个一个独立的功能模块,当然这还是在结构化程序设计的影响下而说出的,在面向对象的程序设计思想下,程序被设计成一个一个类,每个类,包含着与其相关的操作。
但其中每个操作的实现,其实还是有应该有一定的顺序的。
有时这些顺序是由功能之间的相互依赖性所决定的,比如在想把存储有学生信息的链表进行排序之前,首先得要链表存在。
还有时,这些顺序是由调用的方便性所决定的,比如文件的读取功能,实现的是从文件将数据读入,这样一来,将来使调试时数据的输入大为简便,从而大大缩短所需的调试时间。
所以,在这里,对功能的实现,个人认为,以下的顺序是可行的:
添加→删除→修改→读取→保存→排序→查找→统计……
其中,由于对于“排序”功能,初使的设计是点击表头后实现排序,且多次点击同一列的表头,将会使排列在顺序和逆序间切换。
在本次课程设计中,由于时间紧迫,将只能完成到排序的功能,后续的功能暂时不作考虑。
另外,在这里还想对大的储存框架做一个说明,如图所示:
数据的存储介质分为内存和外存,内存与外存数据的交互其表现形式也就是读写文件,在这里,外存只有一个具体表现,那就是磁盘上的文件;但内存却有两个表现,一个是我们自己创建的链表,还有一个就是MFC为我们提供的List Control控件。
其实,就其List Control的本质来讲,也是一个链表,只不过这个链表是MFC为我们提供的(从英文名称上就可以看出,链表的英文就应当是list),所以说即使我们在这里将自己的链表丢掉(即将图中虚线框中的步骤省略),也不会影响该程序的所有功能的实现。
但根据本次课程设计的目的,其中有一条就是让我们进一步熟练掌握链表的各项操作与实现,所以自然不能丢掉我们自己的链表。
在笔者所编写的程序中,仅有文件的读取以及排序与链表相连,其余均与链表无关。
原因很简单:我们的链表是个累赘。
5.1.1添加
这里的添加,指用户输入数据,向链表及List Control控件中插入数据,向List Control控件中插入数据的具体实现将会在5.3.1中详述,在这里,先仅讨论向链表中插入数据。
由于链表是由我们程序员自己建立起的一个动态数据存储结构,所以,首先应该判其是否存在。
如果存在,则仅进行结点的插入;相反,如其不存在,则第一步还要建立一个空的链表。
当然,这
只是一种思路,还有一种思路,是在程序一旦被执行,在初始化的时候就创建一个空的链表,这样在后面的所有操作都无需再创建链表。
(前提要保证在程序的运行过程中不会将链表的头结点释放)详见5.3.1的内容。
5.1.2删除
删除功能的实现如果想要通过链表,就必须将所需删除的项目号通过List Control传给我们的链表,然后使链表找到该结点,然后删除,最后将链表中的结果再返回到List Control显示出来。
可以想见,这是一个多么愚蠢的思路。
当然,你也可以通过List Control先执行完删除操作后,再用现在的List Control刷新链表中的内容。
可以想见,这是一个非常不合理的思路,因为到最后你将很容易将数据的版本搞混,而不晓得该是从哪个往哪个传数据。
(反而前面一种方法可以避免,因为它总是以链表为数据的标本。
)
所以,在这里,数据的删除没有和链表挂上。
详见5.3.2的内容。
5.1.3修改
如果和链表连接,同样会遇到上面的麻烦,所以在这里也不通过链表。
详见5.3.3的内容。
5.1.4读取
数据从文件里读取是通过链表的,具体的分为两步:
1)从文件里读取数据,同时向链表中添加结点。
2)当数据全部读取完毕后,将链表中的所有数据再更新到List Control使之显示出来。
5.1.5保存
没有通过链表,其他操作和读取类似。
5.1.6排序
这个功能其实是在最后添加的,在最一开始就已经提出过,排序的实现应当是通过点击列表头来实现的,并且还应该能够支持正序和逆序两种方式,那么这个排序模块应有的接口就很明显了。
接口应有两个,一个是用来排序的表列的列序号,另一个是正序还是逆序的判断标志。
(其实在一开始设计的时候并不一定能够这么到位,像笔者一开始就只留了前面一个接口)
然后就要开始构造模块内部的实现了。
由于前面的多个功能都没有与链表挂钩,感觉是不是有点“离题”,所以在这里,排序功能是通过对链表排序后再返回给List Control来实现的。
下面,就会想到排序代码的通用性问题,因为每一项数据都需要排序,但各个数据的数据变量名与类型都不一样,怎么样才能使排序的代码段通用呢?在参考了网上的一些简单的排序程序后想到了使用下面这段代码:
switch (iSort)
{
case 0:
text1=p1->GetID();
text2=p2->GetID();
break;
case 1:
text1=p1->GetName();
text2=p2->GetName();
break;
case 2:
text1.Format("%3d",p1->GetMath());
text2.Format("%3d",p2->GetMath());
break;
case 3:
text1.Format("%3d",p1->GetEnglish());
text2.Format("%3d",p2->GetEnglish());
break;
}
可以看到,这就是一个switch语句,变量iSort的值是用来排序的表列的列序号,即根据不同的列序号,来将text1和text2赋上值,然后只要用text1和text2去作比较就可以了。
在这里,text1和text2都是CString型的变量。
5.2类的设计
5.2.1CStudent类的设计
CStudent类的定义及说明如下:
class CStudent
{
private:
CString szID;
CString szName;
int iMathScore;
int iEnglishScore;
public:
CStudent *pHead;
CStudent *pPrev;
CStudent *pNext;
// 用来记录链表中有效结点的总数(即总结点数-1),因第一个结点为无效空结点
static UINT nCount;
public:
CStudent();
CStudent(CStudent&);
CStudent(char *,char *,int,int);
~CStudent();
public:
CString GetID ();
CString GetName();
int GetMath();
int GetEnglish();
void SetID (CString);
void SetName(CString);
void SetMath(int);
void SetEnglish(int);
public:
// 链表的操作函数
// 用来从文件里读数据,创建链表
CStudent * CreatLink(FILE *);
// 用来向List Control更新数据
void UpdateLink(CListCtrl &);
// 用来实现链表的排序,iSort是排序的索引,iOrder是排序的顺序
CStudent * SortLink(int iSort,int iOrder);
private:
// 若参数值不为0,则进行当前结点和下一结点的物理位置的交换
int Exchange(int);
} ;
需要说明一下的是Exchange是一个私有函数,在这里,它是用来被SortLink这个成员函数调用,来进行链表中当前结点和下一结点有条件交换的实现。
其余实现部分详见代码。
5.2.2 CDlgListCtrl类的设计
CDlgListCtrl是为我们的主对话框建的一个类,通过Class Wizard为它添加了两个数据成员,分别是:
m_nItem是一个静态文本框的成员变量,它是CString类型的,作用是显示当前的总人数。
其余部分详见代码。
5.2.2 CDlgGetInfo类的设计
这是我们为学生信息输入框这个资源建的一个类,同样通过Class Wizard为它添加数据成员,分别是:
5.3控件List Control的使用
首先在所插入的List Control上右击,选择"properties"(属性)在分页对话框中选择"Styles"(风格),在将其中"View"(视图)项改为"Report"(报告),这样一来,List Control的显示风格就是我们所需要的了。
5.3.1 初始化表列
在Report的风格下,一个横行称为一个Item,一个竖行称为一个Column,记住,Item和Column 的编号都和数组一样,从0开始的。
插入表列的函数是InsertItem,它是一个重载函数,有两种传参数的方式,其中一个是:
int InsertColumn( int nCol, LPCTSTR lpszColumnHeading, int nFormat= LVCFMT_LEFT, int nWidth = -1, int nSubItem = -1 );
举例如下:
m_ListCtrl.InsertColumn(0, "学号", LVCFMT_CENTER, 80);
┇
其实,这里的LVCFMT_CENTER仅仅是一个宏定义,对应着一个16进制的一个值而已,可以在COMMCTRL.H文件的开头看到这样的定义:
#define LVCFMT_LEFT 0x0000
#define LVCFMT_RIGHT 0x0001
#define LVCFMT_CENTER 0x0002
但还有一个问题,就是这个插入表列的操作应该在哪里进行。
让我们理一下思路,我们现在有一个主对话框对象,有一个用来接受数据的对象,另外,还有MFC为我们提供的四个大类形成的对象,而我们现在想要做的是想初始化我们的表列,即使得我们的List Control控件在随着主对话框一起显示出来后即显示出表列,那么好,我们至少确认了一点,那就是应该“随着主对话框一起显示出来后即显示出表列”,这样一来,就要求我们能够在主对话框初始化后得知,并随即初始化我们的List Control控件中的表列。
Windows是"Message based,event driven"(基于消息,事件驱动)的,所以在这里,利用的也是Windows的消息机制,就好像我们单击一个按钮会发出一个消息一样,当主对话框也会发出一个消息,而且这个消息是Windows为我们准备好的:
WM_INITDIALOG
在CDlgListCtrl中为CDlgListCtrl自己映射一个响应WM_INITDIALOG
消息的处理函数OnInitDialog(),然后在函数体中写上刚才所说的插入表列的代码即可。
5.3.2 插入表项
一个记录就是一个表项,插入表项要用到CListCtrl自己的一个成员函数InertItem,它也是一个重载函数,有四种传参数的方式,这里用到的一种,其原形是:int InsertItem( int nItem, LPCTSTR lpszItem );
实现如下:
m_ListCtrl.InsertItem(m_ListCtrl.GetItemCount(),_T(""));
在这里可以先不对所插入表项的第0列的表项文本进行设置,而使用随后将要用到的SetItemText函数进行设置。
它的原形是:
BOOL SetItemText(int nItem,int nSubItem,LPTSTR lpszText);
举例如下:
m_ListCtrl.SetItemText(iPos,0,"041106225");
m_ListCtrl.SetItemText(iPos,1,"天才");
m_ListCtrl.SetItemText(iPos,2,"33");
m_ListCtrl.SetItemText(iPos,3,"44");
这里是我们程序员赋给的数据,如果想要实现用户能够输入数据,把相应的地方用变量传入即可。
5.3.3 删除表项
删除一个表项的函数是DeleteItem,其原形是:
BOOL DeleteItem(int nItem);
很好理解,就是传入一个所需删除的表项的序号就可以了。
5.3.4 修改表项
修改表项的实现和添加表项的实现极其类似,只要将插在表尾,改成在原来的地方修改就可以了。
6、调试分析
这里着重说明一下的就是关于排序功能的调试。
在刚开始时没有注意到类型的问题,将整型的成绩直接赋给了CString型的text1和text2,结果居然是排序功能不受影响。
以为是由CString自动转成了字符串,单步跟踪后发现并非如此,如果将一个int型的赋给一个CString型的变量,其结果和将一个int型的赋给一个char型变量的结果是一样的。
只是相当于赋给了一个ASCII码值为那个整型数的一个字符。
所以说,这个操作在这
里是形得通的,因为我们的成绩虽说是一个整型数,但所限定的范围是在0到100之间,所以,还是能够转换成字符的,但是如果是一些较大的整数和一些较小的整数掺在一些作比较的话,极有可能得出不可思议的解,因为毕竟字符的个数是有限的,其中必存在溢出。
所以这里的转换最后采取了稳妥的方式,用CString自带的一个Format函数进行转换。
这里又有了一个问题,前面的格式控制符为什么用的是"%3d"而不是"%d"呢?
举个例子:如果是成绩50分和成绩100分进行比较,显然应该是100分比50分要高,但程序是怎么做的呢?将50分转为字符串,变为"50";将100分转为字符串,变为"100"。
好了,还没有看出问题,开始比较了,问题来了,字符串的比较是逐位比较大小,一旦分出大小就不再比较。
所以,这里的"50"是大于"100"的,如果再加一个成绩为80分的,那么将会是:
"100"<"50"<"80"
为了避免这样的事情发现,我们使用了"%3d ",这样,50分和80分这样两位数的成绩都变成了"050","080"这样的三位长度的字符串,于是比较就不会再出错了。
另外,在这里还想作几个备忘,因为在这周里帮大家查了好多的错,有几个比较有代表性的错误想在这里提一下。
1)指针空指
这个错误的例子最好举,例如:
CStudent *p;
p->GetMath();
可以看到,指针p还没有任何实际的东西,但程序员却想通过成员函数GetMath去访问成员变量的值,这样的代码无论你是Compile或是Build都不会出错,但一旦你Run,就会报错(除非你在GetMath 中没有对自己的数据成员进行访问)。
当然,如果你真的那样写,在Compile的时候就会有一个warning,告诉你p没有被赋值就被使用,但如果你写成:
CStudent *p=MULL;
p->GetMath();
这样Compile或是Build都不会出错,连warning都不会给你的。
2)局部对象
记得那个错的大致情形是这样的:
可以看到Student.cpp 文件中有一行后面有ERROR 的注释,那就是问题的所在,因
为本程序的作者原意是想将pName 传出去,但去不知传出去的仅仅是一个作为局部对象数组name 的地址,当Add 函数结束后,name 数组的生命周期也随之结束,此时栈空间被释放,所以,此时pName 所指的那个必然是一个不存在的东西。
正确的写法是:
pName =new char[strlen(name)+1];
strcpy(pName,name);
这样,pName 所指的不再是随本函数生命结束而被释放的栈空间,而是指向了一个堆空间,于是,pName 所指的内容被保留出来。
3) 析构函数
一个类可能需要在构造函数内动态分配资源,那么这些动态开辟的资源就需要在对象不复存在之前被销毁掉,那么c++类的析构函数就提供了这个方便。
但如果你调用的那个构造函数里根本没有开辟内存,但你却想在析构函数里释放它,那么就一定会出错。
由于篇幅的缘故在这里就不详述了。
7、用户手册
1) 本程序的开发环境为Microsoft Visual C++ 6.0;
2) 本程序的运行环境为Windows 98/NT 或与其能够正确兼容的环境,程序名为ListCtrl.EXE,运行
时不需要其它任何动态链接库;
3) 程序运行成功后会首先弹出一个主框架,请选择在菜单栏中选择“功能”→“链表”,如图1所
示:
或者您也可以使用快捷键:Ctrl+F 来打开主界面;
4) 打开主界面如图2所示,你可以选择您所需要功能。
⑤ ⑥
① ② ⑦
图1。