多态初步
来源:互联网 发布:黑莓解网络锁 编辑:程序博客网 时间:2024/05/01 20:29
多态初步
1. 什么是多态
多态就是同一个处理手段可以用来处理多种不同的情况。
同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,这就是多态性。
2. 多态作用
在C++中,就是相同代码,实现不同功能,从而简化编程。
在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。
(1)应用程序不必为每一个派生类编写功能调用,只需要对抽象基类进行处理即可。这一招叫“以不变应万变”,可以大大提高程序的可复用性。
(2)派生类的功能可以被基类指针引用,这叫向后兼容,可以提高程序的可扩充性和可维护性。将来写的程序可以被以前写的程序调用。
3. 多态分类
多态分为三种:
第一种是函数重载;(静态多态)
第二种是模板函数;(静态多态)
第三种是虚函数。(动态多态)
静态绑定是指在编译时确定函数调用,而动态绑定是指在运行时确定调用函数。
4. 虚函数
动态多态是依靠虚函数实现的,在C++中用关键字virtual实现虚函数。
4.1 对象模型
单一继承情况下,对拥有虚函数的类来说,都会为该类的对象添加一个虚函数表指针(vptr),该指针指向该类唯一的虚函数表(vtable)。
例:
Base *pb = new Derived;
pb->print(); // print()为虚函数
会被转换为
(*pb->vptr[ix])(); // ix为虚函数print在虚函数表中的偏移量
(*pb->vptr[ix])(pb); // 也有可能是这样,还会传递对象的地址来充当this指针
以上调用中,如果print有参数,则可能会是这样的(*pb->vptr[ix])(pb, args_list);
派生类会继承基类的虚函数,如果自己覆盖了基类的虚函数,则在自己的虚函数表中指向自己定义的虚函数体,否则指向基类定义的虚函数体。假如基类有两个虚函数,Test和NotOverride,则如下图
4.2 使用虚函数,要遵循以下重要规则:
1.如果虚函数在基类与派生类中出现,仅仅是名字相同,而形式参数不同,或者是返回类型不同,那么即使加上了virtual关键字,也是不会进行动态编联的。
2.只有类的成员函数才能说明为虚函数,因为虚函数仅适合用与有继承关系的类对象,所以普通函数不能说明为虚函数。
3.静态成员函数不能是虚函数,因为静态成员函数的特点是不受限制于某个对象。
4.内联(inline)函数不能是虚函数,因为内联函数不能在运行中动态确定位置。
5.构造函数不能是虚函数,因为构造的时候,对象还是一片未定型的空间,只有构造完成后,对象才是具体类的实例。
6.析构函数可以是虚函数,而且通常声名为虚函数。
4.3 虚析构函数
通过基类指针删除派生类对象,只会调用基类的析构函数,而不会调用派生类析构函数。如果将基类的析构函数声明为虚的,则会先调用派生类析构函数,然后调用基类析构函数,从而完全析构对象。
例:
class Base
{
public:
Base() {}
//virtual
~Base() { cout<<"~Base/n"; }
};
class Derived : public Base
{
int *pa;
public:
Derived() { pa = new int[100]; }
~Derived() {
delete []pa;
cout<<"~Derived/n";
}
};
int main()
{
Base *pb = NULL;
pb = new Derived();
delete pb;
}
4.4 纯虚函数
有纯虚函数的类被称为抽象类,不能被实例化。纯虚函数定义形式如下
virtual void vfun() = 0;
4.5 基类的private虚函数
基类可以将虚函数定义为private,并且在派生类中定义,再在基类中调用在派生类中实现的private方法。
例:
class Base
{
private:
/*
这种写法的语意是:告诉派生类,你最好覆盖基类的Test()函数,
但是你不要管它如何使用,也不要自己调用这个函数。
*/
virtual void Test()
{
cout << "call Base Test()" << endl;
}
public:
void CallTest()
{
Test();
}
};
class Derived : public Base
{
private:
virtual void Test()
{
cout << "call Derived Test()" << endl;
}
};
int main()
{
Base b;
Derived d;
Base *pb = NULL;
pb = &b;
pb->CallTest();
pb = &d;
pb->CallTest();
}
4.6 构造函数和析构函数中的虚函数调用
一个类的虚函数在它自己的构造函数和析构函数中被调用时,它们就变成了普通函数,不起作用了。也就是说不能在构造函数和析构函数中让自己“多态”。
例:
class A
{
public:
A() { // 在这里,无论如何都是A::foo()被调用!
cout<<"A() call ";
foo();
}
~A() { // 在这里,无论如何都是A::foo()被调用!
cout<<"~A() call ";
foo();
}
virtual void foo() { cout<<"A::foo()/n"; }
};
class B: public A
{
public:
virtual void foo() { cout<<"B::foo()/n"; }
};
int main()
{
A * a = new B;
delete a;
}
5. 容易混淆的概念
(OverLoad)成员函数被重载的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual关键字可有可无。
(OverRide)覆盖是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual关键字。
(5)返回值也必须相同
(OverWrite)重写或隐藏(Hide)
这里“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。
例:(这个例子来自高质量C/C++编程)
class Base
{
public:
virtual void f(float x) {
cout << "Base::f(float) " << x << endl;
}
void g(float x) {
cout << "Base::g(float) " << x << endl;
}
void h(float x) {
cout << "Base::h(float) " << x << endl;
}
};
class Derived : public Base
{
public:
virtual void f(float x) {
cout << "Derived::f(float) " << x << endl;
//return 0;
}
void g(int x) {
cout << "Derived::g(int) " << x << endl;
}
void h(float x) {
cout << "Derived::h(float) " << x << endl;
}
};
void main(void)
{
// pb,pd指向同一个派生类对象
Derived d;
Base *pb = &d;
Derived *pd = &d;
// Good : behavior depends solely on type of the object
pb->f(
pd->f(
// Bad : behavior depends on type of the pointer
pb->g(
pd->g(
// Bad : behavior depends on type of the pointer
pb->h(
pd->h(
}
6. 模板函数(静态多态)
通过模板来实现多态。
实现的方式有:
Ø 编写不同的代码,可以是函数,也可以是类。
Ø 模板特化,编写某个函数模板的特例。
例:
// 为类Circle和Rectangle定义同名的成员函数名draw
class Circle
{
public:
void draw() {
cout<<"draw circle/n";
}
};
class Rectangle
{
public:
void draw() {
cout<<"draw Rectangle/n";
}
};
// 通过引用shape来画任何图形
template <typename Shape>
void drawShape(Shape& shape)
{
shape.draw(); // 根据shape的具体类型调用对应的draw()
}
// 模板特化方式
template <class T>
T Max( T t1, T t2 )
{
cout<<"geneal template/n";
return (t1 > t2 ? t1 : t2);
}
// 特化
template<> int Max<int>(int t1, int t2)
{
cout<<"my int template/n";
return (t1 > t2 ? t1 : t2);
}
int main()
{
// 自己写的具体类
Circle c;
Rectangle r;
drawShape(c); // 调用Circle::draw()
drawShape(r); // 调用Rectangle::draw()
cout<<"/n";
// 模板特化形成的多态
char ch1 = 'a';
char ch2 = 'b';
Max(ch1, ch2); // 调用通用模板
int a = 1;
int b = 2;
Max(a, b); // 调用特化模板
}
7. 补充说明
本文中并没有讨论多重继承下的情况,这个时候的对象模型将会非常复杂,包含多个vptr。具体情况可以参考侯捷的《深度探索C++对象模型》一书。
- 多态初步
- java多态初步
- 多态的初步理解
- JAVA编程心得-多态设计初步
- Java关于多态的初步理解
- 第十章 JAVA多态初步学习
- 多文件编程初步
- 初步
- PHP多进程编程初步
- 关于多态(polymorphism)的初步知识(此处指动态多态)
- 菜鸟学习,大侠多指教(对多态、重载和封装的初步了解)
- Windows多线程多任务设计初步
- Windows多线程多任务设计初步
- Windows多线程多任务设计初步
- Windows多线程多任务设计初步
- Windows多线程多任务设计初步1
- Windows多线程多任务设计初步2
- Windows多线程多任务设计初步3
- 禁用Windows XP的自动播放功能
- cs文件常用js代码
- Linux上安装GCC编译器过程
- 取数字问题(M*N)
- Delphi 7事件的多处理机制
- 多态初步
- extern const
- 三国人物顺口溜
- smarty常规函数
- 某日无聊 在Free BSD上架设了一个BIND服务器
- 【转】main函数的参数
- 动态加载TreeNode -- ComponentArt TreeView
- java中数据类型转换
- 几个免费的.net控件