您的位置:首页 > 编程语言 > C语言/C++

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

#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++ 课程