1、第7章 类和对象的基础,本章内容提要,类与对象的定义 对象的初始化 成员函数的特性 静态成员 友元 类的作用域与对象的生存期,一、类与对象的定义,什么是类?类是一种新的数据类型。类是逻辑相关的函数和数据的封装,是对所处理的问题的抽象描述,所以,类是实现抽象类型的工具。类(Class):是一种用户定义的类型,它包括定义的数据和其对应的操作(函数)。使用对象将数据和操作进行封装(Encapsulation)。,什么是对象?,类实例化后便生成对象。对象(Object):含有数据(对象的属性Attribute)又含有对数据的操作代码(Method)的一个逻辑封装体。属性:描述对象特征的数据。方法:对属
2、性的操作。面向对象编程利用对象进行程序设计,1. 类的定义,类的定义一般分为说明部分和实现部分。说明部分:类中的成员数据成员成员函数的说明实现部分成员函数的具体定义例如:定义一个汽车类型号、外壳、车轮等(数据成员)启动、加速和停止等(成员函数),类的定义格式:,class public: private: protected: ; ,例:坐标点类,问题:将坐标点与相应操作封装成类,class TPoint public: void Setxy(int a, int b);/说明在类体内,定义在类体外 void Move(int,int);/函数原型声明,参数名可给可不给出 void Print
3、(); int Getx(); int Gety();private: int x,y; /数据成员;,例:坐标点类 (续上),问题:将坐标点与相应操作封装成类,void TPoint:Setxy(int a, int b) x=a; y=b;void TPoint:move(int a,int b)x=x+a;y=y+b; void TPoint:Print();coutx= Getx()y=Gety();int TPoint:Getx() return x;int TPoint:Gety() return y;,在类体外定义成员函数的一般格式为:函数类型 类名:成员函数(参数表)其中,作用
4、域运算符“:”是用来标识成员函数是属于哪个类的,“类名”是成员函数所属类的名字。若在函数前加关键词inline,则成为内联成员函数。若函数较短,可以在类内定义,也称作内联成员函数。,例:坐标点类 (内联函数形式),问题:将说明部分和实现部分都在类体内定义 。,class TPoint /类名public: /声明其后为公有成员 void Setxy(int a, int b) x=a; y=b; void Move(int a,int b) x=x+a;y=y+b; void Print() coutx= Getx()y=或者(*).-()或者(*).()例如:p-x,p-y,p-Getxy(
5、int a,int b)或者(*p).x, (*p).y, (*p).Getxy(int a,int b),例7.1,问题:对象的定义和成员函数的调用。,#include#include Tpoint.hvoid main()TPoint p1,p2; TPoint *p=,该程序的运行结果为x=1y=2x=3y=4x=5y=6x=11y=14x=6y=8x=11y=14,对象定义的实质,定义一个类,就相当于定义了一种类型,它并不会接受或存储具体的值,只作为一个样板,只有被实例化生成对象以后,系统才为对象分配存储空间。为节省内存,在创建对象时,只为每个对象的数据成员分配内存,而成员函数只是一次
6、性存放在静态存储区中,为所有的对象共享。例如:TPoint p1,p2;,二、对象的初始化,在类的定义中,无法用表达式初始化数据成员,因此,在声明一个对象时,对象的初始状态是不确定的,需要初始化。对象初始化可以使用:构造函数析构函数复制构造函数,1.构造函数,构造函数是一种特殊的成员函数,它主要用来为对象分配空间,给他的数据成员赋值(初始化)并执行对象的其他内部管理操作。说明:构造函数的名字必须与类的名字相同,并且构造函数无返回值,不允许定义构造函数的返回值类型,包括void类型。但允许有参数。构造函数是成员函数,函数体可写在类体内,也可写在类体外。构造函数可以重载。,注意:构造函数在对象创建
7、时由系统自动调用,这是它与类的普通成员函数之间的最大区别。,构造函数的定义格式如下: : ,例7.2,问题:构造函数的定义举例。,#include class Tpointpublic:Tpoint(int m,int n)x=m;y=n;/定义构造函数int Getx()return x;int Gety()return y;private:int x,y;void main() Tpoint a(10,20);Tpoint b(15,25); couta.Getx(),a.Gety()endl; coutb.Getx(),b.Gety()endl;,该程序的运行结果如下:10,2015,2
8、5,2.析构函数,析构函数也是类中的特殊成员函数,与类名字相象,在前加一个波浪号“”。它没有参数,也没有返回值,在一个类中只能有一个析构函数。析构函数执行与构造函数相反的操作。通常用于释放分配给对象的存储空间,当程序超出类对象的作用域时,或者当对一个类指针使用运算符delete时,将自动执行析构函数。说明:和构造函数一样,如果不定义析构函数,编译程序将提供一个缺省的析构函数。对大多数类来说,缺省的析构函数就能满足要求,如果在一个对象完成其操作之前还需要做一些内部处理,则应显式的定义析构函数。,析构函数的定义格式如下: : ,注意:析构函数仅能有一个,不能重载。,例7.3,问题:构造函数和析构函
9、数举例。,#include class Tpointpublic:Tpoint(int m,int n)x=m;y=n;/定义构造函数Tpoint();int Getx()return x;int Gety()return y;private:int x,y;Tpoint:Tpoint()coutDestructor called.n;void main() Tpoint a(10,20);Tpoint b(15,25); couta.Getx(),a.Gety()endl; coutb.Getx(),b.Gety()endl;,该程序的运行结果如下:10,2015,25Destructor
10、called.Destructor called.,类的构造函数和析构函数注意:,类的构造函数在程序中为对象创建时分配系统资源提供了初始化的方法;析构函数为对象撤销时释放系统资源提供了方法。无论用户是否定义构造函数和析构函数,它们都默认存在。只要定义了新类,类的对象在创建和撤销时,构造函数和析构函数将会被调用。,3.复制构造函数,已有变量可以初始化另一个变量:int a=5;int b=a;同样,已有对象也可以初始化另一个对象。复制构造函数是一种特殊的成员函数,其功能是用一个已经初始化过了的对象去初始化另一个新创建的对象。复制构造函数的定义格式如下::(&)注意:它只有一个参数,是对某个对象的
11、引用。,例7.4,问题:调用复制构造函数的例子。,#include class Tpointpublic: Tpoint(int m,int n);/定义构造函数 Tpoint(); /定义析构函数 Tpoint (Tpoint ,例7.4,问题:调用复制构造函数的例子。,void main() Tpoint a(10,20); Tpoint b(15,25); Tpoint c(a); couta.Getx(),a.Gety()endl; coutb.Getx(),b.Gety()endl; coutc.Getx(),c.Gety()endl;,该程序的运行结果为:Call construc
12、tor.Call constructor.Copy constructor.10,2015,2510,20Destructor called.Destructor called.Destructor called.,7.3成员函数的特性,成员函数除了说明和定义之外,还有一些相关的特性 :内联函数和外联函数 成员函数重载 设置参数的缺省值,1.内联函数和外联函数,类的成员函数可以分为内联函数和外联函数。内联函数是指那些定义在类体内的成员函数,即该函数的函数体放在类体内。而说明在类体内,定义在类体外的成员函数叫外联函数。外联函数的函数体在类的实现部分。两者的区别:一般函数进行调用时,要将程序执行权
13、转到被调用函数中,然后再返回到调用它的函数中;而内联函数则是在编译时,将调用表达式用内联函数体来替换。这样在使用时去掉了调用环节,提高运行速度。缺点:如果内联函数代码过大,则目标程序会加大。,例7.5,问题:内联函数与外联函数实例。,#include class Apublic:A(int m,int n) /内联函数,定义在类体内x=m;y=n;int Getx()return x; /内联函数,定义在类体内int Gety()return y; /内联函数,定义在类体内int sumxy(); /内联函数,定义在类体外,但进行了转化int area(); /外联函数,定义在类体外priva
14、te:int x,y;inline int A:sumxy() /由外联函数转化为内联函数return Getx()+Gety();int A:area()return Getx()*Gety();void main() A s(10,20); coutsum=s.sumxy(),area=s.area()endl; ,在使用内联函数时,应注意以下几点: (1)在内联函数内不允许用循环语句和开关语句。(2)内联函数的定义必须出现在内联函数第一次被调用之前。(3)内联函数无法进行递归调用。,2.成员函数重载,在前面介绍的成员函数中,除了析构函数不能重载外,其他的成员函数都可以进行重载。但重载必须
15、满足以下条件之一:参数个数不同。参数类型不同。当参数个数相同时,参数类型至少有一个不同。仅有返回值不同的函数是不能重载的。,例7.6,问题:成员函数重载实例。,#includeclass Bpublic:B(int a)x=a+1;y=a;B(int a,int b)x=a;y=b;int add();int add(int a);int add(int a,int b);int Getx()return x;int Gety()return y;private:int x,y;int B:add() return x+y;int B:add(int a) x=y=a; return x+y;
16、int B:add(int a,int b) x=a; y=b; return x+y;,例7.6,问题:成员函数重载实例。,void main() B m(5,7),n(6); coutm=m.Getx(),m.Gety()endl; coutn=n.Getx(),n.Gety()endl; int i=m.add(); int j=m.add(1,2); int k=n.add(4); coutiendljendlkendl;,该程序的运行结果为:m=5,7n7,61238,3.设置参数的缺省值,成员函数和普通函数一样,可以设置参数的缺省值。一般的成员函数和构造函数都可以被设置参数的缺省值
17、。,例7.7,问题:设置默认参数应用举例。,#includeusing namespace std;class Bpublic:B(int a=10,int b=20,int c=30);int Getx()return x;int Gety()return y; int Getz()return z;private: int x,y,z;B:B(int a,int b,int c) x=a;y=b;z=c;,例7.7,问题:设置默认参数应用举例。,void main() B i,j(1),k(1,2),r(1,2,3); couti=i.Getx() i.Gety() i.Getz()end
18、l; coutj=j.Getx() j.Gety() j.Getz()endl; coutk=k.Getx() k.Gety() k.Getz()endl; coutr=r.Getx() r.Gety() r.Getz()endl;,该程序的运行结果为:i=10 20 30j=1 20 30k=1 2 30r=1 2 3,四、静态成员,静态成员可以实现多个对象之间的数据共享,并且使用静态成员还不会破坏隐藏的原则,保证了安全性。静态数据成员静态成员函数,1. 静态数据成员,静态数据成员是类的所有对象中共享的成员,而不是某个对象的成员。弥补了全局变量的不足,它相对于类中的“全局变量”。注意:静态成
19、员所占空间不会随着对象的产生而分配,随着对象的消失而回收。,例7.8,问题:静态数据成员定义。,#include using namespace std;class timepublic: time(int,int); void display();private:static int hour; /hour是一个静态数据成员。 int minute; int second;,例7.8,问题:静态数据成员定义。,time:time(int m,int s):minute(m),second(s);int time:hour=15;void time:display() couthour:min
20、ute:secondendl;void main() time t1(06,20),t2(10,30); t1.display(); t2.display();,该程序的运行结果为:15:6:2015:10:30,说明:,静态数据成员的初始化应在类体外进行,其格式如下::=例如,int time:hour=15;在数据成员前不加static,而应使用作用域运算符来表明所属类。在程序中如果静态数据成员的访问权限为公有(public)成员,则可按如下格式引用:: /前面直接用类名静态数据成员被存放在静态存储区。,2.静态成员函数,在静态成员函数的实现中,可以直接引用静态成员,但不能直接引用非静态成
21、员,如果要引用非静态成员时,可通过对象来引用。对静态成员函数的引用,在程序中一般使用下列格式:类名.静态成员函数(参数表)有时,也可以由对象来引用,格式如下:对象.静态成员函数(参数表),例7.9,问题:静态成员函数对非静态成员的引用。,#includeclass testpublic: test(int a)A=a;B+=a; static void f(test m); /静态公有成员函数声明private: int A; static int B; /静态私有成员;,例7.9,问题:静态成员函数对非静态成员的引用。,int test:B=0; /静态成员初始化void test:f(te
22、st m)coutA=m.Aendl; /引用类的非静态成员,用对象引用coutB=Bendl; /引用类的静态成员,直接引用void main()test X(10),Y(20);test:f(X); /调用静态成员函数test:f(Y); /调用静态成员函数,该程序的运行结果为:,静态成员函数的使用注意:,只能访问类的静态数据成员,而不能直接访问类中的普通成员(需用点域法)静态成员函数与类相联系,而不与类的对象相联系,所以在类的外部调用类中的公有静态成员函数,必须在其左面加类名:,而不是通过对象名调用公有静态成员函数。在类的外部不能调用类中的私有静态成员函数。,五、友元,为了方便类内数据的
23、访问,提出友元方案。两种形式友元函数友元类特点:破坏了类的封闭性和隐藏性,但是提高了程序的运行效率。,1.友元函数,友元函数是定义在类外部的普通函数,但对它的说明是在类内部,在友元函数的说明前加关键字friend。友元函数说明格式:friend 类型 友元函数名(参数表);在类外定义友元函数的格式:类型 函数名(类 注意:友元函数不是成员函数,但可以访问类中的私有成员。声明可在私有部分,也可在公有部分,作用相同。友元函数的调用方式于普通函数相同。,友元函数关系示意图,例7.10,问题:友元函数与普通函数。,#includeusing namespace std;class Student pr
24、ivate:int x; friend void getf(Student/调用该友元函数,该程序的运行结果为:10,例7.10,问题:友元函数与普通函数。,#includeusing namespace std;class Studentprivate: int x; public: void get() /成员函数 x=20; coutxendl; ;void main() Student s; s.get();,该程序的运行结果为:20,2.友元类,当希望一个类可以存取另一个类的私有成员时,可以将该类声明为另一个类的友元类。此时,该类的所有成员函数都可以访问另一个类中的所有成员。定义友元
25、类的语句格式如下:friend class ;其中:friend和class是关键字,类名必须是程序中的一个已定义过的类。,使用友元类注意事项:,是单向的。如类A是类B的友元,但类B并不是类A的友元。不具有传递性。若类X是类Y的友元,类Y是类Z的友元,但类X不是类Z的友元。,例7.11,问题:友元类实例。,#include using namespace std;class X;class Ypublic: Y()strcpy(cname,计算机语言); void print() coutcnameendl; friend class X; /友元类的声明 private: char cnam
26、e30;,例7.11,问题:友元类实例。,class Xpublic: X(char *na,char *ena) strcpy(X:name,na);strcpy(X:ename,ena); void Editcname(Y ,例7.11,问题:友元类实例。,void main() X a(C+语言程序设计, The C+ Programming Language.); Y b; a.Editcname(b); a.print();b.print();,该程序的运行结果为:C+语言程序设计The C+ Programming Language.计算机高级语言,六、类的作用域,类的作用域简称类
27、域,它是指类的定义和相应成员函数的定义范围。注意:一个类在类域内对任何成员都是开放的,而对类域外的类或函数的访问受到限制。在类域中定义的变量不能使用auto,register和extern等修饰符,只能用static修饰符,而定义的函数也不能用extern修饰符。类本身可被定义在三种作用域内:全局作用域。(一般的C+类)在另一个类的作用域中。(嵌套类)在一个块的局部作用域中。(局部类),例,问题:全局作用域。,int fork (void); /全局fork函数 class Processint fork (void);.;成员函数fork隐藏了全局函数fork,前者能通过单目域运算符调用后者
28、:int Process:fork (void)int pid = :fork(); / 调用全局fork函数.,例,问题:嵌套类的例子。,class Rectangle public: Rectangle (int, int, int, int); private: class Point public: Point (int, int); private: int x, y; ; Point topLeft, botRight;,Rectangle:Point:Point (int x, int y).同样,在类域外访问嵌套类需要限制类名,例如:Rectangle:Point pt(1,1
29、);,例,问题:局部类的例子 。,void Render (Image .,例,问题:综合例子。,class a int x; /类变量x说明public: int fun(); /类成员函数fun()说明int x; /全局变量x的说明int fun(); /全局函数fun()的说明int a:fun() /类a中fun()成员函数的定义 :x+; /全局变量x,必须用“:”来引用 return x; /类的成员x ,2.对象的生存期,不同存储的对象生存期不同。对象的生存期是指对象从被创建开始到被释放为止的时间。按生存期的不同对象可分为如下三种:局部对象静态对象全局对象,1.局部对象,当对象
30、被定义时调用构造函数,该对象被创建;当程序退出定义该对象所在的函数体或程序块时,调用析构函数,释放该对象。注意:一般定义在一个函数体或程序块内,它的作用域小,生存期短。,2.静态对象,当程序第一次执行所定义的静态对象时,该对象被创建,当程序结束时,该对象被释放。注意:定义在一个文件中,它的作用域从定义时起到文件结束时止。,3.全局对象,当程序开始时,调用构造函数创建该对象。当程序结束时调用析构函数释放该对象。注意:被定义在某个文件中,它的作用域在包含该文件的整个程序中。,例7.12,问题:不同对象生存期实例。,#include class A public: A(int);A(); priva
31、te: int x;A:A(int a) x=a;coutThe number is:xendl;A:A()coutDestructor called no.xendl;,例7.12,问题:不同对象生存期实例。,void f1() A A5(5); /局部对象 coutIn f1()endl;A A1(1); /全局对象static A A2(2); /外部静态对象void main() static A A3(3); /内部静态对象 A A4(4); /局部对象 coutIn main() before call f1.endl; f1(); coutIn main() after call
32、 f1.endl;,该程序的运行结果为:The number is :1The number is :2The number is :3The number is :4In main() before call f1.The number is :5In f1()Destructor called no.5In main() after call f1.Destructor called no.4Destructor called no.3Destructor called no.2Destructor called no.1,小结,类是一种复杂的数据类型,它是抽象数据类型的实现,是数据和相关操作的封装体.类的定义分两部分:说明和实现对象是类的一个实例,它是属于某个已知类的。,