【 1. 多态 】
- 多态按字面的意思就是多种形态。当 类之间存在层次结构,并且类之间是通过继承关联时 ,就会用到多态。 C++ 多态 意味着调用成员函数时, 会根据调用函数的 对象的类型 来执行不同的函数 。
派生类对象调用成员函数
- 派生类的对象调用成员函数的几种情况:
情况 | 调用形式 |
---|---|
若 派生类中 有 同名成员函数,优先调用 派生类内部 的成员函数 | 派生类对象.同名成员函数 |
若 派生类中 无 同名成员函数,直接调用 基类内部 的同名函数 | 派生类对象.同名成员函数 |
直接调用 基类内部的成员函数 | 派生类对象.基类名称::同名的成员函数 |
- 实例
#include <iostream>
using namespace std;
// 基类 Shape
class Shape
{
protected:
int width, height;
public:
Shape(int a = 0, int b = 0)
{
width = a;
height = b;
}
int area()
{
cout << "Parent class area :" << width * height << endl;
return 0;
}
};
// 派生类 Rectangle
class Rectangle : public Shape
{
public:
Rectangle(int a = 0, int b = 0) :Shape(a, b) { }
int area()
{
cout << "Rectangle class area : " << width * height << endl;
return (width * height);
}
};
int main()
{
Rectangle rec(10, 7);
rec.area(); // 默认调用派生类内部的函数 area
//rec.Shape::area(); // 直接调用基类内部的成员函数 area
return 0;
}
【 2. 虚函数 】
2.1 问题背景 : 基类指针指向派生类对象时默认调用基类的同名函数
- 如果基类类型的指针指向派生类的对象,则通过 该指针调用的同名成员函数是基类的。
- 实例
- 基类 Shape 被派生为两个类,定义基类的指针,指针指向两个派生类的对象,分别调用派生类内部的成员函数 area() 。
- 导致错误输出的原因是, 调用函数 area() 被编译器设置为基类中的版本,这就是所谓的 静态多态 / 静态链接 ,即 函数调用在程序执行前就准备好了, area() 函数在程序编译期间就已经设置好了,有时候这也被称为早绑定。
#include <iostream>
using namespace std;
// 基类 Shape
class Shape
{
protected:
int width, height;
public:
Shape( int a=0, int b=0)
{
width = a;
height = b;
}
int area()
{
cout << "Parent class area :" << width * height<<endl;
return 0;
}
};
// 派生类 Rectangle
class Rectangle: public Shape
{
public:
Rectangle( int a=0, int b=0):Shape(a, b) { } // 构造函数
int area ()
{
cout << "Rectangle class area : " << width * height<<endl;
return (width * height);
}
};
// 派生类 Triangle
class Triangle: public Shape
{
public:
Triangle( int a=0, int b=0):Shape(a, b) { } // 构造函数
int area ()
{
cout << "Triangle class area : " <<width * height<<endl;
return (width * height / 2);
}
};
int main( )
{
Shape *shape; // 创建1个指向基类类型的指针
Rectangle rec(10,7);
Triangle tri(10,5);
shape = &rec; // 将指针指向派生类对象 rec
shape->area();// 调用求面积函数 area
shape = &tri; // 将指针指向派生类对象 tri
shape->area();// 调用求面积函数 area
return 0;
}
2.2 虚函数
- 为了 基类指针指向派生类对象时,都能调用派生类内部与基类内部名字相同的成员函数 ,引入 虚函数 的概念。
虚函数 是指在基类中使用 关键字 virtual 声明的函数,派生类中重新定义该函数时不用加 virtual,此时基类指针指向派生类对象并调用同名函数时便会直接调用派生类内部的同名函数。 - 底层机理
在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数,我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为 动态链接 / 后期绑定。 - 基本语法1
在基类的同名成员函数前添加 关键字 virtual 。
class MyBaseClass // 基类 MyBaseClass
{
public:
virtual DataType FunctionName()
{
...你的代码
}
};
- 基本语法2
基类的同名成员函数不给出具体实现,但也不是纯虚函数:
virtual DataType FunctionName();
- 实例
对程序稍作修改,在 Shape 类中,area() 的声明前放置关键字 virtual。此时,编译器看的是指针的内容,而不是它的类型。因此,由于 tri 和 rec 类的对象的地址存储在 *shape 中,所以会调用各自的 area() 函数。这样就可以让每个子类都有一个函数 area() 的独立实现,这就是多态的一般使用方式。
#include <iostream>
using namespace std;
// 基类 Shape
class Shape
{
protected:
int width, height;
public:
Shape( int a=0, int b=0)
{
width = a;
height = b;
}
virtual int area()
{
cout << "Parent class area :" << width * height<<endl;
return 0;
}
};
// 派生类 Rectangle
class Rectangle: public Shape
{
public:
Rectangle( int a=0, int b=0):Shape(a, b) { }
int area ()
{
cout << "Rectangle class area : " << width * height<<endl;
return (width * height);
}
};
// 派生类 Triangle
class Triangle: public Shape
{
public:
Triangle( int a=0, int b=0):Shape(a, b) { }
int area ()
{
cout << "Triangle class area : " <<width * height<<endl;
return (width * height / 2);
}
};
int main( )
{
Shape *shape;
Rectangle rec(10,7);
Triangle tri(10,5);
shape = &rec; // 将指针指向派生类对象 rec
shape->area();// 调用求面积函数 area
shape = &tri; // 将指针指向派生类对象 tri
shape->area();// 调用求面积函数 area
return 0;
}
【 3. 纯虚函数 】
- 如果我们在 基类中不能对虚函数给出有意义的实现即没有主体 ,这个时候就会用到 纯虚函数 。纯虚函数的声明在基类中,但不提供实现,它的作用是为派生类提供一个 接口框架,并强制要求任何派生类都必须提供该函数的实现。
- 一定要在派生类中定义和基类纯虚函数相同名称的成员函数 ,否则在定义该派生类的对象时 会编译报错。
- 纯虚函数的基类不能用于创建对象,否则 会编译报错。因此,纯虚函数所在的类称为 抽象类 (详见:【C++面向对象】12. 数据抽象*、数据封装*、接口/抽象类*),相对应的,可以用于实例化对象的类被称为 具体类 。
- 纯虚函数的一般形式
virtual int FunctionName() = 0;
- 实例
#include <iostream>
using namespace std;
// 基类 Shape
class Shape
{
protected:
int width, height;
public:
Shape( int a=0, int b=0)
{
width = a;
height = b;
}
virtual int area() =0; // 纯虚函数
};
// 派生类 Rectangle
class Rectangle: public Shape
{
public:
Rectangle( int a=0, int b=0):Shape(a, b) { }
int area ()
{
cout << "Rectangle class area : " << width * height<<endl;
return (width * height);
}
};
// 派生类 Triangle
class Triangle: public Shape
{
public:
Triangle( int a=0, int b=0):Shape(a, b) { }
int area ()
{
cout << "Triangle class area : " <<width * height<<endl;
return (width * height / 2);
}
};
int main( )
{
/*
Shape Myshape; // 定义基类对象会导致编译报错
*/
Shape *shape;
Rectangle rec(10,7);
Triangle tri(10,5);
shape = &rec; // 将指针指向派生类对象 rec
shape->area();// 调用求面积函数 area
shape = &tri; // 将指针指向派生类对象 tri
shape->area();// 调用求面积函数 area
return 0;
}
- 虚函数和纯虚函数的对比
虚函数 | 纯虚函数 | |
---|---|---|
定义 | virtual int FunctionName( ) { //函数主体 } | virtual int FunctionName() =0; |
派生类内是否要必须定义同名函数 | 否 当派生类没有定义同名函数时,派生类调用同名函数会默认调用基类的函数。 | 是 当派生类没有定义同名函数时,定义派生类对象就会导致编译报错。 |
基类是否可以实例化对象 | 是 | 否 会导致编译报错。 |