谭浩强版《C++程序设计》第9章n1.ppt

上传人:xt****7 文档编号:6045377 上传时间:2020-02-15 格式:PPT 页数:89 大小:476.50KB
返回 下载 相关 举报
谭浩强版《C++程序设计》第9章n1.ppt_第1页
第1页 / 共89页
谭浩强版《C++程序设计》第9章n1.ppt_第2页
第2页 / 共89页
谭浩强版《C++程序设计》第9章n1.ppt_第3页
第3页 / 共89页
点击查看更多>>
资源描述
第9章关于类和对象的进一步讨论 9 1构造函数9 2析构函数9 3调用构造函数和析构函数的顺序9 4对象数组9 5对象指针9 6共用数据的保护9 7对象的动态建立和释放9 8对象的赋值和复制9 9静态成员9 10友元9 11类模板 在建立一个对象时 作某些初始化的工作如对数据成员赋初值 即在创建对象 分配内存时 进行数据成员的初始化 因为对象是实实在在的对象 不能无具体属性值 注意 类的数据成员是不能在声明类时初始化的 9 1构造函数作用 创建对象 分配内存时 时进行数据成员的初始化9 1 1对象的初始化 如果一个类中所有的成员都是公用的 则可以在定义对象时对数据成员进行初始化 如classTime public 声明为公用成员hour minute sec Timet1 14 56 30 将t1初始化为14 56 30但是 一般数据成员是私有的 或者类中有private或protected的成员 就不能用这种方法初始化 如何实现 C 提供了构造函数 constructor 来处理对象的初始化 构造函数是特殊的成员函数 与其他成员函数不同 不需要用户来调用它 而是在建立对象时自动执行 构造函数的名字必须与类名同名 而不能由用户任意命名 以便编译系统能识别它并把它作为构造函数处理 它不具有任何类型 不返回任何值 构造函数的功能是由用户定义的 用户根据初始化的要求设计函数体和函数参数 9 1 2构造函数的作用 例9 1在例8 3基础上定义构造成员函数 includeusingnamespacestd classTime public Time 定义构造成员函数 函数名与类名相同 hour 0 利用构造函数对对象中的数据成员赋初值minute 0 sec 0 voidset time 函数声明voidshow time 函数声明private inthour 私有数据成员intminute intsec voidTime set time 定义成员函数 向数据成员赋值 cin hour cin minute cin sec voidTime show time 定义成员函数 输出数据成员的值 cout hour minute sec endl intmain Timet1 建立对象t1 同时调用构造函数t1 Time t1 set time 对t1的数据成员赋值t1 show time 显示t1的数据成员的值Timet2 建立对象t2 同时调用构造函数t2 Time t2 show time 显示t2的数据成员的值return0 程序运行的情况为 102554 从键盘输入新值赋给t1的数据成员 10 25 54 输出t1的时 分 秒值 0 0 0 输出t2的时 分 秒值 也可以在类外定义构造函数 Time Time 要加上类名Time和域限定符 hour 0 minute 0 sec 0 有关构造函数的使用 有以下说明 1 在类对象进入其作用域时调用构造函数 2 构造函数没有返回值 因此也不需要在定义构造函数时声明类型 这是它和一般函数的一个重要的不同之点 3 构造函数不需用户调用 也不能被用户调用 4 如果用户自己没有定义构造函数 则C 系统会自动生成一个构造函数 只是这个构造函数的函数体是空的 也没有参数 不执行初始化操作 不带参数构造函数 这种方式使该类的每一个对象都得到同一组初值 带参数的构造函数 用户希望对不同的对象赋予不同的初值 构造函数首部的一般格式 构造函数名 类型1形参1 类型2形参2 实参是在定义对象时给出的 定义对象的一般格式为 类名对象名 实参1 实参2 9 1 3带参数的构造函数 例9 2有两个长方柱 其长 宽 高分别为 1 12 20 25 2 10 14 20 求它们的体积 编一个基于对象的程序 在类中用带参数的构造函数 includeusingnamespacestd classBox public Box int int int 声明带参数的构造函数intvolume 声明计算体积的函数private intheight intwidth intlength Box Box inth intw intlen 在类外定义带参数的构造函数 height h width w length len intBox volume 定义计算体积的函数 return height width length intmain Boxbox1 12 25 30 建立对象box1 并指定box1长 宽 高的值cout Thevolumeofbox1is box1 volume endl Boxbox2 15 30 21 建立对象box2 并指定box2长 宽 高的值cout Thevolumeofbox2is box2 volume endl return0 程序运行结果如下 Thevolumeofbox1is9000Thevolumeofbox2is9450注意 带参数的构造函数中的形参 其对应的实参在定义对象时给定 C 还提供另一种初始化数据成员的方法 参数初始化表来实现对数据成员的初始化 这种方法不在函数体内对数据成员初始化 而是在函数首部实现 例如例9 2中定义构造函数可以改用以下形式 Box Box inth intw intlen height h width w length len 这种写法方便 简练 尤其当需要初始化的数据成员较多时更显其优越性 甚至可以直接在类体中 而不是在类外 定义构造函数 9 1 4用参数初始化表对数据成员初始化 在一个类中可以定义多个构造函数 以便对类对象提供不同的初始化的方法 供用户选用 这些构造函数具有相同的名字 而参数的个数或参数的类型不相同 这称为构造函数的重载 在第4章第4 6节中所介绍的函数重载的知识也适用于构造函数 通过下面的例子可以了解怎样应用构造函数的重载 9 1 5构造函数的重载 例9 3在例9 2的基础上 定义两个构造函数 其中一个无参数 一个有参数 includeusingnamespacestd classBox public Box 声明一个无参的构造函数Box inth intw intlen height h width w length len 声明一个有参的构造函数 用参数的初始化表对数据成员初始化intvolume private intheight intwidth intlength Box Box 定义一个无参的构造函数 height 10 width 10 length 10 intBox volume return height width length intmain Boxbox1 建立对象box1 不指定实参cout Thevolumeofbox1is box1 volume endl Boxbox2 15 30 25 建立对象box2 指定3个实参cout Thevolumeofbox2is box2 volume endl return0 在本程序中定义了两个重载的构造函数 其实还可以定义其他重载构造函数 说明 1 调用构造函数时不必给出实参的构造函数 称为默认构造函数 defaultconstructor 显然 无参的构造函数属于默认构造函数 一个类只能有一个默认构造函数 2 如果在建立对象时选用的是无参构造函数 应注意正确书写定义对象的语句 3 尽管在一个类中可以包含多个构造函数 但是对于每一个对象来说 建立对象时只执行其中一个构造函数 并非每个构造函数都被执行 构造函数中参数的值既可以通过实参传递 也可以指定为某些默认值 即如果用户不指定实参值 编译系统就使形参取默认值 例9 4将例9 3程序中的构造函数改用含默认值的参数 长 宽 高的默认值均为10 在例9 3程序的基础上改写如下 9 1 6使用默认参数的构造函数 includeusingnamespacestd classBox public Box inth 10 intw 10 intlen 10 在声明构造函数时指定默认参数intvolume private intheight intwidth intlength Box Box inth intw intlen 在定义函数时可以不指定默认参数 height h width w length len intBox volume return height width length intmain Boxbox1 没有给实参cout Thevolumeofbox1is box1 volume endl Boxbox2 15 只给定一个实参cout Thevolumeofbox2is box2 volume endl Boxbox3 15 30 只给定2个实参cout Thevolumeofbox3is box3 volume endl Boxbox4 15 30 20 给定3个实参cout Thevolumeofbox4is box4 volume endl return0 析构函数 destructor 也是一个特殊的成员函数 它的作用与构造函数相反 它的名字是类名的前面加一个 符号 在C 中 是位取反运算符 从这点也可以想到 析构函数是与构造函数作用相反的函数 当对象的生命期结束时 会自动执行析构函数 如果在一个函数中定义了一个对象 它是自动局部对象 当这个函数被调用结束时 对象应该释放 在对象释放前自动执行析构函数 9 2析构函数 static局部对象 只在main函数结束或调用exit函数结束程序时 才调用static局部对象的析构函数 全局对象 则在程序的流程离开其作用域时 如main函数结束或调用exit函数 时 调用该全局对象的析构函数 如果用new运算符动态地建立了一个对象 当用delete运算符释放该对象时 先调用该对象的析构函数 析构函数的作用 并不是删除对象 而是在撤销对象占用的内存之前完成一些清理工作 使这部分内存可以被程序分配给新对象使用 程序设计者事先设计好析构函数 只要对象的生命期结束 程序就自动执行析构函数来完成这些工作 析构函数不返回任何值 没有函数类型 也没有函数参数 因此它不能被重载 一个类可以有多个构造函数 但只能有一个析构函数 析构函数的作用并不仅限于释放资源方面 它还可以输出有关的信息 一般情况下 类的设计者应当在声明类的同时定义析构函数 以指定如何完成 清理 的工作 如果用户没有定义析构函数 C 编译系统会自动生成一个析构函数 但它只是徒有析构函数的名称和形式 实际上什么操作都不进行 想让析构函数完成任何工作 都必须在定义的析构函数中指定 例9 5包含构造函数和析构函数的C 程序 include includeusingnamespacestd classStudent 声明Student类 public student intn stringnam chars 定义构造函数 num n name nam sex s cout Constructorcalled endl 输出有关信息 Student 定义析构函数 cout Destructorcalled endl 输出有关信息voiddisplay 定义成员函数 cout num num endl cout name name endl cout sex sex endl endl private intnum charname 10 charsex intmain Studentstud1 10010 Wang li f 建立对象stud1stud1 display 输出学生1的数据Studentstud2 10011 Zhang fun m 定义对象stud2stud2 display 输出学生2的数据return0 程序运行结果如下 Constructorcalled 执行stud1的构造函数 num 10010 执行stud1的display函数 name Wang lisex fConstructorcalled 执行stud2的构造函数 num 10011 执行stud2的display函数 name Zhang funsex mDestructorcalled 执行stud2的析构函数 Destructorcalled 执行stud1的析构函数 在使用构造函数和析构函数时 需要特别注意对它们的调用时间和调用顺序 在一般情况下 调用析构函数的次序正好与调用构造函数的次序相反 最先被调用的构造函数 其对应的 同一对象中的 析构函数最后被调用 而最后被调用的构造函数 其对应的析构函数最先被调用 如图9 1示意 9 3调用构造函数和析构函数的顺序 图9 1 但是 并不是在任何情况下都是按这一原则处理的 对象可以在不同的作用域中定义 可以有不同的存储类别 这些会影响调用构造函数和析构函数的时机 下面归纳一下什么时候调用构造函数和析构函数 1 在全局范围中定义的对象它的构造函数在文件中的所有函数 包括main函数 执行之前调用 当main函数执行完毕或调用exit函数时 此时程序终止 调用析构函数 2 如果定义的是局部自动对象 例如在函数中定义对象 则在建立对象时调用其构造函数 如果函数被多次调用 则在每次建立对象时都要调用构造函数 在函数调用结束 对象释放时先调用析构函数 3 如果在函数中定义静态 static 局部对象 则只在程序第一次调用此函数建立对象时调用构造函数一次 只在main函数结束或调用exit函数结束程序时 才调用析构函数 数组也可以由对象组成 对象数组的每一个元素都是同类的对象 例如一个班有50个学生 每个学生的属性包括姓名 性别 年龄 成绩等 如果为每一个学生建立一个对象 需要分别取50个对象名 用程序处理很不方便 这时可以定义一个 学生类 对象数组 每一个数组元素是一个 学生类 对象 例如Studentstud 50 假设已声明了Student类 定义stud数组 有50个元素 9 4对象数组 在建立数组时 同样要调用构造函数 如果有50个元素 需要调用50次构造函数 如果构造函数有多个参数 则不能用在定义数组时直接提供所有实参的方法 因为一个数组有多个元素 对每个元素要提供多个实参 如果再考虑到构造函数有默认参数的情况 很容易造成实参与形参的对应关系不清晰 出现歧义性 如果构造函数有多个参数 在定义对象数组时应当怎样实现初始化呢 在花括号中分别写出构造函数并指定实参 如果构造函数有3个参数 分别代表学号 年龄 成绩 则可以这样定义对象数组 StudentStud 3 定义对象数组Student 1001 18 87 调用第1个元素的构造函数 为它提供3个实参Student 1002 19 76 调用第2个元素的构造函数 为它提供3个实参Student 1003 18 72 调用第3个元素的构造函数 为它提供3个实参 在建立对象数组时 分别调用构造函数 对每个元素初始化 每一个元素的实参分别用括号包起来 对应构造函数的一组形参 不会混淆 例9 6对象数组的使用方法 includeusingnamespacestd classBox public Box inth 10 intw 12 intlen 15 height h width w length len 声明有默认参数的构造函数 用参数初始化表对数据成员初始化intvolume private intheight intwidth intlength intBox volume return height width length intmain Boxa 3 定义对象数组Box 10 12 15 调用构造函数Box 提供第1个元素的实参Box 15 18 20 调用构造函数Box 提供第2个元素的实参Box 16 20 26 调用构造函数Box 提供第3个元素的实参 cout volumeofa 0 is a 0 volume endl cout volumeofa 1 is a 1 volume endl cout volumeofa 2 is a 2 volume endl 运行结果如下 volumeofa 0 is1800volumeofa 1 is5400volumeofa 2 is8320 在建立对象时 编译系统会为每一个对象分配一定的存储空间 以存放其成员 对象空间的起始地址就是对象的指针 可以定义一个指针变量 用来存放对象的指针 如果有一个类 classTime public inthour intminute intsec voidget time 9 5对象指针9 5 1指向对象的指针 voidTime get time couthourpt所指向的对象中的hour成员 即t1 hour pt get time 调用pt所指向的对象中的get time函数 即t1 get timept get time 调用pt所指向的对象中的get time函数 即t1 get time 对象有地址 存放对象初始地址的指针变量就是指向对象的指针变量 对象中的成员也有地址 存放对象成员地址的指针变量就是指向对象成员的指针变量 9 5 2指向对象成员的指针 1 指向对象数据成员的指针定义指向对象数据成员的指针变量的方法和定义指向普通变量的指针变量方法相同 例如int p1 定义指向整型数据的指针变量定义指向对象数据成员的指针变量的一般形式为数据类型名 指针变量名 如果Time类的数据成员hour为公用的整型数据 则可以在类外通过指向对象数据成员的指针变量访问对象数据成员hour p1 输出t1 hour的值 例9 7有关对象指针的使用方法 includeusingnamespacestd classTime public Time int int int inthour intminute intsec voidget time 声明公有成员函数 Time Time inth intm ints hour h minute m sec s voidTime get time 定义公有成员函数 coutget time 调用p2所指向对象 即t1 的get time函数 每个对象中的数据成员都分别占有存储空间 如果对同一个类定义了n个对象 则有n组同样大小的空间以存放n个对象中的数据成员 但是 不同对象都调用同一个函数代码段 那么 当不同对象的成员函数引用数据成员时 怎么能保证引用的是所指定的对象的数据成员呢 9 5 3this指针 在每一个成员函数中都包含一个特殊的指针 这个指针的名字是固定的 称为this 它是指向本类对象的指针 它的值是当前被调用的成员函数所在的对象的起始地址 例如 当调用成员函数a volume时 编译系统就把对象a的起始地址赋给this指针 于是在成员函数引用数据成员时 就按照this的指向找到对象a的数据成员 例如volume函数要计算height width length的值 实际上是执行 this height this width this length 由于当前this指向a 因此相当于执行 a height a width a length 这就计算出长方体a的体积 this指针是隐式使用的 它是作为参数被传递给成员函数的 本来 成员函数volume的定义如下 intBox volume return height width length C 把它处理为intBox volume Box this return this height this width this length 即在成员函数的形参表列中增加一个this指针 在调用该成员函数时 实际上是用以下方式调用的 a volume 将对象a的地址传给形参this指针 然后按this的指向去引用其他成员 需要说明 这些都是编译系统自动实现的 编程序者不必人为地在形参中增加this指针 也不必将对象a的地址传给this指针 在需要时也可以显式地使用this指针 例如在Box类的volume函数中 下面两种表示方法都是合法的 相互等价的 return height width length 隐含使用this指针return this height this width this length 显式使用this指针可以用 this表示被调用的成员函数所在的对象 this就是this所指向的对象 即当前的对象 例如在成员函数a volume 的函数体中 如果出现 this 它就是本对象a 上面的return语句也可写成return this height this width this length 注意 this两侧的括号不能省略 不能写成 this height 所谓 调用对象a的成员函数f 实际上是在调用成员函数f时使this指针指向对象a 从而访问对象a的成员 在使用 调用对象a的成员函数f 时 应当对它的含义有正确的理解 有时人们希望在需要用到对象时才建立对象 在不需要用该对象时就撤销它 释放它所占的内存空间以供别的数据使用 这样可提高内存空间的利用率 用new运算符动态建立对象 用delete运算符撤销对象 如果已经定义了一个Box类 可以用下面的方法动态地建立一个对象 9 7对象的动态建立和释放 newBox 编译系统开辟了一段内存空间 并在此内存空间中存放一个Box类对象 同时调用该类的构造函数 以使该对象初始化 如果已对构造函数赋予此功能的话 但是此时用户还无法访问这个对象 因为这个对象既没有对象名 用户也不知道它的地址 这种对象称为无名对象 它确实是存在的 但它没有名字 用new运算符动态地分配内存后 将返回一个指向新对象的指针的值 即所分配的内存空间的起始地址 用户需要定义一个指向本类的对象的指针变量来存放该地址 如Box pt 定义一个指向Box类对象的指针变量ptpt newBox 在pt中存放了新建对象的起始地址 在程序中就可以通过pt访问这个新建的对象 如coutheight 输出该对象的height成员coutvolume 调用该对象的volume函数 计算并输出体积C 还允许在执行new时 对新建立的对象进行初始化 如 调带参数的构造函数 Box pt newBox 12 15 18 这种写法是把上面两个语句 定义指针变量和用new建立新对象 合并为一个语句 并指定初值 这样更精炼 新对象中的height width和length分别获得初值12 15 18 调用对象既可以通过对象名 也可以通过指针 用new建立的动态对象一般是不用对象名的 是通过指针访问的 它主要应用于动态的数据结构 如链表 访问链表中的结点 并不需要通过对象名 在执行new运算时 如果内存量不足 无法开辟所需的内存空间 目前大多数C 编译系统都使new返回一个0指针值 只要检测返回值是否为0 就可判断分配内存是否成功 在不再需要使用由new建立的对象时 可以用delete运算符予以释放 如deletept 释放pt指向的内存空间 这就撤销了pt指向的对象 在执行delete运算符时 在释放内存空间之前 自动调用析构函数 完成有关善后清理工作 如果对一个类定义了两个或多个对象 则这些同类的对象之间可以互相赋值 即一个对象的值可以赋给另一个同类的对象 对象的值是指对象中所有数据成员的值 对象之间的赋值也是通过赋值运算符 进行的 本来 赋值运算符 只能用来对单个的变量赋值 现在被扩展为两个同类对象之间的赋值 这是通过对赋值运算符的重载实现的 实际这个过程是通过成员复制来完成的 即将一个对象的成员值一一复制给另一对象的对应成员 9 8对象的赋值和复制COPY9 8 1对象的赋值 对象名1 对象名2 注意对象名1和对象名2必须属于同一个类 例如Studentstud1 stud2 定义两个同类的对象 stud2 stud1 将stud1赋给stud2通过下面的例子可以了解怎样进行对象的赋值 例9 9对象的赋值 includeusingnamespacestd classBox public Box int 10 int 10 int 10 声明有默认参数的构造函数intvolume private intheight intwidth intlength Box Box inth intw intlen height h width w length len intBox volume return height width length 返回体积 intmain Boxbox1 15 30 25 box2 定义两个对象box1和box2cout Thevolumeofbox1is box1 volume endl box2 box1 将box1的值赋给box2cout Thevolumeofbox2is box2 volume endl return0 运行结果如下 Thevolumeofbox1is11250Thevolumeofbox2is11250说明 1 对象的赋值只对其中的数据成员赋值 而不对成员函数赋值 2 类的数据成员中不能包括动态分配的数据 否则在赋值时可能出现严重后果 有时需要用到多个完全相同的对象 此外 有时需要将对象在某一瞬时的状态保留下来 这就是对象的复制机制 用一个已有的对象快速地复制出多个完全相同的对象 如Boxbox2 box1 其作用是用已有的对象box1去克隆出一个新对象box2 其一般形式为类名对象2 对象1 用对象1复制出对象2 9 8 2对象的复制 可以看到 它与前面介绍过的定义对象方式类似 但是括号中给出的参数不是一般的变量 而是对象 在建立对象时调用一个特殊的构造函数 复制构造函数 copyconstructor 这个函数的形式是这样的 Thecopyconstructordefinition Box Box constBox 复制构造函数也是构造函数 但它只有一个参数 这个参数是本类的对象 不能是其他类的对象 而且采用对象的引用的形式 一般约定加const声明 使参数值不能改变 以免在调用此函数时因不慎而使对象值被修改 此复制构造函数的作用就是将实参对象的各成员值一一赋给新的对象中对应的成员 回顾复制对象的语句Boxbox2 box1 这实际上也是建立对象的语句 建立一个新对象box2 由于在括号内给定的实参是对象 因此编译系统就调用复制构造函数 它的形参也是对象 而不会去调用其他构造函数 实参box1的地址传递给形参b b是box1的引用 因此执行复制构造函数的函数体时 将box1对象中各数据成员的值赋给box2中各数据成员 如果用户自己未定义复制构造函数 则编译系统会自动提供一个默认的复制构造函数 其作用只是简单地复制类中每个数据成员 C 还提供另一种方便用户的复制形式 用赋值号代替括号 如Boxbox2 box1 用box1初始化box2其一般形式为类名对象名1 对象名2 可以在一个语句中进行多个对象的复制 如Boxbox2 box1 box3 box2 按box1来复制box2和box3 可以看出 这种形式与变量初始化语句类似 请与下面定义变量的语句作比较 inta 4 b a 这种形式看起来很直观 用起来很方便 但是其作用都是调用复制构造函数 请注意对象的复制和对象的赋值在概念上和语法上的区别 对象的赋值是对一个已经存在的对象赋值 因此必须先定义被赋值的对象 才能进行赋值 而对象的复制则是从无到有地建立一个新对象 并使它与一个已有的对象完全相同 包括对象的结构和成员的值 可以对例9 7程序中的主函数作一些修改 intmain Boxbox1 15 30 25 定义box1cout Thevolumeofbox1is box1 volume endl Boxbox2 box1 box3 box2 按box1来复制box2 box3cout Thevolumeofbox2is box2 volume endl cout Thevolumeofbox3is box3 volume endl 执行完第3行后 3个对象的状态完全相同 请注意普通构造函数和复制构造函数的区别 1 在形式上类名 形参表列 普通构造函数的声明 如Box inth intw intlen 类名 类名 实参是对象名 调用复制构造函数 3 在什么情况下被调用普通构造函数在程序中建立对象时被调用 复制构造函数在用已有对象复制一个新对象时被调用 在以下3种情况下需要克隆对象 程序中需要新建立一个对象 并用另一个同类的对象对它初始化 如前面介绍的那样 当函数的参数为类的对象时 在调用函数时需要将实参对象完整地传递给形参 也就是需要建立一个实参的拷贝 这就是按实参复制一个形参 系统是通过调用复制构造函数来实现的 这样能保证形参具有和实参完全相同的值 如voidfun Boxb 形参是类的对象 intmain Boxbox1 12 15 18 fun box1 实参是类的对象 调用函数时将复制一个新对象breturn0 函数的返回值是类的对象 在函数调用完毕将返回值带回函数调用处时 此时需要将函数中的对象复制一个临时对象并传给该函数的调用处 如Boxf 函数f的类型为Box类类型 Boxbox1 12 15 18 returnbox1 返回值是Box类的对象 intmain Boxbox2 定义Box类的对象box2box2 f 调用f函数 返回Box类的临时对象 并将它赋值给box2 以上几种调用复制构造函数都是由编译系统自动实现的 不必由用户自己去调用 读者只要知道在这些情况下需要调用复制构造函数就可以了 如果有n个同类的对象 那么每一个对象都分别有自己的数据成员 不同对象的数据成员各自有值 互不相干 但是有时人们希望有某一个或几个数据成员为所有对象所共有 这样可以实现数据共享 在第7章中曾介绍过全局变量 它能够实现数据共享 如果在一个程序文件中有多个函数 在每一个函数中都可以改变全局变量的值 全局变量的值为各函数共享 但是用全局变量的安全性得不到保证 由于在各处都可以自由地修改全局变量的值 很有可能偶一失误 全局变量的值就被修改 导致程序的失败 因此在实际工作中很少使用全局变量 如果想在同类的多个对象之间实现数据共享 也不要用全局对象 可以用静态的数据成员 9 9静态成员 静态数据成员是一种特殊的数据成员 它以关键字static开头 例如classBox public intvolume private staticintheight 把height定义为静态的数据成员intwidth intlength 如果希望各对象中的height的值是一样的 就可以把它定义为静态数据成员 这样它就为各对象所共有 而不只属于某个对象的成员 9 9 1静态数据成员 所有对象都可以引用它 每个对象都可以引用这个静态数据成员 静态数据成员的值对所有对象都是一样的 如果改变它的值 则在各对象中这个数据成员的值都同时改变了 这样可以节约空间 提高效率 说明 1 静态数据成员不属于某一个对象 在为对象所分配的空间中不包括静态数据成员所占的空间 静态数据成员是在所有对象之外单独开辟空间 只要在类中定义了静态数据成员 即使不定义对象 也为静态数据成员分配空间 它可以被引用 2 静态数据成员 它不随对象的建立而分配空间 也不随对象的撤销而释放 静态数据成员是在程序编译时被分配空间的 到程序结束时才释放空间 3 静态数据成员可以初始化 但只能在类体外进行初始化 如intBox height 10 表示对Box类中的数据成员初始化 其一般形式为数据类型类名 静态数据成员名 初值 不必在初始化语句中加static 注意 不能用参数初始化表对静态数据成员初始化 如在定义Box类中这样定义构造函数是错误的 Box inth intw intlen height h 错误 height是静态数据成员如果未对静态数据成员赋初值 则编译系统会自动赋予初值0 4 静态数据成员既可以通过对象名引用 也可以通过类名来引用 例9 10引用静态数据成员 includeusingnamespacestd classBox public Box int int intvolume staticintheight 把height定义为公用的静态的数据成员intwidth intlength Box Box intw intlen 通过构造函数对width和length赋初值 width w length len intBox volume return height width length intBox height 10 对静态数据成员height初始化 intmain Boxa 15 20 b 20 30 cout a height endl 通过对象名a引用静态数据成员cout b height endl 通过对象名b引用静态数据成员cout Box height endl 通过类名引用静态数据成员cout a volume endl 调用volume函数 计算体积 输出结果 上面3个输出语句的输出结果相同 都是10 所有对象的静态数据成员实际上是同一个数据成员 请注意 在上面的程序中将height定义为公用的静态数据成员 所以在类外可以直接引用 在类外可以通过对象名引用公用的静态数据成员 也可以通过类名引用静态数据成员 即使没有定义类对象 也可以通过类名引用静态数据成员 这说明静态数据成员并不是属于对象的 而是属于类的 但类的对象可以引用它 类内可随便访问 如果静态数据成员被定义为私有的 则不能在类外直接引用 而必须通过公用的成员函数引用 5 静态数据成员的作用 各对象之间的数据有了沟通的渠道 实现数据共享 因此可以不使用全局变量 全局变量破坏了封装的原则 不符合面向对象程序的要求 注意公用静态数据成员与全局变量的不同 静态数据成员的作用域只限于定义该类的作用域内在此作用域内 可以通过类名和域运算符 引用静态数据成员 而不论类对象是否存在 成员函数也可以定义为静态的 在类中声明函数的前面加static就成了静态成员函数 如staticintvolume 和静态数据成员一样 如果要在类外调用公用的静态成员函数 要用类名和域运算符 如Box volume 实际上也允许通过对象名调用静态成员函数 如a volume 但这并不意味着此函数是属于对象a的 而只是用a的类型而已 9 9 2静态成员函数 静态成员函数的作用 不是为了对象之间的沟通 而是为了能处理静态数据成员 前面曾指出 当调用一个对象的成员函数 非静态成员函数 时 系统会把该对象的起始地址赋给成员函数的this指针 而静态成员函数并不属于某一对象 它与任何对象都无关 因此静态成员函数没有this指针 既然它没有指向某一对象 就无法对一个对象中的非静态成员进行默认访问 静态成员函数与非静态成员函数的根本区别 非静态成员函数有this指针 而静态成员函数没有this指针 由此 静态成员函数不能访问本类中的非静态成员 静态成员函数可以直接引用本类中的静态数据成员 静态成员函数主要用来访问静态数据成员 而不访问非静态成员 假如在一个静态成员函数中有以下语句 cout height endl 若height已声明为static 则引用本类中的静态成员 合法cout width endl 若width是非静态数据成员 不合法如果一定要引用本类的非静态成员 应该加对象名和成员运算符 如cout a width endl 引用本类对象a中的非静态成员假设a已定义为Box类对象 且在当前作用域内有效 则此语句合法 例9 11静态成员函数的应用 includeusingnamespacestd classStudent 定义Student类 public Student intn inta floats num n age a score s 定义构造函数voidtotal staticfloataverage 声明静态成员函数private intnum intage floatscore staticfloatsum 静态数据成员staticintcount 静态数据成员 voidStudent total 定义非静态成员函数 sum score 累加总分 类内访问静态数据成员count 累计已统计的人数 类内访问静态数据成员 floatStudent average 定义静态成员函数 return sum count 只能访问静态数据成员 floatStudent sum 0 对静态数据成员初始化intStudent count 0 对静态数据成员初始化intmain Studentstud 3 定义对象数组并初始化Student 1001 18 70 Student 1002 19 78 Student 1005 20 98 intn cout n 输入需要求前面多少名学生的平均成绩for inti 0 i n i 调用3次total函数stud i total cout theaveragescoreof n studentsis Student average endl 调用静态成员函数return0 运行结果为pleaseinputthenumberofstudents 3 theaveragescoreof3studentsis82 3333说明 1 在Student类中定义了两个静态数据成员sum 总分 和count 累计需要统计的学生人数 这是由于这两个数据成员的值是需要进行累加的 它们并不是只属于某一个对象元素 而是由各对象元素共享的 可以看出 它们的值是在不断变化的 而且无论对哪个对象元素而言 都是相同的 而且始终不释放内存空间 2 注意 total是公有的成员函数 公有的成员函数可以引用本对象中的一般数据成员 非静态数据成员 也可以引用类中的静态数据成员 score是非静态数据成员 sum和count是静态数据成员 3 average是静态成员函数 可以直接引用私有的静态数据成员 不必加类名或对象名 函数返回成绩的平均值 4 在main函数中 引用total函数要加对象名 今用对象数组元素名 引用静态成员函数average函数要用类名或对象名 类的基本访问规则 在一个类中可以有公用的 public 成员和私有的 private 成员 在类外可以访问公用成员 只有本类中的函数可以访问本类的私有成员 现在 我们来补充介绍一个例外 友元 friend 友元可以访问与其有好友关系的类中的私有成员 友元包括友元函数和友元类 9 10友元 如果在本类以外的其他地方定义了一个函数 这个函数可以是不属于任何类的非成员函数 也可以是其他类的成员函数 在类体中用friend对其进行声明 此函数就称为本类的友元函数 友元函数可以访问这个类中的私有成员 但是外部访问 9 10 1友元函数 1 将普通函数声明为友元函数例9 12友元函数的简单例子 includeusingnamespacestd classTime public Time int int int friendvoiddisplay Time minute m sec s voiddisplay Time 调用display函数 实参t1是Time类对象 程序输出结果如下 10 13 56 由于声明了display是Time类的friend函数 所以display函数可以引用Time中的私有成员hour minute sec 但注意在引用这些私有数据成员时 必须加上对象名 不能写成cout hour minute sec endl 与类的成员函数的区别 只能以外部方式访问私有成员 2 友元成员函数friend函数不仅可以是一般函数 非成员函数 而且可以是另一个类中的成员函数 例9 13友元成员函数的简单应用 includeusingnamespacestd classDate 对Date类的提前引用声明 因在Time类中用到classTime 定义Time类 public Time int int int voiddisplay Date intminute intsec classDate 声明Date类 public Date int int int friendvoidTime display Date voidTime display Date 运行时输出 12 25 2004 输出Date类对象d1中的私有数据 10 13 56 输出Time类对象t1中的私有数据 在本例中定义了两个类Time和Date 程序第3行是对Date类的声明 因为在第7行和第16行中对display函数的声明和定义中要用到类名Date 而对Date类的定义却在其后面 在一般情况下 两个不同的类是互不相干的 在本例中 由于在Date类中声明了Time类中的display成员函数是Date类的 朋友 因此该函数可以引用Date类中所有的数据 请注意在本程序中调用友元函数访问有关类的私有数据方法 不仅可以将一个函数声明为一个类的 朋友 而且可以将一个类 例如B类 声明为另一个类 例如A类 的 朋友 这时B类就是A类的友元类 友元类B中的所有函数都是A类的友元函数 可以访问A类中的所有成员 不讲 9 10 2友元类 本章概念总结 构造函数 析构函数 对象指针 this指针 复制构造函数静态数据成员 静态函数成员友员 友员函数 友员成员函数掌握其作用与使用规则
展开阅读全文
相关资源
相关搜索

当前位置:首页 > 图纸专区 > 课件教案


copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!