• 1. C++面向对象程序设计 第三章 再论类和对象
  • 2. 第一章 C++的初步知识 第二章 类和对象 第三章 再论类和对象 第四章 运算符重载 第五章 继承与派生 第六章 多态性与虚函数 第七章 输入输出流 第八章 C++工具
  • 3. 3.1 构造函数 3.2 析构函数 3.3 调用构造函数和析构函数的顺序 3.4 对象数组 3.5 对象指针 3.6 共用数据的保护 3.7 对象的动态建立和释放 3.8 对象的赋值和复制 3.9 静态成员 3.10 友元 3.11 类模板
  • 4. 3.1 构造函数对象的初始化 和普通变量一样,我们定义一个变量,往往同时进行初始化: int a = 3; 而声明类时,数据成员不能进行初始化: class time { int hour = 0; int minute = 0; int second = 0; } 因为类不是实体,不占分配存储空间,显然无法容纳数据。
  • 5. 3.1 构造函数 如果一个类的所有数据成员都是公用的,我们可以象结构体变量那样,在定义对象时(而不是声明类时)进行初始化: class time { public: int hour ; int minute; int second; }; time t1 = {13,30,20}; 但在类的声明中,数据成员往往都是私有的,不能这样初始化。就需要一个公有成员函数来完成,而且应该是自动调用地完成。这就是构造函数。
  • 6. 3.1 构造函数构造函数的作用 C++提供了构造函数( constructor )来处理对象的初始化。构造函数是一个由用户定义的特殊的成员函数。与其他成员函数不同之处在于: 用户不能调用它,而是在定义对象时,有系统自动调用构造函数。 构造函数的名字必须与类名一致,不能是其他名字。 构造函数不能有任何返回类型。 用户如果没有定义构造函数,系统会自动生成一个构造函数,只不过函数体中没有任何语句。
  • 7. 3.1 构造函数例3.1 将前例的时间类定义构造函数。在构造函数中加入输出语句,看看运行效果。class time { private: int hour, minute, second; public: time ( ) //time 类的构造函数 { hour = 0; minute = 0; second = 0; } void setTime ( ); void showTime ( ) { cout << hour <<":" <8. 3.1 构造函数带参数的构造函数 前一个示例中,每产生一个time了的对象,其初值都初始化为0。如果用户初始化时,不想将对象初值置为0而是其它值,就需要用带参数的构造函数来实现。 声明一个构造函数的一般格式为: 构造函数名 ( 类型1 形参1,类型2 形参2,…); 定义一个对象的一般格式为: 类名 对象名 ( 实参1,实参2,… ); 下面举例说明用法。
  • 9. 3.1 构造函数例3.2 :有两个长方体,长宽高分别为(1,2,3)和(4,5,6)。试编写一基于对象的程序,分别求他们的体积,并且要求用带参数的构造函数初始化他们。#include class box { private: int height, width, length; public: box ( int h, int w, int len ) { height = h; width = w; length = len; } int volume ( ) { return height * width * length; } };void main ( ) { box box1 (1,2,3); cout <<"box1的体积为" <
  • 10. 3.1 构造函数用参数初始化表对数据成员初始化 C++提供另一种初始化数据成员的方法:参数初始化表来实现对数据成员的初始化。这种方法不在函数体内初始化数据成员,而是在函数首部实现。例3.2 我们改写成如下形式:#include class box { private: int height, width, length; public: box(int h,int w,int len):height(h),width(w),length(len) { } int volume ( ) { return height * width * length; } }; void main ( ) { box box1 (1,2,3); cout <<"box1的体积为" <
  • 11. 3.1 构造函数 示例中的初始化表表示,用形参h的值初始化数据成员height,用w值初始化width,用len值初始化 length。这种初始化方法比较简练,可以直接在类体中定义构造函数。
  • 12. 3.1 构造函数构造函数的重载 一个类中,可以有多个构造函数,只要他们的参数表不同。以方便同类对象不同初始化的需要。见下例3.3 :#include class circle { private: float radius; public: circle( ); // 声明一个无参数构造函数 circle( float r ): radius (r) { } // 声明一个带参数构造函数 float area ( ) { return radius * radius * 3.14159; } }; circle::circle ( ) { radius=10.0; } void main ( ) { circle c1 ( 1.0 ), c2; cout <<"c1的面积为" <
  • 13. 3.1 构造函数说明 参数表为空的构造函数叫默认构造函数,一个类中只能有一个默认函数。 定义对象时,如果想用默认构造函数初始化它,正确的定义形式为: circle c2; 而不是: circle c2 ( ); 一个类尽管定义了多个构造函数,一个对象只能用其中一个来初始化。
  • 14. 3.1 构造函数使用默认参数的构造函数 构造函数的参数既可以通过实参传送,也可以指定为某些默认值。当用户不指定实参值时,编译系统便将默认值为形参值。 在实际生活中,常有一些这样的默认值。如计数器的默认值为0;战士的性别默认值为"男";男性职工退休年龄默认值为60岁等,如果实际情况不是默认值,则由用户另外指定。采用默认值,可以减少用户的输入量。 下面举例说明。
  • 15. 3.1 构造函数例3.4 试将例3.3 的构造函数改用默认值的参数,半径值默认为1.0。#include class circle { private: float radius; public: circle( float r = 1.0 ); // 声明构造函数是指定默认参数 float area ( ) { return radius * radius * 3.14159; } }; circle::circle ( float r ) //定义函数时,可不再指定默认参数 { radius = r; } void main ( ) { circle c1 (10.0), c2; cout <<"c1的面积为" <
  • 16. 3.1 构造函数构造函数中使用默认参数的好处 提供建立对象时的多种选择,相当于好几个重载的构造函数。 即使在调用构造时不提供参数也不会出错,因为有默认参数值参与对象初始化。 当每一个对象都是相同的初始值时,非常方便,用户不需要输入数据。
  • 17. 3.1 构造函数默认参数值的构造函数使用注意 何处指定默认参数值?构造函数的声明处还是定义处?应该在构造函数的声明处指定默认参数值。因为类的声明在头文件中,用户是看得见的,而防在函数的定义处,用户不一定看得见。 一个类定义了全部是默认参数的构造函数后,不能再定义重载的构造函数。否则会产生多义性,系统不知道调用哪一个。例如一个类有右边形式 的三个重载构造函数,若定义了如 下对象: circle circle1; 它调用哪一个构造函数呢?系统不 能确定,从而引起混乱。circle ( float r=2.3 ); circle ( ); circle ( float );
  • 18. 3.2 析构函数什么是析构函数? 析构函数( destructor ) 也是一个特殊函数,它的作用与构造函数相反,是在撤消对象占用的内存前进行一些清理工作。它还可以被用来执行"用户希望在最后依次使用对象之后所执行的任何操作"。 析构函数的名称是类名的前面加一个取反符号"~"。我们在类的声明中定义析构函数。如果用户不定义析构函数,系统便自动生成一个空的析构函数。 析构函数特点: 没有返回类型; 没有函数参数; 不能被重载。
  • 19. 3.2 析构函数什么时候运行析构函数? 当对象的生命结束时,会自动执行它的析构函数。具体而言,当出现以下几种情况,析构函数就会被执行。 如果在函数中定义了一个对象,当函数调用结束时,释放对象,释放对象前自动执行析构函数。 static 局部对象在函数调用结束时,包含的对象不会被释放,只在main函数结束或调用exit函数时,才调用static局部对象的析构函数。 如果定义了一个全局对象,,则在程序的流程离开其作用域时(如main函数结束,或exit语句),调用该全局对象的析构函数。 如果用new运算符动态地建立了一个对象,当用delete 运算符释放对象时,先调用该全局对象的析构函数。
  • 20. 3.2 析构函数#include class box { private: int height, width, length; public: box ( int h, int w, int len ) { height = h; width = w; length = len; } ~box ( ) // 析构函数 { cout <<"Destructoring a object" << endl; } int volume ( ) { return height * width * length; } };void main ( ) { box box1 (1,2,3); cout <<"box1的体积为" <
  • 21. 3.3 调用构造函数和析构函数的顺序调用构造函数和析构函数的顺序 先构造的后析构,后构造的先析构。 例: C3-5-1.CPP
  • 22. 3.3 调用构造函数和析构函数的顺序注意:先构造的后析构,后构造的先析构,并非绝对成立! 对象不同的作用域,不同的存储类别,那么调用构造函数和析构函数的时候也有所不同. 例如: 一个程序中有多个文件,在多个文件中定义了全局对象,那么这些对象的执行顺序是不确定的 在函数中定义局部自动对象,如果函数被多次调用 如果函数中定义静态局部对象,那么函数调用结束时对象并不释放,只有main结束或调用exit中才调用析构函数. Void fn() { Student s1; static Student s2; … } 例: C3-5-2.CPP
  • 23. 3.4 对象数组 数组不仅可以由简单变量组成,也可以由对象组成,即每一个数组元素都是同类的对象。 例如,一个班有30人,每个学生的属性包括学号、姓名、性别。我们建立起学生类后,为每个学生建立一个对象,需要分别取30个对象名,很不方便。较好的做法是,定义一个"学生类",的对象数组,每一个数组元素是一个"学生类"的对象。 例如: Student stud[30];
  • 24. 3.4 对象数组在建立对象数组时,同样调用构造函数。 如果构造函数只有一个参数,在定义数组时可以在等号后面花括号内提供实参。 Student stud[3]={60,70,80}; 实参个数不能超过数组元素个数,因为编译系统.只为每个对象元素的构造函数传递一个实参.   Student stud[3]={60,70,80,90};//不合法 如果构造函数有多个参数,则不能用在定义数组时直接提供所有实参的方法。 例如: Student::Student(int=1001,int=18,int=60)//构造函数 Student stud[3]={111,34,31}; 这种使用方式关系不清晰,出现歧义性.
  • 25. 3.4 对象数组构造函数有多个参数时,如下实现对数组对象的初始化. Student Stud[3]={Student(11,32,74), Student(12,34,27), Student(14,45,63) }; 如下面的例子.
  • 26. 3.4 对象数组例:3.6-1.cpp#include #include using namespace std; class student {private: int num; string name; char sex; public: student( int n, string nam, char s ) {num = n; name=nam; sex = s; cout <<"Constructor called." <
  • 27. 3.4 对象数组例:C3-6.CPP#include using namespace std; class Box {public: Box(int h=10,int w=12,int len=15):height(h),width(w),length(len) { } int volume(); private: int height; int width; int length; };int Box::volume() {return(height*width*length); } int main() { Box a[3]={ Box(10,12,15), Box(15,18,20), Box(16,20,26) }; cout<<"volume of a[0] is "<
  • 28. 3.5 对象指针指向对象的指针 创建一个类的对象时,系统会为每一个对象分配一定的存储空间,以存放成员。对象空间的起始地址就是对象的指针。可以定义一个指针,用来存放对象的指针. 格式: 类名 *对象指针名;
  • 29. 3.5 对象指针指向对象的指针 例:c3-point.cpp 访问成员的方法:( *pt ).hour, (*pt).put ( ) 或者:pt->hour, pt->put( ) class Time {public: int hour, minute, sec; void put ( ) { hour = 12; minute = 0; sec = 0; } };void main ( ) {Time *pt, t1; pt = &t1; p1.put ( ); cout <hour<<":"<minute <<":"<
  • 30. 3.5 对象指针指向对象数据成员的指针 定义一个指向对象数据成员的指针变量的方法和前面介绍的定义指向普通变量的指针变量方法相同。 定义格式:数据类型名 * 指针变量名; 例如: int *pl; // 定义指向整形数据的指针变量 pl=&t1.hour; // 将t1的数据成员hour地址赋给指针pl,使其指向t1.hour cout << *pl <
  • 31. 3.5 对象指针指向对象成员函数的指针 定义指向对象成员函数的指针变量的方法和定义指向普通函数的指针变量的方法有所不同。 指向普通函数的指针变量的定义方法: 返回数据类型 (*指针变量名) (参数表列); 例:void (*p) ( ); // p是指向void 类型函数的指针变量 p = fun; // 将fun函数的入口地址赋给指针变量p (*p)( ); // 调用fun函数 而定义一个指向对象成员函数的指针变量则不能这样: p = t1.put; // 出现编译错误,不知道t1.put所属的类
  • 32. 3.5 对象指针定义指向公用成员函数的指针变量方法: 返回数据类型 ( 类名:: *指针变量名) (参数表列); 例:void (Time::*p2)( ); // Time::*p2的括号必须加上 令指针变量指向一个公用成员函数的方法: 指针变量名 = & 类名::成员函数名; 例:p2 = & Time::put;
  • 33. 3.5 对象指针#include using namespace std; class Time {public: int hour, minute, sec; Time ( int h, int m, int s ) { hour = h; minute = m; sec = s; } void get_Time( ) { cout << hour<<":" << minute<<":" << sec<get_Time( ); //调用p2所指对象的成员函数 void (Time::*p3)( ); // 定义指向Time类公用成员函数的指针变量p3 p3 = &Time::get_Time; p53页 // 使p3指向Time类公用成员函数get_Time( ) (t1.*p3) ( ); //调用p3所指的成员函数t1.get_Time( ) return 0; }
  • 34. 3.5 对象指针this 指针 通过第二章的学习我们知道,多个同类对象在内存中是共享成员函数的,以节省内存空间。那么,不同对象的成员函数引用数据成员时,怎么找到自己的数据成员呢? C++在每一个成员函数中都包含一个特殊的指针,这个指针的名字是固定的,成为"this"。它是指向本对象的指针,它的值是当前被调用的成员函数所在对象的起始地址。 例如,当a的成员函数调用数据成员a.volume时,编译系统就把对象a的起始地址赋给 this 指针,于是在成员函数引用数据成员时,就按照 this 的指向找到对象a 的数据成员。
  • 35. 3.5 对象指针 比如下列涉及数据成员的运算的返回语句: return length*width*height ; 实际上C++处理为: return ( this ->length)*(this->width)*(this->height); 也可以写成如下形式: return ((*this).length*(*this).width*(*this).height); 但不能写成: return ((*this.length) *(*this.width)*(*this.height)); //错误 因为"."操作符的优先级高于指针运算符"*",(*this.length)就相当于*(this.length),而 this.length 是不合法的,编译出错(应该是this->length)。
  • 36. 3.6 共用数据的保护 C++采用了不少的数据保护措施。最常用的,是将数据成员设置成私有数据( private ),以增加数据的安全性和私密性。 但是,有时候要求数据在能共享的前提下不能被篡改,我们就需要借助其他手段了。 什么手段呢?可以采用const,即把要保护的数据定义为常量。
  • 37. 3.6 共用数据的保护常对象 在定义对象时指定对象为常对象。常对象中的数据成员为常变量,并且必须要有初值: Time const t1(12,34,56); 这样,在所有的场合中,对象t1 的所有数据成员都被保护了,不能被修改。因此,凡是希望数据成员不能被改变的对象,可以声明为常对象。其声明格式为: 类名 const  对象名(参数表列); 或者: const 类名 对象名(参数表列); 两者等价。
  • 38. 3.6 共用数据的保护 如果一个对象被定义成常对象,那么不能调用该对象的非const 型成员函数,当然,系统隐含调用的构造函数和析构函数除外。比如: Time const t1( 12,34,56); // 定义t1为常对象 t1.get_Time( ); // 错误,get_Tiem( )不是const 型,不能调用 为什么会这样?因为成员函数有可能修改数据成员,而成员函数的定义可能和成员函数的声明不在同一文件,系统没法检测。所以,系统只能统一拦截,不让用户调用常对象的成员函数,除非该成员函数被声明成 const 类型。
  • 39. 3.6 共用数据的保护 怎样才能引用常对象的成员函数呢?很简单,只需要把该成员函数的声明为 const 类型,即常成员函数就行了。方法为: 函数返回类型 函数名(参数表列) const; 比如:void get_Time( ) const; // 将函数声明成const 类型 常成员函数可以访问常对象中的数据成员,但仍然不准修改它们。 有时候编程要求必须修改某个常对象中的数据成员,如某个计数器count ,ANSI C++对此做了特殊的处理,将该数据成员 声明为 mutable,如: mutable int count; 这样,常对象的数据成员count,就可以用常成员函数来访问和修改了。
  • 40. 3.6 共用数据的保护常对象成员 常数据成员:其作用和用法与一般常变量相似,在类的声明中,用关键词 const 来声明常数据成员,例如: const int hour; 注意:常数据成员的初始化,不能采用在构造函数中对常数据成员赋予初值的方法,只能通过构造函数的参数初始化表对常数据成员进行初始化。 在类外定义构造函数,初始化形式为: Time::Time ( int h ): hour ( h ) { } 在类中声明了某一个数据成员为常数据成员后,该类的所有对象中的该数据成员的值是不可改变的,但可以是不同的(由每个对象的参数初始化表决定)。
  • 41. 3.6 共用数据的保护 常成员函数 一般的成员函数可以引用本对象中的非const 数据成员,也可以修改它们。但如果成员函数声明为常成员函数,则只能引用本对象中的数据成员,而不能修改它们。如 void get_Time( ) const; // const位置在最后 const 是函数类型的一部分,在声明函数和定义函数时都要用到 const 关键字。 常成员函数可以引用常数据成员,也可以引用非const数据成员;而常数据成员可以被常成员函数引用,也可以被非 const 成员函数引用。见90页表总结。
  • 42. 3.6 共用数据的保护数据成员非const成员函数const 成员函数非const 数据成员可引用,可修改可引用,不可修改const 数据成员可引用,不可修改可引用,不可修改常对象的数据成员不可引用,不可修改可引用,不可修改
  • 43. 3.6 共用数据的保护怎样使用常成员函数呢? 类中如果一些数据成员需要保护,另一些数据成员不需保护,我们就将需要保护的数据成员声明为 const ,以保证其值不被改变。 类中如果所有数据成员都需保护,可以将所有数据成员都声明成 const ,本对象的任何成员函数,都只能引用,不能改变它们。或者将这些数据成员所属的对象声明成const,只让本对象的const 成员函数可引用,但不能改变。 如果一个对象被定义成了常对象,只能调用其中的const成员函数,不能调用非const成员函数。 常成员函数不能调用另一个非const成员函数
  • 44. 回忆:常对象,常数据成员,常成员函数class Time{  public:     Time(int=0,int=0,int=0);     void call();     int hour;    int min;    int sec; };     Time::Time(int h,int m,int s) { hour=h; min=m; sec=s;}   Time const t(2,3,4); //t为常对象
  • 45. 回忆:常对象,常数据成员,常成员函数class Time{  public:         …    void call() const; …};     void Time::call() const { cout<<"Call:"<
  • 46. 回忆:常对象,常数据成员,常成员函数class Time{  public:     …     void setHour(int) const; mutable int hour;//可以修改hour的值,通过setHour() int min; //不可以修改min的值,read-only int sec; //不可以修改sec的值,read-only … };   void Time::setHour(int h) const{  hour=h; } …  Time const t(2,3,4);  t.setHour(5);如果需要修改常对象中某个数据成员的值,则可将该数据成员声明为mutable。
  • 47. 回忆:常对象,常数据成员,常成员函数class Time{  public:    Time(int,int,int);     const int hour;//声明hour为常数据成员    int min;    int sec;};     Time::Time(int h,int m,int s):hour(h) {//通过参数初始化表hour(h)对常数据成员hour初始化  min=m;  sec=s; }   …  Time t(2,3,4);用关键字const来声明常数据成员。在类体中声明了某一个常数据成员后,该类所有对象中的该数据成员的值都是不能改变的,但不同对象中该数据成员的值是可以不同的(在定义对象时给定)。只能通过构造函数的参数初始化表对常数据成员进行初始化。
  • 48. 3.6 共用数据的保护指向对象的常指针 将指向对象的指针变量声明为const ,并将之初始化,可以使指针值恒定为初始值,即其指向始终不变。如 Time t1(10,12,14), t2; //定义对象 Time * const ptr = &t1; // 定义常指针,恒定指向t1对象 ptr = &t2; // 错误,常指针不能改变指向 定义一个指向对象的常指针的格式为: 类名 * const 指针变量名 = 对象地址; 指向对象的常指针,这个变量的值是一个对象的地址,他不允许被改变,但所指对象的内容可以改变。 什么时候需要常指针让其指向对象?如果想将一个指针变量与一个对象固定地联系在一起,以防止误操作;往往用常指针作为函数的形参,目的是不允许在函数执行过程中改变指针值,使其始终指向原来的对象。
  • 49. 3.6 共用数据的保护Time t(2,3,4); Time *const p; //error:uninitialized const 'p' p=&t;Time t(2,3,4); Time *const p=&t; Time tt(1,1,1); p=&tt;//error:assignment of read-only variable 'p'指向对象的常指针
  • 50. 3.6 共用数据的保护class Time{  public: …     void call();     int hour;  private:     int min; …   void Time::call() {  cout<<"Call:"<call();  p->hour=0;  p->min=0;  p->call(); …指向对象的常指针:找出下面的错误!error:`int Time::min' is private
  • 51. 3.6 共用数据的保护指向常对象的指针 定义指向常对象的指针格式: const 类名 * 指针名; 这和C语言中指向常变量的指针是一样的。只需将上述格式中的类名换成类型名就行了。我们不妨在一起作如下讨论: 如果一个变量被声明为常对象(/ 常变量),只能用指向常对象(/ 常变量)的指针指向它。如: const char c[ ] = "boy"; // 定义const型的常字符数组 const char * p1; // p1为指向常字符变量的指针 p1 = c; // 合法,p1指向常变量 char * p2 = c // 非法,p2不是指向常变量的指针 指向常变量的指针如果指向了非const对象,其指向的对象值不能用指针改变,可以用非指针方式改变。 Time t1(10,12,14); // 定义Time类对象t1,它不是常对象 const Time *p = &t1; // p是指向常对象t1 的指针 t1.hour = 18; // 合法,t1不是常量 (*p).hour = 18; // 非法,不能通过指针改变t1的值
  • 52. 3.6 共用数据的保护用指针变量作形参时,形参和实参的对应关系见下表形参实参是否合法指针所指变量值能否改变指向非const变量或 非const对象的指针非const变量的地址/ 非const对象的地址合法可以const变量的地址/ const对象的地址非法/指向常变量或 常对象的指针非const变量的地址/ 非const对象的地址合法不可以const变量的地址/ const对象的地址合法不可以
  • 53. 3.6 共用数据的保护例如: const char str[ ] = "boy"; // str是常数组名 void func(char * ptr); // 函数形参为指向非const变量的指针 func (str); // 非法,实参是常变量指针,形参是非const变量指针 出错原因: 形参是非指向非const型变量的指针,在函数中可以也可能改变指针所指变量值,而实参是指向常变量的指针,不能被改变,发生矛盾。
  • 54. 3.6 共用数据的保护指向常对象的指针最常用于函数的形参,目的是保护形参指针所指的对象,使它在函数执行过程中不被修改。如 void func( const Time * P) //形参是指向常对象的指针变量 { p->hour = 18; // 错误,指向的常对象被修改了 cout<hour<
  • 55. 3.6 共用数据的保护 有人可能会问,我不将func函数的形参定义为const型对象,只将 t1 定义为const 型对象,不是也能保护 t1 在func中不被篡改吗? 不行,会出现编译错误。因为指向非const 对象的指针不能指向const 对象;同理,指向非const 变量的指针不能指向const 变量。 不能通过指向常对象的指针改变所指对象的值,但指针变量本身的值可以改变。
  • 56. 3.6 共用数据的保护何时使用指向常对象的指针?何时使用常对象? 当希望在调用函数时对象的值不能被修改,就应该把形参定义为指向常对象的指针,同时用对象的地址做实参,而实参对象可以是const型,也可以是非 const型。 如果要求对象不仅在函数调用过程中不被修改,而且在整个程序运行时不被修改,就应该把该对象定义成常对象。
  • 57. 3.6 共用数据的保护对象的常引用 前面讲过,引用主要是用于函数调用,将改变后的变量值带回到被调用的函数外。 但如果不希望在函数中修改参数,可以把引用型形参定义成 const 型: 函数返回类型 函数名( const 形参类型 & 形参名); 则在函数中不能改变形参值,也就不能改变对应的实参值。 (例3-8) 什么时候使用常指针和常引用?使用常指针或常引用作为函数参数,既能保证数据安全,不被修改;调用函数时又能不必建立实参的拷贝,提高了程序运行效率,节省了内存空间。
  • 58. 3.6 共用数据的保护const 数据小结 形式含义Time const t1; const Time t1; t1是常对象,其值在任何情况下不能改变void Time::func( ) const func是Time类中的常成员函数,可以引用,但不能修改本类中的数据成员Time * const p; p是指向Time对象的常指针,p的值,即p的指向不能改变Const Time *p; p是指向Time类常对象的指针,其指向的对象的值不能通过指针来改变const Time &t1 = t; t1是Time类对象t的常引用,函数中不能改变实参值
  • 59. 3.7 对象的动态建立和释放问题的提出:前面我们所学创建对象的方法都是静态的,它们在程序 运行过程中所占用的内存空间不能被释放。比如,在一个函数中定义了一个对象,只有在函数结束时,该对象才能被释放。 有时候,人们希望对象的创建和释放是在程序运行时,由运行程序的人决定,比如,链表结构的建立和删除,这就需要动态建立和释放对象。 C++语言用new、delete 这两个运算符来实现。内存的分配和释放,也用来实现对象的建立与撤消。
  • 60. 3.7 对象的动态建立和释放动态建立对象的方法: 如果已经定义了一个 Box 类,可以用new 运算符动态地创建一个 Box 对象: new Box; 系统执行此语句时,首先开辟一段内存空间,并在此空间中存放一个 Box 类对象,同时运行该类的构造函数,以初始化该对象,然后返回一个指向该对象的指针值。 但此时用户还不能访问这个对象,因为它既没有对象名,用户也不知道它的地址。这种对象称为"无名对象"。我们应该这样做: Box *pt; // 定义一个指向Box类对象的指针pt pt = new Box; // 创建一个Box对象,将新对象的指针值赋给pt cout << pt->height <
  • 61. 3.7 对象的动态建立和释放 用new 动态创建的对象一般没有对象名,只能通过指针访问。 在执行new 运算时,如果内存不足,则创建失败。大多数C++编译系统都让 new 返回一个0指针,表示内存不足,操作失败。 动态撤消对象的方法:由new 创建的对象不再需要时,可以由delete运算符释放。上例pt的释放方法为: delete pt; 这样就撤消了pt指向的对象。 在执行 delete运算符时,在释放空间以前,系统自动调用析构函数,完成有关善后清理工作。
  • 62. 3.8 对象的赋值与复制对象的赋值 如果一个类定义了两个或多个对象,则这些同类对象之间可以互相赋值. 一般形式: 对象名1=对象名2 赋值过程实际上是将一个对象的成员值一一复制给另一个对象的对应成员完成的. 例3.9 注意: 对象的赋值只对其中的数据成员赋值,而不对成员函数赋值 类的数据成员中不能包括动态分配的数据,否则可能出现严重后果.
  • 63. 3.8 对象的赋值与复制对象的复制 问题的提出:有时需要多个完全相同的对象,用前面介绍的办法进行处理,我们得定义多个同类对象,然后用相同的数据去构造初始化它们,比较费时麻烦。可不可以用克隆的方法呢? 可以。C++可以根据一个已知的对象快速地复制出多个完全相同的对象。比如: Box box2(box1); 其作用就是对象的克隆,即用一个已知的对象 box1复制出一个完全相同的新对象 box2。 对象复制的格式: 类名 被克隆出来的新对象名(已有的对象名);
  • 64. 3.8 对象的赋值与复制 从上面的一般形式可以看出,复制对象是一种特殊的构造对象方法,其构造参数参数不是一般变量,而必须是一个对象!请看: // 复制构造函数 Box::Box ( const Box &b) { height = b.height; width = b.width; height = b.height; } 复制构造函数也是构造函数,它只有一个参数,这个参数是本类已有对象,而且采用常引用形式,使参数不能被改变。复制构造函数的作用是将实参对象的各个数据成员值一一赋予给新的对象中对应的数据成员。
  • 65. 3.8 对象的赋值与复制 C++还使用另一种方便的对象复制形式,形式为: 类名 目标对象名 = 源象名; 如: Box box2 = box1, box3 = box2; 对象的赋值与复制的不同点 对象的赋值:是在已经存在的对象之间进行数据赋值,因此必须先定义,再赋值;参数表是一般变量 对象的复制:从无到有地建立一个相同的新对象,参数只有一个,而且是已有的同类对象。 两者形式的不同: 类名 (形参表列); // 普通构造函数的声明 类名 (类名 & 对象名); // 复制构造函数的声明
  • 66. 3.8 对象的赋值与复制什么时候使用复制构造函数? 程序中需要建立一个对象,并用另一个已知对象初始化它,系统自动调用复制构造函数; 当函数的参数是类的对象时,系统自动调用复制构造函数; void main( ) { Box b1(12,13,15); func(b1); } 当函数的返回值是类的对象,系统自动调用复制构造函数 Box f( ) { Box b1(12,14,16); return b1; } // 返回值是Box类的对象 void main ( ) { Box b2; b2 = f( ); }// f函数返回Box类的临时对象,并将它赋值给b2 由于b1是在函数 f 中定义的,函数f 结束时,b1的生命期就结束了。因此不能将b1 带回给main函数,实际上,在f 结束前,执行return 语句时,复制一个b1一样的临时对象,然后将它赋值给b2。
  • 67. 3.9 静态成员 在C语言中,如果想在多个函数中共享一个变量值,我们一般用全局变量。但由于全局变量破坏了封装性,安全得不到保证,在C++中不提倡使用全局变量,我们可以使用静态的数据成员来达到这个目的。 静态数据成员 静态数据成员以 static 关键字定义。例如: class student { public: int display( ); private: static int count; char name[10]; int age; };
  • 68. 3.9 静态成员 将对象中的 count 数据成员定义成 static 型,它就被同一种类的各个对象所共有,而不只属于某一个对象。静态数据成员只占一份内存空间,而不是各个对象个拥有一份内存空间!每个对象都可以引用这个静态数据成员。静态数据成员的值对各个对象都是一样的。如果改变它的值,则在各个对象中这个数据成员的值都同时改变。
  • 69. 3.9 静态成员说明: 在为对象分配空间时,不分配静态数据成员的空间,因为它不属于任何对象。只要类中定义了静态数据成员,即使不定义对象,编译系统也要为静态数据成员开辟内存空间。 C语言中,我们知道,如果在一个函数中定义了一个静态变量,在函数结束时该静态变量不被释放,并保留其值。静态数据成员也是这样,它不随对象的建立而分配空间,也不随对象的撤消而释放空间,其值也被保留。静态数据成员在程序被编译时就分配了空间,在程序结束时,才释放空间。 静态数据成员可以被初始化,但只能在类体之外初始化: 数据类型 类名::静态数据成员名 = 初值; 不必在初始化语句中加 static 关键字,不能用参数初始化表初始化静态数据成员: student (int c, char *p, int a) : count(c) { } // 错误,count是静态数据成员
  • 70. 3.9 静态成员静态数据成员既可以通过类名引用,也可以通过对象名引用。#include using namespace std; class Box {public: Box(int,int); int volume(); static int height; int width; int length; }; Box::Box(int w,int len) { width=w; length=len; } int Box::volume() {return(height*width*length); }int Box::height=10; int main() { Box a(15,20),b(20,30); cout<
  • 71. 3.9 静态成员静态成员函数 在类的定义中,成员函数前如果加了 static 限定词,该成员函数就成为静态成员函数。例: static int volume( ); 用途:静态成员函数的作用不是为了对象之间的沟通,主要是为了引用本类中的静态数据成员。它可以直接引用本类的静态数据成员。 静态成员函数与普通成员函数的区别:静态成员函数没有 this 指针,由此决定静态成员函数不能访问本类中的非静态数据成员,除非用"对象名.非静态数据成员"的形式。
  • 72. 3.9 静态成员静态成员函数示例#include using namespace std; class Student {public: Student(int,int,int); void total(); static float average(); private: int num; int age; float score; static float sum; static int count; }; Student::Student(int m,int a,int s) {num=m; age=a; score=s; } void Student::total() { sum+=score; count++; }float Student::average() { return(sum/count); } float Student::sum=0; int Student::count=0; int main() { Student stud[3]={ Student(1001,18,70), Student(1002,19,79),Student(1005,20,98) }; int n; cout<<"please input the number of students:"; cin>>n; for(int i=0;i
  • 73. 3.10 友元 类具有封装性,类中的私有数据只有通过该类的成员函数才可以访问。如果在程序中需要访问类的私有成员,就必须通过对象来调用类的成员函数,频繁调用成员函数将影响程序运行效率。 为解决上述问题,C++ 提供一种友元机制,友元可以不通过调用成员函数就可以直接访问类的私有数据,以提高程序运行效率。 友元机制在数据封装这堵不透明的墙上开了一个小孔,友元的使用要慎重。 友元可以是一个普通函数,可以是一个类的成员函数,也可以是一个类。
  • 74. 3.10 友元友元函数 特点: 在类体里边说明,在函数的类型符前加关键字friend; 在类体外定义,定义格式与普通函数相同; 友元函数是非成员函数,在调用上与普通函数相同; 友元函数可以直接访问该类中的私有成员。 例如:下面程序段说明友元函数的说明和定义方法class X { int i; friend void func( X *, int ); // friend function declaration Public: void member_func ( int ); }; void func(X *ptr, int a) { //friend function definition ptr->i=a; } void X::member_func(int a) { //member function definition i=a; }
  • 75. 一个友元的综合示例#include using namespace std; class Time {public: Time(int,int,int); friend void display(Time &); private: int hour; int minute; int sec; }; Time::Time(int h,int m,int s) {hour=h; minute=m; sec=s; }void display(Time &t) { cout<
  • 76. 3.10 友元友元成员函数:friend 函数也可以是另一个类中的成员函数。#include using namespace std; class Date; class Time {public: Time(int,int,int); void display(const Date&); private: int hour; int minute; int sec; }; class Date {public: Date(int,int,int); friend void Time::display(const Date &); private: int month; int day; int year; }; Time::Time(int h,int m,int s) {hour=h; minute=m; sec=s; }void Time::display(const Date &da) {cout<
  • 77. 3.10 友元友元类: 当说明一个类为另一个类的友元时,友元类中的所有成员函数都是另一个类的友元函数。例如:#include class X { public: friend class Y;//类Y 是类X 的友元类 void set( int i ) { x=i; } void display( ) { cout <<"x="<78. 3.11 类模板(Class Templates)为什么要使用类模板 如果要对功能相同、仅类的数据类型不同的各种情况,都重新定义一种新的类型,会产生较大的重复。例如: class compare_int {public: compare_int (int a, int b ) { x=a; y=b; } int max( ) { return (x>y)? x : y; } private: int x,y; }; class compare_char {public: compare_char (char a, char b ) { x=a; y=b; } char max( ) { return (x>y)? x : y; } private: char x,y; }; void main( ) { compare_int c1( 5, 6 ); cout << c1.max( ) << endl; compare_char c2(‘a’, ‘f’); cout << c2.max( ) << endl; }
  • 79. 3.11 类模板(Class Templates)类模板的定义: 为解决这一问题,C++引进类模板的概念。我们在类的声明前先加一行模板关键字。它用一个通用参数 T 来替代不同的数据类型。类模板的定义格式为: template class <类名> { //类体说明 }; 其中template 是关键字;中可以有多个参数,其间用逗号分隔。 使用类模板定义对象的: 类模板名 < 实际数据类型名 > 对象名( 实参列表 ); 例如:下面是一个数组类模板的例子
  • 80. #include template class compare {public: compare (T a, T b ) { x=a; y=b; } T max( ) { return (x>y)? x : y; } private: T x,y; }; void main( ) { compare < int > cmp1(3,7); cout< cmp2(‘a’,’g’); cout< cmp3(1.0, 3.0); cout<