11.2 C++程序设计
面向对象设计是一种把面向对象的思想应用于软件开发过程中,指导开发活动的系统方法,是建立在“对象”概念基础上的方法学。按照Bjarne Stroustrup(本贾尼·斯特劳斯特卢普)博士的说法,面向对象的编程过程如下:
(1)决定需要的类。
(2)给每个类提供完整的一组操作。
(3)明确地使用继承来表现共同点。
由此可以看出:面向对象设计就是“根据需求决定所需的类、类的操作以及类之间关联的过程”。
11.2.1 C++基本语法
我们在C语言的基础上讨论C++的基本语法,主要介绍一下C++引入的新概念。正如前面所说的那样,C++是带类的C,所以我们从类说起。
(1)类
在所有C++语言的概念中,类(class)是核心内容。
C++语言中的类实际上是一种由用户自定义的数据类型,它不仅包括一组数据成员,而且还包括将对这些数据成员进行操作的成员函数。后者是C语言的结构类型中所不允许的。
类作为自定义的数据结构只是一种抽象的描述。按照面向对象的分析理论,类还具有定义其实体———对象的能力。在C++语言中,用类定义对象的过程就是按照类所需的容量,动态占用内存资源、复制类中的全部数据结构并向其内部注入各项初始的数值。类在编程书写时用“class”保留字声明,类名习惯以大写字母C开头,其格式是:
格式一:class类名{成员名字定义表}[对象名表];
格式二:class已声明的类名对象名表;
格式三:class已声明的类名;
格式一是用来实现类声明的。也可以通过跟在其后的对象名表同时声明该类的一至多个对象。格式二是用已声明好的类名来定义对象(object)的。格式三是类的引用的声明,用于类之间相互引用的场合(即先声明后引用)。格式一的成员名表的结构如下:
public:
成员声明区,完成数据成员和成员函数的说明
private:
成员声明区,完成数据成员和成员函数的说明
protected:
成员声明区,完成数据成员和成员函数的说明
这里public、private和protected同class一样都是C++语言的保留字。成员声明区内各种成员数据的书写格式与struct内的成员数据书写格式一样。与之发生操作关系的成员函数只能对声明在同一class内的成员数据进行操作。
这里涉及两个很重要的概念———属性和方法。属性是类的静态特征描述,而方法则描述了类的动态特征。在C++中,属性用变量和常量(当然还有对象)来实现,方法用函数来实现。
一个类中的三个区public(公有区)、private(私有区)和protected(保护区)分别对声明于各区中的成员附加了三种不同的属性,缺省的情况下默认为private。由于本书不打算深入讨论C++,因而我们仅仅简单地讨论public和private两个区。至于protected区则和继承有着密切的关系。
凡声明在public区中的成员,当本类对象生成后可以被类外的全局程序或其他类中的成员函数直接访问。凡声明在private区内的所有成员,当本类对象生成后只能被本类的成员函数直接访问。类外的全局程序则要通过一个定义在public区内的成员函数为媒介间接访问。这实际上就形成了类的三大基本特性之封装特性。这样就可以描述一个完整的类了。
下面给出一个类的完整定义。
【例11.1】在【例2.1】的基础上定义一个书类。
①打开Visual C++6.0环境,建立一个控制台工程,其名称为Example_11_1_BookClass;
②添加C++源文件,并编写如下代码:
③编译链接并运行。
④运行结果如图11-1所示。
图11-1
⑤代码分析
本例在例2.1的基础上进行了书类的设计,可以看出CBook有两个属性m_cName和m_iPagination,有两个方法SetName和PutName,属性描述了书名和页数,方法描述了设置书名和显示书名。属性在private区里(缺省即为private),方法在public区中,这样就体现了类的封装特性。从第2行到第18行,完整地定义了书类CBook。
程序到第18行为止,已经完成了书类的描述,即定义了一个书类CBook,它有两个属性,同时有两个方法。对于外部而言,能进行两个操作,即设置书名和显示书名。
类实际上仅仅是一个抽象描述,如果想进行具体的动作,比如这里的显示书名,将如何进行呢?通过前面的学习我们知道类是对象的抽象,也就是说对象是类的具体化。好比我们见到的一个个人,实际上都是人类的具体化。人是一个类的概念,所以我们无法和人进行任何的交流,我们只能和具体化的人比如张三、李四进行沟通。因而我们可以推断出,在面向对象程序设计中,只有将类进行实例化(也就是具体化),才能做实际的操作(当然如果进一步深入的学习,你就会对这句话产生质疑,不过我暂且这样说,而且从很大程度上讲也是很合理的)。
第23行就创建了一个名字为cbkShuiHu的对象,从C语言的角度来看,完全就是一个变量的定义方法。由此我们就可以这样认为———类就是自定义的数据类型。第25行和第26行代码调用了对象的方法进行书名的设置和显示,从中可以体会出面向对象的特点是“不需告诉如何做,仅仅告诉做什么”。
现在大家再思考另外一个问题:书类已经定义好了,但是感觉还不够完整。比如我想扩充属性,增加方法,使之能够描述更为丰富的信息,能够进行更丰富的操作该如何进行呢?实际上,C++提供了很好的继承机制来实现,其实现过程也是相当复杂,必将涉及很多的知识点,这里不再深入讨论,在本章最后的例子中将进一步说明。
至此已经将三个基本特性中的封装和继承做了介绍,对于多态性本书不再做进一步的探讨,大家可以通过专门介绍C++的书进行深入的学习。
(2)简单I/O操作
我们再看例11.1,在第15行中有这么一条语句:
cout<<"书的名字是"<<m_cName<<endl;
我们可能已经从结果中分析出它的大概意思———就是输出信息,这就是本节要讨论的I/O操作。
在程序中经常需要将数据输出到屏幕、打印机、存储器等,也经常需要从键盘接受用户输入的数据,这种输入输出操作统称为I/O操作。在这里将简单介绍键盘和屏幕操作。
在C++中把数据的I/O称为数据流,并提供了强大的“流”处理功能,以控制数据从一个位置流向另外一个位置。相对于内存,当数据从内存流向屏幕、打印机或硬盘时称为输出;当数据从键盘、硬盘流向内存时称为输入。C++用两个对象cin和cout实现标准的输入输出。
cin:istream类的对象,用来处理标准输入,即键盘输入。
cout:ostream类的对象,用来处理标准输出,即屏幕输出。
在C++中用istream类和ostream类的派生类iostream控制输入输出,并提供了输入和输出操作符。“>>”称为提取操作符,其作用是从cin流中提取字符。“<<”称为插入操作符,其作用是把表达式的值插入到输出流中。
在此简单介绍一下屏幕的输入输出方法。
①使用提取操作符和cin实现键盘输入
格式如下:
cin>>变量或者对象>>变量或者对象;
提取操作符可连续使用,后跟表达式,表达式通常是获得输入值的变量或者对象。例如:
int a,b;
cin>>a>>b;
要求从键盘上输入两个整数。在键盘上输入
43 20
这时,变量a获取值43,变量b获取值20。
说明:从键盘上输入数值时两个值之间一般用空格分隔,也可以用Tab键或换行符。
②使用插入操作符和cout实现屏幕输出
格式如下:
cout<<变量或者对象<<变量或者对象;
与>>一样,插入操作符可连续使用,后跟表达式,在输出时系统自动计算表达式的值并插入到数据流中。例如:
cout<<“您好!”;
关于这一点,大家如果跟printf和scanf进行一下对比,就会发现C++的流操作(cout和cin)的巨大优越性了。不需要格式字符串,就是说不用考虑输出和输入的具体数据类型,就可以进行输入和输出操作了。功能强大而且形式统一,很好地降低了程序的复杂度,提高了开发效率,同时也为多态的实现提供了一个强有力的保证。
(3)引用类型
“引用”(reference)是C++的一种新的变量类型,是对C的一个重要补充。它的作用是为变量起一个别名。假如有一个变量a,想给它起一个别名,可以这样写:
int a;int&b=a;
这就表明了b是a的“引用”,即a的别名。经过这样的声明,使用a或b的作用相同,都代表同一变量。在上述引用中,“&”是引用声明符,并不代表地址。不要理解为“把a的值赋给b的地址”。声明引用并不开辟新的内存单元,b和a都代表同一变量单元。
在C++中,有三条规定:
①引用被创建的同时必须被初始化。
②不能有NULL引用,引用必须与合法的存储单元关联。
③一旦引用被初始化,就不能改变引用的关系。
值得一提的是:引用可以作为函数的参数和返回值来用。
C++语言中,函数的参数和返回值的传递方式有三种:值传递、指针传递和引用传递。
我们来看一个例子:
【例11.2】分别以值、指针和引用作为函数的形参,比较三种方式的异同。
①打开Visual C++6.0环境,建立一个控制台工程,其名称为Example_11_2_CallFunction;
②添加C++源文件,并编写如下代码:
③编译链接并运行。
④运行结果如图11-2所示。
图11-2
⑤代码分析
由于Func1函数体内的x是外部变量n的一份拷贝,改变x的值不会影响n,所以n的值仍然是0;对于Func2,由于是通过引用传递参数的,x是外部变量n的别名,改变x当然改变了n,因而此时n为10;对于Func3则是通过指针传递的,传递的是外部变量的地址,直接修改的是同一段地址,改变了n的值,由10变为20。
(4)函数重载
函数重载提供了定义和使用同名函数的功能。在C语言中,如果我们需要一个求两个整数最大值的函数,可以这样写:
int Max_Int(int,int);
如果我们又需要一个求两个浮点数最大值的函数,必须这样定义:
float Max_Float(float,float);
C++允许同名函数,只要定义的参数类型或个数不同,编译器会自动进行区分。
因此在C++中我们可以定义函数:
float Max(float,float);
int Max(int,int);
从表面上看,似乎还是定义了两个函数,但是它们的函数名称是一样的,因为它们提供的函数功能是一样的。函数名一样,就为实现多态性提供了重要的保证。
(5)操作符重载
C++中还有一种函数重载的特例,可以对操作符进行重载,允许对诸如+、=、<<、>>、+=等运算符进行重载,以适应不同的需要。这一特性对于面向对象来说是非常必要的。在前面的例子中,我们使用了
cout<<"书的名字是"<<m_cName<<endl;
其中cout是一个在iostream.h中定义的屏幕对象,这个头文件中已经对“<<”进行了重载,使它支持诸如整数、浮点数、字符、字符串等常用数据类型的输出,所以我们可以简单地使用cout<<m_cName来输出书名而不用指明类型。
一般的,C++通过operator关键字实现运算符的重载,形如:
类名operator运算符(参数列表){函数体}
需要指出的是,并不是所有的运算符都可以进行重载,而且参数列表也是有特殊要求的,其中的参数的意义都已经规定好了。操作符重载其实就是一个函数,只是函数名特殊一些。具体应用将在本章最后一节的综合程序中进行探讨。
(6)缺省参数
在C语言中,函数调用必须按照参数表全部显式传送。C++允许使用缺省参数。如已有函数声明in Default(int num=10),调用时可以直接写Default(),编译器会自动认为我们接受num=10这个参数。这一特性对于参数很多,而大多数参数一般又无须特殊设置的函数调用显得很方便。
注意,对于多个缺省参数的情况,如果一个参数缺省的话,后边的参数也必须缺省。例如void AFunction(int a=10,char b,int c=3)这样的声明就是错误的。改为void AFunction(char b,int a=10,int c=3)就正确了。修改后,对于调用函数来说,如果想接受a的默认值,那么必须同时接受c的默认值。换句话说,如果想给c传参数的话,必须也给a传参数。
(7)内联函数
C语言中一个比较麻烦的特性是#define宏指令。这是因为宏指令和其他的语句不同,在编译之前宏指令会被预处理程序翻译解释,而预处理程序的规则显然与编译程序不同。那么是不是在C++中禁止使用#define宏指令,而要求写成函数的形式呢?C++的设计人员不能这么做,原因很简单:宏指令在预处理时展开为代码,而函数则放在不同的段内,调用时需额外的开销。在这一方面,宏指令具有函数调用所没有的优点。
为了解决这个问题,C++的设计人员引入了inline关键字。在一个函数前面加上inline表示这是个内联函数。内联函数与普通函数具有相同的语法,只是它们被调用的时候会像宏调用那样扩展为内联。
【例11.3】内联函数应用举例。
①打开Visual C++6.0环境,建立一个控制台工程,其名称为Example_11_3_Inline;
②添加C源文件,并编写如下代码:
③编译链接并运行。
④运行结果如图11-3所示。
图11-3
⑤代码分析
程序第2行定义了一个宏,实现了比较大小的功能,但是要注意,这里仅仅是预编译阶段进行的“毫无判断”的替换,没有类型检查;第4~12行定义了一个比较大小的内联函数,这样在第17行调用Min()函数时,就不再进行一般的函数调用,而是直接嵌入函数代码以加快程序的执行。但是一定会进行类型检查的,就是说此时编译器要进行一个判断。注意这一点和宏定义的不同。
(8)动态内存分配和释放
运算符new和delete是C++新增的运算符,提供了内存的动态分配和释放功能。它的作用相当于C语言的函数malloc()和free(),但是性能更为优越。相对而言,使用new具有以下的几个优点:
①new自动计算要分配类型的大小。
②自动地返回正确的指针类型,不用进行强制指针类型转换。
③可以用new对分配的对象进行初始化。
11.2.2 一个“麻雀型”的C++程序
为了对本章的知识有一个整体的认识,同时对部分内容做一个有益的补充,以期引发深入的思考和进一步的交流,在本节中给出一个具体的程序。
【例11.4】编写一个计算器类,实现各种数据类型(整数、小数和复数)的+、-、*、/运算。
(1)打开Visual C++6.0环境,建立一个控制台工程,其名称为Example_11_4_All;
(2)添加C++源文件,并编写如下代码:
(3)编译链接并运行。
(4)运行结果如图11-4所示。
图11-4
(5)代码分析
本程序是一个综合性较强的例子,从中能够看到类的定义方法和对象创建的方法以及构造函数、函数重载、运算符重载、输入输出操作等知识点的应用,进而较为深刻地了解和体会面向对象程序设计方法带来的崭新的编程思想和方式。
第4行到第64行定义了CComplex复数类,实现了构造函数,加、减、乘、除运算符的重载,复数的输入和输出等功能。构造函数是唯一一个没有返回类型的函数,而且函数名和类名相同,参数和一般函数一样,由系统在构造对象时调用。第9行和第10行到第15行分别定义了两个构造函数,后者重载了前者。
第17行到第44行分别重载了加、减、乘、除运算符。可以看到复数也可以像整数、浮点数那样进行加、减、乘、除运算,类的多态性得到了体现。
第65行到第139行,定义了一个带有复数计算功能的简单计算器类,实现了加、减、乘、除功能。可以看到,这都是通过大量地运用函数重载实现的。
第141行以后实现了主函数,里面定义了对象,并通过对象的属性和方法进行程序的设计和实现。可以明显地看到在这里仅仅需要告诉系统做什么,而怎么做是不用管的,因为在类的设计阶段已经完成了。本例子体现了面向对象编程思想的概貌。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。