C++ 多态
当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。
基类 Shape
被派生为两个类。
//============================================================================
// Name : polymorphism
// Author : Yongqiang Cheng
// Version : Version 1.0.0
// Copyright : Copyright (c) 2019 Yongqiang Cheng
// Description : Hello World in C++, Ansi-style
//============================================================================
#include <iostream>
using namespace std;
class Shape
{
protected:
int width, height;
public:
Shape(int a = 0, int b = 0)
{
width = a;
height = b;
}
int area()
{
cout << "Parent class area :" << endl;
return 0;
}
};
class Rectangle: public Shape
{
public:
Rectangle(int a = 0, int b = 0) :Shape(a, b)
{
}
int area()
{
cout << "Rectangle class area :" << endl;
return (width * height);
}
};
class Triangle: public Shape
{
public:
Triangle(int a = 0, int b = 0) :Shape(a, b)
{
}
int area()
{
cout << "Triangle class area :" << endl;
return (width * height / 2);
}
};
int main()
{
Shape *shape;
Rectangle rec(10, 7);
Triangle tri(10, 5);
shape = &rec;
shape->area();
shape = &tri;
shape->area();
return 0;
}
Parent class area :
Parent class area :
调用函数 area()
被编译器设置为基类中的版本,这是静态多态或静态链接,函数调用在程序执行前就准备好了,这也被称为早绑定,因为 area()
函数在程序编译期间就已经设置好了。
在 Shape
类中,area()
的声明前放置关键字 virtual
。
//============================================================================
// Name : polymorphism
// Author : Yongqiang Cheng
// Version : Version 1.0.0
// Copyright : Copyright (c) 2019 Yongqiang Cheng
// Description : Hello World in C++, Ansi-style
//============================================================================
#include <iostream>
using namespace std;
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 :" << endl;
return 0;
}
};
class Rectangle: public Shape
{
public:
Rectangle(int a = 0, int b = 0) :Shape(a, b)
{
}
int area()
{
cout << "Rectangle class area :" << endl;
return (width * height);
}
};
class Triangle: public Shape
{
public:
Triangle(int a = 0, int b = 0) :Shape(a, b)
{
}
int area()
{
cout << "Triangle class area :" << endl;
return (width * height / 2);
}
};
int main()
{
Shape *shape;
Rectangle rec(10, 7);
Triangle tri(10, 5);
shape = &rec;
shape->area();
shape = &tri;
shape->area();
return 0;
}
Rectangle class area :
Triangle class area :
此时编译器看的是指针的内容,而不是它的类型。由于 tri
和 rec
类的对象的地址存储在 shape
中,所以会调用各自的 area()
函数。
每个子类都有一个函数 area()
的独立实现,这是多态的一般使用方式。有了多态,您可以有多个不同的类,都带有同一个名称但具有不同实现的函数,函数的参数甚至可以是相同的。
1. 虚函数
虚函数是在基类中使用关键字 virtual
声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到基类中定义的虚函数。
我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接或后期绑定。
2. 纯虚函数
想要在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是您在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。
基类中的虚函数 area()
改写为纯虚函数。
//============================================================================
// Name : polymorphism
// Author : Yongqiang Cheng
// Version : Version 1.0.0
// Copyright : Copyright (c) 2019 Yongqiang Cheng
// Description : Hello World in C++, Ansi-style
//============================================================================
#include <iostream>
using namespace std;
class Shape
{
protected:
int width, height;
public:
Shape(int a = 0, int b = 0)
{
width = a;
height = b;
}
// pure virtual function
virtual int area() = 0;
};
class Rectangle: public Shape
{
public:
Rectangle(int a = 0, int b = 0) :Shape(a, b)
{
}
int area()
{
cout << "Rectangle class area :" << endl;
return (width * height);
}
};
class Triangle: public Shape
{
public:
Triangle(int a = 0, int b = 0) :Shape(a, b)
{
}
int area()
{
cout << "Triangle class area :" << endl;
return (width * height / 2);
}
};
int main()
{
Shape *shape;
Rectangle rec(10, 7);
Triangle tri(10, 5);
shape = &rec;
shape->area();
shape = &tri;
shape->area();
return 0;
}
Rectangle class area :
Triangle class area :
= 0
告诉编译器,函数没有主体,上面的虚函数是纯虚函数。
C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数:
- 必须存在继承关系。
- 继承关系必须有同名虚函数 (其中虚函数是在基类中使用关键字
virtual
声明的函数,在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到基类中定义的虚函数)。 - 存在基类类型的指针或者引用,通过该指针或引用调用虚函数。
虚函数可以不实现 (定义),不实现 (定义) 的虚函数是纯虚函数。在一个类中如果存在未定义的虚函数,那么不能直接使用该类的实例,可以理解因为未定义 virtual 函数,其类是抽象的,无法实例化。我们必须实现抽象类,否则无法实例化。
//============================================================================
// Name : polymorphism
// Author : Yongqiang Cheng
// Version : Version 1.0.0
// Copyright : Copyright (c) 2019 Yongqiang Cheng
// Description : Hello World in C++, Ansi-style
//============================================================================
#include <iostream>
using namespace std;
class Base
{
public:
virtual void tall();
};
class People: Base
{
public:
void tall()
{
cout << "people" << endl;
}
};
int main()
{
People people;
people.tall();
(&people)->tall();
return 0;
}
20:51:56 **** Build of configuration Debug for project yongqiang ****
make all
Building file: ../yongqiang.cpp
Invoking: GCC C++ Compiler
g++ -O0 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"yongqiang.d" -MT"yongqiang.o" -o "yongqiang.o" "../yongqiang.cpp"
Finished building: ../yongqiang.cpp
Building target: yongqiang
Invoking: GCC C++ Linker
g++ -o "yongqiang" ./yongqiang.o
./yongqiang.o: In function `Base::Base()':
/home/strong/eclipse-workspace/yongqiang/Debug/../yongqiang.cpp:12: undefined reference to `vtable for Base'
makefile:44: recipe for target 'yongqiang' failed
./yongqiang.o:(.rodata._ZTI6People[_ZTI6People]+0x18): undefined reference to `typeinfo for Base'
collect2: error: ld returned 1 exit status
make: *** [yongqiang] Error 1
20:51:56 Build Finished (took 683ms)
//============================================================================
// Name : polymorphism
// Author : Yongqiang Cheng
// Version : Version 1.0.0
// Copyright : Copyright (c) 2019 Yongqiang Cheng
// Description : Hello World in C++, Ansi-style
//============================================================================
#include <iostream>
using namespace std;
class Base
{
public:
virtual void tall()
{
}
};
class People: Base
{
public:
void tall()
{
cout << "people" << endl;
}
};
int main()
{
People people;
people.tall();
(&people)->tall();
return 0;
}
在 main
方法中,我们不能使用 Base base;
这行代码,此时的 tall
没有实现,函数表 (vtable) 的引用是未定义的,故而无法执行。但我们可以使用 People people;
然后 people.tall();
或 (&people)->tall();
因为 People
实现或者说重写、覆盖了 Base
的纯虚方法 tall()
,使其在 People
类中有了定义,函数表挂上去了,于是可以诞生实例了。
people
people
针对普通的函数,即使我们只声明,不定义,也不会产生上述不可用的问题。
- 纯虚函数声明如下:
virtual void funtion1() = 0;
。纯虚函数一定没有定义,纯虚函数用来规范派生类的行为,即接口。包含纯虚函数的类是抽象类,抽象类不能定义实例,但可以声明指向实现该抽象类的具体类的指针或引用。 - 虚函数声明如下:
virtual ReturnType FunctionName(Parameter) {};
虚函数必须实现,如果不实现,编译器将报错。 - 对于虚函数来说,父类和子类都有各自的版本。由多态方式调用的时候动态绑定。
- 实现了纯虚函数的子类,该纯虚函数在子类中就编程了虚函数,子类的子类即孙子类可以覆盖该虚函数,由多态方式调用的时候动态绑定。
- 虚函数是 C++ 中用于实现多态 (polymorphism) 的机制。核心理念就是通过基类访问派生类定义的函数。
- 友元不是成员函数,只有成员函数才可以是虚拟的,因此友元不能是虚拟函数。但可以通过让友元函数调用虚拟成员函数来解决友元的虚拟问题。
动态联编的实现机制 VTABLE
编译器对每个包含虚函数的类创建一个虚函数表 VTABLE,表中每一项指向一个虚函数的地址,即 VTABLE 表可以看成一个函数指针的数组,每个虚函数的入口地址就是这个数组的一个元素。
每个含有虚函数的类都有各自的一张虚函数表 VTABLE。每个派生类的 VTABLE 继承了它各个基类的 VTABLE。如果基类 VTABLE 中包含某一项 (虚函数的入口地址),则其派生类的 VTABLE 中也将包含同样的一项,但是两项的值可能不同。如果派生类中重载了该项对应的虚函数,则派生类 VTABLE 的该项指向重载后的虚函数。如果派生类中没有对该项对应的虚函数进行重新定义,则使用基类的这个虚函数地址。
在创建含有虚函数的类的对象的时候,编译器会在每个对象的内存布局中增加一个 vptr 指针项,该指针指向本类的 VTABLE。在通过指向基类对象的指针调用一个虚函数时,编译器生成的代码是先获取所指对象的指针,然后调用指针所指向类的 VTABLE 中的对应项 (具体虚函数的入口地址)。
当基类中没有定义虚函数时,其长度 = 数据成员长度。派生类长度 = 自身数据成员长度 + 基类继承的数据成员长度。
当基类中定义虚函数后,其长度 = 数据成员长度 + 虚函数表的地址长度。派生类长度 = 自身数据成员长度 + 基类继承的数据成员长度 + 虚函数表的地址长度。
包含一个虚函数和几个虚函数的类的长度增量为 0。含有虚函数的类只是增加了一个指针用于存储虚函数表的首地址。
派生类与基类同名的虚函数在 VTABLE 中有相同的索引号 (或序号)。
父类的虚函数或纯虚函数在子类中依然是虚函数。有时我们并不希望父类的某个函数在子类中被重写,在 C++11 及以后可以用关键字 final
来避免该函数再次被重写。
//============================================================================
// Name : polymorphism
// Author : Yongqiang Cheng
// Version : Version 1.0.0
// Copyright : Copyright (c) 2019 Yongqiang Cheng
// Description : Hello World in C++, Ansi-style
//============================================================================
#include <iostream>
using namespace std;
class Base
{
public:
virtual void func()
{
cout << "This is Base" << endl;
}
};
class _Base: public Base
{
public:
void func() final
{
cout << "This is _Base" << endl;
}
};
class __Base: public _Base
{
//public:
// void func()
// {
// cout << "This is __Base" << endl;
// }
};
int main()
{
Base *ptr;
_Base a;
__Base b;
ptr = &a;
ptr->func();
ptr = &b;
ptr->func();
_Base *ptr2;
ptr2 = &b;
ptr2->func();
return 0;
}
21:10:29 **** Build of configuration Debug for project yongqiang ****
make all
Building file: ../yongqiang.cpp
Invoking: GCC C++ Compiler
g++ -O0 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"yongqiang.d" -MT"yongqiang.o" -o "yongqiang.o" "../yongqiang.cpp"
../yongqiang.cpp:24:15: warning: override controls (override/final) only available with -std=c++11 or -std=gnu++11
void func() final
^
Finished building: ../yongqiang.cpp
Building target: yongqiang
Invoking: GCC C++ Linker
g++ -o "yongqiang" ./yongqiang.o
Finished building target: yongqiang
21:10:29 Build Finished (took 677ms)
//============================================================================
// Name : polymorphism
// Author : Yongqiang Cheng
// Version : Version 1.0.0
// Copyright : Copyright (c) 2019 Yongqiang Cheng
// Description : Hello World in C++, Ansi-style
//============================================================================
#include <iostream>
using namespace std;
class Base
{
public:
virtual void func()
{
cout << "This is Base" << endl;
}
};
class _Base: public Base
{
public:
void func() final
{
cout << "This is _Base" << endl;
}
};
class __Base: public _Base
{
public:
void func()
{
cout << "This is __Base" << endl;
}
};
int main()
{
Base *ptr;
_Base a;
__Base b;
ptr = &a;
ptr->func();
ptr = &b;
ptr->func();
_Base *ptr2;
ptr2 = &b;
ptr2->func();
return 0;
}
21:15:47 **** Incremental Build of configuration Debug for project yongqiang ****
make all
Building file: ../yongqiang.cpp
Invoking: GCC C++ Compiler
g++ -std=c++0x -O0 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"yongqiang.d" -MT"yongqiang.o" -o "yongqiang.o" "../yongqiang.cpp"
../yongqiang.cpp:33:10: error: virtual function ‘virtual void __Base::func()’
void func()
^
../yongqiang.cpp:24:10: error: overriding final function ‘virtual void _Base::func()’
void func() final
^
make: *** [yongqiang.o] Error 1
subdir.mk:18: recipe for target 'yongqiang.o' failed
21:15:47 Build Finished (took 677ms)
//============================================================================
// Name : polymorphism
// Author : Yongqiang Cheng
// Version : Version 1.0.0
// Copyright : Copyright (c) 2019 Yongqiang Cheng
// Description : Hello World in C++, Ansi-style
//============================================================================
#include <iostream>
using namespace std;
class Base
{
public:
virtual void func()
{
cout << "This is Base" << endl;
}
};
class _Base: public Base
{
public:
void func() final
{
cout << "This is _Base" << endl;
}
};
class __Base: public _Base
{
//public:
// void func()
// {
// cout << "This is __Base" << endl;
// }
};
int main()
{
Base *ptr;
_Base a;
__Base b;
ptr = &a;
ptr->func();
ptr = &b;
ptr->func();
_Base *ptr2;
ptr2 = &b;
ptr2->func();
return 0;
}
This is _Base
This is _Base
This is _Base
如果不希望一个类被继承,可以使用 final
关键字。
class Class_Name final
{
...
};
则该类将不能被继承。
多态用到纯虚函数或虚函数,C++ 的这种特殊处理函数利用父对象指针来访问子对象,然后根据子对象的函数来调用。