1、第3章 类和对象的进一步讨论,3.1 构造函数3.2 析构函数3.3 调用构造函数和析构函数的顺序3.4 对象数组3.5 对象指针3.6 共用数据的保护,3.7 对象的动态建立和释放3.8 对象的赋值和复制3.9 静态成员3.10 友元3.11 类模板,3.1 构造函数,3.1.1 对象的初始化类的数据成员的初始化不能在类声明时初始化。,class Time int hour=0; int minute=0; int sec=0;,x,若类中所有成员都是公用的,则可以在定义对象时对数据成员初始化。,class Time public: int hour,minute,sec;Time t1=1
2、4,56,30;,3.1.2 构造函数的作用构造函数是在类中声明的一种特殊的成员函数,作用是在对象被创建时使用特定的值构造对象,将对象初始化为一个特定的状态。构造函数的名字与它所属的类名相同,被声明为公有函数,且没有任何类型的返回值。在创建对象时被自动调用。如果程序中未声明,则系统自动产生出一个默认形式的构造函数。允许为内联函数、重载函数、带默认形参值的函数。,例:构造函数例题。#include class dateprivate: int year, month, day;public: date(); void print( );,date:date( )year = 2012; mont
3、h = 1; day = 1; coutconstructor called“;void date:print( )cout year . month . day endl;,void main( )date today, tomorrow;cout today is ;today.print( );cout tomorrow is ;tomorrow.print( );,程序运行结果为constructor calledconstructor calledtoday is 2012.1.1tomorrow is 2012.1.1,3.1.3 带参数的构造函数 在调用不同对象构造函数时,从外面
4、将不同的数据传给构造函数,以实现不同的初始化。,class dateprivate: int year, month, day;public: date(int y, int m, int d ); void print( );,date:date(int y, int m, int d )year = y; month = m; day = d; coutconstructor called“;void date:print( )cout year . month . day endl;,void main( )date today(2001,8,11), tomorrow(2001,8,1
5、2);cout today is ;today.print( );cout tomorrow is ;tomorrow.print( );,程序运行结果为constructor calledconstructor calledtoday is 2001.8.1tomorrow is 2001.8.12,3.1.4 用参数初始化列表对数据成员初始化date:date(int y, int m, int d ):year(y),month(m),day(d) ,3.1.5 构造函数重载例3.3 class Box public:Box();Box(int h, int w, int len ) :
6、height(h),width(w),length(len) int volume(); private:int height,width,length;Box:Box()height=10; width=10; length=10;,int Box:volume()return height*width*length;int main()Box box1; cout“The volume of box1 is”box1.volume()endl; Box box2(15,30,25); cout“The volume of box2 is”box2.volume()endl; return
7、0;,Box box1();,3.16 使用默认参数的构造函数如果用户不指定实参值,编译系统就使形参取默认值。(减少输入量。)例3.4class Box public: Box(int h=10, int w=10, int len=10 ); int volume(); private:int height,width,length;Box:Box(int h,int w,int len)height=h; width=w; length=len;,int Box:volume()return height*width*length;int main()Box box1; coutbox1.
8、volume()endl; Box box2(15); coutbox2.volume()endl; Box box3(15,30); coutbox3.volume()endl;Box box4(15,30,20);coutbox4.volume()endl; return 0;,说明:(1)应该在声明构造函数时指定默认值。(用户可以看到)(2)一个类只能有一个默认构造函数。 Box(); Box(int h=10, int w=10, int len=10 ); Box box1;,(3)在一个类中定义了全部是默认参数的构造函数后,不能再定义重载的构造函数。,一般不应同时使用构造函数重载和
9、有默认参数值的构造函数。,Box box1,box2(15),box3(15,30);,Box(int =10,int=10,int=10);Box();Box(int,int);,Box(int ,int=10,int=10);Box();Box(int,int);,3.2 析构函数,析构函数也是类的成员函数,它的名字是在类名前加字符“”。完成对象被删除前的一些清理工作。在对象的生存期结束的时刻系统自动调用它,然后再释放此对象所属的空间。函数中定义的对象(局部对象)static局部对象全局对象new动态建立的对象,析构函数没有参数,也没有返回值。析构函数不能重载,即一个类中只可能定义一个析构
10、函数。 如果程序中未声明析构函数,编译器将自动产生一个默认的析构函数。,#include #include using namespace std;class Studentpublic: Student(int n,string nam,char s) num=n;name=nam;sex=s; cout“Constructor called.”endl; Student()cout“Desturctor called.”endl;void display()coutnumnamesexendl;private: int num; string name; char sex;,int mai
11、n() Student stu1(1010,”wang_Li”,f);stu1.display();Student stu2(1011,”Li_na”,m);stu2.display();return 0;,3.3 调用构造函数和析构函数的顺序,一般情况下,调用析构函数的次序正好与调用构造函数的次序相反。,#include #include using namespace std;class Studentpublic: Student(int n,string nam,char s) num=n;name=nam;sex=s; cout“Constructor called.”numendl
12、; Student()cout“Desturctor called.”numendl;void display()coutnumnamesexendl;private: int num; string name; char sex;,int main() Student stu1(1010,”wang_Li”,f);stu1.display();Student stu2(1011,”Li_na”,m);stu2.display();return 0;,不同存储类型的对象调用构造函数及析构函数,1、对于全局定义的对象(在函数外定义的对象),在程序开始执行时,调用构造函数;到程序结束时,调用析构函
13、数。,2、对于局部定义的对象(在函数内定义的对象),当程序执行到定义对象的地方时,调用构造函数;在退出对象的作用域时,调用析构函数。,3、用static定义的局部对象,在首次到达对象的定义时调用构造函数;到程序结束时,调用析构函数,4、对于用new运算符动态生成的对象,在产生对象时调用构造函数,只有使用delete运算符来释放对象时,才调用析构函数。若不使用delete来撤消动态生成的对象,程序结束时,对象仍存在,并占用相应的存储空间,即系统不能自动地调用析构函数来撤消动态生成的对象。,class A float x,y;public: A(float a, float b)x=a;y=b;c
14、outPrint(); delete pa1; cout退出main()函数n;,进入main()函数,调用了构造函数,35,调用了析构函数,退出main()函数,class Afloat 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);
15、/定义局部自动对象 static A a3; /初始化局部静态对象 void main(void) cout进入main函数n;f(); f(); ,初始化全局对象,进入main函数,初始化自动局部对象,进入f()函数,初始化静态局部对象,进入f()函数,初始化自动局部对象,调用析构函数,调用析构函数,调用析构函数(static),调用析构函数(全局),3.4 对象数组,声明:类名 数组名元素个数;访问方法:只能引用单个数组元素。通过下标访问 数组名下标.成员名,对象数组初始化数组中每一个元素对象被创建时,系统都会调用类构造函数初始化该对象。如果构造函数只有一个参数,在定义数组时可以直接在等号
16、后面的花括号内提供实参。 Circle cir3=2,5,8;(假定圆类只有数据成员半径)如果构造函数有多个参数,在定义数组时在等号后面的花括号内分别写出构造函数并制定实参。 Point p3=Point(1,2), Point(2,3), Point(4,5);,例3.6 对象数组的使用class Boxpublic: Box(int h=0,int w=12,int len=15):height(h),width(w),length(len) int volume();private: int height,width,length;int Box:volume()return heigh
17、t*width*length;,int main()Box a3= Box(10,12,15), Box(15,18,20), Box(16, 20,26); cout“volume of a0 is”a0.volume()endl;cout“volume of a1 is”a1.volume()endl;cout“volume of a2 is”a2.volume()endl;,class Point public: Point(); Point(int xx,int yy); Point(); int GetX() return X; int GetY() return Y; privat
18、e: int X,Y;,Point:Point() X=Y=0; coutDefault Constructor called.endl;Point:Point(int xx,int yy) X=xx; Y=yy; cout Constructor called.endl;Point :Point() coutDestructor called.endl; ,例:,27,int main()Point A2;for(int i=0;i2;i+)coutAi.GetX“,”Ai.GetYendl;Point B2=Point(1,2),Point(3,4);for(int i=0;i2;i+)c
19、outBi.GetX“,”Bi.GetYendl;return 0;,27,Default Constructor called.Default Constructor called.0,00,0Constructor called.Constructor called.1,23,4Destructor called.Destructor called.Destructor called.Destructor called.,3.5 对象指针,对象的指针:对象空间的起始地址指向对象的指针变量的定义形式 类名 * 对象指针名,class Timepublic: int hour,minute,s
20、ec; void get_time() couthourminute公有成员名,指向对象数据成员的指针 数据类型名 * 指针变量名,class Timepublic: int hour,minute,sec; void get_time() couthourminutesec;,Time t;int *p;p=,指向对象成员函数的指针 数据类型名 (类名:*指针名)(参数表);eg:void(Time:*p2)();/p2为指向Time类中公用成员函数的指针变量。使指针变量指向一个公有的成员函数的形式: 指针变量名=,指向普通函数的指针变量 数据类型 (*指针变量名)(参数表);eg:void
21、(*p)(); p=fun; (*p)( );/fun();,例3.7 有关对象指针的使用class Timepublic: Time(int h,int m,int s) hour=h;minute=m;sec=s; void get_time() couthourminutex=a;,this-y=b;,系统自动将对象的指针带到成员函数中,this-x,this-y,(*this).y,(*this).x,调用对象t的成员函数f,是在调用函数f时使this指向对象t,从而访问对象t的成员。,3.6 共用数据保护,1.常对象(保证数据成员不被改变的对象)定义形式:类名 const 对象名(实
22、参列表); const 类名 对象名(实参列表);常对象中的数据成员为常变量且必须有初值。eg: Time const t1(10,15,24);常对象不能调用该对象的非const型的成员函数 (防止这些函数会修改常对象的数据成员的值)常成员函数可以访问常对象的数据成员,但仍然不允许修改常对象的数据成员值。,2.常对象成员常数据成员常数据成员用const声明,其值不能改变。只能通过构造函数的参数初始化列表对常数据成员进行初始化。class Timepublic: Time(int h); const int hour;,Time:Time(int h)hour=h;,Time:Time(int
23、 h):hour(h),常成员函数常成员函数只能引用本类中的数据成员,而不能修改它们。 eg :void get_time() const;常成员函数说明格式: 类型说明符 函数名(参数表)const;const是函数类型的一部分,在声明和定义函数时都要用const,在调用时不加const。 const关键字可以被用于参与对重载函数的区分常成员函数不能调用另一个非const成员函数。,class Rpublic: R(int r1, int r2) R1=r1;R2=r2; void print(); void print() const; private: int R1,R2;,void R
24、:print()coutR1:R2endl;void R:print() constcoutR1;R2endl;int main() R a(5,4); a.print(); const R b(20,52); b.print();,3.指向对象的常指针定义形式: 类名 * const 指针变量名=对象地址指针值始终保持为其初值,不能改变。(其指向始终不变)可以改变常指针所指向对象的数据成员的值。eg: Time t1(10,12,15),t2; Time * const ptr1=,4.指向常对象的指针变量指向常变量的指针变量 const 类型名 *指针变量名;常变量只能用指向常变量的指针变
25、量指向它,const char c=“boy”;const char *p1=c;char *p2=c;,指向常变量的指针变量还可以指向变量。 但不能通过此指针变量改变该变量的值,char c1=a;const char *p=,不能通过指向常变量的指针变量改变其所指向的常变量或变量的值,若函数的形参是指向非const型变量的指针,实参只能是指向非const变量的指针,这样在执行函数过程中可以改变形参指针变量所指向的变量的值。若函数的形参是指向const型变量的指针,实参可以是指向const变量的指针或指向非const变量的指针。,常对象只能用指向常对象的指针变量指向它若指向常对象的指针变量指
26、向非const对象,则其指向的对象不能通过指针来改变。,Time t1(10,12,15);const Time *p=,const Time *p;/指向常对象的指针变量Time * const p;/指向对象的常指针变量,指向常对象的指针变量本身的值可以改变。 const Time *p=,指向常对象的指针常用于函数的形参,目的是保护形参指针所指向的对象,使它在函数执行过程中不被修改。,int main()void fun(const Time *p); Time t1(10,10,12); fun(,t1是const型?,const,const,5.对象的常引用 用常指针和常引用做函数参
27、数,既能保证数据安全,使数据不能被随意修改,在调用函数是又不必建立实参的拷贝。,class Timepublic: Time(int h,int m,int s) hour=h;minute=m;sec=s; int hour,minute,sec; ;void fun( Time ,int main() Time t1(10,10,12); fun(t1); coutt1.hourendl; return 0;,const,6.const型数据的小结,3.7 对象的动态建立和释放,动态建立对象 new 类名; 系统为对象开辟一段内存空间,同时调用该类的构造函数,以使对象初始化。此运算返回一个
28、指向新对象的指针的值,通过它访问这个对象。 eg:Time *p=new Time; Time *p=new Time(0,0,0); couthour;,动态释放对象 delete 指针变量名 new建立的对象不再需要时,用delete释放。在释放内存空间之前,自动调用析构函数。 eg: delete p;,3.8 对象的赋值和复制,1.对象的赋值一般形式:对象名1=对象名2;注意:相互赋值的对象必须属于同一个类。例如: student stud1,stud2; stud2=stud1;,例3.9 对象的赋值class Box public: Box(int h=10, int w=10,
29、int len=10 ) int volume(); private: int height,width,length;Box:Box(int h,int w,int len)height=h; width=w; length=len;,int Box:volume()return height*width*length;int main()Box box1(15,30,25),box2; coutbox1.volume()endl;box2=box1; coutbox2.volume()endl; return 0;,2.对象的复制用一个已有对象快速复制出多个完全相同的对象。一般形式: 类名
30、 对象2(对象1); 或 类名 对象2=对象1;用一个已有对象来构造同一类型的另一对象,可以通过调用一种特殊的构造函数复制构造函数来实现。复制构造函数的功能是使用一个已经存在的对象去初始化一个新创建的同类的对象,它可以将一个已有对象的数据成员的值拷贝给正在创建的另一个同类的对象。,拷贝构造函数的形参为本类的对象引用。class 类名 public : 类名(形参);/构造函数 类名(类名 &对象名);/拷贝构造函数 .;类名:类名(类名 &对象名)/拷贝构造函数的实现 函数体 ,class Point public:Point(int xx=0, int yy=0) X=xx; Y=yy;Po
31、int(Point ,拷贝构造函数在三种情况下会被调用: 用类的一个对象去初始化该类的另一个对象时。 int main(void) Point A(1,2); Point B(A); coutB.GetX( )endl; return 0; , 函数的形参是类的对象,调用函数进行形参和实参的结合时。 void f( Point p) coutp.GetX( )endl; int main() Point A(1,2); f(A);return 0; , 函数的返回值是类的对象,函数执行完返回调用者时。 Point g( ) Point A(1,2); return A; int main( )
32、 Point B; B=g( );return 0;,如果程序员没有为类声明拷贝初始化构造函数,则编译器自己生成一个默认的拷贝构造函数。这个构造函数执行的功能是:用作为初始值的对象的每个数据成员的值,初始化将要建立的对象的对应数据成员。,3.9 静态成员,使用全局变量实现数据共享,int x,y,z;int main()x=5;y=6; void max();max(); couty?x:y;,没有控制可见性范围,没有限制访问权限,没有限制操作的合法性。,将数据和使用数据的函数封装在类中。一方面实现了类内函数间数据的共享。同时控制它们在类外共享访问的范围和权限。,将函数与数据封装,#inclu
33、declass Application public: void f();void g(); private: int global;void Application:f() global=5;void Application:g()coutglobalendl;,int main() Application MyApp; MyApp.f(); MyApp.g(); return 0;,静态成员用于解决同一个类的不同对象之间的数据和函数的共享问题。静态数据成员静态成员函数,静态数据成员,例如,我们可以抽象出学生的共性,设计如下的学生类: class Student private: int S
34、tudentNo; char *name; int ID; ;,如果需要统计学生总数,这个数据存放在什么地方呢?,静态数据成员在类定义中用关键字static声明,class A int x,y; static int z; public: void Setxy(int a, int b) x=a; y=b;A a1, a2;,z,a1. z,a2. z,不同对象,同一空间,在内存中只占一份空间,其空间是在所有对象之外单独开辟的。在程序编译时被分配存储空间,到程序结束时才释放空间。属于类,为所有对象所共享。所以,在类外可通过类名和对象名对它进行引用。类名:静态数据成员名; 对象名.静态数据成员名
35、;静态数据成员同一般数据成员一样要服从访问控制限制。必须在类外定义和初始化,用(:)来指明类属性。如果未对静态数据成员赋初值,编译系统自动赋初值0。,静态数据成员在类定义中用关键字static声明,class Aint i; static int count;public:A(int a=0) i=a; count+; cout Number of Objects=countn; A() count-; cout Number of Objects=countn;void Show() cout i=in; cout count=countn;int A:count;void main(voi
36、d )A a1(100);A b2;a1.Show();,Number of Objects=1,Number of Objects=2,Number of Objects=3,i=100count=3,Number of Objects=2,Number of Objects=1,Number of Objects=0,#include using namespace std;class Pointpublic:static int countP;/静态数据成员引用性说明 Point(int xx=0, int yy=0) X=xx; Y=yy; countP+; void GetC()co
37、ut Object id=countP endl; private:int X,Y;,int Point:countP=0; /静态数据成员定义性说明int main()Point A(4,5);coutA.countPendl; A.GetC();Point B(A); coutPoint:countPendl;B.GetC();,静态成员函数,int main() Point:GetC( ); Point A(4,5); coutA.countPendl; A.GetC(); Point B(A); coutPoint:countPendl; B.GetC();,countP的初始值如何输
38、出?,class Pointpublic:static int countP; Point(int xx=0, int yy=0) X=xx; Y=yy; countP+; void GetC()cout Object id=countP endl; private:int X,Y;int Point:countP=0;,静态成员函数用关键字static来修饰成员函数。作用:为了能处理静态数据成员。类外代码可以使用类名和作用域操作符来调用静态成员函数。,静态成员函数属于类,它不包含对象地址的this指针。 静态成员函数只能直接访问属于该类的静态数据成员或静态成员函数。静态成员函数访问非静态成员
39、,必须通过对象名来访问。,class Pointpublic:Point(int xx=0,int yy=0)X=xx;Y=yy;countP+;static void GetC() cout Object id=countPendl;private:int X,Y;static int countP;int Point:countP=0;,int main() Point:GetC(); Point A(4,5);A.GetC();/输出对象号,对象名引用Point B(A);Point:GetC();/输出对象号,类名引用,class Apublic: static void f(A a)
40、; private: int x;void A:f(A a) coutx; couta.x; ,静态成员属于类,非静态成员属于对象。在静态函数成员实现中不能直接引用类中非静态成员,可以通过对象来访问非静态成员x。,/对x的引用是错误的,/正确,例3.11 静态成员函数的应用class Studentpublic: Student(int n,int a,float s):num(n),age(a),score(s) void total() sum+=score; count+; static float average() return(sum/count); private: int num, age; float score; static float sum; static int count;float Student:sum=0;int Student:count=0;,