(C++-面向对象程序设计-谭浩强)第12章-多态性与虚函数(共44页).doc

上传人:飞****2 文档编号:14092909 上传时间:2022-05-02 格式:DOC 页数:44 大小:105.50KB
返回 下载 相关 举报
(C++-面向对象程序设计-谭浩强)第12章-多态性与虚函数(共44页).doc_第1页
第1页 / 共44页
(C++-面向对象程序设计-谭浩强)第12章-多态性与虚函数(共44页).doc_第2页
第2页 / 共44页
点击查看更多>>
资源描述

《(C++-面向对象程序设计-谭浩强)第12章-多态性与虚函数(共44页).doc》由会员分享,可在线阅读,更多相关《(C++-面向对象程序设计-谭浩强)第12章-多态性与虚函数(共44页).doc(44页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。

1、精选优质文档-倾情为你奉上第12章 多态性与虚函数12.1 多态性的概念12.2 一个典型的例子12.3 虚函数12.4 纯虚函数与抽象类12.1 多态性的概念多态性(polymorphism)是面向对象程序设计的一个重要特征。利用多态性可以设计和实现一个易于扩展的系统。在C+程序设计中,多态性是指具有不同功能的函数可以用同一个函数名,这样就可以用一个函数名调用不同内容的函数。在面向对象方法中一般是这样表述多态性的:向不同的对象发送同一个消息,不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。在C+程序设计中,在不同的类中定义了其响应消息的方法,

2、那么使用这些类时,不必考虑它们是什么类型,只要发布消息即可。从系统实现的角度看,多态性分为两类:静态多态性和动态多态性。以前学过的函数重载和运算符重载实现的多态性属于静态多态性,在程序编译时系统就能决定调用的是哪个函数,因此静态多态性又称编译时的多态性。静态多态性是通过函数的重载实现的(运算符重载实质上也是函数重载)。动态多态性是在程序运行过程中才动态地确定操作所针对的对象。它又称运行时的多态性。动态多态性是通过虚函数(virtual function)实现的。有关静态多态性的应用已经介绍过了,在本章中主要介绍动态多态性和虚函数。要研究的问题是:当一个基类被继承为不同的派生类时,各派生类可以使

3、用与基类成员相同的成员名,如果在运行时用同一个成员名调用类对象的成员,会调用哪个对象的成员?也就是说,通过继承而产生了相关的不同的派生类,与基类成员同名的成员在不同的派生类中有不同的含义。也可以说,多态性是“一个接口,多种方法”。12.2 一个典型的例子下面是一个承上启下的例子。一方面它是有关继承和运算符重载内容的综合应用的例子,通过这个例子可以进一步融会贯通前面所学的内容,另一方面又是作为讨论多态性的一个基础用例。例12.1 先建立一个Point(点)类,包含数据成员x,y(坐标点)。以它为基类,派生出一个Circle(圆)类,增加数据成员r(半径),再以Circle类为直接基类,派生出一个

4、Cylinder(圆柱体)类,再增加数据成员h(高)。要求编写程序,重载运算符“”,使之能用于输出以上类对象。对于一个比较大的程序,应当分成若干步骤进行。先声明基类,再声明派生类,逐级进行,分步调试。声明基类Point类可写出声明基类Point的部分如下:#include /声明类Pointclass Pointpublic:Point(float x=0,float y=0);/有默认参数的构造函数void setPoint(float ,float);/设置坐标值float getX( )const return x;/读x坐标float getY( )const return y;/读y

5、坐标friend ostream & operator (ostream &,const Point &);/重载运算符“”protected :/受保护成员float x, y;/下面定义Point类的成员函数Point:Point(float a,float b) /Point的构造函数x=a;y=b; /对x,y初始化void Point:setPoint(float a,float b) /设置x和y的坐标值x=a;y=b; /为x,y赋新值ostream & operator (ostream &output, const Point &p)outputp.x,p.yendl; re

6、turn output;/重载运算符“”,使之能输出点的坐标以上完成了基类Point类的声明。现在要对上面写的基类声明进行调试,检查它是否有错,为此要写出main函数。实际上它是一个测试程序。int main( )Point p(3.5,6.4);/建立Point类对象pcoutx=p.getX( ),y=p.getY( )endl;/输出p的坐标值p.setPoint(8.5,6.8);/重新设置p的坐标值coutp(new):pendl;/用重载运算符“”输出p点坐标程序编译通过,运行结果为x=3.5,y=6.4p(new):8.5,6.8测试程序检查了基类中各函数的功能,以及运算符重载的

7、作用,证明程序是正确的。(2)声明派生类Circle在上面的基础上,再写出声明派生类Circle的部分:class Circle:public Point/circle是Point类的公用派生类public:Circle(float x=0,float y=0,float r=0);/构造函数void setRadius(float );/设置半径值float getRadius( )const;/读取半径值float area ( )const;/计算圆面积friend ostream &operator (ostream &,const Circle &);/重载运算符“”private:

8、float radius;/定义构造函数,对圆心坐标和半径初始化Circle:Circle(float a,float b,float r):Point(a,b),radius(r)/设置半径值void Circle:setRadius(float r)radius=r;/读取半径值float Circle:getRadius( )const return radius;/计算圆面积float Circle:area( )constreturn 3.14159*radius*radius;/重载运算符“”,使之按规定的形式输出圆的信息ostream &operator (ostream &ou

9、tput,const Circle &c)outputCenter=c.x,c.y,r=c.radius,area=c.area( )endl;return output;为了测试以上Circle类的定义,可以写出下面的主函数:int main( )Circle c(3.5,6.4,5.2);/建立Circle类对象c,并给定圆心坐标和半径coutoriginal circle:nx=c.getX(), y=c.getY(), r=c.getRadius( ), area=c.area( )endl;/输出圆心坐标、半径和面积c.setRadius(7.5);/设置半径值c.setPoint(

10、5,5);/设置圆心坐标值x,ycoutnew circle:nc;/用重载运算符“”输出圆对象的信息Point &pRef=c;/pRef是Point类的引用变量,被c初始化coutpRef:pRef;/输出pRef的信息return 0;程序编译通过,运行结果为original circle:(输出原来的圆的数据)x=3.5, y=6.4, r=5.2, area=84.9486new circle:(输出修改后的圆的数据)Center=5,5, r=7.5, area=176.714pRef:5,5 (输出圆的圆心“点”的数据)(3)声明Circle的派生类Cylinder前面已从基类P

11、oint派生出Circle类,现在再从Circle派生出Cylinder类。class Cylinder:public Circle/ Cylinder是Circle的公用派生类public :Cylinder (float x=0,float y=0,float r=0,float h=0);/构造函数void setHeight(float );/设置圆柱高float getHeight( )const;/读取圆柱高loat area( )const;/计算圆表面积float volume( )const;/计算圆柱体积friend ostream& operator (ostream&,

12、const Cylinder&);/重载运算符”protected :float height;/圆柱高;/定义构造函数Cylinder:Cylinder(float a,float b,float r,float h):Circle(a,b,r),height(h)/设置圆柱高void Cylinder:setHeight(float h)height=h;/读取圆柱高float Cylinder:getHeight( )const return height;/计算圆表面积float Cylinder:area( )const return 2*Circle:area( )+2*3.141

13、59*radius*height;/计算圆柱体积float Cylinder:volume()const return Circle:area()*height;ostream &operator (ostream &output,const Cylinder& cy)outputCenter=cy.x,cy.y,r=cy.radius,h=cy.height narea=cy.area( ), volume=cy.volume( )endl;return output; /重载运算符“”可以写出下面的主函数:int main( )Cylinder cy1(3.5,6.4,5.2,10);/定

14、义Cylinder类对象cy1coutnoriginal cylinder:nx=cy1.getX( ), y=cy1.getY( ), r=cy1.getRadius( ), h=cy1.getHeight( )narea=cy1.area(),volume=cy1.volume()endl;/用系统定义的运算符“”输出cy1的数据cy1.setHeight(15);/设置圆柱高cy1.setRadius(7.5);/设置圆半径cy1.setPoint(5,5);/设置圆心坐标值x,ycoutnnew cylinder:ncy1;/用重载运算符“”输出cy1的数据Point &pRef=cy

15、1;/pRef是Point类对象的引用变量coutnpRef as a Point:pRef;/pRef作为一个“点”输出Circle &cRef=cy1;/cRef是Circle类对象的引用变量coutncRef as a Circle:display( );”可以调用不同派生层次中的display函数,只需在调用前给指针变量pt赋以不同的值(使之指向不同的类对象)即可。C+中的虚函数就是用来解决这个问题的。虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。请分析例12.2。这个例子开始时没有使用虚函数,然后再讨论使用虚函数的情况

16、。例12.2 基类与派生类中有同名函数。在下面的程序中Student是基类,Graduate是派生类,它们都有display这个同名的函数。#include #include using namespace std;/声明基类Studentclass Studentpublic:Student(int, string,float);/声明构造函数void display( );/声明输出函数protected :/受保护成员,派生类可以访问int num;string name;float score;/Student类成员函数的实现Student:Student(int n, string

17、nam,float s)/定义构造函数num=n;name=nam;score=s;void Student:display( )/定义输出函数coutnum:numnname:namenscore:scorenn;/声明公用派生类Graduateclass Graduate:public Studentpublic:Graduate(int, string, float, float);/声明构造函数void display( );/声明输出函数private:float pay;/ Graduate类成员函数的实现void Graduate:display( )/定义输出函数coutnum

18、:numnname:namenscore:scorenpay=paydisplay( );pt=&grad1;pt-display( );return 0;运行结果如下,请仔细分析。num:1001(stud1的数据)name:Liscore:87.5num:2001 (grad1中基类部分的数据)name:wangscore:98.5下面对程序作一点修改,在Student类中声明display函数时,在最左面加一个关键字virtual,即virtual void display( );这样就把Student类的display函数声明为虚函数。程序其他部分都不改动。再编译和运行程序,请注意分析

19、运行结果:num:1001(stud1的数据)name:Liscore:87.5num:2001 (grad1中基类部分的数据)name:wangscore:98.5pay=1200 (这一项以前是没有的)由虚函数实现的动态多态性就是:同一类族中不同类的对象,对同一函数调用作出不同的响应。虚函数的使用方法是:(1)在基类用virtual声明成员函数为虚函数。这样就可以在派生类中重新定义此函数,为它赋予新的功能,并能方便地被调用。在类外定义虚函数时,不必再加virtual。(2)在派生类中重新定义此函数,要求函数名、函数类型、函数参数个数和类型全部与基类的虚函数相同,并根据派生类的需要重新定义函

20、数体。C+规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。因此在派生类重新声明该虚函数时,可以加virtual,也可以不加,但习惯上一般在每一层声明该函数时都加virtual,使程序更加清晰。如果在派生类中没有对基类的虚函数重新定义,则派生类简单地继承其直接基类的虚函数。(3)定义一个指向基类对象的指针变量,并使它指向同一类族中需要调用该函数的对象。(4)通过该指针变量调用此虚函数,此时调用的就是指针变量指向的对象的同名函数。通过虚函数与指向基类对象的指针变量的配合使用,就能方便地调用同一类族中不同类的同名函数,只要先用基类指针指向即可。如果指针不断地指向同一类族中

21、不同类的对象,就能不断地调用这些对象中的同名函数。这就如同前面说的,不断地告诉出租车司机要去的目的地,然后司机把你送到你要去的地方。需要说明;有时在基类中定义的非虚函数会在派生类中被重新定义(如例12.1中的area函数),如果用基类指针调用该成员函数,则系统会调用对象中基类部分的成员函数;如果用派生类指针调用该成员函数,则系统会调用派生类对象中的成员函数,这并不是多态性行为(使用的是不同类型的指针),没有用到虚函数的功能。以前介绍的函数重载处理的是同一层次上的同名函数问题,而虚函数处理的是不同派生层次上的同名函数问题,前者是横向重载,后者可以理解为纵向重载。但与重载不同的是:同一类族的虚函数

22、的首部是相同的,而函数重载时函数的首部是不同的(参数个数或类型不同)。12.3.2 静态关联与动态关联编译系统要根据已有的信息,对同名函数的调用作出判断。对于调用同一类族中的虚函数,应当在调用时用一定的方式告诉编译系统,你要调用的是哪个类对象中的函数。这样编译系统在对程序进行编译时,即能确定调用的是哪个类对象中的函数。确定调用的具体对象的过程称为关联(binding)。在这里是指把一个函数名与一个类对象捆绑在一起,建立关联。一般地说,关联指把一个标识符和一个存储地址联系起来。前面所提到的函数重载和通过对象名调用的虚函数,在编译时即可确定其调用的虚函数属于哪一个类,其过程称为静态关联(stati

23、c binding),由于是在运行前进行关联的,故又称为早期关联(early binding)。函数重载属静态关联。在上一小节程序中看到了怎样使用虚函数,在调用虚函数时并没有指定对象名,那么系统是怎样确定关联的呢?是通过基类指针与虚函数的结合来实现多态性的。先定义了一个指向基类的指针变量,并使它指向相应的类对象,然后通过这个基类指针去调用虚函数(例如“pt-display( )”)。显然,对这样的调用方式,编译系统在编译该行时是无法确定调用哪一个类对象的虚函数的。因为编译只作静态的语法检查,光从语句形式是无法确定调用对象的。在这样的情况下,编译系统把它放到运行阶段处理,在运行阶段确定关联关系。

24、在运行阶段,基类指针变量先指向了某一个类对象,然后通过此指针变量调用该对象中的函数。此时调用哪一个对象的函数无疑是确定的。例如,先使pt指向grad1,再执行“pt-display( )”,当然是调用grad1中的display函数。由于是在运行阶段把虚函数和类对象“绑定”在一起的,因此,此过程称为动态关联(dynamic binding)。这种多态性是动态的多态性,即运行阶段的多态性。在运行阶段,指针可以先后指向不同的类对象,从而调用同一类族中不同类的虚函数。由于动态关联是在编译以后的运行阶段进行的,因此也称为滞后关联(late binding)。12.3.3 在什么情况下应当声明虚函数使用

25、虚函数时,有两点要注意:(1)只能用virtual声明类的成员函数,使它成为虚函数,而不能将类外的普通函数声明为虚函数。因为虚函数的作用是允许在派生类中对基类的虚函数重新定义。显然,它只能用于类的继承层次结构中。(2)一个成员函数被声明为虚函数后,在同一类族中的类就不能再定义一个非virtual的但与该虚函数具有相同的参数(包括个数和类型)和函数返回值类型的同名函数。根据什么考虑是否把一个成员函数声明为虚函数呢?主要考虑以下几点:(1)首先看成员函数所在的类是否会作为基类。然后看成员函数在类的继承后有无可能被更改功能,如果希望更改其功能的,一般应该将它声明为虚函数。(2)如果成员函数在类被继承

26、后功能不需修改,或派生类用不到该函数,则不要把它声明为虚函数。不要仅仅考虑到要作为基类而把类中的所有成员函数都声明为虚函数。(3)应考虑对成员函数的调用是通过对象名还是通过基类指针或引用去访问,如果是通过基类指针或引用去访问的,则应当声明为虚函数。(4)有时,在定义虚函数时,并不定义其函数体,即函数体是空的。它的作用只是定义了一个虚函数名,具体功能留给派生类去添加。在12.4节中将详细讨论此问题。需要说明的是:使用虚函数,系统要有一定的空间开销。当一个类带有虚函数时,编译系统会为该类构造一个虚函数表(virtual function table,简称vtable),它是一个指针数组,存放每个虚

27、函数的入口地址。系统在进行动态关联时的时间开销是很少的,因此,多态性是高效的。12.3.4 虚析构函数析构函数的作用是在对象撤销之前做必要的“清理现场”的工作。当派生类的对象从内存中撤销时一般先调用派生类的析构函数,然后再调用基类的析构函数。但是,如果用new运算符建立了临时对象,若基类中有析构函数,并且定义了一个指向该基类的指针变量。在程序用带指针参数的delete运算符撤销对象时,会发生一个情况:系统会只执行基类的析构函数,而不执行派生类的析构函数。例12.3 基类中有非虚析构函数时的执行情况。为简化程序,只列出最必要的部分。#include using namespace std;cla

28、ss Point/定义基类Point类public:Point( )/Point类构造函数Point()coutexecuting Point destructorendl;/Point类析构函数;class Circle:public Point/定义派生类Circle类public:Circle( )/Circle类构造函数Circle( )coutexecuting Circle destructorendl;/Circle类析构函数private:int radius;int main( )Point *p=new Circle;/用new开辟动态存储空间delete p;/用dele

29、te释放动态存储空间return 0;这只是一个示意的程序。p是指向基类的指针变量,指向new开辟的动态存储空间,希望用detele释放p所指向的空间。但运行结果为:executing Point destructor表示只执行了基类Point的析构函数,而没有执行派生类Circle的析构函数。原因是以前介绍过的。如果希望能执行派生类Circle的析构函数,可以将基类的析构函数声明为虚析构函数,如virtual Point()coutexecuting Point destructorendl;程序其他部分不改动,再运行程序,结果为executing Circle destructorexec

30、uting Point destructor先调用了派生类的析构函数,再调用了基类的析构函数,符合人们的愿望。当基类的析构函数为虚函数时,无论指针指的是同一类族中的哪一个类对象,系统会采用动态关联,调用相应的析构函数,对该对象进行清理工作。如果将基类的析构函数声明为虚函数时,由该基类所派生的所有派生类的析构函数也都自动成为虚函数,即使派生类的析构函数与基类的析构函数名字不相同。最好把基类的析构函数声明为虚函数。这将使所有派生类的析构函数自动成为虚函数。这样,如果程序中显式地用了delete运算符准备删除一个对象,而delete运算符的操作对象用了指向派生类对象的基类指针,则系统会调用相应类的析

31、构函数。虚析构函数的概念和用法很简单,但它在面向对象程序设计中却是很重要的技巧。专业人员一般都习惯声明虚析构函数,即使基类并不需要析构函数,也显式地定义一个函数体为空的虚析构函数,以保证在撤销动态分配空间时能得到正确的处理。构造函数不能声明为虚函数。这是因为在执行构造函数时类对象还未完成建立过程,当然谈不上函数与类对象的绑定。12.4 纯虚函数与抽象类12.4.1 纯虚函数有时在基类中将某一成员函数定为虚函数,并不是基类本身的要求,而是考虑到派生类的需要,在基类中预留了一个函数名,具体功能留给派生类根据需要去定义。例如在本章的例12.1程序中,基类Point中没有求面积的area函数,因为“点

32、”是没有面积的,也就是说,基类本身不需要这个函数,所以在例12.1程序中的Point类中没有定义area函数。但是,在其直接派生类Circle和间接派生类Cylinder中都需要有area函数,而且这两个area函数的功能不同,一个是求圆面积,一个是求圆柱体表面积。有的读者自然会想到,在这种情况下应当将area声明为虚函数。可以在基类Point中加一个area函数,并声明为虚函数:virtual float area( )const return 0;其返回值为0,表示“点”是没有面积的。其实,在基类中并不使用这个函数,其返回值也是没有意义的。为简化,可以不写出这种无意义的函数体,只给出函数的

33、原型,并在后面加上“=0”,如virtual float area( )const =0;/纯虚函数这就将area声明为一个纯虚函数(pure virtual function)。纯虚函数是在声明虚函数时被“初始化”为0的函数。声明纯虚函数的一般形式是virtual 函数类型 函数名 (参数表列)=0;注意:纯虚函数没有函数体;最后面的“=0”并不表示函数返回值为0,它只起形式上的作用,告诉编译系统“这是纯虚函数”;这是一个声明语句,最后应有分号。纯虚函数只有函数的名字而不具备函数的功能,不能被调用。它只是通知编译系统:“在这里声明一个虚函数,留待派生类中定义”。在派生类中对此函数提供定义后,

34、它才能具备函数的功能,可被调用。纯虚函数的作用是在基类中为其派生类保留一个函数的名字,以便派生类根据需要对它进行定义。如果在基类中没有保留函数名字,则无法实现多态性。如果在一个类中声明了纯虚函数,而在其派生类中没有对该函数定义,则该虚函数在派生类中仍然为纯虚函数。12.4.2 抽象类如果声明了一个类,一般可以用它定义对象。但是在面向对象程序设计中,往往有一些类,它们不用来生成对象。定义这些类的惟一目的是用它作为基类去建立派生类。它们作为一种基本类型提供给用户,用户在这个基础上根据自己的需要定义出功能各异的派生类。用这些派生类去建立对象。一个优秀的软件工作者在开发一个大的软件时,决不会从头到尾都

35、由自己编写程序代码,他会充分利用已有资源(例如类库)作为自己工作的基础。这种不用来定义对象而只作为一种基本类型用作继承的类,称为抽象类(abstract class ),由于它常用作基类,通常称为抽象基类(abstract base class )。凡是包含纯虚函数的类都是抽象类。因为纯虚函数是不能被调用的,包含纯虚函数的类是无法建立对象的。抽象类的作用是作为一个类族的共同基类,或者说,为一个类族提供一个公共接口。一个类层次结构中当然也可不包含任何抽象类,每一层次的类都是实际可用的,可以用来建立对象的。但是,许多好的面向对象的系统,其层次结构的顶部是一个抽象类,甚至顶部有好几层都是抽象类。如果

36、在抽象类所派生出的新类中对基类的所有纯虚函数进行了定义,那么这些函数就被赋予了功能,可以被调用。这个派生类就不是抽象类,而是可以用来定义对象的具体类(concrete class )。如果在派生类中没有对所有纯虚函数进行定义,则此派生类仍然是抽象类,不能用来定义对象。虽然抽象类不能定义对象(或者说抽象类不能实例化),但是可以定义指向抽象类数据的指针变量。当派生类成为具体类之后,就可以用这种指针指向派生类对象,然后通过该指针调用虚函数,实现多态性的操作。12.4.3 应用实例例12.4 虚函数和抽象基类的应用。在本章例12.1介绍了以Point为基类的点圆圆柱体类的层次结构。现在要对它进行改写,

37、在程序中使用虚函数和抽象基类。类的层次结构的顶层是抽象基类Shape(形状)。Point(点), Circle(圆), Cylinder(圆柱体)都是Shape类的直接派生类和间接派生类。下面是一个完整的程序,为了便于阅读,分段插入了一些文字说明。程序如下:第(1)部分#include using namespace std;/声明抽象基类Shapeclass Shapepublic:virtual float area( )const return 0.0;/虚函数virtual float volume()const return 0.0;/虚函数virtual void shapeNam

38、e()const =0;/纯虚函数;第(2)部分/声明Point类class Point:public Shape/Point是Shape的公用派生类public:Point(float=0,float=0);void setPoint(float ,float ); float getX( )const return x;float getY( )const return y;virtual void shapeName( )const coutPoint:;/对虚函数进行再定义friend ostream & operator (ostream &,const Point &);prote

39、cted:float x,y;/定义Point类成员函数Point:Point(float a,float b)x=a;y=b;void Point:setPoint(float a,float b)x=a;y=b;ostream & operator (ostream &output,const Point &p)outputp.x,p.y;return output;第(3)部分/声明Circle类class Circle:public Pointpublic:Circle(float x=0,float y=0,float r=0);void setRadius(float );floa

40、t getRadius( )const;virtual float area( )const;virtual void shapeName( )const coutCircle:;/对虚函数进行再定义friend ostream &operator (ostream &,const Circle &);protected :float radius;/声明Circle类成员函数Circle:Circle(float a,float b,float r):Point(a,b),radius(r)void Circle:setRadius(float r):radius(r)float Circl

41、e:getRadius( )const return radius;float Circle:area( )const return 3.14159*radius*radius;ostream &operator (ostream &output,const Circle &c)outputc.x,c.y, r=c.radius;return output;第(4)部分/声明Cylinder类class Cylinder:public Circlepublic:Cylinder (float x=0,float y=0,float r=0,float h=0);void setHeight(float );virtual float area( )const;virtual float volume( )const;virtual void shapeName( )const coutCylinder:;/对虚函

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 教育专区 > 教案示例

本站为文档C TO C交易模式,本站只提供存储空间、用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。本站仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知淘文阁网,我们立即给予删除!客服QQ:136780468 微信:18945177775 电话:18904686070

工信部备案号:黑ICP备15003705号© 2020-2023 www.taowenge.com 淘文阁