版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第
3
章面向对象程序设计【学习目标】
随着信息技术的发展和企业结构的调整,使用传统的面向过程语言开发、管理、维护应用软件变得越来越复杂,越来越困难。面向对象的程序设计技术解决了上述问题。在面向对象程序设计语言里,将数据和处理数据的方法紧密的结合在一起,形成类,将类实例化,就形成了对象。在本章中将向读者介绍C++面向对象的相关知识。通过学习本章,读者可以达到以下学习目的:
l
理解对象和类的概念
l
定义并实例化类对象
l
理解类的继承
l
掌握类的静态数据成员和静态方法
l
掌握运算符重载技术课件制作人:宋坤【学习导航】
C++语言被认为是C的超集,对C语言的扩展主要体现在面向对象的技术上。本章主要介绍了C++语言中涉及面向对象的基本知识,包括类和对象的定义,类成员的访问,构造函数和析构函数的作用,类的继承,静态数据成员和静态方法,运算符重载等。这些内容是面向对象程序设计最基本的知识,希望读者能够熟练掌握。本章在书中的学习位置如图3-1所示。课件制作人:宋坤【知识框架】
本章学习内容知识框架如图3-2所示。课件制作人:宋坤√本章目录3.1类和对象3.2类的继承3.3类的高级方法3.4类模板3.5异常处理3.6课后练习√√√√√课件制作人:宋坤3.1
类和对象
面向对象最大的特征就是提出了类和对象的概念。在以面向对象的方式开发应用程序时,将遇到的各种事物抽象为类,类中包含数据和操作数据的方法,用户通过实例化类对象来访问类中的数据和方法。本节将介绍有关类和对象的相关知识。
深入理解类和对象类的定义类的实例化——对象类成员的访问类成员的保护构造函数析构函数this指针√√√√√√√√课件制作人:宋坤深入理解类和对象
世界中的各种事物,如花、鸟、鱼、虫,都有自己的特征。花具有美丽的色彩,鸟具有一对翅膀……这些事物也都有自己的行为,花能够生长,鸟能够鸣叫,鱼能够游动……在面向对象程序设计中,将事物的特征和行为组织在一起,便形成了类。在类中,事物的特征被描述为数据成员,行为被描述为方法。
课件制作人:宋坤类的定义
声明一个类,需要使用关键字class,其后是类名,类名后是一个“{”号,而后是数据成员和方法,结尾是“}”号和分号。下面声明一个CDog类,包含两个数据成员和一个方法。classCDog{//数据成员
unsignedintm_Age;unsignedintm_Weight;//方法
voidyelp();};在声明CDog时,并没有为其分配内存。它只是告诉编译器CDog包含哪些数据成员和方法,CDog占用多少内存空间。课件制作人:宋坤类的实例化——对象
对象是类的实例化。在声明一个类时,并没有为其分配内存空间,只有在实例化一个对象时,才为对象分配空间。声明一个对象和声明一个整型变量类似。下面声明了一个整型变量i和一个CDog类对象mydog。inti;CDogmydog; //声明一个对象课件制作人:宋坤类成员的访问
在定义一个类的对象之后,就可以通过对象访问类的成员了。在对象之后使用“.”运算符访问类成员。下面的代码演示了如何设置mydog的a_Age成员变量,如何调用mydog的yelp方法。mydog.m_Age=2;mydog.yelp();课件制作人:宋坤类成员的保护
在类的声明时,类的成员(数据和方法)具有安全级别。常用的安全级别有public、private、protected。默认情况下,类的成员为私有的(private),私有成员只能在类本身的方法内访问,类的对象不能够访问私有成员,并且私有成员不能够被派生类继承。共有成员(public)能够被类的所有对象访问,能够被派生类继承。保护成员(protected)不能够被对象访问,但能够被派生类继承。在类外部不能访问私有成员和保护成员,只能访问共有成员。下面的代码定义了一个CDog类,在该类中定义一个私有成员变量m_Age和一个受保护的成员变量m_Weight,在main函数中访问CDog对象的m_Age和m_Weight成员时,将出现编译错误。classCDog{//私有成员
unsignedintm_Age;protected://保护成员
unsignedintm_Weight;};
intmain(intargc,char*argv[]){CDogmydog;mydog.m_Age=2; //错误,不能访问私有成员
mydog.m_Weight=10; //错误,不能访问保护成员
return0;}课件制作人:宋坤构造函数
在建立一个对象时,常常需要对对象进行初始化,为了进行初始化工作,C++提供了一个特殊的成员函数——构造函数(constructor)。构造函数是一个与类名相同的方法,可以根据需要设置参数,但不具有返回值,甚至空值也不行。如果在声明类时,没有提供构造函数,编译器会提供一个默认的构造函数,默认构造函数没有参数,不进行任何操作。在开发程序时,用户可以自己定义一个默认的构造函数,只要构造函数没有任何参数就可以了。下面的代码定义了一个CDog类,在该类中定义一个默认的构造函数,用于初始化成员变量。classCDog{public:unsignedintm_Weight;unsignedintm_Age;CDog();//默认构造函数
~CDog();};
CDog::CDog(){m_Age=1;m_Weight=2;printf("constructinstance\n");}CDog::~CDog(){
}intmain(intargc,char*argv[]){CDogmydog;//调用默认的构造函数
printf("%d",mydog.m_Age);//mydog.m_Age=1return0;}课件制作人:宋坤构造函数编译器除了提供默认的构造函数外,还提供了默认的复制构造函数。在一个函数中,按值传递一个对象或是将对象作为函数的返回值,均会调用类的复制构造函数。所有的复制构造函数均只有一个参数,即该类的对象的引用。因为复制构造函数的目的是生成一个对象的拷贝,所以参数是类的对象的常量引用,即在复制构造函数中不允许修改参数。下面的代码声明了一个CDog类的复制构造函数。CDog(constCDog&theDog)当按值传递一个对象作为函数参数或者函数返回一个对象时,编译器将调用复制构造函数重新构造一个对象。下面的代码演示了复制构造函数的调用。在CDog类中定义一个复制构造函数,并在函数中输出一个语句。课件制作人:宋坤构造函数classCDog{public:unsignedintm_Weight;unsignedintm_Age;CDog(intweight,intage);CDog(intweight=20);CDog(constCDog&theDog);voidSetAge(unsignedintage);intGetAge();~CDog();};CDog::CDog(intweight,intage){m_Weight=weight;m_Age=age;}CDog::CDog(intweight){m_Weight=weight;}CDog::CDog(constCDog&theDog){m_Weight=theDog.m_Weight;m_Age=theDog.m_Age;printf("copyconstructorfunctionwasinvoked\n");}课件制作人:宋坤构造函数voidCDog::SetAge(unsignedintage){m_Age=age;}
intCDog::GetAge(){returnm_Age;
}
CDog::~CDog(){}
CDogCopyData(CDogm_dog){returnm_dog;}
voidtest(){CDogcustomdog(3,20);//调用自定义的构造函数
CopyData(customdog);}intmain(intargc,char*argv[]){test();return0;}//运行结果copyconstructorfunctionwasinvokedcopyconstructorfunctionwasinvoked课件制作人:宋坤构造函数在上面的代码中,函数test调用了CopyData函数,该函数是按值传递的,因此在调用CopyData函数时,会执行复制构造函数,将实际参数复制一份传给形式参数,输出第1行字符串。那么,第2行字符串是如何输出的?不要忘记,CopyData函数需要一个CDog类型的返回值,在函数返回CDog类型的对象时,会调用一次复制构造函数,将对象返回给被调用函数,输出第2条语句。总的来说,构造函数的定义和其他方法的定义相同,但需要注意以下几点。(1)构造函数不能指定返回类型和返回值。(2)一个类可以有多个构造函数,如果没有定义构造函数,编译器会自动为类创建一个默认的构造函数。(3)构造函数可以没有参数,也可以有多个参数,多个构造函数之间就是靠参数的个数和类型来区分的。
课件制作人:宋坤析构函数析构函数是与构造函数是相对的,它是在对象被撤销后清除并释放所分配的内存。析构函数与类同名,只是前面有一个“~”号。析构函数没有返回值,也没有参数。例如,CDog类的析构函数声明:~CDog();如果对象是在栈中被创建的,那么在对象失去作用域时,系统会自动调用其析构函数来释放对象占用的内存。下面根据一个例子来说明析构函数的调用。例如:下面定义一个CDog类,在该类中定义了一个析构函数,用于在CDog对象被释放时,输出一条语句。程序代码如下:课件制作人:宋坤析构函数classCDog{
public:unsignedintm_Weight;unsignedintm_Age;
CDog(intweight,intage);CDog(intweight=20);
CDog(constCDog&theDog);
voidSetAge(unsignedintage);intGetAge();~CDog();};
CDog::CDog(intweight,intage){m_Weight=weight;m_Age=age;}
CDog::CDog(intweight){m_Weight=weight;}CDog::CDog(constCDog&theDog){m_Weight=theDog.m_Weight;m_Age=theDog.m_Age;}
课件制作人:宋坤析构函数voidCDog::SetAge(unsignedintage){m_Age=age;}intCDog::GetAge(){returnm_Age;}CDog::~CDog(){printf("destructorfunctionwasinvoked\n");}voidtest(){CDogmydog;
//调用默认的构造函数
CDogcustomdog(3,20);
//调用自定义的构造函数
}intmain(intargc,char*argv[]){test();return0;}运行结果:destructorfunctionwasinvokeddestructorfunctionwasinvoked在test函数中,本教材声明了两个CDog对象,在test函数结束后,系统会自动调用CDog类的析构函数释放对象,因此输出了两行字符串。
课件制作人:宋坤this指针对于类的非静态成员,每一个对象都有自己的一份拷贝,即每个对象都有自己的数据成员和成员函数。例如:classCBook //定义一个CBook类{public: intm_Pages; //定义一个数据成员
voidOutputPages() //定义一个成员函数 {
printf("%d\n",m_Pages); //输出信息 }};intmain(intargc,char*argv[]){ CBookvbBook,vcBook; //定义2个CBook类对象
vbBook.m_Pages=512; //设置vbBook对象的成员数据
vcBook.m_Pages=570; //设置vcBook对象的成员数据
vbBook.OutputPages(); //调用OutputPages方法输出vbBook对象的数据成员
vcBook.OutputPages(); //调用OutputPages方法输出vcBook对象的数据成员
return0;}执行上述代码,效果如图3-1所示。
课件制作人:宋坤this指针从图3-1中可以发现,vbBook和vcBook两个对象均有自己的数据成员m_Pages,在调用OutputPages成员函数时输出的均是自己的数据成员。在OutputPages成员函数中只是访问了m_Pages数据成员,没个对象在调用OutputPages方法时是如何区分自己的数据成员呢?答案是通过this指针。在每个类的成员函数(非静态成员函数)中都隐含包含一个this指针,指向被调用对象的指针,其类型为当前类类型的指针类型,在const方法中,为当前类类型的const指针类型。当vbBook对象调用OutputPages成员函数时,this指针指向vbBook对象,当vcBook对象调用OutputPages成员函数时,this指针指向vcBook对象。在OutputPages成员函数中,用户可以显式地使用this指针访问数据成员。例如:voidOutputPages(){ printf("%d\n",this->m_Pages); //使用this指针访问数据成员}实际上,编译器为了实现this指针,在成员函数中自动添加了this指针对数据成员的访问,类似于上面的OutputPages方法。此外,为了将this指针指向当前调用对象,并在成员函数中能够使用,在每个成员函数中都隐含包含一个this指针作为函数参数,并在函数调用时将对象自身的地址隐含作为实际参数传递。例如,以OutputPages成员函数为例,编译器将其定义为:voidOutputPages(CBook*this) //隐含添加this指针{
printf("%d\n",this->m_Pages);}在对象调用成员函数时,传递对象的地址到成员函数中。以“vc.OutputPages();”语句为例,编译器将其解释为“vbBook.OutputPages(&vbBook);”。这样,就使得this指针合法,并能够在成员函数中使用。课件制作人:宋坤3.2
类的继承
继承是一种编程技术,可以从现有类中构造一个新类,通过新类来实现面向对象的程序设计。本节将从浅入深地介绍类的继承。
单一继承多重继承虚继承√√√课件制作人:宋坤单一继承
面向对象的最大特点是实现了继承。开发人员能够根据已有类派生一个新类,新类继承了父类所有公有和受保护的数据成员和方法。类的继承是指派生类可以全部或者部分的继承基类的特征,同时加入所需要的新特征和功能。简单来说,派生类可以有选择的继承基类的某些数据成员和方法,同时定义一些新的数据成员和方法。新的类继承了原有类的特性,被称为原有类的派生类或子类,原有类被称为新类的基类或父类。而单继承就是以一个基类派生新类。在派生一个新类时,使用class关键字,其后是类名称、冒号、访问限定符(public、private、protected)、基类名称。在C++中,共有3种访问限定符,如下。访问限定符public:表示对于基类中的public数据成员和方法,在派生类中仍然是public,对于基类中的protected数据成员和方法,在派生类中仍然是protected。访问限定符protected:表示对于基类中的public、protected数据成员和方法,在派生类中均为protected。访问限定符private:表示对于基类中的public、protected数据成员和方法,在派生类中均为private。下面的代码定义了一个动物类CAnimal,在该类中定义两个保护的成员变量,并通过定义4个方法来获取和设置成员变量。课件制作人:宋坤单一继承
classCAnimal{protected:char*m_Name;//名称
char*m_Color;//颜色public:char*GetName();char*GetColor();voidSetName(char*name);voidSetColor(char*color);voidMove();CAnimal();~CAnimal();};
char*CAnimal::GetName(){returnm_Name;}
char*CAnimal::GetColor(){returnm_Color;}
voidCAnimal::SetName(char*name){strcpy(m_Name,name);}
课件制作人:宋坤单一继承voidCAnimal::SetColor(char*color){strcpy(m_Color,color);}
CAnimal::CAnimal(){m_Name=newchar[5];m_Color=newchar[5];}
CAnimal::~CAnimal(){delete[]m_Name;delete[]m_Color;}
voidCAnimal::Move(){printf("Animalismoving\n");}现在,定义一个CDog类,该类继承于CAnimal类。这样,CDog类就拥有了CAnimal类的所有非私有的数据成员和方法。
课件制作人:宋坤单一继承classCDog:publicCAnimal{protected:char*m_Kind;public:char*GetKind();voidSetKind(char*kind);voidyelp();CDog();~CDog();};
voidCDog::yelp(){printf("Dogcanyelp");}CDog::CDog(){m_Kind=newchar[10];}char*CDog::GetKind(){returnm_Kind;}
voidCDog::SetKind(char*kind){strcpy(m_Kind,kind);}CDog::~CDog(){delete[]m_Kind;}课件制作人:宋坤单一继承下面的代码演示了CDog类调用基类的Move方法。intmain(intargc,char*argv[]){CDogdog;dog.SetColor("白色");
dog.SetName("狗");
dog.Move();//调用基类的方法
dog.yelp();//调用自身的方法
return0;}//运行结果:AnimalismovingDogcanyelp课件制作人:宋坤多重继承多继承是指派生类有多个基类,这个派生类继承了多个无关基类的特性。语法:class类名称:访问限定符1基类1名称,访问限定符2基类2名称,……在多继承中,派生类继承了多个基类的特征,每个基类都由一个访问限定符来控制其成员在派生类的访问权限。多继承使程序重用性得到更大的发挥,可以通过已有的多个不同基类来生成需要的新类。在下面的代码中,首先定义了一个CBird类和一个CFish类,并为每个类提供了相应的方法。然后定义了一个CWaterbird类,该类派生于CBird类和CFish类,那么CWaterbird类对象就可以调用CBird类和CFish类中定义的方法了。课件制作人:宋坤多重继承classCBird{public:voidfly();voidmove();};
classCFish{public:voidswim();voidmove();};
voidCBird::fly(){printf("Icanfly\n");}
voidCBird::move(){printf("birdcanmove\n");}
voidCFish::swim(){printf("Icanswim\n");}
voidCFish::move(){printf("fishcanmove\n");}课件制作人:宋坤多重继承{public:voidrun();};
voidCWaterbird::run(){printf("waterbirdcanrun\n");}
intmain(intargc,char*argv[]){CWaterbirdwaterbird;waterbird.fly();//调用CBird的fly方法
waterbird.swim();//调用CFish的swim方法
return0;}在上面的代码中,CWaterbird派生于CBird和CFish,因此继承了CBird的fly方法和CFish的swim方法。课件制作人:宋坤虚继承采用多重继承会带来许多问题。例如,CBird和CFish中存在相同的方法move,子类CWaterbird在调用move方法时会调用哪个父类的方法呢?waterbird.move();答案是编译器会产生错误,你必须指明具体调用类的方法。waterbird.CBird::move();waterbird.CFish::move();采用多重继承还会带来一个问题。如果CBird和CFish派生于同一个基类CAnimal,那么CWaterbird类中就会有两个CAnimal拷贝,如果CWaterbird类没有覆盖CAnimal中的方法,如何调用CAnimal中的方法呢?如果直接调用CAnimal中的方法,会出现编译错误,编译器不知道调用的是CBird父类的方法,还是CFish父类的方法。在调用CAnimal中的方法时,需要指定类名称。
课件制作人:宋坤虚继承classCAnimal{
public:voidbreath();};voidCAnimal::breath(){printf("animalcanbreath\n");}classCBird:publicCAnimal{public:
voidfly();voidmove();};classCFish:publicCAnimal{public:
voidswim();voidmove();};voidCBird::fly(){printf("Icanfly\n");}课件制作人:宋坤虚继承voidCBird::move(){printf("birdcanmove\n");}voidCFish::swim(){printf("Icanswim\n");}voidCFish::move(){printf("fishcanmove\n");}classCWaterbird:publicCBird,publicCFish{public:voidrun();};voidCWaterbird::run(){printf("waterbirdcanrun\n");}intmain(intargc,char*argv[]){CWaterbirdwaterbird;
waterbird.CBird::breath();return0;}课件制作人:宋坤虚继承在上面的代码中,CWaterbird调用CAnimal中的breath方法,需要明确指定父类,因为CWaterbird的两个父类CBird、CFish均派生于CAnimal,CWaterbird类对象中存在两个CAnimal拷贝。许多读者会问:是否能够使CWaterbird类对象中只存在一个CAnimal对象呢?这样,就可以直接调用CAnimal中的方法了。C++中可以采用虚继承的方式实现。用virtual关键字进行的继承叫虚继承,当进行虚继承时,编译器会通过指针对其进行处理,使其只能产生一个基类子对象,这样在编译器就不会产生错误。语法:class类名称:virtual访问限定符基类名称在上面代码中,只要在CBird和CFish的声明中使用virtual关键字就可以了。classCAnimal{public:voidbreath();};voidCAnimal::breath(){printf("animalcanbreath\n");}classCBird:virtualpublicCAnimal{public:CBird(inti);voidfly();voidmove();};
课件制作人:宋坤虚继承CBird::CBird(inti){
}classCFish:virtualpublicCAnimal{public:voidswim();voidmove();};voidCBird::fly(){printf("Icanfly\n");}voidCBird::move(){printf("birdcanmove\n");}voidCFish::swim(){printf("Icanswim\n");}voidCFish::move(){printf("fishcanmove\n");}课件制作人:宋坤虚继承classCWaterbird:publicCBird,publicCFish{public:CWaterbird();voidrun();};CWaterbird::CWaterbird():CBird(1){
}
voidCWaterbird::run(){printf("waterbirdcanrun\n");}
intmain(intargc,char*argv[]){CWaterbirdwaterbird;
waterbird.breath();return0;}在上述代码中,由于采用了虚继承,CWaterbird类对象中只有一个CAnimal的拷贝,因此可以直接调用CAnimal中的方法,编译器不会产生歧义。
课件制作人:宋坤3.3类的高级方法
继承是一种编程技术,可以从现有类中构造一个新类,通过新类来实现面向对象的程序设计。本节将从浅入深地介绍类的继承。
内联方法静态数据成员和静态方法友元类和友元函数const方法运算符重载√√√√√课件制作人:宋坤内联方法
在C++中,你可以编写像内联函数一样的内联方法,只需要在方法返回值前添加inline关键字。下面的代码定义了一个CMan类,在该类中定义了一个内联方法。classCMan{public:voidrun();};inlinevoidCMan::run(){printf("mancanrun\n");}也可以将方法的定义放在类的声明中,此时该方法自动成为内联方法。classCMan{public:voidrun(){printf("mancanrun\n");}//inline方法};方法run自动成为内联方法。课件制作人:宋坤静态数据成员和静态方法
在C++中可以定义静态成员数据,所谓静态成员数据是指其数据属于类的,通过类名就可以访问成员数据。在声明类成员数据时,只要在类型前添加static关键字,该成员数据就变为静态成员数据。下面的代码定义了一个CMan类,在该类中定义了一个整型静态成员变量。classCMan{public:staticintdata;};如前所述,静态数据成员不仅可以由对象访问,也可以由类访问。下面的代码演示了如何通过类名设置和访问静态成员数据。classCMan{public:staticintdata;};intCMan::data=1;//初始化静态成员intmain(intargc,char*argv[]){CMan::data=10;printf("%d",CMan::data);
return0;}//运行结果:10课件制作人:宋坤静态数据成员和静态方法静态成员数据被多个对象所共有,无论定义多少个对象,内存中的静态成员只有一个。因此,当一个对象修改了静态成员数据,另一个对象在访问静态成员数据时,也会发生变化。classCMan{public:staticintdata;};
intCMan::data=1;
intmain(intargc,char*argv[]){CManman1,man2;man1.data=5;
printf("%d",man2.data);printf("\n");
man2.data=10;printf("%d",man1.data);printf("\n");return0;}//运行结果:510在上面的代码中,当设置man2的data成员时,实际上也是设置man1的data成员,因为data是CMan的静态成员变量,man1和man2以及所有CMan类的对象都共享一个data成员变量。课件制作人:宋坤静态数据成员和静态方法静态方法与静态成员数据一样,可以由类直接调用。声明静态方法与声明普通方法相似,只是在方法返回值类型前添加static关键字。下面的代码定义了一个CMan类,在该类中定义了一个静态成员变量和一个静态成员方法。classCMan{public:staticintdata;staticintgetdata();//静态方法};
intCMan::getdata(){returndata;}
intCMan::data=1;
intmain(intargc,char*argv[]){CManman1;man1.data=5;printf("%d",CMan::getdata());//由类名直接调用静态方法
printf("\n");return0;}//运行结果:5课件制作人:宋坤友元类和友元函数在开发应用程序时,一个类经常将另一个类的对象作为自己的成员。这样,在该类中就可以访问另一个类的公有(public)数据和方法了。但是,有些时候需要访问另一个类中的私有数据和方法,该如何实现呢?C++中提供了友元类,在类A中将类B声明为一个友元类,那么在类B中就可以访问类A中的私有数据和方法了。下面的代码定义了一个CHand类和一个CMan类。在CHand类中,将CMan声明为自己的友元类,这样,在CMan中就可以访问CHand中的私有数据和方法了。
classCHand{private:intfingernum;voidwrite();public:friendclassCMan;//将CMan作为自己的友元类
CHand();};
classCMan{public:CHandhand;intgetfingernum();voidhandwrite();};课件制作人:宋坤友元类和友元函数intCMan::getfingernum(){returnhand.fingernum;//访问CHand中的私有数据}voidCMan::handwrite(){hand.write();//调用CHand中的私有方法}CHand::CHand(){fingernum=5;}voidCHand::write(){printf("handcanwrite\n");}intmain(intargc,char*argv[]){CManman;
printf("%d",man.getfingernum());printf("\n");man.handwrite();return0;}课件制作人:宋坤const方法在声明类的方法时,可以使用const关键字。这样,该方法就不会改变类中的任何数据成员。对于类中的一些只读方法,应将其声明为const方法。例如:intgetheight()const;在开发程序时,对于不想改变类成员数据的函数,应将其声明为const方法,这样,编译器会帮助你发现错误。在下面的代码中,定义了一个CMan类,在该类中定义了两个const方法,getheight与getweight。在getheight方法中修改了对象的成员变量,出现了编译错误。定义const方法实现成员保护classCMan{private: intheight; intage; intweight;public: intgetheight()const; intgetage()const; intgetweight()const; CMan(); ~CMan();};课件制作人:宋坤const方法CMan::CMan(){ height=0; age=0; weight=0;}
CMan::~CMan(){
}
intCMan::getheight()const{
height=168;//错误的代码,在const方法中不能够修改height成员变量
returnheight;}
intCMan::getage()const{ returnage;}
intCMan::getweight()const{ returnweight;}课件制作人:宋坤运算符重载C++提供了许多内置数据类型,例如整型、实型、字符型等,也提供了相应的运算符(+、-、>、<等),进行数据的数学运算和逻辑运算。是否能够在类对象间进行这些运算呢?C++提供了运算符重载的功能,在可以在类中重载某个运算符,这样就可以运算符作用与类对象。运算符重载的格式为:返回值类型
operator运算符
参数列表。例如:intoperator++();重载++运算符实现自加操作
classCMan{private: intheight;public: intgetheight()const; CMan(); ~CMan(); intoperator++();};intCMan::operator++(){ return++height;}CMan::CMan(){ height=0;}CMan::~CMan(){
}课件制作人:宋坤运算符重载intCMan::getheight()const{ returnheight;}intmain(intargc,char*argv[]){ CManman;
inti=++man; printf("%d",i); printf("\n");
i=++man; printf("%d",man.getheight()); return0;}//运行结果:12在开发程序时,经常重载+运算符实现两个对象的加法运算,并返回结果(对象)。示例:在下面的代码中,定义了一个CMan类,在该类中重载+运算符,并返回一个CMan对象。这样,在程序中就可以实现两个CMan对象相加了。重载+运算符,实现两个CMan对象的相加
课件制作人:宋坤运算符重载classCMan{private: intheight; intage;public: intgetheight()const; intgetage()const; CMan(); ~CMan(); CManoperator+(CMan&one);};CManCMan::operator+(CMan&one){
CMan*temp=newCMan; temp->age=age+one.getage(); temp->height=height+one.getheight(); return*temp;}CMan::CMan(){ height=100; age=10;}CMan::~CMan(){
}课件制作人:宋坤运算符重载intCMan::getheight()const{ returnheight;}
intmain(intargc,char*argv[]){ CManman1,man2,man3;
man3=man1+man2; printf("%d",man3.getage()); printf("\n"); printf("%d",man3.getheight()); return0;}//运行结果:20200课件制作人:宋坤3.4类模板类模板的定义及应用定义类模板的静态数据成员√√
函数模板为函数的参数、返回值等提供了动态参数化的机制,使得用户能够动态设置函数的参数类型和返回值类型。而类模板能够为类的数据成员、成员函数的参数、返回值提供动态参数化的机制,使得用户可以方便地设计出功能更为灵活的类。本节将介绍有关类模板的相关知识。
课件制作人:宋坤类模板的定义及应用
在介绍类模板之前,先来设计一个简单的单向链表。链表的功能包括向尾节点添加数据,遍历链表中的节点,在链表结束时释放所有节点。代码如下:定义单向链表
classCNode //定义一个节点类{public: CNode*m_pNext; //定义一个节点指针,指向下一个节点
intm_Data; //定义节点的数据
CNode() //定义节点类的构造函数 {
m_pNext=NULL; //将m_pNext设置为空 }};classCList //定义链表类CList类{private: CNode*m_pHeader; //定义头节点
intm_NodeSum; //节点数量public: CList() //定义链表的构造函数 {
m_pHeader=NULL; //初始化m_pHeader m_NodeSum=0; //初始化m_NodeSum }课件制作人:宋坤类模板的定义及应用CNode*MoveTrail() //移动到尾节点 {
CNode*pTmp=m_pHeader; //定义一个临时节点,将其指向头节点
for(inti=1;i<m_NodeSum;i++) //遍历节点 {
pTmp=pTmp->m_pNext; //获取下一个节点 }
returnpTmp; //返回尾节点 }
voidAddNode(CNode*pNode) //添加节点 {
if(m_NodeSum==0) //判断链表是否为空 {
m_pHeader=pNode;//将节点添加到头节点中 }
else //链表不为空 {
CNode*pTrail=MoveTrail(); //搜索尾节点
pTrail->m_pNext=pNode; //在尾节点处添加节点 }
m_NodeSum++; //使链表节点数量加1 }
voidPassList() //遍历链表 {
if(m_NodeSum>0) //判断链表是否为空 {
CNode*pTmp=m_pHeader; //定义一个临时节点,将其指向头节点
printf("%4d",pTmp->m_Data); //输出节点数据
for(inti=1;i<m_NodeSum;i++) //遍历其他节点 {
pTmp=pTmp->m_pNext; //获取下一个节点
printf("%4d",pTmp->m_Data); //输出节点数据 } } }课件制作人:宋坤类模板的定义及应用~CList() //定义链表析构函数 {
if(m_NodeSum>0) //链表不为空 {
CNode*pDelete=m_pHeader; //定义一个临时节点,指向头节点
CNode*pTmp=NULL; //定义一个临时节点
for(inti=0;i<m_NodeSum;i++) //遍历节点 {
pTmp=pDelete->m_pNext; //获取下一个节点
deletepDelete; //释放当前节点
pDelete=pTmp; //将下一个节点设置为当前节点 }
m_NodeSum=0; //将m_NodeSum设置为0
pDelete=NULL; //将pDelete置为空
pTmp=NULL; //将pTmp置为空 }
m_pHeader=NULL;
//将m_pHeader置为空 }};下面定义一个链表对象,向其中添加节点,并遍历链表节点。代码如下:遍历单向链表intmain(intargc,char*argv[]){ CListlist; //定义链表对象
for(inti=0;i<5;i++) //利用循环向链表中添加5个节点 {
CNode*pNode=newCNode(); //构造节点对象
pNode->m_Data=i; //设置节点数据
list.AddNode(pNode); //添加节点到链表 }
list.PassList(); //遍历节点
printf("\n"); //输出换行
return0;}课件制作人:宋坤类模板的定义及应用执行上述代码,效果如图3-2所示。
分析上述代码中定义的链表类CList,一个最大缺陷就是链表不够灵活,其节点只能是CNode类型。为了让CList能够适应各种类型的节点,一个最简单的方法就是使用类模板。类模板的定义与函数模板类似,以关键字template开始,其后是由尖括号<>构成的模板参数。下面重新修改链表类CList,以类模板的形式进行改写。利用类模板设计链表类template<classType> //定义类模板classCList //定义CList类{private: Type*m_pHeader; //定义头节点
intm_NodeSum; //节点数量public: CList() //定义构造函数 {
m_pHeader=NULL; //将m_pHeader置为空
m_NodeSum=0; //将m_NodeSum置为0 }课件制作人:宋坤类模板的定义及应用Type*MoveTrail() /获取尾节点 {
Type*pTmp=m_pHeader; //定义一个临时节点,将其指向头节点
for(inti=1;i<m_NodeSum;i++) //遍历链表 {
pTmp=pTmp->m_pNext; //将下一个节点指向当前节点 }
returnpTmp; //返回尾节点 }
voidAddNode(Type*pNode) //添加节点 {
if(m_NodeSum==0) //判断链表是否为空 {
m_pHeader=pNode; //在头节点处添加节点 }
else //链表不为空 {
Type*pTrail=MoveTrail(); //获取尾节点
pTrail->m_pNext=pNode; //在尾节点处添加节点 }
m_NodeSum++; //使节点数量加1 }
voidPassList() //遍历链表 {
if(m_NodeSum>0) //判断链表是否为空 {
Type*pTmp=m_pHeader; //定义一个临时节点,将其指向头节点
printf("%4d",pTmp->m_Data); //输出头节点数据
for(inti=1;i<m_NodeSum;i++) //利用循环访问节点 {
pTmp=pTmp->m_pNext; //获取下一个节点
printf("%4d",pTmp->m_Data); //输出节点数据 } } }课件制作人:宋坤类模板的定义及应用~CList() //定义析构函数 {
if(m_NodeSum>0) //判断链表是否为空 {
Type*pDelete=m_pHeader; //定义一个临时节点,将其指向头节点
Type*pTmp=NULL; //定义一个临时节点
for(inti=0;i<m_NodeSum;i++) //利用循环遍历所有节点 {
pTmp=pDelete->m_pNext;//将下一个节点指向当前节点
deletepDelete; //释放当前节点
pDelete=pTmp; //将当前节点指向下一个节点 }
m_NodeSum=0; //设置节点数量为0
pDelete=NULL; //将pDelete置为空
pTmp=NULL; //将pTmp置为空 }
m_pHeader=NULL; //将m_pHeader置为空 }};上述代码利用类模板对链表类CList进行了修改,实际上是在原来链表的基础上将链表中出现CNode类型的地方替换为模板参数Type。下面再定义一个节点类CNet,演示模板类CList是如何适应不同的节点类型。演示类模板适应不同的节点类型classCNet //定义一个节点类{public: CNet*m_pNext; //定义一个节点类指针
charm_Data; //定义节点类的数据成员
CNet() //定义构造函数 {
m_pNext=NULL; //将m_pNext置为空 }};
课件制作人:宋坤类模板的定义及应用intmain(intargc,char*argv[]){
CList<CNode>nodelist;
//构造一个类模板实例
for(intn=0;n<5;n++) //利用循环向链表中添加节点 {
CNode*pNode=newCNode(); //创建节点对象
pNode->m_Data=n; //设置节点数据
nodelist.AddNode(pNode); //向链表中添加节点 }
nodelist.PassList(); //遍历链表
printf("\n"); //输出换行
CList<CNet>netlist; //构造一个类模板实例
for(inti=0;i<5;i++) //利用循环向链表中添加节点 {
CNet*pNode=newCNet(); //创建节点对象
pNode->m_Data=97+i; //设置节点数据
netlist.AddNode(pNode); //向链表中添加节点 }
netlist.PassList(); //遍历链表
printf("\n"); //输出换行
return0;}课件制作人:宋坤类模板的定义及应用执行上述代码,效果如图3-3所示。
类模板CList虽然能够使用不同类型的节点,但是对节点的类型是有一定要求的。第一,节点类必须包含一个指向自身的指针类型成员m_pNext,因为在CList中访问了m_pNext成员。第二,节点类中必须包含数据成员m_Data,其类型被限制为数字类型或有序类型。实际上m_Data成员可以是任意类型,只是在CList类的PassList方法中,本教材为了演示遍历链表节点,使用了“printf("%4d",pTmp->m_Data);”语句输出节点数据,导致m_Data只能是数字类型或有序类型。课件制作人:宋坤定义类模板的静态数据成员在类模板中用户也可以定义静态的数据成员,只是类模板中的每个实例都有自己的静态数据成员,而不是所有的类模板实例共享静态数据成员。为了说明这一点,本教材对模板类CList进行简化,向其中添加一个静态数据成员,并初始化进行静态数据成员。在类模板中使用静态数据成员template<classType> //定义一个模板类classCList //定义CList类{private: Type*m_pHeader; //定义头节点
intm_NodeSum; //节点数量public:
staticintm_ListValue; //定义静态数据成员
CList() //定义构造函数 {
m_pHeader=NULL; //将m_pHeader置为空
m_NodeSum=0; //将m_NodeSum置为0 }};template<classType>intCList<Type>::m_ListValue=10; //初始化静态数据成员
课件制作人:宋坤定义类模板的静态数据成员从图3-4中可以发现,模板实例nodelist和netlist均有各自的静态数据成员。但是,对于同一类型的模板实例,其静态数据成员是共享的。例如:同一类型的模板实例,其静态数据成员是共享的intmain(intargc,char*argv[]) //主函数{
CList<CNode>
nodelist; //定义模板实例
nodelist.m_ListValue=2008; //设置静态数据成员
CList<CNode>list; //定义模板实例
list.m_ListValue=88; //设置静态数据成员
printf("%d\n",nodelist.m_ListValue); //输出数据成员
printf("%d\n",list.m_ListValue); //输出数据成员
return0;}执行上述代码,效果如图3-5所示。
从图3-5中可以发现,类模板实例nodelist和list共享静态数据成员,因为她们的模板类均为CNode。
课件制作人:宋坤定义类模板的静态数据成员下面定义两个模板实例,分别设置并访问各自的静态数据成员。代码如下:intmain(intargc,char*argv[]) //主函数{
CList<CNode>nodelist; //实例化类模板
nodelist.m_ListValue=2008; //设置静态数据成员
CList<CNet>netlist; //实例化类模板
netlist.m_ListValue=88; //设置静态数据成员
printf("%d\n",nodelist.m_ListValue); //输出静态数据成员
printf("%d\n",netlist.m_ListValue); //输出静态数据成员
return0;}执行上述代码,效果如图3-4所示。课件制作人:宋坤3.5异常处理异常是指程序运行时出现的非正常的情况。例如,访问指针地址无效、除零错误等。为了防止程序因出现异常而中断,C++语言提供了异常处理语句,使得用户有机会对出现的异常进行处理,增强程序的健壮性。本节将介绍有关C++语言异常处理的相关知识。异常捕捉语句抛出异常√√课件制作人:宋坤异常捕捉语句在C++语言中,为了处理异常,提供了try和catch语句。try语句和catch语句实际上是一个语句块,try语句块包含的是可能产生异常的代码,catch语句块包含的是处理异常的代码。下面编写一段代码演示try和catch语句的使用。try //try语句块
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2026年浙江省部编版小学英语三年级上册第8单元听力练习题
- 运输二队员工不安全行汇编
- 全球气候变暖背景下黄大豆单产波动与投资风险重估
- 从流体动力学仿真看PB弯头适配器对超纯水系统良率的隐性影响
- 人工智能交互界面嵌入对桌面多用途座产品定义权的争夺
- ESG评级体系嵌入电镀拉手项目投资决策的量化影响路径
- AP抗氧化剂专利悬崖期后的市场格局重塑与投资窗口
- 2026年湖南铁道职业技术学院高职单招笔试英语试题库含答案解析3套试卷
- 2026年湖南有色金属职业技术学院高职单招笔试化学试题库含答案解析2套试卷
- 2026年湖南体育职业学院高职单招笔试职业技能测验试题库含答案解析3套试卷
- 【新教材】2026秋人教PEP版六年级上册英语全册教案(含教学计划)
- 2026年高考地理全国卷真题卷附答案
- 2.1牢记初心使命 2026-2027学年统编版道德与法治 九年级上册
- 2026新版人教版PEP小学英语六年级上册单词表
- GB/T 47949-2026资产管理数据资产分类与代码
- 2026年固态变压器(SST)行业产业链全景图谱及上下游投资机会分析
- 《生态环境法典》培训
- 2026-2030中国高纯铝市场深度调查研究报告
- 每年5万吨环保新材料EGDA生产项目原材料生产装置技术改造项目可行性研究报告模板拿地申报
- 风电场设备故障诊断与维修技术方案
- 新部编八年级语文上册第一单元新闻联读(大单元教学课件)
评论
0/150
提交评论