Virtual是C++
//---------------------------------------------------------
class
{
public:Base(){}
public:
};
class
{
public:Derived(){}
public:
};
int
{
}
//---------------------------------------------------------
Output:
Derived
//---------------------------------------------------------
这也许会使人联想到函数的重载,但稍加对比就会发现两者是完全不同的:
(1)
覆盖的函数必须在有继承关系的不同的类中
(2)
重载的函数必须函数名相同,参数不同。参数不同的目的就是为了在函数调用的时候编译器能够通过参数来判断程序是在调用的哪个函数。这也就很自然地解释了为什么函数不能通过返回值不同来重载,因为程序在调用函数时很有可能不关心返回值,编译器就无法从代码中看出程序在调用的是哪个函数了。
(3)
重载和Virtual没有任何瓜葛,加不加都不影响重载的运作。
关于C++的隐藏规则:
我曾经听说过C++的隐藏规则:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual
关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual
关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。
这里,林锐博士好像犯了个错误。C++并没有隐藏规则,林锐博士所总结的隐藏规则是他错误地理解C++多态性所致。下面请看林锐博士给出的隐藏规则的例证:
//------------------------------------------------------
#include
class
{
public:
virtual
void
void
};
class
{
public:
virtual
void
void
};
void
{
Derived
Base
Derived
//
pb->f(3.14f);
pd->f(3.14f);
//
pb->g(3.14f);
pd->g(3.14f);
//
pb->h(3.14f);
pd->h(3.14f);
}
//---------------------------------------------------------
林锐博士认为bp
所以并没有所谓的隐藏规则,虽然《高质量C++/C
纯虚函数:
C++语言为我们提供了一种语法结构,通过它可以指明,一个虚拟函数只是提供了一个可被子类型改写的接口。但是,它本身并不能通过虚拟机制被调用。这就是纯虚拟函数(pure
virtual
class
public:
//
virtual
//
};
这里函数声明后面紧跟赋值0。
包含(或继承)一个或多个纯虚拟函数的类被编译器识别为抽象基类。试图创建一个抽象基类的独立类对象会导致编译时刻错误。(类似地通过虚拟机制调用纯虚拟函数也是错误的例如)
//
//
//
Query
//
Query
抽象基类只能作为子对象出现在后续的派生类中。
如果只知道virtual加在函数前,那对virtual只了解了一半,virtual还有一个重要用法是virtual
在缺省情况下C++中的继承是“按值组合”的一种特殊情况。当我们写
class
每个Bear
class
则PolarBear
class
缺省情况下,每个iostream
C++语言的解决方案是,提供另一种可替代按“引用组合”的继承机制虚拟继承(virtual
中出现多少次共享的基类子对象被称为虚拟基类。
//
//
class
class
虚拟派生不是基类本身的一个显式特性,而是它与派生类的关系如前面所说明的,虚拟继承提供了“按引用组合”。也就是说,对于子对象及其非静态成员的访问是间接进行的。这使得在多继承情况下,把多个虚拟基类子对象组合成派生类中的一个共享实例,从而提供了必要的灵活性。同时,即使一个基类是虚拟的,我们仍然可以通过该基类类型的指针或引用,来操纵派生类的对象。
c++
这几天一直在笔试,有人遇到这类题了。翻了翻标准,总结一下
当决定调用哪个函数时,
如果是虚函数,那么取决于指针所指向的对象的类型。
如果是非虚函数,那么取决于指针的类型
ISO/IEC
[Note:
called
only
当用父类指针(引用)指向子类对象时判断调用哪个函数总结起来就是:
先看指针(或引用)是什么类型的,再看调用的函数是否是虚函数。
如果是虚函数再看是否被子类override,如果override了,那么调用的是子类中的函数。
如果不是虚函数,那么就在指针(引用)所指的类中查找对应的函数,调用的是父类中的函数。
22:49
了解了上面的内容后,就能明白为什么要将基类的析构函数声明为虚函数。
因为,如果父类析构函数不是虚函数。而此时你有一个指向子类对象的父类指针,那么当你delete这个指针的时候,父类的析构函数被调用,但是子类的析构函数没有被调用!就是情况C。如果父类的析构函数声明为虚函数那么子类的析构函数也会是虚函数。这样二者的虚函数都会被调用,也就可以避免潜在的资源未释放的问题。
这也是Effective
多态提供了与具体实现相隔离的另一类接口,即把“what”从“how”中分离出来,多态性提高了代码的组织性和可读性。多态性是一种对消息的处理方式可以随接手消息的处理对象而变的一种机制。
1.向上映射
将派生类对象通过引用或者指针变成基类对象,引用或者指针的活动称为向上映射。向上映射总是安全的,因为是从更专门的类型到更一般的类型
eg:
derived
base*
base&
void
fun(d);//发生向上映射
2.函数调用与捆绑
捆绑:把函数体与函数调用相联系
早捆绑:由编译器连接器完成,捆绑在程序运行之前
晚捆绑:捆绑在运行时发生,实现晚捆绑事必须有一种机制在运行时确定对象的类型和合适的调用函数
c++中晚捆绑的实现:
关键字virtual告诉编译器它应该实行晚捆绑
编译器对每个包含虚函数的类创建VTABLE,放置类的虚函数地址
编译器秘密放置vpointer(VPTR)
当多态调用时,使用VPTR在VTABLES表中查找函数地址
3.虚函数
为了引起晚捆绑的特定的函数
虚函数提供了一种分辨对象执行不同功能的能力
但是从效率角度出发时,虚函数不是高效的,它需要VPTR指针的压栈出栈操作等
4.纯虚函数
纯虚函数是一种特殊的虚函数,它的一般格式如下:
纯虚函数和虚函数的区别:
1)虚函数和纯虚函数可以定义在同一个类(class)中,含有纯虚函数的类被称为抽象类(abstract
2)虚函数可以被直接使用,也可以被子类(sub
纯虚函数是非常有用的,因为它们使得类具有明显的抽象性
5.抽象类
含有纯虚函数的类称为抽象类
任何对抽象类的实例化操作都将导致错误的产生,因为抽象类是不能直接被调用的,必须被子类继承重载后,根据要求调用其子类的方法
6.对象切片
C++提供了继承机制和虚拟,并通过(且只能通过)指向同一类族的指针或者引用来实现多态,否则多态不起作用。原因之一著名的对象切片(Object
1)无虚拟机制继承的对象切片
//:objcut.cpp
#include
class
{
public:
public:
};
class
{
public:
};
main()
{
}
编译将出错,这是因为在将aDMB拷贝给aMB时发生了对象切片,在aMB对象中只有MyBase的信息,所有的关于DerivedMyBase类的信息都被切片了。
注释后面那个语句也将导致同样的错误,但是并没有发生对象切片,发生错误的原因是:
2)对象切片的原理
对象切片产生的原因是bitwise的copy构造函数的调用。在“MyBase
3)虚拟机制与拷贝方式
当类中没有虚拟机制、没有其他类对象的成员时(只包含built-in类型、指针或者数组),默认copy
因为包含虚拟机制的类在定义一个对象时,编译器会向ctor中添加初始化vtable和vbaseclasstable(依赖于具体编译器)的代码,这样可以保证vtable中的内容与类型完全匹配。也就是说MyBase和DerivedMyBase有这相似的VTABLE,但不是完全相同——例如DerivedMyBase中还可以定义自己的virtual函数,这样它的VTABLE就会有更多表项。
而多态的实现是通过将函数调用解析为VTABLE中的偏移量来实现。pMB->Get()可能会被编译器解析成:
(*pMB->__vtable[Offset_of_Get])();
而当MyBase作为虚基类时,访问其中的数据成员可能就是:
pMB->__vBaseClassMyBase->b;
那么,当“aMB
7.虚函数和构造函数
当创建一个包含有虚函数的对象时,必须初始化它的VPTR以及指向相应的VTABLE,这必须有关虚函数的调用之前完成。编译器在构造函数的开头部分秘密的插入了能初始化VPTR的代码,这个相当与一个小小的内联函数调用
8.虚拟析构函数
如果一个指针是指向基类的,编译器只能知道delete期间调用这个析构函数的基类版本,当把虚构函数声明为虚函数时则可以解决这个问题。
eg:
//:pvdest.cpp
#include
class
{
public:
};
class
{
public:
};
main()
{
}
输出:~base()
如果改为
输出:~derived()
出处:http://blog.sina.com.cn/s/blog_6ae7d6b00100pb4v.html
virtual
virtual ReturnType Function_2() = 0;
先讲示例吧,再总结结论。
2、示例:
class Animail{
public:
};
class Dog: public Animail{
};
int main(int argc, char* argv[])
{
}
这段代码的输出结果是什么呢?起初我认为是:Animail::Function_1()与Dog::Function_1(), 因为第一次输出是引用基类Animail的实例,第二次输出是引用子类Dog的实例。事实上答案是Animail::Function_1()与 Animail::Function_1(),为什么呢?
这里我们需要明白:你就记住,不管引用的实例是哪个类的,当你调用的时候,系统会调用左值那个对象所属类的方法。比如说 上面的代码类Animail和 Dog都有一个Function_1函数,因为p是一个Animail类的指针,所以不管你将p指针指向类Animail或是类Dog,最终调用的函数都是类Animail的Function_1函数。这就是静态联篇,编译器在编译的时候就已经确定好了。可是如果我想实现跟据实例的不同来动态决定调用哪个函数呢?这就须要用到虚函数(也就是动态联篇)。
class Animail{
public:
};
class Dog: public Animail{
};
在基类的成员函数前加关键字virtual,则表示这个函数是一个虚函数。
所谓虚函数就是在编译的时候不确定要调用哪个函数,而是动态决定将要调用哪个函数。它的作用就是为了能让这个函数在它的子类里面可以被重载,这样的话,编译器就可以使用后期绑定来达到多态了,也就是:用基类的指针来调用子类的这个函数。
要实现虚函数必须保证派生类的函数名与基类相同,参数名参数类型等也要与基类相同。但派生类中的virtual关键字可以省略,也表示这是一个虚函数。
下面来分析一下代码,声明一个基类的指针(必须是基类,反之则不行)p,把p指向类Animail的实例cAnimail,调用Function_1函数,这时系统会判断p所指向的实例的类型,如果是Animail类的实例就调用Animail类的Function_1函数,如果是Dog类的实例cDog就调用Dog类的Function_1函数。
下面来讲一下纯虚函数,包含纯虚函数的类也可叫虚基类或者抽象类:
class Animail{
public:
};
class Dog : public Animail{
};
class Pig : public Animail{
public:
};
如上代码,定义了一个动物类(Animail),类中有一函数GetColor可取得动物颜色,但动物有很多很多种,颜色自然无法确定,所以就把它声明为纯虚函数,也就是光声明函数名不去定义(实现)它,类Dog继承了Animail并实现了里面的代码,返回黄色。Bike类同样道理。有一点须要注意一下,纯虚函数不能实例化,但可以声明指针,所以上面的main函数中: Animail cAnimail; 编译器会告诉你:由于它的成员的原因,无法instantiate 抽象类Animail,并且警告你GetColor()
虚函数
纯虚函数