课程首页地址:http://blog.csdn.net/sxhelijian/article/details/7910565
【目的】
1. 体验用面向对象的方法操作数组和动态链表
2. 体验窗口程序的实现
第一部分 引言
大学中的学习死守着课本非常的没有劲。我不是说课本和课堂没用,而是说在课内的学习之余要有所拓展和扩充。大学的课程(和课本)成为一个体系,受到各种因素的制约,势必会形成一个框框,所涉及的内容可能就会形成“铁路警察,各管一段”的局面。课程和课本是有局限的,采用的是“统一”的体系,而各人形成自己的知识体系却是各有特色的。这是大学学习自由的一部分。
大学生对课程的学习,要习惯于超越“老师讲过的”和“考试要考的”,按照学科(而不是有限学时的课程)和工程需求(而不是仅某门课程中狭窄的知识面)去学,要找到除了考试以外的目标。这个寻找和转变的过程中可能会遇到不少的困难,但却是必须的。其实,转变其实也并不难,也没有诀窍,尝试、体验、总结,直至将新的状态成为自然。
本周的任务就基于这样的考虑。项目1-3体验用面向对象的方法操作数组和动态链表,代表的是将来在工程中更实用的对数据的操纵方法,项目4体验利用VS2008或VC++6.0中提供的向导,体验窗口程序的开发,而后会发现,我们在C++程序设计中所学的,也正是编程中的核心。
第二部分 实践项目
【项目1】建立专门的数组类处理有关数组的操作
数组是几乎所支持的组织数据的方法。C和C++对数组类型提供了内置支持,使我们利用数组实现软件中需要的各种实用的功能。但是,这种支持仅限于用来读写单个元素的机制。C++不支持数组的抽象abstraction,也不支持对整个数组的操作。例如:把一个数组赋值给另外一个数组,对两个数组进行相等比较或者想知道数组的大小size,等等。对C++而言,数组是从C语言中继承来的,它反映了数据与对其进行操作的算法的分离,有浓厚的过程化程序设计的特征。数组并不是C++语言的一等公民。所以在实际项目中,对一个C++程序员,更多的是使用标准库中提供的Vector类型实现数组功能。这个任务也将从面向对象角度重新审视和理解数组,进而扫清自学Vector等标准类中可能存在的障碍。
在下面代码的基础上,完成支持数组操作的类的设计,增强C++内置数组类型功能。
class MyArray { private: int *arr; //用于存放动态分配的数组内存首地址 int size; //数组大小 public: MyArray(int sz=50); MyArray(int a[],int sz); //由一个内置类型的数组初始化 MyArray(const MyArray &A); //复制构造函数 ~MyArray(void); //析构函数,注意释放空间 MyArray&operator =(const MyArray &A); //重载“=”使得数组对象可以整体赋值 bool operator == (MyArray& A); //重载==,使得Array对象能整体判断两个数组是否相等(size相等且对应元素相等) friend ostream& operator << (ostream& out,MyArray& A); //重载<<,输出数组 int GetSize(void) const; //取数组大小; }; //以下为类成员函数的定义 …… //测试函数 int main() { int a[10]= {1,2,3,4,5,6,7,8,9,10}; int b[10]= {4,5,6,7,8,9,10,11,12,13}; MyArray arr1(a,10); //测试用内置的数组初始化新定义的数组对象 MyArray arr2(b,10); MyArray arr3(10); //测试只指定大小的新数组对象的初始化 cout<<arr1; //测试对<<的重载 cout<<arr2; //测试对<<的重载 cout<<arr3; //测试对<<的重载 cout<<"The size of arr1 is: "<<arr1.GetSize()<<endl; //测试GetSize()成员函数 return 0; }
【项目1扩展1(选做)】在MyArray基础上增加下面的成员或友元函数,扩充MyArray类的功能,并修改main函数完成测试。
//重载[],使得Array对象也可以如C++普通数组一样,用a[i]形式取出值【选做】 int& operator[](int i); //重载+,使两个Array对象可以整体相加(前提大小相等)【选做】 MyArray operator + (MyArray& A); //修改数组的大小,如果sz大于数组的原大小,增加的元素初始为;如果sz大于数组的原大小,舍弃后面的元素【选做】 void Resize(int sz);
【项目1扩展2(选做)】规定MyArray只能处理元素为整型的数据未免太弱了,请设计成模板类,使之适应各种类型(事实上,C++增加的标准类对些类情况均设计成了模板类)。
【项目1扩展3(选做)】可以施加于数组的操作还有很多,例如最经典的排序,还有求最大、最小、查找某一元素、截取其中的片段(取从第5个互第10个之间的所有元素,可以形成新的数组对象)、向量乘法,……。将这些操作构造为类的成员函数,开始拓展之旅吧!
【项目2】建立专门的链表类处理有关动态链表的操作
动态链表也是程序设计中的一种非常有用的数据结构。可以说,是否能够理解有关操作的原理,决定了你是否有资格称为“科班”出身。在后续的专业基础课中,相关的内容还会从不同的角度,反复地认识,反复地实践。不过,在现阶段多些体验,也是很有必要的了。
先阅读下面的程序,回顾一下动态链表,阅读程序过程中,请用笔画一画形成链表的过程中指针值的变化。
#include <iostream> using namespace std; struct Student { int num; double score; struct Student *next; }; int main( ) { Student *head=NULL,*p,*q; //建立动态链表 for(int i=0;i<3;i++) { p = new Student; cin>>p->num>>p->score; p->next=NULL; if (i==0) head=p; else q->next=p; q=p; } //输出动态链表 p=head; while(p!=NULL) { cout<<p->num<<" "<<p->score<<endl; p=p->next; } return 0; }
上面一段代码产生的链表形如:
现在我们通过下面的任务,用面向对象的程序设计的思维看待最简单的动态链表,初步体验有关的操作。
现在,请在已有代码的基础上完善程序,完成动态链表的简单操作,程序运行的截图供参考。
class Student //结点类 { public: Student(int n,double s){num=n;score=s;next=NULL;} ~Student(); Student *next; //指向下一个结点 int num; double score; }; class MyList //链表类 { public: MyList(){head=NULL;} MyList(int n,double s); //以Student(n,s)作为单结点的链表 ~MyList(); int display(); //输出链表,返回值为链表中的结点数 void insert(int n,double s); //插入:将Student(n,s)结点插入链表,该结点作为第一个结点 void append(int n,double s); //追加:将Student(n,s)结点插入链表,该结点作为最后一个结点 void cat(MyList &il); //将链表il连接到当前对象的后面 int length(); //返回链表中的结点数 private: Student *head; //链表的头结点 }; //以下为类成员函数的定义 …… //测试函数 int main() { int n; double s; MyList head1; cout<<"input head1: "<<endl; //输入head1链表 for(int i=0;i<3;i++) { cin>>n>>s; head1.insert(n,s); //通过“插入”的方式 } cout<<"head1: "<<endl; //输出head1 head1.display(); MyList head2(1001,98.4); //建立head2链表 head2.append(1002,73.5); //通过“追加”的方式增加结点 head2.append(1003,92.8); head2.append(1004,99.7); cout<<"head2: "<<endl; //输出head2 head2.display(); head2.cat(head1); //把head1追加到head2后面 cout<<"length of head2 after cat: "<<head2.length()<<endl; cout<<"head2 after cat: "<<endl; //显示追加后的结果 head2.display(); return 0; }
运行结果示例:
【项目2扩展一(选做)】项目2中的结点只处理包含包含学号和分数的学生信息。如何将之用于其他应用?结点类Students也可换作描述其他事物的类。请设计建立一个动态链表,其中有5个结点,分别描述5个三角形,从头结点开始,逐个输出三角形的信息。
【项目2扩展二(选做)】上面的处理,仍然不够抽象,所以,只能就事论事地做,这是设计的大忌。实际上,结点的类型可以定义为以下模板类:
template <class T> class Node { public: Node *next; T data; };
这样,“一劳永逸”地解决了data的类型,只要在定义类时,对T进行实例化即可。在这里,T类型中也不需要涉及有关链表中指针的内容。
请按这种思路重写程序,为了测试,在main()函数中建立一个MyArray<double>型对象进行测试。另外,再建立一个MyArray<Triangle>型对象进行测试(Triangle为自定义三角形类)。
【项目2扩展三(选做)】本任务实现的是最简单的单向链表中的最基本的操作。从链表的类型上,还可以有双向链表(有头结点和尾结点,方便从前往后和从后往前的访问)、十字链表等,类似的方法可以构造二叉树、多叉树、图(例如,计算机网络结构可以抽象描述为图,社交网络中用户的关系也是图,顶有用的结构)。从操作角度,单链表在插入时,可以让结点保持有序;可以从链表中查找元素;很多的应用中涉及的算法需要借助于数据结构和算法的设计获得最佳的处理性能。关于这方面的内容不再以具体任务的形式给出,在后续的专业基础课中将会逐渐引出。另外,有程序设计基础,同学们是可以自己往前走一走,找相关的教材和书籍(数据结构、算法类)看一看,是否能依靠自己的力量往前走一走了。
【项目3】Josephus(约瑟夫环)问题
n个小孩子围成一圈,从第一个小孩子开始顺时针方向数数字,到第m个小孩子离开,这样反反复复,最终只剩下一个小孩子,求第几个小孩子留下?
提示:约瑟夫环即是一个首尾相连的链表,在建立好这个环以后,从头结点开始,每次间隔m孩子删除一个结点,直至只余下一个结点(删除了n-1个)。
参考下面的代码,也可以自行设计类。
//链表结点kid,其中number为这个人的编号 struct kid { int number; kid *next; }; //约瑟夫环类 class joseph_ring { private: int n;//用于存放人数 int m;//用于存放初始密码 kid *head;//链表的头结点,初始化时指向1号孩子 public: joseph_ring(int nn, int mm);//创建nn个孩子,间隔为mm的约瑟夫环 ~joseph_ring(); void show();//运算并输出的成员函数 }; //定义joseph_ring类中的成员函数 …… int main() { int n,m; cout<<"n="; cin>>n; cout<<"m="; cin>>m; joseph_ring j(n,m); j.show(); return 0; }
【项目4】窗口程序体验
阅读《C++“窗口”程序设计启蒙(之二)》,按照示例的提示完成三角形面积的求解器。如果不方便使用VS2008,要在VC++6.0下进行实践体验,请看《C++“窗口”程序设计启蒙(之一)》,类似方法完成。
要完成的任务:
1、以示例为基础,为应用程序增加求周长的功能;
2、设计分数类,开发一个窗口式程序,可以完成分数的四则运算。可供参考的一个界面如下图,在报告中展示主要的代码。
拓展(选做):将上面的输入运算符的编辑框换为下拉列表框,使之只能选“+-*/”。
3、(选做)自己设计窗口布局,做一个利息计算器窗口程序。不一定用面向对象的编程(如《C++“窗口”程序设计启蒙(之一)》),完成功能即可。
关于项目4的说明:本项目目标仅在于同学们进行体验。从本项目中可以看出利用向导和可视化界面,自动生成部分代码的开发方法,这可以极大的减轻开发工作量,提供了一个应用程序的框架,这是在工程中非常有利的。在专业的开发中,如果用本项目的“拖”控件方式,C#已经提供了更为方便的操作。但这种方法有时缺乏灵活性,开发出的代码没有特色,只能够满足一般性的要求,更专业的开发要求技术人员掌握MFC,直接利用类族中提供的继承关系进行开发。另外,现在已经有不少比MFC更加优秀的类库可供使用,这将作为进一步学习有关的开发技术需要解决的问题。