1、1,高级语言程序设计-2,谭火彬,C+,2,第三章 类:深入剖析(II),北京航空航天大学软件学院,thbin,-3-,内容安排,常量 组成:对象作为类的成员 友元函数和友元类 this指针和动态内存分配 静态类成员,北京航空航天大学软件学院,thbin,-4-,内容安排,常量 组成:对象作为类的成员 友元函数和友元类 this指针和动态内存分配 静态类成员,北京航空航天大学软件学院,thbin,-5-,常量,const类型修饰符:常量说明符 是constant的缩写, “恒定不变”的意思,被其修饰的东西都受到强制保护,可以预防意外的变动,提高程序健壮性 在变量说明语句中,const 用于冻结
2、一个变量,使其在程序中不能被修改 在用const 声明变量时, 必须对该变量进行初始化 很多C+程序设计书籍建议:“Use const whenever you need”,北京航空航天大学软件学院,thbin,-6-,const关键字主要使用场合,1. 修饰一个简单的变量 2. 修饰函数的输入参数 3. 修饰函数的返回值 4. 修饰类的成员函数,北京航空航天大学软件学院,thbin,-7-,1. 修饰一个简单的变量,const int a = 1;,int const a = 1;,const 可以放在类型修饰符的 前面和后面,没有任何差别, 功能:定义一个常量a,其值为是1,const i
3、nt* p1 = ,int const* p2 = ,int *const p3 = ,功能:指向int常量的指针,功能:指向int常量的指针,功能:指向int变量的常量指针,在常量指针(const pointer)中,“const”永远出现在“*”之后,北京航空航天大学软件学院,thbin,-8-,const_cast转换常量性,#include using std:cout; using std:endl; int main() int* p1;const int abc=10;p1= ,北京航空航天大学软件学院,thbin,-9-,2. 修饰函数的输入参数,const修饰输入参数,不能修
4、饰输出参数: 输入参数采用“指针传递”,那么加const修饰可以防止意外地改动该指针,起到保护作用 输入参数采用“值传递”,由于函数将自动产生临时变量用于复制该参数,该输入参数本来就无需保护,所以不要加const修饰 对于非内部数据类型的输入参数,应该将“值传递”的方式改为“const 引用传递”,目的是提高效率。例如将void Func(T a) 改为void Func(const T &a) ,如类的拷贝构造函数,void strcpy(char *strDestination, const char *strSource); 其中strSource是输入参数,strDestination
5、是输出参数。给strSource加上const修饰后,如果函数体内的语句试图改动strSource的内容,编译器将指出错误,北京航空航天大学软件学院,thbin,-10-,3. 修饰函数的返回值,如果给以“指针传递”方式的函数返回值加const修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const修饰的同类型指针 如果函数返回值采用“值传递方式”,由于函数会把返回值复制到外部临时的存储单元中,加const修饰没有任何价值 函数返回值采用“引用传递”的场合并不多,这种方式一般只出现在类的赋值函数中,目的是为了实现链式表达 ;如果将赋值函数的返回值加const修饰,那么该返
6、回值的内容不允许被改动,函数:const char * GetString(void); 如下语句将出现编译错误:char *str = GetString(); 正确的用法是:const char *str = GetString();,北京航空航天大学软件学院,thbin,-11-,4. 修饰类的成员函数,任何不会修改数据成员的成员函数都应该声明为const类型 const关键字放在成员函数定义的最后 如果在编写const成员函数时,不慎修改了数据成员,或者调用了其它非const成员函数,编译器将指出错误,北京航空航天大学软件学院,thbin,-12-,为时间类添加const约束,clas
7、s Time public:Time(const Time,void Time:printUniversal() const cout setfill(0)setw(2)hour “:“setw(2)minute“:“setw(2)second; ,北京航空航天大学软件学院,thbin,-13-,使用const对象,/Fig. 10.3 int main()Time wakeUp( 6, 45, 0 ); const Time noon( 12, 0, 0 );wakeUp.setHour( 7 ); noon.setHour( 12 ); /?wakeUp.getHour(); noon.g
8、etMinute();noon.printUniversal(); noon.printStandard(); /?return 0; ,北京航空航天大学软件学院,thbin,-14-,常量数据的初始化,常量在程序运行期间不允许修改,在何时为其指定初值? 只能在定义时初始化 const int i=10; const Time noon(12, 0, 0); 如果类中有常量数据成员,如何初始化? class Increment private: const int increment=0; /? ; 类的常量数据成员,只能在构造函数中初始化,但又不能在构造函数体内初始化,需采用成员初始化器的方
9、式进行初始化,北京航空航天大学软件学院,thbin,-15-,const变量初始化问题,1. 普通变量:在声明时初始化,const int a=10;,2. 类的数据成员:利用成员初始化器进行初始化(fig 10.5),class Increment public:Increment(int c, int i); private: int count; const int increment; ;,Increment:Increment(int c, int i) : increment(i) count=c; ,Increment:Increment(int c, int i) : incr
10、ement(i), count(c) ,北京航空航天大学软件学院,thbin,-16-,const成员函数常见错误总结,常见错误 定义修改对象数据成员的const成员函数是个语法错误 定义调用同一类实例的非const成员函数的const成员函数是个语法错误 const成员函数调用非const成员函数是个语法错误 将构造函数和析构函数声明为const是语法错误 不为const数据成员提供成员初始化器是一个语法错误,北京航空航天大学软件学院,thbin,-17-,mutable类成员,标准C+提供了一个存储类说明符mutable代替const_cast,即使在const对象和const成员函数中,
11、mutable数据成员也是可以修改的,class TestMutable public:TestMutable(int v=0):value(v) void modifyValue() const value+; int getValue() const return value; private: mutable int value; ;,北京航空航天大学软件学院,thbin,-18-,内容安排,常量 组成:对象作为类的成员 友元函数和友元类 this指针和动态内存分配 静态类成员,北京航空航天大学软件学院,thbin,-19-,组成:对象作为类的成员,组成(Composition) 类的数
12、据成员可以是简单数据类型,也可以是自定义的类类型 把其它类的对象作为类的数据成员,是一种重要的软件复用技术,class Employee private:char name25;const Date birthDate; ,北京航空航天大学软件学院,thbin,-20-,类成员的初始化,组成的类成员需要初始化 在构造函数中初始化,但是却不能在函数体中初始化 作为类成员的对象也必须通过它的构造函数初始化 构造函数不同于普通的函数,不能被直接调用,只能在声明类的对象时自动调用 只能在构造函数的成员初始化器中初始化(和const数据成员一样) 没有在成员初始化器进行初始化的数据成员将调用缺省构造函数
13、进行初始化,北京航空航天大学软件学院,thbin,-21-,为Employee的Date初始化,class Employeepublic: Employee(char *n; int y1; int m1; int d1; int y2; int m2; int d2):birthDate(y1,m1,d1), hireDate(y2, m2, d2) Employee(char *n; const Date ,class Date public:Date(int y=0; int m=0; int d=0);Date(const Date,北京航空航天大学软件学院,thbin,-22-,使用
14、Employee对象,int main()Employee employee(“Tom“, 1976, 7, 7, 2006, 1, 1);Date birth( 1949, 7, 24 );Date hire( 1998, 3, 12 );Employee manager( “Bob“, birth, hire );.return 0; ,北京航空航天大学软件学院,thbin,-23-,组成的基本实现原理,如右图的类定义,/P402. Fig. 10.12 class Employeepublic: private:char firstName25; char lastName25;cons
15、t Date birthDate; const Date hireDate; ;,firstName25,lastName25,year,month,day,birthDate,year,month,day,hireDate,北京航空航天大学软件学院,thbin,-24-,组成中构造和析构的过程,类对象和数据成员对象的构造顺序 基本原则:先别人,后自己 先构造数据成员,再构造自身 类对象和数据成员对象的析构顺序 基本原则:同一作用域内先构造的后析构,后构造的先析构 仔细阅读p404页fig10.14的程序 考虑类的构造和析构顺序? 为什么Date Object析构两遍?,北京航空航天大学软件学
16、院,thbin,-25-,内容安排,常量 组成:对象作为类的成员 友元函数和友元类 this指针和动态内存分配 静态类成员,北京航空航天大学软件学院,thbin,-26-,友元函数与友元类,对象的私有成员:只允许本类的成员函数访问,希望:本类以外的对象或函数能够访问类中的私有成员,友元:提供了本类外的对象或函数访问私有成员的途径,友元类,友元函数 友元成员,北京航空航天大学软件学院,thbin,-27-,友元,友元(friend):一个类的友元可以访问这个类的私有成员(公有成员当然能够访问) 某个类的友元:肯定不属于这个类的成员 友元可以是下列之一 友元函数不属于任何类的一般函数 友元成员另一
17、个类的某个成员函数 友元类另一个类(整个类作友元) 注意:友元使得数据的封装性受到影响,程序的可维护性变差,应慎重使用,北京航空航天大学软件学院,thbin,-28-,友元函数,友元函数(p406, fig 10.15) 不属于任何类 是某个类的友元的一般函数,class A int x; public: int z; A(int xx=0,int zz=0) :x(xx),z(zz)friend void in(A,void in(A ,北京航空航天大学软件学院,thbin,-29-,友元成员,友元成员 是其他某个类的成员函数,是当前类的友元 friend 返回类型 类名:函数名(参数列表)
18、; 说明: 1.友元成员的声明,除了前面冠以“friend”外,还要注明所属类的类名 2.友元成员应该在自己所属类的定义体中进行成员函数声明(或定义),北京航空航天大学软件学院,thbin,-30-,友元类,友元类 是一个类,而且是另一个类的友元 friend class 类名; 说明: 类A和B,B被声明为A的友元类,则B类的所有成员函数都可以访问A类的私有成员,北京航空航天大学软件学院,thbin,-31-,友元类范例,class A int x,y; public: A(int xx=0,int yy=0):x(xx),y(yy)friend class B; ;,class B cha
19、r n20; public: B(char nn ) strcpy(n,nn); void sh(const A,北京航空航天大学软件学院,thbin,-32-,友元关系一些注意问题,友元关系是“给予”,而不是“索取”的 友元关系既不对称也不能传递 尽管类定义中有友元函数的原型,但友元函数然仍不是成员函数 private、protected和public的成员访问符号与友元关系的声明无关 因此友元关系声明可以放在类定义中任何地方 习惯:将类中所有友元关系的声明放在类定义最前面,并不加任何成员访问说明符,北京航空航天大学软件学院,thbin,-33-,内容安排,常量 组成:对象作为类的成员 友元
20、函数和友元类 this指针和动态内存分配 静态类成员,北京航空航天大学软件学院,thbin,-34-,使用this指针,C+为每个类提供一个this指针 指向“当前”对象 通过this指针可以访问当前对象的成员 说明 在非静态成员函数中使用this指针 友元函数不是成员函数,没有this指针 注意友元成员函数中的this指针所指向的对象,北京航空航天大学软件学院,thbin,-35-,this指针的用法,/p408, Fig 10.17 class Test public:Test( int = 0 );void print() const; private:int x; ;,Test:Tes
21、t( int value ) : x(value) void Test:print() const cout x = “ x;cout “n(*this).x = “ (*this).x endl; ,int main()Test testObject( 12 ); testObject.print();return 0; ,北京航空航天大学软件学院,thbin,-36-,串联的函数调用,Time提供了set函数修改时、分、秒信息,这些函数原型为 void setTime(int, int, int); void setHour(int);、void setMinute(int);、void
22、setSecond(int); 按照这种定义,如果需要同时修改时、秒的信息,则必须调用两遍不同的函数 Time t(14, 13, 12); t.setHour(15); t.setSecond(10); 能否实现函数的串联调用?(只写一行代码实现同时调用两个函数) t.setHour(15).setSecond(10);,北京航空航天大学软件学院,thbin,-37-,串联的函数调用,分析函数调用的过程 t.setHour(15).setSecond(10); t.setHour(15)应返回t对象本身的地址 函数原型应该为 Time 思考 为什么是引用、而不是值返回? 如何返回当前对象的引
23、用?,北京航空航天大学软件学院,thbin,-38-,使用this指针实现串联函数调用,Time ,北京航空航天大学软件学院,thbin,-39-,动态内存分配,new语法格式: 格式1:指针标识符 = new 类型标识符; 格式2:指针标识符 = new 类型标识符(初始化值); 格式3:指针标识符 = new 类型标识符数组维数;,delete语法格式: 格式1:delete 指针标识符; 格式2:delete指针标识符;,北京航空航天大学软件学院,thbin,-40-,对象的动态分配,利用new和delete可以为指针对象动态分配和释放空间 作用域 文件 动态分配的对象 在创建时(new
24、)自动调用构造函数 释放时(delete)自动调用析构函数 Time *pt; pt=new Time(15, 30, 21);/调用构造函数 delete pt; /调用析构函数,北京航空航天大学软件学院,thbin,-41-,组合指针成员:编写字符串类,字符串是一种常用的数据类型,但是C+语言并没有直接提供字符串这种数据类型 可是使用类的概念自己编写字符串处理类MyString 该类的数据成员 char *str; /存储所需的字符串 int length; /存储当前字符串的长度 该类的成员函数 构造函数、拷贝构造函数、析构函数 get、set操作 ,北京航空航天大学软件学院,thbin
25、,-42-,MyString的定义,思考 为什么有的成员没有get/set函数? 为什么有两个数据成员,构造函数却只有一个参数?,class MyStringpublic:MyString(const char *s=“);MyString(const MyString,北京航空航天大学软件学院,thbin,-43-,构造和析构函数中动态内存分配,MyString:MyString(const char *s) setString(s); MyString:MyString(const MyString ,北京航空航天大学软件学院,thbin,-44-,内容安排,常量 组成:对象作为类的成员
26、友元函数和友元类 this指针和动态内存分配 静态类成员,北京航空航天大学软件学院,thbin,-45-,static类成员,普通的类成员(数据成员和成员函数) 必须通过类的对象去访问 在实际使用过程中是属于当前对象的 static类成员 类的所有对象共享,是属于类的,而不是某个对象的 在类成员的定义中,前面加上“static”关键字 两类静态类成员 静态数据成员 静态成员函数,北京航空航天大学软件学院,thbin,-46-,静态数据成员,静态数据成员 是该类所有对象所共有的一个数据成员,其值可以被任何一个对象所改变 必须在使用前进行初始化 int或者枚举类型的const static数据成员
27、可以在类定义中其声明处初始化 其它static数据成员必须在类的定义体外初始化(一般写在类的实现文件中):数据类型 类名:静态数据成员名 = 初值;数据类型 类名:静态数据成员名(初值); 思考 为什么不在构造函数中初始化?,北京航空航天大学软件学院,thbin,-47-,静态成员函数,静态成员函数 不与任何对象联系,不存在this指针 不能声明为const成员函数 在静态成员函数中,只能访问静态数据成员或全局变量,不能对类的非静态成员进行默认访问 如果要访问非静态数据成员,利用参数传递进行(以限定哪个对象的数据),北京航空航天大学软件学院,thbin,-48-,静态类成员的访问,静态类成员的
28、访问规则 访问权限与普通成员相同 与普通成员一样,可以通过对象名(引用、指针)访问(.、-、.*、-*) 可直接通过类名访问(:)类名:静态成员 可以没有声明类的任何对象时就使用静态类成员,北京航空航天大学软件学院,thbin,-49-,使用静态数据成员,class Aint a; static int x; public: A(int aa=0):a(aa) x+; cout“a.x=“xendl; static void view() cout“A:x=“xendl; ;,/初始化静态数据成员 int A:x(900); /或者:int A:x=900;,void main() A:vie
29、w();A a1,a2; A:view(); a2.view(); ,/执行结果:A:x=900a.x=901a.x=902A:x=902A:x=902,北京航空航天大学软件学院,thbin,-50-,使用静态类成员,P415, Fig 10.21Fig 10.23 利用静态类成员统计类对象的个数(统计员工的个数),class Employee public: Employee(const char* const, const char* const); Employee(); static int getCount(); private:static int count; / 统计类对象的数
30、量 ;,北京航空航天大学软件学院,thbin,-51-,使用静态类成员,int Employee:count = 0; /初始个数为0 Employee:Employee()firstName = new char strlen( first ) + 1 ;strcpy( firstName, first );lastName = new char strlen( last ) + 1 ;strcpy( lastName, last );count+; / 对象个数+1 Employee:Employee()delete firstName;delete lastName;count-; /对
31、象个数-1 ,北京航空航天大学软件学院,thbin,-52-,上机实验2实验内容,参考Fig10.1210.13、Fig10.2110.22,实现学生类CStudent;具体要求如下: 数据成员:姓名name,必须使用char*类型(不能使用char,或string类型),在构造和析构函数中实现动态内存分配 组成数据成员:出生日期birthDate,使用实验1所完成的CDate类型 静态数据成员:学生数量count,用于统计在程序中产生学生对象的个数 提供构造函数、拷贝构造函数和析构函数 提供必要的get和set函数,存取私有数据成员 编写main函数,测试所要求的功能 按标准C+语法编写程序
32、,北京航空航天大学软件学院,thbin,-53-,作业提交方式,1. 准备 本机新建一目录,以自己的学号、命名、作业次数,如:“10211001张三2” 将本次作业所完成的5个源代码文件放在该目录下(日期类和学生类的2个.h和3个cpp),打包压缩为一个文件“10211001张三2.zip”后提交 2. 提交 登录http:/ 选择菜单“协同学习/教学辅助”,再选择“高级语言程序设计2”课程,单击“课程作业”菜单,在“作业2-学生类”下面选择选择“提交作业”链接,提交压缩好的作业即可 注意事项:必须用IE提交,IE6可以直接提交,IE7及以上版本需要修改设置,将“将文件上载到服务器时包含本地目录路径”启用,北京航空航天大学软件学院,thbin,-54-,请预习,教材第11章:运算符重载 11.111.7、11.911.14 主要内容 运算符重载的基本原理和运算符函数 流运算符的重载 一元、二元运算符的重载,注意返回类型的区别 特殊的运算符:=、()、类型转换函数等,