《C++课件第7 章类与对象.pdf》由会员分享,可在线阅读,更多相关《C++课件第7 章类与对象.pdf(49页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、第 7 章类与对象7.1 类与对象的定义与使用一、类和对象的定义类可以理解为用户定义的数据类型,而“int”和“double”是系统定义的简单数据类型。变量是简单数据类型的实例,对象是类的实例。一个对象由几个简单数据类型的变量,和几个函数,封装在一起组成。把数据和函数封装在一起组成对象,是面向对象技术的基本特征。用成员访问符访问对象中的成员。也可以用指向对象的指针访问,此时”(*p).a”等效于例:复数类class complex(private:复数的实部和虚部double real;double image;public:void set(double r,double i)共有函数成员,
2、设定复数值(real=r;image=i;)void dislayO 输出复数值(coutreal,+M im ag e,i,endl;);void main()(complex x,y,*p;x.set(l,3);调用对象x 的成员函数,设定复数值y.set(3,5);x.dislayO;y.dislayO;p=&x;(*p).set(6,6);p-dislay();coutx.real;错误,私有成员只能被complex类的成员函数访问,不能被main函数访问注意:1.定义类时,用关键字“publicprivate”protected”来表示各数据成员和函数成员的访问权限。公有权限的成员,
3、在程序的任何位置都可以被访问:私有和保护权限的成员,只有本类(不是本对象)的成员函数可以访问。2.用关键字“class”和“struct”都可以定义类,区别在于,不指定访问权限的情况下,“struct”默认公有,包括公有成员和公有继承;“class”默认私有,包括私有成员和私有继承。3.类的定义只是说明了 组对象的共同特征,所以类定义中说明的数据成员并不是具体的变量,没有分配内存空间,也不能赋初值。定义对象的时候,才会把类实例化,对象的数据成员有对应的内存空间。4.类定义结尾有分号二、定义对象的其他方法I.定义类的同时定义对象class complex(private:复数的实部和虚部doub
4、le real;double image;public:void set(double r,double i)共有函数成员,设定复数值(real=r;image=i;)void dislay()输出复数值(coutreal+im ageiendl;)x,y;定义类以后立刻定义了对象,因为类定义是放在全局位置,这里的“x”和“尸两个对象也是全局变量。2.不出现类名定义对象class(private:复数的实部和虚部double real;double image;public:void set(double r,double i)共有函数成员,设定复数值real=r;image=i;void d
5、islayO 输出复数值coutreal,+im age,i,endl;x,y;因为类没有名字,以后不能再定义该类的对象,这个类只有“x”和“y”两个对象。三、定义类时,可以把成员函数的实现代码放到类外class complex(private:double real;double image;public:void set(double r,double i);void dislayO;);void complex:set(double redouble i)(real=r;image=i;)void complex:dislay()(coutreal+im ageiendl;)注:L:“是
6、作用域操作符,函数名前加“complex:表示“set”和“dislay”函数是类“complex”的成员函数。2.当类的成员函数实现代码写在类内部时,这个函数被默认定义成内联函数。实现代码写在类外部的函数,要想定义成内联函数,需加关键字“inline”。7.2 类的特殊成员一、this指针在对象调用自己的非静态成员函数时,会自动传给成员函数一个指针参数“this”,这个指针指向该对象自己。该参数是隐含的不可见,在被调用的成员函数内,出现的所有本类成员前都隐含的加上“this-”,也可以显式使用this指针。例:class complexprivate:double real;double i
7、mage;public:void set(double r,double i)(this-real=r;this-image=i;)void display()(coutreal,+,im ag e,iendl;);void main()(complex x,y;x.set(l,3);x.displayO;y.set(3,5);y.display();)注:“set”函数把this指针显式写出了,和原先效果-样。this指针使得对象能够访问自己的成员。二、静态成员类的静态成员属于类本身,而不属于具体的某个对象,所以一个类静态成员只有一个数据拷贝。类的静态数据成员是在类定义语句被执行时即创建,所
8、以是静态生存期,与对象的创建和注销无关。定义方法:在类成员定义中加关键字“static”,然后在类外为静态数据成员分配内存空间并初始化。调用方法:内部,任何成员函数都可以直接调用;外部,用任何一个该类成员调用,或者用类 名 加“直接调用。例:class complex(private:double real;double image;public:static int sum;void set(double r,double i)real=r;image=i;sum+;用静态成员统计set函数的调用次数)void displayO(coutreal+im ageiendl;);int comp
9、lex:sum=0;void main()(complex x,y;x.set(l,3);y.set(3,5);coutx.sum;coutcomplex:sum;)注:也可以定义静态函数成员。静态成员函数的特点是,不会自动传递this指针,所以静态成员函数一般专用于处理静态数据成员。三、友元在类中可以指定另个类或别的函数为自己的友元。友元和类内部成员函数的访问权限相同,即友元可以访问类的所有成员。例:class complex(friend void set(double r,double i,complex*p);声明了一个友元函数friend class c;声明了类c 是类comple
10、x的友元friend void b:fl();声明了类b 的成员函数f l 是友元函数private:double real;double image;public:void displayO(coutreal+im ageninendl;);void set(double r,double i,complex*p)(p-real=r;p-image=i;void main()(complex x;set(l,3,&x);x.display();)注意:1.友元函数并不是类的一个成员,而是单独的一个外部函数。友元函数要访问类的成员,需要利用对象指针等参数,指定要访问的是哪个对象的成员。2.如果
11、指定类b 是 类 a 的友元类,则类b 中的所有成员函数都可以任意访问类a 的成员。但是反过来类a 并不是类b 的友元,不能任意访问类b 的成员。四、常量成员和对象成员类中的数据成员是常量或者另个类的对象的情况。例:class B(int s;);class A(B x;const int y;);注意:定义类的时候不能给常量成员指定初值,常量成员是属于对象的,每个对象的同名常量成员可以取不同的值。五、构造函数1.因为对象定义后在外部无法访问私有的成员,所以用个特殊的成员函数,构造函数给对象初始化。当系统新定义一个对象时.,会自动调用构造函数。构造函数名字与类名相同,没有返回值类型,可以重载,
12、应设置为公有权限。如果没有定义任何构造函数,系统会自动产生一个默认构造函数,这个默认函数没有参数并且什么也不做。如果自定义了构造函数,系统就不会自动产生默认构造函数,注意这时不带参数定义对象可能就会出错。例:class complex(private:double real;double image;public:complex(double r,double i)构造函数(real=r;image=i;)void set(double r,double i)(real=r;image=i;)void display()(coutreal,+,im ag e,iendl;);void main
13、()(complex x(l,3),y(3,4);通过构造函数初始化complex z;错误,此时已经没有自动生成的无参构造函数了x.displayO;y.set(3,5);y.displayO;)注:构造函数如果要求参数,定义对象时必须用括号加上初始化参数。构造函数不要求参数,定义时对象名后不加括号。构造函数的参数可以带上默认值。例:class complex(private:double real;double image;public:complex(double r=0,double i=0)构造函数(real=r;image=i;);void main()(complex x;构造函
14、数默认参数,复数为0complex y(3,4);2.冒号初始化列表C+允许构造函数在冒号后加成员初始化列表,列表中用括号给出初始化参数。初始化列表中列出的数据成员初始化的顺序,按照类的声明中这些成员出现的顺序进行,不是按照初始化列表中出现的顺序。一般初始化列表中的顺序应和声明的顺序一致。类的对象成员,类的常量成员,对象继承,引用型的参数,这些情况下都必须使用初始化列表。对于有构造函数的数据成员,用初始化列表效率较高。例:class complexprivate:double real;double image;public:complex(double r=0,double i=O):rea
15、l(r),image(i);void main()(complex x;complex y(3,4);例:class clpublic:cl(int i=0):a(i)int a;);class c2public:c2(int i=O):t(5),a(b+l),b(i)cl t;const int b;int a;);void main()(c2 x,y(3);coutx.ay.a;)3.构造函数中开辟动态空间在类中有一个指针成员,这个指针用来挂载动态申请的存储空间。例:class person(private:char*name;int age;public:person(int a,cha
16、r*p=nonamen);void display()(coutname:n n a m e u age:ageendl;);person:person(int a,char*p):age(a)(name=new charstrlen(p)+l;strcpy(name,p);)void main()(char t20;int a;person x(18,tom);cout请输入名字”;cint;coutvv请输入年龄”;cina;person y(a,t);x.displayO;y.display();六、析构函数析构函数用来在对象注销之前执行一些操作,特别是释放动态开辟的内存空间等操作。析
17、构 函 数 名 字 是 类 名 前 加 没 有 返 回 值 类 型,一个类只有一个析构函数,不可以重载,没有参数,应设置为公有权限。例:class personprivate:char*name;ini age;public:person(int a,char*p=nnonamen):age(a)(name=new charstrlen(p)+1;strcpy(name,p);)person()(delete name;)void display()(coutuname:n am en age:uageendl;七、转换构造函数1.如果构造函数可以只带一个参数,这个构造函数就是隐式定义的类型转
18、换构造函数,可以把这个参数的数据类型转换为该类得类型。定义过转换构造函数后,系统会在必要时自动调用进行隐式类型转换,包括赋值、表达式计算、参数传递等。例:重载复数类的构造函数class complex(private:double real;double image;public:void set(double r,double i)(real=r;image=i;)void displayO(coutreal,+,iTnage,i,endl;complex(double redouble i)real=r;image=i;)complex(double x):real(x),image(x)
19、转换构造函数);void main()(complex x(2,3);complex y(4);y=6;x.displayO;y.display();2.显示定义的转换函数,用 operator加类型名,重载成成员函数,无返回值类型。可以把对象转换成其他类型。例:class complex(private:double real;double image;public:void set(double redouble i)(real=r;image=i;)void displayO(coutreal+,im ag e,iendl;)complex(double r,double i):real
20、(r),image(i)operator double。转换函数(double t;t=real+image;return t;);void main()complex x(2,3);double a;a=x;coutaendl;x.displayO;八、拷贝构造函数1.根据现有对象,创建一个相同的新对象时,需耍用到拷贝构造函数。例:class complex(private:double real;double image;public:void set(double r,double i)(real=r;image=i;void displayO(coutreal+im ageiendl;
21、)complex(double r,double i)real=r;image=i;)complex(const complex&t)拷贝构造函数,形参是一个只读的引用(real=t.real;image=t.image;);void main()(complex x(2,3);complex y(x);使用拷贝构造函数y.display();除了在定义对象时使用拷贝构造函数,当函数参数是对象时,实参对象传给形参会调用拷贝构造函数;当函数返回值是一个对象时,会利用拷贝构造函数产生一个临时对象表示函数返回值。这也是为什么拷贝构造函数的参数是对象的引用。由于对象的创建和注销都耗费系统资源,一般函数
22、参数和函数返回值都用对象的引用。为了保证函数调用的成功,如果没有自定义拷贝构造函数,系统会创建一个默认拷贝构造函数,它的功能是复制参数对象的所有数据成员到新建对象。2.深拷贝和浅拷贝当对象中含有指针和动态开辟的内存空间时,可以看到简单的数据复制不能正确的拷贝对象。例:class person(private:char*name;int age;public:person(int a,char*p=,nonameM);person(const person&t);void displayO(cout,nam e:nam e,age:ageendl;void setname(char*p)(del
23、ete name;name=new charstrlen(p)+l;strcpy(name,p);)person()(delete name;);person:person(int a,char*p):age(a)(name=new charfstrlen(p)+l;strcpy(name,p);person:person(const person&t):age(t.age)(name=new charstrlen(t.name)+1;给新对象开辟内存空间strcpy(name,t.name);)void main()person x(18,ntomn);person y(x);y.setna
24、me(nmikeM);x.displayO;y.display();)简单的数据复制称为浅拷贝,带有动态申请空间和指针的数据复制称为深拷贝。系统自动生成的拷贝构造函数只能实现浅拷贝。九、成员函数重载例:person类重载set函数class person(private:char*name;int age;public:person(int a,char*p=Hnoname);person(const person&t);person()(deleted name;)void display()(coutname:n n a m e u age:ageendl;)void set(char*p
25、)参数是字符串,改名字(delete name;name=new charstrlen(p)+l;strcpy(name,p);)void set(int a)参数是数字,改年龄age=a;void set(int a,char*p)改名字年龄set(p);set(a););person:person(int a,char*p):age(a)(name=new charstrlen(p)+l;strcpy(name,p);)person:person(const person&t):age(t.age)(name=new charstrlen(t.name)+1 ;strcpy(name,t.
26、name);)void main()(person x(18,utom);person y(x);y.set(m 汰 e);x.set(20/abcn);x.display();y.displayO;)十、运算符重载C+中的运算符能够以函数重载的方式重新定义,以扩充运算符的功能。运算符的函数形式是运算符前加“operator、运算符可以被重载为类的成员函数或者友元函数。例:复数类加法class complex(private:double real;double image;public:void display()(coutreal,+,im agei,endl;)complex(doubl
27、e r=0,double i=O):real(r),image(i)complex operator+(complex&a)重载加法运算(complex t;t.real=real+a.real;t.i mage=i mage+a.image;return t;void main()complex x(2,3),y(4,5),z;z=x+y;z.displayO;)因为把加法运算定义为类的成员函数,所以只带了 个参数表示两个加数中的一个,另 个加数是调用这个成员函数的对象。参数用引用省去了创建和注销对象的过程。例:重载成友元class complex(friend complex operat
28、or+(complex&a,complex&b);private:double real;double image;public:void displayO(coutreal+,im ag e,i,endl;)complex(double r=0,double i=O):real(r),image(i);complex operator+(complex&a,complex&b)重载力口法运算(complex t;t.real=a.real+b.real;t.image=a.image+b.image;return t;)void main()(complex x(2,3),y(4,5),z;
29、z=x+y;z.display();)运算符重载有一定限制,不是所有运算法都可以重载成友元,也不是所有运算符都可以重载,运算符重载也不能改变结合性和优先级,参数个数等。十一、使用构造函数的其他格式1.当构造函数的参数只有一个时.,用“。”和用=”等效,调用的都是构造函数。例:class complex(private:double real;double image;public:void display()(coutreal+im ageiendl;)complex(double r)(real=r;image=r;)complex(const complex&t)real=t.real;i
30、mage=t.image;);void main()(complex x=2;complex y=x;y.display();)2.构造函数也可以直接调用,会产生一个临时对象作为返回值。例:class complex(private:double real;double image;public:void display()(coutreal+,im ag e,i,endl;)complex(double r,double i)(real=r;image=i;)complex(const complex&t)real=t.real;image=t.image;void main()(compl
31、ex x(l,2);x=complex(4,5);/除了调用构造函数,还调用了赋值运算符x.displayO;)例:class complex(private:double real;double image;public:void display()(coutreal+,im ag e,i,endl;)complex(double r=0,double i=O):real(r),image(i)complex operator+(complex&a)重载加法运算(return complex(real+a.real,image+a.image););void main()(complex x
32、(2,3),y(4,5),z;z=x+y;z.displayO;I十二、典型运算符重载1.赋值运算符赋值运算只能被重载为成员函数。赋值运算符重载不能被继承。如果不定义赋值运算符重载,系统在需要时会自动生成一个,效果是实现对象数据成员的浅拷贝。例:class person(private:char*name;int age;public:person(int a=20,char*p=noname);void displayOcoutuname:n n a m e u age:uageendl;)-person()(delete name;)person&operator=(person&t)if
33、(this=&t)return*this;age=t.age;if(name)(deletename;name=NULL;if(t.name)(name=new charstrlen(t.name)+1 ;strcpy(name,t.name);)return*this;);person:person(int a,char*p):age(a)(name=new charstrlen(p)+l;strcpy(name,p);)void main()(person x(18,tom);person y;y=x;x.displayO;y.display();)注意:赋值运算符和拷贝构造函数是两个不同
34、的函数,赋值运算符一定要考虑赋值号两边是同一个对象的情况,还要清理左边的对象。赋值运算符的返回值是一个对象引用,即被赋值的对象本身。2.自增运算符重载因为自增运算分前缀后缀两种情况,重载时,后缀运算视为双目运算,第二个参数是int型的 0前缀+的返回值是左值,后缀+的返回值是右值。例:复数类class complex(private:double real;double image;public:void set(double redouble i)(real=r;image=i;)void displayO(coutreal+,im ag e,i,endl;)complex(double r
35、=0,double i=O):real(r),image(i)complex&operator+()前缀+(real+;image+;return%his;)complex operator+(int)后缀+(complex t=*this;用到了拷贝构造函数real+;image+;return t;);void main()(complex x(2,3),y(l,l);x=y+;x.display();y.display();+y;y.displayO;例:重载为友元函数class complex(friend complex&operator+(complex&a);friend com
36、plex operator+(complex&a,int);private:double real;double image;public:void set(double redouble i)(real=r;image=i;)void displayO(coutreal,+,im ag e,iendl;)complex(double r=0,double i=O):real(r),image(i);complex&operator+(complex&a)前缀+(a.real+;a.image+;return a;)complex operator+(complex&a,int)后缀+(com
37、plex t=a;a.real+;a.image+;return t;Ivoid main()(complex x(2,3),y(l,l);x=y+;x.display();y.display();+y;y.display();重载为友元就多了一个参数。第八章继承与派生8.1 继承概念一、继承概念和派生类定义继承就是在已有的类基础匕添加新成员,构成一个新的类,新类中的成员包括新加成员和继承过来的成员。被继承的类称为基类、父类,新类称为派生类、子类。定义派生类时,用冒号表示基类,用 public、private、protected表示公有、私有、保护三种继承方式。不写继承方式时,用 class定
38、义的派生类默认私有继承,用 struct定义的派生类默认公有继承。例:#include using namespace std;class circle(public:double r;double area()return r*r*3.14159;void set(double a)r=a;);class cylinder:public circle(public:double h;double volume()(return r*r*3.14159*h;)void setc(double a,double b)(r=a;h=b;);void main()circle x;x.set(3);
39、coutx.area()endl;cylinder y;y.setc(3,5);couty.volume()endl;couty.area()endl;圆柱类公有继承自圆类,圆柱类拥有圆类的所有成员,外加自己新定义的成员。二、类成员的访问属性1.类成员访问属性分为四种定义类成员时,public protected private表示公有、保护、私有三种访问权限。类内成员函数可以访问本类的私有、保护、公有成员,类外函数通过对象只能访问公有成员。派生类新加成员函数不可以访问原基类的私有成员,但是从基类继承的成员函数可以。类成员访问属性分为四种:公有、保护、私有、隐 藏(基类私有成员)。2.继承方式
40、对成员访问属性的影响不论采用何种继承方式,基类私有成员到了派生类中一定变成了隐藏属性,以下讨论基类的公有和保护成员。公有继承:基类成员的访问属性不变。保护继承:基类成员访问属性全部变成保护。私有继承:基类成员访问属性全部变成私有。注意,派生类中的隐藏成员并不是不存在,隐藏成员是通过基类的公有成员函数这个接口来访问的。#include using namespace std;class Mprivate:int c;protected:int b;public:int a;void set(int x,int y,int z)b=y;void displayOc o u t a ,b ,c en
41、 d l;class N:public M(public:int d;);void main()(Nx;x.set(l,2,3);x.d=4;x.displayO;coutx.ax.dendl;)建议:;在设计类时尽量用私有成员,私有成员对派生类是隐藏的,派生类中只考虑基类的接口函数即可;继承方式尽量用公有,这样不会改变基类成员的访问属性。三、多继承和多重继承如果派生类从多个基类继承而来,叫做多继承。如果类A 派生出类B,类 B 又派生出类C,叫做多重继承。继承必须单向,不允许构成环形结构。四、基类成员二义性处理如果派生类新定义的成员,和从基类继承过来的可访问成员名字重复,这时默认访问派生类新
42、定义的成员。如果是多重继承,几个基类中有重名成员,优先访问继承树中距离近的成员。如果要访问指定的某个基类成员,在成员名前加上定义它的基类名和:“。在派生类中定义基类的同名成员,不会形成函数重载,而是把基类成员重新定义和修正,使之适应派生出来的新类。重载是overload,意为超载。重定义是覆盖override,重定义的成员一般要求参数表与老版本保持一致。例:#include using namespace std;class circle(private:double r;public:double area()return r*r*3.14159;double circumference()
43、(return 2*r*3.14159;)void set(double a)(r=a;);class cylinder:public circle(private:double h;public:double volume()(return circle:area()*h;)double area()(return circle:area()*2+circumference()*h;)void set(double a,double b)(circle:set(a);h=b;);void main()(circle x;x.set(3);coutx.area()endl;cylinder y
44、;y.set(3,5);couty.volume()endl;couty.area()endl;couty.circle:area()endl;例:#includeusing namespace std;struct A struct定义的类默认公有成员,默认公有继承int a;struct B:Aint b;struct C:Bint a;void main()(Ct;t.A:a=l;coutt.B:a;t.B:a=5;coutt.A:a;)运算结果:15注意:只要不产生二义性,这里用类A 的派生类类B 调用成员a 也可以。调用被覆盖了的基类成员,最好用该成员第一次定义所在类的类名,即用A
45、来调用。五、特殊成员的继承特性1.静态成员基类的静态成员可以被继承,基类和派生类将共享同一个静态成员。2.友元基类的友元并不会成为派生类的友元,但是基类成员如果是别的类的友元,派生后仍保留友元权限。六、派生类的构造和析构构造函数和析构函数都不能继承。创建对象时,通过初始化列表,派生类调用基类的构造函数完成初始化。初始化列表只是初始化参数的分配表,它的顺序和实际初始化的顺序无关。系统先自动调用基类构造函数初始化基类,再初始化派生类的新成员。如果初始化列表中没有基类构造函数,系统仍然会自动调用基类构造函数,但是不给参数。注销对象时,系统在执行完派生类析构函数后自动调用基类的析构函数,基类析构函数名
46、不出现。先注销派生类新加成员,再调用基类析构函数注销基类成员。例:class A(public:int a;A(int i):a(i)coutA A()(coutA);class B:public A(public:ini b;B(int i,intj):b(j),A(i)coutBB()coutB);class C:public B(public:int c;C(int i,int j,int k):c(k),B(i,j)coutCC()coutC);void f()(C x(2,3,4);coutx.ax.bx.cendl;)void main()f();多重继承情况下,系统只是调用自己上
47、一层的父类的构造和析构函数,并不会调用祖父类的。例:class A(public:int a;A(inl i):a(i)(co u tA)A()co u t-A );class B(public:int b;B(int i):b(i)co u tB B()cout Bclass C:public B,public A(public:int c;C(int i,int j,int k):B,A,c(k)co u tC)C()(cout C;void f()c x(2,3,4);cou tx.ax.b x.cen d l;)void main()(f();)七、虚基类和虚继承1.在复杂的树形继承关
48、系中,有时会形成数据冗余。例:class Aint a;class B:Aint b;class C:A,Bintc;这种情况下,明显有两份A类拷贝,数据冗余。这种继承方式大部分C+编译器会报错。例:class Aint a;class BI:Aintx;class B2:Aint y;class C:B,Cintc;此时D类的对象中有两份A类拷贝。这时系统不会报错,可以通过B l:a和B2:a分别调用两个成员a。注意不能用A:a。例:class A(public:int a;A(int i):a(i);class Bkpublic A(public:int x;Bl(int i,intj):
49、A(i),x(j);class B2:public Apublic:int y;B2(int i,intj):A(i),y(j);class C:public Bl,public B2(public:int c;C(int i,int j,int k,int m,int n):B 1 (i j),B2(m,n),c(k);void main()(Ct(l,2,3,4,5);coutt.B 1 :at.B 2:at.xt.yt.c;)2.虚继承虚继承专门解决上述继承方式下(菱形继承)的数据冗余,虚继承将重复的基类拷贝合并成一个。加了 virtual关键字的继承方式叫做虚继承。例:class Ai
50、nt a;class B1 :virtual public Aintx;class B2:virtual public Aint y;class C:B1,B2int b;此时类B l和 B2虚继承了类A,类 A 是类B l和类B 2的虚基类,或说B l和 B2得到的类A拷贝是一个虚的基类拷贝。并不是类A 就是虚基类,如果一个类X 以一般方式继承了类A,类 A 就是类X 的“实”基类。虚的基类拷贝在多重继承树中向下传递(以一般的继承方式),不管传递多少代,仍然是虚的。类 C 从 B 1和 B2得到的类A 拷贝就仍然是一个虚的类A 拷贝。从不同父类多次继承的同一虚基类,只会产生一份类拷贝。此时父