1
? 函数原型与函数特征
? 数据声明与作用域
? 类、对象、构造函数与析构函数
? 输入 /输出
? 函数、参数传递与函数返回值
? 函数名重载与操作符重载
? 动态存储分配
? 友元函数与内联函数
? 结构、联合与类
? 模板类
2
函数原型
? 下面的程序给出了典型的 C 程序结构,它是
? Hello,world”程序的变型。
? 这个程序由三个文件组成:
/* File,hello.h */
char *hello( );
/* File,hello.c */
# include <stdio.h> /*包括 sprintf ( )的原型 */
# include <stdlib.h> /*包括 malloc( )的原型 */
# include <string.h> /*包括 strlen( )的原型 */
# include "hello.h" /*包括 hello( )的原型 */
3
char *hello(name)
char *name;
{
char *value; /*返回串 "Hello,name.",*/
value = (char *)(malloc(9+strlen(name));
sprintf (value,"Hello,%s.",name);
return value;
}
/* File,main.c */
# include <stdio.h> /*包括 printf ( )的原型 */
# include "hello.h" /*包括 hello( )的原型 */
4
main(argc,argv)
int argc;
char *argv[ ];
{
printf("%s",hello("world"));
}
? 头文件名字的后缀用 ?,h”表示,程序文件
名字的后缀用 ?,c”表示。
? hello.h:包含 hello函数的原型。 main函数
可通过 ? # include”定向到该原型的定义文
件,取得对原型的访问性。
5
? hello.c:这是 hello函数的定义文件。它通过
一个 string类型的形式参数 接受需要打印的
串,返回一个 string类型的值 作为打印串。
返回类型必须与在 #include定向的 ?,h”文件
中所给出的原型的类型匹配。
? main.c:这是打印 ? Hello,world”的主程序,
它构造和返回一个欢迎词字符串,其结果通
过函数 printf打印出来。
? C 把函数和数据定义放在后缀为 ?,c”的代
码文件中。在各代码文件中使用后缀为 ?,h”
的 include文件,定义 对其它各模块的调用接
口 。
6
C++的函数特征
? 特征是函数参数表的描述 。
? 利用特征信息可进行严格的类型检查。它
允许编译器检验实际参数的数目是否正确,
对应的形参和实参是否相容,函数返回的
类型与函数调用的环境是否相容。
? 它克服了在许多 C 程序的开发中,由于 在
C 原型中没有定义参数的类型和数量,而
造成的 实参和形参之间不匹配, 函数返回
类型与使用之间不匹配 等许多缺陷。
7
? 现用 C++ 语句改写前面的 C 程序。
? C++ 程序在 ?,h”文件中对 hello( ) 使用了
函数特征。对于 hello( ) 的原型,
? 不要求形式参数的名字出现在特征中
? 参数的数目和类型,以及返回类型,都
完整地在函数说明中定义
? C++允许在函数说明时,在括号内直接声明
形式参数的类型。
? 除了标准注释分界符 (/*...*/) 外,C++还接
受以 // 开始,直到该行末尾的注释。
8
/* File,hello.h */
char *hello(char *);
/* File,hello.cpp */
# include <stdio.h> //包含函数 sprintf( )的原型
# include <string.h> //包含函数 strlen( )的原型
# include,hello.h” //包含函数 hello( )的原型
char *hello(char *name) {
char *value = new char [9 + strlen(name)];
sprintf (value,"Hello,%s.",name);
return value;
}
9
/* File,main.cpp */
# include <iostream.h> //说明输出流对象 cout
# include "hello.h" //包含函数 hello( )的原型
main(int argc,char *argv[ ]) {
cout << hello("world");
}
10
C++的数据声明
? C++的数据声明 将数据名与数据类型联系起
来 。其主要形式有:
? 常数值,如 25,13.4,“value is”,它们的
内容保持不变。
? 变量,数据类型的实例,在程序执行时可
以改变其内容。
? 常量,数据声明时在变量名前冠以保留字
const,如 const int MAX = 500,可定义 一
个常量。其内容在声明时给定,在声明它
的程序运行时内容再赋值无效。
11
? 枚举,是声明一个整型常数序列的方式。
例如,在程序开头做如下声明
enum Boolean { FALSE,TRUE }
则建立一个 Boolean类型。 FALSE,TRUE
都是 Boolean类型整型常数,默认值 0 和 1。
? 指针,存放对象的存储地址,例如
int i = 5;
int *np; //np为一个指向整型量的指针
np = &i; //把整型变量 i 的地址赋给它
//np 成为指向整型变量 i 的指针
int k = *np; //k中存入 np所指地址 i的内容
12
? 引用,它用来给一个对象提供一个替代的
名字 。 例如
int i = 5;
int& j = i;
i = 7;
printf (“i = %d,j = %d”,i,j );
此时,j 是一个引用类型,它代表 i 的一个
替代名 。 当 i 的值改变时,j 的值也跟着
改变 。 当 printf 语句执行后,打印出的 i
和 j 的值都是 7。
13
C++的作用域
? 在 C++中,每个变量都有一个作用域。区分
一个变量时要同时考虑变量名及其作用域。
? 在函数定义中声明的变量,仅能在该函数内
部有效使用
? 在类定义中声明的变量,仅能在该类内部有
效使用
? 在一个段中声明的名字,也仅能在该段及其
子段中有效使用。
14
? 在整个程序各处都能访问的变量叫做 全局
变量 。 如果一个全局变量 在文件 1中声明,
在文件 2中使用, 那么在文件 2中必须使用
保留字 extern对该变量进行声明 。
? 如果在构成一个程序的两个文件中分别声
明了两个同名的全局变量, 这两个变量分
别代表两个不同实体, 此时需在两个文件
中分别使用保留字 static对变量进行声明 。
? 如果一个段中的局部变量与一个全局变量
同名, 且还要在此段中使用该全局变量,
此时需利用域操作符,:访问该全局变量 。
15
C++的类
? C++的核心部分是类的定义。 类定义体现
了抽象数据类型的思想 。为达到信息隐蔽
的原则。规定对类的成员有三级存取:
? 共有 (public)
? 私有 (private)
? 保护 (protected)
? 在 public 域中声明的数据成员和函数成员
(成员函数 ),程序中其它类的对象或操作
都能请求该类的对象执行它们,因此 这些
数据成员和成员函数构成类的界面部分 。
16
? 在 private域和 protected域中声明的数据成
员和成员函数构成类的私有部分,只能由
该类的对象和成员函数,以及声明为友元
(friend)的函数或类的对象才能访问它们。
? 在 protected域中声明的数据成员和成员函
数,还允许该类的派生类访问它们;
? 在 private域中声明的数据成员和成员函数,
则不允许该类的派生类访问它们。
? 下面给出一个 point 类的声明。 Point 类中
点的表示由两个整数变量 x,y 组成。类的
用户不能直接访问它们。
17
#ifndef POINT_H
#define POINT_H //In the header file point.h
class Point { //类定义
private,//私有域
int x; //数据成员:点坐标
int y;
public,//共有域
Point ( int,int ); //构造函数
Point ( Point & ); //复制构造函数
~Point ( ); //析构函数
int get_x ( ); //取 x坐标
int get_y ( ); //取 y坐标
18
Point operator + ( point ); //点加点
Point operator / ( int ); //点除整数
Point operator * ( int ); //点乘整数
int operator > ( Point ); //点比较
int operator < ( Point ); //点比较
int operator == ( Point& ); //点比较
friend istream& operator >>
( istream&,Point& ); //输入友元函数
friend ostream& operator <<
( ostream&,Point& ); //输出友元函数
};
#endif
19
? 为了存取一个点的 x,y分量,类提供了两个
函数 get_x,get_y。这样可用 private域来保
护数据的表示,防止类的用户直接使用数
据的内部表示来编写代码,通过使用存取
函数来操作数据来维持类的抽象性。
? private是声明默认的存取级别。
? 系统开发时,把类的声明放在头文件中,成
员函数的实现放在源程序文件中。在源程
序文件中函数的实现通过作用域设定命令
?,:”而被归属到某一个类。
20
例,对于 Point类的输出 友元 函数的实现可以
在源程序文件中给出,形为:
ostream & operator << (ostream& strm,
Point p) {
return strm << "(" << p.get_x ( )
<< "," << p.get_y () << ")";
}
这个函数把点 p的值以? x,y”的格式送到 strm
指明的输出流中去。
21
C++中的对象
? 建立类的对象 (亦称为 实例化 )时采用的方式
类似于定义 C变量的方式,可以 自动地,或
静态地,或通过 动态分配 来建立。建立一个
Point类实例的语句是:
? Point p (6,3); 自动地
? Point q; 自动地
? static Point s (3,4); 静态地
? Point *t = new Point(1,1); 通过动态分配
? 对象 p,q和 s都是 Point类的对象。
22
构造函数
? 当遇到以上的每一个语句时,将隐式地调用
一个构造 (constructor)函数,这个构造函数
属于一个与它同名的类。
? 在 Point类的定义中声明了两个构造函数,构
造函数的参数用于初始化表达式的值。
? 例如,当使用声明 Point p(6,3) 建立 Point 类
的对象 p 时,调用了构造函数 Point (int,int);
通过以下函数定义,将其 x,y分量设定为 6,3:
Point,,Point (int a,int b) { x = a; y = b; }
Point,,Point (int a,int b), x(a),y(b) { }
23
? 构造函数可以定义默认值。例如
Point,,Point ( int a = 0,int b = 0 )
,x(a),y(b) { }
? 当定义实例时给定初始值,则该实例以给
定初始值来初始化其数据成员
Point p(6,3);
则有 x = a = 6,y = b = 3
? 当定义实例时未给出初始值。则该实例
以默认值来初始化其数据成员
Point q;
则有 x = a = 0,y = b = 0
24
析构函数
? 当要放弃对象时,需隐式地调用另一个函数,
叫做 析构 (destructor)函数,它属于名字相同
的类,但在名字前面加上了一个?~?。例
如
~ Point。
? 为一个类可定义几个构造函数,但只能定义
一个析构函数。当控制要退出自动变量的作
用域时,或当通过 delete 命令释放一个动态
分配的变量时,就要调用析构函数。当 main
函数执行结束时,将释放静态声明的变量。
? 一个析构函数用于 在删除一个类的对象 时做
清除工作。
25
C++的输入 /输出
? 在 C++中执行输入 /输出操作,需用 #include
预处理指令包括一个 <iostream.h>头文件。
用它可支持 C++的流 (stream)操作。
? ?流?是个简单的字符序列。在 C++中有两
个预定义的类 istream和 ostream,它们定义
了输入流和输出流。
? 基本输入 /输出方式:
? 键盘屏幕输入 /输出
? 文件输入 /输出
26
键盘屏幕输入 /输出
? 在 C中有用于定向到键盘输入设备、屏幕输
出设备和错误文件的命令 stdin,stdout和
stderr。
? 在 C++中用 cin,cout和 cerr来定义键盘输入
类、屏幕输出类和错误信息输出类。
? 操作符 << 用于写出类 ostream的一个对象,
对于一系列输出对象,可用 <<分开。
? 操作符 >> 用于读入类 istream的一个对象 。
27
? 在下面程序中使用了流 cin >>,相继从标准
输入设备上输入两个整型变量 a和 b,并将它
们打印到标准输出设备上。
? 在输出语句中最后输出的 endl是 C++的 I/O操
作符,它的用途是输出一个换行符并清空流。
#include <iostream.h>
void main ( ) {
int a,b;
cin >> a >> b;
cout <<,a, " << n << "f, " << f << endl;
}
28
? C++中的输入 /输出可以是自由格式,程序
员不需要使用格式化符号来指定输入 /输出
项的类型和顺序。
? 与其它 C++操作符一样,输入 /输出操作符
能够被重载。
29
文件输入 /输出
? C++中的文件输入 /输出方式如下所示。
? 在程序开头必须用预处理指令 #include包含
头文件 <fstream.h>,它定义了类 ifstream、
ofstream和 fstream。
? 要创建一个 输入流,必须声明它为 ifstream
类的实例
? 要创建一个 输出流,必须声明它为 ofstream
类的实例
? 执行输入和输出操作的流 必须声明它为
fstream类的实例
30
#include <fstream.h>
#include <iostream.h>
#include <stdlib.h>
void main ( ) {
ifstream inFile; //inFile为输入流对象
ofstream outFile; //outFile为输出流对象
outFile.open ( "my.dat",ios,,out );
//建立输出文件 ?my.dat?
char univ[ ] = ?Tsinghua?,name[10];
int course = 2401,number;
outFile << univ << endl; //输出到 ?my.dat?
outFile << course << endl;
31
inFile.open("my.dat",ios::out | ios::nocreate);
//打开输入文件 ?my.dat?
if ( !inFile ) {
cerr <<,不能打开 my.dat” << endl;
exit(1);
}
char c;
inFile >> name >> c >> number;
outFile << "name," << name << endl;
outFile << "number," << number << endl;
}
32
? ifstream类,ofstream类和 fstream类都是从
istream类和 ostream类派生出来的,而类
istream和 ostream又是从类 ios派生出来的,
因此这些类都可使用类 ios的所有运算。
? 在调用打开文件函数 open( )时,函数参数表
包括 实际文件名 和 数据流动的方向,函数
返回 文件的开始地址 。系统在存储文件时,
在其末尾添加有文件结束标记。
? 如果文件未被打开,则 outFile = 0; 如果文件
被成功地打开,则它将代替 cout,将输出引
导到文件 ?my.dat ?中。
33
? 在文件打开的操作中, 指定的文件模式有
以下几种:
? iso::app:把所有对文件的输出添加在文
件尾 。 它只用于输出文件 。
? iso::binary:文件以二进制方式打开 。 此
项缺省时文件以文本方式打开 。
? iso::nocreate:若文件不存在则将导致打
开操作失败 。
? iso::out:表明该文件用于输出 。 此项可
缺省 。
? iso::in:表明该文件用于输入 。 此项可缺
省 。
34
C++中的函数
? 在 C++中有两种函数:
常规函数 和 成员函数
? 不论哪种函数,其定义都包括 4 个部分, 函
数名, 形式参数表, 返回类型 和 函数体 。
? 函数的使用者通过函数名来调用该函数;
调用时把实际参数传送给形式参数表作为
数据的输入;通过函数体中的处理程序实
现该函数的功能;最后得到返回值作为输
出。
35
? 下面给出一个函数的例子 。 max是函数名,
int a 和 int b 是形式参数表,函数名前面的
int 是返回类型,在花括号内括起来的是函
数体,它给出了函数操作的实现 。
int max ( int a,int b ) {
//函数返回 a 与 b 中的大值
if (a > b) return a;
else return b;
}
? 在 C++中所有函数都有一个返回值, 或者返
回计算结果, 或者返回执行状态 。
36
? 如果函数不需要返回值,可使用 void 来表
示它的返回类型。函数的返回值通过函数
体中的 return 语句返回。
? return 的作用是返回一个 与返回类型相同
类型 的值,并中止函数的执行。
? 函数返回时可以通过引用方式,参看下面
程序,此时在函数类型后面加上一 个 ? &”。
#include <iostream.h>
char& replace(int m);
char s[80] =,Hello There”;
37
main ( ) {
replace(5) = ?x?; cout << s;
//用 x代替 Hello后面的空格
}
char& replace ( int m ) {
return s[m];
}
? 函数 replace( )的返回类型说明为返回一个字
符的引用类型,在函数执行时返回 参数 m 指
定的 s数组元素 的值。 main ( )执行时把字符
? x” 送给 s[5]。
38
C++中的参数传递
? 函数调用时传送给形参表的实参必须与形
参在类型、个数、顺序上保持一致。
? 参数传递有两种方式。一种是 传值,这是
缺省的参数传递方式 ; 一种是 引用类型 。
? 使用 传值 方式时,把实参的值传送给函数
局部工作区相应的副本中,函数使用这个
副本执行必要的功能。这样,函数修改的
是副本的值,实参的值不变 。
39
? 使用 引用类型 方式传递时,需将形参声明为
引用类型, 即在参数名前加一个 ? &”。 参
看下面的程序示例 。
? 当一个实参与一个引用型形参结合时, 被
传递的不是实参的值, 而是实参的地址,
函数通过地址存取被引用的实参 。 函数执
行后实参的值将发生改变 。
? 当一个函数的返回值多于一个时,其中一个
可由 return语句返回,其它返回值可使用引
用型参数返回 。
#include <iostream.h>
void swap (int& i,int& j);
40
main ( ) {
int a = 1,b =2;
cout << "a and b," << a << " " << b << "\n";
swap ( a,b );
//调用时实际参数不需要加 &
cout << "a and b," << a << " " << b << "\n";
}
void swap ( int& i,int& j ) {
//对换 i 与 j 的内容
int t = j; j = i; i = t; //不需要加 *
}
41
? 一种特殊的引用调用方式叫做 常值引用,其
格式为 const Type& a,其中 Type为参数的
数据类型 。 在函数体中不能修改常值参数 。
? 一种特殊情况是 数组参数的传递 。 数组作
为形参可按传值方式声明, 但实际采用引
用方式传递, 传递的是数组第一个元素的
地址 。 在函数体内对形参的数组所做的任
何改变都将反映到作为实参的数组中 。
? 此外, 在参数表中一般按形如 int R[ ] 的形
式声明, 因此需要显式地声明数组的大小 。
42
? 若传送的 值参是一个对象 (作为类的实例 )
时,在函数中就创建了该对象的一个副本。
在创建这个副本时不调用该对象的构造函
数,但在函数结束前要调用该副本的析构
函数撤消这个副本。
? 若 采用引用方式传递对象,在函数中不创
建该对象的副本,也不存在最后撤消副本
的问题。但是,通过引用传递的是对象时,
函数对对象的改变将影响调用的对象 。
43
成员函数的返回值
? 当成员函数的返回值为 传值方式 时,允许改
变该对象的私有数据成员。
? 当成员函数的返回值为 常值传值方式 时,需
加 const 标识,该对象的私有成员不能改变。
? 当成员函数的返回值为 引用方式 时,该成员
函数的返回值应 是一个已存在变量 (或对象 )
的别名 。当该成员函数被重新赋值时,其对
应变量 (或对象 )的值将改变。
? 当成员函数的返回值为 常值引用方式 时,其
返回值与引用方式的成员函数返回值类同 。
但该成员函数不能改变该对象的私有成员。
44
? 当成员函数返回值为 常值传值方式 或 常值引
用方式 时,const 标识符一般放在最后。
#include<iostream.h>
class Temperature {
private:
float highTemp,lowTemp; //数据成员
public:
Temperature(int hi,int lo) //构造函数
{ highTemp = hi; lowTemp = lo; }
void UpdateTemp(float temp); //传值返回
float GetHighTemp( ) const; //常值返回
45
float GetLowTemp( ) const; //常值传值返回
};
void Temperature,,UpdateTemp(float temp) {
if (temp > highTemp) highTemp = temp;
if (temp < LowTemp) LowTemp = temp;
}
float Temperature,,GetHighTemp( ) const {
return highTemp;
}
float Temperature,,GetHighTemp( ) const {
return highTemp;
}
46
C++中的函数名重载
? 函数名重载允许 C++程序中多个函数取相同
的函数名,但其形参或返回类型可以不同。
? 例如,C标准函数库中有 3个标准函数 abs( )、
labs( )和 fabs( ),分别计算整型数、长整型数
和双精度型数的绝对值。在 C中因处理的数
据类型不同,必须取不同的函数名。在 C++
中,可以把这 3 个函数都命名为 abs ( ):
int abs ( int );
long abs ( long );
double abs ( double );
47
C++的操作符重载
? C++提供了一种能力,可 用同一个名字定义
多个函数,这种能力叫做 操作符重载 。
? 例如,可以命名一个函数, clear(* int),它将
一个整数清零。还可以再命名另一个函数
clear(int[ ]),它把一个整数数组清零。
? 编译器能够比较具有同名的函数的特征,通
过识别 实参的数目 和每个 实参的类型,来标
识使用于一个特定调用的是哪一个版本的
abs( )。
48
? 在 C中,必须使用名字 clearIntArray( ) 和
clearInt( ) 来区分这两个函数。在 C++中,
编译器能够比较同名函数的特征,通过识
别实参的数目和每个实参的类型,来标识
一个特定调用中用的是哪一个版本的 clear。
? 为了支持面向对象,C++ 提供了 双目重载
操作符 (如‘+’和‘<’ )。这种操作可
使得程序更可读、写得更自然。
? 例如,可定义 ?点 (Point)”的运算,像
? p1+p2,把两个点 (x1,y1)和 (x2,y2)相加
成一个点 (x1+x2,y1+y2)。
49
? p1<p2,两个点 p1和 p2的 ?小于? 关系,
表示 p1比 p2更靠近原点 (0,0)。
? p1/ i,一个点 p= (x,y) 除以一个整数 i
的除法 (x/i,y/i)。
? 可以按以下方式使用重载操作:
Point operator + ( Point p );
Point operator / (int i);
int operator < (Point p);
? 使用这些新的操作的表达式如,
Point midPoint = (point1+ point2) / 2;
或 if ( midPoint < referencePoint ),..
50
? 注意, 每一个这样的操作符在调用时可看成
是该操作符左边对象的成员函数。
? 例如,(point1+point2) 实际上是一个消息。
由类 Point的 实例 point1调用成员函数 ? +”,
该对象的属性确定第一个操作数的值。
? 函数参数表中指定的 Point的 实例 point2的
属性确定第二操作数的值。
? 这种重载能力允许像使用内建类型 (如 int,
float) 那样来使用用户自定义类型。与在不
允许重载操作的语言中相同的语句比,这样
可以改善程序的可读性。
51
C++的动态存储分配
? 在 C程序中,使用一个函数 malloc,为程序
分配它所需要的空间,一旦程序执行结束
需要返回到它的调用者时,必须释放这个
空间。
? C++为动态存储分配提供了两个新的命令:
new和 delete。它们可用于取代 C 中的库函
数 malloc和 free。
? 在 C++中没有 无用单元收集,使用 new分配
的存储必须显式地使用 delete释放。
52
? 操作 new要求以 被建立对象的类型 做为参
数,并 返回一个 指向新分配空间 的指针 。
? 作为对比,在 C中,函数 malloc要求它的调用
者提供所需存储空间的数量。
? 例如,为动态分配一个整数或一个点,可编
写如下语句:
int *ip = new int;
或 Point *p = new Point;
? 它们组成了指针变量的声明 (如 * name) 和
动态存储分配 ( new 类型 )。
53
? Delete 命令必须能够知道 new 分配了多少
存储。当要释放动态分配的数组时,必须
告诉 delete 该数组中包含的元素个数。
? 例如,如果已建立下列有 100个点的数组:
Point* p = new Point[100];
? 则通过以下命令释放该存储,
delete [100]p;
? 若遗漏了 ? [100]”,则将只释放 p 所指示的
第一个元素,将?失去?其它 99个点所占据
空间,以致不能再复用它们。若使用时元素
下标超出 100,程序将会出错且结果不可预
测。
54
友元 (friend)函数
? 在类的声明中可使用保留字 friend 定义友
元函数。
? 友元函数实际上并不是这个类的成员函数,
它可以是一个常规函数,也可以是另一个
类的成员函数。如果想通过这种函数存取
类的私有成员和保护成员,则必须在类的
声明中给出函数的原型,并在该函数原型
前面加上一个 friend。
? 参看 Point类的声明,有两个重载操作符 <<
与 >>,它们都被声明为友元函数。
55
内联 (inline)函数
? 在函数定义前加上一个 inline前缀就成为内
联函数。编译程序在编译时将会把这个函
数语句直接插入到普通代码中,因而减少
了与函数调用和参数传递有关的系统开销。
? 直接插入代码所需要的空间比不直接插入
的调用方式所需要的空间要多,这取决于
函数定义的大小。
? 除了加上 inline保留字外,内联函数的定义
与其它任何函数定义的方式一样。
inline Point operator + (Point p);
56
结构 (struct)与类
? C++ 扩充了 C 中结构 (struct) 型的功用,加
进成员函数以说明一个类 (class)。在 C++
中 struct 与 class 的区别在于,
? 在 struct 中,默认的访问级别是 public。若
在 struct 内部自始至终缺省访问级别,则所
有的成员都是共有的。
? 在 class 中,缺省的访问级别是 private。
? 除此之外,struct 与 class 是等价的。例如,
下面给出定义矩形类的三种等价的类声明。
57
class Rectangle {
int x1,y1,h,w;
public:
Rectangle ( );
~Rectangle ( );
int GetX ( );
int GetY ( );
void SetX (int x);
void SetY (int y);
int GetHeight ( );
int GetWidth ( );
…………
}
58
struct Rectangle {
Rectangle ( );
~Rectangle ( );
int GetX ( );
int GetY ( );
void SetX (int x);
void SetY (int y);
int GetHeight ( );
int GetWidth ( );
…………
private:
int x1,y1,h,w;
}
59
联合 (Union)与类
? 与结构一样,用 Union也可以定义类。
? 在 C++中,Union可包含 函数 和 变量,还可包
含 构造函数 和 析构函数 。
? C++的 Union保留了所有 C的特性,主要是让
所有的数据成员共享相同的存储地址。
? 与 class和 struct相比,Union可节省存储。与
结构相似,Union中默认存取级别是 public。
60
模板 (template)
定义
适合 多种数据类型 的 类定义 或 算法,
在特定环境下通过简单地代换,变成
针对具体某种数据类型 的 类定义 或 算
法
61
用模板定义用于排序的数据表类
#include <iostream.h>
template <class Type>
class dataList {
private:
Type *Element;
int ArraySize;
void Swap (int m1,int m2);
int MaxKey (int low,int high);
62
public:
dataList (int size = 10), ArraySize (size),
Element (new Type [Size]) { }
~dataList ( ) {delete [ ] Element;}
void Sort ( );
friend ostream& operator << (ostream&
outStream,datalist<Type>& outList);
friend istream& operator >> (istream&
inStream,datalist<Type>& inList);
}
63
类中所有操作作为模板函数的实现
#include,datalist.h”
template <class Type> void dataList
<Type>,,Swap (int m1,int m2) {
//交换由 m1,m2为下标的数组元素的值
Type temp = Element [m1];
Element [m1] = Element [m2];
Element [m2] = temp;
}
64
template <class Type>
int dataList<Type>::
MaxKey (int low,int high) {
//查找数组 Element[low]到 Element[high]
//中的最大值,函数返回其位置
int max = low;
for (int k = low+1,k <= high,k++)
if ( Element[max] < Element[k] )
max = k;
return max;
}
65
template <class Type> ostream&
operator<< (ostream& OutStream,
dataList<Type> OutList) {
OutStream <<,数组内容, \n”;
for (int i = 0; i < OutList.ArraySize; i++)
OutStream << OutList.Element[i] << ? ?;
OutStream << endl;
OuStream <<,数组当前大小,, <<
OutList.ArraySize << endl;
return OutStream;
}
66
template <class Type>
istream& operator >> (istream& InStream,
dataList<Type> InList) {
cout <<,录入数组当前大小,,;
Instream >> InList.ArraySize;
cout <<,录入数组元素值, \n”;
for (int i = 0; i < InList.ArraySize; i++) {
cout <<,元素, << i <<,:” ;
InStream >> InList.Element[i];
}
return InStream;
}
67
template <class Type>
void dataList<Type>::Sort ( ) {
//按非递减顺序对 ArraySize个关键码
//Element[0]到 Element[ArraySize-1]排序
for ( int i = ArraySize -1; i > 0; i-- ) {
int j = MaxKey (0,i);
if ( j != i ) swap (j,i);
}
}
68
使用模板的选择排序算法的主函数
#include,selecttm.h”
const int SIZE = 10;
int main ( ) {
dataList <int> TestList (SIZE);
cin >> TestList;
cout << TestList << endl;
TestList.Sort ( );
cout << TestList << endl;
return 0;
}
? 函数原型与函数特征
? 数据声明与作用域
? 类、对象、构造函数与析构函数
? 输入 /输出
? 函数、参数传递与函数返回值
? 函数名重载与操作符重载
? 动态存储分配
? 友元函数与内联函数
? 结构、联合与类
? 模板类
2
函数原型
? 下面的程序给出了典型的 C 程序结构,它是
? Hello,world”程序的变型。
? 这个程序由三个文件组成:
/* File,hello.h */
char *hello( );
/* File,hello.c */
# include <stdio.h> /*包括 sprintf ( )的原型 */
# include <stdlib.h> /*包括 malloc( )的原型 */
# include <string.h> /*包括 strlen( )的原型 */
# include "hello.h" /*包括 hello( )的原型 */
3
char *hello(name)
char *name;
{
char *value; /*返回串 "Hello,name.",*/
value = (char *)(malloc(9+strlen(name));
sprintf (value,"Hello,%s.",name);
return value;
}
/* File,main.c */
# include <stdio.h> /*包括 printf ( )的原型 */
# include "hello.h" /*包括 hello( )的原型 */
4
main(argc,argv)
int argc;
char *argv[ ];
{
printf("%s",hello("world"));
}
? 头文件名字的后缀用 ?,h”表示,程序文件
名字的后缀用 ?,c”表示。
? hello.h:包含 hello函数的原型。 main函数
可通过 ? # include”定向到该原型的定义文
件,取得对原型的访问性。
5
? hello.c:这是 hello函数的定义文件。它通过
一个 string类型的形式参数 接受需要打印的
串,返回一个 string类型的值 作为打印串。
返回类型必须与在 #include定向的 ?,h”文件
中所给出的原型的类型匹配。
? main.c:这是打印 ? Hello,world”的主程序,
它构造和返回一个欢迎词字符串,其结果通
过函数 printf打印出来。
? C 把函数和数据定义放在后缀为 ?,c”的代
码文件中。在各代码文件中使用后缀为 ?,h”
的 include文件,定义 对其它各模块的调用接
口 。
6
C++的函数特征
? 特征是函数参数表的描述 。
? 利用特征信息可进行严格的类型检查。它
允许编译器检验实际参数的数目是否正确,
对应的形参和实参是否相容,函数返回的
类型与函数调用的环境是否相容。
? 它克服了在许多 C 程序的开发中,由于 在
C 原型中没有定义参数的类型和数量,而
造成的 实参和形参之间不匹配, 函数返回
类型与使用之间不匹配 等许多缺陷。
7
? 现用 C++ 语句改写前面的 C 程序。
? C++ 程序在 ?,h”文件中对 hello( ) 使用了
函数特征。对于 hello( ) 的原型,
? 不要求形式参数的名字出现在特征中
? 参数的数目和类型,以及返回类型,都
完整地在函数说明中定义
? C++允许在函数说明时,在括号内直接声明
形式参数的类型。
? 除了标准注释分界符 (/*...*/) 外,C++还接
受以 // 开始,直到该行末尾的注释。
8
/* File,hello.h */
char *hello(char *);
/* File,hello.cpp */
# include <stdio.h> //包含函数 sprintf( )的原型
# include <string.h> //包含函数 strlen( )的原型
# include,hello.h” //包含函数 hello( )的原型
char *hello(char *name) {
char *value = new char [9 + strlen(name)];
sprintf (value,"Hello,%s.",name);
return value;
}
9
/* File,main.cpp */
# include <iostream.h> //说明输出流对象 cout
# include "hello.h" //包含函数 hello( )的原型
main(int argc,char *argv[ ]) {
cout << hello("world");
}
10
C++的数据声明
? C++的数据声明 将数据名与数据类型联系起
来 。其主要形式有:
? 常数值,如 25,13.4,“value is”,它们的
内容保持不变。
? 变量,数据类型的实例,在程序执行时可
以改变其内容。
? 常量,数据声明时在变量名前冠以保留字
const,如 const int MAX = 500,可定义 一
个常量。其内容在声明时给定,在声明它
的程序运行时内容再赋值无效。
11
? 枚举,是声明一个整型常数序列的方式。
例如,在程序开头做如下声明
enum Boolean { FALSE,TRUE }
则建立一个 Boolean类型。 FALSE,TRUE
都是 Boolean类型整型常数,默认值 0 和 1。
? 指针,存放对象的存储地址,例如
int i = 5;
int *np; //np为一个指向整型量的指针
np = &i; //把整型变量 i 的地址赋给它
//np 成为指向整型变量 i 的指针
int k = *np; //k中存入 np所指地址 i的内容
12
? 引用,它用来给一个对象提供一个替代的
名字 。 例如
int i = 5;
int& j = i;
i = 7;
printf (“i = %d,j = %d”,i,j );
此时,j 是一个引用类型,它代表 i 的一个
替代名 。 当 i 的值改变时,j 的值也跟着
改变 。 当 printf 语句执行后,打印出的 i
和 j 的值都是 7。
13
C++的作用域
? 在 C++中,每个变量都有一个作用域。区分
一个变量时要同时考虑变量名及其作用域。
? 在函数定义中声明的变量,仅能在该函数内
部有效使用
? 在类定义中声明的变量,仅能在该类内部有
效使用
? 在一个段中声明的名字,也仅能在该段及其
子段中有效使用。
14
? 在整个程序各处都能访问的变量叫做 全局
变量 。 如果一个全局变量 在文件 1中声明,
在文件 2中使用, 那么在文件 2中必须使用
保留字 extern对该变量进行声明 。
? 如果在构成一个程序的两个文件中分别声
明了两个同名的全局变量, 这两个变量分
别代表两个不同实体, 此时需在两个文件
中分别使用保留字 static对变量进行声明 。
? 如果一个段中的局部变量与一个全局变量
同名, 且还要在此段中使用该全局变量,
此时需利用域操作符,:访问该全局变量 。
15
C++的类
? C++的核心部分是类的定义。 类定义体现
了抽象数据类型的思想 。为达到信息隐蔽
的原则。规定对类的成员有三级存取:
? 共有 (public)
? 私有 (private)
? 保护 (protected)
? 在 public 域中声明的数据成员和函数成员
(成员函数 ),程序中其它类的对象或操作
都能请求该类的对象执行它们,因此 这些
数据成员和成员函数构成类的界面部分 。
16
? 在 private域和 protected域中声明的数据成
员和成员函数构成类的私有部分,只能由
该类的对象和成员函数,以及声明为友元
(friend)的函数或类的对象才能访问它们。
? 在 protected域中声明的数据成员和成员函
数,还允许该类的派生类访问它们;
? 在 private域中声明的数据成员和成员函数,
则不允许该类的派生类访问它们。
? 下面给出一个 point 类的声明。 Point 类中
点的表示由两个整数变量 x,y 组成。类的
用户不能直接访问它们。
17
#ifndef POINT_H
#define POINT_H //In the header file point.h
class Point { //类定义
private,//私有域
int x; //数据成员:点坐标
int y;
public,//共有域
Point ( int,int ); //构造函数
Point ( Point & ); //复制构造函数
~Point ( ); //析构函数
int get_x ( ); //取 x坐标
int get_y ( ); //取 y坐标
18
Point operator + ( point ); //点加点
Point operator / ( int ); //点除整数
Point operator * ( int ); //点乘整数
int operator > ( Point ); //点比较
int operator < ( Point ); //点比较
int operator == ( Point& ); //点比较
friend istream& operator >>
( istream&,Point& ); //输入友元函数
friend ostream& operator <<
( ostream&,Point& ); //输出友元函数
};
#endif
19
? 为了存取一个点的 x,y分量,类提供了两个
函数 get_x,get_y。这样可用 private域来保
护数据的表示,防止类的用户直接使用数
据的内部表示来编写代码,通过使用存取
函数来操作数据来维持类的抽象性。
? private是声明默认的存取级别。
? 系统开发时,把类的声明放在头文件中,成
员函数的实现放在源程序文件中。在源程
序文件中函数的实现通过作用域设定命令
?,:”而被归属到某一个类。
20
例,对于 Point类的输出 友元 函数的实现可以
在源程序文件中给出,形为:
ostream & operator << (ostream& strm,
Point p) {
return strm << "(" << p.get_x ( )
<< "," << p.get_y () << ")";
}
这个函数把点 p的值以? x,y”的格式送到 strm
指明的输出流中去。
21
C++中的对象
? 建立类的对象 (亦称为 实例化 )时采用的方式
类似于定义 C变量的方式,可以 自动地,或
静态地,或通过 动态分配 来建立。建立一个
Point类实例的语句是:
? Point p (6,3); 自动地
? Point q; 自动地
? static Point s (3,4); 静态地
? Point *t = new Point(1,1); 通过动态分配
? 对象 p,q和 s都是 Point类的对象。
22
构造函数
? 当遇到以上的每一个语句时,将隐式地调用
一个构造 (constructor)函数,这个构造函数
属于一个与它同名的类。
? 在 Point类的定义中声明了两个构造函数,构
造函数的参数用于初始化表达式的值。
? 例如,当使用声明 Point p(6,3) 建立 Point 类
的对象 p 时,调用了构造函数 Point (int,int);
通过以下函数定义,将其 x,y分量设定为 6,3:
Point,,Point (int a,int b) { x = a; y = b; }
Point,,Point (int a,int b), x(a),y(b) { }
23
? 构造函数可以定义默认值。例如
Point,,Point ( int a = 0,int b = 0 )
,x(a),y(b) { }
? 当定义实例时给定初始值,则该实例以给
定初始值来初始化其数据成员
Point p(6,3);
则有 x = a = 6,y = b = 3
? 当定义实例时未给出初始值。则该实例
以默认值来初始化其数据成员
Point q;
则有 x = a = 0,y = b = 0
24
析构函数
? 当要放弃对象时,需隐式地调用另一个函数,
叫做 析构 (destructor)函数,它属于名字相同
的类,但在名字前面加上了一个?~?。例
如
~ Point。
? 为一个类可定义几个构造函数,但只能定义
一个析构函数。当控制要退出自动变量的作
用域时,或当通过 delete 命令释放一个动态
分配的变量时,就要调用析构函数。当 main
函数执行结束时,将释放静态声明的变量。
? 一个析构函数用于 在删除一个类的对象 时做
清除工作。
25
C++的输入 /输出
? 在 C++中执行输入 /输出操作,需用 #include
预处理指令包括一个 <iostream.h>头文件。
用它可支持 C++的流 (stream)操作。
? ?流?是个简单的字符序列。在 C++中有两
个预定义的类 istream和 ostream,它们定义
了输入流和输出流。
? 基本输入 /输出方式:
? 键盘屏幕输入 /输出
? 文件输入 /输出
26
键盘屏幕输入 /输出
? 在 C中有用于定向到键盘输入设备、屏幕输
出设备和错误文件的命令 stdin,stdout和
stderr。
? 在 C++中用 cin,cout和 cerr来定义键盘输入
类、屏幕输出类和错误信息输出类。
? 操作符 << 用于写出类 ostream的一个对象,
对于一系列输出对象,可用 <<分开。
? 操作符 >> 用于读入类 istream的一个对象 。
27
? 在下面程序中使用了流 cin >>,相继从标准
输入设备上输入两个整型变量 a和 b,并将它
们打印到标准输出设备上。
? 在输出语句中最后输出的 endl是 C++的 I/O操
作符,它的用途是输出一个换行符并清空流。
#include <iostream.h>
void main ( ) {
int a,b;
cin >> a >> b;
cout <<,a, " << n << "f, " << f << endl;
}
28
? C++中的输入 /输出可以是自由格式,程序
员不需要使用格式化符号来指定输入 /输出
项的类型和顺序。
? 与其它 C++操作符一样,输入 /输出操作符
能够被重载。
29
文件输入 /输出
? C++中的文件输入 /输出方式如下所示。
? 在程序开头必须用预处理指令 #include包含
头文件 <fstream.h>,它定义了类 ifstream、
ofstream和 fstream。
? 要创建一个 输入流,必须声明它为 ifstream
类的实例
? 要创建一个 输出流,必须声明它为 ofstream
类的实例
? 执行输入和输出操作的流 必须声明它为
fstream类的实例
30
#include <fstream.h>
#include <iostream.h>
#include <stdlib.h>
void main ( ) {
ifstream inFile; //inFile为输入流对象
ofstream outFile; //outFile为输出流对象
outFile.open ( "my.dat",ios,,out );
//建立输出文件 ?my.dat?
char univ[ ] = ?Tsinghua?,name[10];
int course = 2401,number;
outFile << univ << endl; //输出到 ?my.dat?
outFile << course << endl;
31
inFile.open("my.dat",ios::out | ios::nocreate);
//打开输入文件 ?my.dat?
if ( !inFile ) {
cerr <<,不能打开 my.dat” << endl;
exit(1);
}
char c;
inFile >> name >> c >> number;
outFile << "name," << name << endl;
outFile << "number," << number << endl;
}
32
? ifstream类,ofstream类和 fstream类都是从
istream类和 ostream类派生出来的,而类
istream和 ostream又是从类 ios派生出来的,
因此这些类都可使用类 ios的所有运算。
? 在调用打开文件函数 open( )时,函数参数表
包括 实际文件名 和 数据流动的方向,函数
返回 文件的开始地址 。系统在存储文件时,
在其末尾添加有文件结束标记。
? 如果文件未被打开,则 outFile = 0; 如果文件
被成功地打开,则它将代替 cout,将输出引
导到文件 ?my.dat ?中。
33
? 在文件打开的操作中, 指定的文件模式有
以下几种:
? iso::app:把所有对文件的输出添加在文
件尾 。 它只用于输出文件 。
? iso::binary:文件以二进制方式打开 。 此
项缺省时文件以文本方式打开 。
? iso::nocreate:若文件不存在则将导致打
开操作失败 。
? iso::out:表明该文件用于输出 。 此项可
缺省 。
? iso::in:表明该文件用于输入 。 此项可缺
省 。
34
C++中的函数
? 在 C++中有两种函数:
常规函数 和 成员函数
? 不论哪种函数,其定义都包括 4 个部分, 函
数名, 形式参数表, 返回类型 和 函数体 。
? 函数的使用者通过函数名来调用该函数;
调用时把实际参数传送给形式参数表作为
数据的输入;通过函数体中的处理程序实
现该函数的功能;最后得到返回值作为输
出。
35
? 下面给出一个函数的例子 。 max是函数名,
int a 和 int b 是形式参数表,函数名前面的
int 是返回类型,在花括号内括起来的是函
数体,它给出了函数操作的实现 。
int max ( int a,int b ) {
//函数返回 a 与 b 中的大值
if (a > b) return a;
else return b;
}
? 在 C++中所有函数都有一个返回值, 或者返
回计算结果, 或者返回执行状态 。
36
? 如果函数不需要返回值,可使用 void 来表
示它的返回类型。函数的返回值通过函数
体中的 return 语句返回。
? return 的作用是返回一个 与返回类型相同
类型 的值,并中止函数的执行。
? 函数返回时可以通过引用方式,参看下面
程序,此时在函数类型后面加上一 个 ? &”。
#include <iostream.h>
char& replace(int m);
char s[80] =,Hello There”;
37
main ( ) {
replace(5) = ?x?; cout << s;
//用 x代替 Hello后面的空格
}
char& replace ( int m ) {
return s[m];
}
? 函数 replace( )的返回类型说明为返回一个字
符的引用类型,在函数执行时返回 参数 m 指
定的 s数组元素 的值。 main ( )执行时把字符
? x” 送给 s[5]。
38
C++中的参数传递
? 函数调用时传送给形参表的实参必须与形
参在类型、个数、顺序上保持一致。
? 参数传递有两种方式。一种是 传值,这是
缺省的参数传递方式 ; 一种是 引用类型 。
? 使用 传值 方式时,把实参的值传送给函数
局部工作区相应的副本中,函数使用这个
副本执行必要的功能。这样,函数修改的
是副本的值,实参的值不变 。
39
? 使用 引用类型 方式传递时,需将形参声明为
引用类型, 即在参数名前加一个 ? &”。 参
看下面的程序示例 。
? 当一个实参与一个引用型形参结合时, 被
传递的不是实参的值, 而是实参的地址,
函数通过地址存取被引用的实参 。 函数执
行后实参的值将发生改变 。
? 当一个函数的返回值多于一个时,其中一个
可由 return语句返回,其它返回值可使用引
用型参数返回 。
#include <iostream.h>
void swap (int& i,int& j);
40
main ( ) {
int a = 1,b =2;
cout << "a and b," << a << " " << b << "\n";
swap ( a,b );
//调用时实际参数不需要加 &
cout << "a and b," << a << " " << b << "\n";
}
void swap ( int& i,int& j ) {
//对换 i 与 j 的内容
int t = j; j = i; i = t; //不需要加 *
}
41
? 一种特殊的引用调用方式叫做 常值引用,其
格式为 const Type& a,其中 Type为参数的
数据类型 。 在函数体中不能修改常值参数 。
? 一种特殊情况是 数组参数的传递 。 数组作
为形参可按传值方式声明, 但实际采用引
用方式传递, 传递的是数组第一个元素的
地址 。 在函数体内对形参的数组所做的任
何改变都将反映到作为实参的数组中 。
? 此外, 在参数表中一般按形如 int R[ ] 的形
式声明, 因此需要显式地声明数组的大小 。
42
? 若传送的 值参是一个对象 (作为类的实例 )
时,在函数中就创建了该对象的一个副本。
在创建这个副本时不调用该对象的构造函
数,但在函数结束前要调用该副本的析构
函数撤消这个副本。
? 若 采用引用方式传递对象,在函数中不创
建该对象的副本,也不存在最后撤消副本
的问题。但是,通过引用传递的是对象时,
函数对对象的改变将影响调用的对象 。
43
成员函数的返回值
? 当成员函数的返回值为 传值方式 时,允许改
变该对象的私有数据成员。
? 当成员函数的返回值为 常值传值方式 时,需
加 const 标识,该对象的私有成员不能改变。
? 当成员函数的返回值为 引用方式 时,该成员
函数的返回值应 是一个已存在变量 (或对象 )
的别名 。当该成员函数被重新赋值时,其对
应变量 (或对象 )的值将改变。
? 当成员函数的返回值为 常值引用方式 时,其
返回值与引用方式的成员函数返回值类同 。
但该成员函数不能改变该对象的私有成员。
44
? 当成员函数返回值为 常值传值方式 或 常值引
用方式 时,const 标识符一般放在最后。
#include<iostream.h>
class Temperature {
private:
float highTemp,lowTemp; //数据成员
public:
Temperature(int hi,int lo) //构造函数
{ highTemp = hi; lowTemp = lo; }
void UpdateTemp(float temp); //传值返回
float GetHighTemp( ) const; //常值返回
45
float GetLowTemp( ) const; //常值传值返回
};
void Temperature,,UpdateTemp(float temp) {
if (temp > highTemp) highTemp = temp;
if (temp < LowTemp) LowTemp = temp;
}
float Temperature,,GetHighTemp( ) const {
return highTemp;
}
float Temperature,,GetHighTemp( ) const {
return highTemp;
}
46
C++中的函数名重载
? 函数名重载允许 C++程序中多个函数取相同
的函数名,但其形参或返回类型可以不同。
? 例如,C标准函数库中有 3个标准函数 abs( )、
labs( )和 fabs( ),分别计算整型数、长整型数
和双精度型数的绝对值。在 C中因处理的数
据类型不同,必须取不同的函数名。在 C++
中,可以把这 3 个函数都命名为 abs ( ):
int abs ( int );
long abs ( long );
double abs ( double );
47
C++的操作符重载
? C++提供了一种能力,可 用同一个名字定义
多个函数,这种能力叫做 操作符重载 。
? 例如,可以命名一个函数, clear(* int),它将
一个整数清零。还可以再命名另一个函数
clear(int[ ]),它把一个整数数组清零。
? 编译器能够比较具有同名的函数的特征,通
过识别 实参的数目 和每个 实参的类型,来标
识使用于一个特定调用的是哪一个版本的
abs( )。
48
? 在 C中,必须使用名字 clearIntArray( ) 和
clearInt( ) 来区分这两个函数。在 C++中,
编译器能够比较同名函数的特征,通过识
别实参的数目和每个实参的类型,来标识
一个特定调用中用的是哪一个版本的 clear。
? 为了支持面向对象,C++ 提供了 双目重载
操作符 (如‘+’和‘<’ )。这种操作可
使得程序更可读、写得更自然。
? 例如,可定义 ?点 (Point)”的运算,像
? p1+p2,把两个点 (x1,y1)和 (x2,y2)相加
成一个点 (x1+x2,y1+y2)。
49
? p1<p2,两个点 p1和 p2的 ?小于? 关系,
表示 p1比 p2更靠近原点 (0,0)。
? p1/ i,一个点 p= (x,y) 除以一个整数 i
的除法 (x/i,y/i)。
? 可以按以下方式使用重载操作:
Point operator + ( Point p );
Point operator / (int i);
int operator < (Point p);
? 使用这些新的操作的表达式如,
Point midPoint = (point1+ point2) / 2;
或 if ( midPoint < referencePoint ),..
50
? 注意, 每一个这样的操作符在调用时可看成
是该操作符左边对象的成员函数。
? 例如,(point1+point2) 实际上是一个消息。
由类 Point的 实例 point1调用成员函数 ? +”,
该对象的属性确定第一个操作数的值。
? 函数参数表中指定的 Point的 实例 point2的
属性确定第二操作数的值。
? 这种重载能力允许像使用内建类型 (如 int,
float) 那样来使用用户自定义类型。与在不
允许重载操作的语言中相同的语句比,这样
可以改善程序的可读性。
51
C++的动态存储分配
? 在 C程序中,使用一个函数 malloc,为程序
分配它所需要的空间,一旦程序执行结束
需要返回到它的调用者时,必须释放这个
空间。
? C++为动态存储分配提供了两个新的命令:
new和 delete。它们可用于取代 C 中的库函
数 malloc和 free。
? 在 C++中没有 无用单元收集,使用 new分配
的存储必须显式地使用 delete释放。
52
? 操作 new要求以 被建立对象的类型 做为参
数,并 返回一个 指向新分配空间 的指针 。
? 作为对比,在 C中,函数 malloc要求它的调用
者提供所需存储空间的数量。
? 例如,为动态分配一个整数或一个点,可编
写如下语句:
int *ip = new int;
或 Point *p = new Point;
? 它们组成了指针变量的声明 (如 * name) 和
动态存储分配 ( new 类型 )。
53
? Delete 命令必须能够知道 new 分配了多少
存储。当要释放动态分配的数组时,必须
告诉 delete 该数组中包含的元素个数。
? 例如,如果已建立下列有 100个点的数组:
Point* p = new Point[100];
? 则通过以下命令释放该存储,
delete [100]p;
? 若遗漏了 ? [100]”,则将只释放 p 所指示的
第一个元素,将?失去?其它 99个点所占据
空间,以致不能再复用它们。若使用时元素
下标超出 100,程序将会出错且结果不可预
测。
54
友元 (friend)函数
? 在类的声明中可使用保留字 friend 定义友
元函数。
? 友元函数实际上并不是这个类的成员函数,
它可以是一个常规函数,也可以是另一个
类的成员函数。如果想通过这种函数存取
类的私有成员和保护成员,则必须在类的
声明中给出函数的原型,并在该函数原型
前面加上一个 friend。
? 参看 Point类的声明,有两个重载操作符 <<
与 >>,它们都被声明为友元函数。
55
内联 (inline)函数
? 在函数定义前加上一个 inline前缀就成为内
联函数。编译程序在编译时将会把这个函
数语句直接插入到普通代码中,因而减少
了与函数调用和参数传递有关的系统开销。
? 直接插入代码所需要的空间比不直接插入
的调用方式所需要的空间要多,这取决于
函数定义的大小。
? 除了加上 inline保留字外,内联函数的定义
与其它任何函数定义的方式一样。
inline Point operator + (Point p);
56
结构 (struct)与类
? C++ 扩充了 C 中结构 (struct) 型的功用,加
进成员函数以说明一个类 (class)。在 C++
中 struct 与 class 的区别在于,
? 在 struct 中,默认的访问级别是 public。若
在 struct 内部自始至终缺省访问级别,则所
有的成员都是共有的。
? 在 class 中,缺省的访问级别是 private。
? 除此之外,struct 与 class 是等价的。例如,
下面给出定义矩形类的三种等价的类声明。
57
class Rectangle {
int x1,y1,h,w;
public:
Rectangle ( );
~Rectangle ( );
int GetX ( );
int GetY ( );
void SetX (int x);
void SetY (int y);
int GetHeight ( );
int GetWidth ( );
…………
}
58
struct Rectangle {
Rectangle ( );
~Rectangle ( );
int GetX ( );
int GetY ( );
void SetX (int x);
void SetY (int y);
int GetHeight ( );
int GetWidth ( );
…………
private:
int x1,y1,h,w;
}
59
联合 (Union)与类
? 与结构一样,用 Union也可以定义类。
? 在 C++中,Union可包含 函数 和 变量,还可包
含 构造函数 和 析构函数 。
? C++的 Union保留了所有 C的特性,主要是让
所有的数据成员共享相同的存储地址。
? 与 class和 struct相比,Union可节省存储。与
结构相似,Union中默认存取级别是 public。
60
模板 (template)
定义
适合 多种数据类型 的 类定义 或 算法,
在特定环境下通过简单地代换,变成
针对具体某种数据类型 的 类定义 或 算
法
61
用模板定义用于排序的数据表类
#include <iostream.h>
template <class Type>
class dataList {
private:
Type *Element;
int ArraySize;
void Swap (int m1,int m2);
int MaxKey (int low,int high);
62
public:
dataList (int size = 10), ArraySize (size),
Element (new Type [Size]) { }
~dataList ( ) {delete [ ] Element;}
void Sort ( );
friend ostream& operator << (ostream&
outStream,datalist<Type>& outList);
friend istream& operator >> (istream&
inStream,datalist<Type>& inList);
}
63
类中所有操作作为模板函数的实现
#include,datalist.h”
template <class Type> void dataList
<Type>,,Swap (int m1,int m2) {
//交换由 m1,m2为下标的数组元素的值
Type temp = Element [m1];
Element [m1] = Element [m2];
Element [m2] = temp;
}
64
template <class Type>
int dataList<Type>::
MaxKey (int low,int high) {
//查找数组 Element[low]到 Element[high]
//中的最大值,函数返回其位置
int max = low;
for (int k = low+1,k <= high,k++)
if ( Element[max] < Element[k] )
max = k;
return max;
}
65
template <class Type> ostream&
operator<< (ostream& OutStream,
dataList<Type> OutList) {
OutStream <<,数组内容, \n”;
for (int i = 0; i < OutList.ArraySize; i++)
OutStream << OutList.Element[i] << ? ?;
OutStream << endl;
OuStream <<,数组当前大小,, <<
OutList.ArraySize << endl;
return OutStream;
}
66
template <class Type>
istream& operator >> (istream& InStream,
dataList<Type> InList) {
cout <<,录入数组当前大小,,;
Instream >> InList.ArraySize;
cout <<,录入数组元素值, \n”;
for (int i = 0; i < InList.ArraySize; i++) {
cout <<,元素, << i <<,:” ;
InStream >> InList.Element[i];
}
return InStream;
}
67
template <class Type>
void dataList<Type>::Sort ( ) {
//按非递减顺序对 ArraySize个关键码
//Element[0]到 Element[ArraySize-1]排序
for ( int i = ArraySize -1; i > 0; i-- ) {
int j = MaxKey (0,i);
if ( j != i ) swap (j,i);
}
}
68
使用模板的选择排序算法的主函数
#include,selecttm.h”
const int SIZE = 10;
int main ( ) {
dataList <int> TestList (SIZE);
cin >> TestList;
cout << TestList << endl;
TestList.Sort ( );
cout << TestList << endl;
return 0;
}