46
励志照亮人生 编程改变命运零基础学 Visual C++
第3 章面向对象程序设计与C++语言
Windows编程采用的是面向对象的方法(Objec Oriented Programming,简称 OOP) 。面向对象软件开发的方法是吸收了软件工程领域中有益概念和有效的方法,而发展起来的一种软件开发方法。它集抽象性、封装性、继承性和多态性于一体,可以帮助人们开发出模块化、数据抽象程度高的、体现信息隐蔽、可复用、易修改、易扩大等特性的程序。
Visual C++是 Windows环境下最主要的 C++开发环境,它支持面向对象编程,并提供可视化编程环境。要使用Visual C++进行程序开发,有必要了解面向对象的基本概念和掌握C++语言。本章首先简要介绍面向对象的基本概念,而后对 C++语言的基础及要点作简单的介绍。
3.1 面向对象程序设计概述面向对象的程序的最根本的目的就是使程序员更好地理解和管理庞大而复杂的程序,它在结构化程序设计的基础上完成进一步的抽象。这种在设计方法上更高层次的抽象正是为了适应目前软件开发的特点。 3
3.1.1 面向对象的基本概念实际上,面向对象的技术不只是一种程序设计方法,而是建立客观事务模型,分析复杂事务的思想方法。本节将简要介绍一下与面向对象相关的基本概念。
1,对象、类、实体面向对象的程序是由若干“对象”有机结合而成。对象是面向对象的程序设计中最基本的概念,
一般意义上的对象指的是一个实体的实例,确切地说,面向对象的方法中的对象就是现实世界中某个具体的物理实体在计算机逻辑中的映射和体现。
而现实世界的实体(如自行车),在计算机世界表达为一个计算机可理解、可操纵、具有一定属性和行为的对象。
而类则可以理解为对同种对象的集合与抽象。类和对象的关系,就如同整数与 1,2,3的关系一样,即对象是类的实例化。可以理解为类是一个抽象的框架,类中的变量赋了值就是对象。类、对象与实体的关系可表示为如图3.1所示。
面向对象的程序是由若干“对象”有机结合而成。这些对象可以互相通讯、协调和配合,从而共同完成整个程序的任务和功能。
2,对象的属性面向对象的方法中,一个对象具有状态、行为和标识三方面特性。
对象的状态也称为对象的静态属性,包括对象内部包含的各种信息 — 变量,这些变量的值标明
图3.1 类、对象与实体的关系计算机世界现实世界客观世界主观 世界抽象对象 实体类抽象类别抽象实例化映射映射了对象所处的状态。当对象通过某种操作改变了状态,也就体现在它的属性变量的值的改变。对象的行为也称对象的操作方法 — 对象的动态属性,其作用是设置或改变对象的状态。对象的标识则用于区分不同的对象。
简单地说,对象包含了数据和方法(在 C++中,方法叫作类的成员函数),每个对象就是一个微小的程序。由于其他对象不能直接操纵该对象的私有数据,只有对象本身的方法才能得到它,因而对象具有很强的独立性,可把对象当作软件的基本组件,就像电器中的电子元件一样。对象的这种软件组件作用使它具有很强的可重用性。而且增强了程序的可靠性和可维护性。
3,消息面向对象的程序设计中,通过“消息”来请求对象进行动作,对象间的联系也是通过消息
( Message)来完成的。消息中只包括了消息发送者的要求,不指示接收者具体该如何处理这些消息。
一个对象可以接收不同形式、不同内容的消息;相同的消息可以传送给不同的对象;不同的对象对同样的消息可以作出不同的反映。有关 Windows的消息机制在第 3章已经详细介绍了。
4,类的组织结构类是对具有公共的方法和一般特性的一组基本相同对象的描述,在面向对象的方法中,对象是构成程序的基本单位,每个对象都应该属于某一类,就象传统程序设计中的变量,每个变量都应有一定的类型。
在程序执行过程中,由类动态生成相应地对象,一个类可以生成多个不同的对象,这些对象具有相同的属性。因此,对象也称为类的实例( Instance) 。一个类可以由其他的类派生出来,类与类之间根据具体情况以层次结构组织起来。处于上层的类称为父类,处于下层的类称为子类或派生类。
采用面向对象的方法来进行 Windows程序设计还可以简化对资源的管理。将资源映射成一个 C++
对象时,对资源的使用可以翻译成以下 C++顺序。
创建一个对象:如定义一个画笔对象。
使用对象:用画笔绘图。
撤销该对象。
一个对象的创建是对一个对象的定义过程,可以由对象的构造函数处理对资源的请求过程。当某一个对象退出活动范围时,它的撤销可以由编译器来自动管理。
3.1.2 面向对象技术的基本特征到目前为止,对面向对象的概念的定义仍有不同的认识,但它的几个基本特征,如封装性、继承性、多态性等,基本上得到认可。
1,封装性封装就是把对象的属性和方法结合成一个独立的系统单位,并尽可能隐蔽对象的内部细节。封装使对象形成两个部分:接口和实现。对于用户来说,接口是可见的,实现是不可见的。同样形式的接口,其实现可能不同。好比取款机都有相同的取钱接口,但是不同的取款机实现这种取钱的接口方法不一样。
封装提供了两种保护:其一是保护对象,防止用户误用对象内部的属性和方法;其二是保护客户端,即实现过程的改变不影响到接口,从而减小对客户端的不利影响。
47
励志照亮人生 编程改变命运第4 章面向对象程序设计与C++语言
2,继承性真实的对象不是孤立存在的,每个对象都与其他一个或多个对象相关。事实上,当描述一个新对象时,往往指出该新对象的特征与已存在的对象的特征有何不同。对于面向对象来说,继承性是指一个新类可以从现有的类中派生出来,新类具有父类中的所有特性,直接继承了父类的方法和数据,新类的对象可以调用该类及父类的成员变量和成员函数。
如 MFC中的 CEdit类,它封装了 Windows中的编辑框控件,它的继承结构如图3.2所示。
类 CObject是所有的 MFC类的根类。类 CCmdTarget从类 CObject直接派生,它是 Microsoft基础类库的消息映射结构的基类。类 CWnd提供了 MFC中所有窗口类的基本功能性,它封装了 Windows中的窗口句柄 hWnd。类 CEdit从 CWnd直接派生,它提供了对 Windows编辑控件的特定支持。由于 CEdit类继承了其基类的数据和方法,因此,可以通过
CEdit类调用 CWnd类中提供的方法来实现对标准 Windows窗口的操作。
继承保证了类之间的一致性,父类可以为所有子类定制规则。利用继承可以开发更加贴近现实的模型,增加软件重用的机会,从而降低开发和维护费用。子类可以继承父类的属性,也可以增加和重新定义继承的属性。同样,子类可以继承父类的操作,也可以增加或者重新定义继承的操作,这种重新定义被称为覆盖( Override) 。
继承可分为单继承和多继承。单继承指的是子类只从一个父类继承,而多继承则允许子类可以继承多个父类。例如,交通工具是父类,它有三个子类,分别为空中交通工具、水上交通工具和陆上交通工具。而一个两栖交通工具则可以同时继承水上交通工具和陆上交通工具。很多面向对象的语言都不支持多重继承,但 C++支持。
3,多态性多态性是指在一般类中定义的属性或方法被特殊类继承后,可以具有不同的数据类型或表现出不同的行为功能。例如,人都有取名字的操作,但是不同的民族取名字的方式不一样,对于一个不知是何民族的人,可以让他执行“人”这个类共有的操作“取名字”时,他实际执行的是以他所属民族的取名字操作。
在 C++中,多态性定义为不同函数的同一接口。从这个定义出发,函数和操作符的重载也属于多态。
说明多态和继承中的重载(Override)是有区别的。多态是运行时问题,而重载(override)是编译时问题。
封装性、继承性和多态性是面向对象编程的三大特征,开始的时候,读者也许对它们还没有非常清晰的概念,但这没有什么关系,当使用了一段时间 C++语言,然后再回过头来看这些概念时,就会发现对它们有了更深入的认识和了解。
3.2 C++语言基础在本节,将简单介绍一下 C++语言的基础,包括程序的基本控制结构、基本数据类型、运算符和表达式、函数和指针的应用等。通过本节的讲解,读者对 C++语言的基本程序结构会有所了解。
48
励志照亮人生 编程改变命运零基础学 Visual C++
图3.2 CEdit类的继承结构
CObject
CCmdTarget
CWnd
CEdit
3.2.1 C++基本控制结构按照结构化程序设计的观点,任何算法功能都可以通过由程序模块组成的三种基本程序结构的组合。
顺序结构:程序是按程序语句或模块在执行流中的顺序逐个执行。
选择结构:程序是按设定的条件实现程序执行流的多路分支。
循环结构:程序是按给定的条件重复地执行指定的程序段或模块。
这三种结构的直观表示如图3.3所示。
图3.3 三种基本程序结构在 C++中,顺序结构的语句包括说明语句、赋值语句,I/O语句、子函数调用语句和返回语句等,
这里不作详细介绍,重点介绍一下 C++中的选择结构和循环结构。
1,选择结构
C++中的选择结构分为一路选择分支、两路选择分支和多路选择分支。
( 1)一路选择分支一路选择语句格式如下:
if (<逻辑表达式>)
<语句序列>
语句序列可以是一个语句,也可以是复合语句结构,其直观表示如图3.4所示。
( 2)两路选择分支两路选择语句格式如下:
if (<逻辑表达式>)
<语句序列1>
else
<语句序列2>
其直观表示如图3.5所示。
49
励志照亮人生 编程改变命运第4 章面向对象程序设计与C++语言模块 1
模块 1
顺序结构选择结构循环结构条件条件模块 2
模块 2
循环体
Y
N
N
Y
图3.4 一路选择分支结构图3.5 两路选择分支结构条件? 条件?
不成立成立 不成立成立语句序列语句序列 1 语句序列 2
( 3)多路选择分支也可以使用 if语句实现多路选择分支,语句格式如下:
if (<逻辑表达式>)
<语句序列1>
else if (<逻辑表达式2>)
<语句序列2>
else
<语句序列3>
else应和最近的 if嵌套,其直观表示如图3.6所示。
switch语句也可以实现多路(开关)选择结构,其语句格式如下:
switch(<整数表达式>)
{ case 数值1:
<语句序列1>;
break;
......
case 数值n:
<语句序列n>;
break;
[default,
<语句序列n+1>;]
}
其直观表示如图3.7所示。
2,循环结构
C++中有三种循环语句构建方式,while循环,do_while循环和 for循环。
( 1) while循环
while循环语句格式可表示如下:
while(<逻辑表达式>)
{<循环体> }
其直观表示如图3.8所示。
( 2) do_while循环
do_while循环语句格式可表示如下:
50
励志照亮人生 编程改变命运零基础学 Visual C++
图3.6 if 语句实现多路选择分支 图3.7 switch语句实现多路选择分支语句序列 1
语句序列 2
语句序列 3
语句序列 2 语句序列 n
值 =?
计算整型表达式语句序列 1
成立成立不成立不成立条件?
条件?
do
{<循环体> }
while(<逻辑表达式>)
其直观表示如图3.9所示。
( 3) for循环
for循环语句格式可表示如下:
for(<表达式 1>;<表达式 2>;<表达式 3>)
{<循环体 >}
3,C++基本控制实例下面通过一个简单实例讲解 C++基本控制结构。该实例实现将百分制转换为五分制,即用户输入一组百分成绩,将它们转换成 5分制成绩。实例程序的模块结构与逻辑功能框图如图3.10所示。
图3.10 实例程序的模块结构与逻辑功能框图实例循环结构采用 for循环语句,而选择结构则采用多路选择语句 switch。子函数 GradeTran()实现代码如下:
int GradeTran (int old_grade)
{ int new_grade;
51
励志照亮人生 编程改变命运第4 章面向对象程序设计与C++语言
¢
A` ˇp x
A` ˇp x
¢
x
A` ˇpx
¢
图3.8 while循环结构 图3.9 do_while循环结构条件?
条件?
循环体循环体成立不成立不成立成立
主函数 main()
返回、输出 X
输入成绩 X
X=1 X=2 X=3 X=4 X=5
X/10=?
继续?
否是
10,9 8 7 6 <=5
子函数 GradeTran()
实现分数转换
switch(old_grade/10) //switch多路选择
{ case 10:
case 9,new_grade = 1; break;
case 8,new_grade = 2; break;
case 7,new_grade = 3; break;
case 6,new_grade = 4; break;
default,new_grade = 5;
}
return new_grade;
}
主函数 main()的实现代码如下:
#include <iostream.h>
void main()
{
int grade[10] ={100,85,72,69,94,74,66,51,89,45}; //要转换的10个百分制分数
int i;
for(i=0; i<10; i++)
//调用子函数进行分数转换,并输出
cout<<"百分制分数:"<<grade[i]<<",五分制分数:"<<TranGrade(grade[i])<<endl;
}
3.2.2 C++的数据与基本数据类型
C++的数据有两种:常量和变量,且每个数据均需指明其类型。在本节,简单介绍 C++的数据和基本数据类型。
1,基本数据类型数据在计算机中采用二进制存放,一个字节包括 8个二进制位。 C++语言的基本数据类型,及其长度和表示的数据范围如表3.1所示。
表3.1 C++语言的基本数据类型数据类型类型说明符占用字节数据范围字符型 char 1 - 128? 127
短整数 short 2 - 32768? 32767
整型 Int 4 - 2
31
2
31
浮点型 float 4 近似 3.4
- 38
3.4
38
精度 7位双精度型 double 8 近似 1.7
- 308
1.7
308
,精度 15位另外,在基本数据类型前加类型修饰符,可以改变数据表示的范围。常用的有:
unsigned:无符号。
long:长型。
short:短型。
如,unsigned char,表示范围变为 0? 255; unsigned int,表示范围变为 0? 65535。
2,常量变化的量称变量,不变化的量称常量,常量和变量是计算机语言中数据的两种基本形式。在 C++
52
励志照亮人生 编程改变命运零基础学 Visual C++
中,常量主要有下面几种形式。
( 1)整型常量在 C++中,整型常量可以表示为 2进制,8进制,10进制和 16进制。
2进制常量:如 10011101B,10B等,即在数后加字符,B” 。
8进制常量:如 04400,0777,0100等,即在数前加字符,0” 。
10进制常量:如 2304,432等。
16进制常量:如 0x900,0xABC,0xffff等,即在数前加字符,0x” 。
( 2)实型常量实数也称为浮点数,用于表示小数,有两种表示方式:
十进制形式,如- 2.68,3.141593,637.312、- 32767.0等。
指数形式,如 0.0E0,1.267E20、- 6.226E- 4等。
( 3)字符型常量
C++中的字符型常量包括符号常量、字符常量和转义常量。
符号常量:用 #define定义的常数,类似变量,但不是变量。
例如:
#define PI 3.1415926
#define MAXNUM 2000
它可以出现在表达式中,如:
S= r * r * PI;
但是符号常量不能作左值,如下列用法是错误的。
PI = PI*2; //错误的用法
字符常量:用来表示一个字符,如 'a','A','1',' ','+'等,即将字符符号用单引号括起来。
转义常量:常用的转义常量包括 '\n'(表示换行),'\r'(表示回车),'\t'(表示横向跳格),'\''
(表示单引号)等。
( 4)字符串常量
C++中,用双引号定义字符串常量,如 "Visual C++6.0","12.34","This is a string.\n"等。在 C++
中,'A'和 "A"是有区别的,前者是字符常数,后者是字符串常数。
字符是用单引号括起来的单个字符,它在存储器中占 1个字节。
字符串是用双引号括起来的一串字符,它在存储器中占 n+1个字节,即字符串的结束符 '\0'也占
1个字节的位置。
( 5) const常量
C++要求在声明 const常量时对其进行初始化,如:
const double pi=3.1415926;
C++中 const常量的另一个特征是,const整数在任何常量表达式中都可作下标使用。如:
const buflen=512;
char buffer[buflen];
3,变量
C++的数据变量声明语句的格式为:
53
励志照亮人生 编程改变命运第4 章面向对象程序设计与C++语言
<类型说明符> <变量名1> ;
如,
int i,j,k; //声明了3个整型变量
long len; //声明了1个长整型变量
char c0,c1,c2[100]; //声明了2个字符型变量和一个字符型数组变量
double array[10][10]; //声明了一个双精度型的二维数据变量
C++中变量具有一定的命名规则,如下:
变量名只能由字母、数字或下划线组成;
变量名的第一个字符必须是字母或下划线;
变量名长度不超过 32个字符;
不要用 C++的保留字定义变量名。
声明变量时,一般需要对变量进行初始化。变量初始化就是给变量赋初值,有两种形式:
先定义,再赋初值。例如,
int sum,fac;
sum=0;
fac=1;
定义时赋值。例如,
char c='A';
int count = 0;
3.2.3 C++的运算符和表达式
C++中的表达式包括算术运算、逻辑运算、关系运算、赋值运算、逗号运算和自增(自减)运算等,本节将简单介绍这些运算的基本概念以及运算符的优先级、左结合和右结合规则。
1,算术运算符和算术表达式由算术运算符组成的表达式称为算术表达式。 C++中的算术运算符包括,+” (加),,-” (减),
,*” (乘),,/” (除)和,%” (取余) 。其运算优先级顺序为先乘、除、取余,后加、减。其具体使用相信读者都已经很熟习了。
2,关系运算符和关系表达式由关系运算符组成的表达式称为关系表达式。 C++中可用的关系运算符有,>” (大于),,<” (小于),,==” (等于),,>=” (大于等于),,<=” (小于等于),,!=” (不等于) 。关系运算符的优先级顺序为 {>,>=,<,<=}高于 {==,!=}。关系表达式最终的运算结果为逻辑值( true或 false) 。
说明赋值运算、算术运算和关系运算的优先级顺序为:赋值运算<关系运算<算术运算。
3,逻辑运算符和逻辑表达式由逻辑运算符组成的表达式称为逻辑表达式。 C++中可用的逻辑运算符有,&&” (与运算),,||”
(或运算)和,!” (非运算) 。逻辑运算符的优先级顺序为,!” >,&&” >,||”,逻辑表达式最终的运算结果为逻辑值( true或 false) 。
54
励志照亮人生 编程改变命运零基础学 Visual C++
说明逻辑运算符与其他运算符组合的运算的优先级顺序为:赋值运算<“&&”、“||”<关系运算<算术运算<“!”。
下面给出几个逻辑表达式的使用实例。
判别闰年表达式可表示如下:
(year %4==0 && year%100!=0)||year%400==0
i和 j均小于或等于 100,或者 i和 j均大于 k,可表示如下:
(i <= 100 && j <= 100) || (i > k && j > k )
4,赋值运算符和赋值表达式由赋值运算符,=”组成的表达式称为赋值表达式。其格式如 V=e,即将表达式 e的值赋值给变量
V。另外,在 C++中还提供了很多复合赋值运算符,包括,+=”,,- =”,,*=”,,/=”,,%=”等 10个。
例如,a += 5,等价于 a = a + 5。
下面给出几种常用的赋值表达式格式。
i=j=m*n;
表示计算表达式 m*n的值,将其结果存入变量 j中,然后再将结果存入变量 i中。
temp=a; a=b; b=temp;
表示交换 a和 b的值。
str[i] = ch+'A'- 'a';
表示将字符变量 ch转换为大写字母,结果存入数组
str[i]中。
5,自增运算符和自减运算符
C++提供了自增运算符,++”和自减运算符“--”,
其含义如表3.2所示。
6,问号表达式
C++中还可以使用问号表达式,其格式为:
e1?e2:e3
运算规则为,当表达式 e1的值为“真”时,结果取 e2的值;否则,结果取 e3的值。
如,求双精度数的绝对值的函数可表示为:
double abs(double x)
{
return x>0?x:-x;
}
至此,C++各运算符介绍完毕,各运算符的优先级别及运算形式如表3.3所示。
55
励志照亮人生 编程改变命运第4 章面向对象程序设计与C++语言表3.2 自增运算符和自减运算符的使用表达式表达式的值i的值
i++ i i的值增大 1
++i i+1 i的值增大 1
i-- ii的值减小 1
-- ii- 1i的值减小 1
表3.3 C++运算符的优先级别及运算形式优先级别运算符运算形式名称或含义
1
() (e) 圆括号
[] a[e] 数组下标
2
-,+ - e 负号和正号
++、-- ++x或 x++ 自增运算和自减运算
3*,/,% e1*e2 乘、除和求余
4+、- e1+e2 加和减
5<,<=,>,>= e1<e2 关系运算(比较)
6==,!= e1==e2 等于和不等于比较
3.2.4 C++的函数
C++语言程序的结构特点是,程序整体由一个或多个称为函数的程序块组成。每个函数都具有各自独立的功能和明显的界面,从而使程序具有清晰的模块结构。
在 C++语言程序中的若干个函数中,必须有一个且只能有一个函数成为主函数。程序的执行总是从主函数开始,主程序的语句执行完,则程序执行结束。从用户使用的角度看,函数有两种:标准函数和用户自己定义的函数。从函数的形式看,函数分两类:无参函数和有参函数。
1,函数的定义函数定义的一般格式如下:
<函数值类型> <函数名>(<形式参数表>)
{
<函数体>
}
调用函数后所得到的函数值类型,是通过函数体内部的 return语句提供,return语句提供的表达式的值的类型应与函数说明中的函数值类型一致。实际上,return语句完成两件事:强制从被调函数返回主调函数;给主调函数返回其执行结果。如果某一函数确实没有返回值,则使用说明符 void。
形式参数可以在函数体中引用,可以输入、输出、赋值或参与运算。参数说明格式为:
<类型><参数1>,类型><参数2>,?,<类型><参数n>
如简单的加减运算的函数定义如下:
double plus(double x,double y) //加法运算
{
return x+y;
}
double minus(double x,double y) //减法运算
{
return x - y;
}
2,函数的调用主函数和子函数之间的信息交换是通过参数的结合和 return语句来实现的。数据流程可以表示如下:
56
励志照亮人生 编程改变命运零基础学 Visual C++
在主程序中,先给函数的实参赋值;
通过函数调用,将数据从主函数传入子函数;
子函数通过调用形参的值,进行相应的数据处理;
如果有结果值,通过 return语句返回到主函数。
调用自定义函数时,要根据函数的形参定义相应的实参,并给这些实参赋值。实参与形参必须一一对应,即类型一致、位置一致、个数一致。函数的调用根据形参的不同赋值方式,可以分为传值调用、引用调用和地址调用。这里简单介绍一下传值调用和引用调用。
( 1)传值调用在调用时仅将实参的值赋给形参,在函数中对形参值的任何修改都不会影响到实参的值。如下面的实例代码:
#include <iostream.h>
void swap(int x,int y)
{ int tmp; tmp = x; x = y; y = tmp;}
void main( )
{ int a = 10,b = 5;
cout << "交换前:a= " << a << ",b= " << b << endl;
swap(a,b);
cout << "交换后:a= " << a << ",b= " << b << endl;
}
运行程序,可以发现交换前后 a,b 的值没有发生变化,即 a = 10,b = 5。
( 2)引用调用引用是一种特殊类型的变量,可以被认为是另一个变量的别名。可以通过引用运算符,&”用来说明一个引用。通过引用名与通过被引用的变量名访问变量的效果是一样的。如上例函数采用引用调用的形式,代码如下:
#include <iostream.h>
void swap(int &x,int &y)
{ int tmp; tmp = x; x = y; y = tmp;}
void main( )
{ int a = 10,b = 5;
cout << "交换前:a= " << a << ",b= " << b << endl;
swap(a,b);
cout << "交换后:a= " << a << ",b= " << b << endl;
}
其实现的功能完全相同。即交换前后 a,b 的值没有发生变化。
注意调用标准库函数时,要包含相应的头文件。如输入/输出函数对应头文件“iostream.h”;常用数学函数对应头文件“math.h”等。
3,全局变量与局部变量根据作用域的不同,可将程序中的变量分为局部变量和全局变量。
定义在函数内或块内的变量称为局部变量,局部变量在程序运行到它所在的块时建立在栈中,该块执行完毕后局部变量占有的空间即被释放,如果局部变量在定义时未初始化,其值为随机数值。
57
励志照亮人生 编程改变命运第4 章面向对象程序设计与C++语言全局变量在所有函数之外定义,可以为本源程序文件中位于该全局变量定义之后的所有函数共同使用,全局变量可以在各个函数之间建立数据传输通道。
全局变量和局部变量的使用举例如下:
int x; //定义全局变量
int func1(int x) //函数func1()有一个名为x的参数
{ y = x;,..,.,}
int func2(int y) //函数func2()中说明了一个名为x的局部变量
{ int x;,..,.,}
void main() //在主函数中为全局变量x赋值
{,..,..
x = 0;
...,..
}
4,内联函数内联函数用标识符 inline来定义。当程序编译时,编译器就对内联函数在其调用处进行替换,即将内联函数的实际代码插入在调用处,从而消除执行过程中的函数调用开销。例如,求最大值的内联函数可表示为:
inline int max(int x,int y)
{
if (x>y) return x;
else return y;
}
说明如果在声明类的同时,在类体内给出成员函数的定义,则默认为内联函数。
与非内联函数不同,如果改变一个内联函数,则所有调用它的源文件都要重新编译,使得函数变成内联函数,不改变其含义,只是影响目标码的速度和长度。因此内联函数通常用于小而常用的函数,
特别是在一个循环中重复调用。
在某种意义上,内联函数类似于用 #define预处理器命令定义的宏,如上面定义的内联函数与下面的宏实现的功能基本相同:
#define MAX((int x,int y)(x>y?x:y)
然而内联函数与宏的处理方法不同。宏替换由预处理程序进行简单的字符串替换,在替换过程中不进行语法的检查;而内联函数由 C++编译器进行处理,在插入代码之前即进行语法检查。
注意如果内联函数由多个源文件引用,则应将该函数的实现代码放在头文件中,而不能仅仅将该函数的原型声明放入头文件中。
5,函数重载具有相似功能的不同函数使用同一函数名,但这些同名函数的参数类型、参数个数、返回值类型和函数功能可以不同。
如下面的函数重载实例代码:
58
励志照亮人生 编程改变命运零基础学 Visual C++
double plus(double x,double y)
{
return x+y;
}
float plus(float x,float y,float z)
{
return x+y+z;
}
如果只是函数返回值类型不同,编译器就无法确定调用哪个函数。
3.2.5 C++的指针指针是 C++语言具有代表性特征的功能之一,利用指针可以直接对内存中不同数据类型的数据进行快速处理,并且它为函数中各种数据的传递提供了简捷便利的方法。
1,基本概念计算机的内存就像一个一维数组,每个数组元素就是一个存储单元,而地址就是存放信息数据的内存单元的编号。程序中定义的任何变量、数组或函数等,在编译时都会在内存中分配一个确定的地址单元。
C++规定,可以用取地址运算符,&”来获取变量的地址;可以用数组名表示数组的地址;可以用函数名表示函数的地址。
而指针是 C++语言中的一种数据类型,是专门用来处理地址的,也可以说指针是包含另一个变量地址的变量。指针变量用星号,*”表示,定义指针变量是通过定义该指针所指向的变量类型进行的。
如,int *ptr; ptr就是一个整形的指针变量。
指针运算符,*”具有取地址内容的作用,如,
int *ptr; x=3; ptr=&x;
*ptr即取 x地址中的值 3。
2,指针的声明声明指针的一般格式如下:
数据类型*指针变量名;
如,int * ptr;float *array;char *s1,*s2;等。
由于内存地址值是固定不变的,所以不同类型的指针本身所占据的存储区域都一样大。指针在定义后必须初始化才能使用,指针的初始化可表示如下:
int *ptr,i=10; ptr=&i; //指向单个变量
char *sp="string"; //指向字符串
int a[5],*ap; ap=a; //指向数组
int max(),(*fp)(); fp=max; //指向函数
3,指针的运算
( 1),&”和,*”运算符
,&”称为取地址运算符,用以返回变量的指针,即变量的地址。而,*”称为指针运算符,用以
59
励志照亮人生 编程改变命运第4 章面向对象程序设计与C++语言返回指针所指向的基类型变量的值。关于,&”和,*”运算符的使用如下面的数据交换函数的代码:
void swap(int *xp,int *yp)
{ int tmp;
tmp = *xp; *xp = *yp; *yp = tmp;
}
void main()
{ int x = 10,y = 5;
cout<<"x="<<x<<",y="<<y<<endl;
swap(&x,&y);
cout<<"交换后的值"<<endl;
cout<<"x="<<x<<",y="<<y<<endl;
}
与3.2.4节介绍的函数的传值调用不同,这里采用的是通过指针进行地址调用,运行后发现 x和 y的值发生了交换,即 x = 5,y =10。函数 swap运算时的内存分配如图3.11所示。
( 2)指针的赋值可以将一个指针赋值给另一个指针,结果是两个指针指向一个相同的地址单元。例如,
jp=&a;ip=jp;
其结果就是 ip和 jp都指向 a。
( 3)指针的算术运算指针的算术运算只能进行加减,完成指针移动,
实现对不同数据单元的访问操作。对不同的类型,
移动的单位长度不同。如对整型( 4个字节)的加减运算,指针的移动可表示为如图3.12所示。
4,指向数组的指针由于数组名代表数组的首地址,故可以把数组名赋给一个指针变量,这就是一个指向数组的指针。
可以利用指针访问数组中不同的元素。例:
int X[10];
int *pX;
pX=&X[0]; //或pX=X;
例中,pX为指向数组 X[]的指针,表示第一个数组元素的地址。因此有 X[i]=*(pX+i)。
3.3 C++的面向对象特性作为支持面向对象的方法( OOP)的最主要代表语言,C++语言具有面向对象技术的所有特性。
它以类和对象为基础,支持类的继承、封装和多态特性。本节将简单介绍一下 C++中与面向对象相关的知识。
60
励志照亮人生 编程改变命运零基础学 Visual C++
图3.11 函数 swap运算时的内存分配地址 值函数 swap中的局部变量 temp
函数 swap中的参数变量 xp
函数 swap中的参数变量 yp
主函数中的变量 y
主函数中的变量 x
10
10
5
0x0012FF18
0x0012FF24
0x0012FF28 0x0012FF78
0x0012FF7C
0x0012FF78
0x0012FF7C
图3.12 指针的算术运算存储单元
ps-1
ps
ps+1
*(ps+1)
*(ps-1)
*ps
3.3.1 C++中的类类是具有相同属性和相同的方法的对象的集合,它是一种既包含数据又包含函数的抽象数据类型。
类是将一类对象和其他对象区别开来的一组描述,类是对象集合的抽象,对象是类的一个实例。
1,类的声明在 C++中,声明类的一般形式为:
class 类名{
private,
私有数据和函数
public,
公有数据和函数
protected,
保护数据和函数
};
类声明以关键字 class开始,其后跟类名。
类所声明的内容用花括号括起来,右花括号后的分号作为类声明语句的结束标志。这一对花括号,{}”之间的内容称为类体。
类中定义的数据和函数称为这个类的成员(数据成员和成员函数) 。
类成员均具有一个属性,叫做访问权限,通过它前面的关键字来定义。顾名思义,关键字
private,public和 protected 以后的成员的访问权限分别是私有、公有和保护的,所以把这些成员分别叫做私有成员、公有成员和保护成员。
private部分的数据成员或成员函数,在类之外是不能访问的,只有类中的成员函数才能访问。
public部分的数据成员和成员函数可被程序中的任何函数或语句存取。 protected部分说明的成员或成员函数在类之外是不能存取的,只有类的成员函数及其子类可以存取。
说明如果没有使用关键字,则所有成员默认声明为private权限。实际编程当中,public成员多为成员函数,用来提供与外界的接口,只有通过这个接口才能实现对private成员的存取。
如,一个简单的 Student类的声明如下:
class Student
{private:
char m_strName[20];
int m_nAge;
public:
Student ( const char *name,int age); //构造函数
~ Student () //析构函数
void Register(char *name,int age);
char * GetName();
int GetAge();
void Show();
};
2,成员函数的定义在 C++的类中,成员函数定义的一般形式如下:
61
励志照亮人生 编程改变命运第4 章面向对象程序设计与C++语言
<类型> <类名>,,<函数名> (<参数表>)
{
<函数体>
}
如 Student类中,各成员函数的实现代码可表示如下:
void Student:,Register(char *name,int age)
{ strcpy(m_strName,name);
m_nAge = age;
}
char * Student:,GetName()
{ return m_strName; }
int Student:,GetAge()
{ return m_nAge; }
void Student:,Show()
{ cout << GetName() << '\t' << GetAge()<< endl; }
3,构造函数与析构函数构造函数的功能是在定义类的对象时,被编译系统自动调用来创建对象,并初始化对象。其定义格式如下:
<类名>::<类名>(<参数表>)
{
<函数体>
}
使用构造函数时,应注意以下几点:
构造函数的函数名与类名相同,且不指定返回值类型,它有隐含的返回值,该值由编译系统内部使用。
构造函数可以没有参数,也可以有参数,因此可以重载,即可以定义参数不同的多个构造函数。
每个类都必须有一个构造函数。如果类中没有定义构造函数,则编译系统自动生成一个默认形式的构造函数,作为该类的公有成员。
程序中不能直接调用构造函数,在定义对象时编译系统自动调用构造函数。
析构函数的功能是在对象的生存期即将结束的时刻,由编译系统自动调用来完成一些清理工作。
它的调用完成之后,对象也就消失了,相应的内存空间也被释放。
析构函数也是类的一个公有成员函数,它的名称是由类名前面加,~”构成,也不指定返回值类型。和构造函数不同的是,析构函数不能有参数,因此不能重载。
如 Student类的构造函数的实现可表示如下:
Student:,Student (const char *name,int age)
{strcpy(m_strName,name); m_nAge = age; }
4,const常量在类中,可以使用 const关键字定义一个常量。 const数据成员的声明必须包含初值,而且这个初值必须是个常量表达式,const常量的值在作用域内保持不变,例如:
62
励志照亮人生 编程改变命运零基础学 Visual C++
const int maxSize=128;
const int intArray[ ]={1,2,3,4,5,6};
const成员是在编译期间就对表达式进行初始化。 const成员本质上就是一种只读的静态成员,访问时必须使用类的名称,而不能是类对象的名称。
说明可以将const常量理解为read-only static,即它是一个静态数据成员,是只读的,即用户不能改写,
其值在定义时就指定。
3.3.2 类的对象对象是包含现实世界物体特征的抽象实体,反映了系统为之保存信息和(或)与之交互的能力。
说明对象可简单表示如下:对象=数据+作用于这些数据上的操作=属性(Attribute)+方法(Method)
声明了一个类之后,即定义了一个用户数据类型。为了使用类,还必须说明类的变量,即类的实例( instance)或对象( object) 。声明对象的语法类似于声明变量,如:
Student stu;
也可以声明由多个对象组成的数组:
Student stu_array[10];
还有一种方法是在声明类的同时声明对象,如:
class Student
{
public:
} stu;
这样就说明了 stu是 Student类的一个对象。声明了类的对象之后,就对该类的数据成员和成员函数进行访问。
要访问类的成员或成员函数,需要在类的对象和 public数据成员或成员函数之间加上,.” 。如:
stu.show();
不能直接访问类的私有成员,如 stu.m_nAge是错误的。
还可以通过说明对象指针,并通过指针调用类的成员,如:
Student * pstu=& stu;
stu->show();
这样就通过 pstu指针来调用成员函数 show()。
3.3.3 C++类的继承与派生保持已有类的特性而构造新类的过程称为继承,在已有类的基础上新增自己的特性而产生新类的过程称为派生,被继承的已有类称为基类(或父类),派生出的新类称为派生类。
当从现存类中派生出新类时,可以对派生类增加新的数据成员、新的成员函数、重新定义已有的成员函数和改变现有成员的属性。
63
励志照亮人生 编程改变命运第4 章面向对象程序设计与C++语言
1,派生类的声明派生类的格式定义可简单表示如下:
class 派生类名:访问权限基类名1,访问限定符基类名2,,访问限定符基类名n
{ private:
成员表1; //派生类增加或替代的私有成员
public,
成员表2; //派生类增加或替代的公有成员
protected,
成员表3; //派生类增加或替代的保护成员
};
其中基类 1、基类 2、?,是已声明的类。可以使用的访问限定符包括 public,private和
protected。在派生类定义的类体中给出的成员称为派生类成员,它们是新增加的数据和函数成员。这些新增加的成员是派生类对基类的发展,它们给派生类添加了不同于基类的新的属性和功能。
2,单一继承与多重继承如果一个派生类可以同时有多个基类,则称为多重继承( multiple-inheritance),这时的派生类同时得到了多个已有类的特征。如果一个派生类只有一个直接基类的情况则称为单一继承
( single-inheritance) 。单一继承和多重继承类的层次关系可表示为如图3.13所示。
可见,一个基类可以直接派生出多个派生类。
而派生出来的新类同样可以作为基类再继续派生出更新的类,依此类推形成一个层次结构。
3,派生类的继承方式派生类对基类的访问控制也是三种:公有
( public)方式、保护( protected)方式和私有
( private)方式,也称为公有继承、保护继承和私有继承。在派生类的定义中,基类前所加的访问限定符有两方面含义:
派生类成员(新增成员)函数对基类(继承来的)成员的访问(调用和操作) 。
从派生类对象之外,对派生类对象中的基类成员的访问。
各继承方式的访问权限如表3.4所示。
4,派生类的构造函数与析构函数派生类的构造函数的一般形式为:
派生类名::派生类名(参数总表):基类名1(参数表1),?,基类名n(参数表n),
内嵌对象名1(对象参数表1),?,内嵌对象名m(对象参数表m)
64
励志照亮人生 编程改变命运零基础学 Visual C++
图3.13 单一继承和多重继承基类派生类 1 派生类 1派生类 2
单一继承 多重继承派生类 2
基类 1 基类 2 基类 n
表3.4 各继承方式的访问权限基类成员继承派生类中对基类外部函数访问权方式成员的访问权的访问权
Public public 可以直接访问
public Protected protected 不可直接访问
Private private 不可直接访问
Public protected 不可直接访问
protected Protected protected 不可直接访问
Private private 不可直接访问
private
Public 不可访问 不可直接访问
Protected 不可访问 不可直接访问
Private 不可访问 不可直接访问
{
派生类新增加成员的初始化;
}
派生类构造函数的执行次序为:
首先,调用基类构造函数,调用顺序按照它们被继承时声明的基类名顺序执行。
其次,调用内嵌对象构造函数,调用次序按各个对象在派生类内声明的顺序。
最后,执行派生类构造函数体中的内容。
派生类与基类的析构函数没有什么联系,彼此独立。派生类析构函数执行过程恰好与构造函数执行过程相反,执行顺序如下:
首先执行派生类析构函数。
然后执行内嵌对象的析构函数。
最后执行基类析构函数。
3.3.4 C++类的继承实例为了使读者对继承的概念有直观地认识,这里给出一个多重继承的实例。已知时间类 TimeType和日期类 DateType通过多重继承定义日期时间类 DateTimeType。其中时间类 TimeType的定义如下:
#include<iostream>
using namespace std;
class TimeType
{
int hour,minute,second; //时、分、秒
public:
TimeType(int h=0,int m=0,int s=0) //构造函数
{
hour=h;
minute=m;
second=s;
}
void display() //输出时间
{
cout<<hour<<":"<<minute<<":"<<second<<endl;
}
void SetTime(int h,int m,int s) //成员函数,设置时间
{
hour=h;
minute=m;
second=s;
}
};
日期类 DateType的定义如下:
class DateType
{
int month,day,year; //月、日、年
65
励志照亮人生 编程改变命运第4 章面向对象程序设计与C++语言
public:
DateType(int mo=1,int d=1,int y=2000) //构造函数
{
month=mo;
day=d;
year=y;
}
void display() //输出日期
{
cout<<month<<"/"<<day<<"/"<<year<<endl;
}
void SetDate(int mo,int d,int y) //成员函数,设置日期
{
month=mo;
day=d;
year=y;
}
};
日期时间类 DateTimeType由 TimeType类和 DateType类两个基类派生,其定义如下:
class DateTimeType:public DateType,public TimeType
{
public:
DateTimeType(int mo=1,int d=1,int y=2000,int h=0,int m=0,int
s=0):DateType(mo,d,y),TimeType(h,m,s){} //构造函数
void display()//显示时间、日期
{
DateType::display(); //调用DateType类的display函数
TimeType::display(); //调用TimeType类的display函数
}
};
主函数中,实现对日期时间类 DateTimeType的调用测试,代码如下:
int main()
{
DateTimeType dt(8,4,2007,10,8,8); //直接使用DateTimeType构造函数设置日期时间
cout<<"DateTimeType类设定的日期、时间为:"<<endl;
dt.display();
dt.SetDate(9,12,2007); //调用基类的成员函数修改日期
dt.SetTime(10,8,14); //调用基类的成员函数修改时间
cout<<"调用基类成员函数修改后的日期、时间为:"<<endl;
dt.display();
return 0;
}
运行结果如图3.14所示。
在本实例中,读者需要注意多重继承构造函数的声明,以及对基类成员函数的访问。
66
励志照亮人生 编程改变命运零基础学 Visual C++
图3.14 程序运行结果
3.3.5 C++类的多态性简单来讲,C++类的多态性是指类中同一函数名对应多个具有相似功能的不同函数,可以使用相同的调用方式来调用。或者说是类的对象在接受同样的消息时,能够做出不同的响应,从而实现“一种接口,多种方法”的技术。
1,多态的实现在 C++中有两种多态性:编译时的多态性和运行时的多态性。
编译时的多态性是指编译器对源程序进行编译时就可以确定所调用的是哪一个函数,它是通过重载来实现的,包括函数重载和运算符重载。关于函数的重载参见3.2.4节的介绍。
运行时的多态性是指在程序执行前,无法根据函数名和参数来确定该调用哪一个函数,必须在程序执行过程中,根据执行的具体情况来动态地确定。它是通过类继承关系和虚函数来实现的。
2,运算符的重载重载运算符主要用于对类的对象的操作,其形式如下:
<类型> <类名>::operator <操作符>(<参数表>)
{
...
}
下面通过重载,+”运算符分别实现复数与复数的加法运算和复数与实数的加法运算,实现代码如下:
#include <iostream.h>
class Complex //定义复数类
{
double m_fReal,m_fImag;
public:
Complex(double r = 0,double i = 0),m_fReal(r),m_fImag(i){} //构造函数
double Real(){return m_fReal;}
double Imag(){return m_fImag;}
Complex operator +(Complex&); //重载运算符+
Complex operator +(double); //重载运算符+
};
Complex Complex::operator + (Complex &c) //重载运算符+,实现两个复数的加法
{
Complex temp;
67
励志照亮人生 编程改变命运第4 章面向对象程序设计与C++语言
temp.m_fReal = m_fReal+c.m_fReal;
temp.m_fImag = m_fImag+c.m_fImag;
return temp;
}
Complex Complex::operator + (double d) //重载运算符+,实现复数与实数的加法
{
Complex temp;
temp.m_fReal = m_fReal+d;
temp.m_fImag = m_fImag;
return temp;
}
//测试主函数
void main()
{ Complex c1(3,4),c2(5,6),c3;
cout << "C1 = " << c1.Real() << "+j" << c1.Imag() << endl;
cout << "C2 = " << c2.Real() << "+j" << c2.Imag() << endl;
c3 = c1+c2;
cout << "C3 = C1 + C2 =" << c3.Real() << "+j" << c3.Imag() << endl;
c3 = c3+6.5;
cout << "C3 + 6.5 = " << c3.Real() << "+j" << c3.Imag() << endl;
}
运行结果如图3.15所示。
3,虚函数虚函数是指在某基类中声明为 virtual,并在一个或多个派生类中被重新定义的成员函数。其声明格式如下:
virtual 函数返回类型函数名(参数表)
{函数体}
关键字 virtual指明该成员函数为虚函数。 virtual仅用于类定义中,如虚函数在类外定义,不可加
virtual。基类的成员函数一旦被声明为虚函数,每一层派生类中该函数都保持虚函数特性,在其派生类中其关键字 virtual可省略。
当在派生类中重新定义虚函数(也称覆盖)时,不必加关键字 virtual。但重新定义时不仅要同名,
而且它的参数表和返回类型全部与基类中的虚函数一样。
说明虚函数的主要用途是实现多态性,即通过指向派生类的基类指针,访问派生类中同名覆盖成员函数。
下面给出一个简单的虚函数的使用实例,代码如下:
#include <iostream.h>
class Person //基类
{
public:
virtual void Show()
{
cout<<"A Person"<<endl;
68
励志照亮人生 编程改变命运零基础学 Visual C++
图3.15 程序运行结果
}
};
class Student:public Person //派生类
{
public:
virtual void Show()
{
cout<<"A Student"<<endl;
}
};
void main() //主函数
{
Person per,*ps;
Student stu;
ps=&per; //指向基类的对象
ps->Show();
ps=&stu; //指向派生类的对象
ps->Show();
}
运行结果如图3.16所示。
在本例中,同样是通过基类 Person指针,调用 Show函数,但由于指针对象的不同,实现的虚函数 Show的形式也就不同。程序对虚函数的调用可表示为如图3.17所示。
通过定义虚函数,实现了类的对象在接受同样的消息时,能够做出不同的响应。对于本例而言,这种关系可表示为如图3.18所示。
一般而言,可将类中具有共性的成员函数声明为虚函数,派生类定义虚函数的时候,必须保证函数的返回值类型和参数与基类中的声明完全一致。
69
励志照亮人生 编程改变命运第4 章面向对象程序设计与C++语言图3.16 程序运行结果
ps->Show()
Person per
Show()
Student stu
Show()
图3.17 程序对虚函数的调用 图 3.18 虚函数实现同样消息的不同响应消息对象行为
A Person
A Student
励志照亮人生 编程改变命运零基础学 Visual C++
第3 章面向对象程序设计与C++语言
Windows编程采用的是面向对象的方法(Objec Oriented Programming,简称 OOP) 。面向对象软件开发的方法是吸收了软件工程领域中有益概念和有效的方法,而发展起来的一种软件开发方法。它集抽象性、封装性、继承性和多态性于一体,可以帮助人们开发出模块化、数据抽象程度高的、体现信息隐蔽、可复用、易修改、易扩大等特性的程序。
Visual C++是 Windows环境下最主要的 C++开发环境,它支持面向对象编程,并提供可视化编程环境。要使用Visual C++进行程序开发,有必要了解面向对象的基本概念和掌握C++语言。本章首先简要介绍面向对象的基本概念,而后对 C++语言的基础及要点作简单的介绍。
3.1 面向对象程序设计概述面向对象的程序的最根本的目的就是使程序员更好地理解和管理庞大而复杂的程序,它在结构化程序设计的基础上完成进一步的抽象。这种在设计方法上更高层次的抽象正是为了适应目前软件开发的特点。 3
3.1.1 面向对象的基本概念实际上,面向对象的技术不只是一种程序设计方法,而是建立客观事务模型,分析复杂事务的思想方法。本节将简要介绍一下与面向对象相关的基本概念。
1,对象、类、实体面向对象的程序是由若干“对象”有机结合而成。对象是面向对象的程序设计中最基本的概念,
一般意义上的对象指的是一个实体的实例,确切地说,面向对象的方法中的对象就是现实世界中某个具体的物理实体在计算机逻辑中的映射和体现。
而现实世界的实体(如自行车),在计算机世界表达为一个计算机可理解、可操纵、具有一定属性和行为的对象。
而类则可以理解为对同种对象的集合与抽象。类和对象的关系,就如同整数与 1,2,3的关系一样,即对象是类的实例化。可以理解为类是一个抽象的框架,类中的变量赋了值就是对象。类、对象与实体的关系可表示为如图3.1所示。
面向对象的程序是由若干“对象”有机结合而成。这些对象可以互相通讯、协调和配合,从而共同完成整个程序的任务和功能。
2,对象的属性面向对象的方法中,一个对象具有状态、行为和标识三方面特性。
对象的状态也称为对象的静态属性,包括对象内部包含的各种信息 — 变量,这些变量的值标明
图3.1 类、对象与实体的关系计算机世界现实世界客观世界主观 世界抽象对象 实体类抽象类别抽象实例化映射映射了对象所处的状态。当对象通过某种操作改变了状态,也就体现在它的属性变量的值的改变。对象的行为也称对象的操作方法 — 对象的动态属性,其作用是设置或改变对象的状态。对象的标识则用于区分不同的对象。
简单地说,对象包含了数据和方法(在 C++中,方法叫作类的成员函数),每个对象就是一个微小的程序。由于其他对象不能直接操纵该对象的私有数据,只有对象本身的方法才能得到它,因而对象具有很强的独立性,可把对象当作软件的基本组件,就像电器中的电子元件一样。对象的这种软件组件作用使它具有很强的可重用性。而且增强了程序的可靠性和可维护性。
3,消息面向对象的程序设计中,通过“消息”来请求对象进行动作,对象间的联系也是通过消息
( Message)来完成的。消息中只包括了消息发送者的要求,不指示接收者具体该如何处理这些消息。
一个对象可以接收不同形式、不同内容的消息;相同的消息可以传送给不同的对象;不同的对象对同样的消息可以作出不同的反映。有关 Windows的消息机制在第 3章已经详细介绍了。
4,类的组织结构类是对具有公共的方法和一般特性的一组基本相同对象的描述,在面向对象的方法中,对象是构成程序的基本单位,每个对象都应该属于某一类,就象传统程序设计中的变量,每个变量都应有一定的类型。
在程序执行过程中,由类动态生成相应地对象,一个类可以生成多个不同的对象,这些对象具有相同的属性。因此,对象也称为类的实例( Instance) 。一个类可以由其他的类派生出来,类与类之间根据具体情况以层次结构组织起来。处于上层的类称为父类,处于下层的类称为子类或派生类。
采用面向对象的方法来进行 Windows程序设计还可以简化对资源的管理。将资源映射成一个 C++
对象时,对资源的使用可以翻译成以下 C++顺序。
创建一个对象:如定义一个画笔对象。
使用对象:用画笔绘图。
撤销该对象。
一个对象的创建是对一个对象的定义过程,可以由对象的构造函数处理对资源的请求过程。当某一个对象退出活动范围时,它的撤销可以由编译器来自动管理。
3.1.2 面向对象技术的基本特征到目前为止,对面向对象的概念的定义仍有不同的认识,但它的几个基本特征,如封装性、继承性、多态性等,基本上得到认可。
1,封装性封装就是把对象的属性和方法结合成一个独立的系统单位,并尽可能隐蔽对象的内部细节。封装使对象形成两个部分:接口和实现。对于用户来说,接口是可见的,实现是不可见的。同样形式的接口,其实现可能不同。好比取款机都有相同的取钱接口,但是不同的取款机实现这种取钱的接口方法不一样。
封装提供了两种保护:其一是保护对象,防止用户误用对象内部的属性和方法;其二是保护客户端,即实现过程的改变不影响到接口,从而减小对客户端的不利影响。
47
励志照亮人生 编程改变命运第4 章面向对象程序设计与C++语言
2,继承性真实的对象不是孤立存在的,每个对象都与其他一个或多个对象相关。事实上,当描述一个新对象时,往往指出该新对象的特征与已存在的对象的特征有何不同。对于面向对象来说,继承性是指一个新类可以从现有的类中派生出来,新类具有父类中的所有特性,直接继承了父类的方法和数据,新类的对象可以调用该类及父类的成员变量和成员函数。
如 MFC中的 CEdit类,它封装了 Windows中的编辑框控件,它的继承结构如图3.2所示。
类 CObject是所有的 MFC类的根类。类 CCmdTarget从类 CObject直接派生,它是 Microsoft基础类库的消息映射结构的基类。类 CWnd提供了 MFC中所有窗口类的基本功能性,它封装了 Windows中的窗口句柄 hWnd。类 CEdit从 CWnd直接派生,它提供了对 Windows编辑控件的特定支持。由于 CEdit类继承了其基类的数据和方法,因此,可以通过
CEdit类调用 CWnd类中提供的方法来实现对标准 Windows窗口的操作。
继承保证了类之间的一致性,父类可以为所有子类定制规则。利用继承可以开发更加贴近现实的模型,增加软件重用的机会,从而降低开发和维护费用。子类可以继承父类的属性,也可以增加和重新定义继承的属性。同样,子类可以继承父类的操作,也可以增加或者重新定义继承的操作,这种重新定义被称为覆盖( Override) 。
继承可分为单继承和多继承。单继承指的是子类只从一个父类继承,而多继承则允许子类可以继承多个父类。例如,交通工具是父类,它有三个子类,分别为空中交通工具、水上交通工具和陆上交通工具。而一个两栖交通工具则可以同时继承水上交通工具和陆上交通工具。很多面向对象的语言都不支持多重继承,但 C++支持。
3,多态性多态性是指在一般类中定义的属性或方法被特殊类继承后,可以具有不同的数据类型或表现出不同的行为功能。例如,人都有取名字的操作,但是不同的民族取名字的方式不一样,对于一个不知是何民族的人,可以让他执行“人”这个类共有的操作“取名字”时,他实际执行的是以他所属民族的取名字操作。
在 C++中,多态性定义为不同函数的同一接口。从这个定义出发,函数和操作符的重载也属于多态。
说明多态和继承中的重载(Override)是有区别的。多态是运行时问题,而重载(override)是编译时问题。
封装性、继承性和多态性是面向对象编程的三大特征,开始的时候,读者也许对它们还没有非常清晰的概念,但这没有什么关系,当使用了一段时间 C++语言,然后再回过头来看这些概念时,就会发现对它们有了更深入的认识和了解。
3.2 C++语言基础在本节,将简单介绍一下 C++语言的基础,包括程序的基本控制结构、基本数据类型、运算符和表达式、函数和指针的应用等。通过本节的讲解,读者对 C++语言的基本程序结构会有所了解。
48
励志照亮人生 编程改变命运零基础学 Visual C++
图3.2 CEdit类的继承结构
CObject
CCmdTarget
CWnd
CEdit
3.2.1 C++基本控制结构按照结构化程序设计的观点,任何算法功能都可以通过由程序模块组成的三种基本程序结构的组合。
顺序结构:程序是按程序语句或模块在执行流中的顺序逐个执行。
选择结构:程序是按设定的条件实现程序执行流的多路分支。
循环结构:程序是按给定的条件重复地执行指定的程序段或模块。
这三种结构的直观表示如图3.3所示。
图3.3 三种基本程序结构在 C++中,顺序结构的语句包括说明语句、赋值语句,I/O语句、子函数调用语句和返回语句等,
这里不作详细介绍,重点介绍一下 C++中的选择结构和循环结构。
1,选择结构
C++中的选择结构分为一路选择分支、两路选择分支和多路选择分支。
( 1)一路选择分支一路选择语句格式如下:
if (<逻辑表达式>)
<语句序列>
语句序列可以是一个语句,也可以是复合语句结构,其直观表示如图3.4所示。
( 2)两路选择分支两路选择语句格式如下:
if (<逻辑表达式>)
<语句序列1>
else
<语句序列2>
其直观表示如图3.5所示。
49
励志照亮人生 编程改变命运第4 章面向对象程序设计与C++语言模块 1
模块 1
顺序结构选择结构循环结构条件条件模块 2
模块 2
循环体
Y
N
N
Y
图3.4 一路选择分支结构图3.5 两路选择分支结构条件? 条件?
不成立成立 不成立成立语句序列语句序列 1 语句序列 2
( 3)多路选择分支也可以使用 if语句实现多路选择分支,语句格式如下:
if (<逻辑表达式>)
<语句序列1>
else if (<逻辑表达式2>)
<语句序列2>
else
<语句序列3>
else应和最近的 if嵌套,其直观表示如图3.6所示。
switch语句也可以实现多路(开关)选择结构,其语句格式如下:
switch(<整数表达式>)
{ case 数值1:
<语句序列1>;
break;
......
case 数值n:
<语句序列n>;
break;
[default,
<语句序列n+1>;]
}
其直观表示如图3.7所示。
2,循环结构
C++中有三种循环语句构建方式,while循环,do_while循环和 for循环。
( 1) while循环
while循环语句格式可表示如下:
while(<逻辑表达式>)
{<循环体> }
其直观表示如图3.8所示。
( 2) do_while循环
do_while循环语句格式可表示如下:
50
励志照亮人生 编程改变命运零基础学 Visual C++
图3.6 if 语句实现多路选择分支 图3.7 switch语句实现多路选择分支语句序列 1
语句序列 2
语句序列 3
语句序列 2 语句序列 n
值 =?
计算整型表达式语句序列 1
成立成立不成立不成立条件?
条件?
do
{<循环体> }
while(<逻辑表达式>)
其直观表示如图3.9所示。
( 3) for循环
for循环语句格式可表示如下:
for(<表达式 1>;<表达式 2>;<表达式 3>)
{<循环体 >}
3,C++基本控制实例下面通过一个简单实例讲解 C++基本控制结构。该实例实现将百分制转换为五分制,即用户输入一组百分成绩,将它们转换成 5分制成绩。实例程序的模块结构与逻辑功能框图如图3.10所示。
图3.10 实例程序的模块结构与逻辑功能框图实例循环结构采用 for循环语句,而选择结构则采用多路选择语句 switch。子函数 GradeTran()实现代码如下:
int GradeTran (int old_grade)
{ int new_grade;
51
励志照亮人生 编程改变命运第4 章面向对象程序设计与C++语言
¢
A` ˇp x
A` ˇp x
¢
x
A` ˇpx
¢
图3.8 while循环结构 图3.9 do_while循环结构条件?
条件?
循环体循环体成立不成立不成立成立
主函数 main()
返回、输出 X
输入成绩 X
X=1 X=2 X=3 X=4 X=5
X/10=?
继续?
否是
10,9 8 7 6 <=5
子函数 GradeTran()
实现分数转换
switch(old_grade/10) //switch多路选择
{ case 10:
case 9,new_grade = 1; break;
case 8,new_grade = 2; break;
case 7,new_grade = 3; break;
case 6,new_grade = 4; break;
default,new_grade = 5;
}
return new_grade;
}
主函数 main()的实现代码如下:
#include <iostream.h>
void main()
{
int grade[10] ={100,85,72,69,94,74,66,51,89,45}; //要转换的10个百分制分数
int i;
for(i=0; i<10; i++)
//调用子函数进行分数转换,并输出
cout<<"百分制分数:"<<grade[i]<<",五分制分数:"<<TranGrade(grade[i])<<endl;
}
3.2.2 C++的数据与基本数据类型
C++的数据有两种:常量和变量,且每个数据均需指明其类型。在本节,简单介绍 C++的数据和基本数据类型。
1,基本数据类型数据在计算机中采用二进制存放,一个字节包括 8个二进制位。 C++语言的基本数据类型,及其长度和表示的数据范围如表3.1所示。
表3.1 C++语言的基本数据类型数据类型类型说明符占用字节数据范围字符型 char 1 - 128? 127
短整数 short 2 - 32768? 32767
整型 Int 4 - 2
31
2
31
浮点型 float 4 近似 3.4
- 38
3.4
38
精度 7位双精度型 double 8 近似 1.7
- 308
1.7
308
,精度 15位另外,在基本数据类型前加类型修饰符,可以改变数据表示的范围。常用的有:
unsigned:无符号。
long:长型。
short:短型。
如,unsigned char,表示范围变为 0? 255; unsigned int,表示范围变为 0? 65535。
2,常量变化的量称变量,不变化的量称常量,常量和变量是计算机语言中数据的两种基本形式。在 C++
52
励志照亮人生 编程改变命运零基础学 Visual C++
中,常量主要有下面几种形式。
( 1)整型常量在 C++中,整型常量可以表示为 2进制,8进制,10进制和 16进制。
2进制常量:如 10011101B,10B等,即在数后加字符,B” 。
8进制常量:如 04400,0777,0100等,即在数前加字符,0” 。
10进制常量:如 2304,432等。
16进制常量:如 0x900,0xABC,0xffff等,即在数前加字符,0x” 。
( 2)实型常量实数也称为浮点数,用于表示小数,有两种表示方式:
十进制形式,如- 2.68,3.141593,637.312、- 32767.0等。
指数形式,如 0.0E0,1.267E20、- 6.226E- 4等。
( 3)字符型常量
C++中的字符型常量包括符号常量、字符常量和转义常量。
符号常量:用 #define定义的常数,类似变量,但不是变量。
例如:
#define PI 3.1415926
#define MAXNUM 2000
它可以出现在表达式中,如:
S= r * r * PI;
但是符号常量不能作左值,如下列用法是错误的。
PI = PI*2; //错误的用法
字符常量:用来表示一个字符,如 'a','A','1',' ','+'等,即将字符符号用单引号括起来。
转义常量:常用的转义常量包括 '\n'(表示换行),'\r'(表示回车),'\t'(表示横向跳格),'\''
(表示单引号)等。
( 4)字符串常量
C++中,用双引号定义字符串常量,如 "Visual C++6.0","12.34","This is a string.\n"等。在 C++
中,'A'和 "A"是有区别的,前者是字符常数,后者是字符串常数。
字符是用单引号括起来的单个字符,它在存储器中占 1个字节。
字符串是用双引号括起来的一串字符,它在存储器中占 n+1个字节,即字符串的结束符 '\0'也占
1个字节的位置。
( 5) const常量
C++要求在声明 const常量时对其进行初始化,如:
const double pi=3.1415926;
C++中 const常量的另一个特征是,const整数在任何常量表达式中都可作下标使用。如:
const buflen=512;
char buffer[buflen];
3,变量
C++的数据变量声明语句的格式为:
53
励志照亮人生 编程改变命运第4 章面向对象程序设计与C++语言
<类型说明符> <变量名1> ;
如,
int i,j,k; //声明了3个整型变量
long len; //声明了1个长整型变量
char c0,c1,c2[100]; //声明了2个字符型变量和一个字符型数组变量
double array[10][10]; //声明了一个双精度型的二维数据变量
C++中变量具有一定的命名规则,如下:
变量名只能由字母、数字或下划线组成;
变量名的第一个字符必须是字母或下划线;
变量名长度不超过 32个字符;
不要用 C++的保留字定义变量名。
声明变量时,一般需要对变量进行初始化。变量初始化就是给变量赋初值,有两种形式:
先定义,再赋初值。例如,
int sum,fac;
sum=0;
fac=1;
定义时赋值。例如,
char c='A';
int count = 0;
3.2.3 C++的运算符和表达式
C++中的表达式包括算术运算、逻辑运算、关系运算、赋值运算、逗号运算和自增(自减)运算等,本节将简单介绍这些运算的基本概念以及运算符的优先级、左结合和右结合规则。
1,算术运算符和算术表达式由算术运算符组成的表达式称为算术表达式。 C++中的算术运算符包括,+” (加),,-” (减),
,*” (乘),,/” (除)和,%” (取余) 。其运算优先级顺序为先乘、除、取余,后加、减。其具体使用相信读者都已经很熟习了。
2,关系运算符和关系表达式由关系运算符组成的表达式称为关系表达式。 C++中可用的关系运算符有,>” (大于),,<” (小于),,==” (等于),,>=” (大于等于),,<=” (小于等于),,!=” (不等于) 。关系运算符的优先级顺序为 {>,>=,<,<=}高于 {==,!=}。关系表达式最终的运算结果为逻辑值( true或 false) 。
说明赋值运算、算术运算和关系运算的优先级顺序为:赋值运算<关系运算<算术运算。
3,逻辑运算符和逻辑表达式由逻辑运算符组成的表达式称为逻辑表达式。 C++中可用的逻辑运算符有,&&” (与运算),,||”
(或运算)和,!” (非运算) 。逻辑运算符的优先级顺序为,!” >,&&” >,||”,逻辑表达式最终的运算结果为逻辑值( true或 false) 。
54
励志照亮人生 编程改变命运零基础学 Visual C++
说明逻辑运算符与其他运算符组合的运算的优先级顺序为:赋值运算<“&&”、“||”<关系运算<算术运算<“!”。
下面给出几个逻辑表达式的使用实例。
判别闰年表达式可表示如下:
(year %4==0 && year%100!=0)||year%400==0
i和 j均小于或等于 100,或者 i和 j均大于 k,可表示如下:
(i <= 100 && j <= 100) || (i > k && j > k )
4,赋值运算符和赋值表达式由赋值运算符,=”组成的表达式称为赋值表达式。其格式如 V=e,即将表达式 e的值赋值给变量
V。另外,在 C++中还提供了很多复合赋值运算符,包括,+=”,,- =”,,*=”,,/=”,,%=”等 10个。
例如,a += 5,等价于 a = a + 5。
下面给出几种常用的赋值表达式格式。
i=j=m*n;
表示计算表达式 m*n的值,将其结果存入变量 j中,然后再将结果存入变量 i中。
temp=a; a=b; b=temp;
表示交换 a和 b的值。
str[i] = ch+'A'- 'a';
表示将字符变量 ch转换为大写字母,结果存入数组
str[i]中。
5,自增运算符和自减运算符
C++提供了自增运算符,++”和自减运算符“--”,
其含义如表3.2所示。
6,问号表达式
C++中还可以使用问号表达式,其格式为:
e1?e2:e3
运算规则为,当表达式 e1的值为“真”时,结果取 e2的值;否则,结果取 e3的值。
如,求双精度数的绝对值的函数可表示为:
double abs(double x)
{
return x>0?x:-x;
}
至此,C++各运算符介绍完毕,各运算符的优先级别及运算形式如表3.3所示。
55
励志照亮人生 编程改变命运第4 章面向对象程序设计与C++语言表3.2 自增运算符和自减运算符的使用表达式表达式的值i的值
i++ i i的值增大 1
++i i+1 i的值增大 1
i-- ii的值减小 1
-- ii- 1i的值减小 1
表3.3 C++运算符的优先级别及运算形式优先级别运算符运算形式名称或含义
1
() (e) 圆括号
[] a[e] 数组下标
2
-,+ - e 负号和正号
++、-- ++x或 x++ 自增运算和自减运算
3*,/,% e1*e2 乘、除和求余
4+、- e1+e2 加和减
5<,<=,>,>= e1<e2 关系运算(比较)
6==,!= e1==e2 等于和不等于比较
3.2.4 C++的函数
C++语言程序的结构特点是,程序整体由一个或多个称为函数的程序块组成。每个函数都具有各自独立的功能和明显的界面,从而使程序具有清晰的模块结构。
在 C++语言程序中的若干个函数中,必须有一个且只能有一个函数成为主函数。程序的执行总是从主函数开始,主程序的语句执行完,则程序执行结束。从用户使用的角度看,函数有两种:标准函数和用户自己定义的函数。从函数的形式看,函数分两类:无参函数和有参函数。
1,函数的定义函数定义的一般格式如下:
<函数值类型> <函数名>(<形式参数表>)
{
<函数体>
}
调用函数后所得到的函数值类型,是通过函数体内部的 return语句提供,return语句提供的表达式的值的类型应与函数说明中的函数值类型一致。实际上,return语句完成两件事:强制从被调函数返回主调函数;给主调函数返回其执行结果。如果某一函数确实没有返回值,则使用说明符 void。
形式参数可以在函数体中引用,可以输入、输出、赋值或参与运算。参数说明格式为:
<类型><参数1>,类型><参数2>,?,<类型><参数n>
如简单的加减运算的函数定义如下:
double plus(double x,double y) //加法运算
{
return x+y;
}
double minus(double x,double y) //减法运算
{
return x - y;
}
2,函数的调用主函数和子函数之间的信息交换是通过参数的结合和 return语句来实现的。数据流程可以表示如下:
56
励志照亮人生 编程改变命运零基础学 Visual C++
在主程序中,先给函数的实参赋值;
通过函数调用,将数据从主函数传入子函数;
子函数通过调用形参的值,进行相应的数据处理;
如果有结果值,通过 return语句返回到主函数。
调用自定义函数时,要根据函数的形参定义相应的实参,并给这些实参赋值。实参与形参必须一一对应,即类型一致、位置一致、个数一致。函数的调用根据形参的不同赋值方式,可以分为传值调用、引用调用和地址调用。这里简单介绍一下传值调用和引用调用。
( 1)传值调用在调用时仅将实参的值赋给形参,在函数中对形参值的任何修改都不会影响到实参的值。如下面的实例代码:
#include <iostream.h>
void swap(int x,int y)
{ int tmp; tmp = x; x = y; y = tmp;}
void main( )
{ int a = 10,b = 5;
cout << "交换前:a= " << a << ",b= " << b << endl;
swap(a,b);
cout << "交换后:a= " << a << ",b= " << b << endl;
}
运行程序,可以发现交换前后 a,b 的值没有发生变化,即 a = 10,b = 5。
( 2)引用调用引用是一种特殊类型的变量,可以被认为是另一个变量的别名。可以通过引用运算符,&”用来说明一个引用。通过引用名与通过被引用的变量名访问变量的效果是一样的。如上例函数采用引用调用的形式,代码如下:
#include <iostream.h>
void swap(int &x,int &y)
{ int tmp; tmp = x; x = y; y = tmp;}
void main( )
{ int a = 10,b = 5;
cout << "交换前:a= " << a << ",b= " << b << endl;
swap(a,b);
cout << "交换后:a= " << a << ",b= " << b << endl;
}
其实现的功能完全相同。即交换前后 a,b 的值没有发生变化。
注意调用标准库函数时,要包含相应的头文件。如输入/输出函数对应头文件“iostream.h”;常用数学函数对应头文件“math.h”等。
3,全局变量与局部变量根据作用域的不同,可将程序中的变量分为局部变量和全局变量。
定义在函数内或块内的变量称为局部变量,局部变量在程序运行到它所在的块时建立在栈中,该块执行完毕后局部变量占有的空间即被释放,如果局部变量在定义时未初始化,其值为随机数值。
57
励志照亮人生 编程改变命运第4 章面向对象程序设计与C++语言全局变量在所有函数之外定义,可以为本源程序文件中位于该全局变量定义之后的所有函数共同使用,全局变量可以在各个函数之间建立数据传输通道。
全局变量和局部变量的使用举例如下:
int x; //定义全局变量
int func1(int x) //函数func1()有一个名为x的参数
{ y = x;,..,.,}
int func2(int y) //函数func2()中说明了一个名为x的局部变量
{ int x;,..,.,}
void main() //在主函数中为全局变量x赋值
{,..,..
x = 0;
...,..
}
4,内联函数内联函数用标识符 inline来定义。当程序编译时,编译器就对内联函数在其调用处进行替换,即将内联函数的实际代码插入在调用处,从而消除执行过程中的函数调用开销。例如,求最大值的内联函数可表示为:
inline int max(int x,int y)
{
if (x>y) return x;
else return y;
}
说明如果在声明类的同时,在类体内给出成员函数的定义,则默认为内联函数。
与非内联函数不同,如果改变一个内联函数,则所有调用它的源文件都要重新编译,使得函数变成内联函数,不改变其含义,只是影响目标码的速度和长度。因此内联函数通常用于小而常用的函数,
特别是在一个循环中重复调用。
在某种意义上,内联函数类似于用 #define预处理器命令定义的宏,如上面定义的内联函数与下面的宏实现的功能基本相同:
#define MAX((int x,int y)(x>y?x:y)
然而内联函数与宏的处理方法不同。宏替换由预处理程序进行简单的字符串替换,在替换过程中不进行语法的检查;而内联函数由 C++编译器进行处理,在插入代码之前即进行语法检查。
注意如果内联函数由多个源文件引用,则应将该函数的实现代码放在头文件中,而不能仅仅将该函数的原型声明放入头文件中。
5,函数重载具有相似功能的不同函数使用同一函数名,但这些同名函数的参数类型、参数个数、返回值类型和函数功能可以不同。
如下面的函数重载实例代码:
58
励志照亮人生 编程改变命运零基础学 Visual C++
double plus(double x,double y)
{
return x+y;
}
float plus(float x,float y,float z)
{
return x+y+z;
}
如果只是函数返回值类型不同,编译器就无法确定调用哪个函数。
3.2.5 C++的指针指针是 C++语言具有代表性特征的功能之一,利用指针可以直接对内存中不同数据类型的数据进行快速处理,并且它为函数中各种数据的传递提供了简捷便利的方法。
1,基本概念计算机的内存就像一个一维数组,每个数组元素就是一个存储单元,而地址就是存放信息数据的内存单元的编号。程序中定义的任何变量、数组或函数等,在编译时都会在内存中分配一个确定的地址单元。
C++规定,可以用取地址运算符,&”来获取变量的地址;可以用数组名表示数组的地址;可以用函数名表示函数的地址。
而指针是 C++语言中的一种数据类型,是专门用来处理地址的,也可以说指针是包含另一个变量地址的变量。指针变量用星号,*”表示,定义指针变量是通过定义该指针所指向的变量类型进行的。
如,int *ptr; ptr就是一个整形的指针变量。
指针运算符,*”具有取地址内容的作用,如,
int *ptr; x=3; ptr=&x;
*ptr即取 x地址中的值 3。
2,指针的声明声明指针的一般格式如下:
数据类型*指针变量名;
如,int * ptr;float *array;char *s1,*s2;等。
由于内存地址值是固定不变的,所以不同类型的指针本身所占据的存储区域都一样大。指针在定义后必须初始化才能使用,指针的初始化可表示如下:
int *ptr,i=10; ptr=&i; //指向单个变量
char *sp="string"; //指向字符串
int a[5],*ap; ap=a; //指向数组
int max(),(*fp)(); fp=max; //指向函数
3,指针的运算
( 1),&”和,*”运算符
,&”称为取地址运算符,用以返回变量的指针,即变量的地址。而,*”称为指针运算符,用以
59
励志照亮人生 编程改变命运第4 章面向对象程序设计与C++语言返回指针所指向的基类型变量的值。关于,&”和,*”运算符的使用如下面的数据交换函数的代码:
void swap(int *xp,int *yp)
{ int tmp;
tmp = *xp; *xp = *yp; *yp = tmp;
}
void main()
{ int x = 10,y = 5;
cout<<"x="<<x<<",y="<<y<<endl;
swap(&x,&y);
cout<<"交换后的值"<<endl;
cout<<"x="<<x<<",y="<<y<<endl;
}
与3.2.4节介绍的函数的传值调用不同,这里采用的是通过指针进行地址调用,运行后发现 x和 y的值发生了交换,即 x = 5,y =10。函数 swap运算时的内存分配如图3.11所示。
( 2)指针的赋值可以将一个指针赋值给另一个指针,结果是两个指针指向一个相同的地址单元。例如,
jp=&a;ip=jp;
其结果就是 ip和 jp都指向 a。
( 3)指针的算术运算指针的算术运算只能进行加减,完成指针移动,
实现对不同数据单元的访问操作。对不同的类型,
移动的单位长度不同。如对整型( 4个字节)的加减运算,指针的移动可表示为如图3.12所示。
4,指向数组的指针由于数组名代表数组的首地址,故可以把数组名赋给一个指针变量,这就是一个指向数组的指针。
可以利用指针访问数组中不同的元素。例:
int X[10];
int *pX;
pX=&X[0]; //或pX=X;
例中,pX为指向数组 X[]的指针,表示第一个数组元素的地址。因此有 X[i]=*(pX+i)。
3.3 C++的面向对象特性作为支持面向对象的方法( OOP)的最主要代表语言,C++语言具有面向对象技术的所有特性。
它以类和对象为基础,支持类的继承、封装和多态特性。本节将简单介绍一下 C++中与面向对象相关的知识。
60
励志照亮人生 编程改变命运零基础学 Visual C++
图3.11 函数 swap运算时的内存分配地址 值函数 swap中的局部变量 temp
函数 swap中的参数变量 xp
函数 swap中的参数变量 yp
主函数中的变量 y
主函数中的变量 x
10
10
5
0x0012FF18
0x0012FF24
0x0012FF28 0x0012FF78
0x0012FF7C
0x0012FF78
0x0012FF7C
图3.12 指针的算术运算存储单元
ps-1
ps
ps+1
*(ps+1)
*(ps-1)
*ps
3.3.1 C++中的类类是具有相同属性和相同的方法的对象的集合,它是一种既包含数据又包含函数的抽象数据类型。
类是将一类对象和其他对象区别开来的一组描述,类是对象集合的抽象,对象是类的一个实例。
1,类的声明在 C++中,声明类的一般形式为:
class 类名{
private,
私有数据和函数
public,
公有数据和函数
protected,
保护数据和函数
};
类声明以关键字 class开始,其后跟类名。
类所声明的内容用花括号括起来,右花括号后的分号作为类声明语句的结束标志。这一对花括号,{}”之间的内容称为类体。
类中定义的数据和函数称为这个类的成员(数据成员和成员函数) 。
类成员均具有一个属性,叫做访问权限,通过它前面的关键字来定义。顾名思义,关键字
private,public和 protected 以后的成员的访问权限分别是私有、公有和保护的,所以把这些成员分别叫做私有成员、公有成员和保护成员。
private部分的数据成员或成员函数,在类之外是不能访问的,只有类中的成员函数才能访问。
public部分的数据成员和成员函数可被程序中的任何函数或语句存取。 protected部分说明的成员或成员函数在类之外是不能存取的,只有类的成员函数及其子类可以存取。
说明如果没有使用关键字,则所有成员默认声明为private权限。实际编程当中,public成员多为成员函数,用来提供与外界的接口,只有通过这个接口才能实现对private成员的存取。
如,一个简单的 Student类的声明如下:
class Student
{private:
char m_strName[20];
int m_nAge;
public:
Student ( const char *name,int age); //构造函数
~ Student () //析构函数
void Register(char *name,int age);
char * GetName();
int GetAge();
void Show();
};
2,成员函数的定义在 C++的类中,成员函数定义的一般形式如下:
61
励志照亮人生 编程改变命运第4 章面向对象程序设计与C++语言
<类型> <类名>,,<函数名> (<参数表>)
{
<函数体>
}
如 Student类中,各成员函数的实现代码可表示如下:
void Student:,Register(char *name,int age)
{ strcpy(m_strName,name);
m_nAge = age;
}
char * Student:,GetName()
{ return m_strName; }
int Student:,GetAge()
{ return m_nAge; }
void Student:,Show()
{ cout << GetName() << '\t' << GetAge()<< endl; }
3,构造函数与析构函数构造函数的功能是在定义类的对象时,被编译系统自动调用来创建对象,并初始化对象。其定义格式如下:
<类名>::<类名>(<参数表>)
{
<函数体>
}
使用构造函数时,应注意以下几点:
构造函数的函数名与类名相同,且不指定返回值类型,它有隐含的返回值,该值由编译系统内部使用。
构造函数可以没有参数,也可以有参数,因此可以重载,即可以定义参数不同的多个构造函数。
每个类都必须有一个构造函数。如果类中没有定义构造函数,则编译系统自动生成一个默认形式的构造函数,作为该类的公有成员。
程序中不能直接调用构造函数,在定义对象时编译系统自动调用构造函数。
析构函数的功能是在对象的生存期即将结束的时刻,由编译系统自动调用来完成一些清理工作。
它的调用完成之后,对象也就消失了,相应的内存空间也被释放。
析构函数也是类的一个公有成员函数,它的名称是由类名前面加,~”构成,也不指定返回值类型。和构造函数不同的是,析构函数不能有参数,因此不能重载。
如 Student类的构造函数的实现可表示如下:
Student:,Student (const char *name,int age)
{strcpy(m_strName,name); m_nAge = age; }
4,const常量在类中,可以使用 const关键字定义一个常量。 const数据成员的声明必须包含初值,而且这个初值必须是个常量表达式,const常量的值在作用域内保持不变,例如:
62
励志照亮人生 编程改变命运零基础学 Visual C++
const int maxSize=128;
const int intArray[ ]={1,2,3,4,5,6};
const成员是在编译期间就对表达式进行初始化。 const成员本质上就是一种只读的静态成员,访问时必须使用类的名称,而不能是类对象的名称。
说明可以将const常量理解为read-only static,即它是一个静态数据成员,是只读的,即用户不能改写,
其值在定义时就指定。
3.3.2 类的对象对象是包含现实世界物体特征的抽象实体,反映了系统为之保存信息和(或)与之交互的能力。
说明对象可简单表示如下:对象=数据+作用于这些数据上的操作=属性(Attribute)+方法(Method)
声明了一个类之后,即定义了一个用户数据类型。为了使用类,还必须说明类的变量,即类的实例( instance)或对象( object) 。声明对象的语法类似于声明变量,如:
Student stu;
也可以声明由多个对象组成的数组:
Student stu_array[10];
还有一种方法是在声明类的同时声明对象,如:
class Student
{
public:
} stu;
这样就说明了 stu是 Student类的一个对象。声明了类的对象之后,就对该类的数据成员和成员函数进行访问。
要访问类的成员或成员函数,需要在类的对象和 public数据成员或成员函数之间加上,.” 。如:
stu.show();
不能直接访问类的私有成员,如 stu.m_nAge是错误的。
还可以通过说明对象指针,并通过指针调用类的成员,如:
Student * pstu=& stu;
stu->show();
这样就通过 pstu指针来调用成员函数 show()。
3.3.3 C++类的继承与派生保持已有类的特性而构造新类的过程称为继承,在已有类的基础上新增自己的特性而产生新类的过程称为派生,被继承的已有类称为基类(或父类),派生出的新类称为派生类。
当从现存类中派生出新类时,可以对派生类增加新的数据成员、新的成员函数、重新定义已有的成员函数和改变现有成员的属性。
63
励志照亮人生 编程改变命运第4 章面向对象程序设计与C++语言
1,派生类的声明派生类的格式定义可简单表示如下:
class 派生类名:访问权限基类名1,访问限定符基类名2,,访问限定符基类名n
{ private:
成员表1; //派生类增加或替代的私有成员
public,
成员表2; //派生类增加或替代的公有成员
protected,
成员表3; //派生类增加或替代的保护成员
};
其中基类 1、基类 2、?,是已声明的类。可以使用的访问限定符包括 public,private和
protected。在派生类定义的类体中给出的成员称为派生类成员,它们是新增加的数据和函数成员。这些新增加的成员是派生类对基类的发展,它们给派生类添加了不同于基类的新的属性和功能。
2,单一继承与多重继承如果一个派生类可以同时有多个基类,则称为多重继承( multiple-inheritance),这时的派生类同时得到了多个已有类的特征。如果一个派生类只有一个直接基类的情况则称为单一继承
( single-inheritance) 。单一继承和多重继承类的层次关系可表示为如图3.13所示。
可见,一个基类可以直接派生出多个派生类。
而派生出来的新类同样可以作为基类再继续派生出更新的类,依此类推形成一个层次结构。
3,派生类的继承方式派生类对基类的访问控制也是三种:公有
( public)方式、保护( protected)方式和私有
( private)方式,也称为公有继承、保护继承和私有继承。在派生类的定义中,基类前所加的访问限定符有两方面含义:
派生类成员(新增成员)函数对基类(继承来的)成员的访问(调用和操作) 。
从派生类对象之外,对派生类对象中的基类成员的访问。
各继承方式的访问权限如表3.4所示。
4,派生类的构造函数与析构函数派生类的构造函数的一般形式为:
派生类名::派生类名(参数总表):基类名1(参数表1),?,基类名n(参数表n),
内嵌对象名1(对象参数表1),?,内嵌对象名m(对象参数表m)
64
励志照亮人生 编程改变命运零基础学 Visual C++
图3.13 单一继承和多重继承基类派生类 1 派生类 1派生类 2
单一继承 多重继承派生类 2
基类 1 基类 2 基类 n
表3.4 各继承方式的访问权限基类成员继承派生类中对基类外部函数访问权方式成员的访问权的访问权
Public public 可以直接访问
public Protected protected 不可直接访问
Private private 不可直接访问
Public protected 不可直接访问
protected Protected protected 不可直接访问
Private private 不可直接访问
private
Public 不可访问 不可直接访问
Protected 不可访问 不可直接访问
Private 不可访问 不可直接访问
{
派生类新增加成员的初始化;
}
派生类构造函数的执行次序为:
首先,调用基类构造函数,调用顺序按照它们被继承时声明的基类名顺序执行。
其次,调用内嵌对象构造函数,调用次序按各个对象在派生类内声明的顺序。
最后,执行派生类构造函数体中的内容。
派生类与基类的析构函数没有什么联系,彼此独立。派生类析构函数执行过程恰好与构造函数执行过程相反,执行顺序如下:
首先执行派生类析构函数。
然后执行内嵌对象的析构函数。
最后执行基类析构函数。
3.3.4 C++类的继承实例为了使读者对继承的概念有直观地认识,这里给出一个多重继承的实例。已知时间类 TimeType和日期类 DateType通过多重继承定义日期时间类 DateTimeType。其中时间类 TimeType的定义如下:
#include<iostream>
using namespace std;
class TimeType
{
int hour,minute,second; //时、分、秒
public:
TimeType(int h=0,int m=0,int s=0) //构造函数
{
hour=h;
minute=m;
second=s;
}
void display() //输出时间
{
cout<<hour<<":"<<minute<<":"<<second<<endl;
}
void SetTime(int h,int m,int s) //成员函数,设置时间
{
hour=h;
minute=m;
second=s;
}
};
日期类 DateType的定义如下:
class DateType
{
int month,day,year; //月、日、年
65
励志照亮人生 编程改变命运第4 章面向对象程序设计与C++语言
public:
DateType(int mo=1,int d=1,int y=2000) //构造函数
{
month=mo;
day=d;
year=y;
}
void display() //输出日期
{
cout<<month<<"/"<<day<<"/"<<year<<endl;
}
void SetDate(int mo,int d,int y) //成员函数,设置日期
{
month=mo;
day=d;
year=y;
}
};
日期时间类 DateTimeType由 TimeType类和 DateType类两个基类派生,其定义如下:
class DateTimeType:public DateType,public TimeType
{
public:
DateTimeType(int mo=1,int d=1,int y=2000,int h=0,int m=0,int
s=0):DateType(mo,d,y),TimeType(h,m,s){} //构造函数
void display()//显示时间、日期
{
DateType::display(); //调用DateType类的display函数
TimeType::display(); //调用TimeType类的display函数
}
};
主函数中,实现对日期时间类 DateTimeType的调用测试,代码如下:
int main()
{
DateTimeType dt(8,4,2007,10,8,8); //直接使用DateTimeType构造函数设置日期时间
cout<<"DateTimeType类设定的日期、时间为:"<<endl;
dt.display();
dt.SetDate(9,12,2007); //调用基类的成员函数修改日期
dt.SetTime(10,8,14); //调用基类的成员函数修改时间
cout<<"调用基类成员函数修改后的日期、时间为:"<<endl;
dt.display();
return 0;
}
运行结果如图3.14所示。
在本实例中,读者需要注意多重继承构造函数的声明,以及对基类成员函数的访问。
66
励志照亮人生 编程改变命运零基础学 Visual C++
图3.14 程序运行结果
3.3.5 C++类的多态性简单来讲,C++类的多态性是指类中同一函数名对应多个具有相似功能的不同函数,可以使用相同的调用方式来调用。或者说是类的对象在接受同样的消息时,能够做出不同的响应,从而实现“一种接口,多种方法”的技术。
1,多态的实现在 C++中有两种多态性:编译时的多态性和运行时的多态性。
编译时的多态性是指编译器对源程序进行编译时就可以确定所调用的是哪一个函数,它是通过重载来实现的,包括函数重载和运算符重载。关于函数的重载参见3.2.4节的介绍。
运行时的多态性是指在程序执行前,无法根据函数名和参数来确定该调用哪一个函数,必须在程序执行过程中,根据执行的具体情况来动态地确定。它是通过类继承关系和虚函数来实现的。
2,运算符的重载重载运算符主要用于对类的对象的操作,其形式如下:
<类型> <类名>::operator <操作符>(<参数表>)
{
...
}
下面通过重载,+”运算符分别实现复数与复数的加法运算和复数与实数的加法运算,实现代码如下:
#include <iostream.h>
class Complex //定义复数类
{
double m_fReal,m_fImag;
public:
Complex(double r = 0,double i = 0),m_fReal(r),m_fImag(i){} //构造函数
double Real(){return m_fReal;}
double Imag(){return m_fImag;}
Complex operator +(Complex&); //重载运算符+
Complex operator +(double); //重载运算符+
};
Complex Complex::operator + (Complex &c) //重载运算符+,实现两个复数的加法
{
Complex temp;
67
励志照亮人生 编程改变命运第4 章面向对象程序设计与C++语言
temp.m_fReal = m_fReal+c.m_fReal;
temp.m_fImag = m_fImag+c.m_fImag;
return temp;
}
Complex Complex::operator + (double d) //重载运算符+,实现复数与实数的加法
{
Complex temp;
temp.m_fReal = m_fReal+d;
temp.m_fImag = m_fImag;
return temp;
}
//测试主函数
void main()
{ Complex c1(3,4),c2(5,6),c3;
cout << "C1 = " << c1.Real() << "+j" << c1.Imag() << endl;
cout << "C2 = " << c2.Real() << "+j" << c2.Imag() << endl;
c3 = c1+c2;
cout << "C3 = C1 + C2 =" << c3.Real() << "+j" << c3.Imag() << endl;
c3 = c3+6.5;
cout << "C3 + 6.5 = " << c3.Real() << "+j" << c3.Imag() << endl;
}
运行结果如图3.15所示。
3,虚函数虚函数是指在某基类中声明为 virtual,并在一个或多个派生类中被重新定义的成员函数。其声明格式如下:
virtual 函数返回类型函数名(参数表)
{函数体}
关键字 virtual指明该成员函数为虚函数。 virtual仅用于类定义中,如虚函数在类外定义,不可加
virtual。基类的成员函数一旦被声明为虚函数,每一层派生类中该函数都保持虚函数特性,在其派生类中其关键字 virtual可省略。
当在派生类中重新定义虚函数(也称覆盖)时,不必加关键字 virtual。但重新定义时不仅要同名,
而且它的参数表和返回类型全部与基类中的虚函数一样。
说明虚函数的主要用途是实现多态性,即通过指向派生类的基类指针,访问派生类中同名覆盖成员函数。
下面给出一个简单的虚函数的使用实例,代码如下:
#include <iostream.h>
class Person //基类
{
public:
virtual void Show()
{
cout<<"A Person"<<endl;
68
励志照亮人生 编程改变命运零基础学 Visual C++
图3.15 程序运行结果
}
};
class Student:public Person //派生类
{
public:
virtual void Show()
{
cout<<"A Student"<<endl;
}
};
void main() //主函数
{
Person per,*ps;
Student stu;
ps=&per; //指向基类的对象
ps->Show();
ps=&stu; //指向派生类的对象
ps->Show();
}
运行结果如图3.16所示。
在本例中,同样是通过基类 Person指针,调用 Show函数,但由于指针对象的不同,实现的虚函数 Show的形式也就不同。程序对虚函数的调用可表示为如图3.17所示。
通过定义虚函数,实现了类的对象在接受同样的消息时,能够做出不同的响应。对于本例而言,这种关系可表示为如图3.18所示。
一般而言,可将类中具有共性的成员函数声明为虚函数,派生类定义虚函数的时候,必须保证函数的返回值类型和参数与基类中的声明完全一致。
69
励志照亮人生 编程改变命运第4 章面向对象程序设计与C++语言图3.16 程序运行结果
ps->Show()
Person per
Show()
Student stu
Show()
图3.17 程序对虚函数的调用 图 3.18 虚函数实现同样消息的不同响应消息对象行为
A Person
A Student