C++语言程序设计基础知识.docx
《C++语言程序设计基础知识.docx》由会员分享,可在线阅读,更多相关《C++语言程序设计基础知识.docx(30页珍藏版)》请在冰豆网上搜索。
![C++语言程序设计基础知识.docx](https://file1.bdocx.com/fileroot1/2023-6/29/50011a7a-c68f-4546-80bf-fa64dae167ff/50011a7a-c68f-4546-80bf-fa64dae167ff1.gif)
C++语言程序设计基础知识
第12章面向对象程序设计与C++基础
程序设计语言是编写程序的工具,程序设计语言的发展反映了程序设计方法的演变过程。
随着计算机技术的日新月异,要求软件具有良好的可重用性和可扩展性,这就导致程序设计方法从结构化程序设计方式转变到面向对象程序设计方式。
C语言能够很好地支持结构化程序设计,C++不仅兼容C,同时具有面向对象的特征,因此充分支持面向对象程序设计。
12.1面向对象程序设计的基本概念
面向对象技术变革了软件设计的传统方法。
解决结构化程序设计不足的方法就是限制数据的存取权限,面向对象程序设计把数据和使用此数据的过程封装成一个类(类可看成是一个基本数据类型)。
数据的封装和隐藏一方面使得内部数据不被破坏,另一方面程序具有良好的可读性和相对独立性。
类的继承机制不仅提高了代码的可重用性,降低了软件开发难度和开发周期,同时程序的修改变得容易,软件结构更加稳定,软件的可维护性大大提高。
面向对象软件系统由类的层次结构组成,类的具体实例叫做对象,对象之间通过彼此的联系,共同实现程序的功能。
如图12-1所示。
面向对象程序设计方法提出一些全新的概念:
对象、类、封装、数据隐藏、继承和多态性等。
多态性、数据封装和继承共同构筑了面向对象程序设计的三大机制。
1.类与对象
面向对象的方法学认为,客观世界由对象组成,对象有自己独特的特征和行为,两者密不可分。
面向对象的软件技术中,类是将不同类型的数据和与这些数据相关的操作封装在一起的集合体。
对象可看成类的具体实例。
图12-2描述了洗衣机类。
【例12-1】在现实世界中,钢笔具有笔帽、笔体、笔尖、颜色、形状等属性特征,钢笔具有的行为是书写、灌钢笔水。
钢笔作为一个整体,具有独特的属性和行为,它们将被抽象成一个钢笔类。
这种自成一体性称为封装性。
而你正在使用的钢笔,可以看成钢笔类中的一个具体实例。
我们不必关心钢笔的内部结构及实现细节,仅利用钢笔对外提供的操作,即外部接口,就可以方便地使用钢笔。
【例12-2】对于整型类型的数据,可以定义对它的四则运算,这是整型数据具有的行为,四则运算与整型数据封装成一体,命名为“int类”。
字符型运算和逻辑运算不可以访问、修改int类中的数据,只有int类中定义的四则运算才能对其进行操作。
显然,我们也无需知道四则运算是如何定义的,int类对外界提供的操作接口是“+”、“-”、“*”、“/”,可以直接利用这些接口实现整数的四则运算,这叫做数据的隐藏性。
2.数据的封装和隐藏
封装强调的是数据和行为的自成一体性,也就是将数据和操作数据的函数代码进行有机的结合,形成“类”。
封装具有如下意义:
1)面向对象的程序表现出强烈的可读性和独立性。
2)程序不依赖数据结构的改变,具有良好的可维护性。
当数据结构改变时,只有类中对数据进行操作的函数需要修改,程序的其他部分不受影响。
3)对数据起到一定的保护作用。
只有类中的函数才可以修改类中的私有数据。
隐藏是指类把对数据的操作信息隐藏在内部,只提供对象对外的操作接口,用户只能看见对象封装接口上的信息,这样可以保护内部数据不被破坏,同时使用者使用起来非常方便,并可以容易地将其扩展成新的产品。
3.类的继承
类的继承是指新的派生类可以继承原有父类的全部数据和操作的机制,并可以在派生类中添加新的数据和操作方法。
新的派生类又叫子类,原有父类又叫基类或超类。
例如,想生产敞篷汽车不用从草图开始,只需要对原有的产品添加新的敞篷功能,这样就会快速开发出一种新的产品。
利用类的继承特性,可以非常方便地产生一系列具有相似特征的对象。
再看一个类继承的例子。
在小学,我们学过数的四则运算,到中学会进一步学到对数、指数等运算,到大学将学到微积分等内容。
我们每一次的进步都将继承已学过的知识,在已有的基础上不断扩充新的知识。
4.消息
对象之间通过消息相互联系,也是通过消息让某一个对象完成一定的功能。
例如,给洗衣机对象发洗涤的操作信号,洗衣机就会执行洗涤的功能。
在面向对象的程序设计中,消息名就是在对象中定义的操作(或称方法),给对象发消息,就是让对象执行某一操作,使对象具有行为的能力。
如图12-3所示。
5.多态性
多态性表示同一种东西有多种形态。
例如,与对方通信,这个动作将有多种表现形态,如采用写信的方式、打电话、发电报、发电子邮件等。
在面向对象的程序设计中,多态性是指同一个(或相似的)操作作用于不同的对象上可以有不同的独特行为,比如add操作,作用在实数对象上,表现为两个实数相加,作用在虚数对象上,表现为两个虚数相加。
面向对象的C++语言允许程序员给不同的对象发同一消息,软件系统会做出决策,即到底执行什么动作。
在静态(编译或连接)时就可以分辨的,可通过函数重载实现;在运行时才能分辨的,可通过虚函数实现。
利用多态性,用户能够发送一般形式的消息,而将所有的实现细节留给接收消息的对象。
这种高层次的抽象,使得多态性、数据封装和继承共同构筑了面向对象程序设计的三大机制。
12.2面向对象程序设计语言C++简介
C语言是一种受到广泛重视和应用的面向过程编程的语言,它采用结构化程序设计技术,即把复杂的模块层层分解为一系列简单模块。
人们侧重于开发具有特定功能的函数,而有些数据(特别是全局数据)在函数间可随意传递。
结构化程序设计语言无法处理高度复杂的程序,为了弥补结构化C语言的不足,支持面向对象的程序设计的C++语言应运而生。
C++语言是一种混合性语言,保留了C语言的全部内容,同时扩展了C语言所不具有的面向对象编程的机制。
C++在技术上与C完全兼容,但它具有与C语言完全不同的思维方式,即支持面向对象的程序设计思想,强调对数据进行封装、保护等深入的维护,强调软件的可扩展性、可修改性和可维护性。
利用C++语言可以进行结构化编程,更重要的功能是进行面向对象的编程。
12.2.1C++程序结构
本小节主要介绍两个程序,一个是面向过程的C++程序,见例12-3;一个是面向对象的C++程序,见例12-4。
通过这两个实例,读者可以对C++程序结构有个初步认识。
【例12-3】以下程序实现输入一个整数,求平方根并打印出来。
//程序名:
csqrt.cpp
#include//定义cout()、cin()的头文件
#include//定义sqrt()的头文件
usingnamespacestd;//使用命名空间std
voidmain()
{
intx;
cout<<"inputnumber\n";//提示从终端输入一个整数
cin>>x;//从键盘接收数据存入变量x中
cout<<"thesquartofnumber="<}
程序运行情况:
inputnumber
4<回车>
thesquartofnumber=2
说明:
1)C++程序注释行以“//”开头,也可以使用C语言的注释行方式。
2)C++程序的后缀通常为cpp。
3)cout、cin是C++语言中增加的用来输出和输入的函数,分别代表标准的输出设备对象和输入设备对象。
这两个C++函数是在输入输出流头文件iostream内定义的。
cout的使用形式:
cout<<输出字符串或输出变量。
cin的使用形式:
cin>>变量。
4)endl操作符表示以后的输出换行打印。
5)C++标准程序库中的所有标识符都被定义于一个名为std的namespace中,程序中第4行的使用,使得命名空间std内定义的所有C++标准库函数名和标识符都有效,就好像它们被声明为全局变量一样。
面向对象程序设计的首要任务是设计类,自顶向下利用演绎的思想,由抽象类派生具体的子类;自底向上利用归纳的思想,由具体的子类归纳出具有共同特征的抽象的父类,从而建立类的层次结构。
再以类为模板创建不同的对象,然后协调这些对象共同工作。
程序的运行从main函数开始。
【例12-4】编写一个简单的面向对象的C++程序:
设计一个sample类,对整型数据进行管理,初始化并显示对象。
1:
#include
2:
usingnamespacestd;
3:
classsample//createaclass
4:
{
5:
private:
6:
inti;
7:
public:
8:
voidinitial();
9:
voiddisplay();
10:
};
11:
voidsample:
:
initial()//定义类的初始化成员函数
12:
{i=0;
13}
14:
voidsample:
:
display()//定义类的显示成员函数
15:
{cout<
16}
17:
voidmain()
18:
{sampleA;//定义类sample的对象A
19:
A.initial();//给对象A发消息,进行对象的初始化
20:
cout<<"ObjectA,i=";
21:
A.display();//给对象A发消息,显示对象
22:
}
运行结果:
ObjectA,i=0
说明:
程序的第3~10行,用来声明类sample。
其中class是C++用来声明类的关键字。
该类中定义了私有的整型数据成员i,以及公有成员函数initial()和display()。
私有的整型数据成员i只能通过两个公有成员函数进行初始化和显示。
程序的第11~13行,用来定义类的公有成员函数initial(),其作用是初始化对象。
程序的第14~16行,用来定义类的公有成员函数display(),其作用是显示对象。
程序的第17~22行,是主函数,其中第18行用来定义一个具体的对象A,这意味着在内存为对象A分配空间,如图12-4所示。
第19行是给对象A发初始化的消息,对象A执行initial()函数,改变对象A的状态,如图12-5所示。
第21行是给对象A发显示的消息,对象A执行display()函数,显示对象A的状态。
12.2.2C++对C的补充
1.注释行
C++的注释有两种类型:
一种是沿用C语言的方法,采用“/*”和“*/”括起来进行注释,另一种方法是只能用于C++的单行注释,即从“//”开始,直到所在行的行尾,所有字符都被作为注释处理。
2.C++的cin与cout
cout、cin是C++语言中增加的用来输出和输入的函数,分别代表输出设备对象和输入设备对象。
cout和cin函数的使用方式不随变量的类型变化,这正是面向对象语言所追求的。
cout是代表标准输出的流设备,如显示屏。
输出操作必须配合操作符“〈〈”,表示把该操作符右边的数据输出到显示屏上。
cin是代表标准输入的流设备,如输入设备键盘。
输入操作必须配合操作符“〉〉”,代表从标准输入设备读取数据到内存。
cin与cout定义在输入输出流头文件iostream.h中。
3.关键字const定义符号常量
C++中的符号常量使用关键字const来说明,其一般形式:
const类型说明符常量名=常量值
或
类型说明符const常量名=常量值
例如:
定义一个buf的常量。
constintbuf=512
或
intconstbuf=512
4.new和delete运算符
C++新增new和delete作为动态内存分配和释放的操作符,用来增强C语言中原有的函数malloc和free,提高了内存管理的灵活性。
例如:
int*p;//定义指向整数的指针变量
p=newint;//动态申请存放整数的内存空间
deletep;//释放p指向的动态内存空间
5.引用作为函数参数
C++新增了引用,它使函数调用变得清晰、简单,参数传递效率更高。
同时,将引用作为函数参数给编程带来了很大的灵活性。
引用可看成某个目标变量的别名,对引用的操作,实际上就是对目标变量的操作,引用与目标变量完全对应。
一旦用目标变量给引用赋初值,引用就被维系在该目标变量上,即引用不会再与其他目标变量建立联系。
引用不能是空,与此相反,指针变量可以为空。
声明并初始化引用的一般格式:
数据类型&引用名=目标变量;
其中,“数据类型”是定义目标变量的类型,而“数据类型&”是用来声明引用的。
例如:
intm;
int&kk=m;//声明kk是对整型变量m的引用
m=4;
kk=10;//此时目标变量m的值通过引用改为10
【例12-5】引用作为函数参数。
#include
usingnamespacestd;
voidswap(int&,int&);//声明形参是引用类型
voidmain()
{
intx=10,y=20;
cout<<"beforeswap,x="<swap(x,y);//函数调用,实际传递的是实参地址
cout<<"afterswap,x="<}
voidswap(int&xx,int&yy)//形参是引用类型
{
inttemp;
temp=xx;
xx=yy;
yy=temp;
}
运行结果:
beforeswap,x=10,y=20
afterswap,x=20,y=10
该程序通过调用函数swap(),交换主函数中变量x和y的值。
在主函数中,调用函数swap(x,y)时,实参看起来是值传递,其实传递的是实参地址。
在定义函数swap()时,形参是引用类型,这就使得引用xx与目标实参x的地址维系在一起,引用yy与目标实参y的地址维系在一起。
在执行函数swap()时,对引用xx与yy的操作,实际上就是对x与y的操作。
【例12-6】指针作为参数实现与上述程序同样的功能。
#include
usingnamespacestd;
voidswap(int*,int*);//声明形参是指针类型
voidmain()
{
intx=10,y=20;
cout<<"beforeswap,x="<swap(&x,&y);//函数调用,传递实参的地址
cout<<"afterswap,x="<}
voidswap(int*xx,int*yy)//形参是指针类型
{
inttemp;
temp=*xx;
*xx=*yy;
*yy=temp;
}
运行结果:
beforeswap,x=10,y=20
afterswap,x=20,y=10
该程序在调用函数swap()时,实参是&x与&y,表明将把x与y的地址传入到形参中,而形参必须声明为指针类型,接收传进来的地址。
在函数swap()的定义体中,*xx是指针变量xx所指向的变量,实际上就是主函数中的变量x;*yy是指针变量yy所指向的变量,实际上就是主函数中的变量y。
通过上述两个程序风格的对比,形参是引用时,函数体简练、清晰、可读性好。
而形参是指针类型时,函数体内过多的指针运算降低了程序的可读性,容易产生错误。
当形参是引用时,传递的是实参本身(通过传递地址的形式),因此当函数被调用时,在函数作用域内,并没有建立实参的副本,这种机制大大提高了效率,节省了空间。
比如,若要传递的参数是结构或类这样较大的数据类型,显然不希望复制副本,以提高参数传递的效率,并节省内存。
当形参既不是引用也不是指针变量时,参数传递属于值传递,此时若函数被调用,必须在函数作用域建立实参的副本,这通常以时间和空间的浪费为代价。
6.函数返回类型是引用
当函数返回类型不是引用时,实际上是在内存中开辟临时空间存放函数返回的值,而函数返回类型是引用时,不需要这样一个中间过程,即不需要产生临时变量以存放返回值,而直接把返回值传回到主函数中,从而大大提高程序执行的效率,同样也节省了空间。
注意不要返回不在主函数作用域内的变量或对象的引用。
【例12-7】返回引用类型的应用。
#include
usingnamespacestd;
intlarge;
int&max(x,y)
{
large=(x>y)?
x:
y;
returnlarge;//返回变量large的引用
}
voidmain()
{
intd=max(10,46);//返回变量large的引用,使得large的值直接传给d
int&f=max(10,56);//引用f与目标large维系在一起
}
7.内联函数
为了减少函数调用的开销,C++引入了内联函数。
内联函数的功能是强迫C++编译程序直接使用函数体代码替换调用语句,以提高程序的运行速度。
内联函数定义的一般形式:
inline函数类型函数名(形参表)
{
函数体
}
如果在类的声明中直接给出成员函数的定义,该成员函数默认为内联函数。
通常只将规模较小又使用频繁的函数设计为内联函数。
8.带默认形参值的函数
C++允许定义带多个默认形参值的函数。
例如,设函数原型声明情况如下:
intchange(intx=10,inty=20);
则函数调用情况如下:
change(40);//形参y采用默认值20
change();//形参x和y都采用默认值
12.2.3C++中的类
1.类的定义
类是由成员组成的,包括一组数据成员和成员函数,其定义的一般形式:
class类名
{
private:
私有数据和成员函数
public:
公有数据和成员函数
protected:
保护数据和成员函数
};
类的名称由关键字class开始,类的定义体夹在一对花括号{}中,后面可以跟分号,或一组属于该类的对象。
类内的一组成员变量和成员函数可以有以下三种类型:
私有数据类型(private)、保护数据类型(protected)、公有数据类型(public)。
如果用private来说明类的数据和成员函数,则意味着它们只能被该类中定义的成员函数和友元函数访问。
通常数据默认为私有类型,私有类型的保留字private可以省略。
如果用protected来说明类的数据和成员函数,则意味着它们只能被该类中的成员函数、友元函数、该类的派生类中的成员函数所访问。
如果用public来说明类的数据和成员函数,意味着可以在类的外部(不通过类的成员函数,如主函数)存取数据。
类中的公有成员函数提供了该类对外的接口,即通过公有成员函数,才可以访问类的私有数据。
2.类的成员
类的数据成员不仅包含普通的数据变量,而且还可以包含其他类的对象。
类是一种新的数据类型,而对象可看成是由类定义的变量。
类的成员函数对类内的数据成员施加操作,实现类的特定功能。
若成员函数很小,可以在类内定义,反之,一般在类外定义。
数据成员通常定义为私有或保护类型,以便隐藏和保护数据。
成员函数通常定义为公有类型,目的是提供对外的接口,以便能够对类访问。
为了在类外定义成员函数,必须指出该成员函数是属于哪个类的,C++中引入了一个符号“:
:
”,称为作用域运算符。
在类外定义成员函数的一般形式:
函数返回类型类名:
:
成员函数名(形参表)
{
函数体
}
3.对象的定义
类是对具有相同属性和行为的一组相似对象的抽象,而对象是类的具体实例。
类可看成是一个抽象的数据类型,而类的对象就是属于类的一个具体变量。
类不是一个占有内存的实体,但对象却是。
创建类中的对象,就是在内存中创建具体的对象,其含义就是在内存中为类中的数据分配存储空间,它们是对象的具体体现。
通常在子函数中、主函数中以及函数的前面采用如下方式定义对象:
类名对象名;
这种方式使得对象如普通变量一样,具有一定的作用域(生存期)。
4.类成员的使用
类中的成员,包括数据成员和成员函数。
有了对象,若想使用类中的公有成员,可采用以下形式:
对象名.公有数据成员
对象名.公有成员函数
例如,m.input();是指给对象m发消息input(),对象m执行input()函数。
注意类中的私有数据只能通过该类的成员函数进行访问,类中的保护数据可被该类的成员函数以及该类的派生类的成员函数或友元函数所访问。
5.类成员的初始化
(1)数据成员初始化
类中公有数据成员可像普通变量一样使用,可在定义对象后,在需要时赋初值。
假设有一公有数据num,对象名为A,则用如下方式初始化:
A.num=10;//公有数据可以直接使用
类中的私有或保护数据只能在类及派生类中的成员函数或友元函数中使用,所以不允许直接引用类的实例对象中的私有或保护数据成员。
如例12-6中,类sample的实例对象为A,由于类sample中的i是私有数据成员,所以不可以采用如下方式赋值:
A.i=5;//错误,私有数据不可以直接引用
对私有数据i的初始化是通过类sample中的初始化成员函数实现的。
(2)对象的初始化
对私有或保护数据成员的初始化可通过对象的初始化实现。
对象的初始化就是给对象数据赋初值。
对象的初始化一般有两种方式:
一是通过类中的普通成员函数,二是通过类中的特殊成员函数。
而最通用的是通过类中的特殊成员函数,即构造函数来实现。
在12.2.4节例12-10的主函数中,首先定义类DATE的实例对象m,以便给对象数据分配存储单元(参看图12-10),由于构造函数的特殊性,它可以在定义类中的对象的同时,由系统自动调用,初始化对象的数据。
普通的初始化成员函数,可用下面的方式初始化类的对象:
A.initial();//参看12.2.1节例12-6
此时对象A调用成员函数initial()响应消息,该函数给对象A的私有数据赋初值。
12.2.4C++中的构造函数和析构函数
1.构造函数
在C++中通常自动初始化对象的数据。
为了做到这一点,可以定义一个特殊的成员函数,它的名字与类名相同,称为构造函数。
构造函数的作用是在对象被创建时使用特定的值构造对象,或者说将对象初始化为一个特定的状态。
当我们定义属于类的对象时,系统就自动执行构造函数,初始化具体的对象。
如果程序没有声明构造函数,系统会自动地为程序设置一个,此构造函数什么也不做。
对象在创建时只是被分配空间,具体的初值只能由用户定义的初始化函数来实现。
关于构造函数,给出以下几点说明:
1)构造函数与类名相同。
2)构造函数没有返回类型,也不能是void类型。
3)构造函数不能被随意调用,由系统自动调用。
4)构造函数可以重载,可以带默认形参,可以是内联函数。
5)构造函数可以带参数也可不带参数,不带参数的构造函数称为默认的构造函数。
2.析构函数
此外,C++还提供了一种特殊的成员函数,叫做析构函数。
当对象被取消时,系统首先会自动调用类的析构函数,然后撤销对象(释放对象所占用的存储空间)。
如果程序中没有声明析构函数,则系统会自动为程序设置一个析构函数。
如果用new()函数动态分配对象,当动态对象被取消时,系统会调用析构函数。
因此,通常在析构