当前位置: 首页 > news >正文

第九讲:共用数据的保护与对象的动态处理

第九讲:共用数据的保护与对象的动态处理

本讲基本要求

    * 掌握:常对象的定义;引入常对象数据成员的方法;对象的常引用。
    * 理解:常对象的指针的定义及引用。
    * 了解:对象的动态建立和释放。
重点、难点
    * 常对象的定义;引入常对象数据成员的方法;对象的常引用。

一、共用数据的保护

    虽然采取了不少有效的措施(如设private保护)以增加数据的安全性,但是有些数据却往往是共享的,例如实参与形参,变量与其引用,数据与其指针等,人们可以在不同的场合通过不同的途径访问同一个数据对象。有时在无意之中的误操作会改变有关数据的状况,而这是人们所不希望出现的。
   既要使数据能在一定范围内共享,又要保证它不被任意修改,这时可以使用const,即把有关的数据定义为常量。

1、常对象

定义常对象的一般形式为:
    类名const 对象名[(实参表列)]


    const 类名 对象名[(实参表列)];
二者等价。

说明:
   
1、在定义对象时指定对象为常对象。常对象中的数据成员为常变量且必须要有初值,这样,在所有的场合中,对象tl中的所有数据成员的值都不能被修改。凡希望保证数据成员不被改变的对象,可以声明为常对象。
   2、 如果一个对象被声明为常对象,则不能调用该对象的非const型的成员函数(除了由系统自动调用的隐式的构造函数和析构函数)。
   3、为了防止函数会修改常对象中数据成员的值。在函数中并没有修改常对象中数据成员的值也不允许调用,因为不能仅依靠编程者的细心来保证程序不出错,编译系统充分考虑到可能出现的情况,对不安全的因素予以拦截。
   4、编译系统不能进入函数去检查它的代码,看它是否修改了常对象中数据成员的值。实际上,函数的定义与函数的声明可能不在同一个源程序文件中。而编译则是以一个源程序文件为单位的,无法测出两个源程序文件之间是否有矛盾。如果有错,只有在连接或运行阶段才能发现。这就给调试程序带来不便。
   5、现在,编译系统只检查函数的声明,只要发现调用了常对象的成员函数,而且该函数未被声明为const,就报错,提请编程者注意。
   6、有时在编程时有要求,一定要修改常对象中的某个数据成员的值(例如类中有一个用于计数的变量count,其值应当能不断变化),ANSI 考虑到实际编程时的需要,对作了特殊的处理,对该数据成员声明为mutable。 如: mutable in count; 把count声明为可变的数据成员,这样就可以用声明为const的成员函数来修改它的值。

2、常对象成员(类)

可以在声明类时将成员声明为const,即声明常数据成员和常成员函数。

   ①常数据成员
   
作用和用法与一般常变量相似,用关键字const来声明常数据成员。常数据成员的值是不能改变的。有一点要注意:只能通过构造函数的参数初始化表对常数据成员进行初始化。

   如在类体中声明了常数据成员hour:
      const int hour; //声明hour为常数据成员

说明:
   
不能采用在构造函数中对常数据成员赋初值的方法,下面的用法是非法的:
      Time::Time(int h)
      { hour=h;} //非法, 因为常数据成员是不能被赋值的。
如果在类外定义构造函数,应写成以下形式:
   Time::Time(int h):hour(h){} //通过参数初始化表对常数据成员hour初始化在类体中声明了某一个数据成员为常数据成员后,该类的所有对象中的该数据成员的值都是不能改变的,但不同对象中的该数据成员的值可以是不同的(在定义对象时给出)。

   ②常成员函数

常成员函数的定义:类型 函数名 const

说明:
   
1、如果将成员函数声明为常成员函数,则只能引用本类中的数据成员,而不能修改它们,例如只用于输出数据等。一般的成员函数可以引用本类中的非const数据成员,也可以修改它们。
   2、在声明函数和定义函数时都要有const关键字,在调用时不必加const。常成员函数可以引用const数据成员,也可以引用非const的数据成员。
   3、const数据成员可以被const成员函数引用,也可以被非const的成员函数引用。具体情况可以用下表1表示。

怎样利用常成员函数呢?
   
(1)如果在一个类中,有些数据成员的值允许改变,另一些数据成员的值不允许改变,则可以将一部分数据成员声明为const,以保证其值不被改变。非const成员函数可以引用这些数据成员的值,并修改非const数据成员的值。
   (2)如果要求所有的数据成员的值都不允许改变,则可以将所有的数据成员声明为const,或将对象声明为const(常对象),然后用coast成员函数引用数据成员,这样起到“双保险”的作用,切实保证了数据成员不被修改。
   (3)如果已定义了一个常对象,只能调用其中的const成员函数,而不能调用非const成员函数(不论这些函数是否会修改对象中的数据)。这是为了保证数据的安全。如果需要访问对象中的数据成员,可将常对象中所有成员函数都声明为const成员函数,并确保在函数中不修改对象中的数据成员。
   不要误认为常对象中的成员函数都是常成员函数。常对象只保证所有数据成员的值不被修改。如果在常对象中的成员函数未加const声明,编译系统把它作为非const成员函数处理。
还有一点要指出:常成员函数不能调用另一个非const成员函数。

3、指向对象的常指针

   定义:将指向对象的指针变量声明为const型并将之初始化,这样指针值始终保持为其初不能改变,即其指向始终不变。

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

说明:
   
1、 Time tl(10,12,15),t2;//定义对象
      
Time *const ptrl=t1   //const位置在指针变量名前面,规定ptrl的值是常值并指向对象t1,此后不能再改变指向;
      
ptrl=&t2;          //错误,ptrl不能改变指向
   2、也可以在定义指针变量时使之初始化。
   3、指向对象的常指针变量的值不能改变,即始终指向同一个对象,但可以改变其所指向对象(如t1)中数据成员的值。
   4、往往用常指针作为函数的形参,目的是不允许在函数执行过程中改变指针变量的值,使其始终指向原来的对象。如果在函数执行过程中修改了该形参的值,编译系统就会发现错误,给出出错信息,这样比用人工来保证形参值不被修改更可靠。

4、 指向常对象的指针变量

   为了使读者更容易理解指向常对象的指针变量的概念和使用,首先了解指向常变量的指针变量,然后再进一步研究指向常对象的指针变量。

定义指向常变量的指针变量的一般形式为

      const 类型名 *指针变量名;

例:
下面定义了一个指向常变量的指针变量ptr:
    const char *ptr;
   
注意:const的位置在最左侧,它与类型名char紧连,表示指针变量ptr指向的char变量是常变量,不能通过ptr来改变其值的。


说明:
   
(1)如果一个变量已被声明为常变量,只能用指向常变量的指针变量指向它,而不能用一般的(指向非const型变量的)指针变量去指向它。
    (2)指向常变量的指针变量除了可以指向常变量外,还可以指向来被声明为const的变量。此时不能通过此指针变量改变该变量的值。
   (3)如果函数的形参是指向非const型变量的指针,实参只能用指向非const变量的指针,而不能用指向const变量的指针,这样,在执行函数的过程中可以改变形参指针变量所指向的变量(也就是实参指针所指向的变量)的值。如果函数的形参是指向const型变量的指针,在执行函数过程中显然不能改变指针变量所指向的变量的值,因此允许实参是指向const变量的指针,或指向非const变量的指针。 使用形参和实参的对应关系见表2。


表2 用指针变量作形参时形参和实参的对应关系

   以上的对应关系与在(2)中所介绍的指针变量和其所指向的变量的关系是一致的:指向常变量的指针变量可以指向const和非const型的变量,而指向非const型变量的指针变量只能指向非const的变量。

   以上介绍的是指向常变量的指针变量,指向常对象的指针变量的概念和使用是与此类似的,只要将“变量”换成“对象”即可。
   (1)如果一个对象已被声明为常对象,只能用指向常对象的指针变量指向它,而不能用一般的(指向非const型对象的)指针变量去指向它。
   (2)如果定义了一个指向常对象的指针变量,并使它指向一个非const的对象,则其指向的对象是不能通过指针来改变的。
   (3)指向常对象的指针最常用于函数的形参,目的是在保护形参指针所指向的对象,使它在函数执行过程中不被修改。
   (4)如果定义了一个指向常对象的指针变量,是不能通过它改变所指向的对象的值的,但是指针变量本身的值是可以改变的。

5、对象象的常引用

   过去曾介绍:一个变量的引用就是变量的别名。实质上,变量名和引用名都指向同一段内存单元。如果形参为变量的引用名,实参为变量名,则在调用函数进行虚实结合时,并不是为形参另外开辟一个存储空间(常称为建立实参的一个拷贝),而是把实参变量的地址传给形参(引用名),这样引用名也指向实参变量。

例8 对象的常引用。
#include <iostream>
using namespace std;
class Time
   { public:
      Time(int,int,int);
      int hour;
      int minute;
      int sec; };
Time::Time(int h,int m,int s) //定义构造函数
  
{ hour=h;
     minute=m;
     sec=s; }
void fun(Time &t)    //形参t是Time类对象的引用
  
{ t.hour=18;}

int main()
 { Time t1(10,13,56); //tl是Time类对象
   
fun(t1);    //实参是Time类对象,可以通过引用来修改实参d的值
   
cout<<t1.hour<<endl; //输出t1.hour的值为18
   
return 0; }

如果不希望在函数中修改实参t1的值,可以把引用t声明为const(常引用),函数原型为
   void fun(const Time &t);
   
则在函数中不能改变t的值,也就是不能改变其对应的实参和t1的值。
   在面向对象程序设计中,经常用常指针和常引用作函数参数。这样既能保证数据安全,使数据不能被随意修改,在调用函数时又不必建立实参的拷贝。在学习8节时会知道,每次调用函数建立实参的拷贝时,都要调用复制构造函数,要有时间开销。用常指针和常引用作函数参数,可以提高程序运行效率。

6、const型数据的小结

   本小节介绍const型数据和引用,利用它们可以对共用的数据进行保护。由于与对象有关的const型数据种类较多,形式又有些相似,容易混淆,因此本节集中归纳一下。为便于理解,以具体的形式表示,对象名设为Time。通过表3可以对几种const型数据的用法和区别一目了然,需要时也便于查阅。表中最后一行是对象的引用,不属于const型数据。

   可能会觉得奉节介绍的内容虽不难理解,但难以记住,请不必着急,也不必硬记,在以后的实际应用中会逐步熟悉并熟练地使用它们。

二、 对象的动态建立和释放

   用前面介绍的方法定义的对象是静态的,在程序运行过程中,对象所占的空间是不能随时释放的。例如在一个函数中定义了一个对象,只有在该函数结束时,该对象才释放。但有时人们希望在需要用到对象时才建立对象,在不需要用该对象时就撤销它,释放它所占的内存空间以供别的数据使用。这样可以提高内存空间的利用率。
   C语言用new运算符动态地分配内存,用delete运算符释放这些内存空间。这也适用于对象,可以用new运算符动态建立对象,用delete运算符撤销对象。

new运算符动态地分配内存的一般格式对象的指针变量=new 对象名

delete运算符释放这些内存空间的一般格式: delete 对象的指针变量

例:如果已经定义了一个Box类,可以用下面的方法动态地建立一个对象:

   Box *pt    //定义一个指向Box类对象的指针变量p1
   
pt=new Box;//在pt中存放了新建对象的起始地址
或:new Box;  //对象既没有对象名,用户也不知道它的地址。这种对象称为无名对象,它确实是存在的,但它没有名字。

说明:
   
1、编泽系统开辟了一段内存空间,并在此内存空间中存放一个Box类对象给pt指针。在程序中就可以通过pt访问这个新建的对象。如
   cout<<pt->height; //输出该对象的height成
   cout<<pt->volume(); //调用该对象的volume函数,计算并输出体积
   还允许在执行new时,对新建立的对象进行初始化。如:
Box *pt=new Box(12,15,18);
   2、调用对象既可以通过对象名,也可以通过指针。用new建立的动态对象一般是不用对象名的,而是通过指针访问的,它主要应用于动态的数据结构,如链表。访问链表中的结点,并不需要通过对象名,而是在上一个结点中存放下一个结点的地址,从而由上一个结点找到下一个结点,构成链接的关系。
   3、在执行new运算时,如果内存量不足,无法开辟所需的内存空间,目前大多数编译系统都使new返回一个。指针值。只要检测返回值是否为0,就可判断分配内存是否成功。AN61标准提出,在执行new出现故障时,就“抛出”一个“异常”,用户可根据异常进行有关处理(关于异常处理可参阅第8章)。但c 4-+标准仍然允许在出现new故障时返回。指针值。当前,不同的编译系统对new故障的处理方法是不同的。
   4、在不再需要使用由new建立的对象时,可以用delete运算符予以释放。如
delete Pt; //释放Pt指向的内存空间
这就撤销了pt指向的对象。此后程序不能再使用该对象。如果用一个指针变量pt先后指向不同的动态对象,应注意指针变量的当前指向,以免删错了对象。
   5、在执行delete运算符时,在释放内存空间之前,自动调用析构函数,完成有关善后清理工作。

转自:http://210.44.195.12/cgyy/text/HTML/text/09.htm

 

相关文章:

  • rtems 4.11 IRQ (arm,beagle)
  • 第十讲:对象的赋值和复制
  • jQuery插件之ajaxFileUpload[转载]
  • 第十一讲:运算符重载与重载函数
  • Google Chrome Frame 自定义渲染方式,调用ActiveX
  • 第十二讲:重载单、双目、插入、提取运算符
  • 第十三讲:不同类型数据间的转换
  • 一)6张表
  • 第十四讲:继承与派生的概念
  • Spring MyBatis Oracle 多数据源
  • 第十五讲:派生类的构造函数和析构函数
  • 解决:JS如何取得当前正在执行的function的名字
  • 第十六讲:多重继承
  • HDU 4891 The Great Pan (模拟)
  • 第十七讲:基类与派生类的转换
  • 《Java编程思想》读书笔记-对象导论
  • Android Studio:GIT提交项目到远程仓库
  • Android框架之Volley
  • Angularjs之国际化
  • css选择器
  • httpie使用详解
  • JavaScript新鲜事·第5期
  • SpingCloudBus整合RabbitMQ
  • Stream流与Lambda表达式(三) 静态工厂类Collectors
  • 产品三维模型在线预览
  • 大数据与云计算学习:数据分析(二)
  • 检测对象或数组
  • 面试题:给你个id,去拿到name,多叉树遍历
  • 排序(1):冒泡排序
  • 提醒我喝水chrome插件开发指南
  • 我是如何设计 Upload 上传组件的
  • 终端用户监控:真实用户监控还是模拟监控?
  • const的用法,特别是用在函数前面与后面的区别
  • Unity3D - 异步加载游戏场景与异步加载游戏资源进度条 ...
  • 策略 : 一文教你成为人工智能(AI)领域专家
  • ​io --- 处理流的核心工具​
  • (day 12)JavaScript学习笔记(数组3)
  • (Mac上)使用Python进行matplotlib 画图时,中文显示不出来
  • (十三)Maven插件解析运行机制
  • (已解决)vue+element-ui实现个人中心,仿照原神
  • (轉貼) 蒼井そら挑戰筋肉擂台 (Misc)
  • (最简单,详细,直接上手)uniapp/vue中英文多语言切换
  • ***php进行支付宝开发中return_url和notify_url的区别分析
  • .dwp和.webpart的区别
  • .net core 控制台应用程序读取配置文件app.config
  • .Net Core 中间件验签
  • .net MVC中使用angularJs刷新页面数据列表
  • .NET8.0 AOT 经验分享 FreeSql/FreeRedis/FreeScheduler 均已通过测试
  • .NET中 MVC 工厂模式浅析
  • .net中应用SQL缓存(实例使用)
  • :中兴通讯为何成功
  • @private @protected @public
  • @zabbix数据库历史与趋势数据占用优化(mysql存储查询)
  • [ 2222 ]http://e.eqxiu.com/s/wJMf15Ku
  • [ CTF ]【天格】战队WriteUp- 2022年第三届“网鼎杯”网络安全大赛(青龙组)