1
第三章
类和对象
2
3.1 类与对象的基本概念
3.1.1 结构与类
1,结构的扩充
C++中对 C语言的结构类型进行了扩充,它还可以含有函数。如,
struct complex{
double real; //复数的实部
double imag; //复数的虚部
void init (double r,double I) //给 real和 imag赋初值
{ real=r; imag=i; }
double realcomplex() //取 复数 的 实部 值
{ return real;}
double imagcomplex() //取 复数 的 虚部 值
{ return imag;}
double abscomplex() //求 复数 的 绝对 值
{ double t;
t=real*real+imag*imag;
return agrt(t);
}
};结构类型中的数据和函数,分别称为数据成员和函数成员 。
3
**例 3.1
#include <iostream.h>
#include <math.h>
struct complex{
double real; //复数的实部
double imag; //复数的虚部
void init (double r,double I) //给 real和 imag赋初值
{ real=r; imag=i; }
double realcomplex() //取 复数 的 实部 值
{ return real;}
double imagcomplex() //取 复数 的 虚部 值
{ return imag;}
double abscomplex() //求 复数 的 绝对 值
{ double t;
t=real*real+imag*imag;
return sqrt(t);
}
} A;
void main()
{ A.init(1.1,2.2);
cout<<“复数 A的实部 =,<<A.realcomplex()<<endl;
cout<<“复数 A的实部
=,<<A.imagcomplex()<<endl;
cout<<“复数 A的绝对值
=,<<A.abscomplex()<<endl;
}
4
2,类的定义 (声明 )
C++中说明类的一般形式为
clsaa 类名 { private,(可缺省 )
私有数据成员和函数成员
protected,
保护段
public,
公有数据成员和函数成员
};
class complex{
private,
double real,imag;
public,
void init (double r,double i) { real=r; imag=i; }
double realcomplex() { return real;}
double imagcomplex() { return imag;}
double abscomplex()
{ double t; t=real*real+imag*imag; return sqrt(t); }
};
类 class 和结构 sturct 的主要区别是:
在缺省 private 时,
在类中,成员是私有的
在结构中,成员是公有的
5
例 3.2
#include <iostream.h>
#include <math.h>
class complex{
private,
double real,imag;
public,
void init (double r,double i) { real=r; imag=i; }
double realcomplex() { return real;}
double imagcomplex() { return imag;}
double abscomplex()
{ double t; t=real*real+imag*imag; return sqrt(t); }
};
void main( )
{ complex A;
A.init(1.1,2.2);
cout<<“复数 A的实部 =,<<A.realcomplex()<<endl;
cout<<“复数 A的实部 =,<<A.imagcomplex()<<endl;
cout<<“复数 A的绝对值 =,<<A.abscomplex()<<endl;
}
6
3.1.2 成员函数的定义
第一种方式是在类定义中只给出成员函数的原型(或者说声明),
而成员函数在类的外部定义。在外部定义的一般形式是,
返回类型 类名,:函数名 (参数表 ){
// 函数体
}
class point{ // 定义类 point
private,
int x,y; // 数据成员,二个整数作为 x,y 坐标
public,
void setpoint(int,int); // 函数成员,名为 setpoint,有二个
整数形参,用于设置坐标值,具体如何在外部再定义,这叫函数
的原型(或者说声明)
int getx(); //取 x 坐标值的成员函数 getx()的函数原型
int gety(); //取 y 坐标值的成员函数 gety()的函数原型
}; //类定义结束
viod point::setpoint(int a,int b) //成员函数 setpoint 具体定义
{ x=a; y=b; }
int point:,getx() //成员函数 getx 具体定义
{ return x; }
int point:,grty() //成员函数 gety 具体定义
{ return y; }
7
说明
( 1)在所定义的成员函数名之前应缀上类名,在类名和函数名之
间应加上分隔符,::”,例如上面例子中的,point::”。
( 2)在定义成员函数时,对函数所带的参数,不但要说明它的类
型,还要指出其参数名。
( 3)在定义成员函数时,其返回类型一定要与函数原型中声明的
返回类型匹配。
第二种方式是将成员函数定义在类的内部,即定义为内置函数。
又可用两种,
(1) 隐式定义 即函数的原型和定义都在类的内部完成
class point{ // 定义类 point
private,
int x,y; // 数据成员,二个整数作为 x,y 坐标
public,
void setpoint(int a,int b) // 函数成员
{ x=a; y=b; }
int getx() { return x; }
int gety() { return y; }
}; //类定义结束
8
(2) 显式定义
即在类定义中仍只给出成员函数的原型(或者说声明),而成员函
数在类的外部定义。但函数定义前冠以关键字, inline”,以此显
式地说明这是一内置函数。
class point{ // 定义类 point
private,
int x,y; // 数据成员,二个整数作为 x,y 坐标
public,
void setpoint(int,int); // 函数成员 setpoint,有二个整数形参
int getx(); //取 x 坐标值的成员函数 getx()的函数原型
int gety(); //取 y 坐标值的成员函数 gety()的函数原型
}; //类定义结束
inline viod point::setpoint(int a,int b) //内置成员函数具体定义
{ x=a; y=b; }
inline int point:,getx() //内置成员函数 getx 具体定义
{ return x; }
inline int point:,grty() //内置成员函数 gety 具体定义
{ return y; }
把成员函数设置成内置,可提高执行的效率,但函数体较长的成员
函数都不设置成内置。
9
3.1.3 对象的定义及引用
1,类与对象的关系
可以用整型 int 和整型变量 i 之间的来类比。 int i ;
2,对象的定义
( 1) 在声明类的同时,直接定义对象 op1,op2
class point{ // 定义类 point
private,
int x,y; // 数据成员,二个整数作为 x,y 坐标
public,
void setpoint(int,int);
int getx();
int gety();
} op1,op2;
( 2) 声明了类之后,在使用时再定义对象,定义的格式与一般变
量的定义格式相同,point op1,op2;
说明:( 1) 声明了一个类便声明了一种类型,它并不接收和存储
具体的值,只有定义了对象后,系统才为对象并且只为对象分配
存储空间。
( 2) 在声明类的同时定义的对象是一种全局对象,在它的
生存期内任何函数都可以使用它。
10
3.对象的引用
对象的引用是指对象的成员的引用,不论是数据成员,还是成员
函数,只要是公有的,就可以被外部函数直接引用,引用格式是:
对象名, 数据成员名 或 对象名, 成员函数名(实参表)
例 3.3
#include <iostream.h>
class point{
private,
int x,y;
public,
void setpoint(int a,int b)
{ x=a; y=b; }
int getx() { return x; }
int gety() { return y; }
};
void main()
{ point op1,op2;
int i,j;
op1.setpoint(1,2);//调用 op1的 setpoint(),初始化对象 op1
op2.setpoint(3,4);//调用 op2的 setpoint(),初始化对象 op2
i=op1.getx(); //调用 op1的 getx(),取 op1的 x值
j=op1.gety(); //调用 op1的 gety(),取 op1的 y值
cout<<“op1 i=”<<i<<,op1 j=”<<j<<endl;
i=op2.getx(); //调用 op2的 getx(),取 op2的 x值
j=op2.gety(); //调用 op2的 gety(),取 op2的 y值
cout<<“op2 i=”<<i<<,op2 j=”<<j<<endl;
}
11
说明
( 1) 本例中 op1.setpoint(1,2)实际上是 op1.point::setpoint(1,2)的一
种缩写形式。
( 2) 外部函数不能引用对象的私有成员。
void main()
{ point op;
int i,j;
op.setpoint(1,2);
i=op.x; //错误,不能直接引用对象的私有成员
j=op.y; //错误,不能直接引用对象的私有成员
cout<<“op i=”<<i<<,op j=”<<j<<endl;
}
( 3)若定义的是对象指针,则用,->”操作符。
point *op;
op->setpoint(5,6);
i=op->getx();
j=op->gety();
12
4,对象赋值语句
同类型的对象之间也可以进行赋值,当一个对象赋值给另一个对
象时,所有的数据成员都会逐位拷贝。
例 3.4
#include <iostream.h>
class myclass{
int a,b;
public,
void set(int i,int j) { a=i; b=j; }
void show() { cout<<a<<“,<<b<<endl; }
};
void main( )
{ myclass o1,o2;
o1.set(20,5);
o2=o1; //将对象 o1的值赋给将对象 o2
o1.show();
o2.show();
}
(1)对象赋值时,两
个对象的类型必须
相同。
(2)对象赋值时,仅
仅使对象中的数据
相同,两个对象仍
是独立的。
(3)两个对象赋值时,
多数情况是成功的,
但当类中有指针成
员时,可能会发生
错误。
13
3.1.4 类的作用域
所谓类的作用域就是指在类的声明中的一对花括号所形成的作用域。
一个类的成员函数可以不受限制地引用该类的数据成员,而在该
类作用域之外,对该类的数据成员和成员函数的引用则要受到一
定的限制,有时甚至是不允许的。这体现了类的封装性。
例 3.5
#include <iostream.h>
class abc{
public,
int i;
void set(int);
void disp()
{ cout<<“i=“<<i<<endl; }//可以 直接引用类中的数据成员 i
};
void main()
{ abc ob;
ob.set(2); //通过成员函数给数据成员 i赋初值 2
ob.disp();
i=1; //非法,不能直接引用类中的数据成员 i,可
// 改为 ob.i=1;
ob.disp();
}
void abc::set(int si)
{ i=si;} //成员函数 可以 直接引用类中的数据成员 i
int fun()
{return i; } //非法,不能直接引用类中的数据成员 i
14
*3.1.5 定义类
1,说明类
C++中说明类的一般形式为
clsaa 类名 { private,(可缺省 )
私有数据和函数 (私有成员 )
protected,
保护段
public,
公有数据和函数 (公有成员 )
};
2,类标识符
3,类体 (成员函数体 )
成员函数可以在定义类时同时说明和定义
class Location {
private,
int X,Y;
public,
void init(int initX,int initY)
{ X=initX;
Y=initY;
};
int GetX() { return X;};
int GetY() { return Y;};
}
也可先说明另外单独定义。
class Location {
private,
int X,Y;
public,
void init(int initX,int initY)
int GetX();
int GetY();
};
void Location::init(int initX,int
initY)
{ X=initX;
Y=initY;
}
int Location::GetX()
{ return X; }
int Location::GetY()
{ return Y; }
返回类型 类名,:成员函数名 (参数说明
)
{
类体
}
16
*3.1.6使用类
1,对象说明 (定义对象 )
可以使用类在程序中定义变量,这种变量即为对象 Location A1,A2;
【 例 2.7】 Location 类的一个完整程序,可上机。
#include <iostream.h>
class Location {
private,
int X,Y;
public,
void init(int initX,int initY)
int GetX();
int GetY();
};
void Location::init(int initX,int initY)
{ X=initX;
Y=initY; }
int Location::GetX()
{ return X; }
int Location::GetY()
{ return Y; }
void main( )
{ Location A1,A2;
A1.init(5,3);
A2.init(6,8);
cout <<A1.GetX()<<" "<<A1.GetY()<<endl;
cout <<A2.GetX()<<" "<<A2.GetY()<<endl;
// 或
Location *pA1,*pA2;
pA1=&A1; pA2=&A2;
cout <<pA1->GetX()<<" "<<pA1->GetY()<<endl;
cout <<pA2->GetX()<<" "<<pA2->GetY()<<endl;
}
17
2,封装性
用 class 定义类,实现了封装,C++中还可用 struct,union等
3,类的存取控制
类的使用者有如下三种,
① 类本身
② 一般用户
③ 派生类
每一种使用者对类的数据有不同的权限
① private
② public
③ protected
4,类的私有成员
private
只有类本身 (或类的友元 )可以存取私有成员。
要使类在程序中被使用,所以类必须至少有一个非私有成员。
5,类的公有成员
pubilc
在 public部分定义的内容允许被其它对象无限制地存取。另外
C++可以有限制地使用 public成员存取 private成员。
18
【 例 】 使用类,设计模似数字式时钟的程序
一般较大的程序用模块化的方法,分成三个文件
① *.h 文件,用来描述类。
② *.cpp 文件,用来实现类,其中是成员函数的定义。
③ *.cpp 文件,用来写 main( ) 函数。
//clock.h
class Clock{
private,
int hour,minute,second;
public,
void init();
void update();
void display();
};
19
//clock.cpp
#include <iostream.h>
#include "clock.h"
void Clock::init()
{ hour=minute=secind=0; }
void Clock::update()
{ second++;
if(second==60){ second=0; minute++;}
if(minute==60){ minute=0; hour++;}
if(hour==24) hour=0;
}
void Clock::display()
{ cout<<“hour<<“:”<<minute<<“:”<<second<<endl;
}
20
//mymain.cpp
#inclode "clock.cpp"
void main( )
{ Clock clockA,clockB;
cout<<"CLOCK A:"<<endl;
clockA.init();
for(int i=0;i<10;i++)
{ clockA.update();
clockA.display();
}
cout<<"CLOCK B:"<<endl;
clockB.init();
for(i=0;i<20;i++)
{ clockB.update();
clockB.display();
}
}
21
(1) 函数 main()不能访问对象 clockA内部私有数据和函数,Clock类
的修改不必改 main()中程序。
(2) Clock类受封装保护。
(3) 类只需定义一次,就可通过实例化建立多个对象。
(4) 三个清楚的模块。
6,类的保护成员
protected
当定义类是为了以后作其它类的基类时,可以定义类的保护成员
。
22
*3.1.7 内联成员函数
在类定义内只有成员函数的声明,而在外面定义完整的成员函数,
并且前面加 inline,这样的成员函数叫内联成员函数。编译器会
自动用函数体去替换函数声明,其目的是提高程序的效率。
class Location {
private,
int X,Y;
public,
void init(int initX,int initY); //成员函数的声明
int GetX(); //成员函数的声明
int Gety(); //成员函数的声明
};
inline void Location::init(int initX,int initY)
{ X = initX; Y = initY; }
inline int Location::GetX()
{ return X; }
inline int Location::GetY()
{ return Y; }
考虑到程序的运行效率,简单的成员函数一般与类
定义同时定义或 被实现为内联函数。
23
*3.1.8成员函数的重载及其缺省参数值
在类说明中可以定义同名的成员函数,称为重载。这时这两个函数
应该有不同的形参情况及函数体,完成不同的程序功能。
在类说明中定义成员函数时可以对形参给以缺省值,称为带缺省参
数值的成员函数。
#include <iostream.h>
class Location {
private,
int X,Y;
public,
void init(int=0,int=0); //带缺省参数值的成员函数
void valueX(int); //这两个函数同名,都叫 valueX
int valueX( ); //但有不同的形参和返回值
void valueY(int);
int valueY( );
};
24
void Location::init(int initX,int initY)
{ X = initX; Y = initY; }
void Location::valueX(int val) { X=val; }
int Location::valueX( ) { return X; }
void Location::valueY(int val) { Y=val; }
int Location::valueY( ) { return Y; }
void main( )
{ Location LA,LB;
LA.init( ); //对象 LA 的数据成员 X 和 Y 被置为 0
LA.valueX(5); //将成员 X 重置为 5
cout<<LA.valueX( )<<endl<<LA.valueY( )<<endl;
LB.init(6,2); //对象 LB 的 数据成员 X 和 Y 被置为 6 和 2
LB.valueY(4); //将成员 Y 重置为 4
cout<<LB.valueX( )<<endl<<LB.valueY( )<<endl;
}
运行时会输出 5
0
6
4
25
*3.1.9结构和类
C 语言的结构定义 struct 在 C++ 中也可用来定义类,结构是类的
一种特例。
结构和类唯一的区别是:在缺省情况下(未指定访问权限时),结
构中的所有成员都是公有的,而在类中是私有的。
在所有其它方面,结构和类等价。
一般我们在仅描述数据时用结构;当既要描述数据又要描述操作时
用类。
26
*3.1.10 this 指针
当一个成员函数被调用时,C++自动产生一个参数,这个参数是一
个类指针,名叫 this,可以指向该类的一个对象,这个对象就是
接受该函数调用的对象。
如,Location LA;
LA.init(5,2);
这时就有一个 this --> LA
这里举一个例子说明 this 指针的用处,假定类中增加一个成员函数
取名为 赋值 assign()
void Location::assign(Location& p)
{ if (this == &p);
else { X=p.X; Y=p.Y }
}
当有二个对象 Location LA,LB;
若 LA.assign(LA); 则 this==&p即 LA成立,执行空语句,表示同一
个对象之间相互赋值没有意义,若 LA.assign(LB); 则 this(LA)
不等于 &p(LB),则 LA.X(或 this->X)=p.X; LA.Y=p.Y; 这里就可
以用来判别接受引用的对象是自己还是另外的对象。
27
*3.1.11类的其它基础知识
类的作用域
1,在类说明 { }中的标识符只在类中可见。类中各数据成员的生存期
由类对象的生存期决定,当对象存在时,它们存在;对象消失时,
它们也消失。
class MyClass{
private,
int number;
public,
void set(int i);
}
int j=number; //出错,number 在此不可见
int number;
void MyClass::set(int i)
{ number = i; //此 number 为 MyClass 中的 number
}
28
2,一个类的说明也分为声明和定义性说明。
class Location; //声明
声明的类名不能用来建立对象,只能用来建立指针或引用。
Location LA; //错
Location *Pa; //对
class Location {,......,
,......,
} //定义
Location LA; //对
Location *Pa; //对
空类
class 名 { };
{ } 中没有数据成员和函数成员称为空类,空类可以建立对象。
类对象的存储方式
定义类对象时也可加 auto,register,extern,static等存储方式。
类嵌套
类定义中的成员可以是类,称为类嵌套。
类的实例化
当用一个类定义对象时,C ++才进行存储分配。这种对象建立的
过程称为实例化。
29
3.2 构造函数与析构函数
3.2.1 构造函数
当类定义时把数据成员定义成公有成员时,可用传统的方法给它赋
初值。
C语言 传统的方法
main()
{ struct student { int sno;
char *sname;
float score;
};
struct student s1={9801,"张三 ",89.5};
class Init{
public,int i;
char *name;
float num[2];
};
Init C = {34,"program",{56.89,1.2}};
对于更一般的类型,我们用构造函数的方法。
30
构造函数
构造函数是特殊成员函数,具有一些特殊的性质,
( 1) 构造函数的名字必须与类名相同。
( 2) 构造函数可以有任意类型的参数,但不能具有返回类型。
( 3) 定义对象时,编译系统会自动地调用构造函数。
class complex{
private,
double real,imag;
public,
complex(double r,double i)
{ real=r; imag=i; }
double abscomplex()
{ double t;
t=real*real+imag*imag;
return sqrt(t);
};
构造函数不能像其它成员函数那样被显式地调用,它是在定义对
象的同时调用的,其一般格式为,类名 对象名(实参表)
31
构造函数的调用
例 3.6
#include <iostream.h>
#include <math.h>
class complex{
private,
double real,imag;
public,
complex(double r,double i)
{ real=r; imag=i; }
double abscomplex()
{ double t;
t=real*real+imag*imag;
return sqrt(t); }
};
void main()
{ complex A(1.1,2.2); //定义对象 A时自动 调用构造函数 complex
cout<<“复数 A的绝对值 =,<<A.abscomplex()<<endl;
}
32
说明,
( 1) 构造函数的名字必须与类名相同,否则编译程序将把它当作
一般的成员函数来处理。
( 2) 构造函数没有返回值,在声明和定义构造函数时,是不能说
明它的类型的的,甚至说明为 void类型也不行。
( 3) 在实际应用中,通常需要给每个类定义构造函数。如果没有
给类定义构造函数,则编译系统自动地生成一个缺省的构造函数。
complex::complex() { }
( 4) 构造函数可以是不带参数的。
( 5) 构造函数也可采用构造初始化表对数据成员进行初始化,这
是某些程序员喜欢使用的方法。
class A{
int i,j;
float f;
public,
A(int I,int J,float F):i(I),j(J),f(F) { }
};
( 6) 对没有定义构造函数的类,其公有数据成员可以用传统的方
法初始值表进行初始化。
33
定义构造函数
在类定义时可以定义一种与类名同名的成员函数,我们称之为构
造函数。它有如下的要点
要点 1,构造函数与类名同名,且要有函数体,可以内置或外置或
内联,没有返类型( void也不能写)。
要点 2,构造函数不能象其他成员函数那样被外界调用,它在程序
定义一个新对象,程序自动调用构造函数来初始化这个对象。
要点 3,构造函数可不带形式参数也可带形式参数,这样可根据不
同的要求对对象不同的初始化。
【 例 】 // test.h
class test {
private,int num;
float fl;
public, test();
test(int n,float f);
int getint() { return num; }
float getfloat() { return fl; }
};
34
test::test()
{ cout<<“这是缺省参数的构造函数进行初始化 "<<endl;
num=0; fl=0.0;
}
test::test(int n,float f)
{ cout<<“这是带参数的构造函数进行初始化
"<<n<<""<<f<<endl;
num=n;
fl=f;
}
下面用类定义对象时,会自动调用以上构造函数 #include
<iostream.h>
#include "test.h"
void main()
{ test x;
test y(10,21.5);
}
执行这个程序,产生如下输出,
这是缺省参数的构造函数进行初始化
这是带参数的构造函数进行初始化 10 21.5
35
构造函数和运算符 new,delete
运算符 new可动态 (生存期可控 )的建立一个对象,new返回这个对象
的指针。
执行运算符 new时,new先分配足够内存,然后自动调用构造函数
给这内存赋初值,再返回这个内存的地址。
void main()
{ test *prt1=new test;
test *prt2=new test(5,12.8);
delete prt1;
delete prt2;
}
执行这个程序,产生如下输出,
这是缺省参数的构造函数进行初始化 Initializing default
这是带参数的构造函数进行初始化 5 12.8 Initialzing
用 new建立的对象在不用时可用 delete删除。
36
3.2.2 缺省参数的构造函数
如果定义对象时给一个默认值,这时可以将其定义成带缺省参数的
构造函数。
class complex{
private,
double real,imag;
public,
complex(double r=0.0,double i=0.0); //带缺省参数的构造函数
double abscomplex();
};
complex::complex(double r,double i) //带参数的构造函数
{ real=r; imag=i; }
double complex::abscomplex();
{ double t;
t=real*real+imag*imag;
return sqrt(t);
}
complex S1; // S1为 0.0,0.0 的对象
complex S2(1.1); // 只给一个参数
complex S3(1.1,2.2); // 给二个参数
37
3.2.3 析构函数
析构函数也是特殊成员函数,用于释放对象。
( 1) 析构函数与构造函数名字相同,但它前面必须加一个波浪号
( ~)。
( 2) 析构函数没有参数,也没有返回值,而且不能重载,因此在
一个类中只能有一个析构函数。
( 3) 当撤消对象时,编译系统会自动地调用析构函数。
例 3.8
#include <iostream.h>
#include <math.h>
class complex{
private,
double real,imag;
public,
complex(double r=0.0,double i=0.0) ; //带缺省参数的构造函数
~complex(); //声明 析构函数
double abscomplex();
};
38
接例 3.8
complex::complex(double r,double i) //带参数的构造函数
{ cout<<“进入构造函数 …..”<<endl;
real=r; imag=i;
}
complex::~complex() //定义 析构函数
{ cout<<“进入析构函数 …..”<<endl; }
double complex::abscomplex()
{ double t;
t=real*real+imag*imag;
return sqrt(t);
}
void main()
{ complex A(1.1,2.2); //定义对象 A时自动 调用构造函数
complex
cout<<“复数 A的绝对值 =,<<A.abscomplex()<<endl;
} //结束时自动 调用析构函数
如果没有给类定义析构函数,则编译系统自动地生成一个缺省的析
构函数,complex::~complex() { }
39
定义析构函数
在类定义时可以定义另一种与类名同名前面加 ~的成员函数,我们
称之为析构函数。
它有如下的要点
要点 1,析构函数与类名同名,但它前面必须加波浪号 (~)
且要有函数体,可以内置或外置或内联,没有返回类型
( void也不能写)。
要点 2,析构函数不能象其他成员函数那样被外界调用,
它在程序结束时,程序自动调用析构函数来析放对象的
内存。
要点 3,析构函数可不带形式参数,不能重载,因此在一
个类中只能有一个析构函数。
40
【 例 】 // test2.h
class test {
private,int num;
float fl;
public, test();
test(int n,float f);
int getint() { return num; }
float getfloat() { return fl; }
~test();
};
test::test()
{ num=0; fl=0.0; }
test::test(int n,float f)
{ num=n; fl=f; }
test::~test()
{ cout << "Destructor is active显示一下己调用析构函数我
"<<endl;
}
41
下面程序运行结束时,会自动调用
以上析构函数。
#include <iostream.h>
#include "test2.h"
void main()
{ test x;
cout<<"退出 main 函数 "<<endl;
}
执行这个程序,产生如下输出,
退出 main 函数
Destructor is active 显示一下己调用析构函数我
全局对象或静态对象的析构函数在程序运行结束之前调用
42
析构函数和对象数组
对象数组的每一个元素调用一次析构函数。
#include <iostream.h>
#include "test2.h"
void main()
{
test array[3];
cout<<"退出 main 函数 "<<endl;
}
执行这个程序,产生如下输出,
退出 main 函数
Destructor is active 显示一下己调用析构函数我
Destructor is active 显示一下己调用析构函数我
Destructor is active 显示一下己调用析构函数我
43
析构函数和运算符 delete
用运算符 delete删除一个动态对象时,它首先调用析构函数,
然后再释放内存。
#include <iostream.h>
#include "test2.h"
void main()
{
test *prt = new test[5];
delete []prt;
}
执行这个程序,产生如下输出,
Destructor is active 显示一下己调用析构函数我
Destructor is active 显示一下己调用析构函数我
Destructor is active 显示一下己调用析构函数我
Destructor is active 显示一下己调用析构函数我
Destructor is active 显示一下己调用析构函数我
44
缺省析构函数
当类定义时没有定义任何析构函数,则C++自动建立一个析构
函数,
称为缺省构造函数,其形式如下,
类名,:~构函数名 ( )
{
}
函数体内什么事也没做,但它释放了对象的内存。
总结,
构造函数可给对象赋初值。
析构函数释放对象的内存。
当定义对象变量时自动调用构造函数,在程序结束时,程序自动
调用析构函数来释放对象的内存。
当定义对象指针时用 new来申请对象内存,new去调用构造函数。
用 delete删除对象时,它首先调用析构函数,然后再释放内存。
一般程序中,析构函数可缺省,构造函数最好不缺省。
45
3.2.4 重载构造函数
C++允许重载构造函数,这些构造函数之间以它们所带参数的个类
或类型的不同而区分。
例 3.9
#include <iostream.h>
#include <stdlib.h>
class timer {
int seconds;
public,
timer() { seconds=0; } // 无参数的构造函数,给 seconds清
零
timer(char *t) { seconds=atoi(t); } // 数字串参数的构造函数
timer(int t) { seconds=t; } // 一个整数参数的构造函
数
timer(int min,int sec)
{ seconds=min*60+sec; } // 二个整数参数的构造函数
int gettime() {return seconds; }
};
46
void main()
{ timer a,b(10),c(“20”),d(1,10);
cout<<“seconds1=“<<a.gettime()<<endl;
cout<<“seconds2=“<<b.gettime()<<endl;
cout<<“seconds3=“<<c.gettime()<<endl;
cout<<“seconds4=“<<d.gettime()<<endl;
}
47
3.2.5 拷贝构造函数
拷贝构造函数是一种特殊的构造函数。它用于依据已存在的对象建
立一个新对象。
1,自定义拷贝构造函数,形式如下,
classname(const classname &ob)
{ //拷贝构造函数函数体 }
例 3.10
#include <iostream.h>
class point {
int x,y;
public,
point (int a,int b) { x=a; y=b; } //构造函数
point (const point &p) { x=2*p.x; y=2*p.y; }//拷贝构造函数
void print() { cout<<x<<“,<<y<<endl; }
};
void main()
{ point p1(30,40); // 定义对象 p1
point p2(p1); //显式调用拷贝构造函数,初始化对象 p2
p1.print();
p2.print();
}
本例除了显式调用拷贝构造函
数外,还可以采用赋值形式调
用拷贝构造函数。
point p2=p1;
48
2.缺省的拷贝构造函数
如果没有编写自定义的拷贝构造函数,C++会自动地将一个已存在
的对象复制给新对象,这种按成员逐一复制的过程是由缺省拷贝
构造函数自动完成的。
例 3.11
#include <iostream.h>
class point {
int x,y;
public,
point (int a,int b) { x=a; y=b; } //构造函数
void print() { cout<<x<<“,<<y<<endl; }
};
void main()
{ point p1(30,40); // 定义对象 p1
point p2(p1); //显式调用缺省拷贝构造函数,初始化对象 p2
point p3=p1; //赋值形式调用缺省拷贝构造函数,初始化对象
p3
p1.print();
p2.print();
p3.print();
}
说明,
( 1) 与一般的构造函数一样,拷贝构造函数没
有返回值。
( 2) 通常缺省的拷贝构造函数是能够胜任工作
的,但若类中有指针类型时,按成员复制的方法
会产生错误。
49
拷贝初始化的构造函数
1,自定义一个拷贝初始化的构造函数,
X::X(X&)
类名,:拷贝初始化的构造函数名 (与类名同 ) (类名 & 形参对象 )
test::test(test& t)
class point{
int x,y;
public,
point(int a,int b) {x=a; y=b;}
point(point& p) { x=2*p.x; y=2*p.y; }
};
void main( )
{ point p1(30,40);
point p2(p1);
}
从这个例子中可看出,可用拷贝初始化的构造函数把一个己初始
化的对象 p1再去初始化另一个对象 p2,这就是拷贝初始化的构造
函数的作用。
50
2,系统自动引成的 (缺省的 )拷贝初始化的构造函数,
class point{
int x,y;
public,
point(int a,int b) {x=a; y=b;}
};
void main( )
{ point p1(30,40);
point p2(p1);
}
这时 p2 与 p1 相同。相当于有如下的拷贝初始化的构造函数,
point::point(point& p)
{
x=p.x;
y=p.y;
}
51
*3.2.6构造函数类型转换
构造函数起类型转换作用,当对象出现在 = 号左边时,右边的数
据被构造 函数转换成对象赋给左边。
#include <iostream.h>
class complex {
private,float x;
float y;
public,complex(float u,float v)
{ x=u; y=v; }
void print()
{ cout<<x<<"+"<<y<"i"<<endl; }
}
void main()
{ complex a(2.0,3.0);
a.print();
a=complex(10.0,20.0);
a.print();
}
52
*3.2.7 构造函数和对象的初始化
这一节说明三个问题,
1,使用缺省拷贝初始化的构造函数在有些情况下会产生
问题。
2,实参对象向形参对象以值调用方式传递参数时,为了
初始化形参,会自动调用拷贝初始化构造函数,调用结
束形参消亡时会自动调用析构函数;当一个函数的返回
值是对象时,相当于有一个隐藏对象,这时也会调用拷
贝初始化的构造函数,隐藏对象的生存期结束时,同样
调用析构函数。
3,因此对有以上二种情况的程序,在类中要恰当地定义
拷贝初始化构造函数,保证程序正确的工作。
53
*3.2.8 对象相互赋值
一般情况下同类的对象可以相互赋值,但有时也会出问题
1,缺省的赋值操作:一个对象的成员值赋给另一个对象相应的成员。
2,这种情况有时也会出问题;如
class string {
private,
char *str;
public,
string(char *s)
{ str=new char[str(s)+1];
strcpy(str,s);
}
~string() { delete str; }
};
void main( )
{ string s1("hello"),s2("world");
s2=s1;
}
这个 s2=s1 赋值操作使 s1,s2都指向
"hello",这导致二个问题:首先
"world" 逃走了再也无法被访问,
这个问题还不大;其次当程序结束
时执行二次析构函数,存储的内存
被释放二次,这是个严重的错误。
54
解决的方法是重新定义 =号 (赋值号 )操作,这叫运算符重载。
string& string::operator= (string& a)
{ if(this==&a) //如果自已赋自已,则返回自已,防止 s=s这样的
赋值。
return *this;
delete str;
str = new char[strlen(a.str)+1];
strcpy(str,a.str);
return *this;
}
operator= 是赋值运算符,a 是用于右边的对象。
55
【 例 】 完整的例子
#include <iostream.h>
#include <string.h>
class string {
private,char *str;
public,string(char *s);
string(string& a);
string& operator= (string& a);
string& operator= (char *s); //定义二种赋值操作
void print() { cout<<str<<endl; }
~string() { delete str; }
};
string::string(char *s)
{
str = new char[strlen(s)+1];
strcpy(str,s);
}
string::string(string& a)
{
str = new
char[strlen(a.str)+1];
strcpy(str,a.str);
}
56
接 【 例 】
string& string::operator= (string& a)
{ if(this==&a)
return *this;
delete str;
str = new char[strlen(a.str)+1];
strcpy(str,a.str);
return *this;
}
string& string::operator= (char *s)
{ delete str;
str = new char[strlen(s)+1];
strcpy(str,s);
return *this;
}
void main( )
{ string s1(“here”),
s2("there");
s1.print();
s2.print();
s2=s1;
s2.print();
s2="hello";
s2.print();
}
57
3.3 对象数组与对象指针
3.3.1 对象数组
当一个类有许多对象时,可以用类定义对象数组。
例 3.12
#include <iostream.h>
class exam{
int x;
public,
void set_x(int n) { x=n; }
int get_x() { return x; }
};
void main()
{ exam ob[4];
int i;
for (i=0; i<4; i++)
ob[i].set_x(i);
for (i=0; i<4; i++)
cout<<ob[i].get_x()<<' '<<;
cout << endl;
}
58
如果类中有构造函数带有参数,则可定义对象数组时,用初值表,
例 3.13
#include <iostream.h>
class exam{
int x;
public,
exam(int n) { x=n; }
int get_x() { return x; }
};
void main()
{ exam ob[4]={11,22,33,44};
int i;
for (i=0; i<4; i++)
cout<<ob[i].get_x()<<' '<<;
cout << endl;
}
59
如果类中有构造函数不带有参数或缺省构造函数,定义对象数组时
自动调用。
例 3.14
#include <iostream.h>
class point {
private,
int x,y;
public,
point()
{ x=5; y=5; } //不带参数的构造函数
point(int a,int b)
{ x=a; y=b;} //带参数的构造函数
int getx()
{ return x;}
int gety()
{ return y;}
};
60
void main()
{ point op(3,4); //调用带参数的构造函数
cout<<"op x="<<op.getx()<<endl;
cout<<"op y="<<op.gety()<<endl;
point op1[3]={(1,2),(3,4),(5,6)}; //通过初值表给对象数组赋初值
cout<<"op1[1] x="<<op1[1].getx()<<endl;
cout<<"op1[1] y="<<op1[1].gety()<<endl;
point op_array[20]; //调用不带参数的构造函数
cout<<"op_array[15] x="<<op_array[15].getx()<<endl;
cout<<"op_array[15] y="<<op_array[15].gety()<<endl;
}
61
3.3.2 对象指针
当需要时,可以用类定义对象指针。
1,用指针引用单个对象,可用 (.)或 (->)引用成员。
例 3.15
#include <iostream.h>
class exe{
int x;
public,
void set_a(int a) { x=a; }
void show_a() { cout << x endl; }
};
void main()
{ exe ob,*p;
ob.set_a(2);
ob.show_a();
p=&ob;
p->show_a();
}
62
2,用指针引用对象数组。
void main()
{ exe ob[2],*p;
ob[0].set_a(10);
ob[1].seta(20);
p=ob;
p->show_a();
p++;
p->shoe_a();
}
63
3.3.3 this 指针
当一个成员函数被调用时,C++自动产生一个参数,这个参数是一
个类指针,叫 this,可以指向该类的一个对象,这个对象就是接
受该函数调用的对象。当有个对象调用同一成员函数时,this 分
别指向不同的对象。
例 3.16
#include <iostream.h>
class exth {
int i;
public,
void load(int val) { this->i=val; } //与 i=val; 相同
int get() { return this->i; } //与 return i 相同
};
void main()
{ exth obj,objj;
obj.load(100); //this 指向 obj
objj.load(200); //this 指向 objj
cout<<obj.get()<<endl; //this 指向 obj
cout<<objj.get()<<endl; //this 指向 objj
}
64
如,Location LA;
LA.init(5,2);
这时就有一个 this --> LA
假定类中一个成员函数 assign(),
void Location::assign(Location& p)
{ if (this == &p);
else { X=p.X; Y=p.Y }
}
当有二个对象 Location LA,LB;
若 LA.assign(LA); 则 this 指向 LA,形参 &p是实参 LA,则
this==&p成立,执行语句,表示同一个对象之间相互赋值没有
意义。若 LA.assign(LB); 则 this 指向 LA,形参 &p是实参 LB,
this==&p 不等于,则 LA.X(或 this->X)=p.X; LA.Y=p.Y;
这里就可以用来判别接受引用的对象是自己还是另外的对象 。
65
3.4 向函数传递对象
对象可作为函数的形参和实参,实参和形参的传递有传值调用
例 3.17
#include <iostream.h>
class tr {
int i;
public,
tr(int n) { i=n; }
void set_i(int n) { i=n; }
int get_i() { return i; }
};
void sqr_it(tr ob)
{ ob.set_i(ob.get_i()*ob.get_i()); //形参对象 i=i*i
cout << "形参对象 i 的值为 "<<ob.get_i()<<endl;
}
void main()
{ tr obj(10); //定义对象 obj
sqr_it(obj); //obj 为实参调用函数 sqr_it(tr
ob)
cout << "但是实参 obj 的 i 还是 ";
cout << obj.get_i()<<endl;
}
66
传地址调用
例 3.18
#include <iostream.h>
class tr {
int i;
public,
tr(int n) { i=n; }
void set_i(int n) { i=n; }
int get_i() { return i; }
};
void sqr_it(tr *p)
{ p->set_i(p->get_i()*p->get_i());
cout << "对象指针 p-> i 的值为 "<<p->get_i()<<endl;
}
void main()
{ tr obj(10); //定义对象 obj
sqr_it(&obj); //&obj 为实参调用函数 sqr_it(tr *p)
cout << "实参 obj 的 i 变成 ";
cout << obj.get_i()<<endl;
}
67
3.5 静态成员
在类定义中,用 static 说明的成员称为静态成员,有静态数据成员
和静态函数成员。
3.5.1 静态数据成员
一个类无论定义多少个对象,都共用同一个静态数据成员。
例 3.19
#include <iostream.h>
class Student {
static int count; //声明静态数据成员 count,用于统计学生总数
int StudentNo;
public,
Student() { count++; StudentNo=count; }
void print(){
cout << "学生 "<<StudentNo<<" ";
cout << "总数 ="<<count<<endl;
}
};
int Student::count=0; //静态数据成员可在程序中可访问
68
void main()
{ Student S1; //学生 1
S1.print();
cout<<"-------------\n";
Student S2;
S1.print();
S2.print();
cout<<"-------------\n";
Student S3;
S1.print();
S2.print();
S3.print();
cout<<"-------------\n";
Student S4;
S1.print();
S2.print();
S3.print();
S4.print();
}
说明 (1) 静态数据成员属于类,用 类名,:数
据成员名。
(2) 静态数据成员不能在类中进行初始化,
必须在类外进行。
(3) 静态数据成员是在编译时分配空间,程
序结束时才消失。
(4) C++用静态数据成员的目的是可不必使
用全局变量
69
3.5.2 静态成员函数
一个类无论定义多少个对象,都共享同一个静态成员函数。一般
而言,静态成员函数访问的基本上是静态数据成员或全局变量。
例 3.21
#include <iostream.h>
class small_cat{
private,
double weight; //普通数据成员,表示一只猫的重量
static double total_weight;
static double total_number;
public,
small_cat(double w)
{ weight=w; total_weight+=w; total_number++; }
void display()
{ cout <<"这只小猫的重量是 "<<weight<<"磅 "<<;endl; }
static void total_disp() //静态成员函数
{ cout <<"小猫的总数是 "<<total_number<<endl;
cout <<"小猫的总重量是 "<<total_weight<<endl;
}
};
70
double small_cat::total_weight=0; //静态数据成员初始化
double small_cat::total_number=0; //静态数据成员初始化
void main()
{ small_cat w1(1.8),W2(1.6),w3(1.5);
W1.display()
W2.display()
W3.display()
small_cat::total_disp();
}
说明
(1) 静态成员函数可以定义成内嵌的,也可以在类外定义。在外面
不要用 static。
(2) 编译系统将静态成员函数定为内联。
(3) 静态成员函数可以在定义对象之前处理静态数据成员。
(4) 静态成员函数没有 this指针,调用时用 类名,:静态成员函数名。
(5) 一般而言,静态成员函数不访问类中的非静态成员,而只访问
静态成员。
71
3.6 友元
类的封装性,使类只能通过成员函数来访问私有成员。这是好事
但也有一定的限制太死的感觉。能否开个小孔口,必要时作个破
例,使外界函数、别的类的个别成员函数、甚致别的类的所有成
员函数可访问某个类的私有成员。这个方法称为友元,有友元函
数、友元成员和友元类三种。
3.6.1 友元函数
在类定义时,在类内定义一个函数,该函数前加上 friend 表示这
个函数不是成员函数,而是本类的友元函数。友元函数可以内嵌
或外部定义。友元函数可以访问本类的私有成员。
72
例 3.22
#include <iostream.h>
#include <string.h>
class girl {
char *name;
int age;
public,
girl(char *n,int d) //构造函数
{ name=new char[strlen(n)+1];
strcpy(name,n);
age=d; }
friend void disp(girl &); //声明 disp是本类的友元函数
~girl() { delete name; } //析构函数
};
void disp(girl &x) //定义友元函数
{ cout << "女孩的名是, "<<x.name<<",年龄是, "<<x.age<<endl; }
void main()
{ girl e("Chen Xingwei",18);
disp(e);
}
73
说明,
1.友元函数不是类的成员函数,因此在外部定义友元函数时,不必
加 类名,,
2.友元函数一般带有一个该类的入口参数。并做成对象引用或指针
的方式。
3,一个友元函数可以做成多个类的友元,能够访问相应的所有类的
数据。
4.友元函数破坏了数据的隐蔽性,降低程序的可维护性,应该谨用。
例 3.23
#include <iostream.h>
#include <string.h>
class boy;
class girl {
char name[25];
int age;
public,
void init(char N[],int A);
friend void prdata(const girl plg,const boy plb);
//声明 prdata是 girl类的友元函数
};
74
void girl::init(char N[],int A)
{ strcpy(name,N); age=A; }
class boy {
char name[25];
int age;
public,
void init(char N[],int A);
friend void prdata(const girl plg,const boy plb);
//声明 prdata是 boy类的友元函数
};
void boy::init(char N[],int A)
{ strcpy(name,N); age=A; }
void prdata(const girl plg,const boy plb) //定义友元函数
{ cout << "姓名, "<<plg.name<<endl;
cout << "年龄, "<<plg.age<<endl;
cout << "姓名, "<<plb.name<<endl;
cout << "年龄, "<<plb.age<<endl;
}
void main()
{ girl G1,G2,G3;
boy B1,B2,B3;
G1.init("Stacy",12);
G2.init("Judith",13);
G3.init("Leah",12);
B1.init("Jim",11);
B2.init("Michael",13);
B3.init("Larry",12);
prdata(G1,B1);
prdata(G2,B2);
prdata(G3,B3);
}
75
3.6.2 友元成员
在类定义时,在类内说明一个函数,该函数是另一个类的成员函
数,前加上 friend表示该另类的成员函数,又是本类的友元成员。
友元成员函数可以访问它自己类私有成员又可本类的私有成员。
例 3.24
#include <iostream.h>
#include <string.h>
class girl;
class boy {
char *name;
int age;
public,
boy(char *n,int d) //构造函数
{ name=new char[strlen(n)+1];
strcpy(name,n);
age=d;
}
void disp(girl &); //声明 disp是成员函数
~boy() { delete name; } //析构函数
};
76
class girl {
char *name;
int age;
public,
girl(char *n,int d) //构造函数
{ name=new char[strlen(n)+1];
strcpy(name,n);
age=d;
}
friend void boy::disp(girl &); //声明 boy类的成员函数 disp()
是
//本类的友元成员函数
~girl() { delete name; } //析构函数
};
void boy::disp(girl &x) //定义友元函数
{ cout << "男孩的名是, "<<name<<",年龄是, "<<age<<endl;
//访问本类对象成员
cout << "女孩的名是, "<<x.name<<",年龄是, "<<x.age<<endl;
//访问友类成员
}
77
void main()
{ boy b("Chen hao",25);
girl e("Zhang wei",18);
b.disp(e);
}
说明,
(1) 一个类的成员函数作为另一类的友元函数时,必须先定义这个
类。
(2) 必要时可用“向前引用法”。
78
3.6.3 友元类
在类定义时,在类内说明一个类 (该类前面已定义 ),前加上
friend表示该类是本类的友元类。友元类的所有成员函数可以访
问它自己类私有成员又可本类的私有成员。
例 3.25
#include <iostream.h>
#include <string.h>
class girl;
class boy {
char *name;
int age;
public,
boy(char *n,int d) //构造函数
{ name=new char[strlen(n)+1];
strcpy(name,n);
age=d;
}
void disp(girl &); //声明 disp()是成员函数
~boy() { delete name; } //析构函数
};
79
class girl {
char *name;
int age;
friend boy; //声明 boy 类是 girl 类的友元类
public,
girl(char *n,int d) //构造函数
{ name=new char[strlen(n)+1];
strcpy(name,n);
age=d;
}
~girl() { delete name; } //析构函数
};
void boy::disp(girl &x) //定义成员函数
{ cout << "男孩的名是, "<<name<<",年龄是, "<<age<<endl;
//访问本类对象成员
cout << "女孩的名是, "<<x.name<<",年龄是, "<<x.age<<endl;
//访问友类对象成员
}
void main()
{ boy b("Chen hao",25);
girl e("Zhang wei",18);
b.disp(e);
}
80
3.7 对象成员
在定义类的数据成员时,可以把别的类的对象作为成
员称为对象成员。
1,对象成员的类定义应该再先。
2,重要的问题是后定义的类中的构造函数要调用这些
对象成员所在类的构造函数,这时后定义类的构造
函数要写成如下形式,
X::X(参数表 0):成员 1(参数表 1),成员 2(参数表 2),...,成员 n(参数表 n)
{ //......,
}
3,如果某项的参数表为空,则相应项可省略。
81
【 例 3.13】 分析下面程序中构造函数与析构函数的调用顺序。
#include <iostream.h>
class object {
private,int val;
public,object();
object(int i);
~object();
};
object::object()
{ val=0;
cout<<"object 类的无参数的构造函数 "<<endl;
}
object::object(int i)
{ val=i;
cout<<"object 类的有参数的构造函数, "<<val<<endl;
}
object::~object()
{
cout<<"object 类的析构函数, 释放 "<<val<<endl;
}
82
接 【 例 3.13】 例之 1 class container {
private,object one;
object two; //二个对象成员
int data; //普通成员
public,container();
container(int i,int j,int k);
~container();
};
container::container() //:tow(),one() 无参的构造函数可省略成员表
{ data=0;
cout<<"container 类的无参数的构造函数 "<<endl;
}
container::container(int i,int j,int k):two(i),one(j)
{ data=k;
cout<<"container 类的有参数的构造函数 "<<endl;
}
container::~container()
{
cout<<"container 类的析构函数 "<<endl;
}
83
接 【 例 3.13】 例之 2
void main()
{
container anobj(5,6,10);
}
主函数运行结果,
object 类的有参数的构造函数, 6 (one)
object 类的有参数的构造函数, 5 (two)
container 类的有参数的构造函数
container 类的析构函数
object 类的析构函数, 释放 5 (two)
object 类的析构函数, 释放 6 (one)
84
对无参的构造函数调用如下,
void main()
{
container bnobj;
}
主函数运行结果,
object 类的无参数的构造函数 (one)
object 类的无参数的构造函数 (two)
container 类的无参数的构造函数
container 类的析构函数
object 类的析构函数 释放 0 (two)
object 类的析构函数, 释放 0 (one)
4,初始化列表的方法还可用于 const成员和引用性成员。
class example {
private,const int num;
int& ret;
public,example(int n,int f):num(n),ret(f)
{ }
};
85
例 3.26
#include <iostream.h>
#include <string.h>
class string {
private,
char *str;
public,
string(char *s)
{ str=new char[strlen(s)+1];
strcpy(str,s);
}
void print()
{ cout<<str<<endl; }
~string()
{ delete str; }
};
86
class girl {
private,
string name; //name 为 string类的对象,为 girl类的成员
int age;
public,
girl(cha *st,int ag):name(st) //定义 girl类的构造函数
{ age=ag; }
void print()
{ cout<<"姓名, "<<name.print();
cout<<"年龄, "<<age<<endl;
}
};
void main()
{ girl obj("陈 好 ",25);
obj.print();
}
第三章
类和对象
2
3.1 类与对象的基本概念
3.1.1 结构与类
1,结构的扩充
C++中对 C语言的结构类型进行了扩充,它还可以含有函数。如,
struct complex{
double real; //复数的实部
double imag; //复数的虚部
void init (double r,double I) //给 real和 imag赋初值
{ real=r; imag=i; }
double realcomplex() //取 复数 的 实部 值
{ return real;}
double imagcomplex() //取 复数 的 虚部 值
{ return imag;}
double abscomplex() //求 复数 的 绝对 值
{ double t;
t=real*real+imag*imag;
return agrt(t);
}
};结构类型中的数据和函数,分别称为数据成员和函数成员 。
3
**例 3.1
#include <iostream.h>
#include <math.h>
struct complex{
double real; //复数的实部
double imag; //复数的虚部
void init (double r,double I) //给 real和 imag赋初值
{ real=r; imag=i; }
double realcomplex() //取 复数 的 实部 值
{ return real;}
double imagcomplex() //取 复数 的 虚部 值
{ return imag;}
double abscomplex() //求 复数 的 绝对 值
{ double t;
t=real*real+imag*imag;
return sqrt(t);
}
} A;
void main()
{ A.init(1.1,2.2);
cout<<“复数 A的实部 =,<<A.realcomplex()<<endl;
cout<<“复数 A的实部
=,<<A.imagcomplex()<<endl;
cout<<“复数 A的绝对值
=,<<A.abscomplex()<<endl;
}
4
2,类的定义 (声明 )
C++中说明类的一般形式为
clsaa 类名 { private,(可缺省 )
私有数据成员和函数成员
protected,
保护段
public,
公有数据成员和函数成员
};
class complex{
private,
double real,imag;
public,
void init (double r,double i) { real=r; imag=i; }
double realcomplex() { return real;}
double imagcomplex() { return imag;}
double abscomplex()
{ double t; t=real*real+imag*imag; return sqrt(t); }
};
类 class 和结构 sturct 的主要区别是:
在缺省 private 时,
在类中,成员是私有的
在结构中,成员是公有的
5
例 3.2
#include <iostream.h>
#include <math.h>
class complex{
private,
double real,imag;
public,
void init (double r,double i) { real=r; imag=i; }
double realcomplex() { return real;}
double imagcomplex() { return imag;}
double abscomplex()
{ double t; t=real*real+imag*imag; return sqrt(t); }
};
void main( )
{ complex A;
A.init(1.1,2.2);
cout<<“复数 A的实部 =,<<A.realcomplex()<<endl;
cout<<“复数 A的实部 =,<<A.imagcomplex()<<endl;
cout<<“复数 A的绝对值 =,<<A.abscomplex()<<endl;
}
6
3.1.2 成员函数的定义
第一种方式是在类定义中只给出成员函数的原型(或者说声明),
而成员函数在类的外部定义。在外部定义的一般形式是,
返回类型 类名,:函数名 (参数表 ){
// 函数体
}
class point{ // 定义类 point
private,
int x,y; // 数据成员,二个整数作为 x,y 坐标
public,
void setpoint(int,int); // 函数成员,名为 setpoint,有二个
整数形参,用于设置坐标值,具体如何在外部再定义,这叫函数
的原型(或者说声明)
int getx(); //取 x 坐标值的成员函数 getx()的函数原型
int gety(); //取 y 坐标值的成员函数 gety()的函数原型
}; //类定义结束
viod point::setpoint(int a,int b) //成员函数 setpoint 具体定义
{ x=a; y=b; }
int point:,getx() //成员函数 getx 具体定义
{ return x; }
int point:,grty() //成员函数 gety 具体定义
{ return y; }
7
说明
( 1)在所定义的成员函数名之前应缀上类名,在类名和函数名之
间应加上分隔符,::”,例如上面例子中的,point::”。
( 2)在定义成员函数时,对函数所带的参数,不但要说明它的类
型,还要指出其参数名。
( 3)在定义成员函数时,其返回类型一定要与函数原型中声明的
返回类型匹配。
第二种方式是将成员函数定义在类的内部,即定义为内置函数。
又可用两种,
(1) 隐式定义 即函数的原型和定义都在类的内部完成
class point{ // 定义类 point
private,
int x,y; // 数据成员,二个整数作为 x,y 坐标
public,
void setpoint(int a,int b) // 函数成员
{ x=a; y=b; }
int getx() { return x; }
int gety() { return y; }
}; //类定义结束
8
(2) 显式定义
即在类定义中仍只给出成员函数的原型(或者说声明),而成员函
数在类的外部定义。但函数定义前冠以关键字, inline”,以此显
式地说明这是一内置函数。
class point{ // 定义类 point
private,
int x,y; // 数据成员,二个整数作为 x,y 坐标
public,
void setpoint(int,int); // 函数成员 setpoint,有二个整数形参
int getx(); //取 x 坐标值的成员函数 getx()的函数原型
int gety(); //取 y 坐标值的成员函数 gety()的函数原型
}; //类定义结束
inline viod point::setpoint(int a,int b) //内置成员函数具体定义
{ x=a; y=b; }
inline int point:,getx() //内置成员函数 getx 具体定义
{ return x; }
inline int point:,grty() //内置成员函数 gety 具体定义
{ return y; }
把成员函数设置成内置,可提高执行的效率,但函数体较长的成员
函数都不设置成内置。
9
3.1.3 对象的定义及引用
1,类与对象的关系
可以用整型 int 和整型变量 i 之间的来类比。 int i ;
2,对象的定义
( 1) 在声明类的同时,直接定义对象 op1,op2
class point{ // 定义类 point
private,
int x,y; // 数据成员,二个整数作为 x,y 坐标
public,
void setpoint(int,int);
int getx();
int gety();
} op1,op2;
( 2) 声明了类之后,在使用时再定义对象,定义的格式与一般变
量的定义格式相同,point op1,op2;
说明:( 1) 声明了一个类便声明了一种类型,它并不接收和存储
具体的值,只有定义了对象后,系统才为对象并且只为对象分配
存储空间。
( 2) 在声明类的同时定义的对象是一种全局对象,在它的
生存期内任何函数都可以使用它。
10
3.对象的引用
对象的引用是指对象的成员的引用,不论是数据成员,还是成员
函数,只要是公有的,就可以被外部函数直接引用,引用格式是:
对象名, 数据成员名 或 对象名, 成员函数名(实参表)
例 3.3
#include <iostream.h>
class point{
private,
int x,y;
public,
void setpoint(int a,int b)
{ x=a; y=b; }
int getx() { return x; }
int gety() { return y; }
};
void main()
{ point op1,op2;
int i,j;
op1.setpoint(1,2);//调用 op1的 setpoint(),初始化对象 op1
op2.setpoint(3,4);//调用 op2的 setpoint(),初始化对象 op2
i=op1.getx(); //调用 op1的 getx(),取 op1的 x值
j=op1.gety(); //调用 op1的 gety(),取 op1的 y值
cout<<“op1 i=”<<i<<,op1 j=”<<j<<endl;
i=op2.getx(); //调用 op2的 getx(),取 op2的 x值
j=op2.gety(); //调用 op2的 gety(),取 op2的 y值
cout<<“op2 i=”<<i<<,op2 j=”<<j<<endl;
}
11
说明
( 1) 本例中 op1.setpoint(1,2)实际上是 op1.point::setpoint(1,2)的一
种缩写形式。
( 2) 外部函数不能引用对象的私有成员。
void main()
{ point op;
int i,j;
op.setpoint(1,2);
i=op.x; //错误,不能直接引用对象的私有成员
j=op.y; //错误,不能直接引用对象的私有成员
cout<<“op i=”<<i<<,op j=”<<j<<endl;
}
( 3)若定义的是对象指针,则用,->”操作符。
point *op;
op->setpoint(5,6);
i=op->getx();
j=op->gety();
12
4,对象赋值语句
同类型的对象之间也可以进行赋值,当一个对象赋值给另一个对
象时,所有的数据成员都会逐位拷贝。
例 3.4
#include <iostream.h>
class myclass{
int a,b;
public,
void set(int i,int j) { a=i; b=j; }
void show() { cout<<a<<“,<<b<<endl; }
};
void main( )
{ myclass o1,o2;
o1.set(20,5);
o2=o1; //将对象 o1的值赋给将对象 o2
o1.show();
o2.show();
}
(1)对象赋值时,两
个对象的类型必须
相同。
(2)对象赋值时,仅
仅使对象中的数据
相同,两个对象仍
是独立的。
(3)两个对象赋值时,
多数情况是成功的,
但当类中有指针成
员时,可能会发生
错误。
13
3.1.4 类的作用域
所谓类的作用域就是指在类的声明中的一对花括号所形成的作用域。
一个类的成员函数可以不受限制地引用该类的数据成员,而在该
类作用域之外,对该类的数据成员和成员函数的引用则要受到一
定的限制,有时甚至是不允许的。这体现了类的封装性。
例 3.5
#include <iostream.h>
class abc{
public,
int i;
void set(int);
void disp()
{ cout<<“i=“<<i<<endl; }//可以 直接引用类中的数据成员 i
};
void main()
{ abc ob;
ob.set(2); //通过成员函数给数据成员 i赋初值 2
ob.disp();
i=1; //非法,不能直接引用类中的数据成员 i,可
// 改为 ob.i=1;
ob.disp();
}
void abc::set(int si)
{ i=si;} //成员函数 可以 直接引用类中的数据成员 i
int fun()
{return i; } //非法,不能直接引用类中的数据成员 i
14
*3.1.5 定义类
1,说明类
C++中说明类的一般形式为
clsaa 类名 { private,(可缺省 )
私有数据和函数 (私有成员 )
protected,
保护段
public,
公有数据和函数 (公有成员 )
};
2,类标识符
3,类体 (成员函数体 )
成员函数可以在定义类时同时说明和定义
class Location {
private,
int X,Y;
public,
void init(int initX,int initY)
{ X=initX;
Y=initY;
};
int GetX() { return X;};
int GetY() { return Y;};
}
也可先说明另外单独定义。
class Location {
private,
int X,Y;
public,
void init(int initX,int initY)
int GetX();
int GetY();
};
void Location::init(int initX,int
initY)
{ X=initX;
Y=initY;
}
int Location::GetX()
{ return X; }
int Location::GetY()
{ return Y; }
返回类型 类名,:成员函数名 (参数说明
)
{
类体
}
16
*3.1.6使用类
1,对象说明 (定义对象 )
可以使用类在程序中定义变量,这种变量即为对象 Location A1,A2;
【 例 2.7】 Location 类的一个完整程序,可上机。
#include <iostream.h>
class Location {
private,
int X,Y;
public,
void init(int initX,int initY)
int GetX();
int GetY();
};
void Location::init(int initX,int initY)
{ X=initX;
Y=initY; }
int Location::GetX()
{ return X; }
int Location::GetY()
{ return Y; }
void main( )
{ Location A1,A2;
A1.init(5,3);
A2.init(6,8);
cout <<A1.GetX()<<" "<<A1.GetY()<<endl;
cout <<A2.GetX()<<" "<<A2.GetY()<<endl;
// 或
Location *pA1,*pA2;
pA1=&A1; pA2=&A2;
cout <<pA1->GetX()<<" "<<pA1->GetY()<<endl;
cout <<pA2->GetX()<<" "<<pA2->GetY()<<endl;
}
17
2,封装性
用 class 定义类,实现了封装,C++中还可用 struct,union等
3,类的存取控制
类的使用者有如下三种,
① 类本身
② 一般用户
③ 派生类
每一种使用者对类的数据有不同的权限
① private
② public
③ protected
4,类的私有成员
private
只有类本身 (或类的友元 )可以存取私有成员。
要使类在程序中被使用,所以类必须至少有一个非私有成员。
5,类的公有成员
pubilc
在 public部分定义的内容允许被其它对象无限制地存取。另外
C++可以有限制地使用 public成员存取 private成员。
18
【 例 】 使用类,设计模似数字式时钟的程序
一般较大的程序用模块化的方法,分成三个文件
① *.h 文件,用来描述类。
② *.cpp 文件,用来实现类,其中是成员函数的定义。
③ *.cpp 文件,用来写 main( ) 函数。
//clock.h
class Clock{
private,
int hour,minute,second;
public,
void init();
void update();
void display();
};
19
//clock.cpp
#include <iostream.h>
#include "clock.h"
void Clock::init()
{ hour=minute=secind=0; }
void Clock::update()
{ second++;
if(second==60){ second=0; minute++;}
if(minute==60){ minute=0; hour++;}
if(hour==24) hour=0;
}
void Clock::display()
{ cout<<“hour<<“:”<<minute<<“:”<<second<<endl;
}
20
//mymain.cpp
#inclode "clock.cpp"
void main( )
{ Clock clockA,clockB;
cout<<"CLOCK A:"<<endl;
clockA.init();
for(int i=0;i<10;i++)
{ clockA.update();
clockA.display();
}
cout<<"CLOCK B:"<<endl;
clockB.init();
for(i=0;i<20;i++)
{ clockB.update();
clockB.display();
}
}
21
(1) 函数 main()不能访问对象 clockA内部私有数据和函数,Clock类
的修改不必改 main()中程序。
(2) Clock类受封装保护。
(3) 类只需定义一次,就可通过实例化建立多个对象。
(4) 三个清楚的模块。
6,类的保护成员
protected
当定义类是为了以后作其它类的基类时,可以定义类的保护成员
。
22
*3.1.7 内联成员函数
在类定义内只有成员函数的声明,而在外面定义完整的成员函数,
并且前面加 inline,这样的成员函数叫内联成员函数。编译器会
自动用函数体去替换函数声明,其目的是提高程序的效率。
class Location {
private,
int X,Y;
public,
void init(int initX,int initY); //成员函数的声明
int GetX(); //成员函数的声明
int Gety(); //成员函数的声明
};
inline void Location::init(int initX,int initY)
{ X = initX; Y = initY; }
inline int Location::GetX()
{ return X; }
inline int Location::GetY()
{ return Y; }
考虑到程序的运行效率,简单的成员函数一般与类
定义同时定义或 被实现为内联函数。
23
*3.1.8成员函数的重载及其缺省参数值
在类说明中可以定义同名的成员函数,称为重载。这时这两个函数
应该有不同的形参情况及函数体,完成不同的程序功能。
在类说明中定义成员函数时可以对形参给以缺省值,称为带缺省参
数值的成员函数。
#include <iostream.h>
class Location {
private,
int X,Y;
public,
void init(int=0,int=0); //带缺省参数值的成员函数
void valueX(int); //这两个函数同名,都叫 valueX
int valueX( ); //但有不同的形参和返回值
void valueY(int);
int valueY( );
};
24
void Location::init(int initX,int initY)
{ X = initX; Y = initY; }
void Location::valueX(int val) { X=val; }
int Location::valueX( ) { return X; }
void Location::valueY(int val) { Y=val; }
int Location::valueY( ) { return Y; }
void main( )
{ Location LA,LB;
LA.init( ); //对象 LA 的数据成员 X 和 Y 被置为 0
LA.valueX(5); //将成员 X 重置为 5
cout<<LA.valueX( )<<endl<<LA.valueY( )<<endl;
LB.init(6,2); //对象 LB 的 数据成员 X 和 Y 被置为 6 和 2
LB.valueY(4); //将成员 Y 重置为 4
cout<<LB.valueX( )<<endl<<LB.valueY( )<<endl;
}
运行时会输出 5
0
6
4
25
*3.1.9结构和类
C 语言的结构定义 struct 在 C++ 中也可用来定义类,结构是类的
一种特例。
结构和类唯一的区别是:在缺省情况下(未指定访问权限时),结
构中的所有成员都是公有的,而在类中是私有的。
在所有其它方面,结构和类等价。
一般我们在仅描述数据时用结构;当既要描述数据又要描述操作时
用类。
26
*3.1.10 this 指针
当一个成员函数被调用时,C++自动产生一个参数,这个参数是一
个类指针,名叫 this,可以指向该类的一个对象,这个对象就是
接受该函数调用的对象。
如,Location LA;
LA.init(5,2);
这时就有一个 this --> LA
这里举一个例子说明 this 指针的用处,假定类中增加一个成员函数
取名为 赋值 assign()
void Location::assign(Location& p)
{ if (this == &p);
else { X=p.X; Y=p.Y }
}
当有二个对象 Location LA,LB;
若 LA.assign(LA); 则 this==&p即 LA成立,执行空语句,表示同一
个对象之间相互赋值没有意义,若 LA.assign(LB); 则 this(LA)
不等于 &p(LB),则 LA.X(或 this->X)=p.X; LA.Y=p.Y; 这里就可
以用来判别接受引用的对象是自己还是另外的对象。
27
*3.1.11类的其它基础知识
类的作用域
1,在类说明 { }中的标识符只在类中可见。类中各数据成员的生存期
由类对象的生存期决定,当对象存在时,它们存在;对象消失时,
它们也消失。
class MyClass{
private,
int number;
public,
void set(int i);
}
int j=number; //出错,number 在此不可见
int number;
void MyClass::set(int i)
{ number = i; //此 number 为 MyClass 中的 number
}
28
2,一个类的说明也分为声明和定义性说明。
class Location; //声明
声明的类名不能用来建立对象,只能用来建立指针或引用。
Location LA; //错
Location *Pa; //对
class Location {,......,
,......,
} //定义
Location LA; //对
Location *Pa; //对
空类
class 名 { };
{ } 中没有数据成员和函数成员称为空类,空类可以建立对象。
类对象的存储方式
定义类对象时也可加 auto,register,extern,static等存储方式。
类嵌套
类定义中的成员可以是类,称为类嵌套。
类的实例化
当用一个类定义对象时,C ++才进行存储分配。这种对象建立的
过程称为实例化。
29
3.2 构造函数与析构函数
3.2.1 构造函数
当类定义时把数据成员定义成公有成员时,可用传统的方法给它赋
初值。
C语言 传统的方法
main()
{ struct student { int sno;
char *sname;
float score;
};
struct student s1={9801,"张三 ",89.5};
class Init{
public,int i;
char *name;
float num[2];
};
Init C = {34,"program",{56.89,1.2}};
对于更一般的类型,我们用构造函数的方法。
30
构造函数
构造函数是特殊成员函数,具有一些特殊的性质,
( 1) 构造函数的名字必须与类名相同。
( 2) 构造函数可以有任意类型的参数,但不能具有返回类型。
( 3) 定义对象时,编译系统会自动地调用构造函数。
class complex{
private,
double real,imag;
public,
complex(double r,double i)
{ real=r; imag=i; }
double abscomplex()
{ double t;
t=real*real+imag*imag;
return sqrt(t);
};
构造函数不能像其它成员函数那样被显式地调用,它是在定义对
象的同时调用的,其一般格式为,类名 对象名(实参表)
31
构造函数的调用
例 3.6
#include <iostream.h>
#include <math.h>
class complex{
private,
double real,imag;
public,
complex(double r,double i)
{ real=r; imag=i; }
double abscomplex()
{ double t;
t=real*real+imag*imag;
return sqrt(t); }
};
void main()
{ complex A(1.1,2.2); //定义对象 A时自动 调用构造函数 complex
cout<<“复数 A的绝对值 =,<<A.abscomplex()<<endl;
}
32
说明,
( 1) 构造函数的名字必须与类名相同,否则编译程序将把它当作
一般的成员函数来处理。
( 2) 构造函数没有返回值,在声明和定义构造函数时,是不能说
明它的类型的的,甚至说明为 void类型也不行。
( 3) 在实际应用中,通常需要给每个类定义构造函数。如果没有
给类定义构造函数,则编译系统自动地生成一个缺省的构造函数。
complex::complex() { }
( 4) 构造函数可以是不带参数的。
( 5) 构造函数也可采用构造初始化表对数据成员进行初始化,这
是某些程序员喜欢使用的方法。
class A{
int i,j;
float f;
public,
A(int I,int J,float F):i(I),j(J),f(F) { }
};
( 6) 对没有定义构造函数的类,其公有数据成员可以用传统的方
法初始值表进行初始化。
33
定义构造函数
在类定义时可以定义一种与类名同名的成员函数,我们称之为构
造函数。它有如下的要点
要点 1,构造函数与类名同名,且要有函数体,可以内置或外置或
内联,没有返类型( void也不能写)。
要点 2,构造函数不能象其他成员函数那样被外界调用,它在程序
定义一个新对象,程序自动调用构造函数来初始化这个对象。
要点 3,构造函数可不带形式参数也可带形式参数,这样可根据不
同的要求对对象不同的初始化。
【 例 】 // test.h
class test {
private,int num;
float fl;
public, test();
test(int n,float f);
int getint() { return num; }
float getfloat() { return fl; }
};
34
test::test()
{ cout<<“这是缺省参数的构造函数进行初始化 "<<endl;
num=0; fl=0.0;
}
test::test(int n,float f)
{ cout<<“这是带参数的构造函数进行初始化
"<<n<<""<<f<<endl;
num=n;
fl=f;
}
下面用类定义对象时,会自动调用以上构造函数 #include
<iostream.h>
#include "test.h"
void main()
{ test x;
test y(10,21.5);
}
执行这个程序,产生如下输出,
这是缺省参数的构造函数进行初始化
这是带参数的构造函数进行初始化 10 21.5
35
构造函数和运算符 new,delete
运算符 new可动态 (生存期可控 )的建立一个对象,new返回这个对象
的指针。
执行运算符 new时,new先分配足够内存,然后自动调用构造函数
给这内存赋初值,再返回这个内存的地址。
void main()
{ test *prt1=new test;
test *prt2=new test(5,12.8);
delete prt1;
delete prt2;
}
执行这个程序,产生如下输出,
这是缺省参数的构造函数进行初始化 Initializing default
这是带参数的构造函数进行初始化 5 12.8 Initialzing
用 new建立的对象在不用时可用 delete删除。
36
3.2.2 缺省参数的构造函数
如果定义对象时给一个默认值,这时可以将其定义成带缺省参数的
构造函数。
class complex{
private,
double real,imag;
public,
complex(double r=0.0,double i=0.0); //带缺省参数的构造函数
double abscomplex();
};
complex::complex(double r,double i) //带参数的构造函数
{ real=r; imag=i; }
double complex::abscomplex();
{ double t;
t=real*real+imag*imag;
return sqrt(t);
}
complex S1; // S1为 0.0,0.0 的对象
complex S2(1.1); // 只给一个参数
complex S3(1.1,2.2); // 给二个参数
37
3.2.3 析构函数
析构函数也是特殊成员函数,用于释放对象。
( 1) 析构函数与构造函数名字相同,但它前面必须加一个波浪号
( ~)。
( 2) 析构函数没有参数,也没有返回值,而且不能重载,因此在
一个类中只能有一个析构函数。
( 3) 当撤消对象时,编译系统会自动地调用析构函数。
例 3.8
#include <iostream.h>
#include <math.h>
class complex{
private,
double real,imag;
public,
complex(double r=0.0,double i=0.0) ; //带缺省参数的构造函数
~complex(); //声明 析构函数
double abscomplex();
};
38
接例 3.8
complex::complex(double r,double i) //带参数的构造函数
{ cout<<“进入构造函数 …..”<<endl;
real=r; imag=i;
}
complex::~complex() //定义 析构函数
{ cout<<“进入析构函数 …..”<<endl; }
double complex::abscomplex()
{ double t;
t=real*real+imag*imag;
return sqrt(t);
}
void main()
{ complex A(1.1,2.2); //定义对象 A时自动 调用构造函数
complex
cout<<“复数 A的绝对值 =,<<A.abscomplex()<<endl;
} //结束时自动 调用析构函数
如果没有给类定义析构函数,则编译系统自动地生成一个缺省的析
构函数,complex::~complex() { }
39
定义析构函数
在类定义时可以定义另一种与类名同名前面加 ~的成员函数,我们
称之为析构函数。
它有如下的要点
要点 1,析构函数与类名同名,但它前面必须加波浪号 (~)
且要有函数体,可以内置或外置或内联,没有返回类型
( void也不能写)。
要点 2,析构函数不能象其他成员函数那样被外界调用,
它在程序结束时,程序自动调用析构函数来析放对象的
内存。
要点 3,析构函数可不带形式参数,不能重载,因此在一
个类中只能有一个析构函数。
40
【 例 】 // test2.h
class test {
private,int num;
float fl;
public, test();
test(int n,float f);
int getint() { return num; }
float getfloat() { return fl; }
~test();
};
test::test()
{ num=0; fl=0.0; }
test::test(int n,float f)
{ num=n; fl=f; }
test::~test()
{ cout << "Destructor is active显示一下己调用析构函数我
"<<endl;
}
41
下面程序运行结束时,会自动调用
以上析构函数。
#include <iostream.h>
#include "test2.h"
void main()
{ test x;
cout<<"退出 main 函数 "<<endl;
}
执行这个程序,产生如下输出,
退出 main 函数
Destructor is active 显示一下己调用析构函数我
全局对象或静态对象的析构函数在程序运行结束之前调用
42
析构函数和对象数组
对象数组的每一个元素调用一次析构函数。
#include <iostream.h>
#include "test2.h"
void main()
{
test array[3];
cout<<"退出 main 函数 "<<endl;
}
执行这个程序,产生如下输出,
退出 main 函数
Destructor is active 显示一下己调用析构函数我
Destructor is active 显示一下己调用析构函数我
Destructor is active 显示一下己调用析构函数我
43
析构函数和运算符 delete
用运算符 delete删除一个动态对象时,它首先调用析构函数,
然后再释放内存。
#include <iostream.h>
#include "test2.h"
void main()
{
test *prt = new test[5];
delete []prt;
}
执行这个程序,产生如下输出,
Destructor is active 显示一下己调用析构函数我
Destructor is active 显示一下己调用析构函数我
Destructor is active 显示一下己调用析构函数我
Destructor is active 显示一下己调用析构函数我
Destructor is active 显示一下己调用析构函数我
44
缺省析构函数
当类定义时没有定义任何析构函数,则C++自动建立一个析构
函数,
称为缺省构造函数,其形式如下,
类名,:~构函数名 ( )
{
}
函数体内什么事也没做,但它释放了对象的内存。
总结,
构造函数可给对象赋初值。
析构函数释放对象的内存。
当定义对象变量时自动调用构造函数,在程序结束时,程序自动
调用析构函数来释放对象的内存。
当定义对象指针时用 new来申请对象内存,new去调用构造函数。
用 delete删除对象时,它首先调用析构函数,然后再释放内存。
一般程序中,析构函数可缺省,构造函数最好不缺省。
45
3.2.4 重载构造函数
C++允许重载构造函数,这些构造函数之间以它们所带参数的个类
或类型的不同而区分。
例 3.9
#include <iostream.h>
#include <stdlib.h>
class timer {
int seconds;
public,
timer() { seconds=0; } // 无参数的构造函数,给 seconds清
零
timer(char *t) { seconds=atoi(t); } // 数字串参数的构造函数
timer(int t) { seconds=t; } // 一个整数参数的构造函
数
timer(int min,int sec)
{ seconds=min*60+sec; } // 二个整数参数的构造函数
int gettime() {return seconds; }
};
46
void main()
{ timer a,b(10),c(“20”),d(1,10);
cout<<“seconds1=“<<a.gettime()<<endl;
cout<<“seconds2=“<<b.gettime()<<endl;
cout<<“seconds3=“<<c.gettime()<<endl;
cout<<“seconds4=“<<d.gettime()<<endl;
}
47
3.2.5 拷贝构造函数
拷贝构造函数是一种特殊的构造函数。它用于依据已存在的对象建
立一个新对象。
1,自定义拷贝构造函数,形式如下,
classname(const classname &ob)
{ //拷贝构造函数函数体 }
例 3.10
#include <iostream.h>
class point {
int x,y;
public,
point (int a,int b) { x=a; y=b; } //构造函数
point (const point &p) { x=2*p.x; y=2*p.y; }//拷贝构造函数
void print() { cout<<x<<“,<<y<<endl; }
};
void main()
{ point p1(30,40); // 定义对象 p1
point p2(p1); //显式调用拷贝构造函数,初始化对象 p2
p1.print();
p2.print();
}
本例除了显式调用拷贝构造函
数外,还可以采用赋值形式调
用拷贝构造函数。
point p2=p1;
48
2.缺省的拷贝构造函数
如果没有编写自定义的拷贝构造函数,C++会自动地将一个已存在
的对象复制给新对象,这种按成员逐一复制的过程是由缺省拷贝
构造函数自动完成的。
例 3.11
#include <iostream.h>
class point {
int x,y;
public,
point (int a,int b) { x=a; y=b; } //构造函数
void print() { cout<<x<<“,<<y<<endl; }
};
void main()
{ point p1(30,40); // 定义对象 p1
point p2(p1); //显式调用缺省拷贝构造函数,初始化对象 p2
point p3=p1; //赋值形式调用缺省拷贝构造函数,初始化对象
p3
p1.print();
p2.print();
p3.print();
}
说明,
( 1) 与一般的构造函数一样,拷贝构造函数没
有返回值。
( 2) 通常缺省的拷贝构造函数是能够胜任工作
的,但若类中有指针类型时,按成员复制的方法
会产生错误。
49
拷贝初始化的构造函数
1,自定义一个拷贝初始化的构造函数,
X::X(X&)
类名,:拷贝初始化的构造函数名 (与类名同 ) (类名 & 形参对象 )
test::test(test& t)
class point{
int x,y;
public,
point(int a,int b) {x=a; y=b;}
point(point& p) { x=2*p.x; y=2*p.y; }
};
void main( )
{ point p1(30,40);
point p2(p1);
}
从这个例子中可看出,可用拷贝初始化的构造函数把一个己初始
化的对象 p1再去初始化另一个对象 p2,这就是拷贝初始化的构造
函数的作用。
50
2,系统自动引成的 (缺省的 )拷贝初始化的构造函数,
class point{
int x,y;
public,
point(int a,int b) {x=a; y=b;}
};
void main( )
{ point p1(30,40);
point p2(p1);
}
这时 p2 与 p1 相同。相当于有如下的拷贝初始化的构造函数,
point::point(point& p)
{
x=p.x;
y=p.y;
}
51
*3.2.6构造函数类型转换
构造函数起类型转换作用,当对象出现在 = 号左边时,右边的数
据被构造 函数转换成对象赋给左边。
#include <iostream.h>
class complex {
private,float x;
float y;
public,complex(float u,float v)
{ x=u; y=v; }
void print()
{ cout<<x<<"+"<<y<"i"<<endl; }
}
void main()
{ complex a(2.0,3.0);
a.print();
a=complex(10.0,20.0);
a.print();
}
52
*3.2.7 构造函数和对象的初始化
这一节说明三个问题,
1,使用缺省拷贝初始化的构造函数在有些情况下会产生
问题。
2,实参对象向形参对象以值调用方式传递参数时,为了
初始化形参,会自动调用拷贝初始化构造函数,调用结
束形参消亡时会自动调用析构函数;当一个函数的返回
值是对象时,相当于有一个隐藏对象,这时也会调用拷
贝初始化的构造函数,隐藏对象的生存期结束时,同样
调用析构函数。
3,因此对有以上二种情况的程序,在类中要恰当地定义
拷贝初始化构造函数,保证程序正确的工作。
53
*3.2.8 对象相互赋值
一般情况下同类的对象可以相互赋值,但有时也会出问题
1,缺省的赋值操作:一个对象的成员值赋给另一个对象相应的成员。
2,这种情况有时也会出问题;如
class string {
private,
char *str;
public,
string(char *s)
{ str=new char[str(s)+1];
strcpy(str,s);
}
~string() { delete str; }
};
void main( )
{ string s1("hello"),s2("world");
s2=s1;
}
这个 s2=s1 赋值操作使 s1,s2都指向
"hello",这导致二个问题:首先
"world" 逃走了再也无法被访问,
这个问题还不大;其次当程序结束
时执行二次析构函数,存储的内存
被释放二次,这是个严重的错误。
54
解决的方法是重新定义 =号 (赋值号 )操作,这叫运算符重载。
string& string::operator= (string& a)
{ if(this==&a) //如果自已赋自已,则返回自已,防止 s=s这样的
赋值。
return *this;
delete str;
str = new char[strlen(a.str)+1];
strcpy(str,a.str);
return *this;
}
operator= 是赋值运算符,a 是用于右边的对象。
55
【 例 】 完整的例子
#include <iostream.h>
#include <string.h>
class string {
private,char *str;
public,string(char *s);
string(string& a);
string& operator= (string& a);
string& operator= (char *s); //定义二种赋值操作
void print() { cout<<str<<endl; }
~string() { delete str; }
};
string::string(char *s)
{
str = new char[strlen(s)+1];
strcpy(str,s);
}
string::string(string& a)
{
str = new
char[strlen(a.str)+1];
strcpy(str,a.str);
}
56
接 【 例 】
string& string::operator= (string& a)
{ if(this==&a)
return *this;
delete str;
str = new char[strlen(a.str)+1];
strcpy(str,a.str);
return *this;
}
string& string::operator= (char *s)
{ delete str;
str = new char[strlen(s)+1];
strcpy(str,s);
return *this;
}
void main( )
{ string s1(“here”),
s2("there");
s1.print();
s2.print();
s2=s1;
s2.print();
s2="hello";
s2.print();
}
57
3.3 对象数组与对象指针
3.3.1 对象数组
当一个类有许多对象时,可以用类定义对象数组。
例 3.12
#include <iostream.h>
class exam{
int x;
public,
void set_x(int n) { x=n; }
int get_x() { return x; }
};
void main()
{ exam ob[4];
int i;
for (i=0; i<4; i++)
ob[i].set_x(i);
for (i=0; i<4; i++)
cout<<ob[i].get_x()<<' '<<;
cout << endl;
}
58
如果类中有构造函数带有参数,则可定义对象数组时,用初值表,
例 3.13
#include <iostream.h>
class exam{
int x;
public,
exam(int n) { x=n; }
int get_x() { return x; }
};
void main()
{ exam ob[4]={11,22,33,44};
int i;
for (i=0; i<4; i++)
cout<<ob[i].get_x()<<' '<<;
cout << endl;
}
59
如果类中有构造函数不带有参数或缺省构造函数,定义对象数组时
自动调用。
例 3.14
#include <iostream.h>
class point {
private,
int x,y;
public,
point()
{ x=5; y=5; } //不带参数的构造函数
point(int a,int b)
{ x=a; y=b;} //带参数的构造函数
int getx()
{ return x;}
int gety()
{ return y;}
};
60
void main()
{ point op(3,4); //调用带参数的构造函数
cout<<"op x="<<op.getx()<<endl;
cout<<"op y="<<op.gety()<<endl;
point op1[3]={(1,2),(3,4),(5,6)}; //通过初值表给对象数组赋初值
cout<<"op1[1] x="<<op1[1].getx()<<endl;
cout<<"op1[1] y="<<op1[1].gety()<<endl;
point op_array[20]; //调用不带参数的构造函数
cout<<"op_array[15] x="<<op_array[15].getx()<<endl;
cout<<"op_array[15] y="<<op_array[15].gety()<<endl;
}
61
3.3.2 对象指针
当需要时,可以用类定义对象指针。
1,用指针引用单个对象,可用 (.)或 (->)引用成员。
例 3.15
#include <iostream.h>
class exe{
int x;
public,
void set_a(int a) { x=a; }
void show_a() { cout << x endl; }
};
void main()
{ exe ob,*p;
ob.set_a(2);
ob.show_a();
p=&ob;
p->show_a();
}
62
2,用指针引用对象数组。
void main()
{ exe ob[2],*p;
ob[0].set_a(10);
ob[1].seta(20);
p=ob;
p->show_a();
p++;
p->shoe_a();
}
63
3.3.3 this 指针
当一个成员函数被调用时,C++自动产生一个参数,这个参数是一
个类指针,叫 this,可以指向该类的一个对象,这个对象就是接
受该函数调用的对象。当有个对象调用同一成员函数时,this 分
别指向不同的对象。
例 3.16
#include <iostream.h>
class exth {
int i;
public,
void load(int val) { this->i=val; } //与 i=val; 相同
int get() { return this->i; } //与 return i 相同
};
void main()
{ exth obj,objj;
obj.load(100); //this 指向 obj
objj.load(200); //this 指向 objj
cout<<obj.get()<<endl; //this 指向 obj
cout<<objj.get()<<endl; //this 指向 objj
}
64
如,Location LA;
LA.init(5,2);
这时就有一个 this --> LA
假定类中一个成员函数 assign(),
void Location::assign(Location& p)
{ if (this == &p);
else { X=p.X; Y=p.Y }
}
当有二个对象 Location LA,LB;
若 LA.assign(LA); 则 this 指向 LA,形参 &p是实参 LA,则
this==&p成立,执行语句,表示同一个对象之间相互赋值没有
意义。若 LA.assign(LB); 则 this 指向 LA,形参 &p是实参 LB,
this==&p 不等于,则 LA.X(或 this->X)=p.X; LA.Y=p.Y;
这里就可以用来判别接受引用的对象是自己还是另外的对象 。
65
3.4 向函数传递对象
对象可作为函数的形参和实参,实参和形参的传递有传值调用
例 3.17
#include <iostream.h>
class tr {
int i;
public,
tr(int n) { i=n; }
void set_i(int n) { i=n; }
int get_i() { return i; }
};
void sqr_it(tr ob)
{ ob.set_i(ob.get_i()*ob.get_i()); //形参对象 i=i*i
cout << "形参对象 i 的值为 "<<ob.get_i()<<endl;
}
void main()
{ tr obj(10); //定义对象 obj
sqr_it(obj); //obj 为实参调用函数 sqr_it(tr
ob)
cout << "但是实参 obj 的 i 还是 ";
cout << obj.get_i()<<endl;
}
66
传地址调用
例 3.18
#include <iostream.h>
class tr {
int i;
public,
tr(int n) { i=n; }
void set_i(int n) { i=n; }
int get_i() { return i; }
};
void sqr_it(tr *p)
{ p->set_i(p->get_i()*p->get_i());
cout << "对象指针 p-> i 的值为 "<<p->get_i()<<endl;
}
void main()
{ tr obj(10); //定义对象 obj
sqr_it(&obj); //&obj 为实参调用函数 sqr_it(tr *p)
cout << "实参 obj 的 i 变成 ";
cout << obj.get_i()<<endl;
}
67
3.5 静态成员
在类定义中,用 static 说明的成员称为静态成员,有静态数据成员
和静态函数成员。
3.5.1 静态数据成员
一个类无论定义多少个对象,都共用同一个静态数据成员。
例 3.19
#include <iostream.h>
class Student {
static int count; //声明静态数据成员 count,用于统计学生总数
int StudentNo;
public,
Student() { count++; StudentNo=count; }
void print(){
cout << "学生 "<<StudentNo<<" ";
cout << "总数 ="<<count<<endl;
}
};
int Student::count=0; //静态数据成员可在程序中可访问
68
void main()
{ Student S1; //学生 1
S1.print();
cout<<"-------------\n";
Student S2;
S1.print();
S2.print();
cout<<"-------------\n";
Student S3;
S1.print();
S2.print();
S3.print();
cout<<"-------------\n";
Student S4;
S1.print();
S2.print();
S3.print();
S4.print();
}
说明 (1) 静态数据成员属于类,用 类名,:数
据成员名。
(2) 静态数据成员不能在类中进行初始化,
必须在类外进行。
(3) 静态数据成员是在编译时分配空间,程
序结束时才消失。
(4) C++用静态数据成员的目的是可不必使
用全局变量
69
3.5.2 静态成员函数
一个类无论定义多少个对象,都共享同一个静态成员函数。一般
而言,静态成员函数访问的基本上是静态数据成员或全局变量。
例 3.21
#include <iostream.h>
class small_cat{
private,
double weight; //普通数据成员,表示一只猫的重量
static double total_weight;
static double total_number;
public,
small_cat(double w)
{ weight=w; total_weight+=w; total_number++; }
void display()
{ cout <<"这只小猫的重量是 "<<weight<<"磅 "<<;endl; }
static void total_disp() //静态成员函数
{ cout <<"小猫的总数是 "<<total_number<<endl;
cout <<"小猫的总重量是 "<<total_weight<<endl;
}
};
70
double small_cat::total_weight=0; //静态数据成员初始化
double small_cat::total_number=0; //静态数据成员初始化
void main()
{ small_cat w1(1.8),W2(1.6),w3(1.5);
W1.display()
W2.display()
W3.display()
small_cat::total_disp();
}
说明
(1) 静态成员函数可以定义成内嵌的,也可以在类外定义。在外面
不要用 static。
(2) 编译系统将静态成员函数定为内联。
(3) 静态成员函数可以在定义对象之前处理静态数据成员。
(4) 静态成员函数没有 this指针,调用时用 类名,:静态成员函数名。
(5) 一般而言,静态成员函数不访问类中的非静态成员,而只访问
静态成员。
71
3.6 友元
类的封装性,使类只能通过成员函数来访问私有成员。这是好事
但也有一定的限制太死的感觉。能否开个小孔口,必要时作个破
例,使外界函数、别的类的个别成员函数、甚致别的类的所有成
员函数可访问某个类的私有成员。这个方法称为友元,有友元函
数、友元成员和友元类三种。
3.6.1 友元函数
在类定义时,在类内定义一个函数,该函数前加上 friend 表示这
个函数不是成员函数,而是本类的友元函数。友元函数可以内嵌
或外部定义。友元函数可以访问本类的私有成员。
72
例 3.22
#include <iostream.h>
#include <string.h>
class girl {
char *name;
int age;
public,
girl(char *n,int d) //构造函数
{ name=new char[strlen(n)+1];
strcpy(name,n);
age=d; }
friend void disp(girl &); //声明 disp是本类的友元函数
~girl() { delete name; } //析构函数
};
void disp(girl &x) //定义友元函数
{ cout << "女孩的名是, "<<x.name<<",年龄是, "<<x.age<<endl; }
void main()
{ girl e("Chen Xingwei",18);
disp(e);
}
73
说明,
1.友元函数不是类的成员函数,因此在外部定义友元函数时,不必
加 类名,,
2.友元函数一般带有一个该类的入口参数。并做成对象引用或指针
的方式。
3,一个友元函数可以做成多个类的友元,能够访问相应的所有类的
数据。
4.友元函数破坏了数据的隐蔽性,降低程序的可维护性,应该谨用。
例 3.23
#include <iostream.h>
#include <string.h>
class boy;
class girl {
char name[25];
int age;
public,
void init(char N[],int A);
friend void prdata(const girl plg,const boy plb);
//声明 prdata是 girl类的友元函数
};
74
void girl::init(char N[],int A)
{ strcpy(name,N); age=A; }
class boy {
char name[25];
int age;
public,
void init(char N[],int A);
friend void prdata(const girl plg,const boy plb);
//声明 prdata是 boy类的友元函数
};
void boy::init(char N[],int A)
{ strcpy(name,N); age=A; }
void prdata(const girl plg,const boy plb) //定义友元函数
{ cout << "姓名, "<<plg.name<<endl;
cout << "年龄, "<<plg.age<<endl;
cout << "姓名, "<<plb.name<<endl;
cout << "年龄, "<<plb.age<<endl;
}
void main()
{ girl G1,G2,G3;
boy B1,B2,B3;
G1.init("Stacy",12);
G2.init("Judith",13);
G3.init("Leah",12);
B1.init("Jim",11);
B2.init("Michael",13);
B3.init("Larry",12);
prdata(G1,B1);
prdata(G2,B2);
prdata(G3,B3);
}
75
3.6.2 友元成员
在类定义时,在类内说明一个函数,该函数是另一个类的成员函
数,前加上 friend表示该另类的成员函数,又是本类的友元成员。
友元成员函数可以访问它自己类私有成员又可本类的私有成员。
例 3.24
#include <iostream.h>
#include <string.h>
class girl;
class boy {
char *name;
int age;
public,
boy(char *n,int d) //构造函数
{ name=new char[strlen(n)+1];
strcpy(name,n);
age=d;
}
void disp(girl &); //声明 disp是成员函数
~boy() { delete name; } //析构函数
};
76
class girl {
char *name;
int age;
public,
girl(char *n,int d) //构造函数
{ name=new char[strlen(n)+1];
strcpy(name,n);
age=d;
}
friend void boy::disp(girl &); //声明 boy类的成员函数 disp()
是
//本类的友元成员函数
~girl() { delete name; } //析构函数
};
void boy::disp(girl &x) //定义友元函数
{ cout << "男孩的名是, "<<name<<",年龄是, "<<age<<endl;
//访问本类对象成员
cout << "女孩的名是, "<<x.name<<",年龄是, "<<x.age<<endl;
//访问友类成员
}
77
void main()
{ boy b("Chen hao",25);
girl e("Zhang wei",18);
b.disp(e);
}
说明,
(1) 一个类的成员函数作为另一类的友元函数时,必须先定义这个
类。
(2) 必要时可用“向前引用法”。
78
3.6.3 友元类
在类定义时,在类内说明一个类 (该类前面已定义 ),前加上
friend表示该类是本类的友元类。友元类的所有成员函数可以访
问它自己类私有成员又可本类的私有成员。
例 3.25
#include <iostream.h>
#include <string.h>
class girl;
class boy {
char *name;
int age;
public,
boy(char *n,int d) //构造函数
{ name=new char[strlen(n)+1];
strcpy(name,n);
age=d;
}
void disp(girl &); //声明 disp()是成员函数
~boy() { delete name; } //析构函数
};
79
class girl {
char *name;
int age;
friend boy; //声明 boy 类是 girl 类的友元类
public,
girl(char *n,int d) //构造函数
{ name=new char[strlen(n)+1];
strcpy(name,n);
age=d;
}
~girl() { delete name; } //析构函数
};
void boy::disp(girl &x) //定义成员函数
{ cout << "男孩的名是, "<<name<<",年龄是, "<<age<<endl;
//访问本类对象成员
cout << "女孩的名是, "<<x.name<<",年龄是, "<<x.age<<endl;
//访问友类对象成员
}
void main()
{ boy b("Chen hao",25);
girl e("Zhang wei",18);
b.disp(e);
}
80
3.7 对象成员
在定义类的数据成员时,可以把别的类的对象作为成
员称为对象成员。
1,对象成员的类定义应该再先。
2,重要的问题是后定义的类中的构造函数要调用这些
对象成员所在类的构造函数,这时后定义类的构造
函数要写成如下形式,
X::X(参数表 0):成员 1(参数表 1),成员 2(参数表 2),...,成员 n(参数表 n)
{ //......,
}
3,如果某项的参数表为空,则相应项可省略。
81
【 例 3.13】 分析下面程序中构造函数与析构函数的调用顺序。
#include <iostream.h>
class object {
private,int val;
public,object();
object(int i);
~object();
};
object::object()
{ val=0;
cout<<"object 类的无参数的构造函数 "<<endl;
}
object::object(int i)
{ val=i;
cout<<"object 类的有参数的构造函数, "<<val<<endl;
}
object::~object()
{
cout<<"object 类的析构函数, 释放 "<<val<<endl;
}
82
接 【 例 3.13】 例之 1 class container {
private,object one;
object two; //二个对象成员
int data; //普通成员
public,container();
container(int i,int j,int k);
~container();
};
container::container() //:tow(),one() 无参的构造函数可省略成员表
{ data=0;
cout<<"container 类的无参数的构造函数 "<<endl;
}
container::container(int i,int j,int k):two(i),one(j)
{ data=k;
cout<<"container 类的有参数的构造函数 "<<endl;
}
container::~container()
{
cout<<"container 类的析构函数 "<<endl;
}
83
接 【 例 3.13】 例之 2
void main()
{
container anobj(5,6,10);
}
主函数运行结果,
object 类的有参数的构造函数, 6 (one)
object 类的有参数的构造函数, 5 (two)
container 类的有参数的构造函数
container 类的析构函数
object 类的析构函数, 释放 5 (two)
object 类的析构函数, 释放 6 (one)
84
对无参的构造函数调用如下,
void main()
{
container bnobj;
}
主函数运行结果,
object 类的无参数的构造函数 (one)
object 类的无参数的构造函数 (two)
container 类的无参数的构造函数
container 类的析构函数
object 类的析构函数 释放 0 (two)
object 类的析构函数, 释放 0 (one)
4,初始化列表的方法还可用于 const成员和引用性成员。
class example {
private,const int num;
int& ret;
public,example(int n,int f):num(n),ret(f)
{ }
};
85
例 3.26
#include <iostream.h>
#include <string.h>
class string {
private,
char *str;
public,
string(char *s)
{ str=new char[strlen(s)+1];
strcpy(str,s);
}
void print()
{ cout<<str<<endl; }
~string()
{ delete str; }
};
86
class girl {
private,
string name; //name 为 string类的对象,为 girl类的成员
int age;
public,
girl(cha *st,int ag):name(st) //定义 girl类的构造函数
{ age=ag; }
void print()
{ cout<<"姓名, "<<name.print();
cout<<"年龄, "<<age<<endl;
}
};
void main()
{ girl obj("陈 好 ",25);
obj.print();
}