抽象基类和纯虚函数
C--程序设计--第10章-多态性及虚函数
使用重载函数注意:
不要使用重载函数描述不相干的函数 在类中,构造函数和普通成员函数均可以
重载 避免与函数的默认参数产生二义性
二、运算符重载
运算符重载(operate overloading)就是 赋予已有的运算符多重含义。
运算符重载实质是函数重载,运算符重载 的选择与函数重载类似,会根据运算符的 操作数的类型、个数和顺序来进行运算符 函数的选择。
#include<iostream.h> str#iinngc:l:usdter<isntgr(icnhga.rh>*s) v{}ossccsssc{s{{ittohtttolsstlsssls*drruarrrueptrepttepsi1trii3tc{pn=rin=rrn=pmn.<nn.<lprgncngncign=agp<*ggp<auitepgtepnte'irssrssbv\hwy:hwyghwnsit1ssitsla0=(:=(:=(tnr=ttnrit'scssscs:sc)rt1"rrt3scesss~ivci;thpt1hpsih1(.T23(.t::tttsnohn}ra,r.a,tza()gh(()grrrrttiatlrsilrsrer";eass;eiiiirdre[)ne[1i;[Ttt1ttnnnniglnl;gnl.nlhl)rlggggnep*e(e}(gesgeiei;2e(((gtrsnsnstnp(nsns)ncsi(lipg)gthg)ig(;(htn)en;t;tr;t;nti)a)artnthhih}ths<<ri{((;+n++<p<snd))}1g1s1aere*ige;]]i]nonszl{{;&;z;ddgd)&eercseelrl;s=teo1)m;a;/18etu)om/)0ut..;)构sr<""/;pn<造);//;s;/复}lp函构e<制n<数造ge构tn函hd造;l数};重} 载
C++基抽象类的构造析构(纯)虚函数
C++基抽象类的构造析构(纯)虚函数⼀、析构函数可定义为⼀、析构函数可定义为纯虚函数纯虚函数,但也必须给出函数定义,但也必须给出函数定义 Effective C++ 条歀07: 为多态基类声明virtual 析构函数(Declare destructors virtual in polymorphic base classes ) 在某些类⾥声明纯虚析构函数很⽅便。
纯虚函数将产⽣抽象类——不能实例化的类(即不能创建此类型的对象)。
有些时候,你想使⼀个类成为抽象类,但刚好⼜没有任何纯虚函数。
怎么办?因为抽象类是准备被⽤做基类的,基类必须要有⼀个虚析构函数,纯虚函数会产⽣抽象类,所以⽅法很简单:在想要成为抽象类的类⾥声明⼀个纯虚析构函数。
1 //这⾥是⼀个例⼦:2 class awov {3 public :4 virtual ~awov() = 0; // 声明⼀个纯虚析构函数5 }; 这个类有⼀个纯虚函数,所以它是抽象的,⽽且它有⼀个虚析构函数,所以不会产⽣析构函数问题。
但这⾥还有⼀件事:必须提供纯虚析构函数的定义: awov::~awov() { ... } // 纯虚析构函数的定义 这个定义是必需的,因为虚析构函数⼯作的⽅式是:最底层的派⽣类的析构函数最先被调⽤,然后各个基类的析构函数被调⽤。
这就是说,即使是抽象类,编译器也要产⽣对~awov 的调⽤,所以要保证为它提供函数体。
如果不这么做,链接器就会检测出来,最后还是得回去把它添上。
⼆、? 关于C++为什么不⽀持虚拟构造函数,Bjarne 很早以前就在C++Style and Technique FAQ ⾥⾯做过回答 Avirtual call is a mechanism to get work done given partialinformation. In particular, "virtual" allows us to call afunction knowing only an interfaces and not the exact type of theobject. To create an object you need complete information.Inparticular, you need to know the exact type of what you want tocreate. Consequently, a "call to a constructor" cannot bevirtual. 含义⼤概是这样的:虚函数调⽤是在部分信息下完成⼯作的机制,允许我们只知道接⼝⽽不知道对象的确切类型。
虚函数以及纯虚函数
虚函数以及纯虚函数 多态性是将接⼝与实现进⾏分离;⽤形象的语⾔来解释就是实现以共同的⽅法,但因个体差异,⽽采⽤不同的策略。
虚函数和纯虚函数都是实现多态的重要⽅法。
本⽂就这两种⽅法进⾏分析以及⽐较1、虚函数在基类中声明为virtual并在⼀个或者多个派⽣类被重新定义的成员函数语法规则:virtual 函数返回类型函数名(参数表) {函数体}语法分析:虚函数的声明和定义和普通的成员函数⼀样,只是在返回值之前加⼊了关键字virtual。
在基类当中定义了虚函数,可以再⼦类中定义和基类中相同函数名、相同参数、相同返回值和不同实现体的虚函数 定义为虚函数是为了让基类函数的指针或者引⽤来指向⼦类。
#include<iostream>using namespace std;class A{public:void fun(){cout << "A::fun()..." << endl;}};class B :public A{public:void fun(){cout << "B::fun()...." << endl;}};int main(){A *a = new A; //A类指针指向A类对象a->fun();A *b = new B; //A类指针指向B类对象b->fun();delete a;delete b;return0;}分析代码:在上述代码中B为A的派⽣类,A *b=new B 是将基类的指针指向B 类对象。
输出为:显然程序没有实现我们想要的输出#include<iostream>using namespace std;class A{public:virtual void fun(){cout << "A::fun()..." << endl;}};class B :public A{public:void fun(){cout << "B::fun()...." << endl;}};int main(){A *a = new A; //A类指针指向A类对象a->fun();A *b = new B; //A类指针指向B类对象b->fun();delete a;delete b;return0;}分析:可以看出利⽤虚函数可以实现多态,也就是说实现了通过不同对象的接⼝实现了不同的功能。
C++14
14.1什么是虚函数
静态绑定是指绑定的是对象的静态类型,某 特性(比如函数)依赖于对象的静态类型, 发生在编译期。动态绑定:绑定的是对象的 动态类型,某特性(比如函数)依赖于对象 的动态类型,发生在运行期。
14.2 抽象类与纯虚函数
在C++中,在许多情况下,在基类中不能对 虚函数给出有意义有实现,而把它说明为纯 虚函数,它的实现留给该基类的派生类去做 。带有纯虚函数的类称为抽象类。下面,详 细介绍下纯虚函数和抽象类。 class <类名> { virtual <类型><函数名>(<参数表>)=0; };
14.4 虚函数表
虚函数(Virtual Function)是通过一张虚 函数表(Virtual Table)来实现的。简称为 V-Table。 在这个表中,主是要一个类的虚 函数的地址表,这张表解决了继承、覆盖的 问题,保证其容真实反应实际的函数。这样 ,在有虚函数的类的实例中这个表被分配在 了 这个实例的内存中,所以,当用父类的 指针来操作一个子类的时候,这张虚函数表 就显得由为重要了,它就像一个地图一样, 指明了实际所应该调用的函数。
14.2 抽象类与纯虚函数
抽象类是一种特殊的类,它是为了抽象和设 计的目的而建立的,它处于继承层次结构的 较上层。抽象类是不能定义对象的,在实际 中为了强调一个类是抽象类,可将该类的构 造函数说明为保护的访问控制权限。
14.3多重继承时抽象类的应用
在多重继承中,以抽象类作为基类,不实现 抽象类中的方法。比较在上例中,先定义汽 车和船的抽象类,在定义汽陆两用船时机可 以多重继承,然后具体实现各个抽象类的方 法。
第14章 虚函数和抽象类
ቤተ መጻሕፍቲ ባይዱ
抽象类与纯虚函数
纯虚函数和抽象类:含有纯虚函数的类是抽象类,不能生成对象,只能派生。
他派生的类的纯虚函数没有被改写,那么,它的派生类还是个抽象类。
定义纯虚函数就是为了让基类不可实例化化,因为实例化这样的抽象数据结构本身并没有意义.或者给出实现也没有意义一. 纯虚函数在许多情况下,在基类中不能给出有意义的虚函数定义,这时可以把它说明成纯虚函数,把它的定义留给派生类来做。
定义纯虚函数的一般形式为:class 类名{virtual 返回值类型函数名(参数表)= 0; // 后面的"= 0"是必须的,否则,就成虚函数了};纯虚函数是一个在基类中说明的虚函数,它在基类中没有定义,要求任何派生类都定义自己的版本。
纯虚函数为各派生类提供一个公共界面。
从基类继承来的纯虚函数,在派生类中仍是虚函数。
二. 抽象类1. 如果一个类中至少有一个纯虚函数,那么这个类被称为抽象类(abstract class)。
抽象类中不仅包括纯虚函数,也可包括虚函数。
抽象类中的纯虚函数可能是在抽象类中定义的,也可能是从它的抽象基类中继承下来且重定义的。
2. 抽象类特点,即抽象类必须用作派生其他类的基类,而不能用于直接创建对象实例。
一个抽象类不可以用来创建对象,只能用来为派生类提供一个接口规范,派生类中必须重载基类中的纯虚函数,否则它仍将被看作一个抽象类。
3. 在effective c++上中提到,纯虚函数可以被实现(定义),但是,不能创建对象实例,这也体现了抽象类的概念。
三. 虚析构函数虚析构函数: 在析构函数前面加上关键字virtual进行说明,称该析构函数为虚析构函数。
虽然构造函数不能被声明为虚函数,但析构函数可以被声明为虚函数。
一般来说,如果一个类中定义了虚函数,析构函数也应该定义为虚析构函数。
例如:class B{virtual ~B(); //虚析构函数…};关于更多的精彩解释,请参考<< c++编程思想 >> 一书。
虚函数与纯虚函数的区别
虚函数与纯虚函数的区别1. 虚函数和纯虚函数可以定义在同⼀个类(class)中,含有纯虚函数的类被称为抽象类(abstract class),⽽只含有虚函数的类(class)不能被称为抽象类(abstract class)。
2. 虚函数可以被直接使⽤,也可以被⼦类(sub class)重载以后以多态的形式调⽤,⽽纯虚函数必须在⼦类(sub class)中实现该函数才可以使⽤,因为纯虚函数在基类(base class)只有声明⽽没有定义。
3. 虚函数和纯虚函数都可以在⼦类(sub class)中被重载,以多态的形式被调⽤。
4. 虚函数和纯虚函数通常存在于抽象基类(abstract base class -ABC)之中,被继承的⼦类重载,⽬的是提供⼀个统⼀的接⼝。
5. 虚函数的定义形式:virtual {method body} 纯虚函数的定义形式:virtual { } = 0;在虚函数和纯虚函数的定义中不能有static标识符,原因很简单,被static修饰的函数在编译时候要求前期bind,然⽽虚函数却是动态绑定(run-time bind),⽽且被两者修饰的函数⽣命周期(life recycle)也不⼀样。
6. 虚函数必须实现,如果不实现,编译器将报错,错误提⽰为:error LNK****: unresolved external symbol "public: virtual void __thiscallClassName::virtualFunctionName(void)"7. 对于虚函数来说,⽗类和⼦类都有各⾃的版本。
由多态⽅式调⽤的时候动态绑定。
8. 实现了纯虚函数的⼦类,该纯虚函数在⼦类中就编程了虚函数,⼦类的⼦类即孙⼦类可以覆盖该虚函数,由多态⽅式调⽤的时候动态绑定。
9. 虚函数是C++中⽤于实现多态(polymorphism)的机制。
核⼼理念就是通过基类访问派⽣类定义的函数10. 多态性指相同对象收到不同消息或不同对象收到相同消息时产⽣不同的实现动作。
纯虚函数 空函数
纯虚函数空函数一、纯虚函数纯虚函数是指在基类中声明但没有定义的虚函数,它的作用是为派生类提供一个接口,派生类必须实现这个函数。
纯虚函数的声明语法为:virtual 返回类型函数名(参数列表) =0;其中“=0”表示该函数为纯虚函数。
纯虚函数的特点:1.没有函数体。
在基类中声明但没有提供函数的具体实现,从而使得基类成为了抽象类,不能被实例化。
2.继承。
子类必须实现纯虚函数,否则也将成为抽象类,无法被实例化。
3.多态性。
子类中实现了基类的纯虚函数后,可以通过基类指针调用子类的实现。
1.抽象类。
基类中有至少一个纯虚函数时,该基类就成为了抽象类。
抽象类不能被实例化,只能被其他类继承和实现。
2.接口。
纯虚函数提供了一种接口,规定了子类必须实现的方法。
这种方法被称为“接口”。
让我们创建一个基类Figure,定义一个纯虚函数area(),用于计算图形的面积。
代码如下:class Figure{public:virtual double area() = 0;};class Circle : public Figure{public:Circle(double r){radius = r;}double area(){return 3.1415926 * radius * radius; // 计算圆的面积}private:double radius;};使用上述代码创建一个程序,可以通过基类指针调用子类实现的结果。
代码如下:以上程序会输出圆的面积,结果如下:Circle's area is:314.15926二、空函数空函数是指没有任何实际功能的函数,用于占位或在后续开发中替换为有用的函数。
空函数的定义语法为:void 函数名(){}1.通常没有函数体,函数体中只有一个空语句,表示不需要执行任何操作。
2.占位。
空函数可以用作占位函数来占据函数列表中的某些位置,等待日后补充功能。
3.代码兼容性。
空函数可以提高代码的兼容性,当代码需要调用某个函数时,即使函数还未完成,也可以使用空函数来代替。
%AB%98级语言C++程序设计(第一版)-第八章
202第八章继承与派生(Inheritance and Derive)第八章 继承与派生(Inheritance and Derive) C++程序用不同的类定义来表示一组数据及对这些数据的操作,往往在不同的类之间有某种关系,除了上一章介绍的包含关系和友元关系之外,更多的是继承与派生关系。
例如,一个公司或工厂的计算机管理系统,与设备有关的数据,如设备登号、设备购入时间、设备价值等数据及若干操作的可以构成一个类。
交通工具是设备中的一类,它除了作为设备具有一般性之外,又会有一些作为交通工具的特定数据及操作,如它应有由公安机关发给的牌照号等。
又如汽车,它是交通工具的一种。
司机姓名、牌号,可能是它特有的数据。
而货车、轿车、大客车又是汽车中的不同集合,与它们分别相关的又会有不少数据,如轿车的使用人姓名、货车的吨位、客车的载人数等等,都是它们特有的数据项。
把与设备,交通工具,汽车,轿车,货车,客车相关的数据及操作定义为互不相关的独立的类是不科学的,同时,也会造成许多重复内容,例如,所有这些类,都包括同一个数据成员:设备登记号、购入时间等等。
不同类的定义应反映出类之间的相关关系,反映出上面例子中的层次关系。
C++语言中提供了类定义的派生和继承的功能,很好地解决了上面提出的问题。
两个类之间的继承关系,若类A是类B的基类,则类B是类A的派生类。
我们首先从下面的实例中学习如何建立类与类之间的继承关系。
8.1 公司雇员档案的管理 公司中的雇员档案的管理是整个公司的管理系统的一个组成部分,其雇员的档案数据内容一般根据雇员在公司中的不同职责和位置而有所区别。
除了一般雇员之外,可能还有管理人员、工程技术人员和高级主管,这些人员也是雇8.1 公司雇员档案的管理203员,但他们又有其特殊性,在数据库中又必须保存某些特定的信息,例如管理人员有级别信息,技术人员有学位、专业信息等等。
因此,在管理软件中所设计的类应反映其层次关系和特殊性,下面的程序是一个简化了的层次模块结构。
C++之普通成员函数、虚函数以及纯虚函数的区别与用法要点
C++之普通成员函数、虚函数以及纯虚函数的区别与⽤法要点普通成员函数是静态编译的,没有运⾏时多态,只会根据指针或引⽤的“字⾯值”类对象,调⽤⾃⼰的普通函数;虚函数为了重载和多态的需要,在基类中定义的,即便定义为空;纯虚函数是在基类中声明的虚函数,它可以再基类中有定义,且派⽣类必须定义⾃⼰的实现⽅法。
假设我们有三个类Person、Teacher、Student它们之间的关系如下:类的关系图普通成员函数【Demo1】根据这个类图,我们有下⾯的代码实现#ifndef __OBJEDT_H__#define __OBJEDT_H__#include <string>#include <iostream>class Person{public:Person(const string& name, int age) : m_name(name), m_age(age){}void ShowInfo(){cout << "姓名:" << m_name << endl;cout << "年龄:" << m_age << endl;}protected:string m_name; //姓名int m_age; //年龄};class Teacher : public Person{public:Teacher(const string& name, int age, const string& title): Person(name, age), m_title(title){}void ShowInfo(){cout << "姓名:" << m_name << endl;cout << "年龄:" << m_age << endl;cout << "职称:" << m_title << endl;}private:string m_title; //职称};class Student : public Person{public:Student(const string& name, int age, int studyId): Person(name, age), m_studyId(studyId){}void ShowInfo(){cout << "姓名:" << m_name << endl;cout << "年龄:" << m_age << endl;cout << "学号:" << m_studyId << endl;}private:int m_studyId; //学号};#endif //__OBJEDT_H__测试代码:void test(){Person* pPerson = new Person("张三", 22);Teacher* pTeacher = new Teacher("李四", 35, "副教授");Student* pStudent = new Student("王五", 18, 20151653);pPerson->ShowInfo();cout << endl;pTeacher->ShowInfo();cout << endl;pStudent->ShowInfo();cout << endl;delete pPerson;delete pTeacher;delete pStudent;}结果:姓名:张三年龄:22姓名:李四年龄:35职称:副教授姓名:王五年龄:18学号:20151653说明:这⾥的ShowInfo就是⼀个普通的函数。
c++ 基类纯虚函数
c++ 基类纯虚函数C++是一种广泛使用的编程语言,同时也是面向对象编程语言。
在C++中,一个类可以从另一个类继承,这个类被称为基类,而继承的类被称为派生类。
基类中的纯虚函数是C++中非常重要的概念之一,它们在设计类的继承层次结构时非常有用。
纯虚函数是一种在基类中定义的虚函数,它没有任何实现代码,只是为了被继承类实现。
纯虚函数可以用一对`virtual`和`= 0`来声明,例如:```virtual void MyFunction() = 0;```这个声明告诉编译器MyFunction是一个虚函数,并且没有实现,只是一个接口,继承类必须对其进行实现。
纯虚函数在基类中起到了规范和约束作用,因为派生类必须实现这个函数才能实现自己的功能。
在许多情况下,基类中的纯虚函数是被设计为通用算法,由派生类提供特定的实现。
这种方法被称为“模板方法”模式。
在一个简单的图形库中,我们可以定义一个基类Shape,这个基类包含一个纯虚函数`Draw()`和派生类Rectangle和Circle。
Rectangle和Circle分别提供它们自己的特殊化实现,Draw()方法则会被调用以完成具体的实际操作。
在C++中,派生类中的实现方法可以通过覆盖和重载来完成。
覆盖是指派生类重新定义基类中的虚函数,以提供不同的实现方法。
重载是指派生类定义具有相同名称的函数,但它们具有不同的参数列表,这使得可以在相同的类中实现两个或更多的函数。
在实际开发中,如果我们定义了一个纯虚函数但没有提供实现,那么它将无法被实例化,因为它是一个抽象的函数。
通常情况下,如果我们忘记实现这个函数,可能会在编译时收到一个错误消息。
在设计一个类的继承时,纯虚函数是一种非常有用的技术。
它可以帮助我们将代码和数据聚集在一起,以便更好地组织和管理。
纯虚函数还可以使我们更迅速和简单地实现代码的重用和复用。
在C++中,基类中的纯虚函数是非常重要的。
它们可以帮助我们在类的继承层次结构中实现一些非常有用的功能,例如模板方法和多态。
C++中虚函数和纯虚函数的区别与总结
C++中虚函数和纯虚函数的区别与总结⾸先:强调⼀个概念定义⼀个函数为虚函数,不代表函数为不被实现的函数。
定义他为虚函数是为了允许⽤基类的指针来调⽤⼦类的这个函数。
定义⼀个函数为纯虚函数,才代表函数没有被实现。
定义纯虚函数是为了实现⼀个接⼝,起到⼀个规范的作⽤,规范继承这个类的程序员必须实现这个函数。
1、简介假设我们有下⾯的类层次:class A{public:virtual void foo(){cout<<"A::foo() is called"<<endl;}};class B:public A{public:void foo(){cout<<"B::foo() is called"<<endl;}};int main(void){A *a = new B();a->foo(); // 在这⾥,a虽然是指向A的指针,但是被调⽤的函数(foo)却是B的!return 0;}这个例⼦是虚函数的⼀个典型应⽤,通过这个例⼦,也许你就对虚函数有了⼀些概念。
它虚就虚在所谓“推迟联编”或者“动态联编”上,⼀个类函数的调⽤并不是在编译时刻被确定的,⽽是在运⾏时刻被确定的。
由于编写代码的时候并不能确定被调⽤的是基类的函数还是哪个派⽣类的函数,所以被成为“虚”函数。
虚函数只能借助于指针或者引⽤来达到多态的效果。
C++纯虚函数⼀、定义 纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派⽣类都要定义⾃⼰的实现⽅法。
在基类中实现纯虚函数的⽅法是在函数原型后加“=0”virtual void funtion1()=0⼆、引⼊原因1. 为了⽅便使⽤多态特性,我们常常需要在基类中定义虚拟函数。
2. 在很多情况下,基类本⾝⽣成对象是不合情理的。
例如,动物作为⼀个基类可以派⽣出⽼虎、孔雀等⼦类,但动物本⾝⽣成对象明显不合常理。
虚函数与抽象类
Account *ptr; // 声明抽象类指针 Account& f3(Account& a); // 抽象类的引用作为形参和返回值
纯虚函数和抽象类的应用举例
雇员类的层次结构
属性:月薪,缺勤天数 属性:产品件数, 属性:工时,每小 操作:重写虚函数 属性:底薪,销售 每件产品报酬 时报酬 金额,提成比例 操作:重写虚函数 操作:重写虚函数 操作:重写虚函数 Employee
属性:编号,姓名 操作:计算月工资, 输出雇员信息
Manager
Salesman
HourlyWorker
PieceworkWorker
雇员类的对象指针数组
Employee* PolyArray Employee* Employee* Employee** data Manager* Salesman* HourlyWorker* PieceworkWorker*
信用卡账户类 活期储蓄账户类
提供特殊属性,操作实现 定期储蓄账户类 提供特殊属性,操作实现 提供特殊属性,操作实现
账户类(抽象类)
提供一般属性,共同操作界面 共同行为:存款和取款
Account
DemandDepositAcct
TimeDepositAcct
CreditCardAcc理人员类的头文件 #ifndef MANAGER_H #define MANAGER_H #include "employee.h" class Manager : public Employee { public: Manager(std::string theID, std::string theName, double theSalary, int theDays); // 构造函数 // 析构函数 virtual ~Manager(); // 计算员工工资 virtual double getEarning(); // 显示员工信息 virtual void displayInfo(); protected: double salary; // 月薪 int absenceDays; // 缺勤天数 }; #endif
虚函数和虚基类的区别
虚函数和虚基类的区别 C++虚函数,纯虚函数,抽象类以及虚基类的区别Part1.C++中的虚函数什么是虚函数:直观表达就是,如果⼀个函数的声明中有 virtual 关键字,那么这个函数就是虚函数。
虚函数的作⽤:虚函数的最⼤作⽤就是实现⾯向对象程序设计的⼀⼤特点,多态性,多态性表达的是⼀种动态的概念,是在函数调⽤期间,进⾏动态绑定,以达到什么样的对象就实现什么样的功能的效果。
虚函数的⼀般声明语法:virtual 函数类型函数名 (形参表)注意:虚函数的声明只能出现在类的定义中,不能出现在成员函数实现的时候虚函数⼀般不声明为内联函数,但是声明为内联函数也不会引起错误在运⾏过程中要实现多态的三个条件:类之间满⾜赋值兼容关系(也就是类之间有继承关系)要声明为虚函数调⽤虚函数时,要由成员函数或者是指针和引⽤来访问代码举例#include <iostream>using namespace std;class Base1 {public:public:virtual void play();};void Base1::play(){cout << "Base1::play()" << endl;}class Base2:public Base1{virtual void play();};void Base2::play() {cout << "Base2::play()" << endl;}class Derived :public Base2{virtual void play();};void Derived::play() {cout << "Derived:: play()" << endl;}void fun(Base1* ba) { //声明⼀个基类的指针ba->play();}int main(){Base1 ba1;Base2 ba2;Derived de;//分别⽤不同的对象指针来调⽤ fun 函数fun(&ba1);fun(&ba2);fun(&de);return 0;}这代码含义就充分体现了虚函数作为实现多态条件的原因,由于 Base1 是 Base2 和 Derived 的⽗类,所以,Base1 是可以兼容 Base2 和Derived 的,所以在 fun 函数这⾥是⽤的 Base1 的指针来作为形参,不同的是当我传⼊参数不同时,fun 函数执⾏的是不同的结果,这就体现了多态的效果,我需要那个类型的实例,他就执⾏那个实例对应的⽅法。
第七章 虚函数
• 虚函数(virtual function)是在一个基类中用保留字virtual定义的 protected或public成员函数。基类的虚函数在派生类中仍然是虚函数, 并且一定需要在派生类中重定义,以形成不同版本。在派生类中重定 义继承成员虚函数时,即使没有保留字virtual,该函数仍然是虚函数, 但为了更好地表达这些函数的实质,最好加上这一保留字。一个含有 虚函数的类称为多态类,无论这些虚函数是继承下来的还是在派生类 中新增加的。 • 虚函数定义的一般形式为: virtual 函数返回值类型 类名::函数名(参数表) { …… //虚函数函数体 } • 一个类的虚函数仅对类中重定义的函数起作用,对其他函数没有影响, 在基类中使用虚函数保证了通过指向基类对象的指针调用基类的一个 虚函数时,C++系统对该调用进行动态绑定,而使用普通函数则是静 态绑定。
• 虚析构函数 构造函数不能是虚函数。因为建立一个派生类时, 必须从类层次的根开始,沿着继承路径逐个调用 基类的构造函数,直至自身的构造函数,不能 “选择性地”调用构造函数。所以虚构造函数没 有意义,定义虚构造函数将产生语法错误。 析构函数可以是虚的。虚析构函数用于动态分配类 层次结构对象时,指引delete运算符选择正确的 析构调用。
3、用构造函数/析构函数访问 构造函数与析构函数是特殊的方法函数,它们访问虚函数时,C++采用静态绑定。 class BASE { public: BASE(){}; virtual void v1(){cout<<"v1 is call in BASE.\n";} }; class A:public BASE{ public: A(){ cout<<"call v1 in A().\n"; v1(); } //调用本类中定义的虚函数 virtual void v1(){cout<<"v1 is called in A.\n";} }; class B:public A{ public: B(){cout<<"called v1 in B().\n"; A::v1(); } //采用静态绑定调用基类中定义的虚函数 void v1(){cout<<"v1 is called in B.\n";将一个函数调用链接上相应函数体代码的过程称 为绑定(binding)。静态绑定(static binding) 在编译时确定,即在编译时即可确定obj->func( ) 调用的是哪一个类中定义的func( )。而动态绑定 (dynamic binding)则必须到程序运行时才能确 定,派生类中重定义了基类的虚函数func( )后指 向基类对象的指针obj以obj->func( )形式调用的是 基类中定义的func( )还是派生类中重定义的func( ) 将根据obj运行期间所指向对象的类型决定。
python 纯虚函数
python 纯虚函数Python中没有纯虚函数的概念,因为Python是一种动态类型语言,所以不需要使用纯虚函数来实现多态性。
在Python中,可以使用抽象基类(ABC)来定义接口,强制实现其子类中的一些方法。
抽象基类是一个只包含抽象方法的类,不能直接实例化,只能通过其子类来实现。
使用抽象基类可以使代码更加清晰易懂,并且提高代码的可维护性和可扩展性。
例如,以下是一个使用抽象基类实现接口的示例: ```pythonfrom abc import ABC, abstractmethodclass Shape(ABC):@abstractmethoddef area(self):passclass Rectangle(Shape):def __init__(self, length, width):self.length = lengthself.width = widthdef area(self):return self.length * self.widthclass Circle(Shape):def __init__(self, radius):self.radius = radiusdef area(self):return 3.14 * self.radius ** 2r = Rectangle(10, 5)print('Rectangle area:', r.area())c = Circle(5)print('Circle area:', c.area())```在上面的代码中,我们定义了一个抽象基类`Shape`,其中包含一个抽象方法`area()`,用于计算形状的面积。
然后我们定义了两个具体的子类`Rectangle`和`Circle`,分别实现了`area()`方法来计算矩形和圆形的面积。
通过使用抽象基类,我们强制子类实现`area()`方法,并确保了代码的一致性和可读性。
《C++程序设计》电子教案第9章 多态性和虚函数
返回本节
9.1.3 派生类指针
指向基类和派生类的指针是相关的。 例如: A * p ; // 指向类型 A 的对象的指针 A A_obj ; // 类型 A 的对象 B B_obj ; // 类型 B 的对象 p = & A_obj ; // p 指向类型 A 的对象 p = & B_obj ; // p 指向类型 B 的对象,它是 A 的派生类
((B_class *)p)->show_phone( ); // 用基类指针访问公有派生类的特定成员,必须进行类型转换 } 此程序的运行结果为: Zhang San Li Si
0731_12345678 0731_12345678
例9-9:写出下面的程序的执行结果。 #include <iostream.h> class Student { public: Student(int xx) { x=xx; } virtual float calcTuition( ); protected: int x; }; float Student::calcTuition() { return float(x*x);
main ( ) { A_class *p; //对象指针 A_class A_obj; //对象 B_class * bp; B_class B_obj; p=&A_obj; //P指针指向基类对象,调用基类成员函数 p->put_name("Zhang San"); p=& B_obj; //P指针指向派生类对象,调用继承自基类的成 员函数 p->put_name("Li Si"); A_obj.show_name( ); B_obj.show_name( ); bp=&B_obj; bp->put_phone("0731_12345678"); bp->show_phone( );
base.在c++语言中的意思
在C++编程语言中,“base”是一个关键字,它在对象导向的编程中有着重要的意义。
下面我们将从多个方面来详细解释在C++语言中“base”的含义。
1. 继承的基类在C++语言中,“base”通常用来表示继承关系中的基类。
在对象导向的编程中,继承是一种重要的机制,它允许一个类(称为派生类)从另一个类(称为基类)继承属性和行为。
在这种情况下,基类通常被称为“base”,而派生类则可以通过“base”类来访问基类中定义的成员和方法。
2. 类似于父类在某些情况下,“base”还可以用来表示类似于父类的意思。
当一个类从另一个类继承而来时,它可以被认为是“base”类的一个具体实例。
在这种情况下,我们可以说派生类是基类的一种特化,它继承了基类的特征,并可以在此基础上添加自己的属性和行为。
3. 抽象基类在C++语言中,我们还可以将“base”视为抽象基类的概念。
抽象基类是一种不能实例化的类,它通常用来定义接口和规范,而不是具体的实现。
派生类可以通过继承抽象基类来实现特定的行为,这样可以提高代码的可复用性和扩展性。
“base”在这种情况下可以表示抽象基类的概念。
4. 多重继承中的基类在C++语言中,还存在多重继承的情况,即一个类可以同时从多个基类中继承属性和行为。
在这种情况下,这些基类可以被称为“base”,派生类可以通过“base”类来访问各个基类中定义的成员和方法。
多重继承使得类之间的关系更加复杂,但也为程序员提供了更大的灵活性。
从多重方面来看,“base”在C++语言中都有着重要的含义。
它可以表示继承的基类、类似于父类、抽象基类以及多重继承中的基类等概念。
掌握“base”的含义对于理解和应用C++语言中的继承机制是非常重要的,它可以帮助程序员更加灵活地设计和组织类之间的关系,从而提高代码的可维护性和扩展性。
希望通过本文的解释,读者能够对“base”在C++语言中的含义有更加清晰的认识。
很抱歉,我之前的回答似乎存在误解。
纯虚函数和抽象类
纯虚函数和抽象类纯虚函数和抽象类定义注意抽象类不能创建对象,但是可以定义⼀个指针注意抽象类不能有任何成员结构, 成员函数必须协成纯虚函数,virtual 返回值函数名(参数列表)=0注意 含有纯虚函数的类被称为抽象类。
抽象类只能作为派⽣类的基类,不能定义对象,但可以定义指针。
在派⽣类实现该纯虚函数后,定义抽象类对象的指针,并指向或引⽤⼦类对象。
1)在定义纯虚函数时,不能定义虚函数的实现部分;2)在没有重新定义这种纯虚函数之前,是不能调⽤这种函数的。
抽象类的唯⼀⽤途是为派⽣类提供基类,纯虚函数的作⽤是作为派⽣类中的成员函数的基础,并实现动态多态性。
继承于抽象类的派⽣类如果不能实现基类中所有的纯虚函数,那么这个派⽣类也就成了抽象类。
因为它继承了基类的抽象函数,只要含有纯虚函数的类就是抽象类。
纯虚函数已经在抽象类中定义了这个⽅法的声明,其它类中只能按照这个接⼝去实现。
抽象类实例计算图形⾯积#include <iostream>using namespace std;// 重点// ⾯向抽象类编程(⾯向⼀套预先定义好的接⼝编程)// 解耦合。
模块的划分class Figure // 抽象类{public:// 约定⼀个统⼀的界⾯(接⼝) 让⼦类使⽤,让⼦类必须去实现virtual void getArea() = 0; // 纯虚函数protected:private:};class Circle : public Figure {public:Circle(int a, int b) {this->a = a;this->b = b;}virtual void getArea() {cout << "圆的⾯积\t" << 3.14 * a * a << endl;}protected:private:int a;int b;};class Sanjiao : public Figure {public:Sanjiao(int a, int b) {this->a = a;this->b = b;}virtual void getArea() {cout << "三⾓形的⾯积\t" << a * b / 2 << endl;}protected:private:int a;int b;};class Squre : public Figure {public:Squre(int a, int b) {this->a = a;this->b = b;}virtual void getArea() {cout << "四边形的⾯积\t" << a * b << endl;}protected:private:int a;int b;};void PlayObj(Figure *base) {base->getArea(); // 会发⽣多态}int main() {// Figure f1; // 抽象类不能被实例化Figure *base = NULL;Circle c1(1, 2);Squre sq(1, 2);Sanjiao s1(2, 4);PlayObj(&c1);PlayObj(&s1);PlayObj(&sq);return0;}抽象类编程例⼦⼀计算程序员⼯资忘记了⼿动调⽤delete,让其调⽤析构函数⽐较好第⼆个有点错误, 不能说是抽象类#include <iostream>using namespace std;/*编写⼀个c++程序计算程序员⼯资(programer)1要求:能计算出初级程序员⼯资(junior_programer),中级程序员(mid_programer),⾼级程序员(Adv_progreamer) 2要求利⽤抽象类统⼀界⾯(⽅便程序拓展),⽐如新增计算架构师architect的⼯资*/// 程序员抽象类class programer{public:virtual void getSal() = 0; // 抽象类接⼝};// 初级程序员class junior_programer: public programer{public:junior_programer(char *name, char *job, int sal) // 浅拷贝{this->name = name;this->job = job;this->sal = sal;}virtual void getSal() // 接⼝类实现{cout << "name = " << name << "\tjob = " << job << "\tsal = " << sal << endl;}private:char *name;char *job;int sal;};// 中级程序员class mid_programer: public programer{public:mid_programer(char *name, char *job, int sal) // 浅拷贝{this->name = name;this->job = job;this->sal = sal;}virtual void getSal() // 接⼝类实现{cout << "name = " << name << "\tjob = " << job << "\tsal = " << sal << endl;}private:char *name;char *job;int sal;};// ⾼级程序员class Adv_programer: public programer{public:Adv_programer(char *name, char *job, int sal) // 浅拷贝{this->name = name;this->job = job;this->sal = sal;}virtual void getSal() // 接⼝类实现{cout << "name = " << name << "\tjob = " << job << "\tsal = " << sal << endl;}private:char *name;char *job;int sal;};// 后来增加的架构师类class architect: public programer{public:architect(char *name, char *job, int sal) // 浅拷贝{this->name = name;this->job = job;this->sal = sal;}virtual void getSal() // 接⼝类实现{cout << "name = " << name << "\tjob = " << job << "\tsal = " << sal << endl;}private:char *name;char *job;int sal;};// 计算函数,简单框架void jisuan(programer *base){base->getSal();}// 引⽤void jisuan(programer &base){base.getSal();}int main(){junior_programer junior("张三", "初级", 5000);mid_programer mid("李四", "中级", 10000);Adv_programer adv("王五", "⾼级", 15000);// 系统扩展增加代码架构师⼯资architect arc("康总", "架构师", 30000);jisuan(&junior);jisuan(&mid);jisuan(&adv);jisuan(&arc);cout << endl;// 引⽤类型auto &i = junior;auto &j = mid;auto &k = adv;auto &l = arc;jisuan(i);jisuan(j);jisuan(k);jisuan(l);return0;}别⼈的的⽅式//编写⼀个c++程序计算程序员⼯资(programer)//1要求:能计算出初级程序员⼯资(junior_programer),中级程序员(mid_programer),⾼级程序员(Adv_progreamer) //2要求利⽤抽象类统⼀界⾯(⽅便程序拓展),⽐如新增计算架构师architect的⼯资#include <iostream>#include <cstring>using namespace std;// 定义程序员类, 抽象类class Programer {virtual double getSal() = 0;};class Junior : Programer {public:Junior(char *name = NULL, char *job = NULL, double sal = 7000) {auto len = strlen(name);this->name = new char[len + 1];strcpy(this->name, name);len = strlen(job);this->job = new char[len + 1];strcpy(this->job, job);this->sal = sal;}virtual ~Junior() {delete[]name;delete[]job;sal = 0;name = NULL;job = NULL;cout << "j" << endl;}virtual double getSal() {cout << this->name << " : " << this->job << ": " << this->sal << endl; }public:char *name;char *job;double sal;};class Mid : public Junior {public:Mid(char *name = NULL, char *job = NULL, double sal = 10000): Junior(name, job, sal) {}virtual ~Mid() // 会默认调⽤⽗类的析构函数{cout << "m" << endl;}};// ⾼级的class Adv : public Junior {public:Adv(char *name = NULL, char *job = NULL, double sal = 10000): Junior(name, job, sal) {}virtual ~Adv() // 会默认调⽤⽗类的析构函数{cout << "Adv" << endl;}};void print(Junior &obj) {obj.getSal();}int main() {Junior j("张三", "初级", 5000);Mid m("李四", "中级", 10000);Adv a("王五", "⾼级", 15000);print(j);print(m);print(a);return0;}⾃⼰的另⼀种写法抽象类编程动物园类#if 0// main.cpp#define _CRT_SECURE_NO_WARNINGS #include <iostream>#include "Animal.h"#include "Dog.h"#include "Cat.h"#include "Dog.cpp"#include "Cat.cpp"#include "Animal.cpp"using namespace std;int main(void){letAnimalCry(new Dog);letAnimalCry(new Cat);#if 0Animal *dog = new Dog;letAnimalCry(dog);delete Dog;#endifreturn0;}#endif// -- Animal.h#if 0#pragma once#define _CRT_SECURE_NO_WARNINGS #include <iostream>using namespace std;class Animal{public://纯虚函数,让⼦类继承并且实现virtual void voice() = 0;Animal();virtual ~Animal();};//架构函数//让动物叫void letAnimalCry(Animal *animal);#endif// Animal.cpp#if 0#include "Animal.h"inlineAnimal::Animal(){cout << "animal().." << endl;}inlineAnimal::~Animal(){cout << "~Animal()..." << endl;}inlinevoid letAnimalCry(Animal *animal){animal->voice();// 需要⼿动调⽤delete 让其调⽤析构函数if (animal != NULL) {delete animal;}}#endif// Dog.h------------------------#if 0#pragma once#include "Animal.h"class Dog : public Animal{public:Dog();~Dog();virtual void voice();};#endif// Dog.cpp#if 0#include "Dog.h"inlineDog::Dog(){cout << "Dog().." << endl;}inlineDog::~Dog(){cout << "~Dog().." << endl;}inlinevoid Dog::voice(){cout << "狗开始哭了, 555" << endl;}#endif// Cat.h#if 0#pragma once#include "Animal.h"class Cat : public Animal{public:Cat();~Cat();virtual void voice();};#endif// Cat.cpp#if 0#include "Cat.h"inlineCat::Cat(){cout << "cat().." << endl;}inlineCat::~Cat(){cout << "~cat().." << endl;}inlinevoid Cat::voice(){cout << "⼩猫开始哭了,66666" << endl; }#endif动物园电脑类实例:#define _CRT_SECURE_NO_WARNINGS #include <iostream>using namespace std;//-------- 抽象层---------//抽象CPU类class CPU {public:// CPU();virtual void caculate() = 0;};//抽象的card类class Card {public:virtual void display() = 0;};//抽象的内存类class Memory {public:virtual void storage() = 0;};//架构类class Computer {public:Computer(CPU *cpu, Card *card, Memory *mem) { this->cpu = cpu;this->card = card;this->mem = mem;}void work() {this->cpu->caculate();this->card->display();this->mem->storage();}~Computer() {if (this->cpu != NULL) {cout << "~cpu" << endl;delete this->cpu;}if (this->card != NULL) {cout << "~card"<<endl;delete this->card;}if (this->mem != NULL) {cout << "~mem"<<endl;delete this->mem;}}private:CPU *cpu;Card *card;Memory *mem;};// --------------------------//-----------实现层----------//具体的IntelCPUclass IntelCPU : public CPU {public:virtual void caculate() {cout << "Intel CPU开始计算了" << endl;}};class IntelCard : public Card {public:virtual void display() {cout << "Intel Card开始显⽰了" << endl;}};class IntelMem : public Memory {public:virtual void storage() {cout << "Intel mem开始存储了" << endl;};class NvidiaCard : public Card {public:virtual void display() {cout << "Nvidia 显卡开始显⽰了" << endl;}};class KingstonMem : public Memory {public:virtual void storage() {cout << "KingstonMem 开始存储了" << endl;}};//--------------------------void test(){Computer *com1 = new Computer(new IntelCPU, new IntelCard, new IntelMem); com1->work();delete com1; // 如果定义⼀个指针不要忘记释放}//--------业务层-------------------int main() {//1 组装第⼀台intel系列的电脑#if 0CPU *intelCpu = new IntelCPU;Card *intelCard = new IntelCard;Memory *intelMem = new IntelMem;Computer *com1 = new Computer(intelCpu, intelCard, intelMem);com1->work();Card *nCard = new NvidiaCard;Memory* kMem = new KingstonMem;Computer *com2 = new Computer(intelCpu, nCard, kMem);com2->work();delete intelCpu;#endif// Computer *com1 = new Computer(new IntelCPU, new IntelCard, new IntelMem); // com1->work();// delete com1; // 这⾥不要忘记释放test();return0;}这个好好看看圆类#define _CRT_SECURE_NO_WARNINGS#include <iostream>using namespace std;//抽象的图形类class Shape{public://打印出图形的基本你属性virtual void show() = 0;//得到图形的⾯积virtual double getArea() = 0;virtual ~Shape() {}};//圆类class Circle :public Shapepublic:Circle(double r) {this->r = r;}//打印出图形的基本你属性virtual void show() {cout << "圆的半径是 " << r << endl;}//得到图形的⾯积virtual double getArea() {cout << "获取圆的⾯积" << endl;return this->r*this->r *3.14;}~Circle() {cout << "圆的析构函数。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
抽象基类和纯虚函数
抽象类和具体类
包含纯虚函数的类不能实例化对象,是抽象类
如果抽象类的派生类实现了所有积累中的纯虚函数,则不再是抽象类
抽象类存在的意义是作为其他类的基类,也较抽象基类
构造函数的执行顺序:从上至下
析构函数的执行顺序:从下至上
创建对象时要执行正确的构造函数
撤销对象时要执行正确的析构函数
问题:动态对象的创建和撤销
虚析构函数
动态对象的创建
动态创建的对象没有问题
New classname(···);
动态对象的撤销
Delete 基类指针;
如果基类指针指向的是派生类的对象呢?
析构函数可以声明为虚函数
Delete 基类指针;
程序会根据积累指针指向的对象的类型确定要调用的析构函数
如果基类的析构函数为虚函数,则所派生的类的析构函数都是虚函数
如果要操作具有继承关系的类的动态对象,最好使用虚析构函数
文件和流——支持大量数据的处理:输入,存储
对文件执行的操作只要求我们掌握对几个函数的操作就行
如果说你不懂对文件的操作和处理,你永远也无法选好编程,你的程序永远也写不好,操作系统能够把外设和文件统一管理。
文件可以保存程序的运行结果
文件使程序处理大量的数据成为可能
大型系统的运行需要文件支持
C++将文件看成有序的字节流
文件被打开后,操作系统为该文件的建立的一个缓冲区,或称为一个字节序列,即流
普通文件
二进制文件
文本文件
输入输出设备:键盘,显示器,打印机等
标准输入流(用指针stdin操作)
标准输出流(用指针stdout操作)
C++采用相同的方式操作普通文件和I/O设备
文件的操作
格式化输入输出(文本)
块输入输出(二进制)
文件操作过程
1.建立并打开文件
2.操作文件:读,写
3.关闭文件
打开文件或建立一个新文件
FILE *fopen(const char *filename,const char *mode);
filename——路劲及文件名
mode——打开方式
关闭文件
Int fclose(FILE *stream);
Stream——要关闭的文件
读写文件——格式化操作(文本文件)
Int fscanf(File *stream,·······);
Int fprintf(File *stream,·······);
读写文件——快读写方式(二进制文件)
size_t fwrite(const void*buffer,size_t size,size_t count,File *stream);
size_t fread(const void*buffer,size_t size,size_t count,File *stream);。