1、1Chap3 C+的类李春花QQ:471767100武汉光电国家实验室B4052类和对象类和对象 对象(object)表示现实世界中可以明确识别的实体。例如,学生、圆、按钮等。对象具有状态和行为。类(class)定义了同一类对象共有的属性和方法。类是对象的模板、蓝图。对象是类的实例。状态(变量)行为(方法)姓名:张三学号:0001身高:1.73学习工作娱乐3class Circle private:double radius;public:Circle();Circle(double r);double findArea();;#include“Circle.h”Circle:Circle()
2、radius=1.0;Circle:Circle(double r)radius=r;double Circle:findArea()return radius*radius*3.14;示例-圆类数据成员函数成员声明构造函数声明Circle c1;radius=1.0;Circle c2(3.0);radius=3.0;Circle c3(15.0);radius=15.0;构造函数定义函数成员定义Circle.hCircle.cppmain.cpp#include“Circle.h”void main()Circle c1,c2(3.0);coutc1.findArea();cout 0.0
3、)radius=r;else radius=1.0;Circle c1;/用户没指定参数,调用缺省构造函数 Circle c2(5.0);/调用带参数构造函数,实参5.0传给形参r,将对象c2的 /半径设为5.0 cout c1.radiuse;/1.0 cout c2.radiuse;/5.05类的声明及定义类的声明:由保留字class、struct或union加上类的名称构成。类的定义:包括类名的声明部分和类由 括起来的主体两部分构成。类的实现:通常指类的函数成员的实现,即定义类的函数成员。class 类型名;/前向声明class 类型名 /类的定义private:私有成员声明或定义;pr
4、otected:保护成员声明或定义;public:公有成员声明或定义;类保留字:class、struct或union可用来声明和定义类。6访问权限l 封装机制定义数据成员、函数成员和类型成员的访问权限,提供对外访问接口访问内部私有或保护数据。包括三类:private:私有成员可被本类的函数成员访问,不能被派生类函数成员、其它类的函数成员和普通函数访问。protected:受保护成员可被本类和派生类的函数成员访问,不能被其它类函数成员和普通函数访问。public:公有成员可被任何函数成员和普通函数访问。l 注意:类的友元不是当前类的成员,可以在private、protected和public等任
5、意位置说明,友元可以像类自己的函数成员一样访问类的所有成员。l 进入class定义的类时,缺省访问权限为private;进入struct和union定义的类时,缺省访问权限为public。77class FEMALE /缺省访问权限为privateint age;/私有的,本类函数成员和友员可访问public:/访问权限改为publictypedef char*NAME;/公有的,都能访问protected:/访问权限改为protectedNAME nickname;/本类和派生类函数成员、友员可访问NAME getnickname()return nickname;/自动内联public:/
6、访问权限改为publicNAME name;/公有的,都能访问 w;void main(void)FEMALE:NAME n=w.name;/任何函数都能访问公有name n=w.nickname;/错误,main不得访问保护成员 n=w.getnickname();/错误,main不得调用保护成员 int d=w.age;/错误,main不得访问私有age【例3-4】定义FEMALE类 访问权限用法8l 使用private、protected和public保留字标识每一区间的访问权限,同一保留字可以多次出现;l 同一区间内可以有数据成员、函数成员和类型成员,习惯上按类型成员、数据成员和函数成
7、员分开;l 成员可以任意顺序出现在类定义中,函数成员的实现既可放在类体外面;也可内嵌在类体定义中,此时会自动成为内联函数;l 若函数成员在类的定义体外实现,则须在函数返回类型和函数名之间,使用类名和作用域运算符“:”指明该函数成员所属的类;l 声明类时不允许初始化数据成员,应由构造函数完成;l 类的定义体花括号后要有“;”作为类体结束标志。99class COMP double r,v;public:COMP(double rp,double vp)r=rp;v=vp;/自动内联inline double getr();/inline保留字可以省略,后面有定义double getv();inl
8、ine double COMP:getv()return v;/定义内联void main(void)COMP c(3,4);double r=c.getr();/此时getr的函数体未定义,内联失败double v=c.getv();/函数定义在调用之前,内联成功inline double COMP:getr()return r;/定义内联【例3-7】定义一个复数类 内联函数必须在使用前进行定义1010#include#include#include struct STRING char*s;STRING(char*);STRING();STRING:STRING(char*t)s=(cha
9、r*)malloc(strlen(t)+1);strcpy(s,t);coutConstruct:s;STRING:STRING()/防止反复释放内存 if(s=0)return;coutDeconstruct:s;free(s);s=0;/提倡0代替null指针void main(void)STRING s1(String 1n);STRING s2(String 2n);STRING(“Constantn”);/常量对象 cout RETURNn;/自动析够构s2,s1Construct:String 1Construct:String 2Construct:ConstantDeconst
10、ruct:ConstantRETURNDeconstruct:String2Deconstruct:String1构造析构顺序113.4new和deletel 内存管理函数和new、delete的区别:n内存分配:malloc为函数,实参用值表达式,分配后内存初始化为0;new为运算符,操作数用类型表达式,先在底层调用malloc,然后调用构造函数;n内存释放:free为函数,实参用指针类型值表达式,直接释放内存;delete为运算符,操作数为指针类型值表达式,先调用析构函数,然后底层调用free。l 如为简单类型(没有构造、析构函数)指针分配和释放内存,则new和malloc、delete和
11、free没有区别,可混合使用,如new分配的内存可用free释放。1212l new 类型表达式:int*p=new int;/等价int*p=new int(0);数组形式仅第一维下标可为任意表达式,其它维为常量表达式:int (*q)68=new intx+2068;【例3-13】为对象数组分配内存时,必须调用参数表无参构造函数。l delete 指针指向非数组的单个实体使用delete p,如p指向对象,则先调用析构函数,再释放对象所占的内存。l delete 指针指向任意维的数组时使用delete p;否则内存泄露 如p指向对象数组,则对所有对象(元素)先调用析构函数。然后释放对象数组
12、占有的内存。1313#include#include class ARRAY /class体的缺省访问权限为privateint*a,r,c;public:/访问权限改为publicARRAY(int x,int y)/int型可用malloc r=x;c=y;a=new int r*c;ARRAY()if(a)delete a;a=0;/可用free(a);也可用delete a;void main(void)ARRAY y(3,5),*p;p=new ARRAY(5,7);delete p;/main返回前析构y/不能用malloc,ARRAY需要调构造函数/不能用free,否则p未调用析
13、构函数1414两种内存管理方式的区别ar=0c=0r.c=35个整型元素p(a)p=new Array(5,7)Step 1:为对象分配内存p=(Array*)malloc(sizeof(Array)Step 2:调用构造函数a=new int r*c(b)p=(Array*)malloc(sizeof(ARRAY)a=nullr=0c=0pr=5c=71515ar=5c=7r.C=35个整型元素p(d)delete pStep 2:释放Array对象内存free(p)Step 1:先调析构函数delete a,释放a指向的内存(b)free(p)ar=5c=7r.C=35个整型元素p直接释放
14、Array对象内存,没有调析构函数,导致a指向的35个整型元素所占内存没释放两种内存管理方式的区别1616l new还可对已经析构的变量重新构造,从而减少变量的说明个数,提高内存的使用效率。(需高版本C+编译器支持,通常要包含iostream.h)STRING x (Hello!),*p=&x;x.STRING ();/析构对象,但对象所占内存没释放new (&x)STRING (The World);new (p)STRING (The World);l 这种用法可以节省内存或栈的空间。17173.5隐含参数thisl this指针是一个隐含的const指针,不能移动或对该指针赋值(即该指针
15、是const的)。它是普通函数成员的第一个参数,指向要调用该函数成员的对象。*this代表当前被指向的对象。l 当通过对象调用函数成员时,对象的地址作为函数的第一个实参首先压栈,通过这种方式将对象地址传递给隐含参数this。l 构造函数和析构函数的this参数类型固定。由于析构函数的参数表必须为空,this参数又无类型变化,故析构函数不能重载。l 类的静态函数成员没有隐含的this指针。1818this指针class A int age;public:void setAge(int age)this-age=age;/this类型:A*const this A a;a.setAge(30);/
16、函数setAge通过对象a被调用时,setAge函数的第一个参数是 /A*const this指针,指向调用对象a。/this-age=a.age=30a的地址传给this1919class TREE int value;/保存节点值 TREE *left,*right;public:TREE(int);/TREE*const this TREE();/TREE*const this const TREE*find(int)const;/这个const是修饰this指针,/const TREE*const this;TREE:TREE(int value)/隐含参数this指向要构造的对象th
17、is-value=value;/等价于TREE:value=valueleft=right=0;/C+提倡空指针NULL用0表示const TREE*TREE:find(int v)const /this指向调用对象if(v=value)return this;/this指向找到的节点 if(vfind(v):0;/递归查左子树return right!=0?right-find(v):0;/递归查右子树 /查右子树,调用时新this=right203.6对象初始化l 若存在以下因素,必须自定义构造函数:类若定义了只读或引用类型的非静态(static)数据成员,这些成员必须通过自定义构造函数初
18、始化;若类A包含类B的非静态对象成员,而类B的构造函数参数表都有参数,则类A必须自定义构造函数(无论参数表是否有参数):以便用实参初试化类B的非静态对象成员。注意:C+缺省的无参的A()只会调用无参的B()。若子类的基类只定义了参数表有参数的构造函数,这些基类成员必须通过带参数的构造函数初始化;那么子类必须自定义构造函数(无论参数表是否有参数),否则无法传递参数给父类构造函数l 初始化位置:在构造函数的参数表之后、函数体之前,所有的基类和数据成员都应在此初始化(称为成员初始化列表),并且只能在该处初始化一次,在其函数体内的赋值不应视作初始化。2121数据成员初始化方法:n 数据成员不能在定义时
19、初始化,必须用构造函数初始化;class A int i=0;/错,不能在定义时初始化。;n 成员按照在类体内定义的先后次序初始化,与出现在初始化列表的次序无关;class A int i;int j;public:A():j(0),i(0)/还是先初始化 i,后初始化 j。按定义顺序初始化 i=1;j=1;/在函数体内不应看做初始化,而是赋值 ;2222数据成员初始化方法:n 普通数据成员没有出现在初始化列表位置时,若所属对象为全局、静态或new的对象,将具有缺省值0;class A int i;public:A();A a;/a为全局对象,a.i=02323数据成员初始化方法:只读、引用、
20、对象成员只能出现在初始化列表中;【例3-16】struct B int i;B(int x):i(x)/B只定义了带参数构造函数 ;class A /以下三种情况之一,必须自定义构造函数 const int i;/只读 int j;int&ri;/引用 B b;/对象成员,/B只有带参数的构造函数 public:A():i(0),j(0),ri(j),b(1)j=3;从这个例子可以看出:由于类 B只定义了带参数的构造函数,为了初始化对象成员b,A必须自定义构造函数。否则没有机会传递参数给B的构造函数。同样的道理,如果A有基类,而且基类只定义了带参数的构造函数,我们必须定义A的构造函数。2424
21、数据成员初始化方法:基类和非静态对象成员没有出现在初始化列表位置时,此时必然调用其无参构造函数对其初始化(因为无法传递参数);如果类仅包含公有成员且没有定义构造函数,则可以采用同C兼容的初始化方式,即:使用花括号初始化数据成员;用new分配的对象数组必须调用无参构造函数初始化。2525struct A int a,b;w=1,2;/全部为公有成员,未定义构造函数class B int c;public:B(int x)c=x;/c可以不出现在初始化列表里 /最好是B(int x):c(x)class C const int d;/只读 B e;/e为对象成员,类B构造函数参数表有参数,类C须定
22、义构造函数public:C():d(0),e(0);/无参构造函数 C(int x):e(x),d(x);/重载构造函数按定义顺序d、e初始化 C(int x,int y):d(x),e(y);/只读、引用、对象成员如d、e不能在 中赋值;void main(void)B b1(8),b2=9,b3=(2,3);C c16=3,(4,5),C(6),C(7,8),C();C*qc=new C(3),*rc=new C4;/对象数组为其元素调用C()共4次 delete qc;delete rc;/对象数组用delete 析构4次、释放1次对象数组/等号右边只能一个值,等于B b1(8),b2(
23、9),b3(3)/元素C(3),C(5),C(6),C(7,8),C(),C()2626为构造函数指定实参的方式l 为构造函数指定实参有三种形式:class A;/假设A的构造函数参数类型为char*A a1(“obejct1 of A”);A a2=A(“obejct1 of A”);A a3=“obejct3 of A”;/只能被用于单个实参的情形l 假设class B有一个无参构造函数B(),我们可以写B b1;B b2=B();B b3();/这样对吗?27判断一个类是否需要自定义构造函数l 一个准则:是否需要传递参数 如果有只读成员,引用成员:必须在成员初始化列表里初始化,我们必须自
24、定义构造函数,否则无法初始化这两种成员 如果有对象成员(注意不是对象指针):必须在成员初始化列表初始化。如果对象成员的初始化必须带参数,我们当然必须自定义构造函数,否则我们是没有机会传递参数的。(如果对象成员的初始化可以不带参数,那我们可以不自定义构造函数,因为我们不需要传递参数,系统会自动地初始化)。如果基类的构造也必须传递参数,道理同上,我们也必须自定义构造函数。l 自定义的构造函数是否必须带参数?不是必须,因为我们仅仅需要一个传递参数的机会。2828什么叫无参构造函数(缺省构造函数)l 无参(缺省构造函数)是指不需要用户指定实参就能被调用的构造函数。这并不意味着它不能接受实参。下面每一个
25、都是无参(缺省)构造函数。A:A()A:(int i=0)A:(int i=0,int j=0)l 当一个类没有定义构造函数时,编译器会为你提供一个缺省构造函数l 当你自己定义了构造函数,编译器不再为你提供缺省构造函数l 因此,当你自定义了一个带参数的构造函数时,你最好再加上一个无参构造函数。为什么?29293.7类的存储空间存储空间:主要与编译程序有关,也与机器字长有关。如果类有基类、虚基类和虚函数,则更复杂。对齐方式:编译时按紧凑和松散方式存数据成员。紧凑方式,数据成员之间没有空闲字节,程序占用的内存较少,但跨边界成员的访问时间较长。若成员X的起始地址不能被sizeof(X)整除,则称X为
26、跨边界的。松散方式,数据成员之间因边界对齐填补空白字节,程序占用的空间较多,但对齐后不跨边界访问,访问时间较短。Microsoft用紧凑方式,Borland用松散方式。可设编译开关。如未作特别声明,则类对象的存储空间都按紧凑方式计算。3030#include struct MESSAGEchar flag;/消息类别标志int size;/消息长度char buff255;/消息缓冲区long sum;/消息累加和;void main(void)coutSize of single word is sizeof(int);/假定sizeof(int)=2 coutSize of double
27、word is sizeof(long);/假定sizeof(long)=4coutnSize of Message is:sizeof(Message);3131l 紧凑方式:一个成员紧接着前一个成员存放,则:sizeof(MESSAGE)=1+2+255+4=262。l 松散方式:成员不跨边界存放,即成员开始地址必须能被size(成员类型)除尽,对数组则考虑其元素类型。假定MESSAGE的开始地址为0,则:存放flag后填补1字节,使size地址被sizeof(int)除尽 存放size后不填字节,buff地址能被sizeof(char)除尽 存放buff后地址用到259,填补1字节使sum地址能被sizeof(long)除尽。故sizeof(MESSAGE)=1+1+2+255+1+4=26432l巩固练习:看懂讲义指定的所有例题 课后作业【3.3】【3.6】上机实践,自定义main函数测试结果