学生姓名: 学 号:专业班级:
实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩:
实验一 熟悉Visual C++6.0环境
一、实验目的
1. 熟悉实验设备、环境; 熟练掌握Visual C++6.0环境;
2.掌握调试程序的操作步骤;初步了解程序的结构。
二、实验原理
使用C++语言编写源程序后,源程序在计算机上的实现与其它高级语言源程序实现的原理是一样 的一般都要经过下述三个步骤:
(1) 编辑:即将编写好的C++源程序输入到计算机当中,生成磁盘文件的过程。
(2) 编译:C++语言的编译器可将程序的源代码转换成为机器代码的形式即目标代码,
然后再对目标代码进行连接,生成可执行文件,等待下一步的执行过程。
(3) 运行:即在编译系统中运行源程序经过编译和连接后生成的可执行文件。
三、实验内容
(一) 启动Visual C++ 6.0,输入并调试执行C源程序
1.启动Visual C++6.0开发环境:打开 “Microsoft Visual Studio 6.0”?“Microsoft Visual C++ 6.0”,如图1-1所示。
图1-1
2.编辑源程序文件:单击菜单栏上的“文件(File)”|“新建(New) Ctrl+N”按扭,如图1-2所示;在弹出的“新建”对话框中选中“文件”选项卡,选择“C++ Source File”,并在文件(File)编辑框内输入文件名(本例中为test_1.c),在位置(Location)编辑框中指定文件存放位置,如图1-3所示。注意观察窗口的变化
学生姓名: 学 号:专业班级:
实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩:
图1-2
图1-3
单击“确定(ok)”按钮。
在工作窗口中输入下面的简单程序,注意程序的格式(程序的注释部份也可以输入),如图1-4所示。
学生姓名: 学 号:专业班级:
实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩:
图1-4
#include
void main() /* 主函数,程序的入口。每一个C程序必须有一个主函数,而且只有这一个 */ {
int a, b, c; /* 定义三个整型变量 */
a=2; b=3; /* 给两个变量赋值 */
c = a+b; /* 计算 */
printf("%d, %d\n", c, a+b); /* 用整数方式输出运算结果,并换行。比较c的值与a+b的
值相同吗? */
}
提示:当输入完或修改了源程序后,注意保存源程序文件。
3.编译源程序:打开“组建(Build)|编译(Compile)”菜单的“编译 Test_1.c Ctrl+F7”命令(或者按快捷键Ctrl+F7),将程序编译成目标文件,其扩展名为.OBJ,如图1-5所示。注意观察窗口的变化。
学生姓名: 学 号:专业班级:
实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩:
图1-5
若程序编译后有语法错,需要回到工作窗口,改正错误后重新编译程序。
注意观察工作窗口下方信息窗口中的提示信息,结合程序中错误的语句理解提示信息的含义。
特别提醒:改正程序中的错误时,每次都要从第一个错误开始。
注意:若此时语言系统开始编译后不能停下来(编译微型工具栏变灰),则可先存盘(或复制),结束Visual C++ 6.0,然后重新启动Visual C++ 6.0,打开源程序再编译。
4.连接目标程序:打开“组建(Build)”菜单的“构件 Test_1.exe F7”命令(或者按快捷键F7),将程序连接成可执行文件,其扩展名为.exe,如图1-6所示。 同样,若程序连接后有错,需要回到工作窗口,改正错误后重新编译、连接程序。
学生姓名: 学 号:专业班级:
实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩:
图1-6
5.执行程序:打开“组建(Build)|执行(Execute)”菜单的“!执行Test_1.exe Ctrl+F5”命令(或者按快捷键Ctrl+F5),执行Test_1.exe文件,执行结果如图1-7所示:
图1-7
此时,按下任意键就可以关闭输出窗口,回到Visual C++ 6.0集成环境。
至此,已经完成了一个C源程序上机执行的过程。但是,程序中可能还存在着因编程者对问题解决方案的错误理解导致程序运行结果不符合问题要求的情况,称逻辑错误。这类错误最难排除。
要排除逻辑错误,首先要找到错误发生的地方。可以借助集成环境中的调试(Debug)工具(或在关键点上插入输出语句)来观察相关变量的值是否与编程者设想的一致。当发现某处不一致时,就定位出逻辑错误所在的源代码。
完成上机任务后,可以退出Visual C++6.0环境。
6.退出Visual C++6.0环境:打开“文件”菜单选择“退出(Exit)”命令即可。
若上机过程中需要调试另一个C源程序,可以关闭当前源程序而不退出Visual C++6.0环境。关闭工作区:打开“文件”菜单选择“关闭 工作区(Close Workspace)”命令即可关闭当前正在工作的文件,如图1-8所示。
学生姓名: 学 号:专业班级: 实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩:
图1-8
再次上机时,可以调出已存储在磁盘上的C源程序,查看、修改或编译执行。
7.打开一个已经存在的程序:打开“文件”菜单选择“打开(Open)”命令选项,即可弹出相应的对话框,选择待打开的文件,双击该文件名或者单击“打开”按钮,可重复上面的3~5步完成编辑、编译、链接、运行等操作。
四、实验思考讨论与心得体会
(1)思考讨论
1.完成本实验需要哪些语句结构。
答:此实验相对比较简单,只是使用了表达式语句结构。
2.你是怎么把命令发给计算机,并要求其执行的?
C程序将我们的C++高级语言编译形成计算机本身所能识别的机器语言,进而实现计算机对我们所发送给它的指令的执行。
(2)心得体会
C++是一种面向对象的程序设计语言,使用它可以实现面向对象的设计。通过此次实验,我熟练地掌握了它的基本用法,这对以后进一步的编程、调试奠定了基础。另一方面,就是感觉整个流程走下来,还是相对蛮简单以及人性化。
学生姓名: 学 号:专业班级:
实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩:
实验二 构造函数和析构函数
一、实验目的
1、清楚构造函数的作用,掌握构造函数的声明、定义方法;
2、掌握重载构造函数、带参数构造函数的定义、应用;
3、熟悉用参数列表初始化数据成员的书写方式;
4、清楚析构函数的作用,掌握析构函数的声明、定义方法;
5、熟悉构造函数、析构函数的调用顺序。
二、实验原理
构造函数和析构函数是在类体中说明的两种特殊的成员函数。构造函数的功能是在创建对象时,用给定的值对对象进行初始化。析构函数的功能是用来释放一个对象,与构造函数的功能刚好相反。
三、实验程序
#include
using namespace std;
class Test
{
private:
int x;
public:
Test()
{
cout<
x=0;
}
~Test()
{
cout<
}
void print()
{
cout<
}
};
void main()
{
Test obj1,obj2; //创建对象时,自动调用构造函数
obj1.print();
obj2.print();
}
学生姓名: 学 号:专业班级:
实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩:
四、实验结果及程序分析
(1)实验结果:将该实验程序放于visual c++6.0上运行,得到如下结果,见截图所示:
(2)程序分析
该程序中,定义了两个对象obj1和obj2,并对它们进行了初始化,初始化是由构造函数实现的,而构造函数又是自动调用的。从输出结果中可以看出,调用了两次构造函数。析构函数在该程序中也被调用了两次,同时也是自动调用的。
五、实验思考讨论与心得体会
1.思考讨论
(1)析构函数有什么作用?在书写时,与构造函数有什么相同点和不同点?
答:作用:析构函数是用来释放对象的。在书写时,只要在构造函数的前面加上“~”即可。
(2)程序中的this代表什么?this能否写在main()函数中?
答:this代表一个指向正在对某个成员函数操作的对象的指针。不能写在main()函数中,只能在一个类的成员函数中调用,表示当前对象的地址。
(3)构造函数、析构函数的调用顺序怎样?
答:先调用构造函数再调用析构函数。
2.心得体会
我们在对类的对象进行初始化时要调用构造函数,另一方面,释放时要调用析构函数。通过此次实验,我掌握了二者的基本用法和其各自独具的特点,对以后类的学习及使用大有裨益。
实验三 常类型与类型转换 学生姓名: 学 号:专业班级: 实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩:
一、实验目的
(1)熟悉常类型的定义、内容以及类型转换中自动隐式转换和强制转换两种类型的转换规则。
(2)掌握定义或说明常类型量时进行初始化的方法
(2)学会如何定义类型转换函数及其使用。
二、实验原理
常类型是指使用类型修饰符const说明的类型,常类型的变量或对象的值是不能被更新的。常类型包括一般常量和对象常量、常指针和常引用、常成员函数和常数据成员。它们都有自己的定义和使用规则,要求熟悉掌握。
类型转换可将一种类型的值映射为另一种类型的值,分为自动隐式转换和强制转换,其中,强制转换又包括构造函数的类型转换与用户自己定义的类型转换函数,它们在实际应用中都发挥着一定的作用。
三、实验程序
(1)常类型
(2)类型转换
学生姓名: 学 号:专业班级:
实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩:
四、实验结果及程序分析
(1)常类型
结果如下截图所示:
学生姓名: 学 号:专业班级:
实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩:
程序分析:该程序中,说明了如下三个常类型数据成员:
const int &r
const int a
static const int b
其中,r是常int 型引用,a是常int型变量,b是静态常int量,而且静态数据成员b是在类体外进行初始化的。这一点应注意。
(2)类型转换
结果如下截图所示:
学生姓名: 学 号:专业班级:
实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩:
程序分析:表达式语句d+=r之所以能够顺利进行,是因为该程序使用了类型转换函数operator double()。为使上述加法能够顺利进行,编译系统先检查Rational的说明,看是否存在一个类型转换函数能够将Rational类型的操作数转换为double类型的操作数。由于Rational类中说明了类型转换函数operator double(),所以上述类型转换成功。
五、实验思考讨论与心得体会
1.思考讨论
(1)判断对错:int const m =15;m=18;
答:错误。因为前面定义了m是一个常量,并且对它初始化了,因此不能改变m的值了。
(2)自动隐式转换规则有哪些?我们在定义类型转换函数时应注意什么?
答:自动隐式转换规则:算术运算时,低类型到高类型;赋值表达式中,右边=左边;函数调用时,实参转换为形参的类型;函数有返回值时,返回值类型转换为函数类型。 定义类型转换函数时应注意:它是非静态的成员函数,不可以有返回值,不带任何参数,不能定义为友元函数。
2.心得体会
通过此次实验,我收获颇丰。我不仅仅解决了以前关于类型转换这样那样的问题,还全面的了解到了类型转换函数这个新鲜事物。当然,常类型以及自动隐式转换中也有之前不为我所知的。我想,此次实验之后,以后再碰到程序中关于类型不符的问题都可以迎刃而解了。
学生姓名: 学 号:专业班级:
实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩:
实验四 派生类的构造函数与虚基类
一、实验目的
1. 掌握单继承和多重继承下派生类的定义方法,理解基类成员在不同的继承方式下不同的访问属性;
2. 正确定义派生类的构造函数与析构函数,理解定义一些个派生类对象时各个构造函数、析构函数被调用的顺序;
3. 正确定义虚基类,消除在多层次多重继承方式下顶层基类中成员访问的二义性问题,关注此时各构造函数、析构函数的调用顺序。
4.通过基类与公有派生类的定义,及基类对象、指针、引用与派生类的对象、地址间相互赋值的方法,正确理解赋值兼容的4种情况,通过程序理解其不可逆性。
二、实验原理
1.派生类的数据成员由所有基类的数据成员与派生类新增的数据成员共同组成,如果派生类新增成员中包括其他类的对象(子对象),派生类的数据成员中实际上还间接包括了这些对象的数据成员。因此,构造派生类的对象时,必须对基类数据成员、新增数据成员和成员对象的数据成员进行初始化。派生类的构造函数必须要以合适的初值作为参数,隐含调用基类和新增对象成员的构造函数,来初始化它们各自的数据成员,然后再加入新的语句对新增普通数据成员进行初始化。
派生类构造函数的一般格式如下:
::() : (), ??,
(),
(),
??,
()
{
//派生类新增成员的初始化}
2. 在多继承中,如果类B1和类B2分别继承了类A,,类C又继承了类B1和B2。那么,在
学生姓名: 学 号:专业班级:
实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩:
该结构中,类C的对象将包含两个类A的子对象。由于类A是派生类C两条继续路径上的一个公共基类,那么这个公共基类将在派生类的对象中产生多个基类子对象。假如要想使这个公共基类在派生类中只产生一个基类子对象,则必须将这个基类设定为虚基类。因此为了彻底避免在这种结构中的二义性,在创建派生类对象时对公共基类的数据成员只进行一次初始化,便引入了虚基类。
虚基类说明格式如下:
virtual
其中,virtual是虚类的关键字。虚基类的说明是用在定义派生类时,写在派生类名的后面。例如:
class A
{
public:
void f();
PRotected:
int a;
};
class B : virtual public A
{
protected:
int b;
};
三、实验程序
(1)派生类的构造函数
#include
class A
{
public:
A()
{a=0;}
A(int i)
{a=i;}
void print()
{cout< private: int a; }; class B:public A 学生姓名: 学 号:专业班级: 实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩: { public: B() {b1=b2=0;} B(int i){b1=i;b2=0;} B(int i,int j,int k):A(i),b1(j),b2(k) {} void print() {A::print();cout< private: int b1,b2;}; void main() { B d1; B d2(5); B d3(4,5,6); d1.print(); d2.print(); d3.print();} (2)虚基类 #include class A { public: A(const char *s) { cout< ~A() {}}; class B : virtual public A {public: B(const char *s1, const char *s2):A(s1) {cout< class C : virtual public A {public: C(const char *s1, const char *s2):A(s1) {cout< class D : public B, public C {public: D(const char *s1, const char *s2, const char *s3, const char *s4) :B(s1, s2), C(s1, s3), A(s1) {cout< void main() 学生姓名: 学 号:专业班级: 实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩: {D *ptr = new D("class A", "class B", "class C", "class D"); delete ptr;} 四、实验结果及程序分析 (1)派生类的构造函数: 程序分析:该程序中,派生类B中定义了三个构造函数,前两个构造函数没有显示地调用基类构造函数,其实隐式地调用了基类A中的默认构造函数。而第三个构造函数则显示地调用了A中的第二个构造函数。 (2)虚基类 学生姓名: 学 号:专业班级: 实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩: 程序分析:在派生类B和C中使用了虚基类,使得建立的D类对象只有一个虚基类子对象。在派生类B,C,D的构造函数的成员初始化列表中都包含了对虚基类A的构造函数。 而在建立类D对象时,只有类D的构造函数的成员初始化列表中列出的虚基类构造函数被调用,并且仅调用一次,而类D基类的构造函数的成员初始化列表中列出的虚基类构造函数不被执行。这一点将从该程序的输出结果可以看出。 五、实验思考讨论与心得体会 1.思考讨论 (1)从此次实验中,可以得到关于派生类构造函数怎样的调用顺序? 答:基类的构造函数; 子对象的构造函数(如果有的话); 派生类构造函数。 (2)对于虚基类的实验中,如果没有使用虚基类,结果将如何?对比二者,可以得出什么样的结论? 答:无虚基类: class A class B class A class C 学生姓名: 学 号:专业班级: 实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩: class A class D 由此可以得到结论:含有虚基类的派生类的构造函数只被调用一次,其引入的目的就是为了解决多继承的二义性问题。 2.心得体会 派生类的构造函数和虚基类是在类使用过程中举足轻重的方面,我们如果掌握了其用法,必然可以为我们减少不必要的麻烦以及减轻编写程序的繁重负担。特别是对于虚基类,我们要加以善用。 实验五 运算符重载 学生姓名: 学 号:专业班级: 实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩: 一、实验目的 1.掌握用成员函数重载运算符的方法 2.掌握用友元函数重载运算符的方法 3.理解并掌握利用虚函数实现动态多态性和编写通用程序的方法 二、实验原理 C++中预定义的运算符的操作对象只能是基本数据类型。但实际上,对于许多用户自定义类型(例如类),也需要类似的运算操作。这时就必须在C++中重新定义这些运算符,赋予已有运算符新的功能,使它能够用于特定类型执行特定的操作。运算符重载是通过创建运算符函数实现的。运算符函数定义的一般格式如下: operator () { } 运算符函数重载一般有两种形式:重载为类的成员函数和重载为类的非成员函数。非成员函数通常是友元。这两种形式都可访问类中的私有成员。 1) 运算符重载为类的成员函数的一般格式为: operator () { } 2) 运算符重载为类的友元函数的一般格式为: friend operator () { } 三、实验程序 1.运算符重载为成员函数 #include #include using namespace std; class Complex { public: Complex(){real=0;imag=0;} Complex(double r,double i){real=r;imag=i;} 学生姓名: 学 号:专业班级: 实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩: Complex operator + (Complex &c2); Complex operator - (Complex &c2); Complex operator * (Complex &c2); Complex operator / (Complex &c2); void display(); private: double real; double imag; }; Complex Complex::operator+(Complex &c2) { Complex c; c.real=real+c2.real; c.imag=imag+c2.imag; return c; } Complex Complex::operator-(Complex &c2) { Complex c; c.real=real-c2.real; c.imag=imag-c2.imag; return c; } Complex Complex::operator*(Complex &c2) { Complex c; c.real=real*c2.real-imag*c2.imag; c.imag=imag*c2.real+real*c2.imag; return c; } Complex Complex::operator/(Complex &c2) { Complex c; c.real=(real*c2.real+imag*c2.imag)/(c2.real*c2.real+c2.imag*c2.imag); c.imag=(imag*c2.real-real*c2.imag)/(c2.real*c2.real+c2.imag*c2.imag); return c; } void Complex::display() {cout<
学生姓名: 学 号:专业班级: 实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩: Int main() { Complex c1(1,2),c2(-2,4),c3; while (1) { int a; cout< cin>>a; switch (a) { case 1: { c3=c1+c2; cout< c3.display(); }break; case 2: { c3=c1-c2; cout< c3.display(); }break; case 3: { c3=c1*c2; cout< c3.display(); }break; case 4: { c3=c1/c2; cout< c3.display(); }break; case 0: { a=0; cout< return a; 学生姓名: 学 号:专业班级: 实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩: } default: break; } } return 0; } 2. 运算符重载为友元函数 #include class complex //复数类声明 { public: complex(double r=0.0,double i=0.0) {real=r;imag=i;} //构造函数 friend int operator> (complex c1,complex c2); //运算符">"重载友元函数 friend int operator //运算符" friend int operator ==(complex c1,complex c2); //运算符"=="重载友元函数 void display( ); //显示复数的值 private: //私有数据成员 double real; double imag; }; complex complex::operator>(complex c1,complex c2) { if(strcmp(complex c1,complex c2)>0) return true; else return false; } complex complex::operator { if(strcmp(complex c1,complex c2)<0) return true; 学生姓名: 学 号:专业班级: 实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩: else return false; } complex complex::operator==(complex c1,complex c2) { if(strcmp(complex c1,complex c2)==0) return true; else return false; } void compare(complex complex c1,complex complex c2) { if(operator>(complex c1,complex c2)==1) { complex c1.display();cout<";complex c2.display(); } else if(operator { complex c1.display();cout< } if(operator==(complex c1,complex c2)==1) { complex c1.display();cout< } cout< } void complex::display() { cout< } int main() { complex complex c1("1+2i"),complex complex c2("3+4i"); compare(complex c1,complex c2); return 0; } 四、实验结果及程序分析 1.运算符重载为成员函数 运行结果 学生姓名: 学 号:专业班级: 实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩: 程序分析:该程序中定义了一个complex类,在该类中定义了4个成员函数作为运算符重载函数。由于我们所实现的加减乘除是双目运算符,因此运算符重载函数只有一个参数c2.而对于单目运算符重载为成员函数时,不能再显示说明参数,总是隐含了一个参数即this指针,它是指向调用该成员函数对象的指针。 2.运算符重载为友元函数 运行结果: 程序分析:对于双目运算符,重载为成员函数时,仅一个参数,另一个被隐含;而重载为友元函数时,有两个参数,没有隐含参数。 五、实验心得体会 通过本实验的学习,我掌握了运算符重载。运算符重载函数就是对一个已有的函数赋予新的含义,使之实现新的功能。因此,同一函数名就可以用来代表不同功能的函数,也就是一名多用。在本次的实验中,我更加深刻体会了运算符重载的方法。运算符重载的方法是定义一个重载运算符的函数,在需要执行被重载的运算符时,系统就自动调用该函数,以实现相应的运算。也就是说,运算符重载是通过定义函数实现的。运算符重载实质上是函数的重载。 实验六 动态联编 学生姓名: 学 号:专业班级: 实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩: 一、实验目的 1.熟悉动态联编的概念和特点以及它与静态联编之间的区别。 2.掌握动态联编方式的使用方法。 二、实验原理 联编就是将模块或者函数合并在一起生成可执行代码的处理过程,同时对每个模块或者函数调用分配内存地址,并且对外部访问也分配正确的内存地址,它是计算机程序彼此关联的过程。按照联编所进行的阶段不同,可分为两种不同的联编方法:静态联编和动态联编。 动态联编是指在程序执行的时候才将函数实现和函数调用关联,因此也叫运行时绑定或者晚绑定,动态联编对函数的选择不是基于指针或者引用,而是基于对象类型,不同的对象类型将做出不同的编译结果。C++中一般情况下联编也是静态联编,但是一旦涉及到多态和虚拟函数就必须要使用动态联编了。 三、实验程序 1. #include class A { public: virtual void f() {cout< }; class B:public A { public: virtual void f() {cout< }; void main() { B b; A &r=b; void (A::*pf)()=A::f; (r.*pf)(); } 2. #include #include 学生姓名: 学 号:专业班级: 实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩: /** *Shape */ class CShape { public: CShape(){} virtual ~CShape(){} virtual void Draw() = 0; }; /** *Point */ class CPoint : public CShape { public: CPoint(){} ~CPoint(){} void Draw() { printf("Hello! I am Point!/n"); } }; /** *Line */ class CLine : public CShape { public: CLine(){} ~CLine(){} void Draw() { printf("Hello! I am Line!/n"); } 学生姓名: 学 号:专业班级: 实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩: }; void main() { CShape* shape = new CPoint(); //draw point shape->Draw();//在这里shape将会调用CPoint的Draw()函数 delete shape; shape = new CLine(); //draw Line shape->Draw();//而在这里shape将会调用CLIne 的Draw()函数 delete shape; return ; } 四、实验结果及程序分析 1.实验结果 学生姓名: 学 号:专业班级: 实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩: 程序分析:该程序说明当使用指向类的成员函数的指针来标识虚函数时,在公有继承的条件下,对该虚函数的调用采取动态联编。 2实验结果 程序分析:这个例子,形象地说明了一个Draw()函数可以有两种实现,并且是在运行时决定的,在编译阶段不知道,也不可能知道!只有在运行的时候才能知道我们生成的shape是哪种图形,而且要实现这种效果必须采取动态联编。在基类我们会把想要多态的函数声明为虚函数,而虚函数的实现原理就使用了动态联编。该程序完全符合采取动态联编的条件。 五、实验思考讨论与心得体会 1.思考讨论 (1)如果在实验一中不说明f()为虚函数,结果又如何? 答:此时结果为: A::f() called. (2)通过上述实验总结动态联编实现的条件。 答:子类型关系成立或者为公有继承;具有说明的虚函数;使用对象指针或对象引用调用虚函数,或者在成员函数中调用虚函数。 2.心得体会 虚函数是动态联编的基础。虚函数是成员函数,而且是非静态的。当我们看到程序中说明了虚函数时,就应该考虑能否采用动态联编。而此时,我们再对照动态联编实现的条件一一比较,若满足,则采用;若不满足,则放弃,而采用静态联编。当然,动态联编的具体用法有待于我在实践中慢慢熟悉并加以应用。 学生姓名: 学 号:专业班级: 实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩: 实验七 扑克牌游戏程序设计 一、实验目的 运用所学过的知识编写一个扑克牌游戏程序,加深对C++语言各方面的理解。 二、实验原理 程序要求如下: 1. 创建一副扑克牌,要求选择没有大小王的扑克牌。 2.参与扑克牌游戏的人数为4,每人13张。 3.重新初始化整副扑克牌。 4.洗牌,并显示。 5.发牌,并显示所有打牌者的情况。 三、实验程序 #include #include #include //用枚举,定义四种花色 enum suit {clubs, diamonds,hearts,spades}; //重定义int 为 pips typedef int pips; //建立扑克牌的数据类型,s 代表 花色 ,p 代表 点数 struct card { suit s; pips p; }; //建立52张扑克牌的数组 struct deck { card d[52]; }; //用函数的参数N来获取一个 1 到 13的随机数 作为牌的点数 pips assign_pips(int n) { return (n%13+1); 学生姓名: 学 号:专业班级: 实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩: }; //用函数的参数N来获取四种花色中的一种 suit assign_suit(int n)\ { return (static_cast } //用上面的函数来制造扑克牌的数据 void assign(int n, card& c) { c.s= assign_suit(n); c.p= assign_pips(n); } //获取当前牌的点数 pips get_pip(card c) { return c.p; } //获取当前牌的花色 suit get_suit(card c) { return c.s; } //输出扑克牌C的花色 void print_card(card c) { cout< switch(c.s) { case clubs: cout< break; case diamonds: cout< break; case hearts: 学生姓名: 学 号:专业班级: 实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩: cout< break; case spades: cout< } } //用上面的函数来输出52张扑克牌 void print_deck(deck& dk) { for (int i=0; i<52;++i) print_card(dk.d[i]); } //这个函数用来洗牌的 void shuffle(deck& dk) { card t; for (int i=0; i<52;++i) { int k=(rand()%(52-i)); t=dk.d[i]; dk.d[i]= dk.d[k]; dk.d[k]=t; } } //这个函数用来给牌 void deal(int n, int pos, card* hand, deck& dk) { for (int i=pos; i { hand[i-pos+1]=dk.d[i]; print_card(dk.d[i]); //cout< //cout< } cout< } 学生姓名: 学 号:专业班级: 实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩: //初始化52张扑克牌 void init_deck(deck& dk) { for (int i=0; i<52;++i) assign(i, dk.d[i]); } void main() { srand(time(NULL)); // 用时间来重置随机数种子 deck desk; card hand1[13],hand2[13],hand3[13],hand4[13]; init_deck(desk); cout< print_deck(desk); cout< shuffle(desk); print_deck(desk); cout< deal(13,1,hand1,desk); deal(13,14,hand2,desk); deal(13,26,hand3,desk); deal(13,38,hand4,desk); } 四、实验结果及程序分析 实验结果 学生姓名: 学 号:专业班级: 实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩: 程序分析:扑克牌的实现过程见程序中的注释所示: 总的来说,本程序就是通过用枚举定义4种花色,并建立了一个包括扑克牌花色和点数数据类型的结构体以及包括52张牌的数组,然后再通过调用函数来实现获取一个1 到 13的随机数作为牌的点数、4种花色中的一种以及它们的初始化等与输出,进而实现扑克牌游戏程序中整个的洗牌和发牌过程。 四、实验思考讨论与心得体会 1.思考讨论: (1)详述程序的洗牌原理与过程 答:我们需要增加一些数组的功能,并通过重载,让card的对象具有随机存取的功能。因为在洗牌的时候,不仅要用到card的先进后出的性质,也要用到随机读取的功能。随机的实现,运用的是老师在短学期补充的生成随机数的方法:包含了头文件stdlib.h、time.h。 srand((unsigned)time(NULL)); //time.h 每个伪随机数都是通过在内部定义的一个特殊“数字压碎函数”由上一个伪随机数生成的,第一个伪随机数是由一个内部定义的变量生成的,该变量称为这个序列的种子。在默认情况下,在程序每次运行时种子由计算机初始化为同一个值(默认为1),为了避免这种破坏随机性的现象,可以用srand( )函数来选择自己的种子。代码srand(seed)将定量seed的值赋给内部的“种子”,函数rand( )使用该种子来初始化所产生的伪随机数序列,不同的种子会生成不同的结果。必须输入一个 seed的值的问题,可以使用计算机的系统时钟来解决,系统时钟以秒为单位跟踪当前的时间。在头文件 学生姓名: 学 号:专业班级: 实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩: 整数返回当前时间,可以作为rand( )函数的种子。这样就能保证每次都能生成不同的随机数了。 int Deck::GetRandInt(int min, int max) { int n = rand(); //stdlib.h n = n % (max - min + 1) + min; return n; } 利用计算机的系统时钟做种子只能解决每次生成不同的随机数的问题,而作为扑克牌,每次生成的随机数应该是有一个严格的范围的。所以设计了GetRandInt函数来解决这个问题,代码如上。 但要完成洗牌不仅仅如此,洗牌需要将牌大致(随机函数)一分为二,然后交叉洗牌,重复N次。代码如下: void shuffle(deck& dk) { card t; for (int i=0; i<52;++i) { int k=(rand()%(52-i)); t=dk.d[i]; dk.d[i]= dk.d[k]; dk.d[k]=t; } } (2)简要说明程序是如何发牌的 答:栈(stack),是一种先进后出的结构,原理类似于子弹匣,最先压入弹匣中的子弹最后一个弹出。局部变量在程序执行过程中动态行获得和释放栈空间。变量出栈的顺序与入栈的顺序相反,即最先分配单元的变量空间最后一个被释放。像主函数调用被调函数时,需要把局部变量压入栈,退出被调函数时,从最顶上的开始退栈。栈定义为只允许在一端进行插入和删除的线性表。递推算法,就是充分利用栈的性质的,只能从栈顶取,只能压入栈顶,具有单向性。而发牌的时候,就很像这个的过程。因此,洗牌选用了了栈(Stack)的运行方式。代码如下: void deal(int n, int pos, card* hand, deck& dk) { for (int i=pos; i { hand[i-pos+1]=dk.d[i]; print_card(dk.d[i]); //cout< 学生姓名: 学 号:专业班级: 实验类型:□ 验证 □ 综合 □ 设计 □ 创新 实验日期:2013/ 实验成绩: //cout< } cout< } (2)心得体会 尽管程序不是完全自己所写,但就是在实验的过程中,我渐渐懂得了其中的编写机制以及流程,更重要的是其原理所在。由于本人专业知识相对匮乏,很多问题不能自行顺利解决,需要查询教材、大量资料或向同学请教才能找到合适的解决方法,所以明显感到力不从心。我觉得,作为一个程序员,不仅仅应该让程序具有易懂性、简洁性、灵活性,更应该让其具有趣味性、挑战性以及创新性。由此看来,我在编程上还远远处于基础阶段,若要更进一步,则须不遗余力加倍努力。