第 2章 基本 C++语言
2.1 C++程序结构
2.2 数据类型和基本输入输出
2.3 运算符和表达式
2.4 基本语句作业 1
2.5 函数和预处理
2.6 构造类型
2.7 指针和引用作业 2
2.1C++程序结构
2.1.1几个 C++程序
[例 Ex_Simple2] 在屏幕上输出一个由星号形成的三角形 。
// 输出星号的三角形阵列
#include <iostream.h>
void DoDraw(int num); // 声明一个全局函数
void main()
{ int num=5; // 定义并初始化变量
DoDraw(num); // 函数的调用
}
void DoDraw(int num) // 函数的定义
{
for (int i=0; i<num; i++) // 循环语句
{ for (int j=0; j<=i; j++)
cout<<'*';
cout<<'\n';
}
}
2.1C++程序结构
[例 Ex_Simple3] 用类的概念重写例 Ex_Draw。
// 输出星号的三角形阵列
#include <iostream.h>
class CDrawArray // 定义一个类
{ public:
void DoDraw(int num); // 声明类的公有成员函数
};
void CDrawArray::DoDraw(int num) // 成员函数的实现
{ for (int i=0;i<num;i++)
{ for (int j=0;j<=i;j++)
cout<<'*';
cout<<'\n';
}
}
void main()
{ int num=5;
CDrawArray myDraw; // 定义类的一个对象
myDraw.DoDraw(num); // 调用此对象的成员函数
}
2.1C++程序结构
2.1.2C++程序的基本组成
C++程序往往由预处理命令,语句,函数,变量和对象,输入与输出以及注释等几个基本部分组成的 。
(1) 预处理命令在程序的一开始出现含有以,#‖开头的命令,它们是预处理命令 。 三类预处理命令:宏定义命令,文件包含命令和条件编译命令 。
(2) 语句可以是用来判断的条件语句,可以是用来反复运行的循环语句等 。
(3) 函数一个程序由若干个函数组成 。 有的是系统中所提供的库函数,有的是根据自己需要自己编制设计的函数 。 程序中必须有一个且只能有一个主函数 main。
(4) 变量和对象变量的类型有整型 (int),字符型 (char)以及浮点型 (float)等 。 对象通常是指,类,的实例 (具有该,类,类型的变量 ),如 myDraw是类 CDrawArray的对象 。
(5) 输入与输出使用输入与输出可以要求键入一些数值或把计算的结果显示在屏幕上 。
(6) 注释程序的目的不仅在于实现某种功能,解决某个问题,而且还在于数据结构和算法的交流 。 注释能提高程序的可读性,帮助用户对程序的理解 。
2.1C++程序结构
2.1.3C++程序的书写风格
标识符命名用来标识变量名、函数名、数组名、类名、对象名、类型名、文件名等的有效字符序列。标识符命名的好坏也会影响程序的可读性,命名时注意:
(1) 合法性标识符由大小写字母,数字字符 (0~9)和下划线组成,第一个字符必须为字母或下划线 。 任何标识符中都不能有空格,标点符号及其他字符 。 注意,标识符的大小写是有区别的 。 用户定义的标识符不能和系统的关键字同名 。
(2) 有效性标识符的长度 (组成标识符的字符个数 )是任意的,最好不超过 32个,因为有的编译系统只能识别前 32个字符,也就是说前 32个字符相同的两个不同标识符被有的系统认为是同一个标识符 。
(3) 易读性在定义标识符时,若能做到,见名知意,就可以达到易读性的目的 。
2.1C++程序结构
缩进和注释缩进是指程序在书写时不要将程序的每一行都由第一列开始,在适当的地方加进一些空行或空格 。 它同注释一样,也是为了提高程序的可读性 。
注释要注意的是:
(1) 注释应在编程的过程中同时进行,不要指望程序开发完成后再补写注释 。
那样只会多花好几倍的时间,更为严重的是,时间长了以后甚至会读不懂自己写的程序 。
(2)必要的注释内容应包含:源程序的总体注释 (文件名,作用,创建时间,版本,作者及引用的手册,运行环境等 ),函数注释 (目的,算法,使用的参数和返回值的含义,对环境的一些假设等 )及其他的少量注释 。 千万不要陈述那些一目了然的内容,否则会使注释的效果适得其反 。
2.2数据类型和基本输入输出
2.2.1基本数据类型使用基本数据类型时需要注意:
(1)无符号和有符号的区别在于数值最高位的含义 。 对 signed类型,最高位是符号位,其余各位表示数值大小; unsigned类型的各个位都用来表示数值大小;
相同基本数据类型的 signed和 unsigned的数值范围是不同 。
(2) char,short,int和 long可统称为整型 。 缺省时,char,short,int和
long本身是有符号 (signed)的 。
2.2数据类型和基本输入输出
2.2.2常量
整型常量
(1) 十进制整型常量,即十进制整数,如 34,128等 。
(2) 八进制整型常量,以 0开头的数,由 0至 7的数字组成 。
(3) 十六进制整型常量,以 0x或 0X开头的数,由 0至 9,A至 F或 a至 f组成 。
需要注意的是:
整型常量中的长整型 (long)要以 L或小写字母 l作为结尾 。
整型常量中的无符号型 (unsigned)要以 U或 u作为结尾 。
浮点型常量
(1) 十进制数形式,由整数部分和小数部分组成 。
(2) 指数形式,采用科学表示法,能表示出很大或很小的浮点数 。
若浮点型常量是以 F(或 f)结尾的,则表示单精度类型 (float),以 L(或小写字母 l)结尾的,表示长双精度类型 (long double)。若一个浮点型常量没有任何说明,表示双精度类型 (double)。
2.2数据类型和基本输入输出
字符常量字符常量是用单引号括起来的一个字符。
2.2数据类型和基本输入输出
[例 Ex_DrawBox] 用转义序列符绘制矩形框 。
#include <iostream.h>
void DrawBox(int nWidth,int nHeight);
void main()
{ DrawBox(20,6); }
void DrawBox(int nWidth,int nHeight)
{ int i;
// 绘制矩形框的顶边
cout<<'\311'; //┌ 字符的扩展 ASCII是 311
for (i=1; i<nWidth; i++)cout<<'\315';//─ 字符的扩展 ASCII是 315
cout<<"\273\n"; //┐ 字符的扩展 ASCII是 273
// 绘制矩形框的两边
for (i=1; i<nHeight; i++)
{ cout<<'\272'; //│ 字符的扩展 ASCII是 272
for (int j=1; j<nWidth; j++) cout<<' ';
cout<<"\272\n";
}
cout<<'\310'; //└ 字符的扩展 ASCII是 310
for (i=1; i<nWidth; i++) cout<<'\315';
cout<<"\274\n"; //┘ 字符的扩展 ASCII是 274
}
2.2数据类型和基本输入输出
字符串常量字符串常量是一对双引号括起来的字符序列 。 可以包含空格,转义序列符或其他字符 。 由于双引号是字符串的分界符,因此如果需要在字符串中出现双引号则必须用,\‖‖表示 。 例如:
,Please press \‖F1\‖ to help!‖
这个字符串被解释为:
Please press ―F1‖ to help!
字符串常量必须在同一行书写,若一行写不下,则需要用 ‘ \‘来连接,不要将字符常量和字符串常量混淆不清,它们主要的区别如下:
(1) 字符常量用单引号括起来,占一个字节;字符串常量是用双引号括起来,
至少占用两个字节 。 例如 ‘ a‘是字符常量,占有的一个字节用来存放字符 a
的 ASCII码值,而,a‖是字符串常量,它的长度不是 1而是 2,除了字符 a之外,
它的末尾还有个 ‘ \0‘字符 。 每个字符串的末尾都有一个这样的字符 。
(2) 字符常量实际上是整型常量的特殊形式,它可以参与常用的算术运算;而字符串常量则不能 。 例如:
int b=?a‘+3;// 结果 b为 100,这是因为将 ‘ a‘的 ASCII码值 97参与运算
2.2数据类型和基本输入输出
符号常量
[例 Ex_PI1] 用 #define定义符号常量 。
#include <iostream.h>
#define PI 3.14159
void main()
{ double r = 100.0,area;
area = PI * r * r;
cout<<"圆的面积是,"<<area<< "\n";
}
[例 Ex_PI2] 用 const定义的变量代替符号常量 。
#include <iostream.h>
const double PI = 3.14159;
void main()
{ double r = 100.0,area;
area = PI * r * r;
cout<<"圆的面积是,"<<area<< "\n";
}
2.2数据类型和基本输入输出
2.2.3变量
变量的定义定义变量是用下面的格式语句进行定义的,<类型 > <变量名表 >;
需要说明的是:
(1) 在同一个程序块中,不能有两个相同的变量名 。
(2) 没有字符串变量类型,字符串是用字符类型的数组或指针来定义的 。
(3) 与其他语言相比,C++变量的定义比较自由 。
变量的初始化程序中常需要对一些变量预先设置初值,这一过程称为初始化。可以在定义变量时同时使变量初始化,也可以在多个变量的定义语句中单独对某个变量初始化。
需要注意的是,一个未初始化的变量不是没有数值,而是取决于变量在系统中的存储形式,它可能是系统的缺省值或无效值。
2.2数据类型和基本输入输出
2.2.4基本输入输出
输出流 (cout)
通过 cout可以输出一个整数、实数、字符及字符串,cout中的插入符,<<‖
可以连续写多个,每个后面可以跟一个要输出的常量、变量、转义序列符、
对象以及表达式等。
为了更好地调整输出格式,有时还可以使用下面的输出函数 。
(1) width()函数,width()函数有两种格式:
int width();
int width(int);
(2) precision()函数,precision()也有两种格式:
int precision();
int precision(int);
(3) fill()函数,fill()函数也有两种格式:
char fill();
char fill(char);
2.2数据类型和基本输入输出
[例 Ex_CoutFrm] cout的格式输出 。
#include <iostream.h>
void main()
{ int nNum = 1234;
double fNum = 12.3456789;
cout<<"1234567890"<<endl;
cout.width(10);
cout<<nNum<<'\n';
cout.width(10);
cout<<fNum<<endl;
cout<<cout.precision(4)<<endl;
cout<<fNum<<endl;
cout.fill('#');
cout.width(10);
cout<<fNum<<endl; }
执行该程序,结果如下:
1 2 3 4 5 6 7 8 9 0
1 2 3 4
1 2,3 4 5 7
6
1 2,3 5
# # # # # 1 2,3 5
2.2数据类型和基本输入输出
输入流 (cin)
cin可以获得多个键盘的输入值,它具有下列格式:
cin>> <表达式 1 > [>> <表达式 2>,..]
其中,提取符,>>‖可以连续写多个,每个后面跟一个表达式,该表达式通常是获得输入值的变量或对象。
格式算子 oct,dec和 hex
能分别将输入或输出的数值转换成八进制,十进制及十六进制,例如:
[例 Ex_Algorism] 格式算子的使用 。
#include <iostream.h>
void main()
{ int nNum;
cout<<"Please input a Hex integer:";
cin>>hex>>nNum;
cout<<"Oct\t"<<oct<<nNum<<endl;
cout<<"Dec\t"<<dec<<nNum<<endl;
cout<<"Hex\t"<<hex<<nNum<<endl; }
程序执行时,结果如下:
Please input a Hex integer:7b?
Oct 173
Dec 123
Hex 7b
2.3运算符和表达式
2.3.1算术运算符算术运算符如下所示:
+ (正号运算符,如 +4,+1.23等 )
- (负号运算符,如 -4,-1.23等 )
* (乘法运算符,如 6*8,1.4*3.56等 )
/ (除法运算符,如 6/8,1.4/3.56等 )
% (模运算符或求余运算符,如 40%11等 )
+ (加法运算符,如 6+8,1.4+3.56等 )
- (减法运算符,如 6-8,1.4-3.56等 )
注意:
(1) 除法运算两个整数相除,结果为整数,如 7/5的结果为 1,它是将小数部分去掉,而不是四舍五入;若除数和被除数中有一个是浮点数,则进行浮点数除法,结果是浮点型 。 如 7/5.0,7.0/5,7.0/5.0的结果都是 1.4。
(2) 求余运算求余运算要求参与运算的两个操作数都是整型,结果是两个数相除的余数。
例如 40%5的结果是 0,40%11的结果是 7。要理解负值的求余运算,例如
40%-11结果是 7,-40%11结果是 -7,-40%-11结果也是 -7。
2.3运算符和表达式
(3) 优先级和结合性
将表达式的求值中多种运算之间的先后关系 (即运算符之间的优先关系 )用运算符的优先级表示,优先级的数值越小优先级越高 。 在算术运算符中,单目运算符的优先级最高,其次是乘,除和求余,最后是加减 。
优先级相同的运算符,则按它们的结合性进行处理 。 运算符的结合性是指运算符和操作数的结合方式,有,从左至右,和,从右至左,。,从左至右的结合,指运算符左边的操作数先与运算符相结合,再与运算符右边的操作数进行运算;自右至左的结合次序是将运算符右边的操作数先与运算符相结合 。
在算术运算符中,除单目运算符外,其余运算符的结合性都是从左至右的 。
(4) 关于书写格式
在使用运算符进行数值运算时,若书写时没有在双目运算符两边加上空格,
则有时编译系统会做出与自己想象中不同的理解 。
书写时,有时有意识地加上一些括号 。 这样不仅增强程序的可读性,而且,
尤其当对优先关系犹豫时,加上括号是保证正确结果的最好方法 。
(5) 溢出处理某数除以 0或当其它浮点溢出时,编译系统将报告错误并终止程序运行。但对整数溢出,系统却不认为是一个错误。
2.3运算符和表达式
2.3.2赋值运算符
复合赋值
10种复合赋值运算符,+=,-=,*=,/=,%=,—=,|=,^=,<<=,>>=
注意:
(1)在复合赋值运算符之间不能有空格,例如 += 不能写成 + = 。
(2) 所有运算符高于逗号运算符,复合赋值运算符的结合性是从右至左的 。
多重赋值指在一个赋值表达式中出现两个或更多的赋值符 (―=‖),例如:
nNum1 = nNum2 = nNum3 = 100;
赋值符的结合性是从右至左,上述赋值的过程:首先对 nNum3 = 100求值,
将 100赋值给 nNum3,同时该赋值表达式取得值 100;然后将该值赋给
nNum2,该赋值表达式也取得值 100;最后将 100赋给 nNum1。
2.3运算符和表达式
2.3.3数据类型转换
自动转换将数据类型从低到高进行转换,例如,10 +?a‘ + 2*1.25 - 5.0/4L运算:
(1)2*1.25的运算,将 2和 1.25都转换成 double型,结果为 double型的 2.5。
(2) 长整型 4L和 5.0转换成 double型,5.0/4L结果为 double型,值为 1.25。
(3) 进行 10 +?a‘ 的运算,先将 ‘ a‘转换成整数 97,运算结果为 107。
(4)整数 107和 2.5相加,先将整数 107转换成 double型,值为 109.5。
(5)进行 109.5-1.25的运算,结果为 double型的 108.25。
强制转换强制类型有下列两种格式,(<类型名 >)<表达式 >,<类型名 >(<表达式 >) 。
整型
(int)
无符号整型
(unsigned)
长整型
(long)
无符号长整型
(unsigned long)
双精度浮点型
(double)
短整型,字符型
(short,char)
单精度浮点型
(float)
图 2.1 类型转换的顺序
2.3运算符和表达式
2.3.4关系运算符是比较两个操作数是否符合给定的条件 。 若符合条件,则关系表达式的值为
,真,,否则为,假,。 在编译系统中,往往将,真,表示为,true‖或 1,
将,假,表示为,false‖或 0。 而任何不为 0的数被认为是,真,,0被认为是,假,。
由于关系运算需要两个操作数,所以关系运算符都是双目运算符 。
提供了 6种关系运算符:
<(小于 ),<=(小于等于 ),>(大于 ),>=(大于等于 ),= =(相等于 ),! =(不等于 )
其中,前 4种的优先级相同且高于后面的两种 。 例如:
a == b > c 等效于 a == ( b > c )
但关系运算符的优先级低于算术运算符 (其他可参见表 2.6)。 例如:
a = b < c 等效于 a = ( b < c )
2.3运算符和表达式
2.3.5逻辑运算符将多个关系表达式或逻辑量组成一个逻辑表达式 。 提供了 3种逻辑运算符:
! 逻辑非 (单目 ):指将,真,变,假,,,假,变,真,。
&& 逻辑与 (双目 ):指两个操作数都是,真,时,结果为,真,,否则为
,假,。
|| 逻辑或 (双目 ):指两个操作数中有一个是,真,时,结果为,真,,只有它们都为,假,时,结果才为,假,。
,逻辑非,,,逻辑与,和,逻辑或,的优先级依次从高到低,且,逻辑非,
的优先级还比算术运算符和关系运算符高,而,逻辑与,和,逻辑或,的优先级却比关系运算符要低 。 例如:
0|| 2 < 4 - !0
表达式的运算过程是这样的:
(1) 进行,!0‖的运算,结果为 1(―真,)。
(2) 进行,8 < 4-1‖的运算,即,8 < 3‖,结果为 0(―假,)。
(3) 进行,0||0‖ 的运算,结果为 0(―假,)。
表达式的值为 0。
2.3运算符和表达式
2.3.6 位运算符是对操作数按其在计算机内表示的二制数逐位地进行逻辑运算或移位运算,
参与运算的操作数只能是整型常量或变量 。 提供了六种位运算符:
~ (按位求反,单目运算符 ):将一个二进制数的每一位求反,即 0变成 1,
1变成 0。
<< (左移,双目运算符 ):将左操作数的二进制值向左移动指定的位数,左移后,低位补 0,移出的高位舍弃 。
>> (右移,双目运算符 ):将左操作数的二进制值向右移动指定的位数,右移后,移出的低位舍弃 。 无符号数高位补 0;有符号数高位补符号位 。
& (按位与,双目运算符 ):将两个操作数对应的二进制位进行逻辑与操作 。
^ (按位异或,双目运算符 ):将两个操作数对应的二进制位进行异或操作 。
| (按位或,双目运算符 ):将两个操作数对应的二进制位进行逻辑或操作 。
说明:
(1) 左移和右移运算可替代乘和除以 2的 n次方运算,n为移位的位数 。
(2) 当多个位运算符出现在同一个表达式中时,要注意它们的优先级别 。
2.3运算符和表达式
2.3.7 三目运算符
C++中唯一的三目运算符是条件运算符,其格式如下:
<条件表达式 >? <表达式 1>,<表达式 2>
―条件表达式,是 C++中可以产生,真,和,假,结果的任何表达式,如果条件表达式的结果为,真,,则执行表达式 1,否则执行表达式 2。 例如:
nNum = (a > b)? 10,8;
注意,只有在表达式 2后面才能出现分号结束符,,表达式 1‖和,表达式 2‖
中都不能有分号。
2.3运算符和表达式
2.3.8增 1和减 1运算符
++和 --既可放在变量的左边也可以出现在变量的右边,称为前缀运算符和后缀运算符 。 例如:
i++; 或 ++i; (等效于 i = i + 1; 或 i += 1;)
i--; 或 --i; (等效于 i = i - 1; 或 i -= 1;)
若前缀运算符和后缀运算符仅用于某个变量的增 1和减 1,这两都是等价的,
如果将这两个运算符和其他的运算符组合在一起,在求值次序上就会产生根本的不同:
如果用前缀运算符对一个变量增 1(减 1),在将该变量增 1(减 1)后,用新的值在表达式中进行其他的运算 。
如果用后缀运算符对一个变量增 1(减 1),用该变量的原值在表达式进行其他的运算后,再将该变量增 1(减 1)。
例如:
a = 5;
b = ++a - 1; // 相当于 a = a + 1; b = a – 1;

a = 5;
b = a++ -1; // 相当于 b = a – 1; a = a + 1;
虽然它们中的 a值的结果都是 6,但 b的结果却不一样,前者为 5,后者为 4。
2.3运算符和表达式
2.3.9逗号运算符逗号运算符是优先级最低的运算符,它可以使多个表达式放在一行上,从而大大简化了程序 。 在计算时,C++将从左至右逐个计算每个表达式,最终整个表达式的结果是最后计算的那个表达式的类型和值 。 例如:
j = ( i = 12,i + 8);
式中,i = 12,i + 8 是含逗号运算符的表达式,计算次序是先计算表达式
i = 12,然后再计算 i + 8,整个表达式的值是最后一个表达式的值,即 i +
8的值 20,从而 j的结果是 20。
再如:
d = (a = 1,b = a + 2,c = b + 3);
d的结果为 6。
2.3运算符和表达式
2.3.10 sizeof运算符
sizeof的目的是返回操作数所占的内存空间大小 (字节数 ),具有两种格式:
sizeof(<表达式 >)
sizeof(<数据类型 >)
例如:
sizeof(―Hello‖) // 计算字符串常量,Hello‖的实际长度 (字符个数 ),
结果为 6
sizeof(int) // 计算整型 int所占内存的字节数需要说明的是,由于同一类型的操作数在不同的计算机中占用的存储字节数可能不同,因此 sizeof的结果有可能不一样 。 例如 sizeof(int)的值可能是 4,
也可能是 2。
2.4基本语句
2.4.1表达式语句、空语句和复合语句
表达式语句,空语句及复合语句是一些系统顺序执行 (操作 )的语句 。 任何一个表达式加上分号就是一个表达式语句 。
如果表达式是一个空表达式,那么构成的语句称为空语句,也就是说仅由分号,;‖
也能构成一个语句 。 空语句仅为语法的需要而设置,并不执行任何动作 。
复合语句由两条或两条以上的语句组成,并由一对花括号 ({ })括起来 。 又称为块语句 。 复合语句中的语句可以是单条语句 (包括空语句 ),也可以再包含复合语句 。
注意,在复合语句中定义的变量只作用于该复合语句的范围,而在复合语句外,这些变量却不能被调用 。 例如:
[例 Ex_Block] 块语句的变量使用范围 。
#include <iostream.h>
void main()
{ int i = 5,j = 6;
cout<<i<<j<<endl; // 输出的结果是 5和 6
{ int i = 2,j = 3,k = 4;
cout<<i<<j<<k<<endl; // 输出结果是 2,3和 4
}
cout<<i<<j<<endl; // 输出的结果仍然是 5和 6,但不能使用 k,如 cout<<k;
// 将发生错误 。
}
2.4基本语句
2.4.2选择语句
条件语句条件语句 if具有下列形式:
if (<表达式 >) <语句 1>
[else <语句 2>]
(1)条件语句中的表达式一般为逻辑表达式或关系表达式 。 表达式的类型也可以是任意的数值类型 (包括整型,浮点型,字符型等 )。
(2)适当添加花括号 (―{ }‖)来增加程序的可读性 。
(3)条件语句中的语句 1和语句 2也可是 if条件语句,这就形成了 if语句的嵌套 。
(4)else总是和其前面最近的 if配套的 。
2.4基本语句
开关语句具有下列形式:
switch ( <表达式 > )
{ case <常量表达式 1>,[语句 1]
case <常量表达式 2 >,[语句 2]
...
case <常量表达式 n>,[语句 n]
[default,语句 n+1]
}
注意:
(1) switch后面的表达式可以是整型,字符型或枚举型的表达式,而 case后面的常量表达式的类型必须与其匹配 。
(2) 多个 case可以共有一组执行语句 。
(3) 若同一个 case后面的语句是复合语句,即有两条或两条以上的语句,则这些语句可以不用花括号 (―{}‖)括起来 。
(4) 由于 case语句起标号作用,因此每一个 case常量表达式的值必须互不相同,
否则会出现编译错误 。
2.4基本语句
2.4.3循环语句
while循环语句
while循环语句具有下列形式:
while (<表达式 >) <语句 >
do...while循环语句
do...while循环语句具有下列形式:
do <语句 >
while (<表达式 >)
for循环语句
for循环语句具有下列形式:
for ([表达式 1]; [表达式 2]; [表达式 3]) <语句 >
(1) 表达式 1、表达式 2、表达式 3都可以省略,但分号,;,不能省略。
(2) 表达式 1和表达 3可以是一个简单的表达式,也可以是逗号表达式,即包含两个或两个以上的简单表达式,中间用逗号分隔。
(3) 由于循环体是由任何类型的语句组成的,因此在循环体内还可以包含前面的几种循环语句,这样就形成了循环的嵌套。
2.4基本语句
2.4.4break和 continue语句若需跳出循环结构或重新开始循环,就得使用 break和 continue语句,格式:
break;从一个循环体跳出,即提前终止循环,也可以跳出 switch结构 。
continue;用于那些依靠条件判断而进行循环的循环语句 。 对于 for语句来说,
目的是将流程转到 for语句的表达 2和表达式 3。 例如:
[例 Ex_Continue] 把 1~100之间的不能被 7整除的数输出 。
#include <iostream.h>
void main()
{ for (int nNum=1; nNum<=100; nNum++)
{ if (nNum%7 == 0) continue;
cout<<nNum<<‖ ‖;
}
cout<<‖\n‖;
}
当 nNum能被 7整除时,执行 continue语句,流程转到 for 语句中的
nNum<=100; nNum++,并根据表达式 nNum<=100的值来决定是否再做循环。而当 nNum不能被 7整除时,才执行 cout<<nNum<<‖ ‖语句。
作业
1。 P357~362:1~30题偶数题
2。 P385:预习实验 2
2.5函数和预处理
2.5.1函数的定义和调用
函数的定义定义一个函数的格式如下:
<函数类型 > <函数名 >( <形式参数表 > )
{
<若干语句 > 函数体
}
函数类型决定了函数所需要的返回值类型。函数名后面必须跟一对圆括号
,( )‖,以区别于变量名及其他用户定义的标识名。函数的形式参数写在括号内,参数表中参数个数可以是 0,一个或多个参数,圆括号不能省略。函数体由在一对花括号中的若干条语句组成,用于实现这个函数执行的动作。
函数的声明声明一个函数可按下列格式进行:
<函数类型 > <函数名 >( <形式参数表 > );
int sum(int x,int y);和 int sum(int,int); 是等价的 。
2.5函数和预处理
函数的调用函数调用的一般形式为:
<函数名 >( <实际参数表 > );
所谓,实际参数,(简称,实参,),它与,形参,相对应,是实际调用函数时所给定的常量,变量或表达式,且必须有确定的值 。 例如:
int a[5] = {7,9,6,3,4};
sum(a[0],6);

sum(a[0]*a[1],a[2]+a[3]);
等都是合法的调用 。 需要注意的是:实参与形参的个数应相等,类型应一致,
且按顺序对应,一一传递数据 。
C++中,调用一个函数的方式可以有很多,例如:
sum(3,4); // A
int c = 2 * sum(4,5); // B
c = sum(c,sum(c,4)); // C
其中,A是将函数作为一个语句,不使用返回值,只要求函数完成一定的操作; B把函数作为表达式的一部分,将返回值参与运算,结果 c = 18; C是将函数作为函数的实参,等价于,c = sum(18,sum(18,4));‖,执行
sum(18,4)后,等价于,c = sum(18,22) ;‖,最后结果为 c = 40。
2.5函数和预处理
2.5.2函数的参数传递函数的参数传递有两种方式,一种是按值传递,另一种是地址传递或引用传递。
[例 Ex_SwapValue] 交换函数两个参数的值 。
#include <iostream.h>
void swap(float x,float y)
{ float temp;
temp = x; x = y; y = temp;
cout<<"x = "<<x<<",y = "<<y<<"\n";
}
void main()
{ float a = 20,b = 40;
cout<<"a = "<<a<<",b = "<<b<<"\n";
swap(a,b);
cout<<"a = "<<a<<",b = "<<b<<"\n";
}
运行结果为:
a = 20,b = 40
x = 40,y = 20
a = 20,b = 40
值传递的最大好处是保持函数的独立性 。 在值传递的情况下,函数只有通过 return来返回某个类型的值 。
2.5函数和预处理
2.5.3作用域和存储类型
作用域
(1) 块作用域在块中声明的标识符,作用域从声明处开始,一直到结束块的花括号为止。
具有块作用域的标识符称作局部标识符,块作用域也称作局部作用域
标识符的作用域完全相同时,不允许出现相同的标识符名。标识符具有不同的作用域时,允许标识符同名。
在 for语句中声明的标识符,其作用域是包含 for语句的那个内层块,而不是仅仅作用于 for语句。
(2) 函数原型作用域在声明函数原型所指定的参数标识符的作用范围。这个作用范围是在函数原型声明中的左、右括号之间。在函数原型中声明的标识符可以与函数定义中说明的标识符名称不同。由于所声明的标识符与该函数的定义及调用无关,
所以可以在函数原型声明中只作参数的类型声明,而省略参数名。
(3) 函数作用域
C++语言中,只有 goto语句中的标号标识符具有函数作用域。具有函数作用域的标识符在声明它的函数内随处可见,但在此函数之外不可见。 goto语句的滥用导致程序流程无规则、可读性差。
2.5函数和预处理
(4) 文件作用域在函数外定义的标识符或用 extern说明的标识符称为全局标识符 。 全局标识符的作用域称为文件作用域,它从声明之处开始,直到文件结束一直是可见的 。
在同一个作用域中,不能对同名的标识符作多种不同的声明 。 当块作用域内的标识符与全局标识符同名时,局部标识符优先,且在块作用域内使用作用域运算符,,:‖来引用与局部标识符同名的全局标识符 。 这一点与块作用域不同,例如:
[例 Ex_Process] 在块作用内引用文件作用域的同名变量 。
#include <iostream.h>
int i = 10;
void main()
{ { int i = 5,j;
::i =,:i + 4;
j =,:i + i;
cout<<"::i = "<<::i<<",j = "<<j<<"\n";
}
cout<<"::i = "<<i<<"\n";
}
运行结果为:
::i = 14,j = 19
::i = 14
2.5函数和预处理
变量的存储类型存储类型的声明格式,<存储类型的关键字 > <数据类型名 > <变量名表 >;
(1) 自动类型 (auto)
一般说,自动存储类型声明的变量都是限制在某个程序范围内使用的 。 从系统角度来说,自动存储类型变量是采用堆栈方式分配内存空间 。 程序执行到超出该变量的作用域时,就释放它所占用的内存空间,其值也随之消失了 。
声明一个自动存储类型的变量是在变量类型前面加上关键字 auto
若自动存储类型的变量是在函数内或语句块中声明,可省略 auto,
(2) 静态类型 (static)
它和自动存储类型的变量的最大不同之处在于:静态类型变量在内存中是以固定地址存放的,而不是以堆栈方式存放的 。 只要程序还在继续执行,静态类型变量的值就一直有效,不会随它所在的函数或语句块的结束而消失 。
静态类型的变量均有确定的初值,当声明变量时没有指定其初值,则编译器将其初值置为 0。
在程序中声明的全局变量总是静态存储类型,若在全局变量前加一个 static,
使该变量只在这个源程序文件内使用,称之为全局静态变量或静态全局变量 。
静态函数也是在某个函数声明前加上 static,它的目的也是使该函数只在声明的源文件中使用,对于其他源文件则无效 。
2.5函数和预处理
(3) 寄存器类型 (register)
使用关键字 register声明寄存器类型的变量的目的是将所声明的变量放入寄存器内,从而加快程序的运行速度 。 但有时,在使用这种声明时,若系统寄存器已经被其他数据占据时,寄存器类型的变量就会自动当作 auto变量 。
(4) 外部类型 (extern)
用关键字 extern声明的变量称为外部变量。某个变量被声明成外部变量时,
不必再次为它分配内存就可以在本程序中引用这个变量。只有在两种情况下要使用外部变量:
第一种情况,在同一个源文件中,若定义的变量使用在前,声明在后,这时在使用前要声明为外部变量。
第二种情况,当由多个文件组成一个完整的程序时,在一个源程序文件中完全定义的变量要被其他若干个源文件引用时,引用的文件中要使用 extern声明外部变量。
2.5函数和预处理
[例 Ex_ Extern2] 不在同一个源文件中的外部变量使用 。
// Ex_Extern2.cpp文件内容
#include <iostream.h>
int n;
void f();
void main()
{ n = 20;
cout<<"n = "<<n<<"\n";
f();
}
// Ex_Extern2_1.cpp文件内容
#include <iostream.h>
extern int n; // 外部变量声明,它在另一个文件中定义
void f()
{ n++;
cout<<"n = "<<n<<"\n";
}
项目 Ex_StaticScope经运行后,结果如下:
n = 20
n = 21
2.5函数和预处理
2.5.4带默认形参值的函数在设置函数的默认参数值时要注意:
(1) 当函数既有声明又有定义后,不能在函数定义中指定默认参数 。
(2) 当一个函数中有多个默认参数时,则形参分布中,默认参数应从右到左逐渐定义。在函数调用时,系统按从左到右的顺序将实参与形参结合,当实参的数目不足时,系统将按同样的顺序用声明或定义中的默认值来补齐所缺少的参数。
(3)默认参数值可以是全局变量,全局常量,甚至是一个函数 。 但不可以是局部变量,因为默认参数的函数调用是在编译时确定的,而局部变量的值在编译时无法确定 。 例如:
int a = 1;
int f1(int);

int g1(int x = fun(a)); // 正确,允许默认参数值为函数

void f2()
{ int i;
void g2(int x = i); // 错误,处理 g2()函数时,i不可见
}
2.5函数和预处理
2.5.5函数的递归调用
[例 Ex_Factorial] 编程 n的阶乘 n!。
n!=n*(n-1)*(n-2)*...*2*1;它一般也可用下式表示:
#include <iostream.h>
long factorial(int n);
void main()
{ cout<<factorial(4)<<endl; // 结果为 24
}
long factorial(int n)
{ long result = 0;
if (n == 0)
result = 1;
else
result = n*factorial(n-1);
return result;
}
f(n) = 1当 n=0时n*f(n-1)当 n>0时
2.5函数和预处理在函数 factorial中,当 n不等于 0时,又调用了该函数本身 。 下面来分析此函数的执行过程:
(1)因 n = 4,不等于 0,故执行,result = 4*factorial(3);‖,函数返回的值为 4*factorial(3),即 factorial(4) = 4*factorial(3);
(2)调用,factorial(3);‖,n = 3不等于 0,执行,result=3*factorial(2);‖,
函数返回的值为 3*factorial(2),即 factorial(3) =3*factorial(2);
(3)调用,factorial(2);‖,n = 2不等于 0,执行,result=2*factorial(1);‖,
函数返回的值为 2*factorial(1),即 factorial(2) =2*factorial(1);
(4)调用,factorial(1);‖,n = 1不等于 0,执行,result=1*factorial(0);‖,
函数返回的值为 1*factorial(0),即 factorial(1) =1*factorial(0);
(5)调用,factorial(0);‖,n等于 0,结果函数返回的值为 1。
2.5函数和预处理
2.5.6内联函数
[例 Ex_Inline] 用内联函数实现求两个浮点数的最大值 。
#include <iostream.h>
inline float fmax(float x,float y)
{ return x>y?x:y; }
void main()
{ float a,b;
cout<<"请输入两个浮点数,";
cin>>a>>b;
cout<<"最大的数为,"<<fmax(a,b)<<"\n";
}
使用内联函数时,还需要注意的是:
(1) 内联函数也要遵循定义在前,调用在后的原则 。
(2) 需要定义成的内联函数不能含有循环,switch和复杂嵌套的 if语句 。
(3) 递归函数是不能被用来做内联函数的 。
(4) 编译器是否将定义成的内联函数作为真正的内联函数处理,由编译器自行决定 。
2.5函数和预处理
2.5.7函数的重载函数重载是指 C++允许多个同名的函数存在,但同名的各个函数的形参必须有区别:形参的个数不同,或者形参的个数相同,但参数类型有所不同。
需要说明的是:
(1) 重载函数必须具有不同的参数个数或不同的参数类型,若只有返回值的类型不同是不行的 。
(2) 当函数的重载带有默认参数时,应该注意避免二义性 。 例如:
int fun(int a,int b = 0);
int fun(int a);
是错误的。因为如果有函数调用 fun(2)时,编译器无法准确地确定应调用哪个函数。
2.5函数和预处理
2.5.8预处理
宏定义命令
#define PI 3.141593
(1) #define,PI和 3.141593之间一定要有空格,且一般将宏名定义成大写,以与普通标识符相区别 。
(2) 宏被定义后,一般不能再重新定义,而只有当使用如下命令才可以:
#undef 宏名
(3) 一个定义过的宏名可以用来定义其它新的宏,但要注意其中的括号 。
(4) 上述宏定义是简单的一种形式,实际上它还可以带参数,例如:
#define MAX(a,b) ((a)>(b)?(a):(b))
文件包含命令有下列两种格式:
#include <文件名 >
#include ―文件名,
在使用 #include命令需要注意的是,一条 #include命令只能包含一个文件,
若想包含多个文件须用多条文件包含命令。
2.5函数和预处理
条件编译命令
(1) 第一种形式
#ifdef <标识符 >
<程序段 1>
[#else
<程序段 2>]
#endif
[例 Ex_UseIfdef] 使用 #ifdef条件编译命令 。
#include <iostream.h>
#define LI
void main()
{ #ifdef LI
cout<<"Hello,LI!\n";
#else
cout<<"Hello,everyone!\n";
#endif
}
运行结果:
Hello,LI!
2.5函数和预处理
(2) 第二种形式
#ifndef <标识符 >
<程序段 1>
[#else
<程序段 2>]
#endif
这与前一种形式的区别仅在于,如果标识符没有被 #define命令定义过,则编译
<程序段 1>,否则就编译 <程序段 2>。
(3) 第三种形式
#if <表达式 1>
<程序段 1>
[#elif <表达式 2>
<程序段 2>
...]
[#else
<程序段 n>]
#endif
2.5函数和预处理
[例 Ex_UseIf] 使用 #if条件编译命令 。
#include <iostream.h>
#define A -1
void main()
{ #if A>0
cout<<"a>0\n";
#elif A<0
cout<<"a<0\n";
#else
cout<<"a==0\n";
#endif
}
运行结果为:
a<0
若将 #define A -1改为
#define A 0
则结果为:
a==0
2.6构造类型
2.6.1数组
数组的定义定义一个数组可按下列格式进行:
<类型 > <数组名 > [<常量表达式 1>][<常量表达式 2>]...
<数组名 >后面的 [<常量表达式 1>][<常量表达式 2>]...用于确定数组的维数和大小 。
一般地,表示某维大小的常量表达式中不能包含变量,但可以包括常量和符号常量,其值必须是一个确定的整型数值,且数值大于 1。 例如:
int a[4 - 2][3 * 6];
const int SIZE=18;
int b[SIZE];
都是合法的数组定义。
2.6构造类型
数组元素的引用数组定义后,就可以引用数组中的元素,引用时按下列格式:
<数组名 > [<下标 >]...
需要注意的是:
(1) C++数组的下标总是从 0开始的,也就是说,a[0]是数组 a的第一个元素;但下标一定要小于数组定义时的大小 。 也就是说,长度为 n的数组,其下标范围为 0 ~ (n-1)。 例如,
int a[5];
的数组元素下标应从 0到 4,而没有 a[5]这个数组元素 。
(2)下标可以是整型常量或整型表达式,引用的元素下标个数应与数组定义的维数一致 。
(3) 数组定义后,系统会根据数组的大小开辟相应的内存,并依照下标的高低依次存放数组中的各个元素 。 例如上述数组 a的存放次序是,a[0],a[1],a[2],a[3],a[4]。 又如:
int b[3][4];
的存放次序是:
b[0][0],b[0][1],b[0][2],b[0][3],
b[1][0],b[1][1],b[1][2],b[1][3],
b[2][0],b[2][1],b[2][2],b[2][3]。
2.6构造类型
数组的赋值数组中的元素既可以在数组定义的同时赋初值,即初始化,也可以在定义后赋值 。 也可以给其中的一部分元素赋值 。 有时,在对全部数组元素赋初值时,
可以不指定数组的长度 。
需要说明的是:
(1)初始化数组的值的个数不能多于数组元素个数,初始化数组的值也不能通过跳过逗号的方式来省略,这在 C中是允许的,但在 C++中是不允许的 。
(2)对于二维数组来说,如果对全部元素都赋初值,则定义数组时对第一维的大小可以忽略,但第二维的大小不能省 。
(3)只对部分元素赋初值,可有两种说明方式:一种是以行为单位,依次列出部分元素的值;另一种是以数组元素的排列顺序依次列出前面部分元素的值 。
(4)在程序中对某个或所有数组元素进行赋值或其他处理时,它的操作与变量是一样的,因为每一个数组元素都可以看成是一个与数组类型相同的变量 。
2.6构造类型
[例 Ex_ArraySort] 把 5个整型数按从小到大的次序排列 。
#include <iostream.h>
void main()
{ int a[5]={20,40,-50,7,13};
for (int i=0; i<5; i++)
{ for (int j=i+1; j<5; j++)
{ if (a[i]>a[j])
{ int temp = a[j];
a[j] = a[i];
a[i] = temp;
}
}
cout<<a[i]<<" ";
}
}
结果如下:
-50 7 13 20 40
2.6构造类型
字符数组
字符数组中的字符串是以空字符,\0‖作为结束符,它不是字符串的内容,
但占据了一个存储空间 。
如果指定的数组长度大于字符串中的字符个数,那么其余的元素将自动定为空字符 ’ \0‘。 例如:
char ch[ 9] = ‖Hello!‖;
因,Hello!‖的字符个数为 6,但还要包括一个空字符 ’ \0‘,故数组长度至少是 7,从 ch[6]开始到 ch[8]都等于空字符 。 一定要小心字符数组与其他数组的这种区别,例如:
char ch[ 6] = ‖Hello!‖;
虽然该代码不会引起编译错误,但由于改写了数组空间以外的内存单元,
所以是危险的 。 正因为这一点,Visual C++将其列为数组超界错误 。
对于二维的字符数组,其初始化也可依上进行 。 例如:
char str[3][ ]={―How‖,‖are‖,‖you‖};
这时,数组元素 str[0][0]表示一个字符,值为 ’ H‘;但 str[0][ ]却表示一个字符串,How‖,因为 str[0][ ]是一个一维字符数组 。
2.6构造类型
2.6.2传递数组参数
[例 Ex_StrChange] 改变字符串中的内容 。
#include <iostream.h>
#include <string.h>
void change(char ch[5]);
void main()
{ char name[5]="Ding";
cout<<name<<endl;
change(name); // 调用时,只需指定数组名
cout<<name<<endl;
}
void change(char ch[5])
{ strcpy(ch,"Zhen"); }
程序运行结果为:
Ding
Zhen
需要注意的是:
(1) 实参数组与形参数组类型应一致,如不一致,结果将出错 。
(2) 形参数组也可以不指定大小,在定义数组时数组名后面跟一个空的方括号,为了在被调用函数中处理数组元素的需要,可以另设一个参数,传递数组元素的个数 。
2.6构造类型
2.6.3结构体
定义结构体结构体定义的格式为:
struct <结构体名 >
{ <成员定义 1>;
<成员定义 2>;
...
<成员定义 n>;
} [结构变量名表 ];
注意:
(1) 在定义结构体时,不要忘记最后一个花括号后面的分号,;‖。
(2) 结构体的成员变量可以是基本数据类型,也可以是其他合法的类型 。 例如:
struct STUDENT
{ PERSON one; // 用已定义的结构体类型声明成员
float eng,phy,math,poli;// 英语,物理,数学和政治的成绩
};
2.6构造类型
结构体变量的初始化和引用结构体变量的初始化的一般形式是在变量后面加上,= {<初值列表 >};
当一个结构体变量定义之后,就可引用这个变量 。 使用时,遵循下列规则:
(1) 只能引用结构体变量中的成员变量,并使用下列格式:
<结构体变量名 >.<成员变量名 >
(2)若成员本身又是一个结构体变量,引用时需要用多个成员运算符一级一级地找到最低一级的成员 。
(3) 结构类型相同的变量之间可以直接赋值,这种赋值等效于各个成员的依次赋值 。 例如:
struct PERSON
{ int age; // 年龄
char name[25]; // 姓名
};
PERSON one = {30,"LiMing"};
PERSON another = one;
cout<<another.name<<"\n"; // 输出 "LiMing"
其中,another = one等效于:
another.age = one.age;
strcpy(another.name,one.name);
2.6构造类型
2.6.4传递结构体参数
[例 Ex_StructValue] 将结构体的值作为参数传给函数 。
#include <iostream.h>
struct PERSON
{ int age; // 年龄
float weight; // 体重
char name[25]; // 姓名
};
void print(PERSON one)
{ cout <<one.name<<" "<<one.age<<" "<<one.weight<<"\n";
}
PERSON all[4] = {{20,60,"Zhang"},{28,50,"Fang "},
{33,78,"Ding "},{19,65,"Chen "}};
void main()
{ for (int i=0; i<4; i++) print(all[i]);
}
运行结果为:
Zhang 20 60
Fang 28 50
Ding 33 78
Chen 19 65
2.6构造类型
2.6.5共用体共用体和结构体最大的区别是:共用体在任一时刻只有一个成员处于活动状态,且共用体变量所占的内存长度等于各个成员中最长的成员的长度,结构体变量所占的内存长度等于各个成员的长度之和 。
共用体中,各个成员所占内存的字节数各不相同,但都从同一地址开始的 。 这种多个成员变量共用一个内存区的技术,能有效地降低程序所占的内存空间 。
定义一个共用体可用下列格式:
union <共用体名 >
{ <成员定义 1>;
<成员定义 2>;
...
<成员定义 n>;
} [共用体变量名表 ]; // 注意最后的分号不要忘记 。
例如:
union NumericType
{ int iValue; // 整型变量,4个字节长
long lValue; // 长整型变量,4个字节长
float fValue; // 浮点型,8个字节长
};
系统为 NumericType开辟了 8个字节的内存空间 。
共用体除了关键字 (union)不同外,其使用方法均与结构体相同。
2.6构造类型
2.6.6枚举类型它是一系列的有标识名的整型常量集合,主要功能是增加程序代码的可读性 。 格式:
enum <枚举类型名 > {<枚举常量表 >} [枚举变量 ]; // 注意最后的分号不要忘记 。
缺省时,系统为每一个枚举常量都对应一个整数,并从 0开始,逐个增 1,缺省的值也可重新指定,例如:
enum Colors { Black,Blue,Green=4,Cyan,Red=8,Yellow,White };
则各枚举常量对应的整数依次为 0,1,4,5,8,9,10。
枚举变量最终的值只能等于该枚举类型中的某个枚举常量,而不能用一个整型数值直接赋值 。 例如:
today = Mon; // 合法,值为 1
tomorrow = today; // 合法,值为 1
int i = today; // 合法,值为 1
yesterday = 3; // 不合法,不能直接赋值不要在定义枚举类型的同时,再对枚举常量,枚举变量及枚举类型名重新定义 。 例如下列的定义是不合法的:
int tomorrow;
int Yellow;
int Sun;
2.6构造类型
2.6.7用 typedef定义类型用 typedef可将已有的类型名用新的类型名 (别名 )来代替,格式:
typedef <已有的类型名 > <类型别名 >;
例如:
typedef float FLOAT;
typedef char CH10[10];
FLOAT表示 float类型,CH10表示具有 10个元素的字符数组类型 。 这样在以后的代码中,就可以使用这些类型名定义新的变量,如:
FLOAT x,y;
CH10 a,b; // a和 b都是具有 10个元素的字符数组 。
它们等价于
float x,y;
char x[10],y[10];
typedef几乎可以对所有的数据类型进行定义,但却不能用来定义变量;而且与 struct,union,enum等相比,它不能构造出新的数据类型 。
2.7指针和引用
2.7.1指针和指针变量
程序中定义了一个变量,编译时系统会给这个变量分配内存单元,根据程序中定义的变量类型,分配一定长度的内存空间,每个内存单元中存放着变量的值 。 系统为每一个内存单元分配一个地址 。 这个,地址,称为,指针,。
指针变量 (pointer)是存放内存地址的变量,一般情况下该地址是另一个变量存储在内存中的首地址,又称该指针变量,指向,这个变量 。
一旦一个变量初始化后,该变量在内存中的地址也就确定下来,不管以后对该变量如何赋值,在程序运行期间,其内存地址总是固定不变的 。
指针变量遵循先定义后使用的规则 。 定义一个指针变量格式:
<类型名 > *<指针变量名 1>[,*<指针变量名 2>,...];
式中的,*” 是一个定义指针变量的说明符,它不是指定变量名的一部分 。
每个指针变量前面都需要这样的,*” 来标明 。 例如:
int *pInt1,*pInt2; // pInt1,pInt2是指向整型变量的指针
float *pFloat; // pFloat是一个指向浮点型变量的指针
char *pChar; // pChar是一个指向字符型变量的指针,
它通常用来处理字符串
定义一个指针后,系统会给指针分配一个内存单元,但分配的空间大小是相同的,因为指针变量的数值是某个变量的地址,而地址值的长度是一样的 。
2.7指针和引用
2.7.2&和 *运算符
C++中有两个专门用于指针的运算符,&(取地址运算符 ),*(取值运算符 )
运算符,&‖只能对变量操作,作用是取该变量的地址 。 运算符,*” 用于指针类型的变量操作,作用是取该指针所指内存单元中存储的内容 。 例如:
int a = 3; // 整型变量,初值为 3
int *p = &a; // 指向整型变量的指针,其值等于 a的地址
int b = *p; // 将指针所指的地址中的内容赋值给 b,值为 3。
说明:
(1) 在使用指针变量前,一定要进行初始化或有确定的地址数值 。
(2) 指针变量只能赋以一个指针的值,若给指针变量赋了一个变量的值而不是该变量的地址或者赋了一个常量的值,则系统会以这个值作为地址 。 根据这个,地址,读写的结果将是致命的 。
(3) 两个指针变量进行赋值,必须使这两个指针变量类型是相同 。 否则,结果将是不可预测的 。 例如:
int *pi;
float f = 1.23,*pFloat = &f;
pi=pFloat; // 尽管本身的赋值没有错误,但结果是不可预测的 。
// 因为 (*pi)的值不会等于 1.23,也不会等于 1。
(4) 给指针变量赋值实际上是,间接,地给指针所指向的变量赋值 。
2.7指针和引用例 Ex_CompUsePointer] 输入 a和 b两个整数,按大小顺序输出 。
#include <iostream.h>
void main()
{ int *p1,*p2,*p,a,b;
cout<<‖输入两个整数:,;
cin>>a>>b;
p1 = &a;
p2 = &b;
if ( a<b )
{ p = p1;
p1 = p2;
p2 = p;
}
cout<<‖a = ‖<<a<<‖,b = ‖<<b<<endl;
cout<<‖最大的值是:,<<*p1<<‖,最小的值是:,<<*p2<<"\n";
}
运行情况如下:
输入两个整数,11 28?
a = 11,b = 28
最大的值是,28,最小的值是,11
2.7指针和引用
2.7.3指针和数组数组中所有元素都是依次存储在内存单元中的,每个元素都有相应的地址 。
规定数组名代表数组中第一个元素的地址 。 例如,当有下列的数组定义时:
int a[5];
则 a所表示的地址就是元素 a[0]的地址 。 指针操作中,若定义了下列指针:
int *pi;
则 pi = &a[0];
等价于
pi = a;
通过指针能引用数组元素 。 例如:
*(pi+1) = 1;

a[1] = 1;
是等价的 。 因为 C++规定,pi+1是下一个数组元素地址,这里是元素 a[1]的地址,而 pi+i 就是元素 a[i]的地址 。
指向数组的指针变量实际上也可像数组变量那样使用下标,而数组变量又可像指针变量那样使用指针。例如,pi[i]与 *(pi+i)及 a[i]是等价的,*(a+i)与
*(pi+i) 是等价的。
2.7指针和引用
[例 Ex_ArrayAndPointer] 分析下列程序的输出结果 。
#include <iostream.h>
void main()
{ int a[ ]={5,8,7,6,2,7,3};
int y,*p = &a[1];
y = (*--p)++;
cout<<y<<"\n";
}
语句,y = (*--p)++;‖先运算 --p,也就是 a[0]的地址,(*--p)是元素 a[0]的值,为 5;
再运算,y = (*--p)++;‖,它相当于,y = (*--p); (*--p) = (*--p)+1;‖,故结果为
5。
设有二维数组 a,它有 2 x 3个元素,如下面的定义:
int a[2][3] = {{1,2,5},{7,9,11}};
理解,a是数组名,a数组包含二个元素 a[0],a[1],每一个元素又是一个一维数组,
例如 a[0]有三个元素,a[0][0],a[0][1],a [0][2],可以用一个指针来表示,例如:
int *p1,*p2;
p1 = a[0];
p2 = a[1];
数组名 a代表整个二维数组的首地址,可理解成是指向一维数组的指针的一维数组。
2.7指针和引用
[例 Ex_MultiArrayAndPointer] 分析下列程序的输出结果 。
#include <iostream.h>
void main()
{ int a[3][3]={1,2,3,4,5,6,7,8,9};
int y = 0;
for (int i=0; i<3; i++)
for (int j=0; j<3; j++)
y += (*(a+i))[j];
cout<<y<<"\n";
}
程序中,,y += (*(a+i))[j];‖是理解本程序的关键。事实上,a+i表示 a[i]的地址值,它等价于 &a[i],因而 *(a+i)等价于 a[i],很明显 (*(a+i))[j]就是
a[i][j]。这时,y += (*(a+i))[j];‖语句就是求数组 a中各个元素之和,结果是 45。
2.7指针和引用
2.7.4指针和结构体
[例 Ex_StructPointer] 指针在结构体中的应用 。
#include <iostream.h>
#include <string.h>
struct PERSON
{ int age; // 年龄
char sex; // 性别
float weight; // 体重
char name[25]; // 姓名
};
void main()
{ struct PERSON one;
struct PERSON *p; // 指向 PERSON类型的指针变量
p = &one;
p->age = 32;
p->sex = ‘M‘;
p->weight = (float)80.2;
strcpy(p->name,"LiMing");
cout<<‖姓名:,<<(*p).name<<endl;
……
}
2.7指针和引用
2.7.5多级指针
[例 Ex_MultiPointer] 使用多级指针 。
#include <iostream.h>
void main()
{ int num = 4;
int *pnum = &num;
int **ppnum = &pnum;
cout<<**ppnum<<endl;
**ppnum = 8;
cout<<‖num = ‖<<num<<‖ = ‖<<**ppnum<<endl;
}
运行结果是:
4
num=8=8
ppnum是一个多级 (二级 )指针变量,指向 pnum指针变量的指针,pnum是指向 num整型变量的指针,注意它们之间的层次关系:
(1) *ppnum与 pnum等价,*pnum与 num等价 。
(2) &num与 pnum等价,&pnum与 ppnum等价 。
(3) **ppnum与 num等价,&(&num) 与 ppnum等价 。
2.7指针和引用
2.7.6指针和函数
指针作为函数的参数
[例 Ex_SwapUsePointer] 指针作为函数参数的调用方式 。
#include <iostream.h>
void swap(int *x,int *y);
void main()
{ int a = 7,b = 11;
swap(&a,&b);
cout<<―a = ‖<<a<< ―,b = ‖<<b<<"\n";
}
void swap(int *x,int *y)
{ int temp;
temp = *x; *x = *y; *y = temp;
cout<<"x = "<<*x<<",y = "<<*y<<"\n";
}
传递指针的函数调用实现过程如下:
(1) 函数声明中指明指针参数,即示例中的,void swap(int *x,int *y);‖;
(2) 函数调用的实参中指明变量的地址,即示例中的,swap(&a,&b);‖;
(3) 函数定义中对形参进行间接访问 。 对 *x和 *y的操作,实际上就是访问函数的实参变量 a和 b,通过局部变量 temp的过渡,使变量 a和 b的值被修改 。
2.7指针和引用
返回指针的函数函数可以返回一个指针,指向一个已定义的任一类型的数据 。 返回指针函数格式:
<函数类型 > * <函数名 >( <形式参数表 > ){ <函数体 > }
[例 Ex_PointerRreturn] 返回指针的函数,用来将一个字符串逆序输出 。
#include <iostream.h>
char* flip(char *str)
{ char *p1,*p2,ch;
p1 = p2 = str;
while (*p2 != '\0')
p2++;
p2-- ; // 指向最后一个字符
while (p1<p2)
{ ch = *p2;*p2 = *p1; *p1 = ch; // 交换字符
p1++; p2--;
}
return str;
}
void main()
{ char str[] = "ABCDEFGH";
cout<<flip(str)<<"\n";
}
2.7指针和引用
指向函数的指针每一个函数都有地址 。 指向函数地址的指针称为函数指针 。 函数指针指向内存空间中的某个函数,通过函数指针可以调用相应的函数 。
函数指针的定义如下,<函数类型 >( * <指针名 >)( <参数表 > );
例如,int (*func)(char a,char b);
就是定义的一个函数指针 。 int为函数的返回类型,*表示后面的 func是一个指针变量名 。 该函数具有两个字符型参数 a和 b。
定义了函数指针变量,就可以给它赋值 。 通常,赋值的函数的返回值类型和参数要和函数指针变量相同 。 例如:
int fn1(char a,char b);
int *fn2(char a,char b);
int fn3(int n);
int (*fp1)(char x,char y);
int (*fp2)(int x);
fp1 = fn1 ; // 正确,fn1函数与指针 fp1指向的函数一致
fp1 = fn2 ; // 错误,fn2函数与指针 fp1指向的函数不一致
fp2 = fn3 ; // 正确,fn3函数与指针 fp2指向的函数一致
fp2 = fp1 ; // 错误,两个指针指向的函数不一致
fp2 = fn3(5) ; // 错误,函数赋给函数指针时,不能加括号函数指针变量赋值后,就可以使用指针来调用函数了 。 调用函数的格式如下:
( * <指针名 >)( <实数表 > ); 或 <指针名 >( <实数表 > );
2.7指针和引用
[例 Ex_FuncPointer] 函数指针的使用 。
#include <iostream.h>
double add(double x,double y)
{ return (x+y); }
double mul(double x,double y)
{ return (x*y); }
void op(double(*func)(double,double),double x,double y)
{ cout<<"x = "<<x<<",y = "<<y<<",result = "<<func(x,y)<<"\n";
}
void main()
{ cout<<"使用加法函数,";
op(add,3,7);
cout<<"使用乘法函数,";
op(mul,3,7);
}
运行结果为:
使用加法函数,x = 3,y = 7,result = 10
使用乘法函数,x = 3,y = 7,result = 21
2.7指针和引用
2.7.7带参数的主函数 main()
[例 Ex_Main] 处理命令行参数 。
#include <iostream.h>
void main(int argc,char *argv[])
{ cout<<"这个程序的程序名是,"<<argv[0]<<"\n";
if (argc<=1) cout<<"没有参数 ! ";
else
{ int nCount = 1;
while(nCount < argc)
{cout<<"第 "<<nCount<<"个参数是,"<<argv[nCount]<<"\n";
nCount++; }
}
}
程序编译连接后,将 Ex_Main.exe复制到 C盘,然后切换到 DOS命令提示符进行测试 。
C:\>Ex_Main ab cd E F?
运行结果为:
这个程序的程序名是,Ex_Main
第 1个参数是,ab
第 2个参数是,cd
第 3个参数是,E
第 3个参数是,F
2.7指针和引用
2.7.8new和 delete
运算符 new返回指定类型的一个指针,如分配失败时则返回 0。 例如:
double *p;
p = new double;
系统自动根据 double类型的空间大小开辟一个内存单元,并将地址放在指针 p中 。 运算符 delete操作是释放 new请求到的内存 。 例如:
delete p;
需要注意的是:
(1) 运算符 delete必须用于先前 new分配的有效指针 。 如果使用了未定义的其它任何类型的指针,就会带来严重问题 。
(2) 用 new也可指定分配的内存大小,例如:
(3) new可以为数组分配内存,但当释放时,必须告诉 delete数组有多少个元素 。 例如:
int *p;
p = new int[10]; // 分配整型数组的内存,数组中有 10元素
if ( !p )
{ cout<<‖内存分配失败 !,;
exit(1); // 中断程序执行
}
for (int i=0; i<10; i++) p[i] = i; // 给数组赋值
...
delete [10]p; // 告诉 delete数组有多少个元素
2.7指针和引用
2.7.9引用定义引用类型变量,给一个已定义的变量起一个别名,系统不为引用类型变量分配空间,只是使引用类型变量与其相关联的变量使用同一个内存空间 。
定义引用类型变量的一般格式为:
<类型 > &<引用名 > = <变量名 >或
<类型 > &<引用名 > ( <变量名 >)
其中,变量名必须是一个已定义过的变量 。 例如:
int a = 3;
int &ra = a;
ra就是一个引用,它是变量 a的别名 。 所有对这个引用 ra的操作,实质上就是对被引用对象 a的操作 。
在使用引用时,需要注意的是:
(1)定义引用类型变量时,必须将其初始化 。 而且引用变量类型必须与为它初始化的变量类型相同 。
(2)当引用类型变量的初始化值是常数的,则必须将该引用定义成 const类型 。
(3)不能引用一个数组,这是因为数组是某个数据类型元素的集合,数组名表示该元素集合空间的起始地址,它自己不是一个真正的数据类型 。
(4)可以引用一个结构体 。
(5)引用本身不是一种数据类型,所以没有引用的引用,也没有引用的指针 。
2.7指针和引用
2.7.10 函数的引用传递
[例 Ex_SwapUseReference] 引用作为函数参数的调用方式 。
#include <iostream.h>
void swap(int &x,int &y);
void main()
{ int a(7),b(11);
swap(a,b);
cout<<―a = ‖<<a<< ―,b = ‖<<b<<"\n";
}
void swap(int &x,int &y)
{ int temp;
temp = x; x = y; y = temp;
cout<<"x = "<<x<<",y = "<<y<<"\n";
}
结果是,
x = 11,y = 7
a = 11,b = 7
swap中的 &x和 &y是形参的引用说明。执行 swap(a,b);时,它实际上是将实参 a,b的地址存放到为形参分配的内存空间中,形参的任何操作都会改变相应的实参的数值。
2.7指针和引用引用除了可作为函数的参数外,还可作为函数的返回值 。
[例 Ex_RefReturn] 返回引用的函数的使用 。
#include <iostream.h>
double area;
double &CalArea(double r)
{ area = 3.141593 * r * r;
return area; }
void main()
{ double c = CalArea(5.0);
double &d = CalArea(10.0);
cout<<c<<"\n";
cout<<d<<"\n";
}
运行结果为:
78.5398
314.159
绝对不要返回不在作用域内的变量的引用,变量退出作用域,对它的引用也没有意义了。
2.7指针和引用
2.7.11 简单链表
链表概述
链表中每一个元素称为,结点,,每一个结点都是由数据域和指针域组成的,每个结点中的指针域指向下一个结点 。 图中,head是,头指针,,表示链表的开始,用来指向第一个结点,而最后一个结点的指针域应为 NULL,表示链表的结束 。
链表结构必须利用指针变量才能实现 。
链表中的每个结点可以有若干个数据和若干个指针 。 结点中只有一个指针的链表称之为单链表,是最简单的链表结构 。 例如:
struct NODE
{ int data;
NODE *next;
};
*next是指针域,用来指向该结点的下一个结点,data是一个整型变量,用来存放结点中的数据;当然,data可以是任何数据类型,包括结构类型 。
然后,定义所需要的指针变量:
NODE *head; // 定义头指针
NODE *p; // 定义一个结点
(*p).data或 p->data用来指向结点 p的数据域,(*p).next或 p->next用来指向结点 p的下一个结点。
a1 a2 a3 a4 ^a5
头指针
head
图 2.2 简单链表的结构形式
2.7指针和引用
链表的输出由于链表中的各个结点是由指针链接在一起的,因此只要知道链表的头指针
(即 head),那么就可以定义一个指针 p,先指向第一个结点,输出 p所指向的结点数据,然后根据结点 p找到下一个结点,再输出,直到链表的最后一个结点 (指针为空 )。 程序如下:
void OutputList(NODE *head)
{
NODE *current = head;
while (current != NULL)
{
cout<<current->data<<‖ ‖;
current=current->next;
}
cout<<"\n";
}
2.7指针和引用
链表的插入如果要在链表中的结点 a之前插入新结点 b,则需要考虑下列几种情况:
(1)插入前链表是一个空表,这时插入新结点 b后,链表如图 2.3(a)所示,实线表示插入前的指针,虚线为插入后的指针 (下同 )。
(2)若 a是链表的第一结点,则插入后,结点 b为第一个结点,如图 2.3(b)。
(3)若链表中存在 a,且不是第一个结点,则首先要找出 a的上一个结点 ak,令 ak
的指针域指向 b,令 b的指针域指向 a,即可完成插入,如图 2.3(c)所示 。
(4)若链表中不存在 a,则先找到链表的最后一个结点 an,并令 an的指针域指向结点 b,而 b结点的指针域为空 。 如图 2.3(d)所示 。
图 2.3 链表的插入
bhead
( a )
b
head a,..
( b )
b
a,..
( c )
..,ak
b
( d )
..,?an
2.7指针和引用
void InsertList(NODE **head,int aData,int bData)
{ NODE *p,*a,*b;
b = (NODE *)new(NODE); // 分配一个新结点 b
b->data = bData;
p = *head;
if (p == NULL) // 若链表是空,符合情况 (1)
{ *head = b; // 将 b作为第一个结点
b->next = NULL; }
else if (p->data == aData)
{ b->next = p;
*head = b; }
else
{ while (p->data!=aData && p->next!=NULL)
{ a = p;
p = p->next; }
if (p->data==aData)
{ a->next = b;
b->next = p; }
else
// 没有结点 a,,符合情况 (4)
{ p->next = b;
b->next = NULL; } }
}
2.7指针和引用
链表的删除如果要在链表中删除结点 a,并释放被删除的结点所占的存储空间,则需要考虑下列几种情况:
(1)若要删除的结点 a是第一个结点,则把 head指向 a的下一个结点 。 如图 。
(2) 若要删除的结点 a存在于链表中,但不是第一个结点,则应使 a的上一个结点 ak的指针域指向 a的下一个结点 ak+1。 如图 2.4(b)所示 。
(3) 空表或要删除的结点 a不存在,则不作任何改变 。
图 2.4 链表的删除
( a )
head a a1,..
( b )
a ak+1,....,ak
2.7指针和引用
void DeleteList(NODE **head,int aData) // 设 aData是结点 a中的数据
{ NODE *p,*a;
p = *head;
if (p==NULL) // 若是空表,符合情况 (3)
return;
if (p->data==aData) // 若 a是第一个结点,符合情况 (1)
{ *head = p->next;
delete p;
}
else
{ while (p->data!=aData&&p->next!=NULL) // 查找结点 a
{ a = p;
p = p->next;
}
if (p->data==aData) // 有结点 a,符合情况 (2)
{ a->next = p->next;
delete p;
}
}
}
2.7指针和引用
[例 Ex_SimpleList] 使用简单链表 。
#include <iostream.h>
struct NODE
{ int data;
NODE *next; };
NODE *head = NULL;
int data[6] = {25,41,17,98,5,67};
void InsertList(NODE **head,int aData,int bData);
void DeleteList(NODE **head,int aData);
void OutputList(NODE *head);
void main()
{ for (int i=0; i<6; i++)
InsertList(&head,data[0],data[i]);
OutputList(head);
DeleteList(&head,98);
DeleteList(&head,41);
OutputList(head);
}
结果如下:
41 17 98 5 67 25
17 5 67 25
作业
1。 P362~368:31~62题偶数题
2。 P388~396:预习实验 3,4