向iOS开发者介绍C++(二)

800 查看

欢迎回到向iOS开发者介绍C++系列的第二部分(向iOS开发者介绍C++(一)) !在第一部分,我们了解了类和内存管理。在第二部分部分我们将深入了解类以及其他有意思的特征。你将会了解到什么是“模板”以及标准模板库。

多态性

简单地说,多态性是一个重载子类中函数的概念。在Objective-C中,你可能已经做过很多次,例如,子类化UIViewController和重载viewDidLoad。
parfwefrot_lion-480x305

C++的多态性比Objective-C的多态性更进一层。因此,当我解释这个强大的功能时要紧跟我的思路。

首先,以下为在类中重载成员函数的例子:

但是,如果你这样做会发生什么呢:

哇,这可不是你所期望的输出!我猜你认为输出值应该是10,对么?这就是C++和Objective-C最大的不同。

在Objective-C中,将子类指针转换成基类指针是无关紧要的。如果你向对象发消息(如调用函数),是运行时找到对象的类并调用最先派生的方法。因此,Objective-C中这种情况下,子类Bar中的方法被调用。这里凸显出了我在第一部分提到的编译时和运行时的不同。

在上面的例子中,编译器调用value()时,编译器的职责是计算出哪个函数需要被调用。由于f的类型是指向Foo类的指针,
它执行跳至Foo:value()的代码。编译器不知道f实际上是Bar类的指针。

在这个简单的例子中,你可以认为编译器能推断出f是Bar类的指针。但是想一想如果f确实是一个函数的输入值的话将会发生什么呢?这种情况下编译器将不会知道它是一个继承了Foo类的指针。

静态绑定和动态绑定
上面的例子很好的证明了C++和Objective-C最主要的区别–静态绑定和动态绑定。上面的例子是静态绑定的例子。编译器负责解决调用哪个函数,并且在编译完成后这个过程将被存储为二进制。在运行时不能改变这个过程。

这与Objective-C中方法调用形成了对比,这就是动态绑定的一个例子。运行时本身负责决定调用哪个函数。

动态绑定会使Objective-C很强大。你可能已经意识到了在运行时可以为类方法或者交换方法实现。这在静态绑定语言中是不能实现的,静态绑定是在编译时调用方法的。

但是,在C++中还不止这样!C++通常是静态绑定,但是也可以使用动态绑定机制,即“虚函数”。

虚函数和虚表

虚函数提供动态绑定机制。通过使用table lookup(每个类定义一个表),虚函数推迟到runtime时选择调用哪个函数。然而,跟静态绑定相比,这确实引起了运行时轻微的间接成本。除了调用函数外,table lookup是必须的。静态绑定时仅需要执行调用的函数。

使用虚函数很简单,只需要将关键词“virtual”添加到谈及的函数。例如上面的例子用虚函数方式写的话,如下:

现在想一想运行同样的代码会发生什么:

这正是前面所预期的输出值,对吧?因此在C++中可以用动态绑定,但是你需要根据遇到的情况决定是用静态绑定还是动态绑定。

在C++中这种类型的灵活性是司空见惯的,这使C++成为一种多范型的语言。Objective-C很大程度上迫使你进入严格的模式,尤其是用Cocoa框架时。而C++中,很多都是由开发者决定的。

现在开始了解虚拟函数是如何发挥作用的吧!

picrgregregerg3

虚函数的内部功能

在你明白虚函数是怎样工作之前,你需要知道非虚函数是如何工作的。

想一想下面的代码:

如果foo()是个非虚函数,那么编译器将会把它转换成代码,直接跳到MyClass类的foo()函数。

但是记住,这就是非虚函数的问题所在。回想之前的例子,如果这个类是多态的,那么编译器由于不知道变量的全部类型,也就不知道应该跳到哪个函数。这就需要一种方法在运行时查找到正确的函数。

要完成这种查找,虚函数要使用“virtual table”(也称“v-table”,虚表)。虚表是一个查找表来将函数映射到其实现上,并且每个类都访问一个表。当一个虚函数被调用时,编译器发出代码来检索对象的虚表从而查找到正确的函数。

回顾上面的例子来看看这是如何工作的: