




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第2章
C++面向对象程序设计——类和对象01类的定义类的定义C++中定义类的一般格式如下:class<类名>{ private: [<私有型数据和函数>] public: [<公有型数据和函数>] protected: [<保护型数据和函数>]};<各个成员函数的实现>其中,class是定义类的关键字,class的后面是用户定义的类名。类中的数据和函数是类的成员,分别称为数据成员和成员函数。数据成员用来描述类状态等的属性,由于数据成员常用变量来定义,所以有时又将这样的数据成员称为成员变量。成员函数是用来对数据成员进行操作,又称为方法。类的定义类中关键字public、private和protected声明了类中的成员与类外(或程序其他部分)之间的关系,称为访问权限。对于public成员来说,它们是公有的,能被外面的程序访问(后面会讨论)。对于private成员来说,它们是私有的,不能被外面的程序所访问,此时的数据成员只能由类中的函数所使用,成员函数只允许在类中调用。从类的声明格式可以看出,类的定义一般地分为声明部分和实现部分。简单地说,声明部分告诉使用者将要“做什么”,而实现部分是告诉使用者“怎么做”。格式中,“各个成员函数的实现”是类定义中的实现部分,它包含所有在类体中声明的函数的定义,即对成员函数的实现。若一个成员函数已在类体中定义,则不必也不能在实现部分再作定义。若所有的成员函数都是在类体中定义,则实现部分可以省略。类的定义需要说明的是,当类的成员函数的函数体在类的外部定义时,必须用域作用符“::”来通知编译器该函数所属的类。例如:classCStuScore{public: //公有类型声明
char strName[12]; //姓名
char strStuNO[9]; //学号
void SetScore(floats0,floats1,floats2) //成员函数:设置三门课成绩
{ fScore[0] =s0; fScore[1] =s1; fScore[2] =s2; } float GetAverage(void);private: //私有类型声明
float fScore[3]; //三门课程成绩}; //注意分号不能省略floatCStuScore::GetAverage(void){ return(fScore[0]+fScore[1]+fScore[2])/3.0f;}类的定义类CStuScore中,成员函数SetScore是在类体中定义的,而GetAverage是在类的外部定义的,注意两者的区别。另外,定义类时还应注意以下几方面。(1)类中数据成员的类型可以是任意合法的数据类型,包含整型、浮点型、字符型、数组、指针和引用等,也可以是另一个类的类型,但不允许对所定义的数据成员进行初始化,也不能指定除static之外的任何存储类型。(2)在“public:”“protected:”或“private:”后面定义的所有成员都是公有、保护或私有的,直到下一个“public:”“protected:”或“private:”出现为止。若成员前面没有任何访问权限的指定,则所定义的成员是private(私有),这是类的默认设置。事实上,结构也可看成是类的一种简单形式,只是其成员的默认访问权限是public(公有)。一般来说,当只需要描述数据结构而不想在结构中进行数据操作时,则用结构较好。而若既要描述数据又要有对数据的处理方法时,则用类为好。(3)关键字public、protected和private可以在类中出现多次,且前后的顺序没有关系。每个访问权限关键词为类成员所指定的访问权限是从该关键词开始到下一个关键词为止的。(4)在进行类设计时,通常将数据成员声明为private,而将大多数成员函数声明成public。这样,类外程序不能直接访问类的私有数据成员,从而实现了数据的封装。而公有成员函数还可为内部的私有数据成员提供外部接口,但接口实现的细节在类外又是不可见的,这就是C++类的优点之一。(5)尽量将类单独存放在一个文件中或将类的声明放在.h文件中而将成员函数的实现放在与.h文件同名的.cpp文件中。以后将会看到,在VisualC++创建的应用程序框架中都是将各个类用.h和同名的.cpp文件来组织的。02对象的定义对象的定义作为一种复杂的数据构造类型,类声明后,就可以定义该类的对象。与结构体类型一样,它也有3种定义方式:声明之后定义、声明之时定义和一次性定义。但由于“类”比任何数据类型都要复杂得多,为了提高程序的可读性,真正将“类”当成一个密闭、“封装”的盒子(接口),在程序中定义对象时应尽量采用“声明之后定义”的方式,并按下列格式进行:<类名><对象名表>;其中,类名是已定义过的类的标识符,对象名可以有一个或多个,多个时要用逗号分隔。被定义的对象既可以是一个普通对象,也可以是一个数组对象或指针对象。例如:CStuScoreone,*two,three[2];则one是CStuScore类的一个普通对象,two和three分别是该类的指针对象和对象数组。对象的定义一个对象的成员就是该对象的类所定义的数据成员(成员变量)和成员函数。访问对象的成员可按下列方式进行:<对象名>.<成员变量><对象名>.<成员函数>(<参数表>)例如:one.strName,three[0].GetAverage();若对象是一个指针,则对象的成员访问形式如下:<对象指针名>-><成员变量><对象指针名>-><成员函数>(<参数表>)“->”也是一个成员运算符,它与“.”运算符的含义相同,只是“->”用于对象指针。需要说明的是,下面的两种表示是等价的:<对象指针名>-><成员>(*<对象指针名>).<成员>例如two->GetAverage()和(*two).GetAverage()是等价的,以前说过,由于成员运算符“.”的优先级比取内容运算符“*”高,因此需要在“*two”两边加上括号。另外,对于引用对象,其成员访问形式与一般对象的成员访问形式相同。例如:CStuScore&other=one;cout<<other.GetAverage()<<endl;03类作用域和成员访问权限1.类名的作用域2.类中成员的可见性3.对象成员的可见性类作用域和成员访问权限1.类名的作用域如果在类声明时指定了类名,则类名的作用范围是从类名指定的位置开始一直到文件结尾都有效,即类名是具有文件作用域的标识符。若类的声明是放在头文件中,则类名在程序文件中的作用范围是从包含预处理指令位置处开始一直到文件结尾。需要说明的是,如果在类声明之前就需要使用该类名定义对象,则必须用下列格式在使用前提前声明(注意,类的这种形式的声明可以在相同作用域中出现多次):class<类名>;例如:classCOne; //将类COne提前声明classCOne; //可以声明多次classCTwo{ //…private: COnea; //数据成员a是一个COne类对象};classCOne{ //…};类作用域和成员访问权限2.类中成员的可见性(1)在类中使用成员时,成员声明的前后不会影响该成员在类中的使用,这是类作用域的特殊性。例如:classA{ voidf1() {
f2(); //调用类中的成员函数f2 cout<<a<<endl; //使用类中的成员变量a } voidf2(){} inta;};(2)由于类的成员函数可以在类体外定义,因而此时由“类名::”指定开始一直到函数体最后一个花括号为止的范围也是该类作用域的范围。例如:classA{ voidf1(); //…};voidA::f1(){ //…}类作用域和成员访问权限(3)在同一个类的作用域中,不管成员具有怎样的访问权限,都可在类作用域中使用,而在类作用域外却不可使用。例如:classA{public: inta; //…};a=10; //错误,不能在A作用域外直接使用类中的成员3.对象成员的可见性前面已说过,对于对象的可见性来说,由于是属于类外访问,故对象只能访问public成员,而对private和protected均不能访问。04构造函数和析构函数1.构造函数2.析构函数3.应用示例构造函数和析构函数1.构造函数C++规定,构造函数必须与相应的类同名,它可以带参数,也可以不带参数,与一般的成员函数定义相同,而且可以重载,即有多个构造函数出现。但不能指定函数返回值的类型,也不能指定为void类型。例如:classCStuScore{public: CStuScore(charstr[12]) //第一个构造函数 { strcpy(strName,str); } CStuScore(charstr[12],charstrNO[9]) //第二个构造函数 { strcpy(strName,str); strcpy(strStuNO,strNO); } char strName[12]; //姓名 char strStuNO[9]; //学号
…};构造函数和析构函数实际上,在类定义时,如果没有定义任何构造函数,则编译器自动为类生成一个不带任何参数的默认构造函数。对于CStuScore类来说,默认构造函数的形式如下:CStuScore() //默认构造函数的形式{}由于构造函数的参数只能在定义对象时指定,因此有:CStuScoreoOne("LiMing");它是自动调用第一个构造函数,使strName内容为“LiMing”。若有:CStuScoreoTwo;则编译器会给出错误的提示,因为类CStuScore中已经定义了构造函数,默认构造函数将不再显式调用,因此在类中还要给出默认构造函数的定义,这样才能对oTwo进行初始化。构造函数和析构函数2.析构函数与构造函数相对应的另一个特殊的C++成员函数是析构函数,它的功能是用来释放一个对象,在对象删除前,用它来做一些清理工作,它与构造函数的功能正好相反。析构函数也要与相应的类同名,并在名称前面加上一个“~”符号。每一个类只有唯一的一个析构函数,没有任何参数,也不返回任何值,也不能被重载。例如:classCStuScore{public:
…
~CStuScore() //析构函数 { }
…};同样,如果一个类中没有定义析构函数时,则编译系统也会为类自动生成一个默认析构函数,其格式如下(以类CStuScore为例):~CStuScore() //默认析构函数的形式{}需要说明的是,析构函数只有在下列两种情况下才会被自动调用:(1)当对象定义在一个函数体中,该函数调用结束后,析构函数被自动调用。(2)用new为对象分配动态内存后,当使用delete释放对象时,析构函数被自动调用。构造函数和析构函数3.应用示例类的构造函数和析构函数的一个典型应用是在构造函数中用new为指针成员开辟独立的动态内存空间,而在析构函数用delete释放它们。【例Ex_Name】使用构造函数和析构函数由于“CNameone(p);”调用的是B重载构造函数,从而使私有指针成员strName指向等于p的指向。而p是指向new开辟的内存空间,其内容为“DING”,一旦p指向的内存空间删除后,p的指向就变得不确定了,此时strName指向也不确定,所以此时运行结果为:葺葺葺葺?显然,输出的是一个无效的字符串。因此,为了保证类的封装性,类中的指针成员所指向的内存空间必须在类中单独开辟和释放。因此,类CName应改成下列代码(如斜体的部分):这样,程序运行后才会有正确的结果:DING05对象赋值和复制对象赋值和复制在C++中,一个类的对象的初值设定可以有多种形式。例如,对于前面的类CName来说,则可有下列对象的定义方式:CNameo1; //通过A显式默认构造函数设定初值CNameo2("DING"); //通过B重载构造函数设定初值等都是合法有效的。但是若有:o1=o2; 因为同类型的变量可以直接用“=”赋值,因而是合法的,但运行后却会出现程序终止,这是为什么呢?这是因为对于“CNameo1;”这种定义方式,编译会自动调用相应的默认构造函数,此时显式的默认构造函数使私有指针成员strName为空值;而“o1=o2;”中,C++赋值运算符的操作是将右值对象内容复制到左值对象的内存空间中,由于左值对象o1中的strName没有指向任何内存空间,因此试图将数据复制到一个不存在的内存空间中,程序必然异常终止。C++还常用下列形式的初始化来将另一个对象作为对象的初值:<类名><对象名1>(<对象名2>)例如:CNameo2("DING"); //A:通过构造函数设定初值CNameo3(o2); //B:通过指定对象设定初值对象赋值和复制B语句是将o2作为o3的初值,与o2一样,o3这种初始化形式要调用相应的构造函数,但此时找不到相匹配的构造函数,因为CName类没有任何构造函数的形参是CName类对象。事实上,CName还隐含一个特殊的默认构造函数,其原型为CName(constCName&),这种特殊的默认构造函数称为默认复制构造函数。在C++中,每一个类总有一个默认复制构造函数,其目的是保证B语句中对象初始化形式的合法性,其功能就等价于“CNameo3=o2;”。但语句“CNameo3(o2);”与语句“o1=o2;”一样,也会出现程序终止,其原因和“o1=o2;”的原因一样。但是,若有类CData:classCData{public: CData(intdata=0) { m_nData=data; } ~CData() {} intgetData() { returnm_nData; }private: int m_nData; };对象赋值和复制则下列初始化形式却都是合法有效的:CDataa(3); //通过重载构造函数设定初值CDatab(a); //通过默认复制构造函数设定初值, //等价于CDatab=a;cout<<a.getData()<<endl; //输出3cout<<b.getData()<<endl; //输出3可见,与变量一样,在C++中类对象的初始化也可以有2种方式:赋值方式和默认复制方式。这两种方式是等价的,例如,CDatab(a);和CDatab=a;是等价的。为什么CData对象的赋值和默认复制初始化是可行的,而CName对象的赋值和默认复制初始化却是不行的呢?问题就出在其数据成员的内存空间上。CName的数据成员strName是一个“char*”指针,由于其自身的内存空间是用来存放指针的地址,因而其数据的存储还需另辟一个不依附外部的独立的内存空间。而CData的数据成员m_nData自身的内存空间就是用来存储数据的,因此CData对象初始化所进行的数值拷贝是有效的。06浅复制和深复制浅复制和深复制事实上,对于数据成员有指针类型的类来说,均会出现如CName类的问题,由于默认拷贝构造函数无法解决,因此必须自己定义一个拷贝构造函数,在进行数值拷贝之前,为指针类型的数据成员另辟一个独立的内存空间。由于这种拷贝还需另辟内存空间,因而称其为深拷贝。拷贝构造函数是一种比较特殊的构造函数,除遵循构造函数的声明和实现规则外,还应按下列格式进行定义。<类名>(参数表){}可见,拷贝构造函数的格式就是带参数的构造函数。由于拷贝操作的实质是类对象空间的引用,因此C++规定,拷贝构造函数的参数个数可以为1个或多个,但左起的第1个参数必须是类的引用对象,它可以是“类名&对象”或是“const类名&对象”形式,其中“类名”是拷贝构造函数所在类的类名。也就是说,对于CName的拷贝构造函数,可有下列合法的函数原型:CName(CName&x); //x为合法的对象标识符CName(constCName&x); CName(CName&x,…); //“…”表示还有其他参数CName(constCName&x,…); 浅复制和深复制需要说明的是,一旦在类中定义了拷贝构造函数,则隐式的默认拷贝构造函数和隐式的默认构造函数就不再有效了。【例Ex_CopyCon】使用拷贝构造函数代码中,类CName定义了两个拷贝构造函数A和B,其中A称为显式的默认拷贝构造函数,B称为重载拷贝构造函数,它还带有字符指针参数,用来将新对象的数据成员字符指针strName指向一个新开辟的动态内存空间,然后将另一个对象one的内容复制到strName中,最后调用库函数strcat将字符指针参数add指向的字符串连接到strName中。程序运行结果如下:DINGDINGYOUHE07对象成员的初始化对象成员的初始化在实际应用中,一个类的数据成员除了普通数据类型变量外,还往往是其他已定义的类的对象,这样的数据成员就称为对象成员,拥有对象成员的类常称为组合类。此时,为提高对象初始化效率,增强程序的可读性,C++允许在构造函数的函数头后面跟由冒号“:”来引导的对象成员初始化列表,列表中包含类中对象成员或数据成员的初始化代码,各对象初始化之间用逗号分隔,如下列格式:<类名>::<构造函数名>(形参表):对象1(参数表),对象2(参数表),…,对象n(参数表){}对象成员初始化列表以前已讨论过,数据成员的初始化是通过构造函数来进行的。这就意味着,类的对象成员也可在类的构造函数体中进行初始化。这样一来,类的对象成员的初始化就可以有两种方式:一是在构造函数体中进行,称为函数构造方式;二是使用由冒号“:”来引导的对象成员初始化列表的形式,称为对象成员列表方式。对象成员的初始化先来看看第一种方式(函数构造方式),例如:classCPoint{public: CPoint(intx,inty) { xPos=x; yPos=y; }private: intxPos,yPos;};classCRect{public: CRect(intx1,inty1,intx2,inty2) { m_ptLT =CPoint(x1,y1); m_ptRB =CPoint(x2,y2); }private: CPointm_ptLT,m_ptRB;};intmain(){ CRectrc(10,100,80,250); return0;}对象成员的初始化虽然,类CRect中的对象成员m_ptLT和m_ptRB的初值的设定是在构造函数中完成的,但此时编译却出现“找不到匹配的CPoint默认构造函数”的编译错误。这是因为当主函数main中定义并初始化CRect对象rc时,它首先为类CRect的数据成员m_ptLT和m_ptRB作定义并分配内存空间,由于m_ptLT和m_ptRB是CPoint对象,因而编译会查找其构造函数进行初始化,此时m_ptLT和m_ptRB不带任何初值,故需要调用CPoint类的默认构造函数,而CPoint类已定义了带参数的构造函数,因此CPoint类的默认构造函数不再存在,所以会出现编译错误。但若使用第二种方式(对象成员列表方式),如下面的代码:classCPoint{//…};classCRect{public: CRect(intx1,inty1,intx2,inty2) :m_ptLT(x1,y1),m_ptRB(x2,y2) {}private: CPointm_ptLT,m_ptRB;};…对象成员的初始化当在main函数中定义了CRect对象rc时,编译首先根据类中声明的数据成员次序,为成员分配内存空间,然后从对象初始化列表中寻找其初始化代码,若查找不到,则调用相应的构造函数进行初始化,若查找到,则根据对象成员的初始化形式调用相应的构造函数进行初始化。显然,在对象成员初始化列表中由于存在m_ptLT(x1,y1)和m_ptRB(x2,y2)对象初始化代码,因此成员m_ptLT和m_ptRB构造时调用的是CPoint(int,int)形式的构造函数,而类CPoint刚好有此形式的构造函数定义,故编译能通过。可见:(1)函数构造方式实际上是将对象成员进行了两次初始化:第一次是在对象成员声明的同时自动调用默认构造函数进行的,而第二次是在构造函数体中执行的初始化代码。(2)对象成员列表方式虽是将对象成员的定义和初始化代码分作两个地方书写,但却是同时运行。对比函数构造方式可以看出,对象成员列表方式能简化对象初始化操作,提高对象初始化效率。(3)在对象成员列表方式下,成员初始化的顺序是按成员的声明次序进行的,而跟成员在对象初始化列表中的次序无关。对象成员的初始化另外,还需说明的是:(1)对象成员初始化也可在类的外部进行,但必须与构造函数的定义在一起。例如:classCRect{public: CRect(intleft,inttop,intright,intbottom); //…};CRect::CRect(intleft,inttop,intright,intbottom); :ptLT(left,top),ptRB(right,bottom), size(ptLT,ptRB){//…}(2)成员初始化列表也可用于类中普通数据成员的初始化,但要注意其初始化格式。例如:CRect(intleft,inttop,intright,intbottom) :ptLT(left,top),ptRB(right,bottom), size(ptLT,ptRB), nLength(size.nLength) //注意:不能是nLength=size.nLength{//…}第2章
C++面向对象程序设计——数据共享和成员特性01静态成员1.静态数据成员2.静态成员函数静态成员1.静态数据成员使用静态数据成员可以节省内存,因为它是所有对象所公有的。因此,对多个对象来说,静态数据成员只存储一处,供所有对象共享。静态数据成员的值是可以修改的,但它对每个对象都是一样的。与静态变量相似,静态数据成员是静态存储(static)的,但定义一个静态数据成员与一般静态变量不一样,它必须按下列两步进行:①在类中使用关键字static声明静态数据成员。在类中声明静态数据成员,仅仅是说明了静态数据成员是类中的成员这个关系,即便用该类定义对象时,该静态数据成员也不会分配内存空间。因此可以说,类中声明的静态数据成员是一种形式上的虚的数据成员。静态数据成员的实际定义是由下一步来完成的。②在类外为静态数据成员分配内存空间并初始化。类中数据成员的内存空间是在对象定义时来分配的,但静态数据成员的内存空间是为所有该类对象所共享,只能分配一次,因而不能通过定义类对象的方式来分配,必须在类的外部作实际定义才能为所有对象共享,其定义格式如下:<数据类型><类名>::<静态数据成员名>=<值>静态成员【例Ex_StaticData】静态数据成员的使用#include<iostream>usingnamespacestd;classCSum{public: CSum(inta=0,intb=0) //A { nSum+=a+b; } intgetSum() { returnnSum; } voidsetSum(intsum) { nSum=sum; }public:
staticintnSum; //声明静态数据成员};intCSum::nSum=0; //静态数据成员的实际定义和初始化intmain(){ CSumone(10,2),two; cout<<"one:sum="<<one.getSum()<<endl; cout<<"two:sum="<<two.getSum()<<endl; two.setSum(5); cout<<"one:sum="<<one.getSum()<<endl; cout<<"two:sum="<<two.getSum()<<endl; return0; }静态成员分析:(1)A中,由于使用了默认参数,因而使得默认构造函数和重载构造函数定义成一个构造函数。这种程序方法在实际应用时要小心使用。(2)程序中,类CSum中的私有数据成员nSum被声明成静态的,由于类中声明的nSum是虚的,因此它还必须在类体外进行实际定义。若不指定初值,则默认为0。(3)main函数中,对象one初始化后,nSum值变为12。对象two由于调用的是(a=0,b=0)的默认构造函数,故nSum的值没有变化,仍然是12(注意构造函数体的语句“nSum+=a+b;”中的“+=”不是“=”)。因此,main函数中前面两条输出语句的结果都是输出12。当执行“two.setSum(5);”后,nSum值被设为5。由于nSum为所有对象所共享,也就是说,nSum是所有对象的公共成员,因此对象one中的nSum的值也是5。程序运行结果如下:one:sum=12two:sum=12one:sum=5two:sum=5静态成员需要说明的是:(1)静态数据成员可看成是类中声明、类外定义的静态全局变量,因此它具有静态生存期,在程序中从实际定义时开始产生,到程序结束时消失。也就是说,静态数据成员的内存空间不会随对象的产生而分配,也不会随对象的消失而释放。当然,静态数据成员的内存空间同样不能在类的构造函数中创建或是在析构函数中释放。(2)静态数据成员是类中的成员,它的访问属性同普通数据成员一样,可以为public、private和protected。当静态数据成员为public时,则在类外对该成员的访问和引用可有两种方式,一是通过对象来引用,二是直接引用。当直接引用时,应使用下列格式:<类名>::<静态成员名>例如,上述CSum::nSum在main函数中可有下列引用:intmain(){ CSumone; one.nSum=10; //通过对象来引用 //… CSum::nSum=12; //直接引用 cout<<one.nSum<<endl; //输出12 return0;}代码中,引用公有型静态数据成员nSum的两种方式都是合法的,也是等价的。静态成员2.静态成员函数静态成员函数和静态数据成员一样,它们都属于类的静态成员,但它们都不专属于某个对象的成员,而是所有对象所共享的成员。同样,对于公有型(public)静态成员来说,除可用对象来引用外,还可通过“类名::成员”直接来引用。在类中,静态数据成员可以被成员函数引用,也可以被静态成员函数所引用。但反过来,静态成员函数却不能直接引用类中说明的非静态成员。假如,静态成员函数可以引用了类中的非静态成员,例如:classCSum{public: staticvoidChangeData(intdata) { nSum=data; //错误:引用类中的非静态成员 }public: intnSum; };则当执行语句:CSum::ChangeData(5); //
合法的静态成员引用静态成员下面来看一个示例,它是用静态成员来实现数据的插入、输出和排序操作。【例Ex_StaticMember】静态成员的使用同普通成员函数,类中的静态成员函数也可在类中声明,而在类外实现。由于类CData中所有的成员都声明成了静态的,因此在main函数直接通过“类名::成员”的形式来引用其public成员,通过静态成员函数Add在静态数组成员data中设置并添加数组,每添加一次,静态指针成员pCur的指向就向下移动一次,使其指向下一个数组元素的内存空间。静态成员函数Print和Sort分别将data数组中的已有数组输出和排序。程序运行结果如下:20,40,-50,7,13,-50,7,13,20,40,静态成员需要强调的是:(1)静态成员中的“静态(static)”与普通静态变量和静态函数中的“静态”含义是不一样的。普通静态变量中的“静态”是使用静态存储内存空间,而类中的静态数据成员的“静态”是对象数据共享的声明,并非具有实际意义的静态存储内存空间。普通静态函数中的“静态”是表示本程序文件的内部函数,而类中的静态成员函数的“静态”表示该成员函数仅能访问静态数据成员,是为所有该类对象共享的声明方式。(2)类的静态数据成员的内存开辟和释放只能通过静态成员函数来实现,而不能通过类的构造函数和析构函数来完成。C++中没有静态构造函数和静态析构函数。02友元1.友元函数2.友元成员函数3.友元类友
元1.友元函数友元函数可分为友元外部函数和友元成员函数。当一个函数f是A类的友元时,若f还是另一个类B的成员函数时,则这样的友元称为友元成员函数;若f不属于任何类的成员,则这样的友元称为友元外部函数。友元外部函数常直接简称为友元函数。友元函数在类中定义的格式如下:friend<函数类型><函数名>(形参表){…}从格式中可以看出,友元函数和类的成员函数定义格式基本一样,只是友元函数前面用一个关键字friend来修饰。由于友元函数与类的关系是一种“友好(friendship)”关系,因此友元函数不属于类中的成员函数,它是在类中声明的一个外部函数。友
元需要说明的是:(1)友元函数的定义可在类中进行,也可将友元函数在类中声明,而将其实现在类外定义。但在类外定义时,不能像成员函数那样指明它所属的类。(2)由于友元函数是一个外部函数,因此它对类中的成员访问只能通过类对象来进行,而不能直接去访问。这里的对象可以通过形参来指定,也可在友元函数中进行定义。(3)由于友元函数是类中声明的外部函数,因而它跟成员的访问权限private、protected和public没有任何关系,因此它的声明可以出现在类中的任何部分,包括在private和public部分。但为了程序的可读性,常将友元函数声明在类体的开头或是最后。(4)由于友元函数不是类的成员,因此它在调用时不能指定其所属的类,更不能通过对象来引用友元函数。(5)大多数外部函数对类中的数据操作是采用形参对象的方式,通过对象的“引用”传递,达到修改对象数据的目的。对于友元函数,也应该采用这种方式,只是友元函数还能修改对象的私有和保护型数据成员。友
元下面来举一个例子,它通过友元函数将一个点(CPoint类封装)的位置发生偏移。【例Ex_FriendFun】使用友元函数在类CPoint中,Inflate是在类中声明,在类外定义的友元函数。它有两个形参:一是引用形参对象pt,二是int形参变量nOffset。由于在友元函数中,对象的所有数据成员可以直接访问,因此可直接将形参对象pt的私有数据成员直接加上nOffset指定的偏移量。由于Inflate指定的pt对象是引用传递,因此对pt内容的修改也就是对实参对象内容的修改。程序运行结果如下:Point(10,20)Point(13,23)友
元2.友元成员函数友元成员函数在类中定义的格式如下:friend<函数类型><类名>::<函数名>(形参表){…}由于友元成员函数还是另一个类的成员函数,因此这里的类名是指它作为成员所在的类名。同成员函数一样,友元成员函数的定义既可在类中进行,也可将友元成员函数在类中声明,而将其实现在类外定义。但当在类外定义时,应像成员函数那样指明它所属的类。【例Ex_FriendMemFun】使用友元成员函数在类CRect中声明了一个友元函数Inflate,由于它还是类CPoint的成员函数,因此Inflate既可以直接访问CPoint的所有成员,也可以通过CRect类对象“friend”访问类CRect中的所有成员。由于在类CPoint中的Inflate函数的形参含有CRect对象,而此时CRect类还没有定义,因此需要在类CPoint前先作CRect类的声明,以便后面能使用CRect数据类型。程序运行结果如下:Point(10,20)Rect(0,0,100,80)Point(13,23)Rect(3,3,103,83)友
元3.友元类除一个类的成员函数可以声明成另一个类的友元外,也可以将一个类声明成另一个类的友元,称为友元类。当一个类作为另一个类的友元时,这就意味着这个类的所有成员函数都是另一个类的友元成员函数。友元类的声明比较简单,其格式如下:friendclass<类名>;下面来看一个例子。【例Ex_ClassFriend】使用友元类在类CPoint的定义中,类COther被声明成CPoint的友元类。这样,在类COther中,可通过CPoint对象pt“friend”访问类CPoint的所有成员。程序运行结果如下:Point(22,28)友
元需要说明的是:(1)友元关系反映了程序中类与类之间、外部函数和类之间、成员函数和另一个类等之间的关系,这个关系是单向的,即当在CPoint中声明COther是CPoint的友元类时,只能在COther类中通过CPoint对象访问CPoint类的所有成员,而在CPoint类中是无法访问COther类的私有和保护型成员。(2)一个类中的友元并非是该类的成员,由于“friend”关系,因而友元只能通过对象来访问声明友元所在类的成员。而静态成员是类的一个成员,它本身具有不同的访问属性,只是对于公有静态成员来说,它可以有对象访问和“类名::静态成员”两种等价的访问方式。(3)与友元函数相比,静态成员函数只是修改类的静态数据成员,而对于友元来说,由于通过对象可以修改声明友元所在类的所有数据成员,因而友元函数比静态成员函数更加危险,并且友元类扩大了这种危险。03常
类
型1.常对象2.常指针和常引用3.常成员函数4.常数据成员常
类
型1.常对象常对象是指对象常量,定义格式如下:<类名>const<对象名>定义常对象时,修饰符const可以放在类名后面,也可以放在类名前面。例如:classCOne{public: COne(inta,intb){x=a;y=b;} //…private: intx,y;};constCOnea(3,4);COneconstb(5,6);常
类
型2.常指针和常引用常指针也是使用关键字const来修饰的。但需要说明的是,const的位置不同,其含义也不同,它有3种形式。第1种形式是将const放在指针变量的类型之前,表示声明的是一个指向常量的指针。此时,在程序中不能通过指针来改变它所指向的数据值,但可以改变指针本身的值。例如:inta=1,b=2;constint*p1=&a; //声明指向int型常量的指针p1,指针地址为a的地址*p1=2; //错误,不能更改指针所指向的数据值p1=&b; //正确,指向常量的指针本身的值是可以改变的需要说明的是,用这种形式定义的常量指针,在声明时可以赋初值,也可以不赋初值。第2种形式是将const放在指针定义语句的指针名前,表示指针本身是一个常量,称为指针常量或常指针。此时,不能改变这种指针变量的值,但可以改变指针变量所指向的数据值。例如:inta=1,b=2;int*constp1=&a; //声明指向int型常量的指针p1,指针地址为a的地址int*constp2; //错误,在声明指针常量时,必须初始化*p1=2; //正确,指针所指向的数据值可以改变p1=&b; //错误,指针常量本身的值是不可改变的常
类
型第3种形式是将const在上述两个地方都加,表示声明的是一个指向常量的指针常量,指针本身的值不可改变,而且它所指向的数据的值也不能通过指针改变。例如:inta=1,b=2;constint*constpp=&a; *pp=2; //错误pp=&b; //错误需要说明的是,用第2种形式和第3种形式定义的指针常量,在声明时必须赋初值。事实上,使用const修饰符也可用来声明引用,被声明的引用为常引用,一个常引用所引用的对象是不能被更新的。其定义格式如下:const<类型说明符>&<引用名>例如:constdouble&v;在实际应用中,常指针和常引用往往用来作函数的形参,这样的参数称为常参数。常
类
型3.常成员函数使用const关键字进行声明的成员函数,称为常成员函数。只有常成员函数才有资格操作常量或常对象,一般成员函数是不能用来操作常对象的。常成员函数说明格式如下:<类型说明符><函数名>(<参数表>)const;其中,const是加在函数说明后面的类型修饰符,它是函数类型的一个组成部分,因此,在函数实现部分也要带const关键字。【例Ex_ConstFunc】常成员函数的使用程序运行结果如下:5,4使用常成员函数:20,52程序中,类COne声明了两个重载成员函数,一个带const,一个不带。语句“one.print();”调用成员函数“voidprint();”,而“two.print();”调用常成员函数“voidprint()const;”。常
类
型4.常数据成员类型修饰符const不仅可以说明成员函数,也可以说明数据成员。由于const类型对象必须被初始化,并且不能更新,因此,在类中声明了const数据成员时,只能通过成员初始化列表的方式来生成构造函数对数据成员初始化。【例Ex_ConstData】常数据成员的使用程序运行结果如下:x=100,y=10,r=10004this指针this指针this指针是一个仅能被类的非静态成员函数所能访问的特殊指针。当一个对象调用成员函数时,编译器先将对象的地址赋给this指针,然后调用成员函数。例如,当下列成员函数调用时:one.copy(two);它实际上被解释成:copy(&one,two);只不过,&one参数被隐藏了。需要说明的是,通过*this可以判断是哪个对象来调用该成员函数或通过*this重新指定对象。【例Ex_This】this指针的使用程序运行结果如下:0,03,4this指针程序中,使用this指针的函数是copy,它在copy函数中出现了2次。“if(this==&a)return;”中的this是指向操作该成员函数的对象,例如“one.copy(…);”中的this指向的是one对象。这里if语句的作用是防止对象自己赋给自己。copy函数中的语句*this=a;是将形参a的值存储到this指向的对象的内存空间中。事实上,当成员函数的形参名与该类的成员变量名同名,则必须用this指针来显式区分,例如:classCPoint{public: CPoint(intx=0,inty=0) { this->x=x; this->y=y; } voidOffset(intx,inty) { (*this).x+=x; (*this).y+=y; } voidPrint()const { cout<<"Point("<<x<<","<<y<<")"<<endl; }private: intx,y;};第2章
C++面向对象程序设计——继承和派生01单
继
承1.公有继承(public)2.私有继承(private)3.保护继承(protected)单继承从一个基类定义一个派生类可按下列格式:class<派生类名>:[<继承方式>]<基类名>{ [<派生类的成员>]};其中,继承方式有3种:public(公有)、private(私有)及protected(保护),若继承方式没有指定,则被指定为默认的public方式。单继承1.公有继承(public)公有继承的特点是基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态,而基类的私有成员仍然是私有的。例如:classCStick:publicCMeter{ int m_nStickNum; //声明一个私有数据成员public: voidDispStick(); //声明一个公有成员函数 voidSetStick(intnPos) { SetPos(nPos); //类中调用基类的保护成员 }}; //注意分号不能省略voidCStick::DispStick(){ m_nStickNum=GetPos(); //调用基类CMeter的成员函数 cout<<m_nStickNum<<’’;}单继承这时,从基类CMeter派生的CStick类除具有CMeter所有公有成员和保护成员外,还有自身的私有数据成员m_nStickNum和公有成员函数DispStick。【例Ex_PublicDerived】派生类的公有继承示例程序运行结果如下:CMeter:20,CStick:10CMeter:21,CStick:10CMeter:21,CStick:111112需要注意的是,派生类中或派生类的对象可以使用基类的公有成员(包括保护成员),例如CStick的成员函数DispStick中调用了基类CMeter的GetPos函数,oStick对象调用了基类的StepIt成员函数;但基类或基类的对象却不可以使用派生类的成员,这是继承的单向特性。单继承2.私有继承(private)私有(private)继承方式具有下列特点:(1)在派生类中,基类的公有成员、保护成员和私有成员的访问属性都将变成私有(private),且基类的私有成员在派生类中被隐藏。因此,私有继承方式下,在派生类中仍可访问基类的公有和保护成员。(2)由于基类的所有成员在派生类中都变成私有,因此基类的所有成员在派生类的子类中都是不可见的。换句话说,基类的成员在派生类的子类中已无法发挥基类的作用,实际上相当于终止基类的继续派生。(3)派生类对象只能访问派生类的公有成员,而不能访问基类的任何成员。单继承3.保护继承(protected)对于保护(protected)继承方式来说,它具有下列特点:(1)在派生类中,基类的公有成员、保护成员的访问属性都将变成保护(protected)的,同样,基类的私有成员在派生类中也是被隐藏的。(2)同私有继承一样,在保护继承方式下,派生类中仍可访问基类的公有成员和保护成员。但派生类对象只能访问派生类的公有成员,而不能访问基类的任何成员。表2.1列出三种不同的继承方式的基类特性和派生类特性。继承方式基类成员基类的成员在派生类中的特性公有继承(public)publicpublicprotectedprotectedprivate不可访问私有继承(private)publicprivateprotectedprivateprivate不可访问保护继承(protected)publicprotectedprotectedprotectedprivate不可访问02派生类的构造函数和析构函数派生类的构造函数和析构函数由于基类的构造函数和析构函数不能被派生类继承,因此,在【例Ex_PublicDerived】中,若有:CMeter oA(3); 是可以的,因为CMeter类有与之相对应的构造函数。而CStick oB(3);是错误的,因为CStick类没有对应的构造函数。但CStick oC;是可以的,因为CStick类有一个隐式的不带参数的默认构造函数。当派生类的构造函数和析构函数被执行时,基类相应的构造函数和析构函数也会被执行。因而,在上例中,CStick对象oStick在建立时还调用了基类的构造函数,使得oStick.GetPos返回的值为10。派生类的构造函数和析构函数事实上,派生类对象在建立时,先执行基类的构造函数,然后执行派生类的构造函数。但对于析构函数来说,其顺序刚好相反,先执行派生类的析构函数,而后执行基类的析构函数。需要注意的是,在对派生类进行初始化时,需要对其基类设置初值,则可按下列格式进行:<派生类名>(总参表):<基类1>(参数表1),<基类2>(参数表2),…,<基类n>(参数表n),
对象成员1(对象成员参数表1),对象成员2(对象成员参数表2),…,
对象成员n(对象成员参数表n){
…}其中,构造函数总参表“:”后面给出的是需要用参数初始化的基类名、对象成员名及各自对应的参数表,基类名和对象成员名之间的顺序可以是任意的,且对于使用默认构造函数的基类和对象成员,可以不列出基类名和对象成员名。这里所说的对象成员是指在派生类中新声明的数据成员,它属于另外一个类的对象。对象成员必须在初始化列表中进行初始化。派生类的构造函数和析构函数例如,在【例Ex_PublicDerived】中,CStick的构造函数可这样定义:classCStick:publicCMeter{ int m_nStickNum;public: CStick():CMeter(30) { } voidDispStick(); voidSetStick(intnPos) { SetPos(nPos); }};此时再重新运行程序,结果就会变为:CMeter:20,CStick:30CMeter:21,CStick:30CMeter:21,CStick:31313203多
继
承多
继
承前面所讨论的是单继承的基类和派生类之间的关系,实际在类的继承中,还允许一个派生类继承多个基类,这种多继承的方式可使派生类具有多个基类的特性,大大提高了程序代码的可重用性。多继承下派生类的定义格式如下:class<派生类名>:[<继承方式1>]<基类名1>,[<继承方式2>]<基类名2>,...{ [<派生类的成员>]};其中的继承方式还是前面提到的3种:public、private和protected。例如:classA{ //…};classB{ //…};classC:publicA,privateB{ //…};由于派生类C继承了基类A和B,具有多继承性,因此派生类C的成员包含了基类A中成员和B中成员以及该类本身的成员。04虚
基
类虚
基
类一般说来,在派生类中对基类成员的访问应该是唯一的。但是,由于在多继承情况下,可能造成对基类中某成员的访问出现不唯一的情况,这种情况称为基类成员调用的二义性。【例Ex_Conflict】基类成员调用的二义性程序中,派生类B1和B2都从基类A继承,这时在派生类C中就有两个基类A的拷贝。当编译器编译到“cout<<"x="<<x<<endl;”语句时,因无法确定成员x是从类B1中继承来的,还是从类B2继承来的,产生了二义性,从而出现编译错误。解决这个问题的方法之一是使用作用域运算符“
::”来消除二义性,例如若将print函数实现代码变为:voidprint(){ cout<<"B1::x="<<B1::x<<endl; cout<<"B2::x="<<B2::x<<endl; cout<<"y1="<<y1<<",y2="<<y2<<endl; cout<<"z="<<z<<endl;}重新运行,结果为:B1::x=200B2::x=400y1=100,y2=300z=500虚
基
类实际上,还有另一种更好的方法,即使用虚基类(或称为虚继承)。使用虚基类的目的是在多重派生的过程中,使公有的基类在派生类中只有一个拷贝,从而解决上述这种二义性问题。【例Ex_VirtualBase】基类成员调用的二义性程序运行结果如下:B1:x=0,y1=100B2:x=0,y2=300z=500B1:x=400,y1=100B2:x=400,y2=300z=500从程序中可以看出:(1)声明一个虚基类的格式如下:virtual<继承方式><基类名>(2)在派生类B1和B2中只有基类A的一个拷贝,当改变成员x的值时,由基类B1和B2中的成员函数输出的成员x的值是相同的。(3)虚基类的构造函数的调用方法与一般基类的构造函数的调用方法是不同的。C++规定,由虚基类经过一次或多次派生出来的派生类,在其每一个派生类的构造函数的成员初始化列表中必须给出对虚基类的构造函数的调用,如果未列出,则调用虚基类的默认构造函数。在这种情况下,虚基类的定义中必须要有默认的构造函数。虚
基
类程序中,类C的构造函数尽管分别调用了其基类B1和B2的构造函数,由于虚基类A在类C中只有一个拷贝,所以编译器无法确定应该由类B1的构造函数还是由类B2的构造函数来调用基类A的构造函数。在这种情况下,C++规定,在执行类B1和B2的构造函数都不调用虚基类A的构造函数,而是在类C的构造函数中直接调用虚基类A的默认构造函数。这样也就说明了运行结果中成员x为什么初始值为“0”了。若将A的构造函数改为:A(inta=100){x=a;}则成员x的初始值为100。当然,不能仅变成:A(inta){x=a;}因为类A中没有定义默认构造函数,因此会出现编译错误。与“A(inta=100){x=a;}”构造函数等价的是:A(inta){x=a;}A():x(100){} //添加默认构造函数的定义第2章
C++面向对象程序设计——多态和虚函数01多态概述多态概述在C++中,多态性可分为两种:编译时的多态性和运行时的多态性。编译时的多态性是通过函数的重载或运算符的重载来实现的。而运行时的多态性是通过虚函数来实现的,它指在程序执行之前,根据函数和参数还无法确定应该调用哪一个函数,必须在程序的执行过程中,根据具体的执行情况动态地确定。与这两种多态性方式相对应的是两种编译方式:静态联编和动态联编。所谓联编(binding,又称绑定),就是将一个标识符和一个存储地址联系在一起的过程,或是一个源程序经过编译、连接,最后生成可执行代码的过程。静态联编是指在编译阶段完成的联编,由于其联编过程是在程序运行前完成的,所以又称为早期联编。动态联编是指在程序运行时动态进行的联编,又称晚期联编。02虚
函
数虚
函
数先来看一个虚函数应用实例。【例Ex_VirtualFunc】虚函数的使用程序运行结果如下:代码中,虚函数area是通过在基类的area函数的前面加上virtual关键字来实现的。程序中*s[2]是定义的基类CShape指针,语句“s[0]=newCTriangle(3,4);”是将s[0]指向派生类CTriangle,因而“s[0]->area();”实际上是调用CTriangle类的area成员函数,结果是6;同样可以分析s[1]->area()的结果。需要说明的是:(1)虚函数在重新定义时参数的个数和类型必须和基类中的虚函数完全匹配,这一点和函数重载完全不同。(2)只有通过基类指针才能实现虚函数的多态性,若虚函数的调用是通过普通方式来进行的,则不能实现其多态性。例如:678.5397CShapess;cout<<ss.area()<<endl;输出的结果为0.0。虚
函
数(3)如果不使用new来创建相应的派生类对象指针,也可使用通过&运算符来获取对象的地址。例如:voidmain(){ CShape*p1,*p2; CTriangletri(3,4); CCirclecir(5); p1=&tri; p2=○ cout<<p1->area()<<endl; cout<<p2->area()<<endl;}(4)虚函数必须是类的一个成员函数,不能是友元函数,也不能是静态的成员函数。(5)可把析构函数定义为虚函数,但不能将构造函数定义为虚函数。03纯虚函数和抽象类纯虚函数和抽象类在定义一个基类时,有时会遇到这样的情况:无法定义基类中虚函数的具体实现,其实现完全依赖于其不同的派生类。例如,一个“形状类”(基类)由于没有确定的具体形状,因此其计算面积的函数也就无法实现。这时可将基类中的虚函数声明为纯虚函数。声明纯虚函数的一般格式为:virtual<函数类型><函数名>(<形参表>)=0;显然,它与一般虚函数不同的是:在纯虚函数的形参表后面多了个“=0”。把函数名赋于0,本质上是将指向函数的指针的初值赋为0。需要说明的是,纯虚函数不能有具体的实现代码。抽象类是指至少包含一个纯虚函数的特殊的类。它本身不能被实例化,也就是说不能声明一个抽象类的对象。必须通过继承得到派生类后,在派生类中定义了纯虚函数的具体实现代码,才能获得一个派生类的对象。【例Ex_PureVirtualFunc】纯虚函数和抽象类的使用程序运行结果如下:6678.539778.5397第2章
C++面向对象程序设计——运算符重载01运算符重载函数运算符重载函数在类中,定义一个运算符重载函数与定义一般成员函数相类似,只不过函数名必须以operator开头,其一般形式如下:<函数类型><类名>::operator<重载的运算符>(<形参表>){…} //函数体由于运算符重载函数的函数是以特殊的关键字开始的,因而编译器很容易与其他函数名区分开来。这里先来看一个实例,它是用来定义一个复数类CComplex,然后重载“+”运算符,使这个运算符能直接完成复数的加运算。【例Ex_Complex】运算符的简单重载程序运行结果如下:实部=62,虚部=90实部=32,虚部=20运算符重载函数分析:(1)程序中,对运算符“+”做了两个重载,一个用于实现两个复数的加法,另一个用于实现一个复数与一个实数的加法。(2)尽管在形式上重载后的运算符的使用方法与一般运算符一样,但在本质上编译将根据表达式的不同自动完成相应的运算符重载函数的调用过程。例如表达式“c=c1+c2”,编译首先将“c1+c2”解释为“c1.operator+(c2)”,从而调用运算符重载函数operator+(CComplex&c),然后再将运算符重载函数的返回值赋给c。同样,对于表达式“c=c1+20”,编译器将“c1+20”解释为“c1.operator+(20)”,调用运算符重载函数operator+(doubler),然后再将运算符重载函数的返回值赋给c。(3)在编译解释“c1+c2”时,由于成员函数都隐含一个this指针,因此解释的“c1.operator+(c2)”就是等价于“operator+(&c1,c2)”。正是因为this指针的存在,当重载的运算符函数是类的成员函数时,运算符函数的形参个数要比运算符操作数个数少一个。对于双目运算符(例如“+”)重载的成员函数来说,它应只有一个参数,用来指定其右操作数。而对于单目运算符重载的成员函数来说,由于操作数就是该类对象本身,因此运算符函数不应有参数。02运算符重载限制运算符重载限制在C++中,运算符重载还有以下一些限制:(1)重载的运算符必须是一个已有的合法的C++运算符,如“+”、“-”、“*”、“/”、“++”等,且不是所有的运算符都可以重载。在C++中不允许重载的运算符有“?:”(条件)、“.”(成员)、“*.”(成员指针)、“::”(域作用符)、sizeof(取字节大小)。(2)不能定义新的运算符,或者说,不能为C++没有的运算符进行重载。(3)当重载一个运算符时,该运算符的操作数个数、优先级和结合性不能改变。(4)运算符重载的方法通常有类的操作成员函数和友元函数两种,但“=”(赋值)、“()”(函数调用)、“[]”(下标)和“->”(成员指针)运算符不能重载为友元函数。03友元重载友元重载友元重载方法既可用于单目运算符,也可以用于双目运算符,其一般格式如下:friend<函数类型>operator<重载的运算符>(<形参>) //单目运算符重载{…} //函数体friend<函数类型>operator<重载的运算符>(<形参1,形参2>) //双目运算符重载{…}
其中,对于单目运算符的友元重载函数来说,只有一个形参,形参类型既可能是类的对象,也可能是类的引用,这取决于运算符的类型。对于“++”、“--”等来说,这个形参类型是类的引用对象,因为操作数必须是左值。对于单目“-”(负号运算符)等来说,形参类型可以是类的引用,也可以是类的对象。对于双目运算符的友元重载函数来说,它有两个形参,这两个形参中必须有一个是类的对象。下面来看一个实例,这个例子是在【例Ex_Complex】的基础上,用友元函数实现双目运算符“+”、单目运算符“-”的重载,而用成员函数实现“+=”运算。【例Ex_ComplexFriend】运算符的友元重载程序运行结果如下:实部=42,虚部=90实部=32,虚部=20实部=50,虚部=70实部=42,虚部=90实部=-12,虚部=-20友元重载分析和比较:(1)类CComplex中,对于双目运算符“+”的重载分别用成员函数和友元函数的方法定义了3个重载函数,如代码注释中所标的A、B和C。(2)当“c=20+c2”时,这里的“20+c2”是无法解释成“20.operator+(c2)”的,因为“20”不是一个类对象,所以无法确定它所在的类,也就无法用该类的成员函数来实现运算符重载。此时,必须用友元函数才能实现。正因为如此,当“c=20+c2”时就会自动调用C版本的友元运算符重载函数。(3)类似的,当“c=c
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 如何处理评估争议问题试题及答案
- 2024年汽车维修工考试综合知识测验试题及答案
- 汽车燃油系统维护与故障排除试题及答案
- 2024年美容师考试热点问题试题及答案
- 2024年二手车评估师考试陷阱试题及答案
- 灵活运用的语文考试试题及答案
- 宠物营养师考试饮食营养成分分析试题及答案
- 宠物营养师考试中论述题的写作技巧与试题及答案
- 2024年省考真题回顾与解析试题及答案
- 口腔内科病例书写规范
- 2024年浪潮入职测评题和答案
- 《肌力训练》课件
- 招标投标法培训课件
- 针灸治疗呃逆
- 2024年中考英语复习:阅读七选五 专项练习题汇编(含答案解析)
- 《吸收与解吸》课件
- 综合实践活动(1年级下册)第1课时 走近身边孝顺的好榜样-课件
- 初中信息技术教学中的项目式学习
- 部编版语文二年级下册第3单元核心素养教案
- DB43-T 1712-2019 银行业金融机构智能预警系统安全防范要求
- 2023年云南省接受军转干部安置考试试题
评论
0/150
提交评论