资源描述
Object- Oriented Programming
C++
主讲 成长生
东华大学计算机科学与技术学院
第一章 概述
§1.1 面向对象程序设计的基本思想
C++是基于C语言发展的, 又冲破C语言局限的面向对象的程序设计语言。它与Java语言都作为当前计算机科学的主流语言, 越来越受到用户的欢迎。
要弄清楚什么是面向对象的程序设计, 首先了解和回顾传统的( Pascal( 或C) ) 结构化程序设计方法及其设计思想、 程序结构及特点。SP(Structure Programming)是60年代诞生的针对当时爆发的所谓”软件危机”, 为此发展形成了现代软件工程学的基础。
SP的总的设计思想是:
.自顶向下、 层次化
.逐步求精、 精细化
程序结构是按功能划分基本模块的树型结构, 使模块间的关系尽可能简单独立。因此SP的程序的基本特点是:
.按层次组织模块( 战略上划分战役)
.每一模块只有一个入口, 一个出口
.代码和数据分离( 程序=数据结构+算法)
归纳得到: SP把数据和过程( 代码、 函数) 分离为相互独立的实体, 用数据代表问题空间中的客体借以表示实际问题中的信息; 程序代码则用来处理加工这些数据。程序员在编程时, 必须时刻考虑所要处理的数据结构和类型。对不同的数据格式即使要作同样的处理计算, 或者要对相同的数据格式作不同的处理都必须编写不同的程序( 如两个整型数和两个浮点数相加) 。这样的编程方法, 即传统的SP方法设计出来的程序或系统其可重用的成分很少。其次把数据和代码作为不同的分离实体时, 总存在着用错误的数据调用正确的程序模块, 或用正确的数据调用错误的程序模块的危险, 从而使数据与程序始终保持兼容, 已成为程序员的一个沉重的负担。在开发一个大型软件课题中, 当工程进入到后期若用户改变了方案要求, 很容易使技术人员的前期工作受到摧毁性的打击, 使其前功尽弃。为克服以上的弊端或者该SP方法难以控制处理的矛盾而产生了面向对象程序设计方法, 即Object-Oriented Programming――OOP。从二十世纪六十年代提出对象的雏形, 到七十年代美国国防部的专用语言Ada语言, 直到当前国际上流行的高品味的Java和C++(Tc++,Balandc++及Vc++), , 应该讲OOP方法与技术吸取了SP的一切优点, 同时又正视和顺应现实世界由物质和意识二部分组成。映射到面向对象的解空间就是: 具体事物-对象; 抽象概念-类。
OOP的基本原理是用问题领域的模型来模拟大千世界, 从而设计出尽可能直接、 自然地表示问题求解方法的软件, 这样的软件由对象组成, 而对象则是完整反映客观世界事物具有不可分割的静态属性( ”数据结构”) 与动态行为( ”方法”) 的。而且它们是既有联系又有变化发展的实体( 实例) 。如人是一种抽象( 类) , 而张三则是一个具体的一个人, 是类的实例, 故为对象, 她有自身的一系列属性( 身高、 体重、 学历等) 。
面向对象抽象的原理有4个。分别是数据抽象, 行为( 操作) 共享, 进化和确定性。它们的作用简单地讲把数据封装起来达到数据隐藏, 达到数据的高度安全; 不同层次的类操作( 行为) 尽可能共享; 进化则是知识积累、 知识更新的长期过程; 确定性则是一个相对概念, 对一个问题的认识和解释, 随着时间的推移, 其解释也是在发生变化的。( 更具体的见P3-但有困难, 这些概念在以后的章节中才能详细讨论) 。
下面再解释三个专业术语。
1. 对象
什么是对象? 对象是私有数据及能够对这些数据施加操作结合在一起所构成的独立实体。这里的操作就是一些操作代码。对象的动作取决于发送给该对象的消息表示式, 消息告诉对象要求完成的功能( What to do) , 并激活该功能, 这意味着对象具有自动”知道”如何完成相应操作代码( How to do) 的”智能”选择机制。与SP相比, OOP的威力在此初步显露出来。从逻辑上来分析, 一个对象是独立存在的模块, 从外部看这模块只需了解它具有哪些功能, 至于它如何实现这些功能和使用哪些局部数据来完成它们的细节则”隐蔽”在模块内部, 这就意味着模块内部状态不受外界干扰改变, 也不会殃及到其它模块, 进而使模块间依赖性小到几乎没有。
2. 类
什么是类?类是对一组具有相同数据成员和相同操作成员的对象的说明。即类是抽象数据类型的实现, 一个类的所有对象都有相同的数据结构和操作代码。每个对象都是某个类的一个具体实例。( 有的文献中用实例来替代对象)
3. 继承
继承是OOP的一个重要机制。继承提供了创立新类的一种方法。一个新类能够经过已有类进行修改或扩充来满足新类的要求。新类共享已有类的行为, 而自己还具有修改或添加的行为。我们把已有类称之为父类, 新类称为子类, 则子类继承父类, 而父类派生子类。继承机制的主要优点是能减小代码和数据的重复冗余度, 大大增强程序的重用性, 能清晰体现相似类之间的层次关系。
C++是一种面向对象的程序设计语言。这种语言有七个主要的特点。
1) 数据封装
例 先说明一个类
class 类名
{
<成员函数>
body
<数据成员>
};
再定义一个属于该类的对象。
在C++中, 类是支持数据封装的工具, 而对象则是数据封装的实现。类能够看作为一种数据类型, 则对象视为该类型的变量, 其定义一个对象的格式为:
类名 对象名
所谓数据封装就是把若干个成员函数( 前面讲是操作、 行为-Pascal中的过程) 和一系列数据成员( 结构成员、 记录中的成员) 全部封装在一个类体中, 成员函数的作用对数据成员进行操作。类外的简单变量无法进入, 以达到数据安全、 数据隐蔽的目的。对象对数据成员进行操作访问也是经过对应的成员函数来实施的, 因此成员函数的作用相当于一个软件的接口, 经过该接口, 内外界进行交换信息。
2) 作用域控制或访问控制权限
访问控制权限有三种, 分别为公有的( public) 、 私有的( private) 及受保护的( protected) 。成员函数一般都采用public。此时, 类体内外都可访问。而private只能由类内函数访问。Protected类内可访问, 类外只能由该类的子类或友元来访问。
3) 消息发送
什么叫消息? 消息是要求某个对象执行其中某个功能操作的规格说明。通俗地讲OOP中的消息相当于现实世界中的”请求”或”命令”。一般OOP中的一条消息, 由消息选择器及若干个变元( 0或多个实参) 和接受消息的对象组成。一般我们把发送消息的对象称为发送者( 函数调用者) , 接收消息的对象称之为接收者( 被调用者) 。在SP中函数调用后必有响应且返回, 而OOP中有发送, 有或无接收。
4) 友元
友元是类外函数。它能够不受限制地访问类中的数据成员, 不论它们是保护还是私有, 可见这种友元打破了数据的封装。在实践中建议慎用或少用( 但有特殊用途-以后再叙) 。
5) 重载技术
重载又分为函数名重载和运算符重载。这在C和Pascal中是没有的。所谓函数名重载即在一个程序中, 允许同名函数存在。具体实现时, 系统根据函数调用时实参的个数或实参类型”智能”地选择一个匹配的函数。同理操作符重载可达到二个简单数相加或二个对象相加。
6) 继承性
继承性又分单一继承和多重继承。目的是减少冗余, 提高程序的可重用性。子类可用自己的成员, 也可用父类, 甚至父父类的成员。这是C++中的重点。
7) 动态联编
动态联编来源于静态联编, 它是多态性技术的一部分, 它又依赖于虚函数, 是继承机制的纵深发展。
以上只能简单地抽象地讨论一下以上特征。整个课程围绕以上七个特征进行讨论。
C语言是C++的子集。 P6。
§1.2 C++的词法及词法规则
C++字符集包括英文大小写字母a-z, A-Z( 注意C++中对字母大小写是敏感的, 即a≠A) ,数字符号0-9, 以及一些允许的特殊字符。
变量名、 数组名、 对象名、 结构名、 函数名等都属于标识符。C++规定, 标识符必须以字母或下划线打头, 后面能够跟字母或数字或下划线, 其长度不能超过32位。经验告诉我们, 为便于记忆, 标识符可用有一定的物理意义或数学意义的字符串所组成。但用户自行定义的标识符不能使用系统的关键字或保留字。
运算符见P.46, 以后逐步进行讨论。包括最简单的+-*/, 但要注意≠不能写<>, 应该写成! =, 全等用二个==,赋值号用=而不是: =( 对学过Pascal的人特别注意) 。
为提高程序的可读性, 在程序中可适当写一些注释, C++的注释有两种: //…和/*…*/。/*…*/为多行注释符, 以/*打头, */结尾。
如 …………
/* ………………
………………*/
则第2、 3行语句被注释, 系统不予运行。而//称之为行注释。
如 ………………
………………//……………
这里第二行后面为注释内容, 但第二行仍运行。
如 ………………
//………………
则第二行屏蔽。/*…………*/也可放在一行用, 起行注释作用。
C++的分隔符号有空格符、 逗号、 分号、 冒号、 { }等各有不同作用, 在具体的程序中再作讨论。分号主要用作语句结束符。
§1.3 C++程序的结构
学习计算机语言的入门, 总是先阅读几个程序, 尔后在此基础再动手编写。
例
#include<iostream.h> //this is a c++ program
void main( )
{
double x,y;
cout<<”Enter two float numbers:\n”;
cin>>x>>y;
double z=x+y;
cout<<”x+y=”<<z<<endl;
}
分别叙述预处理语句、 注释、 void、 主函数、 变量定义、 逗号、 输入输出、 endl=’\n’、 { }。
第二章 数据运算符和表示式
§2.1 基本数据类型
C++所提供的数据类型, 在所有的语言中堪称是最丰富的一种, 这些数据类型基本上又分成二类: 基本类型和构造类型。
四种基本类型是: char、 int、 float、 void, 并引申出unsigned char、 signed char、 unsigned int、 signed int、 long int、 short int、 double、 long double常见的及取值范围( 值域) 见P.23 。
整数类型的标识符是小写的int, 写成INT是错误的, 器取值范围为-32768≤x≤32767。如果所表示的整数值超出了此范围, 则出错。如 0, 246, 312, -30000都属正确, 而0.5, 123456,-50000则溢出。整数类型的变量一般用作计数器、 数组的下标。对于整数类型的数据, 能够有+, -, *,/,%运算符。/为整除符, 二数相除取其整数, 截去小数; %为模运算, 二数相除, 取余。如
11/2=5 11%2=1
从算法来分析
a%b=a-(a/b)*b
对于整数数据, 除了上述算术运算符外, 还有下列的关系运算符: >, <,>=,<=,==,!=
浮点类型类似于Pascal中的实数类型, 其标识符为float, 其取值范围为-10+38与10+38之间。若不能满足你的需要, 则double的取值范围为-10+308与10+308, 如14.7, 1.0, -6.7等均为浮点数。
电子的静止质量为9.10956E-28或9.10956e-28, 这里的E或e是指数的标志, 相当于9.10956×10-38。
一个浮点数如果有小数点, 则小数点的二边至少一边要有数字; 如果它包含E或e, 则在E或e的两边都要有数字, 而且e的后面必为整数。如 0.12e5是正确, 而.E+12,e+4都是错误的。用于浮点数的算术运算符有+、 -、 *、 /, 无取余%,这里的/不再是整除, 如11.0/2.0=5.500000( 认为6位小数) 。一个字符类型变量的值是一个字符。C++所用的字符集为ASCⅡ码。‘A’为65, ‘B’为66, ‘a’为97, ‘b’为98, 则‘A’+1为‘B’( 见P.25表2.2, 单个字符常量用‘ ’, 字符串用” ”) 。程序语言中的数据有常量和变量之分。常量即在程序运行中不可被改变的量, 而变量则是允许被改变量的。C++中规定任何变量必须先定义后使用, 定义的格式为:
变量类型名 变量名
int x; 或int x, y; 表示x是属于int的变量名, 其值域被限制在-32768到32767之间。X=6; 亦可在定义的同时进行初始化, int z=7; const int m=3; 在变量类型名前加了一个const冠词, 则m的值不许被改变, 若试图改变, 哪怕m=m, 编译也会出错; int x=140000; 也出错, 上溢。很容易想到把x定义成浮点型float或double, 但这不是上策, 因为浮点型操作使机器的运行速度要降低2-3个数量级, 怎么办? 采用基本类型的引申: long int, 简写为long, 其值域为-21亿到21亿之间。相对于int用两个字节, long则为4个字节, 短整型short则为一个字节( 但实际上pc机上, short仍为二个字节-具体机器不同) , 另有unsigned , 值域为0到6.4万。在一个表示式中, 允许出现不同类型的变量, 如
int x; float y; long z; double a;
x+y-z+a最终又将出现什么类型的结果? 换言之, 不同的数据类型如何转换? C++中数据类型的转换有自动转换和强置转换二种。
自动转换的思想是二个不同类型的量进行运算, 低级类型自动地向高级类型靠拢, 即
int+double->double ( 4+2.5->6.5)
在C++中又是如何确定类型的高低呢? 系统确定的高低优先级顺序为:
double->float->long->unsigned->int->char
因此 long*int->long long-float->float
另一种为强置转换, 强置转换的格式有二种:
int(4.2)+5->9
(int)4.2+5->9
(float)2+3->5.000000
§2.2 构造类型
C++中的数据类型除了基本类型之外, 还有构造类型。构造类型又包括数组、 枚举、 结构、 联合及指针。
数组的定义方式 int A[5];
A是数组名, 它包括有5个元素, 每一个元素均为int, 下标属于0~4; 而二维数组:
char B[2][3];
二行三列的字符型数组, 下标为00、 01、 02、 10、 11、 12, 该数组在内存中以行次序存放。注意行列分别用二对方括号, 数组也可在定义时进行初始化。
char N[5]=”abcd”; 等同于
N[0]=’a’,N[1]=’b’,N[2]=’c’,N[3]=’d’,N[4]=’\0’;
int A[5]={11,22,33,44,55};
‘\0’称之为串尾结束符。”a”则在内存中占二个字节, ‘a’只占一个字节。
构造的另一种类型为枚举。它是若干个有名字的整型常量的集合。如果一个变量只有几种可能的值, 能够定义为枚举类型。把这几种可能出现的值一一列举出来, 该枚举变量的值就只能被限制在这个列举的范围内。
假定有一个颜色的集合为红、 黄、 蓝、 白、 黑, 则定义一个枚举
enum color{red,yellow,blue,white,black}
关键字 类型名 颜色子集
这里的enum 是enumaret的简写
enum color x; //x为color枚举变量名
此时x的值只能是red~black , 系统内用整型数表示默认为0、 1、 2、 3、 4。
对枚举变量的赋值只能用枚举元素的标识符, 而不能写数字值。
x=black; cout<<x; 则输出4
若在枚举定义时, 也可由程序员定义改变枚举元素的值
enum color={red=4,yellow,blue,white,black};
则元素取值为4, 5, 6, 7, 8 亦可
{red=4,yellow=10,blue,white,black}; 则结果为4, 10, 11, 12, 13
但不允许在某一元素定义一个值, 它的左边为默认值, 如
{red,yellow=5,………}; //出错
枚举的使用频率较低, 只进行了解即可。
指针是C语言的精华, 也是C++中的一个重要方面。指针是我们碰到的第一个难点。正确使用指针能够使程序更为灵活, 速度加快, 用指针能够更加有效地表示复杂的数据结构, 使得被调用函数能够改变调用函数的变量, 以及从被调用函数返回多个值。
什么叫指针? 为了掌握指针的基本概念, 必须了解指针、 地址和间接访问之间的关系。不论是程序代码还是被操作的数据, 它们都必须被存贮在内存中, 它们占用的存贮区按存贮单位( 字节) 从0开始顺序编排地址, 所有这些地址构成了程序活动的空间。
例如 在某个程序中, 定义了二个整型变量: x, y int x, y;
它们在该程序地址空间中的位置如下图:
程序地址空间
┊
5
6
100
┊
x 100
y 102
px 5000
变量x占用地址单元100, y则占用102, 执行赋值语句:
x=5; y=x+1;
就是把整数5送入起始地址为100的二个连续字节单元中去, 语句y=x+1则先从100单元中取出x的值5, 然后加1, 最后送入地址为102的y变量中, 则y变量的内容为6。
一个变量对应于一个地址, 按变量的地址访问变量值的方式称之为直接访问。
在C++中还能够定义另外一种类型的变量, 它们专门用来存放另外一些变量占用的单元地址, 如上的px就是这样的一种变量, 它能够存放任何一个类型变量占用单元的地址, 当然像其它变量一样, 它本身也需要占用存储单元。例如: px的地址为5000, 如若已经使用某种方式将px的值设置为变量x占用的单元地址( 100) , 那么为了取得x的值, 首先根据px占用单元的地址5000, 从中取得其值100, 然后再从地址为100的单元中取得x的值5。对x的这种访问称为间接访问。X是变量, px也是变量, 这是一种特殊的变量, 称之为指针变量, 简称指针。
指针变量也是一种变量, 只不过在这种变量里存放的是另一个变量的地址。定义格式为: type *name (亦可type*name , type* name , type * name)
name为指针变量名, *为指针的标识, 不属于名称的一部分, type为指针变量的最终一级内容。对上例
int *px; //px为变量名, px的一级内容为地址, 二级内容为整型数
例 int x=5 ; //定义x为整型变量, 初始化为5
int *px ; //定义指针变量px
px=&x ; //把x的地址赋给px, &取地址操作符
cout<<x<<px<<*px<<endl ;
输出x的值为5, 输出px的一级内容即x的地址100( 具体上说是8位16进制的地址) , 再输出px的内容的内容即5 。此处*为间接访问符号。
int **pp ,则是二级指针, pp的第三级内容才为int的操作。在C++中凡是能用数组解决的也能用指针来解决。反之用指针技术解决的问题, 不一定能用数组来解决。
系统规定, 数组名表示该数组的首地址, 如
char A[5],*PA ;
PA=&A[0]; //等价于PA=A
讲义p34 int( *pf) ( )函数指针,以及int *pf( )指针函数, 以后再叙。
指针和数组概念要搞清楚, 不同的表现形式, 达到相同的效果。如
char A[5]=”WHAT”;
PA=A ;
则A[0]为‘W’, A[1]=’H’,*PA为‘W’, PA+1为‘H’的地址, *(PA+1)为‘H’, A+1也为‘H’的地址。
上面讲到int **pp为二级指针, 二级指针的又一个实用技术在于指向二维数组, 称之为指向多维数组的指针。如
char D[3][4],(*PD)[4];
D是一个二维数组, 而PD是一个指针, 该指针指行地址, 每一行有4个元素。因此, PD+1后不是指向当前行的下一列, 而是下一行, 这里圆括号是必要的; 否则[ ]的优先级优于*,变成指针数组。对于D数组也可用另一种二级指针
*(*(D+i)+j)
三维忽略
行号 列号
例 p39 例2.3
引用是C++所特有的, 它是某一个变量的别名, 对该别名的访问等价于对该变量的访问。换言之对引用的访问就是对被引用的变量的访问。反推, 对引用的访问之前, 必须先定义引用, 而在定义引用之前又必须先定义被引用的变量。它们之间的顺序不可颠倒。
int x; x=5; int x,*y ;
int &y=x ; x=5 ; y=&x ;
cout<<x<<y ; cout<<x<<*y ;
左右效果相同, 但理解不同。右边, y的一级内容为x的地址, 二级内容为x值即5; 而左边, y等于x, x的内容即y的内容故为5。二者指向同一个空间。
引用的定义格式为:
类型 &引用名=变量名;
在一般情况下引用名的类型即为变量名的类型, 如果二者类型不一致, 如
int x ;
float &y=x;
系统也能接受, 此时系统在块中会生成一个临时变量, 进行强置转换, 即
float &y=(int)x ;
float
不过此操作用户看不到, 由系统自动完成 例p41 例2.5
#include <iostream.h>
void main( )
{
int val=5 ; //定义变量val,且初始化为5
int &refv=val ; //定义引用名为refv,refv引用了val变量
refv=refv+5 ; //refv等于10
cout<<val<<endl ; //输出10
int *p=&refv , val1=refv ; //定义了一个指向整型数的指针p
//此句可分二行, int *p ; p=&refv 把 refv的地址即val的地址送入指针p
// 又定义了一个整型变量val1,把10赋给它
cout<<*p<<”\t”<<val1<<endl ;
}
结果输出用制表符相隔了二个10
§2.3 操作运算符
C++的运算符见p46, 它们的优先级从上到下, 从高到低。坦然地讲, 运算符的优先级还是比较难以一下子掌握的。一来运算符较多, 二者它们的优先级似乎有些出乎我们的一般的意识, 但这并非是说数学中的先乘除后加减, 现在变成先加减后乘除, 结论是否定的。
算术运算符较容易解决。C++为了提高运行速度, 对于x=x+1, 有一个快捷的运算符, 称之为自增( 增1) 运算符, 书写为++; 自减运算符为--。自增自减单独构成一条语句时, x++; 和++x; 是等效的。而在一条组合语句中前置和后置情况就不一样了。P46中的++优先级是讲的前置优先级, 而后置的优先级比逗号还低。 设 int y=5,x;
x=y++; x=++y;
结果x=5 y=6 结果x=y=6
又设 x=5; y=6;
f=x+++y; //默认x++
cout<<x<<y<<f;
输出 6 6 11
而 f=x+(++y);
cout<<x<<y<<f;
输出5 7 12
自增、 自减只能跟随变量, 且为整型变量。
C++中没有逻辑变量, 故无T和F, 关系和逻辑运算的值用1和0来表示
if (x>y)
cout<<1 ;
else
cout<<0 ;
注意! =和==的书写
逻辑运算符与用&&( 不是and) , ||( 不是OR) , ! 为取反( 非) , 现在能够理解! =是不等于的原因了。
逻辑运算时 user->sys ≠0表示真, =0表示假 而
sys->user 1表示真, 0表示假
4567*1234.567&&56.789/1.23456 为何值? 答案是1。
如果逻辑运算以操作数为单位, 那么位运算符则是以二进制的位bit来操作的
5&2=>0 0101
& 0010
0000
除了&之外还有|、 ~、 ∧、 <<、 >>,视有无学过二进制, 未学则不讨论, 否则再深入下去。
诸如x=x+y,x既作为自变量又作为因变量可写成另一种x+=y,此时的赋值语句包括了+, 故称之为复合赋值语句。C++有十个复合赋值语句, 它们分别为+=、 -=、 *=、 /=、 %=、 &=、 |=、 ∧=、 《=、 》=它们的优先级属于赋值语句
x=6,y=7;
x+=y ; 则y不变 x为13
而 x+=y-2 ; x=11
注意 x*=y-2 先减后乘 x为30
表示式是操作数( 常数、 变量) 与操作符的组合要考虑操作符的优先级和操作数的数据类型转换。
int a=6 ;
float b=7.2,c ;
c=a++*(int)b ; //c为42.000000 尔后a再加1成为7
p51 例2.9
C++具有预测的功能。
int a=2,b=3,c=4,s;
s=++a||++b||++c;
cout<<”a=”<<a<<” b=”<<b<<” c=”<<c<<” s=”<<s<<endl;
运行结果 a=3 b=3 c=4 s=1
第三章 语句和控制流
语句是构成程序的最小单位。C++规定分号是语句的终止符。一个程序能够由若干个文件组成, 一个文件又能够由若干个函数组成, 一个函数又是由若干条语句组成。作为最简单的情况一个程序就是一个文件。在一个文件只有一个函数, C++规定主函数必须要有, 也只能有一个。
§3.1 预处理语句
区别于其它语句的一个明显之处, 就是C++具有预处理能力。预处理程序由预处理语句所组成。为了区分预处理语句与一般的语句, 系统规定所有的预处理语句必须冠以#打头, 而且一定要书写在函数的外面。
预处理语句一共有三类: 文件包含、 条件编译、 宏定义( 宏替换)
1. 文件包括
所谓文件包含就是在用户程序A中要用到系统定义的库函数或早先编制的用户程序B, 从程序设计自动化的角度出发, 用户不必自己编制该函数, 直接调用, 如f=xn, 在C++中能够写成f=power(x,n),该幂函数名为power, 功能为求xn。虽然我们用户能够自己用叠代或递归法编, 但既然系统已经为用户考虑编制了该函数, 我们则能够直接调用。如何把该函数与用户程序连起来, 这就是文件包含。
#include <math.h> 后面无分号
头文件名
在math.h中集中存放了所有的数学函数, h=head, 故又称头文件。要输入输出( I/0) ,则必须#include<iostream.h>。
2. 宏定义
宏定义又称宏替换, 它只是进行简单的机械的替换。C++中的宏替换又分为无参数替换和带参数替换:
1) 无参数宏替换的格式
#define 宏名 字符串
例 #define PI 3.14
┊
PI
┊
PI
┊
PI
这样PI( 无法写出π) 就等价于3.14。编译时系统先自动把程序中所有的PI被3.14替换。而符号常量定义:
const float PI=3.14 ; 则是在编译后每碰到PI就替换
PI
┊
PI
┊
PI
表示PI的内容为3.14
例p66 例3.4
若在3.14159265后加一个 ; 则编译出错, 为什么? 展开就一目了然了。
符号常量定义又不同于p55的类型定义, 后者仅为变量类型起个别名, 如typedef int INT ; 这样在程序中int x ; INT y ; 都是正确的。换言之, typedef并非定义一种新的数据类型。
在实践中使用更多的是带参数的宏定义, 其格式为:
#define 宏名( 形参) 宏体
p69 例3.5
在这里只是简单的机械替换
又例
#define square(a) a*a
void main()
{
float x=3.0,y;
y=27.0/square(x);
cout<<”y=”<<y;
}
输出27。0
理由是展开后形成27.0/3.0*3.0, 要使y成为3, 则宏体中加括号
#define square(a) (a*a)
而 y=square(x+1) 又等于多少呢?
机器机械地展开成y=(3+1*3+1)为7, 要想使结果为4的平方, 则定义宏体( ( a) *(a))
宏技术有诸多优点, 在此我们只关心讨论一类, 即参数的数据类型不受限制, 实参不论是什么类型, 形参a都接收。
§3.2 条件语句
C++的控制流有分支和循环二大类: 其中分支又有if、 if_else、 switch, 循环又有while、 do while 及for语句。
语句是函数点元素。C++规定分号是语句的终止符, 因此x=5; ; 就是两条语句。第一条是赋值语句, 再一次重申赋值号不是冒等号, 第二条语句只有一个分号, 称之为空语句, 空语句什么也不做, 但占据了空间。
if语句的格式为:
if( exp)
statement
注意exp必须有括号括起来
if( x<0)
x=-x; //求绝对值
if( x)
x++;
此时的判断条件默认缺省值为x! =0, 等价于if( x! =0) 。
而if( ! x) =>if(!x!=0)=>if(!x==1)=>if(x==0) 这种书写格式在国外很流行。
归纳 if(exp)
s1; //条件满足执行s1
s2; //s2为公操作, 不论条件满足与否都执行
条件满足执行s1, 条件不满足执行s2, 公操作为s3, 则格式为:
if(exp)
s1;
else
s2;
s3;
if和else后面没有; , 因为它们是语句的开始而非结束。注意if条件满足后面没有then
if(x>y)
x++; //T
else
y++; //F
z=x+y; //公共
如果条件满足或不满足时有多条语句, 如各加一条cout<<x或cout<<y语句, 这是要加上花括号, 这时由多条语句构成的子集称之为分程序或复合语句
if( exp)
{ ss1;} //加一个s表示复数, 多条语句
else
{ss2;}
ss3;
对于 if(x>y)
z=x;
else
z=y;
之类的二种选一的结构, C++提供了一个唯一的三目运算符? : 。单目运算符指只有一个操作数: -、 ! 、 ~等。+、 -、 *、 /为双目运算符, 此操作符有二个操作数。三目运算符的优先级为13, 它的实例:
z=(x>y)? x : y ;
例 int x=6,y=5,z ;
z=(x>y)?x--:++y ;
cout<<x<<y<<z ;
结果是什么?
为什么y=5 , 前置++y为什么不起作用? 从p46的优先级表来看++优于? : 。这里回归到if else而知当条件满足时, else部分是不执行的, 下面再举一个奇怪的例子:
x=5 ;
if(4<x<3)
x++;
else
x--;
展开阅读全文