67
Object-Oriented Programming in C++ 第第第 第第第第第第 中中中中中中中中中中中 中中 [email protected]

Object-Oriented Programming in C++ 第三章 再论类和对象

  • Upload
    dory

  • View
    144

  • Download
    10

Embed Size (px)

DESCRIPTION

Object-Oriented Programming in C++ 第三章 再论类和对象. 中国科大学继续教育学院 李艺 [email protected]. 第一 章 C++ 的初步知识 第二章 类和对象 第三章 再论类和对象 第四章 运算符重载 第五章 继承与派生 第六章 多态性与虚函数 第七章 输入输出流 第八章 C++ 工具. 3.1 构造函数 3.2 析构函数 3.3 调用构造函数和析构函数的顺序 3.4 对象数组 3.5 对象指针 3.6 共用数据的保护 3.7 对象的动态建立和释放 - PowerPoint PPT Presentation

Citation preview

Page 1: Object-Oriented Programming  in C++ 第三章 再论类和对象

Object-Oriented

Programming

in C++

第三章 再论类和对象

中国科大学继续教育学院 李艺[email protected]

Page 2: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 2

第一章 C++ 的初步知识第二章 类和对象第三章 再论类和对象第四章 运算符重载第五章 继承与派生第六章 多态性与虚函数第七章 输入输出流第八章 C++ 工具

Page 3: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 3

3.1 构造函数3.2 析构函数3.3 调用构造函数和析构函数的顺序3.4 对象数组3.5 对象指针3.6 共用数据的保护3.7 对象的动态建立和释放3.8 对象的赋值和复制3.9 静态成员3.10 友元

Page 4: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 4

3.1 构造函数对象的初始化

和普通变量一样,我们定义一个变量,往往同时进行初始化:

int a = 3;

而声明类时,数据成员不能进行初始化:class time

{ hour = 0;

minute = 0;

second = 0;

}

因为类不是实体,不占分配存储空间,显然无法容纳数据。

Page 5: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 5

3.1 构造函数如果一个类的所有数据才成员都是公用的,我们可以想

结构体变量那样,在定义对象时(而不是声明类时)进行初始化:

class time

{ public:

hour = 0;

minute = 0;

second = 0;

};

time t1 = {13,30,20};

但在类的声明中,数据成员往往都是私有的,不能这样初始化。就需要一个公有成员函数来完成,而且应该是自动调用地完成。这就是构造函数。

Page 6: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 6

3.1 构造函数构造函数的作用

C++ 提供了构造函数 ( constructor ) 来处理对象的初始化。构造函数是一个由用户定义的特殊的成员函数。与其他成员函数不同之处在于: 用户不能调用它,而是在定义对象时,有系统自

动调用构造函数。 构造函数的名字必须与类名一致,不能是其他名

字。 构造函数不能有任何返回类型。 用户如果没有定义构造函数,系统会自动生成一

个构造函数,只不过函数体中没有任何语句。

Page 7: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 7

3.1 构造函数例 3.1 将前例的时间类定义构造函数。在构造函数中加入

输出语句,看看运行效果。class time{ private: int hour, minute, second;public: time ( ) //time 类的构造函数 { hour = 0; minute = 0; second = 0; } void setTime ( ); void showTime ( ) { cout << hour <<“:” <<minute <<“:” <<second <<endl; }};

void time::setTime ( ){ cout<<“hour = “; cin>>hour; cout<<“minute=“; cin>>minute; cout<<“second=“; cin>>second;}

void main ( ){ time t1; // 定义 time 类对象 t1, 调用构造函数 tume ( )

t1.setTime( ); t1.showTime ( ); time t2; // 定义 time 类对象 t2, 调用构造函数 tume ( )

t2.setTime ( ); t2.showTime ( );}

Page 8: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 8

3.1 构造函数带参数的构造函数

前一个示例中,没产生一个 time 了的对象,其初值都初始化为 0 。如果用户初始化时,不想将对象初值置为 0 而是其它值,就需要用带参数的构造函数来实现。 声明一个构造函数的一般格式为:

构造函数名 ( 类型 1 形参 1 ,类型 2 形参 2 ,… ) ; 定义一个对象的一般格式为:

类名 对象名 ( 实参 1 ,实参 2 ,… );下面举例说明用法。

Page 9: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 9

3.1 构造函数例 3.2 :有两个长方体,长宽高分别为 (1,2,3) 和

(4,5,6) 。试编写一基于对象的程序,分别求他们的体积,并且要求用带参数的构造函数初始化他们。

#include <iostream.h>class box{ private: int height, width, length; public: box ( int h, int w, int len ) { height = h; width = w; length = len; } int volume ( ) { return height * width * length; }};

void main ( ){ box box1 (1,2,3); cout <<“box1 的体积为” <<box1.volume( ) << endl; box box2 (4,5,6); cout <<“box2 的体积为” <<box2.volume( ) << endl;}

Page 10: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 10

3.1 构造函数用参数初始化表对数据成员初始化

C++ 提供另一种初始化数据成员的方法:参数初始化表来实现对数据成员的初始化。这种方法不在函数体内初始化数据成员,而是在函数首部实现。例 3.2 我们改写成如下形式:

#include <iostream.h>class box{ private: int height, width, length;public: box(int h,int w,int len):height(h),width(w),length(len) { } int volume ( ) { return height * width * length; }};void main ( ){ box box1 (1,2,3); cout <<“box1 的体积为” <<box1.volume( ) << endl; box box2 (4,5,6); cout <<“box2 的体积为” <<box2.volume( ) << endl;}

Page 11: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 11

3.1 构造函数

示例中的初始化表表示,用形参 h 的值初始化数据成员 height ,用 w 值初始化 width ,用 len 值初始化 length 。这种初始化方法比较简练,可以直接在类体中定义构造函数。

Page 12: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 12

3.1 构造函数构造函数的重载

一个类中,可以有多个构造函数,只要他们的参数表不同。以方便同类对象不同初始化的需要。见下例 3.3 :

#include <iostream.h>class circle{ private: float radius;public: circle( ); // 声明一个无参数构造函数 circle( float r ): radius (r) { } // 声明一个带参数构造函数 float area ( ) { return radius * radius * 3.14159; }};circle::circle ( ){ radius=10.0; }void main ( ){ circle c1 ( 1.0 ), c2; cout <<“c1 的面积为” <<c1.area( ) << endl; cout <<“c2 的面积为” <<c2.area( ) << endl;}

Page 13: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 13

3.1 构造函数说明

参数表为空的构造函数叫默认构造函数,一个类中只能有一个默认函数。

定义对象时,如果想用默认构造函数初始化它,正确的定义形式为:

circle c2;

而不是:circle c2 ( );

一个类尽管定义了多个构造函数,一个对象只能用其中一个来初始化。

Page 14: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 14

3.1 构造函数使用默认参数的构造函数

构造函数的参数既可以通过实参传送,也可以指定为某些默认值。当用户不指定实参值时,编译系统便将默认值为形参值。

在实际生活中,常有一些这样的默认值。如计数器的默认值为 0 ;战士的性别默认值为“男”;男性职工退休年龄默认值为 60岁等,如果实际情况不是默认值,则由用户另外指定。采用默认值,可以减少用户的输入量。

下面举例说明。

Page 15: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 15

3.1 构造函数例 3.4 试将例 3.3 的构造函数改用默认值的参数,半径值默

认为 1.0 。#include <iostream.h>class circle{ private: float radius;public: circle( float r = 1.0 ); // 声明构造函数是指定默认参数float area ( ) { return radius * radius * 3.14159; }};circle::circle ( float r ) // 定义函数时,可不再指定默认参数{ radius = r; }void main ( ){ circle c1 (10.0), c2; cout <<“c1 的面积为” <<c1.area( ) << endl; cout <<“c2 的面积为” <<c2.area( ) << endl;}

Page 16: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 16

3.1 构造函数构造函数中使用默认参数的好处

提供建立对象时的多种选择,相当于好几个重载的构造函数。

即使在调用构造时不提供参数也不会出错,因为有默认参数值参与对象初始化。

当每一个对象都是相同的初始值时,非常方便,用户不需要输入数据。

Page 17: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 17

3.1 构造函数默认参数值的构造函数使用注意

何处指定默认参数值?构造函数的声明处还是定义处?应该在构造函数的声明处指定默认参数值。因为类的声明在头文件中,用户是看得见的,而防在函数的定义处,用户不一定看得见。

一个类定义了全部是默认参数的构造函数后,不能再定义重载的构造函数。否则会产生多义性,系统不知道调用哪一个。例如一个类有右边形式

的三个重载构造函数,若定义了如 下对象: circle circle1( );

它调用哪一个构造函数呢?系统不 能确定,从而引起混乱。

circle ( float r=2.3 );

circle ( );

circle ( float );

Page 18: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 18

3.2 析构函数什么是析构函数?

析构函数 ( destructor ) 也是一个特殊函数,它的作用与构造函数相反,是在撤消对象占用的内存前进行一些清理工作。它还可以被用来执行“用户希望在最后依次使用对象之后所执行的任何操作”。

析构函数的名称是类名的前面加一个取反符号“ ~” 。我们在类的声明中定义析构函数。如果用户不定义析构函数,系统便自动生成一个空的析构函数。

析构函数特点: 没有返回类型; 没有函数参数; 不能被重载。

Page 19: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 19

3.2 析构函数什么时候运行析构函数?

当对象的生命结束时,会自动执行它的析构函数。具体而言,当出现以下几种情况,析构函数就会被执行。 如果在函数中定义了一个对象,当函数调用结束时,释

放对象,释放对象前自动执行析构函数。 static 局部对象在函数调用结束时,包含的对象不会被

释放,只在 main 函数结束或调用 exit 函数时,才调用static局部对象的析构函数。

如果定义了一个全局对象,,则在程序的流程离开其作用域时(如 main 函数结束,或 exit 语句),调用该全局对象的析构函数。

如果用 new 运算符动态地建立了一个对象,当用delete 运算符释放对象时,先调用该全局对象的析构函数。

Page 20: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 20

3.2 析构函数

#include <iostream.h>class box{ private: int height, width, length; public: box ( int h, int w, int len ) { height = h; width = w; length = len; } ~box ( ) // 析构函数 { cout <<“Destructing a object” << endl; } int volume ( ) { return height * width * length; }};

void main ( ){ box box1 (1,2,3); cout <<“box1 的体积为” <<box1.volume( ) << endl; box box2 (4,5,6); cout <<“box2 的体积为” <<box2.volume( ) << endl;}

例 3.5 析构函数举例。

Page 21: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 21

3.3 调用构造函数和析构函数的顺序 调用构造函数和析构函数的顺序

先构造的后析构,后构造的先析构。

#include <iostream.h>class box{ private: int height, width, length; public: box ( int h, int w, int len ) { height = h; width = w; length = len; cout <<“Constructor a object” << endl; } ~box ( ) // 析构函数 { cout <<“Destructoring a object” << endl; } int vol ( ) { return height * width * length; }};

void fn ( ){ box b1( 2,2,2 ); cout<<“b1=”<<b1.vol( ) << endl; static box b2( 3,3,3 ); // fn 结束不析构 b2, 直到main 结束 cout<<“b2=”<<b2.vol( ) << endl;}

void main ( ){ fn( ); box b3 (1,2,3); cout<<“b3=”<<b3.vol( )<< endl; box b4 (4,5,6); cout <<“b4=”<<b4.vol( )<<endl;}

Page 22: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 22

3.4 对象数组

数组不仅可以由简单变量组成,也可以由对象组成,即每一个数组元素都是同类的对象。

例如,一个班有 30人,每个学生的属性包括学号、姓名、性别。我们建立起学生类后,为每个学生建立一个对象,需要分别取 30 个对象名,很不方便。较好的做法是,定义一个“学生类”,的对象数组,每一个数组元素是一个“学生类”的对象。

请看下面示例程序。

Page 23: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 24

3.5 对象指针指向对象的指针

创建一个类的对象时,系统会为没一个对象分配一定的存储空间,以存放成员。对象空间的起始地址就是对象的指针。可以定义一个指针,用来存放对象的指针:

访问成员的方法: ( *pt ).hour, (*pt).put ( )

或者: pt->hour, pt->put( )

class Time{public: int hour, minute, sec; void put ( ) { hour = 12; minute = 0; sec = 0; }};

void main ( ){Time *pt, t1; pt = &t1; pt.put ( ); cout <<pt->hour<<“:”<<pt->minute <<“:”<<sec<<endl; cout <<(*pt).hour<<“:”<<(*pt).minute <<“:”<<(*pt).sec<<endl;}

Page 24: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 25

3.5 对象指针

指向对象数据成员的指针定义一个指向对象数据成员的指针变量的方法和前面

介绍的定义指向普通变量的指针变量方法相同。定义格式:数据类型名 * 指针变量名;

例如:int *pl; // 定义指向整形数据的指针变量

pl=&t1.hour; // 将 t1 的数据成员 hour 地址赋给指针 pl, 使其指向 t1.hour

cout << *pl <<endl; // 输出 t1.hour 的值

Page 25: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 26

3.5 对象指针

指向对象成员函数的指针定义指向对象成员函数的指针变量的方法和定义指向

普通函数的指针变量的方法有所不同。 指向普通函数的指针变量的定义方法:

返回数据类型 (* 指针变量名 ) ( 参数表列 ) ;例: void (*p) ( ); // p 是指向 void 类型函数的指针变量

p = fun; // 将 fun 函数的入口地址赋给指针变量 p

(*p)( ); // 调用 fun 函数

而定义一个指向对象成员函数的指针变量则不能这样:p = t1.put; // 出现编译错误,不知道 t1.put 所属的类

Page 26: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 27

3.5 对象指针

定义指向公用成员函数的指针变量方法:返回数据类型 ( 类名 :: * 指针变量名 ) ( 参数表

列 ) ;例: void (Time::*p2)( ); // Time::*p2 的括号必须加上

令指针变量指向一个公用成员函数的方法:指针变量名 = & 类名 :: 成员函数名;

例: p2 = & Time::put;

Page 27: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 28

3.5 对象指针

#include <iostream.h>class Time{public: int hour, minute, sec; Time ( int h, int m, int s ) { hour = h; minute = m; sec = s; } void get_Time( ) { cout << hour<<“:” << minute<<“:” << sec<<endl; }};

void main ( ){Time t1( 10,12,56 ); int *pl = &t1.hour; // 定义指向整形数据的指针,指向 t1.hour cout << *p1 <<endl; t1.get_Time( ); // 调用 t1 的成员函数 Time *p2 = &t1; // 定义指向 Time 类对象的指针变量 t2, 并指向t1 p2->get_Time( ); // 调用 p2 所指对象的成员函数 void (Time::*p3)( ); // 定义指向 Time 类公用成员函数的指针变量p3 p3 = &Time::get_Time; // 使 p3 指向 Time 类公用成员函数get_Time( ) (t1.*p3) ( ); // 调用 p3 所指的成员函数 t1.get)Time( )}

Page 28: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 29

3.5 对象指针this 指针

通过第二章的学习我们知道,多个同类对象在内存中是共享成员函数的,以节省内存空间。那么,不同对象的成员函数引用数据成员时,怎么找到自己的数据成员呢?

C++ 在每一个成员函数中都包含一个特殊的指针,这个指针的名字是固定的,成为“ this” 。它是指向本对象的指针,它的值是当前被调用的成员函数所在对象的起始地址。

例如,当 a 的成员函数调用数据成员 a.volume 时,编译系统就把对象 a 的起始地址赋给 this 指针,于是在成员函数引用数据成员时,就按照 this 的指向找到对象 a 的数据成员。

Page 29: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 30

3.5 对象指针比如下列涉及数据成员的运算的返回语句: return length*width*height ;

实际上 C++ 处理为: return ( this ->length)*(this->width)*(this->height);

也可以写成如下形式: return ((*this).length*(*this).width*(*this).height);

但不能写成: return ((*this.length) *(*this.width)*(*this.height)); //错误

因为“ .”操作符的优先级高于指针运算符“ *” ,(*this.length) 就相当于 *(this.length) ,而 this.length 是不合法的,编译出错(应该是 this->length )。

Page 30: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 31

3.6 共用数据的保护

C++采用了不少的数据保护措施。最常用的,是将数据成员设置成私有数据 ( private ) ,以增加数据的安全性和私密性。

但是,有时候要求数据在能共享的前提下能不能被篡改,我们就需要借助其他手段了。

什么手段呢?可以采用 const ,即把要保护的数据定义为常量。

Page 31: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 32

3.6 共用数据的保护

常对象在定义对象时指定对象为常对象。常对象中的数据成

员为常变量,并且必须要有初值:Time const t1(12,34,56);

这样,在所有的场合中,对象 t1 的所有数据成员都被保护了,不能被修改。因此,凡是希望数据成员不能被改变的对象,可以声明为常对象。其声明格式为:

类名  const  对象名 ( 参数表列 ) ;或者:

const 类名 对象名 ( 参数表列 ) ;两者等价。

Page 32: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 33

3.6 共用数据的保护如果一个对象被定义成常对象,那么不能该对象的非

const 型成员函数,当然,系统隐含调用的构造函数和析构函数除外。比如:

Time const t1( 12,34,56); // 定义 t1 为常对象

t1.get_Time( ); // 错误, get_Tiem( ) 不是 const 型 , 不能调用

为什么会这样?因为成员函数有可能修改数据成员,而成员函数的定义可能和成员函数的声明不在同一文件,系统没法检测。所以,系统只能统一拦截,不让用户调用常对象的成员函数,除非该成员函数被声明成 const 类型。

Page 33: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 34

3.6 共用数据的保护怎样才能引用常对象的成员函数呢?很简单,只需要把该

成员函数的声明为 const 类型,即常成员函数就行了。方法为:函数返回类型 函数名 ( 参数表列 ) const;

比如: void get_Time( ) const; // 将函数声明成 const 类型

常成员函数可以访问常对象中的数据成员,但仍然不准修改它们。

有时候编程要求必须修改某个常对象中的数据成员,如某个计数器 count , ANSI C++ 对此做了特殊的处理,将该数据成员 声明为 mustable ,如:

mustable int count;

这样,常对象的数据成员 count ,就可以用常成员函数来访问和修改了。

Page 34: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 35

3.6 共用数据的保护常对象成员

常数据成员:其作用和用法与一般常变量相似,在类的声明中,用关键词 const 来声明常数据成员,例如:

const int hour;

注意:常数据成员的初始化,不能采用在构造函数中对常数据成员赋予初值的方法,只能通过构造函数的参数初始化表对常数据成员进行初始化。

在类外定义构造函数,初始化形式为:Time::Time ( int h ): hour ( h ) { }

在类中声明了某一个数据成员为常数据成员后,该类的所有对象中的该数据成员的值是不可改变的,但可以是不同的(由每个对象的参数初始化表决定)。

Page 35: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 36

3.6 共用数据的保护 常成员函数

一般的成员函数可以引用本对象中的非 const 数据成员,也可以修改它们。但如果成员函数声明为常成员函数,则只能引用本对象中的数据成员,而不能修改它们。如

void get_Time( ) const; // const位置在最后

const 是函数类型的一部分,在声明函数和定义函数时都要用到 const 关键字。

常成员函数可以引用常数据成员,也可以引用非const 数据成员;而常数据成员可以被常成员函数引用,也可以被非 const 成员函数引用。够乱的,见 90页表总结。

Page 36: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 37

3.6 共用数据的保护

数据成员 非 const 成员函数 const 成员函数

非 const 数据成员 可引用,可修改 可引用,不可修改

const 数据成员 可引用,不可修改 可引用,不可修改

常对象的数据成员 不可引用,不可修改 可引用,不可修改

Page 37: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 38

3.6 共用数据的保护怎样使用常成员函数呢?

类中如果一些数据成员需要保护,另一些数据成员不需保护,我们就将需要保护的数据成员声明为 const ,以保证其值不被改变。

类中如果所有数据成员都需保护,可以将所有数据成员都声明成 const ,本对象的任何成员函数,都只能引用,不能改变它们。或者将这些数据成员所属的对象声明成const ,只让本对象的 const 成员函数可引用,但不能改变。

如果一个对象被定义成了常对象,只能调用其中的 const

成员函数,不能调用非 const 成员函数。常成员函数不能调用另一个非 const 成员函数

Page 38: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 39

3.6 共用数据的保护 指向对象的常指针

什么时候需要常指针让其指向对象?如果想将一个指针变量与一个对象固定地联系在一起,以防止误操作;往往用常指针作为函数的形参,目的是不允许在函数执行过程中改变指针值,使其始终指向原来的对象。

将指向对象的指针变量声明为 const ,并将之初始化,可以使指针值恒定为初始值,即其指向始终不变。如

Time t1(10,12,14), t2; // 定义对象Time * const ptr = &t1; // 定义常指针,恒定指向 t1 对象ptr = &t2; // 错误,常指针不能改变指向

定义一个指向对象的常指针的格式为:类名 * const 指针变量名 = 对象地址;

指向对象的常指针,这个变量的值是一个对象的地址,他不允许被改变,但所指对象的内容可以改变。

Page 39: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 40

3.6 共用数据的保护 指向常对象的指针

定义指向常对象的指针格式:const 类名 * 指针名;

这和 C 语言中指向常变量的指针是一样的。只需将上述格式中的类名换成类型名就行了。我们不妨在一起作如下讨论: 如果一个变量被声明为常对象( / 常变量),只能用指向常对象( / 常变量)的指针指向它。如:const char c[ ] = “boy”; // 定义 const 型的常字符数组const char * p1; // p1 为指向常字符变量的指针p1 = c; // 合法, p1 指向常变量

char * p2 = c // 非法, p2 不是指向常变量的指针 指向常变量的指针如果指向了非 const 对象,其指向的对象值不能用

指针改变,可以用非指针方式改变。Time t1(10,12,14); // 定义 Time 类对象 t1 ,它不是常对象const Time *p = &t1; // p 是指向常对象 t1 的指针

t1.hour = 18; // 合法, t1 不是常量(*p).hour = 18; // 非法,不能通过指针改变 t1 的值

Page 40: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 41

3.6 共用数据的保护用指针变量作形参时,形参和实参的对应关系见下表

形参 实参 是否合法指针所指变量值能否改

指向非 const 变量或非 const 对象的指针

非 const 变量的地址 /

非 const 对象的地址

合法 可以

const 变量的地址 /

const 对象的地址非法 /

指向常变量或常对象的指针

非 const 变量的地址 /

非 const 对象的地址

合法 不可以

const 变量的地址 /

const 对象的地址合法 不可以

Page 41: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 42

3.6 共用数据的保护

例如: const char str[ ] = “boy”; // str 是常数组名 void func(char * ptr); // 函数形参为指向非 const 变量的指针 func (str); // 非法,实参是常变量指针,形参是非 const 变量指针出错原因:

形参是非指向非 const 型变量的指针,在函数中可以也可能改变指针所指变量值,而实参是指向常变量的指针,不能被改变,发生矛盾。

Page 42: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 43

3.6 共用数据的保护 指向常对象的指针最常用于函数的形参,目的是保护形参指针所指的对

象,使它在函数执行过程中不被修改。如void main( )

{ void func(const Time * p); // func 的形参是指向常对象的指针

Time t1(10,12,14);

func(&t1); //合法,实参是 非 const 对象 t1 的地址 return;

}

void func( const Time * P)

{ p->hour = 18; // 错误,指向的常对象被修改了 cout<<p->hour<<endl;

}

如果 func 形参不是指向 const 型的 Time 对象的指针,即没有 const限定词,则 t1 的 hour 就可以被修改。

Page 43: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 44

3.6 共用数据的保护有人可能会问,我不将 func 函数的形参定义为

const 型对象,只将 t1 定义为 const 型对象,不是也能保护 t1 在 func 中不被篡改吗?

不行,会出现编译错误。因为指向非 const 对象的指针不能指向 const 对象;同理,指向非 const 变量的指针不能指向 const 变量。

不能通过指向常对象的指针改变所指对象的值,但指针变量本身的值可以改变。

Page 44: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 45

3.6 共用数据的保护何时使用指向常对象的指针?何时使用常对象?

当希望在调用函数时对象的值不能被修改,就应该把形参定义为指向常对象的指针,同时用对象的地址做实参,而实参对象可以是 const 型,也可以是非 const 型。

如果要求对象不仅在函数调用过程中不被修改,而且在整个程序运行时不被修改,就应该把该对象定义成常对象。

Page 45: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 46

3.6 共用数据的保护对象的常引用

前面讲过,引用主要是用于函数调用,将改变后的变量值带回到被调用的函数外。

但如果不希望在函数中修改参数,可以把引用型形参定义成 const 型:

函数返回类型 函数名 ( const 形参类型 & 形参名 );

则在函数中不能改变形参值,也就不能改变对应的实参值。

什么时候使用常指针和长引用?使用常指针或常引用作为函数参数,既能保证数据安全,不被修改;调用函数时又能不必建立实参的拷贝,提高了程序运行效率,节省了内存空间。

Page 46: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 47

3.6 共用数据的保护 const 数据小结

形式 含义Time const t1;

const Time t1;

t1 是常对象,其值在任何情况下不能改变

void Time::func( )

const

func 是 Time 类中的常成员函数,可以引用,但不能修改本类中的数据成员

Time * const p; p 是指向 Time 对象的常指针, p 的值,即p 的指向不能改变

Const Time *p; p 是指向 Time 类常对象的指针,其指向的对象的值不能通过指针来改变

const Time &t1 = t; t1 是 Time 类对象 t 的常引用,函数中不能改变实参值

Page 47: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 48

3.7 对象的动态建立和释放

问题的提出:前面我们所学创建对象的方法都是静态的,它们在程序 运行过程中所占用的内存空间不能被释放。比如,在一个函数中定义了一个对象,只有在函数结束时,该对象才能被释放。

有时候,人们希望对象的创建和释放是在程序运行时,由运行程序的人决定,比如,链表结构的建立和删除,这就需要动态建立和释放对象。

C++ 语言用 new 、 delete 这两个运算符来实现。内存的分配和释放,也用来实现对象的建立与撤消。

Page 48: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 49

3.7 对象的动态建立和释放动态建立对象的方法:

如果已经定义了一个 Box 类,可以用 new 运算符动态地创建一个 Box 对象:

new Box;

系统执行此语句时,首先开辟一段内存空间,并在此空间中存放一个 Box 类对象,同时运行该类的构造函数,以初始化该对象,然后返回一个指向该对象的指针值。

但此时用户还不能访问这个对象,因为它既没有对象名,用户也不知道它的地址。这种对象称为“无名对象”。我们应该这样做:

Box *pt; // 定义一个指向 Box 类对象的指针 pt

pt = new Box; // 创建一个 Box 对象,将新对象的指针值赋给 pt

cout << pt->height <<endl;

Page 49: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 50

3.7 对象的动态建立和释放用 new 动态创建的对象一般没有对象名,只能通过

指针访问。在执行 new 运算时,如果内存不足,则创建失败。

大多数 C++ 编译系统都让 new 返回一个 0 指针,表示内存不足,操作失败。

动态撤消对象的方法:由 new 创建的对象不再需要时,可以由 delete 运算符释放。上例 pt 的释放方法为:

delete pt ;这样就撤消了 pt 指向的对象。在执行 delete 运算符时,在释放空间以前,系统自动调用

析构函数,完成有关善后清理工作。

Page 50: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 51

3.8 对象的赋值与复制对象的赋值

对象之间的赋值是通过运算符“ =” 进行的,其过程是将一个对象的成员复制给另一同类对象的对应成员。

对象赋值格式:对象名 1= 对象名 2

对象的复制C++ 可以根据一个已知的对象快速地复制出多个完全相同

的对象。比如:Box box2(box1);

其作用就是对象的克隆,即用一个已知的对象 box1 复制出一个完全相同的新对象 box2 。

对象复制的格式:类名 被克隆出来的新对象名(已有的对象名);

Page 51: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 52

3.8 对象的赋值与复制从上面的一般形式可以看出,复制对象是一种特殊的

构造对象方法,其构造参数参数不是一般变量,而必须是一个对象!请看:

// 复制构造函数Box::Box ( const Box &b)

{ height = b.height;

width = b.width;

height = b.height;

}

复制构造函数也是构造函数,它只有一个参数,这个参数是本类已有对象,而且采用常引用形式,使参数不能被改变。复制构造函数的作用是将实参对象的各个数据成员值一一赋予给新的对象中对应的数据成员。

Page 52: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 53

3.8 对象的赋值与复制C++还使用另一种方便的对象复制形式,形式为:

类名 目标对象名 = 源象名;如:

Box box2 = box1, box3 = box2; 对象的赋值与复制的不同点

对象的赋值:是在已经存在的对象之间进行数据赋值,因此必须先定义,再赋值;参数表是一般变量

对象的复制:从无到有地建立一个相同的新对象,参数只有一个,而且是已有的同类对象。

普通构造函数与拷贝构造函数的不同: 参数不同:

普通构造函数:类名 ( 形参表列 ); // 普通构造函数的声明拷贝构造函数:类名 ( 类名 & 对象名 ); // 复制构造函数的声明

返回类型不同拷贝构造函数返回一个对象。

Page 53: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 54

3.8 对象的赋值与复制什么时候使用复制构造函数?

程序中需要建立一个对象,并用另一个已知对象初始化它,系统自动调用复制构造函数;

当函数的参数是类的对象时,系统自动调用复制构造函数;void main( ){ Box b1(12,13,15); func(b1); }

当函数的返回值是类的对象,系统自动调用复制构造函数Box f( ){ Box b1(12,14,16); return b1; } // 返回值是 Box 类的对象void main ( ){ Box b2; b2 = f( ); }// f 函数返回 Box 类的临时对象,并将它赋值给 b2

由于 b1 是在函数 f 中定义的,函数 f 结束时, b1 的生命期就结束了。因此不能将 b1 带回给main 函数,实际上,在 f 结束前,执行 return 语句时,复制一个 b1 一样的临时对象,然后将它赋值给 b2 。

Page 54: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 55

#include <string>using namespace std;class student{ string name; int age; float score; static int cnt;public: student(string n,int a,float s){ name=n; age=a; score=s; cnt++; cout<<"1+"<<endl; } student(const student &s); ~student(){ cnt--; cout<<cnt<<"."<<endl; } void show();};

void student::show(){ cout<<name<<" "<<age <<" "<<score<<endl;}student::student(const student &s){ name=s.name; age=s.age; score=s.score; cnt++; cout<<"2*"<<endl; }

int student::cnt=0;void main(){ student s[3]= {student("zhangsan",20,90.0), student("zhaohong",21,70.0), student("lilongxin",19,70.0) }; for (int i=0;i<3;i++) s[i].show();}

Page 55: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 56

3.9 静态成员在 C 语言中,如果想在多个函数中共享一个变量值,我

们一般用全局变量。但由于全局变量破坏了封装性,安全得不到保证,在 C++ 中不提倡使用全局变量,我们可以使用静态的数据成员来达到这个目的。

静态数据成员静态数据成员以 static 关键字定义。例如:

class student

{ public:

int display( );

private:

static int count;

char name[10];

int age;

};

Page 56: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 57

3.9 静态成员将对象中的 count 数据成员定义成 static 型,它

就被同一种类的各个对象所共有,而不只属于某一个对象。静态数据成员只占份内存空间,而不是各个对象个拥有一份内存空间!每个对象都可以引用这个静态数据成员。静态数据成员的值对各个对象都是一样的。如果改变它的值,则在各个对象中这个数据成员的值都同时改变。

Page 57: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 58

3.9 静态成员说明:

在为对象分配空间时,不分配静态数据成员的空间,因为它不属于任何对象。只要类中定义了静态数据成员,即使不定义对象,编译系统也要为静态数据成员开辟内存空间。

C 语言中,我们知道,如果在一个函数中定义了一个静态变量,在函数结束时该静态变量不被释放,并保留其值。静态数据成员也是这样,它不随对象的建立而分配空间,也不随对象的撤消而释放空间,其值也被保留。静态数据成员在程序被编译时就分配了空间,在程序结束时,才释放空间。

静态数据成员可以被初始化,但只能在内体之外初始化:数据类型 类名 :: 静态数据成员名 = 初值;

不必在初始化语句中加 static 关键字,不能用参数初始化表初始化静态数据成员:

student (int c, char *p, int a) : count(c) { } // 错误, count 是静态数据成员

Page 58: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 59

3.9 静态成员静态数据成员既可以通过类名引用,也可以通过对象名引用。

#include <iostream.h>#include <string.h> class Student { public: static int count; // 静态数据成员 char name[40]; Student(char* pN ="no name") { cout<<"Create one student "<<pN<<"\n"; strcpy(name,pN); count ++; cout<<count<<endl; } ~Student ( ) { cout<<"destruct one student “ <<name<<"\n"; count --; cout<<count<<endl; } };

int Student::count=0;

void main( ) {Student s1("zhangsan");

cout<<"Student::count“ <<Student::count <<endl;

Student s2("lisi");

cout<<"Student::count“ << Student::count << endl; }

Page 59: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 60

3.9 静态成员静态成员函数

在类的定义中,成员函数前如果加了 static 限定词,该成员函数就成为静态成员函数。例:

static int volume( );用途:静态成员函数的作用不是为了对象之间的沟通,主

要是为了引用本类中的静态数据成员。它可以直接引用本类的静态数据成员。

静态成员函数与普通成员函数的区别:静态成员函数没有 this 指针,由此决定静态成员函数不能访问本类中的非静态数据成员,除非用“对象名 .非静态数据成员”的形式。

Page 60: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 61

3.9 静态成员静态成员函数示例

#include <iostream.h>#include <string.h> class Student { protected: static int count; // 静态数据成员 char name[40]; public: Student(char* pN ="no name") { cout<<"Create one student "<<pN<<"\n"; strcpy(name,pN); count ++; cout<<count<<endl; } ~Student( ) { cout<<"destruct one student “ <<name<<"\n"; count --; cout<<count<<endl; } static int number( ) // 静态成员函数 { return count; } // 直接引用本类的静态数据成员};

int Student::count=0;

void fn( ) { Student s1("zhangsan"); Student s2("lisi"); cout<<Student::number( ) <<endl; cout<<s1.number( ) <<endl; cout<<s2.number( ) <<endl; }

void main( ) { fn( ); cout<<"Student::number “ <<Student::number( ) <<endl; }

Page 61: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 62

3.10 友元

类具有封装性,类中的私有数据只有通过该类的成员函数才可以访问。如果在程序中需要访问类的私有成员,就必须通过对象来调用类的成员函数,频繁调用成员函数将影响程序运行效率。

为解决上述问题, C++ 提供一种友元机制,友元可以不通过调用成员函数就可以直接访问类的私有数据,以提高程序运行效率。

友元机制在数据封装这堵不透明的墙上开了一个小孔,友元的使用要慎重。

友元可以是一个普通函数,可以是一个类的成员函数,也可以是一个类。

Page 62: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 63

3.10 友元友元函数特点:

在类体里边说明,在函数的类型符前加关键字 friend ;在类体外定义,定义格式与普通函数相同;友元函数是非成员函数,在调用上与普通函数相同;友元函数可以直接访问该类中的私有成员。

例如:下面程序段说明友元函数的说明和定义方法class X{ int i; friend void func( X *, int ); // friend function declarationPublic : void member_func ( int );};

void func(X *ptr, int a) { //friend function definition ptr->i=a;}void X::member_func(int a){ //member function definition i=a;}

Page 63: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 64

一个友元的综合示例#include <iostream.h>#include <math.h>class Point{public: Point ( double i, double j ) { x=i; y=j; } void getxy( )

{cout<<“(”<<x<<“,”<<y<<“)”<<endl; }

friend double Distance (Point a, Point b);

private: double x,y;};double Distance (Point a, Point b){ // 友元函数的定义 double dx=a.x-b.x; double dy=a.y-b.y; return sqrt(dx*dx+dy*dy); }

void main( ){ double d11=3.0,d12=4.0; double d21=6.0,d22=8.0; Point P1(d11,d12),P2(d21,d22); P1.getxy( ); P2.getxy( ); double d=Distance(P1,P2); // 友元函数调用 cout<<“Distance is ”<<d<<endl;}

运行结果:(3,4)(6,8)Distance is 5

// 友员函数既可以在 PUBLIC ,// 也可在 PRIVATE

Page 64: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 65

3.10 友元友元成员函数: friend 函数也可以是另一个类中的成员函数。

#include <iostream.h>class two;class one{ private: int value;public: one(int v) { value=v; } void settwo(two & ob); void show( ) { cout<<"class one's private value= “ <<value<<endl;}};class two{ private: int value;

public: void show() { cout<<"class two's private value= “ <<value<<endl; } friend void one::settwo(two & ob);};

void one::settwo(two & ob){ ob.value=value;}void main( ){ one ob1(2); two ob2; ob1.settwo(ob2); ob1.show( ); ob2.show( );}

Page 65: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 66

3.10 友元友元类: 当说明一个类为另一个类的友元时,友元类中

的所有成员函数都是另一个类的友元函数。例如:#include <iostream.h>class X{ public: friend class Y;// 类 Y 是类 X 的友元类 void set( int i ) { x=i; } void display( ) { cout <<“x=”<<x<<“,” <<“y=”<<y<<endl; }private: int x; static int y; // 静态数据说明};class Y{public: Y ( int i,int j ); void display( );private: X a; // 数据成员为类 X 的对象};

int X::y=10; // 静态数据定义并初始化Y::Y(int i,int j){ a.x=i; X::y=j;}void Y::display( ){ cout <<“x=”<<a.x<<“,” <<“y=”<<X::y<<endl;}void main( ){ X b; b.set(5); b.display( ); Y c(6,9); c.display( ); b.display( );}

结果: x=5, y=10 x=6, y=9 x=5, y=9

Page 66: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 67

3.10 友元#include <iostream.h>class two{ private: int value; friend class one; };class one{ private: int value; public: one(int v) { value=v; } void settwo(two &ob) { ob.value=value; } void show (two &ob) { cout<<"class one's private value="<<value<<endl; cout<<"class one's private value="<<ob.value<<endl; }};void main( ){ one ob1(22); two ob2; ob1.settwo(ob2); ob1.show(ob2);}

Page 67: Object-Oriented Programming  in C++ 第三章 再论类和对象

C++ 3- 68

习题:本章习题请上网查阅教学网页:

http://staff.ustc.edu.cn/~leeyi