内容 构造函数 析构函数 对象数组 对象指针 this指针 const数据保护. 类和对象的使用 潘荣江 山东大学.

Slides:



Advertisements
Similar presentations
课程 要求 参考 书目 课程 内容 课程 练习.
Advertisements

第九讲 类与对象 (I)面向对象基础.
Oracle数据库 Oracle 子程序.
6.4 字符串与指针 1. 用字符数组存放一个字符串.
7.2 访问控制 —— 公有继承 公有继承练习 //Point.h #ifndef _POINT_H #define _POINT_H class Point { //基类Point类的定义 public: //公有函数成员 void initPoint(float x = 0, float.
第14章 c++中的代码重用.
第八章 类和对象.
第10讲 Java面向对象编程基础(4) 教学目标 主要内容.
C++语言程序设计教程 第7章 类与对象 第7章 类与对象.
Using C++ The Weird Way Something about c++11 & OOP tricks
4.3函数 4.3.1函数的概念及定义 1、函数的概念: 可以被其它程序调用具有 特定功能的一段相对独立的 程序(模块),称函数。
类和对象 潘荣江 山东大学计算机科学与技术学院
資料大樓 --談指標與陣列 綠園.
C++语言程序设计 第四章 类与对象 成都信息工程学院计算机系.
4.1 概述 4.2 类与对象的实现 4.3 对象的初始化和析构 4.4 类的包含 4.5 类模板
C++语言程序设计 C++语言程序设计 第六章 指针和引用 第十一组 C++语言程序设计.
Classes: A Deeper Look, Part 2
授课老师:龚涛 信息科学与技术学院 2018年4月 教材:《Visual C++程序员成长攻略》 《C++ Builder程序员成长攻略》
第12讲 多继承与虚基类 多继承 虚基类.
C++语言程序设计教程 第7章 类与对象 第7章 类与对象.
走进编程 程序的顺序结构(二).
辅导课程六.
数据抽象 --对象与类 陈家骏老师.
第9讲 类与对象 9.1 面向对象程序设计的基本概念 9.2 对象与类的定义 9.3 内联函数 9.4 对象的使用.
第一单元 初识C程序与C程序开发平台搭建 ---观其大略
第六章 数据抽象-类 胡昊 南京大学计算机系软件所.
C++语言程序设计 C++语言程序设计 第六章 指针和引用 第十一组 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
用event class 从input的root文件中,由DmpDataBuffer::ReadObject读取数据的问题
第七章 操作符重载 胡昊 南京大学计算机系软件所.
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
C++面向对象程序设计 谭浩强编著 授课教师:姬广永 QQ: 学习网站:
简单介绍 用C++实现简单的模板数据结构 ArrayList(数组, 类似std::vector)
$9 泛型基础.
C#面向对象程序设计 $6 深入理解类.
C++语言程序设计 C++语言程序设计 第六章 指针和引用 第十一组 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
C++语言程序设计 第四章 类与对象.
Classes (1) Lecture 6.
第13讲 多态 友员函数 多态性与虚函数 纯虚函数和抽象类.
第10讲 构造函数和析构函数 构造函数 析构函数 This 指针.
分裂对象模型 C++ otcl.
C++语言程序设计 C++语言程序设计 第六章 指针和引用 第十一组 C++语言程序设计.
授课老师:龚涛 信息科学与技术学院 2016年3月 教材:《Visual C++程序员成长攻略》 《C++ Builder程序员成长攻略》
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
第4章 Excel电子表格制作软件 4.4 函数(一).
本节内容 类成员的访问控制 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
本节内容 结构体 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
C++程序设计基础 主讲人:谢昕 华东交通大学信息工程学院 第十~十二讲 多态性和虚函数 2005年春季学期.
多层循环 Private Sub Command1_Click() Dim i As Integer, j As Integer
C++语言程序设计 C++语言程序设计 第六章 指针和引用 第十一组 C++语言程序设计.
第 9 章 建構函式與解構函式.
辅导课程十五.
第7章 模板 陈哲 副教授 南京航空航天大学 计算机科学与技术学院.
本节内容 C语言的汇编表示 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
C++语言程序设计 C++语言程序设计 第十一章 异常处理 C++语言程序设计.
C++与数据结构简明教程 第五章 类和对象.
本节内容 结构体.
C++语言程序设计 C++语言程序设计 第十章 多态 第十一组 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第八章 继承 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第一章 C++语言概述 第十一组 C++语言程序设计.
本节内容 动态链接库 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第十章 多态 第十一组 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
第3章 关于类和对象的进一步讨论 3.1 构造函数 3.2 析构函数 3.3 调用构造函数和析构函数的顺序 3.4 对象数组
本节内容 this指针 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
Presentation transcript:

类和对象的使用 潘荣江 panrj@sdu.edu.cn 山东大学

内容 构造函数 析构函数 对象数组 对象指针 this指针 const数据保护

构造函数和析构函数 构造函数和析构函数是类的两种特殊的成员函数。 构造函数:在创建对象时,使用给定的值将对象的数据成员初始化,将对象初始化为一个特定的初始状态。。 析构函数:在系统释放对象前,做一些清理工作。

可以是内联函数。 构造函数的几点说明: 构造函数的函数名必须与类名相同。 不能指定函数返回值的类型,也不能指定为void类型,不能有return语句 一个类可以定义若干个构造函数,可以带参数,也可以没有参数。当定义多个构造函数时,必须满足函数重载的原则。 构造函数可以指定参数的缺省值。 可以是内联函数。

构造函数一般是公有的成员函数。如果定义的类仅用于派生其它类时,则可将构造函数定义为保护的成员函数。 构造函数属于类的成员函数,对私有数据成员、保护的数据成员和公有的数据成员均能进行初始化。 在建立类对象时自动调用构造函数。构造函数不需要用户调用,也不能被用户调用。

class A{ float x,y; public: A(float a,float b){ x=a; y=b;}//构造函数,初始化对象 float Sum(void) { return x+y; } void Set(float a, float b) { x=a; y=b;} Print(void) { cout<<"x="<<x<<'\t'<<"y="<<y<<endl;} }; int main( ) { A a1(2.0, 3.0);//定义对象时调用构造函数初始化 A a2(1.0, 2.0); a2.Set(10.0, 20.0); //利用成员函数重新为对象赋值 a1.Print(); a2.Print(); return 0; }

注意:不能写成 A a1( ),这是声明一个函数a1,其返回值是A类型 class A{ float x,y; public: A(float a, float b=10) { x=a; y=b; } A() { x=0; y=0;} void Print(void) { cout<<x<<'\t'<<y<<endl; } }; void main(void) { A a1, a2(20.0), a3(3.0, 7.0); a1.Print(); a2.Print(); a3.Print(); } 带缺省参数的构造函数 不带参数的构造函数 0 0 20 10 3 7 注意:不能写成 A a1( ),这是声明一个函数a1,其返回值是A类型

局部对象: 每次定义对象时,都要调用构造函数。 静态对象: 首次定义对象时,只调用一次构造函数,对象一直存在。 全局对象: 在main函数执行之前定义对象的时候调用构造函数。

1 2 2 2 class A { int x,y; public: A(int a){ x=a; cout<<“1\n”;} A(int a, int b) { x=a, y=b; cout<<“2\n”;} }; A a1(3); void f(void) { A a3(2,3);} void main(void) { A a2(4,5); f(); } 1 2 2 2

class A{ float x,y; public: A(float a, float b){x=a;y=b;cout<<"初始化自动局部对象\n";} A(){ x=0; y=0; cout<<"初始化静态局部对象\n";} A(float a){ x=a; y=0; cout<<"初始化全局对象\n"; } void Print(void){ cout<<x<<'\t'<<y<<endl; } }; A a0(100.0); //定义全局对象 void f(void) { cout<<" 进入f()函数\n"; A a2(1,2); static A a3; //初始化局部静态对象 } void main(void) { cout<<"进入main函数\n"; A a1(3.0, 7.0);//定义局部自动对象 f(); 初始化全局对象 进入main函数 初始化自动局部对象 进入f()函数 初始化自动局部对象 初始化局部静态对象 进入f()函数 初始化自动局部对象

缺省的构造函数 在定义类时,若没有定义类的构造函数,则编译器自动产生一个缺省的构造函数,其格式为: className::className() { } 缺省的构造函数: 参数列表为空,不为数据成员设置初始值; 如果类内定义了成员的初始值,则使用类内定义的初始值; 如果没有定义类内的初始值,则以默认方式初始化:基本类型的 数据默认初始化的值是不确定的。 在定义类时,只要显式定义了一个类的构造函数,则编译器就不产生缺省的构造函数。如果此时依然希望编译器隐含生成默认构造函数,可以使用“=default”。

class Clock { public:    Clock() = default; //指示编译器提供默认构造函数    Clock(int newH, int newM, int newS);     //构造函数 private:    int hour, minute, second; };

class A{ float x,y; public: float Sum(void) { return x+y; } void Set(float a,float b) { x=a; y=b;} void Print(void) { cout<<"x="<<x<<'\t'<<"y="<<y<<endl; } }; void main(void) { A a1,a2;//产生对象时,自动调用缺省的构造函数,不赋值 a1.Set (2.0,4.0); cout<<"a1: "; a1.Print (); cout<<"a1.sum="<<a1.Sum ()<<endl; a2.Print();//打印随机值 } 只允许这样定义对象

显式定义了构造函数,不产生缺省的构造函数 class A{ float x,y; public: A(float a,float b) { x=a; y=b; } void Print(void){ cout<<x<<'\t'<<y<<endl; } }; void main(void) { A a1; A a2(3.0,30.0); } 显式定义了构造函数,不产生缺省的构造函数 错误, 没有构造函数可供调用

两个构造函数均可供调用,构造函数不唯一,将产生编译错误 不需要实参的构造函数 1.参数表为空的构造函数 2.全部参数都有默认值的构造函数 class A{ float x,y; public: A(float a=10, float b=20){ x=a; y=b; } A(){ } void Print(void){ cout<<x<<'\t'<<y<<endl; } }; void main(void) { A a1; A a2(3.0,30.0); } 两个构造函数均可供调用,构造函数不唯一,将产生编译错误

用参数初始化表(member initializer list )对数据成员初始化 C++还提供另一种初始化数据成员的方法——参数初始化表 来实现对数据成员的初始化。这种方法不在函数体内对数 据成员初始化,而是在函数首部实现 Box∷Box(int h,int w,int len) : height(h), width(w), length(len) { } 这种写法方便、简练,尤其当需要初始化的数据成员较多 时更显其优越性。

//类定义 class Clock { public:      Clock(int newH,int newM,int newS);//构造函数 Clock() { } //无参构造函数      void setTime(int newH, int newM, int newS);      void showTime(); private:      int hour, minute, second; };   //构造函数的实现: Clock::Clock(int newH,int newM,int newS): hour(newH),minute(newM),  second(newS) { } //其它函数实现同上面 int main() {   Clock c(10,20,5); //自动调用构造函数   c.showTime();   Clock c2;         //调用无参数的构造函数      return 0;

委托构造函数 类中往往有多个构造函数,只是参数表和初始化列表不同,其初始化算法都是相同的,这时为了避免代码重复,可以使用委托构造函数。 委托构造函数使用类的其他构造函数执行初始化过程。 例如: Clock类的两个构造函数: Clock(int newH, int newM, int newS): hour(newH),minute(newM),  second(newS) { }  //构造函数 Clock::Clock(): hour(0),minute(0),second(0) { } //无参构造函数 Clock(int newH, int newM, int newS): hour(newH),minute(newM),  second(newS) { } Clock(): Clock(0, 0, 0) { } //委托构造函数

析构函数 析构函数的作用与构造函数正好相反,是在对象的生命期结束时,释放系统为对象所分配的空间。 在程序的执行过程中,当某一对象的生存期结束时,系统自动调用析构函数,收回为对象分配的存储空间。 析构函数的格式为: ClassName::~ClassName( ) { ...... // 函数体; }

析构函数的几点说明: 析构函数是成员函数,函数体可写在类体内,也可写在类体外。 析构函数名必须是在类名前面加上字符“~”。 析构函数不能带有任何参数,没有返回值,不指定函数类型。 一个类只能定义一个析构函数,不允许重载。 析构函数是在撤消对象时由系统自动调用的。 若在类的定义中没有显式定义析构函数,则编译器自动产生一个缺省的析构函数,其函数体为空。ClassName::~ClassName() { };

cout<<“调用有参的构造函数\n"; } class A{ float x,y; public: A(float a,float b) { x=a; y=b; cout<<“调用有参的构造函数\n"; } A(){ x=0; y=0; cout<<“调用无参的构造函数\n" ;} ~A() { cout<<"调用析构函数\n";} void Print(void) { cout<<x<<'\t'<<y<<endl; } }; void main(void) { A a1; A a2(3.0,30.0); cout<<"退出主函数\n"; 调用无参的构造函数 调用有参的构造函数 退出主函数 调用析构函数 调用析构函数

一般情况下,调用析构函数的次序正好与调用构造函数的次序相反: 最先创建的对象,其对应的析构函数最后被调用 最后创建的对象,其对应的析构函数最先被调用。

构造函数与new运算符、析构函数与delete运算符

5 0 0 class A{ float x,y; public: A(float a, float b) { x=a; y=b; } A() { x=0; y=0; } void Print(void) { cout<<x<<'\t'<<y<<endl; } }; void main(void) { A *pa1,*pa2; pa1=new A(3.0, 5.0);//用new动态开辟对象空间,初始化 pa2=new A; //用new动态开辟空间,调用默认构造函数初始化 pa1->Print(); pa2->Print(); delete pa1; //用delete释放空间 delete pa2; //用delete释放空间 } 5 0 0

用delete删除由new创建的对象时,自动调用析构函数,收回分配的动态存储空间。 A *p; p=new A; delete p; //调用析构函数

class A{ float x,y; public: A(float a, float b){ x=a; y=b; cout<<"调用了构造函数\n";} void Print(void){ cout<<x<<'\t'<<y<<endl; } ~A() { cout<<"调用了析构函数\n"; } }; void main(void) { cout<<"进入main()函数\n"; A *pa1; pa1=new A(3.0, 5.0);//调用构造函数 pa1->Print(); delete pa1; //调用析构函数 cout<<"退出main()函数\n"; } 进入main()函数 调用了构造函数 3 5 调用了析构函数 退出main()函数

如果在构造函数中用new为对象分配了动态存储空间,则在类中应该定义一个析构函数,并在析构函数中使用delete,收回由new分配的动态存储空间。

length=strlen(string); sp=new char[length+1]; strcpy(sp, string); } class Str{ char *sp; int length; public: Str(char *string) { if(string){ length=strlen(string); sp=new char[length+1]; strcpy(sp, string); } else { sp=0; length=0;} void Show(void) {cout<<sp<<endl; } ~Str() { if(sp) delete [] sp; } }; void main(void) { Str s1("Study C++"); s1.Show(); 在构造函数中将sp指向动态开辟的内存 用初值为开辟的内存赋值 析构函数,释放对象时收回用new开辟的空间 ‘S’ ‘t’ ‘u’ ‘d’ ‘y’ ‘ ’ ‘C’ ‘+’ ‘\0’ string ‘S’ ‘t’ ‘u’ ‘d’ ‘y’ ‘ ’ ‘C’ ‘+’ ‘\0’ sp new开辟的空间

不同存储类型的对象调用构造函数及析构函数 1、对于全局对象(在函数外定义的对象),在程序开始执行时,调用构造函数;到程序结束时,调用析构函数。 2、对于局部对象(在函数内定义的对象),当程序执行到定义对象的地方时,调用构造函数;在退出对象的作用域时,调用析构函数。 3、用static定义的局部对象,在首次到达对象的定义时调用构造函数;程序结束时,调用析构函数 4、对于用new运算符动态生成的对象,在产生对象时调用构造函数,只有使用delete运算符释放对象时,才调用析构函数。若不使用delete来撤消动态生成的对象,程序结束时,对象仍存在,并占用相应的存储空间,系统不能自动撤消动态生成的对象。

class A{ float x,y; public: A(float a, float b){x=a;y=b;cout<<"初始化自动局部对象\n";} A(){ x=0; y=0; cout<<"初始化静态局部对象\n";} A(float a){ x=a; y=0; cout<<"初始化全局对象\n"; } ~A(){ cout<<“调用析构函数”<<endl; } }; A a0(100.0);//定义全局对象 void f(void) { cout<<" 进入f()函数\n"; A ab(10.0, 20.0);//定义局部自动对象 static A a3; //初始化局部静态对象 } void main(void) { cout<<"进入main函数\n"; f(); 初始化全局对象 进入main函数 进入f()函数 初始化自动局部对象 初始化静态局部对象 调用析构函数 进入f()函数 初始化自动局部对象 调用析构函数 调用析构函数 调用析构函数

对象数组及数组元素的使用 #include <iostream> using namespace std; class Box { public: Box(int h=10,intw=12,int len=15): height(h),width(w),length(len){ } //声明有默认参数的构造函数,用参数初始化表对数据成员初始化 int volume( ); private: int height, width, length; }; int Box∷volume( ) { return(height*width*length); } int main( ) { Box a[3]={ //定义对象数组 Box(10,12,15), //调用构造函数Box, 提供第1个元素的实参 Box(15,18,20), //调用构造函数Box, 提供第2个元素的实参 Box(16,20,26) //调用构造函数Box, 提供第3个元素的实参 }; cout<<″volume of a[0] is″<<a[0].volume( )<<endl; //调用a[0]的volume函数 cout<<″volume of a[1] is″<<a[1].volume( )<<endl; //调用a[1]的volume函数 cout<<″volume of a[2] is″<<a[2].volume( )<<endl; //调用a[2]的volume函数 }

动态建立对象数组 用new运算符来动态生成对象数组时,自动调用构造函数, 用delete运算符来释放指针所指向的对象数组占用的存储空间时,在指针变量的前面必须加上[ ], 才能将数组元素所占用的空间全部释放。否则,只释放第0个元素所占用的空间。 A * pa1; pa1=new A[3]; ..... delete [ ]pa1;

A(float a=0, float b=0){x=a; y=b; cout<<"调用了构造函数\n";} class A{ float x,y; public: A(float a=0, float b=0){x=a; y=b; cout<<"调用了构造函数\n";} void Print(void){ cout<<x<<'\t'<<y<<endl; } ~A() { cout<<"调用了析构函数\n"; } }; void main(void) { cout<<"进入main()函数\n"; A *pa; pa=new A[3];//开辟数组空间 cout<<"\n完成开辟数组空间\n\n"; delete [ ]pa; //必须用[]删除开辟的空间 cout<<"退出main()函数\n"; } 进入main()函数 调用了构造函数 调用了构造函数 调用了构造函数 完成开辟数组空间 调用了析构函数 调用了析构函数 调用了析构函数 退出main()函数

指向对象的指针 在建立对象时,编译系统会为每一个对象分配一定的存储空间,以存放其成员。对象空间的起始地址就是对象的指针。可以定义一个指针变量,用来存放对象的指针。 class Time { public: int hour; int minute; int sec; void get_time( ); }; void Time∷get_time( ) { cout<<hour<<″:″<<minute<<″:″<<sec<<endl; } Time *pt; //定义pt为指向Time类对象的指针变量 Time t1; //定义t1为Time类对象 pt=&t1; //将t1的起始地址赋给pt,pt就是指向Time类对象的指针变量,它指向对象t1。 定义指向类对象的指针变量的一般形式为 类名 *对象指针名; 可以通过对象指针访问对象和对象的成员。如 *pt //pt所指向的对象,即t1。 (*pt).hour //pt所指向的对象中的hour成员,t1.hour pt->hour //pt所指向的对象中的hour成员,t1.hour (*pt).get_time ( ) //调用pt所指向的对象中的get_time函数,即t1.get_time( ) pt->get_time ( ) //调用pt所指向的对象中的get_time函数,即t1.get_time ( )

指向对象成员的指针 对象有地址,存放对象初始地址的指针变量就是指向对象的指针变量。 对象中的成员也有地址,存放对象成员地址的指针变量就是指向对象成员的指针变量。 如果Time类的数据成员hour为公用的整型数据,则可以在类外通过指向对象数据成员的指针变量访问对象数据成员hour。 int * p1=&t1.hour; //将对象t1的数据成员hour的地址赋给p1, p1指向t1.hour cout<<*p1<<endl; //输出t1.hour的值

this 指针 不同对象占据内存中的不同区域,它们所保存的数据各不相同,但对成员数据进行操作的成员函数的程序代码均是一样的。 当不同对象的成员函数引用数据成员时,怎么能保证引用的是所指定的对象的数据成员呢? 在每一个成员函数中都包含一个特殊的指针,这个指针的名字是固定的,称为this。 它是指向本类对象的指针,它的值是当前被调用的成员函数所属对象的起始地址。 例如,当调用成员函数a.volume()时,编译系统就把对象a的起始地址赋给this指针,在成员函数引用数据成员时,就按照this的指向找到对象a的数据成员。a.volume()函数要计算height*width*length的值,实际上是执行: (this->height)*(this->width)*(this->length) 当前this指向a, 相当于执行:(a.height)*(a.width)*(a.length) 如果执行b.volume( ),编译系统就把对象b的起始地址赋给成员函数volume的this指针,计算出来的是长方体b的体积.

class A{ int x,y; public: void Setxy(int a, int b) { x=a; y=b;} }; A a1, a2; a1. Setxy() a2. Setxy() ...... x=a ; y=b ; a1.x this->x=a; a2.x this->y=b; a1.y a2.y 系统自动将对象的指针带到成员函数中 a1.Setxy(1,2); a2.Setxy(3,4);

C++程序到C程序的翻译 class CCar { public: int price; void SetPrice(int p); }; void CCar::SetPrice(int p) { price = p; } int main() { CCar car; car.SetPrice(20000); return 0; } struct CCar { int price; }; void SetPrice(struct CCar * this, int p) { this->price = p; } int main() { struct CCar car; SetPrice( & car, 20000); return 0; }

共用数据的保护 C++虽然采取了不少有效的措施(如private保护)以增加数据的安全性,但是有些数据却往往是共享的,人们可以在不同的场合通过不同的途径访问同一个数据对象。有时在无意之中的误操作会改变数据的状况,这是不希望出现的。 要使数据能在一定范围内共享,又要保证它不被任意修改,这时可以使用关键字const,把有关的数据定义为常量。 l  对于既需要共享、又需要防止改变的数据应该声明为常类型(用const修饰)。 l  对于不改变对象状态的成员函数应该声明为常函数。

常(量)对象 为保证对象的数据成员的值不被改变,可以声明为常对象。 定义常对象的一般形式为 类名 const 对象名[(实参表列)]; 或 const 类名 对象名[(实参表列)]; Time const t1(12,34,46); //t1是常对象 常对象必须进行初始化,不能被更新。 如果一个对象被声明为常对象,则不能调用该对象的非const型的成员函数(除了由系统自动调用的隐式的构造函数和析构函数)。例如,对于已定义的Time类, const Time t1(10,15,36); //定义常对象t1 t1.get_time( ); //企图调用常对象t1中的非const型成员函数,非法 要使用常对象中的成员函数,需将该成员函数声明为const。如 void get_time( ) const; //将函数声明为const  通过常对象只能调用它的常成员函数。常成员函数可以访问常对象中的数据成员,但仍然不允许修改常对象中数据成员的值。

可变的数据成员 有时编程时一定要修改常对象中的某个数据成员的值,ANSI C++考虑到实际编程时的需要,对此作了特殊的处理,对该数据成员声明为mutable, 如:mutable int count; 把count声明为可变的数据成员后,可以用声明为const的成员函数来修改它的值。

常成员 1、常数据成员 用关键字const来声明常数据成员。常数据成员的值是不能改变的。 注意:只能通过构造函数的参数初始化表对常数据成员进行初始化。 如在类体中定义了常数据成员hour: const int hour; //声明hour为常数据成员 不能采用在构造函数中对常数据成员赋初值的方法。 在类外定义构造函数,应写成以下形式: Time∷Time(inth):hour(h){} //通过参数初始化表对常数据成员hour初始化 常对象的数据成员都是常数据成员,因此常对象的构造函数只能用参数初始化表对常数据成员进行初始化。

2.常成员函数 一般的成员函数可以引用本类中的非const数据成员,也可以修改它们。如果将成员函数声明为常成员函数,则只能引用本类中的数据成员,而不能修改它们,例如只用于输出数据等。如 void get_time( ) const; //注意const的位置 const是函数类型的一部分,在声明函数和定义函数时都要有const关键字,在调用时不必加const。 常成员函数可以引用const数据成员,也可以引用非const的数据成员。 常成员函数不能调用同类的非const成员函数。 const数据成员可以被const成员函数引用,也可以被非const的成员函数引用。

(1)如果在一个类中,有些数据成员的值允许改变,另一些数据成员的值不允许改变,则可以将一部分数据成员声明为const,以保证其值不被改变,可以用非const的成员函数引用这些数据成员的值,并修改非const数据成员的值。 (2)如果要求所有的数据成员的值都不允许改变,则可以将所有的数据成员声明为const, 或将对象声明为const(常对象),然后用const成员函数引用数据成员,这样起到“双保险”的作用,切实保证了数据成员不被修改。

(3)如果已定义了一个常对象,只能调用其中的const成员函数,而不能调用非const成员函数(不论这些函数是否会修改对象中的数据)。这是为了保证数据的安全。

class Sample { public: int value; void GetValue() const; void func() { }; Sample() { } }; void Sample::GetValue() const { value = 0; // wrong func(); //wrong } int main() { const Sample o; o.value = 100; //err.常量对象不可被修改 o.func(); //err.常量对象上面不能执行非常量成员函数 o.GetValue(); //ok,常量对象上可以执行常量成员函数 return 0; }

两个成员函数,名字和参数表都一样,但是一个是const,一个不是,这算重载。 class CTest { private : int n; public: CTest() { n = 1 ; } int GetValue() const { return n ; } int GetValue() { return 2 * n ; } }; int main() { const CTest test1; CTest test2; cout << test1.GetValue() << "," << test2.GetValue() ; return 0; }

指向对象的常指针 将指针变量声明为const型,这样指针值始终保持为其初值,不能改变。如 Time t1(10,12,15),t2; //定义对象 Time * const ptr1; //const位置在指针变量名前面,规定ptr1的值是常值 ptr1=&t1; //ptr1指向对象t1, 此后不能再改变指向 ptr1=&t2; //错误, ptr1不能改变指向 定义指向对象的常指针的一般形式为: 类名 * const 指针变量名; 可以在定义指针变量时对常指针进行初始化,如: Time*const ptr1=&t1; //指定ptr1指向t1

注意: 指向对象的常指针变量的值不能改变,即始终指向同一个对象,但可以改变其所指向对象的值。 如果想将一个指针变量固定地与一个对象相联系(即该指针变量始终指向一个对象),可以将它指定为const型指针变量。 往往用常指针作为函数的形参,目的是不允许在函数执行过程中改变指针变量的值,使其始终指向原来的对象。

指向常对象的指针变量 指向常变量的指针变量 const char *ptr; 注意const的位置在最左侧,与类型名char紧连,表示指针变量ptr指向的char变量是常变量,不能通过ptr来改变其值的。 说明: (1)如果一个变量已被声明为常变量,只能用指向常变量的指针变量指向它,而不能用一般的(指向非const型变量的)指针变量去指向它。 (2)指向常变量的指针变量可以指向常变量,也可以指向未被声明为const的变量,此时不能通过此指针变量改变该变量的值。

(3)如果函数的形参是指向非const型变量的指针,实参只能用指向非const变量的指针,而不能用指向const变量的指针。这样,在执行函数的过程中可以改变形参指针变量所指向的变量(也就是实参指针所指向的变量)的值。 (4) 如果函数的形参是指向const变量的指针,在执行函数过程中显然不能改变指针变量所指向的变量的值,因此允许实参是指向const变量的指针,或指向非const变量的指针。 指向常对象的指针变量的概念和使用是与此类似的,将“变量”换成“对象”即可。

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

对象的常引用 用对象的引用作为参数,如: class Sample { … }; void PrintfObj(Sample & o) //对象引用作为函数的参数有一定风险性,若函数中不小心修改了形参o,则实参也跟着变,这可能不是我们想要的。如何避免? { …… } 可以用对象的常引用作为参数,如: class Sample { … }; void PrintfObj( const Sample & o) //这样函数中就能确保不会出现无意中更改o值的语句了。 { …… } 常引用:被引用的对象不能被更新。

const数据的小结 在C++面向对象程序设计中,经常用常指针和常引用作函数参数。这样既能保证数据安全,使数据不能被随意修改,在调用函数时又不必建立实参的拷贝。用常指针和常引用作函数参数,可以提高程序运行效率。 如果希望在调用函数时对象的值不被修改,应当把形参定义为指向常对象的指针变量,用对象的地址作实参(对象可以是const或非const型)。 如果要求该对象不仅在调用函数过程中不被改变,而且要求它在程序执行过程中都不改变,则应把它定义为const型。