




已阅读5页,还剩138页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第三章 类和对象及其封装性 本章要点 1 类的定义及其类对象的封装性 2 类成员函数的定义 3 类对象的定义和使用 4 类的构造函数和析构函数 5 类对象的动态创建和释放 6 类对象的赋值与复制 7 与类对象有关的指针 8 类的静态成员 9 类对象成员、类对象数组和类对象参数 10 友元(友元函数、友元成员和友元类) 11 类的只读成员函数定义 3.1 类的定义及其类对象的封装性 无论采用哪种程序设计范型所设计的程序都是由数 据处理这些数据的操作组成的。程序的运行就是按照 特定的结构和流程将操作施加在相应的数据上,完成 程序需要实现的功能。 在传统设计范型中,数据是使用语言所提供的简单 数据类型和构造数据类型(例如 C 语言中的结构类型 struct)定义生成的;而操作是通过过程或函数的形式 定义提供的。 在面向对象设计范型中,使用了数据抽象的概念, 即数据总是与允许施加它们的操作绑定在一起的。这 就要求编程语言能够提供符合数据抽象的预定义数据 类型,特别需要提供能构造符合数据抽象用户自定义 类型的构造数据类型(例如 C+ 和 Java 语言中的类类 型 class)。程序中的数据和操作都是由按数据抽象封 装起来的对象提供的。 3.1.1 C+ 的类类型定义 在 C+ 中,用户可以使用类类型关键字 class 定义自 己的抽象数据类型。这种定义方法和形式与使用结构 体类型关键字 struct 定义数据结构类型十分相似。例 如,可以用 struct 定义描述学生基本信息的数据结构类 型 Student: struct Student int num; char name20; char sex; ; 同样,可以用 class 定义描述学生基本信息和基本操 作的数据类型 Student: class Student int num; char name20; char sex; public: void display() cout 成员名; 其中成员名必须是指针所指对象的所属类的公有数 据成员名或公有成员函数名。例如: class point int x, y; public: setpoint(int vx, int vy) x = vx; y = vy; ; void fun() point *p=new point;/ p 指向动态创建的 point 对 象 p-setpoint(10, 10); / 给 p 所指对象的坐标 x, y 赋值 . 3 通过对象的引用访问对象成员 一般形式:对象引用名.成员名; 其中成员名必须是对象引用名所引用对象的所属类 的公有数据成员名或公有成员函数名。例如: class point int x, y; public: setpoint(int vx, int vy) x = vx; y = vy; ; void fun() point pt, / p 引用 point 对象 pt p.setpoint(10, 10); / 给 p 引用的对象的坐标 x, y 赋值 . 3.3.4 成员名解析 由于类成员作用域在该类定义体所限定的边界内, 因此,不同类中具有同名的成员是不会产生二义性。 例如: class realSet/ 定义一个实数集合类 public: void print(); ; class intSet/ 定义一个整数集合类 public: void print(); ; void fun() intSet is; realSet rs; is.print();/ 调用 intSet 类中的 print() 函数 rs.print();/ 调用 realSet 类中的 print() 函数 显然不会引起二义性错误。返回 3.4 构造函数 使用类定义对象时,需要一种操作,使所定义的对 象与类的定义域相关。实现这一操作的成员函数称为 构造函数,该函数要完成的操作包括: 依据类数据成员的个数和类型为对象分配内存; 根据需要为对象的数据成员进行必要的初始化。 构造函数是类必须拥有的特殊成员函数,该函数从 定义形式到使用场合和方法上都与一般成员函数有所 区别,这些差异表现在以下几个方面: 构造函数名必须与类名相同,否则编译器将会把它 当作一般成员函数对待。例如: class Student public: Student(); ; Student:Student() 构造函数没有返回值,因此,声明和定义构造函数 时都不能说明它的返回类型; 构造函数的功能是将对象初始化,因此构造函数一 般只对数据成员进行初始化和必要的辅助操作,而 不提倡做与初始化无关的操作。 系统总会为类提供一个隐含的缺省构造函数。该构 造函数实际上是一个空定义体函数,因此只能为对 象分配空间而不能为数据成员进行初始化。 在大多数情况下,数据成员的初始化操作是十分必 要的,因此通常需要显式定义构造函数。构造函数 一旦显式定义,缺省构造函数将被覆盖。 在程序运行过程中,类对象是在进入其作用域时才 被创建的。也就是说,此时类对象的构造函数被调 用。 构造函数在类对象创建时由系统自动执行,不需要 用户调用,也不能由用户调用。例如: Student stud1;/ 系统调用构造函数创建 stud1 stud1.Student();/ 企图用一般成员函数的调用方法 / 调用构造函数,因此是错误的。 不能为构造函数定义函数指针,也不能获取构造函 数的调用地址。 基类的构造函数不能被派生类继承。 构造函数不能声明为虚函数。 例例3-13-1 定义一个整数队列类,使用由系统隐含提供的缺 省构造函数创建整数队列,并测试队列功能。 1 问题分析 用例分析 类图描述 向队列中 装入整数 从队列中 取出整数 用户 qurue -q2*:int -head:int -tail:int +qput(in i:int) +qget():int 2 详细设计 类设计 qurue 类 类定义 class queue int q100;/ 队列空间 int head, tail;/ 队列头、尾指示 public: void qput(int i); / 队列插入操作 int qget();/ 队列取出操作 ; 算法描述 成员函数 qput 的 N-S 流程图: 成员函数 qget 的 N-S 流程图: tail=100 head = 0; 类对象创建和使用 main 函数的 N-S 流程图: 使用 queue 类创建实例 a, b 向队列实例 a, b 中分别插入: 10,20 和 20,19 从队列实例 a, b 中分别顺序 取出数据,并显示 3.4.1 参数化的构造函数 与其他成员函数一样,构造函数也可以有参数。通 过这些参数为类对象的数据成员传递初值。例如: class point int x,y; public: point(int vx, int vy);/ 声明带参数的构造 函数 void offset(int ax, int ay); ; point:point(int vx, int vy) x = vx;/ 用传递来的实参对 x,y 赋 初值 y = vy; main() point p(10,20);/ 定义对象,并传递初值 / 注意,不要将使用参数创建类对象的代码写成: point p = point(10, 20); 3.4.2 构造函数的重载 在一个类中允许定义多个参数不同构造函数,即构 造函数重载。这样就为在不同情况下创建对象的特定 初始化需要提供了实现手段。也就是说,在类对象定 义时,编译器可以依据创建对象所需要的参数差异确 定调用构造函数的哪一个版本来创建类对象。 例例3-1-13-1-1 定义一个有两个构造函数的类,并使用不同构 造函数定义对象。 3.4.3 使用缺省参数值的构造函数 与其他函数一样,构造函数的参数也可以具有缺省 值,表示类对象的某些属性在大多数情况下是预先可 以确定的缺省状态,例如计数器的初值一般为 “0”、战 士的性别多数为 “男”、大学教师的学位一般为 “硕士” 等。构造函数的缺省参数值的定义和使用规则与其他 带缺省参数值的函数相同。 例例3-1-23-1-2 描述了如何声明,定义和使用带有缺省参数值 的类构造函数。 归纳构造函数使用缺省参数值的编程要点是: 指定缺省参数值只能在构造函数的声明中,而不能 出现在构造函数定义的首部。构造函数定义在类定 义体中的情况除外。 函数声明中的参数可以省略参数名,此时指定缺省 参数值的格式为:类型名 = 缺省值。例如: Box(int = 10, int = 10, int = 10); 如果构造函数的全部参数都指定了缺省值,应该避 免再定义一个无参数的构造函数。因为在定义构造 函数时,编译器会认为可能是重复定义。例如: Box(); Box(int = 10, int = 10, int = 10); 更重要的是定义类对象时,遇到如下情况: Box box1; 编译器无法确定调用哪一个构造函数版本来创建类 对象。 如果构造函数的全部参数都指定了缺省值,就容易 在重载构造函数时造成二义性。例如: Box(int = 10, int = 10, int = 10); Box(); Box(int, int); 因此,应避免全部参数都指定了缺省值。例如: Box(); Box(int, int = 10, int = 10); Box(int, int); 3.4.4 用参数初始化表对数据成员初始化 所谓参数初始化是指系统在为类对象的各个数据成 员分配内存空间的同时能按照用户通过参数指定的值 为数据成员赋值,而不是在各个数据成员的内存空间 分配完成后,再对它们进行赋值。这就需要通过一种 语法格式,即参数初始化表,使编译器能按照上述要 求实现对类对象的各个数据成员的初始化。构造函数 参数初始化表的一般形式为: : 基类初始化列表,属性初始化列表 其中基类初始化列表只有在派生类的构造函数初始化 表才会存在,这一部分将在第五章中介绍。属性初始 化列表由若干个属性初始化项组成,项间用 “,” 隔开 : 属性初始化项1, 属性初始化项2, 属性初始化项n 每个属性初始化项的一般格式为: 属性名(参数列表) 不难看出,属性初始化项的含义是调用相应的属性类 的具有参数的构造函数用于属性对象的创建和赋初值 操作。 这从另一个角度告诉我们,一个构造函数的定义中 没有出现初始化表意味着使用了隐含的初始化表,该 表的功能是分别调用了相应类的无参数(或有缺省参 数值)构造函数完成类对象的基类部分和各个属性对 象创建和赋初值。 例例3-1-33-1-3 是将例3-1-1中的类构造函数改写为使用参数初 始化表实现类对象各数据成员的初始化。 虽然两个实例中对类对象的数据成员初始化的结果 是完全相同的,但两种初始化方法对数据成员的赋值 的时间和方法是完全不同的。 在构造函数定义中使用初始化表另一个非常重要的 原因就是对于类对象的常数据成员、引用数据成员的 初始化就必须在创建的同时进行赋值操作,而不能在 函数体中进行赋值。例如: class A public: A(int i); const int/ 常引用数据成员 private: const int a;/ 常数据成员 ; A:A(int i) : a(i), ref(a) 3.4.5 拷贝构造函数 拷贝构造函数是一个特定的构造函数。该构造函数 与其他构造函数在形式上的差别仅在于函数的参数必 须是同类型对象的常引用。拷贝构造函数的原型格式 如下: 类型名(const 类型名 拷贝构造函数的功能是创建一个新对象,并将参数 所引用对象的各个数据成员值复制到新对象的对应的 数据成员域中。 系统会为每个类缺省定义一个拷贝构造函数,也允 许用户定义一个拷贝构造函数,用以取代缺省的拷贝 构造函数。一般情况下,使用系统定义的缺省拷贝构 造函数就可以满足类对象的复制操作,但在有些情况 下,用户必须定义自己的拷贝构造函数。例如: class string int length; char *str;/ 指针数据成员 public: string(int len); ; string:string(int len) length = len; str = new charlen + 1; / 指针数据成员指向动态分配的内存空间 main() string s1(10);/ 创建一个 string 对象 s1 string s2(s1);/ 复制 s1 到新 string 对象 s2 在这种情况下,s1 和 s2 的指针数据成员 str 指向了 同一内存空间,使得通过 str 对该内存空间的任何操作 都不能保持应有的独立性。更严重的是当 s1 和 s2 之 中有一个被撤消时,在堆中分配的内存空间被撤消回 收,使得另一个 string 对象的指针数据成员 str 成为无 效指针,任何通过该指针的操作均为非法操作,会导 致严重的运行错误。造成这一问题的原因是系统提供 的缺省拷贝构造函数不能复制 string 对象。因此,在 这 种情况下必须定义自己的拷贝构造函数: length str s1 length str s2 string:string(const string str = new charlength + 1; strcpy(str, s.str); Java 没有 C+ 那种含义的指针,也没有拷贝构造函 数。同时,对象的撤消是由垃圾收集器完成的,因此 也不会产生像 C+ 中那样的问题。当然在 Java 中也可 以用赋值运算符 “=” 来进行对象赋值,但是,这并不 意味着一个简单赋值操作所具有的直觉含义。例如: class User public string name; class Test public static void main(String args) User u1 = new User(“ariel”, 112); System.out.println();/ ariel User u2 = u1; = “muriel”; System.out.println();/ muriel 显然,这里的 u2 = u1 只是对对象引用的复制。由于 u1 和 u2 引用同一个 User 对象,所以才会导致修改 实际上等价于 对 的修改(这与 C+ 中两个指针指向同一个对象的情况类似)。如果要完 成直觉意义上的复制,就必须通过实现 User 类的克隆 接口 Cloneable 后,调用逐字节复制的克隆函数 clone() 完成。例如: class User implements Cloneable public string name; class Test public static void main(String args) User u1 = new User (“ariel”, 112); System.out.println ();/ ariel User u2 = (User)u1.clone(); = “muriel”; System.out.println ();/ ariel 3.5 析构函数 对象撤消时,也需要一种操作,使被撤消的对象从 程序的数据区中合法消失。实现这一操作的成员函数 称为析构函数,该函数要完成的操作包括: 回收被撤消对象数据成员所占用的内存; 根据需要完成回收被撤消对象数据成员所占内存之 前的必要操作。 析构函数也是类必须拥有的特殊成员函数,该函数 从定义形式到使用场合和方法上都与构造函数相似, 主要特点表现在以下几个方面: 析构函数名必须是类名加字符 “” 前缀,否则编译 器将会把它当作一般成员函数对待。例如: class Student public: Student(); ; Student:Student() 析构函数没有返回值,因此,声明和定义析构函数 时都不能说明它的返回类型; 系统总会为类提供一个隐含的缺省析构函数。该析 构函数实际上是一个空定义体函数,因此只能撤消 回收对象所占用的空间。 如果在对象被撤消之前无须做必要的预处理操作, 则可以放心使用缺省析构函数。但在有些情况下, 则必须定义自己析构函数。析构函数一旦显式定义 ,缺省析构函数将被覆盖。例如: class string int length; char *contents; public: string(char *s);/ 声明构造函数 string();/ 声明析构函数 ; 类 string 的对象在撤消之前需要先检查指针类型属性 contents 是否指向有效的内存空间,如果是,则应回收 所占用的内存空间。因此,必须重新定义析构函数, 取代系统隐含定义的缺省析构函数。 string 类的构造函 数和析构函数的操作可以按如下定义: string:string(char *s)/ 定义构造函数 if(s) length = strlen(s); contents = new charlength + 1;/ 分配存储 strcpy(contents, s);/ 字串赋值 else length = 0; contents = 0;/ 设置指针数据成员为空 string:string()/ 定义析构函数 if(contents) delete contents;/ 释放 contents 指向的内存 空间 在程序运行过程中,类对象是在退出其作用域时才 被析构的。也就是说,此时类对象的析构函数被调 用。 析构函数在类对象撤消时由系统自动执行,不需要 用户调用,也不能由用户直接调用。 不能为析构函数定义函数指针,也不能获取析构函 数的调用地址。 基类的析构函数不能被派生类继承。 析构函数可以声明为虚函数,并且提倡声明为虚函 数(详细原因在第六章 运行多态性中讲述)。 返回 3.6 对象的动态创建和释放 与预定义类型一样,自定义类型也可以使用运算符 new 动态创建对象和使用运算符 delete 撤消类对象。 例例3-23-2 描述了动态创建、撤消和使用 point 类对象。 注意: 1 使用无参数或具有缺省参数值的构造函数动态创建 类对象(即创建对象时不传递初始值)的格式为: new 类型名; 例如, point *p = new point; 而不应写成: new 类型名(); 例如, point *p = new point(); 上述格式与动态创建系统预定义类型变量的格式完 全一致(预定义类型无缺省初始值)。例如: int *p = new int; 2 使用有参数的构造函数动态创建类对象(即创建对 象时传递初始值)的格式为: new 类型名(初始值); 例如,point *p = new point(10,20); 上述格式与动态创建系统预定义类型变量并传递初 始值的格式完全一致。例如: int *p = new int(10); 例例3-33-3 通过构造函数对对象数组进行初始化。 对象数组初始化的方法一般有两种: 1 使用缺省构造函数创建对象数组后,调用一个专门 用于初始化的成员函数对数组中的每个对象分别进 行初始化。该方法虽然必须分两步完成对象数组的 创建和初始化,但可以将对象数组中的元素初始化 为任意值。 2 定义一个带缺省值参数的构造函数。使得在创建对 象数组的同时,由构造函数的缺省参数值完成数组 中的每个对象对象的初始化。使用该方法创建和初 始化对象数组简单、方便,但只能将对象数组中的 每个元素初始化为固定的缺省值。返回 3.7 对象的赋值与复制 1 对象的赋值 对象的赋值只能发生在同类型对象之间的,这与系 统预定义类型变量的赋值是一致的。对象赋值的一 般格式为: 对象名1 = 对象名2; 赋值操作是由赋值运算符 “=” 完成的,该运算符( 函 数)的功能是将对象名2 所指示对象的各个数据成员 值依次传递给对象名1 所指示对象的各个数据成员, 使对象1 与对象2 完全相同。 系统会为每一个自定义类型自动添加一个隐含的缺 省赋值运算符,因此一般的自定义类型不必显式定 义赋值运算符。但如果类定义中包含有指针类属性 (3.4.5 拷贝构造函数中已经讨论这种情况),则必 须显式定义赋值运算符用于取代隐含的缺省赋值运 算符。如何定义赋值运算符将在第五章中讨论。 例例3-2-13-2-1 描述了使用缺省赋值运算符完成对象的赋值 操作。 2 对象的复制 对象的复制是指按照一个已经存在的对象创建一个 与该对象完全相同的新对象。显然对象的复制操作 是由类的拷贝构造函数完成的,复制一个已有对象 的一般形式为: 类型名 对象2(对象1); 例如,Box box2(box1); 在 C+ 中上述复制也适用于预定义类型,即每个预 定义类型都有一个隐含的拷贝构造函数。 例如,int a(10), b(a); C+ 还提供了另外一种方便用户的复制表达式,即 用赋值运算符代替括号调用类的拷贝构造函数: 类型名 对象2 = 对象1; 例如,Box box2 = box1; 显然,这种复制形式与预定义类型变量赋值定义形 式是一致的。 例如,int a = 10, b = a; 程序中需要对象复制操作的情况有三种: 用户需要定义与已有对象完全一致的新对象。 函数调用时,系统需要复制被传递的实参对象。 函数返回时,系统会复制被返回的操作结果。返回 3.8 与对象有关的指针 指针变量可以用于指向任何预定义类型变量,当然 也可以指向自定义类型对象。不仅如此,指针变量还 可以指向对象的类成员。 1 指向对象的指针 定义指向对象的指针变量的一般形式: 类型名 *对象指针名;例如,Box *pt; 显然,这与定义预定义类型指针的形式是完全一致 的。同样,指针的使用形式也是相同的。例如: pt = new Box; pt-volume();/ 与 (*pt).volume(); 等价 2 指向对象成员的指针 对象的成员有数据成员和成员函数两种,因此指向 对象成员的指针也有两种。 指向数据成员的指针 定义指向数据成员的指针变量的一般形式: 类型名 *指针变量名; 显然,这与定义预定义类型指针是完全一致的。 同样,指针的使用形式也是相同的。例如: class Time public: int hour, minute, sec; void show(); ; void Time:show() cout *成员函数指针)(实参列表); 例如,Time *pt = (pt-*pf)(); 3 this 指针 this 指针是类成员函数拥有的一个隐含指针,它指向 该成员函数被调用时,操作所施加的类对象地址, 实现不同对象的相同行为的表现差异。如图所示: 成员函数 this 数据成员 类对象1 数据成员 类对象2 数据成员 类对象n 成员函数通过 this 指针可以访问所指对象中的类成 员,数据成员的访问格式可写为: this-数据成员名 成员函数的访问格式可写成: this-成员函数名(参数列表) 例如: 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; obj.load(100); cout x = x; this-y = y; 当然,这种问题也可以用如下方法解决: point:point(int x, int y) point:x = x; point:y = y; 为了进一步了解成员函数被调用时 this 指针是如何 在 “幕后” 工作的,我们再来分析一段程序: class abc private: char a; int b; public: void init(char ma, int mb) a = ma; b = mb; ; int main() abc ob; ob.init(x, 12); return 0; 分析:在 main() 中,abc 类成员函数 init 通过类对象 ob 被调用的: ob.init(x, 12); C+ 是如何使 this 指针指向对象 ob 的呢?编译器在 编译过程中对上述语句经过如下的转换: 首先将成员运算符“.”之前的对象 ob 的地址作为 实参传给函数 init,则语句变为: init( 虽然成员函数 init 的原型中没有相应的形参,但 程序编译时,编译器将该函数的原型和定义隐含 转换成以下形式: init(abc* this, char ma, int mb) this-a = ma; this-b = mb; 显然,该成员函数被调用时,第一个形参应是调 用该成员函数的对象地址,因此当对象 ob 调用 成员函数 init 时,形参 this = 例如本例中为 Student:count 提供初始化定义语句: int Student:count = 0; 如果只定义不赋初值,则被缺省设置为零。 注意,对于类的常静态(static const)数据成员, 也 需要和一般静态数据成员一样在类外初始化。 静态数据成员与其他静态变量一样,是在编译时创 建并被初始化的。它在该类的任何对象被建立之前 就存在,因此它可以在程序中不依赖对象被访问。 C+ 使用静态数据成员的一个主要原因是可以避免 使用全局变量。使用全局变量的类都是违反面向对 象的程序设计的封装原则的。 Java 中静态数据成员的声明、定义和用途与 C+ 中 的静态数据成员基本一致。但应注意表示上的不同, 例如,通过类名访问的表示为:Student.count 而不是 Student:count;类的常静态数据成员的声明关键字是 “static final”。 3.9.2 静态成员函数 静态成员函数的特点: 首先静态成员函数是类成员,因此它不能像类外函 数那样直接使用函数名被调用。调用静态成员函数 的形式有三种: 类名:函数 对象名.函数(对象名.类名:函数) 对象指针-函数(对象名-类名:函数) 不难看出,后两种形式中用于指明对象的“对象名.” 和 “对象指针-” 实际上是多余的,因此推荐使用第 一种形式。 其次静态成员函数是一个特殊的成员函数,它的调 用不依赖于任何一个特定的对象,因此静态成员函 数中没有隐含的 this 指针。所以试图让一个静态函 数去访问一个类中非静态数据成员是非常不方便 的,因为非静态数据成员总是属于一个特定的对 象,而要使函数的操作施加于特定对象必须借助 this 指针。一般而言,静态成员函数基本上只访问 静态数据成员。 例例3-53-5 中的描述了静态成员函数的用途、定义方法和调 用方法。 使用静态成员函数的几点说明: 静态成员函数可以在类内定义,也可以在类外定 义。如果在类外定义,只需要在类内将成员函数声 明为 static,而类外定义不必再用关键字 static。 编译系统将静态成员函数限定为内部链接,也就是 说,在编译链接的多个源文件中使用同名静态函数 不会发生冲突,维护了静态函数使用安全性,这也 是使用静态成员函数的一个原因。 使用静态成员函数的另一个原因是可以用它在建立 任何对象之前处理静态数据成员,实现建立任何对 象都需要的预操作。这是非静态成员函数所不能实 现的功能。 一般而言,静态成员函数不访问类的非静态成员。 若确实需要访问某个对象的非静态数据成员,则静 态成员函数只能通过参数提供要访问的对象名或对 象指针或对象引用。例如,如果要将例3-5 中的 display 函数定义为静态成员函数,则应将要访问的 对象引用作为参数: static void display(small_cat 在例3-12中: void settoreal(intset *set1, realset *set2) set2-card = set1-card; for(int i = 0; i card; i+) set2-elemsi = (double)set1-elemsi; 不难看出采用友元函数将大大地提高了转换效率。 虽然两个集合类 print 函数的操作类同,但无法通过 友元实现代码共享,而需要通过继承机制和模板来 实现。 3.13.3 友元成员函数 在一个类的定义中使用关键字 friend 说明的另一个 类的成员函数是所定义类的友元。这可以使两个类通 过这种方式相互直接访问、完成某一特定任务。 分析例3-12中的友元函数 settoreal,不难看出,集合之 间的转换操作使用友元成员函数来实现更为合理,即 不但可以将成员函数 intset:settoreal 声明为 realset 的友 元,实现整数集合转换为实数集合的操作;还可以定 义另一个成员函数 intset:settoint,也声明为 realset 的友 元,实现实数集合转换为整数集合的操作。使用友元 成员函数对封装性的消弱较少。 例例3-133-13 描述了如何实现这两个友元成员函数以及应用 它们结果。 3.13.4 友元类 在一个类的定义中使用关键字 friend 说明的另一个 类是所定义类的友元。友元类中的所有公有成员函数 都成为所定义类的友元。 当一个类的大部分或所有的成员函数都需要直接访 问该类的某个数据成员类的私有成员时,则将该类声 明为其需要直接访问的数据成员类的友元是最好的解 决方法。 例例3-143-14 实现的是链表堆栈的压入和弹出。 为此需要定义两个类,一个是结点类 node ,它包含值 和指针两个私有数据成员;另一个是堆栈类 stack,它 的数据成员为 node 类的堆栈头指针。stack 类的所有 操 作都是建立在对 node 类数据成员访问之上的,因此, 将 stack 类声明为 node 类的友元类是合理而有效的。 3.13.5 应用举例(例3-15) 设计一个应用程序能将一组整数数据按照升序顺序 存放在一个顺序表中,并要求能以该顺序表为依据生 成一棵二叉树。 1 问题分析 二叉树实际上与一个有序序列相对应。构造一棵二 叉树,其结点是按照左小右大的原则逐个插入的。 为此,需要定义一个二叉树类和有序序列类。在二 叉树类中包含了一个从有序序列转换生成二叉树的 成员函数。显然,该函数需要逐个访问有序序列中 元素(私有成员)。考虑到访问的效率和方便,应 该将该成员函数声明为有序序列类的友元。返回 3.14 类的只读成员函数定义 对于预定义类型的常量,编译器能够自动判别哪些 操作能施加于这些常量。例如,对于加、减、乘、除 等操作,常量可以作为任何一个操作数使用;而对于 赋值操作,常量是不允许作为左操作数使用的。 对于自定义类型的常对象,要使编译器能像判别预 定义类型常量的合法操作那样判别自定义类型常对象 的合法操作,则需要在类定义中声明哪些操作能施加 于该类的常对象,哪些不能施加于该类的常对象。实 现这些声明的方法是在允许施加于常对象的成员函数 原型和函数定义首部添加后缀修饰符 const 。例如: #include class Location public: Location(int x = 0, int y =0); void Move(int x, int y); void Print() const; private: int X, Y; ; Location:Location(int x, int y) : X(x), Y(y) void Location:Move(int x, int y) X = x; Y = y; void Location:Print() const cout X “
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025福建泉州市洛江区总商会招聘编外工作人员1人考前自测高频考点模拟试题及答案详解(考点梳理)
- 2025广东省事业单位集中招聘高校毕业生广州市中级人民法院岗位笔试模拟试卷带答案详解
- 贵州国企招聘2025贵州惠信餐饮管理有限责任公司招聘笔试历年参考题库附带答案详解
- 浙江国企招聘2025绍兴鉴湖酿酒有限公司公开招聘劳务派遣外包人员16人笔试历年参考题库附带答案详解
- 225四川长虹电子控股集团有限公司招聘宣传策划经理等岗位2人笔试历年参考题库附带答案详解
- 2025陕西西安建工物流设备集团3月招聘笔试历年参考题库附带答案详解
- 2025陕西榆林府谷能源投资集团有限公司选聘24人笔试历年参考题库附带答案详解
- 2025辽控集团所属国合集团招聘1人笔试历年参考题库附带答案详解
- 2025贵州黔南州都匀市农旅集团诚聘驻场生猪养殖技术人员6人笔试历年参考题库附带答案详解
- 2025贵州水投水库运营管理黔东南有限公司面向社会招聘笔试笔试历年参考题库附带答案详解
- 2025国企竞聘上岗与干部竞聘上岗笔试题及答案
- 人工智能与建筑产业体系智能化升级研究报告
- 武科大大学生手册考试内容及答案
- 集装箱吊装专项施工方案
- 2025年中国家用WiFi路由器行业市场全景分析及前景机遇研判报告
- 2025年领导干部任前廉政法规知识考试题库(含答案)
- 2025年山东省济宁市邹城市第十一中学中考二模数学试题
- 信息技术基础教程(WPS版)课件 第3章 Windows 10 操作系统的使用
- 小鹿斑比题目及答案
- 2024超声法检测混凝土缺陷技术规程
- 2025-2030中国建筑行业供应链金融发展现状与前景分析
评论
0/150
提交评论