C++第九节:多态、虚函数、抽象类
2015-08-06 19:49
411 查看
多态、虚函数、抽象类
1、虚函数
1.1 当父类指针或引用指向子类对象,而子类中又覆盖了父类的函数,希望用父类指针或引用调用到正确版本的成员函数,需要把该成员函数声明为虚函数
1.2 调用虚函数时,到底调用哪个版本,是根据调用该函数的对象本身的类型,而不是指向那个对象的指针或引用的类型,即对象的内存空间为谁(父类or子类)开辟就调用谁的成员方法或成员变量
1.3 虚函数目的:父类指针或引用,不管指向父类还是子类,在调用覆盖函数时,可以反映真实情况;有了虚函数,无需向下转型,就可以正确的用父类的指针或引用调用到子类的函数
1.4 如果函数的参数是传值方式,形参是父类对象,实参是子类对象,则在函数内部,用形参调用的成员函数,依然是父类版本。因为传值只是用子类对象给父类对象赋值,父类对象不是指向子类的引用或指针
1.5 如果一个虚函数被其他成员函数调用,子类的版本也会被正确调用
1.6 如果一个类有子类,则这个父类的析构函数必须是虚函数,即虚析构。如果不是虚析构,则当(用delete)删除一个指向子类对象的父类指针时,将调用父类版本的析构函数,子类只释放了来自于父类的那部分成员变量,而没有释放子类扩展的成员变量,造成内存泄漏。
1.7 如果子类的成员函数是虚函数,则子类覆盖后,不写virtual也是虚函数。
1.8 虚函数被调用的时候,到底调用哪个版本,在编译的时候无法确定,只有在执行时才能确定,称为动态绑定。之前的函数调用,是在编译时就可以确定调用哪个版本的函数
1.9 动态绑定使得程序可以照顾到未来增加的代码,比如创建一个新的子类,并在子类中覆盖了父类的虚函数,用之前的父类指针依然可以正确的调用到新子类中的函数,而无需对旧有代码进行修改。
2、抽象基类、纯虚函数
2.1 纯虚函数,没有函数体,不需要实现,在子类中实现纯虚函数的具体功能
2.2 拥有纯虚函数的类,称为抽象类,抽象类提供了不同种类对象的一个通用接口。
2.3 不能创建抽象类的对象,因为抽象类里面的纯虚函数没有实现。
2.4 抽象类只能作为基类使用,即抽象基类,如果想创建子类对象,必须实现抽象基类中的所有纯虚函数,否则,子类依然是抽象类
2.5 不能单独调用抽象类的构造函数,仅可以用于子类构造函数的初始化列表里,用于初始化子类中继承自父类的成员变量
2.6 抽象类不是必须有析构函数,一旦有,必须是虚析构
2.7 不能以传值的方式,向一个函数传递抽象基类的参数。如果函数形参是抽象类,实参是子类,就相当于用子类对象,创建了一个临时的抽象基类对象,后者是不允许的,所以必须以传引用或指针的方式来传参
3、多态:用父类的指针或引用指向子类的对象,在函数调用时可以调用到正确版本的函数
3.1 用一个父类的指针指向一个子类对象
3.2 用一个父类的指针当函数的形参,用这个指针可以接受到任何它的子类对象也包括他自己
3.3 在复合类,尽量饮用高层次的类(父类的指针)当做类的成员变量,这样就可以通过它创建出它所对应的任何子类对象包括他自己
3.4 在容器中,可以声明一个父类指针的容器,这时可以往容器中添加它所对应的任何子类对象包括他自己
A、虚函数
Base.h
Thing.h
//I'm Thing I'm Animal 析构Thing 析构Thing
(内存泄漏)
//I'm Thing I'm Animal 析构Thing 析构Animal
析构Thing (虚析构)
C、纯虚函数
Point.h
1、虚函数
1.1 当父类指针或引用指向子类对象,而子类中又覆盖了父类的函数,希望用父类指针或引用调用到正确版本的成员函数,需要把该成员函数声明为虚函数
1.2 调用虚函数时,到底调用哪个版本,是根据调用该函数的对象本身的类型,而不是指向那个对象的指针或引用的类型,即对象的内存空间为谁(父类or子类)开辟就调用谁的成员方法或成员变量
1.3 虚函数目的:父类指针或引用,不管指向父类还是子类,在调用覆盖函数时,可以反映真实情况;有了虚函数,无需向下转型,就可以正确的用父类的指针或引用调用到子类的函数
1.4 如果函数的参数是传值方式,形参是父类对象,实参是子类对象,则在函数内部,用形参调用的成员函数,依然是父类版本。因为传值只是用子类对象给父类对象赋值,父类对象不是指向子类的引用或指针
1.5 如果一个虚函数被其他成员函数调用,子类的版本也会被正确调用
1.6 如果一个类有子类,则这个父类的析构函数必须是虚函数,即虚析构。如果不是虚析构,则当(用delete)删除一个指向子类对象的父类指针时,将调用父类版本的析构函数,子类只释放了来自于父类的那部分成员变量,而没有释放子类扩展的成员变量,造成内存泄漏。
1.7 如果子类的成员函数是虚函数,则子类覆盖后,不写virtual也是虚函数。
1.8 虚函数被调用的时候,到底调用哪个版本,在编译的时候无法确定,只有在执行时才能确定,称为动态绑定。之前的函数调用,是在编译时就可以确定调用哪个版本的函数
1.9 动态绑定使得程序可以照顾到未来增加的代码,比如创建一个新的子类,并在子类中覆盖了父类的虚函数,用之前的父类指针依然可以正确的调用到新子类中的函数,而无需对旧有代码进行修改。
2、抽象基类、纯虚函数
2.1 纯虚函数,没有函数体,不需要实现,在子类中实现纯虚函数的具体功能
2.2 拥有纯虚函数的类,称为抽象类,抽象类提供了不同种类对象的一个通用接口。
2.3 不能创建抽象类的对象,因为抽象类里面的纯虚函数没有实现。
2.4 抽象类只能作为基类使用,即抽象基类,如果想创建子类对象,必须实现抽象基类中的所有纯虚函数,否则,子类依然是抽象类
2.5 不能单独调用抽象类的构造函数,仅可以用于子类构造函数的初始化列表里,用于初始化子类中继承自父类的成员变量
2.6 抽象类不是必须有析构函数,一旦有,必须是虚析构
2.7 不能以传值的方式,向一个函数传递抽象基类的参数。如果函数形参是抽象类,实参是子类,就相当于用子类对象,创建了一个临时的抽象基类对象,后者是不允许的,所以必须以传引用或指针的方式来传参
3、多态:用父类的指针或引用指向子类的对象,在函数调用时可以调用到正确版本的函数
3.1 用一个父类的指针指向一个子类对象
3.2 用一个父类的指针当函数的形参,用这个指针可以接受到任何它的子类对象也包括他自己
3.3 在复合类,尽量饮用高层次的类(父类的指针)当做类的成员变量,这样就可以通过它创建出它所对应的任何子类对象包括他自己
3.4 在容器中,可以声明一个父类指针的容器,这时可以往容器中添加它所对应的任何子类对象包括他自己
A、虚函数
Base.h
#ifndef __C__No806Class__Base__ #define __C__No806Class__Base__ #include <iostream> using namespace std; class Base { //当父类指针或父类引用指向子类对象,而子类中又覆盖了父类的函数,希望用父类指针或父类引用,调用到正确版本的成员函数,需要把该成员函数声明为虚函数 //调用虚函数时,到底调用哪个版本,是根据调用该函数的对象本身的类型,而不是指向那个对象的指针或引用的类型 //对象的内存空间是为谁(子类或父类)开辟的就调用谁的成员方法或成员变量 public: virtual void func(); }; #endif /* defined(__C__No806Class__Base__) */Base.cpp
#include "Base.h" void Base::func() { cout << "Base func" << endl; }Derived.h
#ifndef __C__No806Class__Derived__ #define __C__No806Class__Derived__ #include <iostream> #include "Base.h" class Derived : public Base { public: void func(); }; #endif /* defined(__C__No806Class__Derived__) */Derived.cpp
#include "Derived.h" void Derived::func() { cout << "Derived func" << endl; }main.cpp
#include <iostream> #include "Derived.h" void foo(Base &b) //如果不是引用,则调用父类 { b.func(); //子类??形参b是d的引用 } int main() { Derived d; Base b; Base *p = &d; //父类指针或引用指向子类对象 Base &br = d; b = d; //根据调用该函数的对象本身的类型,而不是指向那个对象的指针或引用的类型 b.func(); //b,父类 d.func(); //d,子类 p -> func(); //d,子类 foo(d); //如果函数的参数是传值方式,形参是父类对象,实参是子类对象,则在函数内部,用形参调用的成员函数依然是父类版本,因为传值只是用子类对象给父类对象赋值,父类对象不是指向子类的引用或指针 br.func(); //d,子类 return 0; }B、虚析构
Thing.h
#ifndef __C__No806Class__Thing__ #define __C__No806Class__Thing__ #include <iostream> using namespace std; class Thing { public: virtual void what_am_i(); virtual ~Thing(); }; #endif /* defined(__C__No806Class__Thing__) */Thing.cpp
#include "Thing.h" void Thing::what_am_i() { cout << "I'm Thing" << endl; } Thing::~Thing() { cout << "析构Thing" << endl; }Animal.h
#ifndef __C__No806Class__Animal__ #define __C__No806Class__Animal__ #include <iostream> #include "Thing.h" class Animal : public Thing { public: //如果父类的成员函数是虚函数,则子类覆盖后,不写virtual,也是虚函数 void what_am_i(); ~Animal(); }; #endif /* defined(__C__No806Class__Animal__) */Animal.cpp
#include "Animal.h" void Animal::what_am_i() { cout << "I'm Animal" << endl; } Animal::~Animal() { cout << "析构Animal" << endl; }mian.cpp
#include "Animal.h" int main() { Thing t; Animal x; Thing *array[2]; array[0] = &t; array[1] = &x; //虚函数:父类指针或引用,不管指向父类或子类,在调用覆盖函数时,可以反映真实情况 //有了虚函数,无需向下转型,就可以正确的用父类的指针或引用,调用到子类的函数 //如果一个虚函数被其他成员函数调用,子类的版本也会被正确调用 //如果一个类有子类,则这个父类的析构函数必须是虚函数,即虚析构 //如果父类的析构不是虚析构,则当删除一个指向子类对象的父类指针时,将调用父类版本的析构函数,子类只释放了来自于父类的那部分成员变量,而没有释放子类扩展的成员变量,造成内存泄漏 for (int i = 0; i < 2; i++) array[i] -> what_am_i(); return 0; }
#include "Animal.h" int main () { Thing * t = new Thing(); Animal * x = new Animal(); Thing * array[2]; array[0] = t; array[1] = x; for (int i = 0; i < 2; i++) array[i] -> what_am_i(); //delete t; //delete x; delete array[0]; delete array[1]; return 0; }
//I'm Thing I'm Animal 析构Thing 析构Thing
(内存泄漏)
//I'm Thing I'm Animal 析构Thing 析构Animal
析构Thing (虚析构)
C、纯虚函数
Point.h
#include <iostream> using namespace std; class Point { private: double x; double y; public: Point (double i, double j); void print () const; }; #endif /* defined(__C__No806Class__Point__) */Point.cpp
#include "Point.h" Point::Point (double i, double j) { x = i; y = j; } void Point::print () const { cout << "(" << x << "," << y << ")"; }Figure.h
#ifndef __C__No806Class__Figure__ #define __C__No806Class__Figure__ #include <iostream> #include "Point.h" class Figure { private: Point center; //组合关系 public: Figure (double i = 0, double j = 0); Point & location(); void move(Point p); virtual void draw() = 0; //纯虚函数,没有函数体,不需要实现,在子类中实现纯虚函数的具体功能 virtual void rotate(double d) = 0; //对于父类来说,无法确定如何绘制,函数也就无法实现 //抽象基类无法创建对象,因为抽象类里的纯虚函数没有实现 //抽象类提供了不同种类对象的一个通用接口 //抽象类不是必须有析构函数,一旦有,必须是虚析构 }; #endif /* defined(__C__No806Class__Figure__) */Figure.cpp
#include "Figure.h" Figure::Figure (double i, double j) : center(i, j) {} Point & Figure::location() { return center; } void Figure::move(Point p) { center = p; draw(); //移动图形后重新绘制 }Circle.h
#ifndef __C__No806Class__Circle__ #define __C__No806Class__Circle__ #include <iostream> #include "Figure.h" class Circle : public Figure { private: double radius; public: Circle (double i = 0, double j = 0, double r = 0); void draw(); void rotate(double d); }; #endif /* defined(__C__No806Class__Circle__) */Circle.cpp
#include "Circle.h" Circle::Circle (double i, double j, double r) : Figure(i, j) //不能单独调用抽象类的构造函数,仅可用于子类构造函数的初始化列表里,用于初始化子类中继承自父类的成员变量 { radius = r; } void Circle::draw() //在子类实现继承自父类的纯虚函数,如果不实现,则子类包含纯虚函数而依然是抽象类 { cout << "Center:"; location().print(); cout << " and r = " << radius << endl; } void Circle::rotate(double d) { cout << "No effect" << endl; }Square.h
#ifndef __C__No806Class__Square__ #define __C__No806Class__Square__ #include <iostream> #include "Figure.h" class Square : public Figure { private: double side; double angle; public: Square (double i = 0, double j = 0, double d = 0, double a = 0); void draw(); void rotate(double a); void vertices(); }; #endif /* defined(__C__No806Class__Square__) */Square.cpp
#include "Square.h" Square::Square (double i, double j, double d, double a) : Figure(i, j) { side = d; angle = a; } void Square::draw() { cout << "Center:"; location().print(); cout << ", side = " << side << ", angle = " << angle << endl; } void Square::rotate(double a) { angle += a; cout << "angle = " << angle << endl; } void Square::vertices() { cout << "VERTICES" << endl; }main.cpp
#include "Circle.h" #include "Square.h" int main() { Circle c(1, 2, 3); Square s(4, 5, 6); Figure *f = &c; Figure &g = s; f -> draw(); //纯虚函数 f -> move(Point (2, 2)); //在普通成员函数内,调用虚函数 g.draw(); g.rotate(1); g.move(Point(1, 1)); //父类普通成员函数中,调用纯虚函数 s.vertices(); //g.vertices(); //父类没有这个函数,不能通过一个指向子类对象的父类引用,调用子类自己扩展的成员函数 }
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- 关于指针的一些事情
- c++ primer 第五版 笔记前言
- share_ptr的几个注意点
- Lua中调用C++函数示例
- Lua教程(一):在C++中嵌入Lua脚本
- Lua教程(二):C++和Lua相互传递数据示例
- C++联合体转换成C#结构的实现方法
- C++编写简单的打靶游戏
- C++ 自定义控件的移植问题
- C++变位词问题分析
- C/C++数据对齐详细解析
- C++基于栈实现铁轨问题
- C++中引用的使用总结
- 使用Lua来扩展C++程序的方法
- C++中调用Lua函数实例
- Lua和C++的通信流程代码实例
- C与C++之间相互调用实例方法讲解
- C++ Custom Control控件向父窗体发送对应的消息
- C++中拷贝构造函数的应用详解