向iOS开发者介绍C++

85 篇文章 1 订阅
50 篇文章 0 订阅
 
http://www.cocoachina.com/industry/20140415/8163.html
你已经精通了Objective-C,并且一直想学更酷的东西?看看这篇文章吧!本文将向iOS开发者介绍C++。稍后我会介绍,Objective-C能够无缝地使用C和C++代码。因此,基于以下几点原因,iOS开发者理解C++将会很有帮助:
 
1.有时候你想在应用中使用一个用C++编写的库。
 
2.你可能用C++写一部分应用程序的代码,以便更容易跨平台移植。
 
3. 了解其他语言通常能帮助你更好地理解编程。
 
这篇文章针对那些已经理解Objective-C的iOS开发者。前提是假定你已明白怎么写Objective-C代码,并熟悉基本的C概念,比如类型、指针、函数等。
 
准备好学C++了么?那么就马上开始吧!
 
开始:语言简史
C++和Objective-C有一些共源:它们都根植于老式的好用的C语言,都是C语言的“超集”。因此,你可以在这两种语言中使用C语言的一些功能,和每种语言的附加特性。
 
如果你熟悉Objective-C,那么你将能粗略地理解你所遇到的C++代码。例如,两种语言中的数值类型(int型、float型和char型)的表现方式和使用规则都是完全一样的。
 
Objective-C和C++都在C语言基础上添加了面向对象的特征。如果你不熟悉“面向对象”,那么你真正需要明白的是面向对象指数据是由对象表示的,而对象是类的实例。事实上,C++最初称为“C with Classes”,内在的涵义是使C++面向对象。
 
“那么有什么区别么?”我听到了你的疑问。最大的区别是面向对象特性的方法。在C++中,很多行为是发生在编译时,而在Objective-C中,大多数是发生在运行时。你可能已经修改了Objective-C的运行时间来实现了一个类似 method swizzling的诡计,而在C++中这是不可能的。
 
C++也不像Objective-C一样有大量内省以及映射方法。在C++中,没有办法获得C++对象的类,而在Objective-C中你可以在一个实例中调用“类”方法。同样的,在C++中也没有相当于isMemberOfClass或者isKindOfClass的类。
 
以上对C++的粗略介绍显示了C++和Objective-C的历史和主要不同点。历史部分已经完成了,到我们继续学习一些C++特征的时间了。
 
 
C++ 类
在任何面向对象语言中,首先你要知道的是如何定义一个类。在Objective-C中,你通过创建一个头文件和一个执行文件来定义一个类,在C++中同样如此,语法也十分相似。
 
如下,是一个Objective-C类的例子:
  
  
  1. // MyClass.h 
  2.   
  3. #import <Foundation/Foundation.h> 
  4.   
  5. @interface MyClass : NSObject 
  6. @end 
  7.   
  8. // MyClass.m 
  9.   
  10. #import “MyClass.h” 
  11.   
  12. @implementation MyClass 
  13. @end 
 
作为一个经验丰富的iOS开发者你应该很明白,但是看看同样用C++写的例子:
  
  
  1. // MyClass.h 
  2.   
  3. class MyClass { 
  4. }; 
  5.   
  6. // MyClass.cpp 
  7.   
  8. #include “MyClass.h” 
  9.   
  10. /* Nothing else in here */ 
 
这里有一些本质的区别。首先,C++中的实现文件中什么都没有,这是因为你并没有在类中声明任何的方法。同理,就像Objective-C,一个空类不需要@implemenation/@end模块。
 
在Objective-C中,几乎每个类都继承自NSObject。你可以创建自己的根类,这意味着你的类将没有任何superclass。但是,你可能从来没有这么做过,除非你只是为了运行时好玩儿。对比C++,正如上面的例子一样,创建一个没有超类的类是很普遍的。
 
另外一个微小的区别是#include和#import。Objective-C将#import预处理器指令添加到C。在C++中没有相同的,标准的C-style是使用#include。Objective-C中的#import是确保一个文件只被包含一次,但在C++中你必须自己检查。
 
类成员变量和成员函数
当然,有比声明一个类多得多的事情。正如,在Objective-C和C++中,你可以在类中添加实例变量和方法。或许,你知道在C++中这两个不是这样命名的,C++中通常称为成员变量和成员函数。
 
 注意:“method(实体方法)”这个术语通常不用于C++中,这个特性只用在Objective-C中。在Objective-C中,通过消息分派带调用“method(实体方法)”。另外,function(函数)通过一个静态的C-style函数被调用。稍后在这篇文章中我将更多的解释静态和动态。
 
那么接下来你要如何声明成员变量和成员函数呢?如下:
  
  
  1. class MyClass { 
  2.     int x; 
  3.     int y; 
  4.     float z; 
  5.   
  6.     void foo(); 
  7.     void bar(); 
  8. }; 
 
这里有三个成员变量和两个成员函数。但是在C++中这里要有更多,在C++中,你可以限定成员变量和成员函数的范围,并且可以声明它们是公开访问的还是私有访问的。这个可以用于限制什么代码可以访问每个变量或者函数。
 
思考下面这个例子:
  
  
  1. class MyClass { 
  2.   public
  3.     int x; 
  4.     int y; 
  5.     void foo(); 
  6.   
  7.   private
  8.     float z; 
  9.     void bar(); 
 
这里,x,y和foo函数是公开访问。意思是可以在MyClass类的外部被调用。然而,z和bar函数是私有的。意味着只能在MyClass内部调用被调用。成员变量默认是私有的。
 
虽然这种区别确实存在于Objective-C中的实例变量中,但是很少使用。另外,在Objective-C中不太可能限制方法的调用范围。即使你只是在实现类内部声明一个方法而没有在接口中显示,技术上你还是可以外部调用这个方法。
 
Objective-C中的方法只约定为公开或私有。这就是为什么很多开发者选择给私有方法加前缀(例如“p_”前缀)来定义这个区别。这是为了和C++作比较,在C++中如果你试图从类的外部调用一个私有方法,编译器会抛出一个错误。
 
那么你要怎么使用类呢?和Objective-C非常相似,真的!你可以像下面这样创建一个实例:
  
  
  1. MyClass m; 
  2. m.x = 10; 
  3. m.y = 20; 
  4. m.foo(); 
 
简单吧!这里创建了一个MyClass的实例,分别设x=10,y=20,然后调用foo函数。
 
实现类的成员函数
你已经看到了如何定义一个类接口,但是函数呢?事实证明,这个十分简单。有如下两种方法你可以定义。
 
第一个实现函数的方法是在类的实现文件中定义--.cpp文件。例如:
  
  
  1. // MyClass.h 
  2. class MyClass { 
  3.     int x; 
  4.     int y; 
  5.     void foo(); 
  6. }; 
  7.   
  8. // MyClass.cpp 
  9. #include “MyClass.h” 
  10.   
  11. MyClass::foo() { 
  12.    // Do something 
 
以上是第一个方法。在Objective-C中定义十分简单。注意MyClass::的用法,这就是你如何表明foo()函数已经作为MyClass类的一部分被实现了。
 
第二个实现函数的方法是你在Objective-C中不能做到的。在C++中,你可以直接在头文件中定义一个函数,如下:
  
  
  1. // MyClass.h 
  2. class MyClass { 
  3.     int x; 
  4.     int y; 
  5.     void foo() { 
  6.         // Do something 
  7.     } 
  8. }; 
 
如果你只用过Objective-C,这看上去会很奇怪。确实奇怪,但是这种方法会十分有用。当一个函数以这种方式被声明时,编译器可以执行“内联”优化。这意味着当函数被调用时,整个函数代码在调用站点被内联编译而不是跳到一个新的代码块。
 
虽然内联可以使代码更快,但会增加编辑器代码的大小,因为如果函数被多次调用,代码将通过二进制复制。如果函数很大,或者被调用很多次,那么这可能会对二进制文件的大小产生重大的影响。由于很少的代码会在缓存中,这将会导致性能下降,这就意味着可能会有潜在的更多的缓存丢失。
 
我的目标是举例证明C++允许更多的灵活性。作为一个开发者,你需要去理解权衡并做决定。当然,唯一能真正明白哪种选择对你是正确的方法就是测试你的代码!
命名空间
上面的例子介绍了一些你之前没有遇到过的新的语法--双冒号::,即指在C++中如何指代范围。双冒号用来告诉编译器应该在哪里可以找到foo函数。
 
下一次你会在使用命名空间的时候遇到双冒号。命名空间是分离代码的一种方式,以便减少命名冲突。
 
例如,你可能会在代码中定义一个叫Person的类,但是一个第三方库也可能命名一个叫Person的类。因此,在写C++代码时,你通常会将你的代码放到一个命名空间中来避免这些类型的命名冲突。
 
很容易做这个,套用以下命名空间声明即可:
  
  
  1. namespace MyNamespace { 
  2.     class Person { … }; 
  3.   
  4. namespace LibraryNamespace { 
  5.     class Person { … }; 
 
现在,当使用任何一个Person类的实现时,你可以使用两个冒号消除歧义,如下:
  
  
  1. MyNamespace::Person pOne; 
  2. LibraryNamespace::Person pTwo; 
简单吧?
 
除了在类前加一个前缀来约定,在Objective-C中没有类似的命名空间。你确实这样命名类,对吧?如果不是这样命名的话,那就马上这样做吧!
 
 注意:在Objective-C中已经有很多命名空间的建议了。这样的方案可以在这里(链接)找到。我不知道在Objective-C中是否还能用到它们,但是我希望如此。
 
内存管理
哦,不……不是那个可怕的词吧!在任何语言中,内存管理都是需要理解的最重要的概念之一。Java基本上是用内存回收器来管理内存。Objective-C需要你明白引用计数以及ARC所扮演的角色。在C++中,嗯。。。C++又不同了。
 
首先,在C++中,要理解内存管理,你需要先了解堆和栈。即使你认为你知道这一点,我建议你继续往下阅读,或许你能略有收获。
 
是指用于运行应用程序的一个内存块。栈大小固定,并用于存储应用程序的代码的数据。栈基于puch/pop工作,当一个给定函数将数据压入栈中,当函数运行结束时,出栈的必须是等量的数据。因此,随着时间的推移,栈使用率不会增长。
 
同样也是运行应用程序的一个内存块。堆大小不固定,并且随着程序的运行而增长。应用程序倾向于使用堆来储存在函数范围外使用的数据。此外,大的数据单元通常会存储到堆中,因为存到栈中有可能会溢出。--记住,栈的大小是固定的。
 
以上是一个堆和栈原理的简述,以下为两者的C语言示例:
  
  
  1. int stackInt = 5; 
  2. int *heapInt = malloc(sizeof(int)); 
  3. *heapInt = 5; 
  4. free(heapInt); 
 
这里,stackInt使用栈空间。程序返回后,用来存储“5”的这块内存就会自动释放。
 
然而,heapInt使用堆空间,在堆上调用malloc分配足够的空间来存储一个整数(int)。但是由于堆必须是由你分配,在用完数据后,开发者需要调用一个free函数来确保你没有内存泄露。
 
在Objective-C中,你只能在堆上创建对象。如果你试着在栈上创建对象,那么编译器就会报错。根本行不通。
 
思考下面的例子:
  
  
  1. NSString stackString; 
  2. // Untitled 32.m:5:18: error: interface type cannot be statically allocated 
  3. //         NSString stackString; 
  4. //                  ^ 
  5. //                  * 
  6. // 1 error generated. 
 
这就是为什么在Objective-C代码上会看到星号,所有的对象都在堆上创建,并且所有对象都有指针。这在很大程度上归结为Objective-C处理内存管理。引用计数广泛应用于Objective-C中,对象需要在堆中以便它们的生命周期能被严格控制。
 
在C++中你既可以把数据存到栈中也可存到堆中。由开发者自己决定。然而,在C++中你也必须自己管理内存。数据放入栈中时内存将自动被处理;但用堆时,你必须自己管理内存,否则要面临内存泄露的风险。
 
C++中new和delete运算符
C++中引入一组关键词以帮助堆对象进行内存管理;他们分别用来创建和撤销堆中的对象。
 
创建对象:
  
  
  1. Person *person = new Person(); 
当你不用这个对象时,你就要撤销它:
  
  
  1. delete person; 
事实上,这同样适用于C++中标量类型:
  
  
  1. int *x = new int(); 
  2. *x = 5; 
  3. delete x; 
你可以认为这些运算相当于Objective-C中的初始化和删除对象。在C++中初始化用的new Person()等同于Objective-C中的[[Person alloc] init]。
 
但是,在Objective-C中没有等同于delete的运算符。但是我想你已经意识到了,当引用计数归零时,运行时Objective-C对象的存储单元就会被释放。记住,C++不会自动处理引用计数,开发者调用对象完成后负责释放对象。
 
现在你对C++的内存管理有了大致了解,简言之,在C++中的内存管理要比Objective-C中的要复杂得多。你真的需要考虑下一步是怎样,并且要跟踪对象。

访问栈和堆对象成员
你已经了解到,C++中既可以在栈上也可以在堆上创建对象。然而,这两种方法还有一点微妙但是很重要的区别,即访问成员变量和成员函数的方式稍有不同。
 
使用栈对象时,你需要点运算符(.);使用堆对象时,你需要使用箭头操作符(-->)。如下:
  
  
  1. Person stackPerson; 
  2. stackPerson.name = “Bob Smith”; ///< Setting a member variable 
  3. stackPerson.doSomething(); ///< Calling a member function 
  4.   
  5. Person *heapPerson = new Person(); 
  6. heapPerson->name = “Bob Smith”; ///< Setting a member variable 
  7. heapPerson->doSomething(); ///< Calling a member function 
区别很微妙,但是值得注意。
 
你还看到箭头操作符与this指针一起用,就像在Objective-C中的self指针一样,它用于类内部函数去访问当前的对象。
 
下面的C++例子展示了箭头操作符的用法:
  
  
  1. Person::doSomething() { 
  2.     this->doSomethingElse(); 
 
这会引起一个常见的C++陷阱。在Objective-C中,你可以用空指针调用一个方法,你的应用程序仍会运行的很好:
  
  
  1. myPerson = nil; 
  2. [myPerson doSomething]; // does nothing 
 
然而,在C++中,如果你要用一个NULL指针调用一个方法或者访问一个实例,你的应用程序会崩溃:
  
  
  1. myPerson = NULL; 
  2. myPerson->doSomething(); // crash! 
因此,你必须确保在C++中不要试图使用空指针。
 
引用
向函数传递对象时,你传递的是一个对象副本,而不是对象本身。例如,思考下面的C++代码:
  
  
  1. void changeValue(int x) { 
  2.     x = 5; 
  3.   
  4. // … 
  5.   
  6. int x = 1; 
  7. changeValue(x); 
  8. // x still equals 1 
 
很简单,没什么特别的。但是想一想当用一个函数做同样的事情,并且这个函数可以把一个对象作为一个参数。
  
  
  1. class Foo { 
  2.   public
  3.     int x; 
  4. }; 
  5.   
  6. void changeValue(Foo foo) { 
  7.     foo.x = 5; 
  8.   
  9. // … 
  10.   
  11. Foo foo; 
  12. foo.x = 1; 
  13. changeValue(foo); 
  14. // foo.x still equals 1 
 
这或许令你有些惊讶。仔细想想的话,和简单的int型例子没有不同。在将对象传递给函数之前,创建一个Foo object副本会发生什么情况?不过有时候确实需要传递一个实际对象。一种方法是改变函数指向对象的指针,而不是对象本身。但是无论什么时候调用函数都会产生附加代码。
 
对比上面列举的值传递的例子, C++定义了一个新的概念来允许通过引用来传递变量。这就意味着不需要创建对象副本。
 
利用引用传递可以很简单的改变你的调用,你可以在函数签名前简单地在变量前使用ampersand (&)即可,如下:
  
  
  1. void changeValue(Foo &foo) { 
  2.     foo.x = 5; 
  3.   
  4. // … 
  5.   
  6. Foo foo; 
  7. foo.x = 1; 
  8. changeValue(foo); 
  9. // foo.x equals 5 
 
它也适用于non-class变量:
  
  
  1. void changeValue(int &x) { 
  2.     x = 5; 
  3.   
  4. // … 
  5.   
  6. int x = 1; 
  7. changeValue(x); 
  8. // x equals 5 
 
引用传递很有用,并能显著提高性能。当创建一个对象副本成本相当高的时候引用传递更加有用,例如使用一个大型链表,创建副本意味着要对对象执行深度复制。

继承
一个面向对象的语言没有继承就不完整。C++当然不会违反这一趋势。思考下面的两个Objective-C类,其中一个类从另一个类继承:
  
  
  1. @interface Person : NSObject 
  2. @end 
  3.   
  4. @interface Employee : Person 
  5. @end 
 
同样的事情可以用C++以很相似的方式表达:
  
  
  1. class Person { 
  2. }; 
  3.   
  4. class Employee : public Person { 
  5. }; 
 
唯一的区别是在C++中要加一个public关键词。这里Employee类公共的继承Person类。这就意味着person类中的公共成员在Employee类中也是公共类型的。如果用private代替public,那么Person类中的公共成员在Employee类中就将变为私有的。关于这个话题的更多信息,我建议读一篇很棒的 关于继承和存储说明符的文章
 
以上是关于“继承“的简单部分,下面我们开始复杂的部分。与Objective-C不同的是,C++中允许多重继承,即一个类可以继承两个或以上基类。如果你除了Objective-C没有用过其他语言,那么这对你来说一定很陌生。
 
下面是C++中多重继承的例子:
  
  
  1. class Player { 
  2.     void play(); 
  3. }; 
  4.   
  5. class Manager { 
  6.     void manage(); 
  7. }; 
  8.   
  9. class PlayerManager : public Player, public Manager { 
  10. }; 
在这个例子中,有两个基类,一个类继承这两个基类。意思是PlayerManager类可以访问每个基类的所有成员变量和函数。简单吧?我确定你已经意识到了,在Objective-C中没有这种方法。
 
然而,这并不完全正确,对吧?
 
精明的读者一定注意到在Objective-C中有类似的方法,即protocols(协议)。虽然跟多重继承不太相似,但是两种技术都为了解决同样的问题:提供一个机制来连接两个有相似用途的类。
 
Protocols(协议)有一个微小的区别,那就是协议没有实现,只是描述类必须遵循哪个接口。
 
在Objective-C中,上面的例子可被写成:
  
  
  1. @protocol Player 
  2. - (void)play; 
  3. @end 
  4.   
  5. @protocol Manager 
  6. - (void)manage; 
  7. @end 
  8.   
  9. @interface Player : NSObject <Player> 
  10. @end 
  11.   
  12. @interface Manager : NSObject <Manager> 
  13. @end 
  14.   
  15. @interface PlayerManager : NSObject <Player, Manager> 
  16. @end 
当然,这个细小的差别你是能想象的到的。在Objective-C中你要在PlayerManager类中执行play和manager。在C++中你只要在每个基类中实现该方法,然后PlayerManager类会自动的继承每个实现。
 
虽然,在实践中,多重继承有时会另人混淆和复杂化。对C++开发者来说,多重继承是一个危险的方法,除非绝对必要,开发者会尽量避免使用。
 
为什么呢?想一想如果两个基类用同样的名字去实现一个函数,并接受同样的参数的话,那么这两个基类就会有同样的原型。在这种情况下,你就需要消除歧义。例如,假设Player和Manager两个类都有一个命名为foo的函数。
 
你需要这样消除歧义:
  
  
  1. PlayerManager p; 
  2. p.foo();          ///< Error! Which foo? 
  3. p.Player::foo();  ///< Call foo from Player 
  4. p.Manager::foo(); ///< Call foo from Manager 
这绝对是可行的,但是这增加了混淆,而且最好避免复杂性。这由PlayerManager类的使用者决定。使用协议直接使PlayerManager类实现函数foo,因此这里只有一次实现,没有混淆。
 
下一步
第一部分中我们了解了C++的简史,如何声明类以及C++中内存管理是如何工作的。当然,关于C++还有很多需要学习的!
 
第二部分中,在查阅Objective-C和C++标准库之前,学到了更多的高级类的特征和模板。
 
与此同时,如果你在学C++的过程中有任何问题或者观点,请加入下面的讨论!
 
你已经精通了Objective-C,并且一直想学更酷的东西?看看这篇文章吧!本文将向iOS开发者介绍C++。稍后我会介绍,Objective-C能够无缝地使用C和C++代码。因此,基于以下几点原因,iOS开发者理解C++将会很有帮助:
 
1.有时候你想在应用中使用一个用C++编写的库。
 
2.你可能用C++写一部分应用程序的代码,以便更容易跨平台移植。
 
3. 了解其他语言通常能帮助你更好地理解编程。
 
这篇文章针对那些已经理解Objective-C的iOS开发者。前提是假定你已明白怎么写Objective-C代码,并熟悉基本的C概念,比如类型、指针、函数等。
 
准备好学C++了么?那么就马上开始吧!
 
开始:语言简史
C++和Objective-C有一些共源:它们都根植于老式的好用的C语言,都是C语言的“超集”。因此,你可以在这两种语言中使用C语言的一些功能,和每种语言的附加特性。
 
如果你熟悉Objective-C,那么你将能粗略地理解你所遇到的C++代码。例如,两种语言中的数值类型(int型、float型和char型)的表现方式和使用规则都是完全一样的。
 
Objective-C和C++都在C语言基础上添加了面向对象的特征。如果你不熟悉“面向对象”,那么你真正需要明白的是面向对象指数据是由对象表示的,而对象是类的实例。事实上,C++最初称为“C with Classes”,内在的涵义是使C++面向对象。
 
“那么有什么区别么?”我听到了你的疑问。最大的区别是面向对象特性的方法。在C++中,很多行为是发生在编译时,而在Objective-C中,大多数是发生在运行时。你可能已经修改了Objective-C的运行时间来实现了一个类似 method swizzling的诡计,而在C++中这是不可能的。
 
C++也不像Objective-C一样有大量内省以及映射方法。在C++中,没有办法获得C++对象的类,而在Objective-C中你可以在一个实例中调用“类”方法。同样的,在C++中也没有相当于isMemberOfClass或者isKindOfClass的类。
 
以上对C++的粗略介绍显示了C++和Objective-C的历史和主要不同点。历史部分已经完成了,到我们继续学习一些C++特征的时间了。
 
 
C++ 类
在任何面向对象语言中,首先你要知道的是如何定义一个类。在Objective-C中,你通过创建一个头文件和一个执行文件来定义一个类,在C++中同样如此,语法也十分相似。
 
如下,是一个Objective-C类的例子:
 
 
  1. // MyClass.h 
  2.   
  3. #import <Foundation/Foundation.h> 
  4.   
  5. @interface MyClass : NSObject 
  6. @end 
  7.   
  8. // MyClass.m 
  9.   
  10. #import “MyClass.h” 
  11.   
  12. @implementation MyClass 
  13. @end 
 
作为一个经验丰富的iOS开发者你应该很明白,但是看看同样用C++写的例子:
 
 
  1. // MyClass.h 
  2.   
  3. class MyClass { 
  4. }; 
  5.   
  6. // MyClass.cpp 
  7.   
  8. #include “MyClass.h” 
  9.   
  10. /* Nothing else in here */ 
 
这里有一些本质的区别。首先,C++中的实现文件中什么都没有,这是因为你并没有在类中声明任何的方法。同理,就像Objective-C,一个空类不需要@implemenation/@end模块。
 
在Objective-C中,几乎每个类都继承自NSObject。你可以创建自己的根类,这意味着你的类将没有任何superclass。但是,你可能从来没有这么做过,除非你只是为了运行时好玩儿。对比C++,正如上面的例子一样,创建一个没有超类的类是很普遍的。
 
另外一个微小的区别是#include和#import。Objective-C将#import预处理器指令添加到C。在C++中没有相同的,标准的C-style是使用#include。Objective-C中的#import是确保一个文件只被包含一次,但在C++中你必须自己检查。
 
类成员变量和成员函数
当然,有比声明一个类多得多的事情。正如,在Objective-C和C++中,你可以在类中添加实例变量和方法。或许,你知道在C++中这两个不是这样命名的,C++中通常称为成员变量和成员函数。
 
 注意:“method(实体方法)”这个术语通常不用于C++中,这个特性只用在Objective-C中。在Objective-C中,通过消息分派带调用“method(实体方法)”。另外,function(函数)通过一个静态的C-style函数被调用。稍后在这篇文章中我将更多的解释静态和动态。
 
那么接下来你要如何声明成员变量和成员函数呢?如下:
 
 
  1. class MyClass { 
  2.     int x; 
  3.     int y; 
  4.     float z; 
  5.   
  6.     void foo(); 
  7.     void bar(); 
  8. }; 
 
这里有三个成员变量和两个成员函数。但是在C++中这里要有更多,在C++中,你可以限定成员变量和成员函数的范围,并且可以声明它们是公开访问的还是私有访问的。这个可以用于限制什么代码可以访问每个变量或者函数。
 
思考下面这个例子:
 
 
  1. class MyClass { 
  2.   public
  3.     int x; 
  4.     int y; 
  5.     void foo(); 
  6.   
  7.   private
  8.     float z; 
  9.     void bar(); 
 
这里,x,y和foo函数是公开访问。意思是可以在MyClass类的外部被调用。然而,z和bar函数是私有的。意味着只能在MyClass内部调用被调用。成员变量默认是私有的。
 
虽然这种区别确实存在于Objective-C中的实例变量中,但是很少使用。另外,在Objective-C中不太可能限制方法的调用范围。即使你只是在实现类内部声明一个方法而没有在接口中显示,技术上你还是可以外部调用这个方法。
 
Objective-C中的方法只约定为公开或私有。这就是为什么很多开发者选择给私有方法加前缀(例如“p_”前缀)来定义这个区别。这是为了和C++作比较,在C++中如果你试图从类的外部调用一个私有方法,编译器会抛出一个错误。
 
那么你要怎么使用类呢?和Objective-C非常相似,真的!你可以像下面这样创建一个实例:
 
 
  1. MyClass m; 
  2. m.x = 10; 
  3. m.y = 20; 
  4. m.foo(); 
 
简单吧!这里创建了一个MyClass的实例,分别设x=10,y=20,然后调用foo函数。
 
实现类的成员函数
你已经看到了如何定义一个类接口,但是函数呢?事实证明,这个十分简单。有如下两种方法你可以定义。
 
第一个实现函数的方法是在类的实现文件中定义--.cpp文件。例如:
 
 
  1. // MyClass.h 
  2. class MyClass { 
  3.     int x; 
  4.     int y; 
  5.     void foo(); 
  6. }; 
  7.   
  8. // MyClass.cpp 
  9. #include “MyClass.h” 
  10.   
  11. MyClass::foo() { 
  12.    // Do something 
 
以上是第一个方法。在Objective-C中定义十分简单。注意MyClass::的用法,这就是你如何表明foo()函数已经作为MyClass类的一部分被实现了。
 
第二个实现函数的方法是你在Objective-C中不能做到的。在C++中,你可以直接在头文件中定义一个函数,如下:
 
 
  1. // MyClass.h 
  2. class MyClass { 
  3.     int x; 
  4.     int y; 
  5.     void foo() { 
  6.         // Do something 
  7.     } 
  8. }; 
 
如果你只用过Objective-C,这看上去会很奇怪。确实奇怪,但是这种方法会十分有用。当一个函数以这种方式被声明时,编译器可以执行“内联”优化。这意味着当函数被调用时,整个函数代码在调用站点被内联编译而不是跳到一个新的代码块。
 
虽然内联可以使代码更快,但会增加编辑器代码的大小,因为如果函数被多次调用,代码将通过二进制复制。如果函数很大,或者被调用很多次,那么这可能会对二进制文件的大小产生重大的影响。由于很少的代码会在缓存中,这将会导致性能下降,这就意味着可能会有潜在的更多的缓存丢失。
 
我的目标是举例证明C++允许更多的灵活性。作为一个开发者,你需要去理解权衡并做决定。当然,唯一能真正明白哪种选择对你是正确的方法就是测试你的代码!
命名空间
上面的例子介绍了一些你之前没有遇到过的新的语法--双冒号::,即指在C++中如何指代范围。双冒号用来告诉编译器应该在哪里可以找到foo函数。
 
下一次你会在使用命名空间的时候遇到双冒号。命名空间是分离代码的一种方式,以便减少命名冲突。
 
例如,你可能会在代码中定义一个叫Person的类,但是一个第三方库也可能命名一个叫Person的类。因此,在写C++代码时,你通常会将你的代码放到一个命名空间中来避免这些类型的命名冲突。
 
很容易做这个,套用以下命名空间声明即可:
 
 
  1. namespace MyNamespace { 
  2.     class Person { … }; 
  3.   
  4. namespace LibraryNamespace { 
  5.     class Person { … }; 
 
现在,当使用任何一个Person类的实现时,你可以使用两个冒号消除歧义,如下:
 
 
  1. MyNamespace::Person pOne; 
  2. LibraryNamespace::Person pTwo; 
简单吧?
 
除了在类前加一个前缀来约定,在Objective-C中没有类似的命名空间。你确实这样命名类,对吧?如果不是这样命名的话,那就马上这样做吧!
 
 注意:在Objective-C中已经有很多命名空间的建议了。这样的方案可以在这里(链接)找到。我不知道在Objective-C中是否还能用到它们,但是我希望如此。
 
内存管理
哦,不……不是那个可怕的词吧!在任何语言中,内存管理都是需要理解的最重要的概念之一。Java基本上是用内存回收器来管理内存。Objective-C需要你明白引用计数以及ARC所扮演的角色。在C++中,嗯。。。C++又不同了。
 
首先,在C++中,要理解内存管理,你需要先了解堆和栈。即使你认为你知道这一点,我建议你继续往下阅读,或许你能略有收获。
 
是指用于运行应用程序的一个内存块。栈大小固定,并用于存储应用程序的代码的数据。栈基于puch/pop工作,当一个给定函数将数据压入栈中,当函数运行结束时,出栈的必须是等量的数据。因此,随着时间的推移,栈使用率不会增长。
 
同样也是运行应用程序的一个内存块。堆大小不固定,并且随着程序的运行而增长。应用程序倾向于使用堆来储存在函数范围外使用的数据。此外,大的数据单元通常会存储到堆中,因为存到栈中有可能会溢出。--记住,栈的大小是固定的。
 
以上是一个堆和栈原理的简述,以下为两者的C语言示例:
 
 
  1. int stackInt = 5; 
  2. int *heapInt = malloc(sizeof(int)); 
  3. *heapInt = 5; 
  4. free(heapInt); 
 
这里,stackInt使用栈空间。程序返回后,用来存储“5”的这块内存就会自动释放。
 
然而,heapInt使用堆空间,在堆上调用malloc分配足够的空间来存储一个整数(int)。但是由于堆必须是由你分配,在用完数据后,开发者需要调用一个free函数来确保你没有内存泄露。
 
在Objective-C中,你只能在堆上创建对象。如果你试着在栈上创建对象,那么编译器就会报错。根本行不通。
 
思考下面的例子:
 
 
  1. NSString stackString; 
  2. // Untitled 32.m:5:18: error: interface type cannot be statically allocated 
  3. //         NSString stackString; 
  4. //                  ^ 
  5. //                  * 
  6. // 1 error generated. 
 
这就是为什么在Objective-C代码上会看到星号,所有的对象都在堆上创建,并且所有对象都有指针。这在很大程度上归结为Objective-C处理内存管理。引用计数广泛应用于Objective-C中,对象需要在堆中以便它们的生命周期能被严格控制。
 
在C++中你既可以把数据存到栈中也可存到堆中。由开发者自己决定。然而,在C++中你也必须自己管理内存。数据放入栈中时内存将自动被处理;但用堆时,你必须自己管理内存,否则要面临内存泄露的风险。
 
C++中new和delete运算符
C++中引入一组关键词以帮助堆对象进行内存管理;他们分别用来创建和撤销堆中的对象。
 
创建对象:
 
 
  1. Person *person = new Person(); 
当你不用这个对象时,你就要撤销它:
 
 
  1. delete person; 
事实上,这同样适用于C++中标量类型:
 
 
  1. int *x = new int(); 
  2. *x = 5; 
  3. delete x; 
你可以认为这些运算相当于Objective-C中的初始化和删除对象。在C++中初始化用的new Person()等同于Objective-C中的[[Person alloc] init]。
 
但是,在Objective-C中没有等同于delete的运算符。但是我想你已经意识到了,当引用计数归零时,运行时Objective-C对象的存储单元就会被释放。记住,C++不会自动处理引用计数,开发者调用对象完成后负责释放对象。
 
现在你对C++的内存管理有了大致了解,简言之,在C++中的内存管理要比Objective-C中的要复杂得多。你真的需要考虑下一步是怎样,并且要跟踪对象。

访问栈和堆对象成员
你已经了解到,C++中既可以在栈上也可以在堆上创建对象。然而,这两种方法还有一点微妙但是很重要的区别,即访问成员变量和成员函数的方式稍有不同。
 
使用栈对象时,你需要点运算符(.);使用堆对象时,你需要使用箭头操作符(-->)。如下:
 
 
  1. Person stackPerson; 
  2. stackPerson.name = “Bob Smith”; ///< Setting a member variable 
  3. stackPerson.doSomething(); ///< Calling a member function 
  4.   
  5. Person *heapPerson = new Person(); 
  6. heapPerson->name = “Bob Smith”; ///< Setting a member variable 
  7. heapPerson->doSomething(); ///< Calling a member function 
区别很微妙,但是值得注意。
 
你还看到箭头操作符与this指针一起用,就像在Objective-C中的self指针一样,它用于类内部函数去访问当前的对象。
 
下面的C++例子展示了箭头操作符的用法:
 
 
  1. Person::doSomething() { 
  2.     this->doSomethingElse(); 
 
这会引起一个常见的C++陷阱。在Objective-C中,你可以用空指针调用一个方法,你的应用程序仍会运行的很好:
 
 
  1. myPerson = nil; 
  2. [myPerson doSomething]; // does nothing 
 
然而,在C++中,如果你要用一个NULL指针调用一个方法或者访问一个实例,你的应用程序会崩溃:
 
 
  1. myPerson = NULL; 
  2. myPerson->doSomething(); // crash! 
因此,你必须确保在C++中不要试图使用空指针。
 
引用
向函数传递对象时,你传递的是一个对象副本,而不是对象本身。例如,思考下面的C++代码:
 
 
  1. void changeValue(int x) { 
  2.     x = 5; 
  3.   
  4. // … 
  5.   
  6. int x = 1; 
  7. changeValue(x); 
  8. // x still equals 1 
 
很简单,没什么特别的。但是想一想当用一个函数做同样的事情,并且这个函数可以把一个对象作为一个参数。
 
 
  1. class Foo { 
  2.   public
  3.     int x; 
  4. }; 
  5.   
  6. void changeValue(Foo foo) { 
  7.     foo.x = 5; 
  8.   
  9. // … 
  10.   
  11. Foo foo; 
  12. foo.x = 1; 
  13. changeValue(foo); 
  14. // foo.x still equals 1 
 
这或许令你有些惊讶。仔细想想的话,和简单的int型例子没有不同。在将对象传递给函数之前,创建一个Foo object副本会发生什么情况?不过有时候确实需要传递一个实际对象。一种方法是改变函数指向对象的指针,而不是对象本身。但是无论什么时候调用函数都会产生附加代码。
 
对比上面列举的值传递的例子, C++定义了一个新的概念来允许通过引用来传递变量。这就意味着不需要创建对象副本。
 
利用引用传递可以很简单的改变你的调用,你可以在函数签名前简单地在变量前使用ampersand (&)即可,如下:
 
 
  1. void changeValue(Foo &foo) { 
  2.     foo.x = 5; 
  3.   
  4. // … 
  5.   
  6. Foo foo; 
  7. foo.x = 1; 
  8. changeValue(foo); 
  9. // foo.x equals 5 
 
它也适用于non-class变量:
 
 
  1. void changeValue(int &x) { 
  2.     x = 5; 
  3.   
  4. // … 
  5.   
  6. int x = 1; 
  7. changeValue(x); 
  8. // x equals 5 
 
引用传递很有用,并能显著提高性能。当创建一个对象副本成本相当高的时候引用传递更加有用,例如使用一个大型链表,创建副本意味着要对对象执行深度复制。

继承
一个面向对象的语言没有继承就不完整。C++当然不会违反这一趋势。思考下面的两个Objective-C类,其中一个类从另一个类继承:
 
 
  1. @interface Person : NSObject 
  2. @end 
  3.   
  4. @interface Employee : Person 
  5. @end 
 
同样的事情可以用C++以很相似的方式表达:
 
 
  1. class Person { 
  2. }; 
  3.   
  4. class Employee : public Person { 
  5. }; 
 
唯一的区别是在C++中要加一个public关键词。这里Employee类公共的继承Person类。这就意味着person类中的公共成员在Employee类中也是公共类型的。如果用private代替public,那么Person类中的公共成员在Employee类中就将变为私有的。关于这个话题的更多信息,我建议读一篇很棒的 关于继承和存储说明符的文章
 
以上是关于“继承“的简单部分,下面我们开始复杂的部分。与Objective-C不同的是,C++中允许多重继承,即一个类可以继承两个或以上基类。如果你除了Objective-C没有用过其他语言,那么这对你来说一定很陌生。
 
下面是C++中多重继承的例子:
 
 
  1. class Player { 
  2.     void play(); 
  3. }; 
  4.   
  5. class Manager { 
  6.     void manage(); 
  7. }; 
  8.   
  9. class PlayerManager : public Player, public Manager { 
  10. }; 
在这个例子中,有两个基类,一个类继承这两个基类。意思是PlayerManager类可以访问每个基类的所有成员变量和函数。简单吧?我确定你已经意识到了,在Objective-C中没有这种方法。
 
然而,这并不完全正确,对吧?
 
精明的读者一定注意到在Objective-C中有类似的方法,即protocols(协议)。虽然跟多重继承不太相似,但是两种技术都为了解决同样的问题:提供一个机制来连接两个有相似用途的类。
 
Protocols(协议)有一个微小的区别,那就是协议没有实现,只是描述类必须遵循哪个接口。
 
在Objective-C中,上面的例子可被写成:
 
 
  1. @protocol Player 
  2. - (void)play; 
  3. @end 
  4.   
  5. @protocol Manager 
  6. - (void)manage; 
  7. @end 
  8.   
  9. @interface Player : NSObject <Player> 
  10. @end 
  11.   
  12. @interface Manager : NSObject <Manager> 
  13. @end 
  14.   
  15. @interface PlayerManager : NSObject <Player, Manager> 
  16. @end 
当然,这个细小的差别你是能想象的到的。在Objective-C中你要在PlayerManager类中执行play和manager。在C++中你只要在每个基类中实现该方法,然后PlayerManager类会自动的继承每个实现。
 
虽然,在实践中,多重继承有时会另人混淆和复杂化。对C++开发者来说,多重继承是一个危险的方法,除非绝对必要,开发者会尽量避免使用。
 
为什么呢?想一想如果两个基类用同样的名字去实现一个函数,并接受同样的参数的话,那么这两个基类就会有同样的原型。在这种情况下,你就需要消除歧义。例如,假设Player和Manager两个类都有一个命名为foo的函数。
 
你需要这样消除歧义:
 
 
  1. PlayerManager p; 
  2. p.foo();          ///< Error! Which foo? 
  3. p.Player::foo();  ///< Call foo from Player 
  4. p.Manager::foo(); ///< Call foo from Manager 
这绝对是可行的,但是这增加了混淆,而且最好避免复杂性。这由PlayerManager类的使用者决定。使用协议直接使PlayerManager类实现函数foo,因此这里只有一次实现,没有混淆。
 
下一步
第一部分中我们了解了C++的简史,如何声明类以及C++中内存管理是如何工作的。当然,关于C++还有很多需要学习的!
 
第二部分中,在查阅Objective-C和C++标准库之前,学到了更多的高级类的特征和模板。
 
与此同时,如果你在学C++的过程中有任何问题或者观点,请加入下面的讨论!
 
欢迎回到向iOS开发者介绍C++系列的第二部分 向iOS开发者介绍C++(一)  !在第一部分,我们了解了类和内存管理。在第二部分部分我们将深入了解类以及其他有意思的特征。你将会了解到什么是“模板”以及标准模板库。
 
多态性
简单地说,多态性是一个重载子类中函数的概念。在Objective-C中,你可能已经做过很多次,例如,子类化UIViewController和重载viewDidLoad。
C++的多态性比Objective-C的多态性更进一层。因此,当我解释这个强大的功能时要紧跟我的思路。
 
首先,以下为在类中重载成员函数的例子:
 
 
  1. class Foo { 
  2.   public
  3.     int value() { return 5; } 
  4. }; 
  5.   
  6. class Bar : public Foo { 
  7.   public
  8.     int value() { return 10; } 
  9. }; 
 
但是,如果你这样做会发生什么呢:
 
 
  1. Bar *b = new Bar(); 
  2. Foo *f = (Foo*)b; 
  3. printf(“%i”, f->value()); 
  4. // Output = 5 
哇,这可不是你所期望的输出!我猜你认为输出值应该是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”添加到谈及的函数。例如上面的例子用虚函数方式写的话,如下:
 
 
  1. class Foo { 
  2.   public
  3.     virtual int value() { return 5; } 
  4. }; 
  5.   
  6. class Bar : public Foo { 
  7.   public
  8.     virtual int value() { return 10; } 
  9. }; 
 
现在想一想运行同样的代码会发生什么:
 
 
  1. Bar *b = new Bar(); 
  2. Foo *f = (Foo*)b; 
  3. printf(“%i”, f->value()); 
  4. // Output = 10 
 
这正是前面所预期的输出值,对吧?因此在C++中可以用动态绑定,但是你需要根据遇到的情况决定是用静态绑定还是动态绑定。
 
在C++中这种类型的灵活性是司空见惯的,这使C++成为一种多范型的语言。Objective-C很大程度上迫使你进入严格的模式,尤其是用Cocoa框架时。而C++中,很多都是由开发者决定的。
 
现在开始了解虚拟函数是如何发挥作用的吧!
虚函数的内部功能
在你明白虚函数是怎样工作之前,你需要知道非虚函数是如何工作的。
 
想一想下面的代码:
 
 
  1. MyClass a; 
  2. a.foo(); 
 
如果foo()是个非虚函数,那么编译器将会把它转换成代码,直接跳到MyClass类的foo()函数。
 
但是记住,这就是非虚函数的问题所在。回想之前的例子,如果这个类是多态的,那么编译器由于不知道变量的全部类型,也就不知道应该跳到哪个函数。这就需要一种方法在运行时查找到正确的函数。

要完成这种查找,虚函数要使用“virtual table”(也称“v-table”,虚表)。虚表是一个查找表来将函数映射到其实现上,并且每个类都访问一个表。当一个虚函数被调用时,编译器发出代码来检索对象的虚表从而查找到正确的函数。
 
回顾上面的例子来看看这是如何工作的:
 
 
  1. class Foo { 
  2.   public
  3.     virtual int value() { return 5; } 
  4. }; 
  5.   
  6. class Bar : public Foo { 
  7.   public
  8.     virtual int value() { return 10; } 
  9. }; 
  10.   
  11. Bar *b = new Bar(); 
  12. Foo *f = (Foo*)b; 
  13. printf(“%i”, f->value()); 
  14. // Output = 10 
 
当你创建一个类指针b和一个Bar类的实例,那么它的虚表将是Bar类的虚表。当b指针转换为Foo类的一个指针时,它并没有改变对象的内容,虚表仍然是Bar类的虚表而不是Foo类的。因此当查找v-table以调用value()时,结果是将调用Bar::value()。
 
构造函数和析构函数
每个对象在其生命周期中都要经历两个重要阶段:构造函数和析构函数。C++允许你同时控制这两个阶段。在Objective-C中与这两阶段相同的是初始化方法(例如,init或者以init开头的其他方法)和dealloc(释放内存)。
 
C++中定义构造函数时与类同名。正如在Objective-C中有多个初始化方法,你也可以定义多个构造函数。
 
例如,下面这个类中有两个不同的构造函数:
 
 
  1. class Foo { 
  2.   private
  3.     int x; 
  4.   
  5.   public
  6.     Foo() { 
  7.         x = 0; 
  8.     } 
  9.   
  10.     Foo(int x) { 
  11.         this->x = x; 
  12.     } 
  13. }; 
 
这就是两个构造函数,一个是默认构造函数Foo(),另一个构造函数含有一个参数来设置成员变量。
 
如上例中,如果在构造函数中给成员变量初始化,有用少量代码实现的方法。不需要自己去设置成员变量的值,你可以用下面的语法:
 
 
  1. class Foo { 
  2.   private
  3.     int x; 
  4.   
  5.   public
  6.     Foo() : x(0) { 
  7.     } 
  8.   
  9.     Foo(int x) : x(x) { 
  10.     } 
  11. }; 
通常来讲,如果仅仅是给成员变量赋值的话可以用上面这种方式。但是如果你需要用到逻辑或者调用其他函数的话,那么你就要实现函数主体。你也可以结合以上两种方式。
 
当用继承时,你需要调用父类的构造函数。在Objective-C中,你通常采用先调用父类指定的初始化程序的方法。
 
在C++中,你可以这样做:
 
 
  1. class Foo { 
  2.   private
  3.     int x; 
  4.   
  5.   public
  6.     Foo() : x(0) { 
  7.     } 
  8.   
  9.     Foo(int x) : x(x) { 
  10.     } 
  11. }; 
  12.   
  13. class Bar : public Foo { 
  14.   private
  15.     int y; 
  16.   
  17.   public
  18.     Bar() : Foo(), y(0) { 
  19.     } 
  20.   
  21.     Bar(int x) : Foo(x), y(0) { 
  22.     } 
  23.   
  24.     Bar(int x, int y) : Foo(x), y(y) { 
  25.     } 
  26. }; 
函数签名后,列表中的第一个元素表示对父类构造函数的调用。你可以调用任何一个你想要的超类构造函数。
 
C++没有一个指定的初始化程序。目前,没有办法调用同一个类的构造函数。在Objective-C中,有一个指定的初始化程序可以被其他初始化程序调用,并且只有这个指定的初始化程序去调用超类的指定初始化程序,例如:
 
 
  1. @interface Foo : NSObject 
  2. @end 
  3.   
  4. @implementation Foo 
  5.   
  6. - (id)init { 
  7.     if (self = [super init]) { ///< Call to super’s designated initialiser 
  8.     } 
  9.     return self; 
  10.   
  11. - (id)initWithFoo:(id)foo { 
  12.     if (self = [self init]) { ///< Call to self’s designated initialiser 
  13.         // … 
  14.     } 
  15.     return self; 
  16.   
  17. - (id)initWithBar:(id)bar { 
  18.     if (self = [self init]) { ///< Call to self’s designated initialiser 
  19.         // … 
  20.     } 
  21.     return self; 
  22.   
  23. @end 
在C++中,虽然你可以调用父类的构造函数,但是目前调用自己的构造函数仍是不合法的。因此,下面的解决方案很常见:
 
 
  1. class Bar : public Foo { 
  2.   private
  3.     int y; 
  4.     void commonInit() { 
  5.         // Perform common initialisation 
  6.     } 
  7.   
  8.   public
  9.     Bar() : Foo() { 
  10.         this->commonInit(); 
  11.     } 
  12.   
  13.     Bar(int y) : Foo(), y(y) { 
  14.         this->commonInit(); 
  15.     } 
  16. }; 
然而,这十分麻烦。为什么你不能用Bar(int y)调用Bar(),然后在Bar()中这样写“Bar::commonInit()”呢?毕竟,Objective-C中恰恰是这样写的。
 
2011年发布了最新版的C++标准:C++11。在这个更新的标准中确实可以这样做。目前仍有许多C++代码还没有按C++11标准来更新,所以知道这两种方法很重要。任何2011年前标准的C++代码都按以下这种方式:
 
 
  1. class Bar : public Foo { 
  2.   private
  3.     int y; 
  4.   
  5.   public
  6.     Bar() : Foo() { 
  7.         // Perform common initialisation 
  8.     } 
  9.   
  10.     Bar(int y) : Bar() { 
  11.         this->y = y; 
  12.     } 
  13. }; 
这种方法唯一一个不足的地方是,你不能在同一个类中调用构造函数的同时设置一个成员变量。上面的例子中,成员变量y在构造函数主体中设置。
 
注意:在2011年C++11标准成为一个完整的标准,起初称为C++ 0x。意思是在2000年至2009年之间这项标准成熟的话,x可以替换为这一年的最后一个数字。然而比预期的时间要晚,因此以11为结尾!所有的现代编译器,包括clang,现在都支持C++11标准。
 
以上为构造函数,那么析构函数呢?当一个堆对象被删除或者一个栈函数溢出时会调用析构函数。在析构函数中你需要做的事情就是清理对象。
 
析构函数中不能有任何参数。同样,在Objective-C中dealloc也不需要任何参数。因此每个类中只有一个析构函数。
 
在类中定义析构函数时在函数名字前要加前缀--波浪号(~)。如下:
 
 
  1. class Foo { 
  2.   public
  3.     ~Foo() { 
  4.         printf(“Foo destructor\n”); 
  5.     } 
  6. }; 
 
看一下当你的类被继承时,会发生什么:
 
 
  1. class Bar : public Foo { 
  2.   public
  3.     ~Bar() { 
  4.         printf(“Bar destructor\n”); 
  5.     } 
  6. }; 
 
如果你不这样写的话,当通过Foo指针删除Bar类的一个实例的时候将会发生异常,如下:
 
 
  1. Bar *b = new Bar(); 
  2. Foo *f = (Foo*)b; 
  3. delete f; 
  4. // Output: 
  5. // Foo destructor 
 
这样是错误的。删除的应该是Bar类的实例,但是为什么是去调用Foo类的析构函数呢?
 
回想一下,之前发生的同样的问题,你是使用虚函数解决的。这个正是同样的问题。编译器看到是一个Foo需要被删除,因为Foo的析构函数并不是虚函数,所以编译器认为要调用的是Foo的析构函数。
 
解决这个问题的办法就是将析构函数定义为虚函数,如下:
 
 
  1. class Foo { 
  2.   public
  3.     virtual ~Foo() { 
  4.         printf(“Foo destructor\n”); 
  5.     } 
  6. }; 
  7.   
  8. class Bar : public Foo { 
  9.   public
  10.     virtual ~Bar() { 
  11.         printf(“Bar destructor\n”); 
  12.     } 
  13. }; 
  14.   
  15. Bar *b = new Bar(); 
  16. Foo *f = (Foo*)b; 
  17. delete f; 
  18. // Output: 
  19. // Bar destructor 
  20. // Foo destructor 
 
这就接近了期望的结果,但最终结果不同于之前使用虚函数得到的结果。在这里,两个函数都被调用了。首先Bar的析构函数被调用,然后Foo的析构函数被调用。为什么呢?
 
这是因为析构函数比较特殊。由于Foo的析构函数是父类的析构函数,所以Bar的析构函数自动调用Foo的析构函数。
 
这正是所需要的,正如Objective-c中的ARC方法中,你调用的是父类的dealloc。
你可能在想这个:你认为编译器会为你做这个事情,但是并不是在所有类中都是最佳方法。
 
例如,如果你没有从某个类继承呢?如果析构函数是虚函数,那么每次都要通过虚表来删除一个实例,或许这种间接方法并不是你需要的。C++中你可以自己做决定,另一个方法很强大,但是开发者必须清楚发生了什么。
 
 注意:除非你确定你不需要继承一个类,否则一定要定义析构函数为虚函数。
 
运算符重载
在Objective-C中没有运算符重载的概念,但是这并不复杂。
 
操作符是实体,如我们熟悉的+,-,*,/。例如,你可以用“+”运算符与标准常量(操作数)做如下运算:
 
 
  1. int x = 5; 
  2. int y = x + 5; ///< y = 10 
 
运算符“+”在这里的作用显而易见,将x加上5然后返回一个值。或许这个还不够明显,如果以函数的形式就很清楚了:
 
 
  1. int x = 5; 
  2. int y = add(x, 5); 
 
在函数add()中,两个参数相加并返回一个值。
 
在C++中,在类中使用操作符时是可以定义功能函数的。这一功能很强大。当然,这也不是总能行得通的。例如,将两个Person类相加就无任何实际意义。
 
然而,这一特性很有用处。考虑下面的类:
 
 
  1. class DoubleInt { 
  2.   private
  3.     int x; 
  4.     int y; 
  5.   
  6.   public
  7.     DoubleInt(int x, int y) : x(x), y(y) {} 
  8. }; 
 
这样做可能更好一些:
 
 
  1. DoubleInt a(1, 2); 
  2. DoubleInt b(3, 4); 
  3. DoubleInt c = a + b; 
 
我们想要将DoubleInt(4, 6)的值赋值给变量c,即将两个DoubleInt的实例x和y相加,然后赋值给c。事实证明这很简单。你需要做的就是给DoubleInt类添加一个方法,即:
 
 
  1. DoubleInt operator+(const DoubleInt &rhs) { 
  2.     return DoubleInt(x + rhs.x, y + rhs.y); 
 
函数operator+很特别。编译器将使用这个函数,当它看到“+”运算符任一侧的DoubleInt时。“+”运算符左边的对象将调用这个函数,将“+”运算符右边的对象作为参数进行传递。因此,经常命名参数为“rhs”,意思是“右边”。
 
由于使用实参的副本不仅没必要还可能会改变值,函数的参数将作为引用,可能会创建一个新的对象。此外,这个对象将是常量,这是因为在相加的过程中,对于“+”运算符的右边来讲这是非法的。
 
C++能做的不仅是这些。你可能不仅仅想把DoubleInt添加至DoubleInt。你可能想要给DoubleInt添加一个整数。这些都是可能实现的!
 
为实现此操作,你需要实现如下成员函数:
 
 
  1. DoubleInt operator+(const int &rhs) { 
  2.     return DoubleInt(x + rhs, y + rhs); 
 
然后你可以这样做:
 
 
  1. DoubleInt a(1, 2); 
  2. DoubleInt b = a + 10; 
  3. // b = DoubleInt(11, 12); 
 
很有用吧!
 
除了加法运算,其他运算也可以这样做。你可以重载++, --, +=, -=, *, ->等等。这里就不一一列举了。如果想要对运算符重载做更多了解,你可以访问learncpp.com,这里有整个章节在介绍运算符重载。
 
模板
在C++中,模板很有意思。
 
你是否发现你经常会重复写相同的函数或者类,但只是函数或者类的类型不同呢?例如,交换两个值的函数:
 
 
  1. void swap(int &a, int &b) { 
  2.     int temp = a; 
  3.     a = b; 
  4.     b = temp; 
 
 注:这里是对参数做引用传递,以确保是对函数的实参作交换。如果两个参数是用值传递,那么所交换的值只是实参的副本。这个例子很好的说明了C++中引用好处。
 
上面的例子只适用于整数类型。如果是浮点数类型,那么你需要写另一个函数:
 
 
  1. void swap(float &a, float &b) { 
  2.     float temp = a; 
  3.     a = b; 
  4.     b = temp; 
 
如果你重复写函数的主体,这样很不明智。C++介绍一种语法可以有效的忽略变量的类型。你可以通过模板这个特性来实现这一功能。取代上面的两种方法,在C++中,你可以这样写:
 
 
  1. template <typename T> 
  2. void swap(T a, T b) { 
  3.     T temp = a; 
  4.     a = b; 
  5.     b = temp; 
 
因此,你的函数可以交换任何类型的参数。你可以用以下任一种方式来调用函数:
 
 
  1. int ix = 1, iy = 2; 
  2. swap(ix, iy); 
  3.   
  4. float fx = 3.141, iy = 2.901; 
  5. swap(fx, fy); 
  6.   
  7. Person px(“Matt Galloway”), py(“Ray Wenderlich”); 
  8. swap(px, py); 
 
但是,你在用模板的时候仍需仔细。只有在头文件中实现模板函数,这种方法才能起作用。这是由模板的编译方式决定的。使用模板函数时,如果函数类型不存在,编译器会根据类型实例化一个函数模板。
 
考虑到编译器需要知道模板函数的实现,你需要在头文件中定义一个实现,并且在使用的时候必须要包含这个头文件。
 
同理,如果要修改模板函数中的实现,所有用到这个函数的文件都需要重编译。相比之下,如果在实现文件中修改函数或者实现类成员函数,那么只有这个实现文件需要重编译。
 
因此,过度地使用模板会使应用程序很繁琐。但是正如C++中很多方法,模板的作用很大。
 
模板类
不仅仅有模板函数,还可以在整个类中使用模板。
 
假设你的类中有三个值,这三个值用来存储一些数据。首先,你想用整数类型,因此你要这样写:
 
 
  1. class IntTriplet { 
  2.   private
  3.     int a, b, c; 
  4.   
  5.   public
  6.     IntTriplet(int a, int b, int c) : a(a), b(b), c(c) {} 
  7.   
  8.     int getA() { return a; } 
  9.     int getB() { return b; } 
  10.     int getC() { return c; } 
  11. }; 
 
但是,你继续写程序时发现你需要三个浮点型数据。这是你又要写一个类,如下:
 
 
  1. class FloatTriplet { 
  2.   private
  3.     float a, b, c; 
  4.   
  5.   public
  6.     FloatTriplet(float a, float b, float c) : a(a), b(b), c(c) {} 
  7.   
  8.     float getA() { return a; } 
  9.     float getB() { return b; } 
  10.     float getC() { return c; } 
  11. }; 
 
这里,模板就会很有用。与模板函数相同,可以在类中使用模板。语法是一样的。上面的两个类可以写成这样:
 
 
  1. template <typename T> 
  2. class Triplet { 
  3.   private
  4.     T a, b, c; 
  5.   
  6.   public
  7.     Triplet(T a, T b, T c) : a(a), b(b), c(c) {} 
  8.   
  9.     T getA() { return a; } 
  10.     T getB() { return b; } 
  11.     T getC() { return c; } 
  12. }; 
但是,用模板类需要做一些细微的改动。使用模板函数不会改变代码,这是因为参数类型允许模板推断需要做什么。然而,使用模板类时,你要告诉编译器你需要模板类使用什么类型。
 
幸运的是,这个很简单。用上面的模板类也很简单:
 
 
  1. Triplet<int> intTriplet(1, 2, 3); 
  2. Triplet<float> floatTriplet(3.141, 2.901, 10.5); 
  3. Triplet<Person> personTriplet(Person(“Matt”), Person(“Ray”), Person(“Bob”)); 
很强大,对吧?
此外,模板函数和模板类并不局限于单个未知类型。三重态的类可以被扩展以支持任何三种类型,而不是每个值必须是同样的类型。
 
要做到这一点,只需要扩展提供更多类型的模板定义,如下:
 
 
  1. template <typename TA, typename TB, typename TC> 
  2. class Triplet { 
  3.   private
  4.     TA a; 
  5.     TB b; 
  6.     TC c; 
  7.   
  8.   public
  9.     Triplet(TA a, TB b, TC c) : a(a), b(b), c(c) {} 
  10.   
  11.     TA getA() { return a; } 
  12.     TB getB() { return b; } 
  13.     TC getC() { return c; } 
  14. }; 
 
以上模板中有三个不同类型,每个类型都在代码中的适当位置被使用。
 
使用这样的模板也很简单,如下所示:
 
 
  1. Triplet<intfloat, Person> mixedTriplet(1, 3.141, Person(“Matt”)); 
以上为模板的间接。接下来看看大量使用其特性的一个库--标准模板库
 
标准模板库(STL)
每个规范的编程语言都有一个标准库,这个标准库包含通用的数据结构、算法以及函数。在Objective-C中你有Foundation。其中,包含NSArray、NSDictionary等熟悉或者不熟悉的成员函数。在C++中,标准模板库(简称STL)包含这些标准代码。
 
之所以成为标准模板库,是因为在这个库中使用了大量的模板。
 
STL中有很多内容,要介绍所有需要很长时间,所以在这里我只介绍一些重要的。
 
容器
数组、字典和集合都是对象的容器。在Objective-C中,Foundation框架包含了大部分常用容器的实现。在C++中,STL包含了这些实现。实际上,STL所包含的的容器要比Foundation多一些。
 
在STL中有两点与NSArray不同。分别是vector(列表)和list(列表)。两个都可以存储对象的序列,但是每个容器都有自己的优点和缺点。在C++中,从所给的容器中选择你需要的很重要。
 
首先,看一看vector的用法:
 
 
  1. #include <vector> 
  2.   
  3. std::vector<int> v; 
  4. v.push_back(1); 
  5. v.push_back(2); 
  6. v.push_back(3); 
  7. v.push_back(4); 
  8. v.push_back(5); 
 
 
 注意std::的用法,这是因为大部分STL位于命名空间内。STL将其所有的类放在自己的名为"std"的命名空间中以避免潜在的命名冲突。
 
上面的代码中,首先你创建一个vector来存放整型数据(int),然后五个整数被依次压入vector的栈顶。操作完成后,vector中将是从1到5的有序序列。
 
这里需要注意的是,正如Objective-C中,所有的容器都是可变的,没有可变或者不可变的变量。
 
访问一个vector的元素是这样完成的:
 
 
  1. int first = v[1]; 
  2. int outOfBounds = v.at(100); 
 
这两种方法都能有效地访问vector中的元素。第一种使用方括号的方法,这便是索引C语言数组的方法。Objective-C中的下标取值方法也是用这种方法索引NSArray。
 
上面例子中的第二行使用at()成员函数,和方括号功能相同,只是at()函数需要检查是否在vector范围内索引,超出范围的话会抛出异常。
 
vector被实现为一个单一的或连续的内存块。vector的空间大小等于所存储的对象的大小乘以vector中对象数(存储4字节或者8字节的整数取决于你使用的体系结构是32位还是64位的)。
 
向vector中添加元素是很昂贵的,因为一个新的内存块需要被分配给这个新的vector。然而,访问一个确定的索引很快,因为这仅仅是访问内存中的一个字
 
std::list与std::vector很相似。但是,list的实现方式稍稍有些不同。不是作为一个连续的内存块被实现而是作为一个双向链表被实现。这意味着,list中每个的元素都包含一个数据,一个指向前一个元素的指针和一个指向后一个元素的指针。
 
由于是双向链表,插入和删除操作很简单。然而,如果要访问list中的第n个元素,就需要从0到n去遍历。
 
综上,list和vector的用法很相似:
 
 
  1. #include <list> 
  2.   
  3. std::list<int> l; 
  4. l.push_back(1); 
  5. l.push_back(2); 
  6. l.push_back(3); 
  7. l.push_back(4); 
  8. l.push_back(5); 
 
正如上面的vector例子,这将创建一个从1到5的有序序列。但是,在list中你不能使用方括号或者at()成员函数去访问一个指定元素。你需要用一个迭代器(iterators)去遍历list。
 
你可以这样遍历list中的每个元素:
 
 
  1. std::list<int>::iterator i; 
  2. for (i = l.begin(); i != l.end(); i++) { 
  3.     int thisInt = *i; 
  4.     // Do something with thisInt 
 
大多数容器类有迭代器(iterator)的概念。迭代器是一个对象,可以遍历并指向一个特定的元素。你可以通过增量或减量来控制迭代前移或者后移。
 
用迭代器在当前位置获得元素的值与使用解引用运算符(*)一样简单。
 
 注:在上面的代码中,有两个运算符重载的实例。i++是迭代器重载增量运算符(++),*i是重载解引用操作符(*)。STL中大量使用了这样的运算符重载。
 
除了vector(向量)和list(列表),C++中还有很多容器。都有不同的特征。例如Objective-C中的集合,C++中为std::set;Objective-C中的字典,C++中为std::map。C++中,另一个常用的容器是std::pair,其中只存储两个值。
 
Shared Pointer
回想一下内存管理,当在C++中使用堆对象是,你需要自己处理内存。没有引用计数。在C++中确实是这样。但是在C++ 11标准中,STL中添加了一个新类,这个类中添加了引用计数,称之为shared_ptr,意思是“shared pointer”。
 
Shared Pointer是一个对象,这个对象定义一个指针以便在underlying pointer中实现引用计数。这与在Objective-C中在ARC下使用指针是相同的。例如,以下例子展示了如何用智能指针来定义一个指针去指向一个整数:
 
 
  1. std::shared_ptr<int> p1(new int(1)); 
  2. std::shared_ptr<int> p2 = p1; 
  3. std::shared_ptr<int> p3 = p1; 
 
运行这三行代码后,每个shared pointer的引用计数为3。当每个shared pointer被删除或者被释放后,引用指数减少。直到最后一个包含underlying pointer的shared pointer被删除后,底层指针被删除。
 
由于shared pointer本身就是栈对象,溢出时就会被删除。因此,shared pointer与Objective-C中的自动引用计数(ARC)下的对象指针的约束规则相同。
 
下面的例子为shared pointer创建和删除的全过程:
 
 
 
  1. std::shared_ptr<int> p1(new int(1)); ///< Use count = 1 
  2.   
  3. if (doSomething) { 
  4.     std::shared_ptr<int> p2 = p1; ///< Use count = 2; 
  5.     // Do something with p2 
  6.   
  7. // p2 has gone out of scope and destroyed, so use count = 1 
  8.   
  9. p1.reset(); 
  10.   
  11. // p1 reset, so use count = 0 
  12. // The underlying int* is deleted 
 
把p1分配给p2是将p1的副本分配给p2。记住当一个函数参数是按值传递的话,是将参数的副本传给了函数。这一点是很有用处的,因为如果你将一个shared pointer传给一个函数,传递给这个函数的是一个新的shared pointer。当然,在函数结束时就会发生越界,从而被删除。所以在函数周期中,underlying pointer的使用数量将会增加。这与在Objective-C中的自动引用计数(ARC)下的引用计数功能相同。
 
当然,你需要能够获得或者使用underlying pointer,有两种方式可以实现这一操作。重载解引用操作符(*)和箭头操作符(->)以使shared pointer的工作方式本质上与一个正常的指针相同。如下:
 
 
  1. std::shared_ptr<Person> p1(new Person(“Matt Galloway”)); 
  2.   
  3. Person *underlyingPointer = *p1; ///< Grab the underlying pointer 
  4.   
  5. p1->doADance(); ///< Make Matt dance 
 
Shared Pointer很好地给C++引入了引用计数的技术。当然,shared pointer也添加了一些少量的开销,但是这个开销带来了很明显的好处,所以也是值得的。
 
Objective-C++
C++很好,但是跟Objective-C有什么关系呢?
 
通过用Objective-C++可以将Objective-C和C++结合起来。它并不是一个全新的语言,而是Objective-C和C++两者的结合。
 
通过两者的结合,你可以使用两者的语言特征。可以将C++的对象作为Objective-C的实例,反之亦然。如果在应用程序中使用C++库的话这将会很有用处。
 
要使编译器理解一个Objective-C++文件是很容易的。你需要做的只是将文件名从.m改为.mm。当你这样做的时候,编译器会考虑到这个文件的不同,并将允许你使用Objective-C++。
 
以下为如何在两者间使用对象的例子:
 
 
  1. // Forward declare so that everything works below 
  2. @class ObjcClass; 
  3. class CppClass; 
  4.   
  5. // C++ class with an Objective-C member variable 
  6. class CppClass { 
  7.   public
  8.     ObjcClass *objcClass; 
  9. }; 
  10.   
  11. // Objective-C class with a C++ object as a property 
  12. @interface ObjcClass : NSObject 
  13. @property (nonatomic, assign) std::shared_ptr<CppClass> cppClass; 
  14. @end 
  15.   
  16. @implementation ObjcClass 
  17. @end 
  18.   
  19. // Using the two classes above 
  20. std::shared_ptr<CppClass> cppClass(new CppClass()); 
  21. ObjcClass *objcClass = [[ObjcClass alloc] init]; 
  22.   
  23. cppClass->objcClass = objcClass; 
  24. objcClass.cppClass = cppClass; 
 
简单吧!注意这个属性被定义为assign,然而你不能用strong或者weak,因为这些对非OBjective-C对象类型没有意义。编译器不能“保留”或者“释放”一个C++对象类型,因为它并不是一个Objective-C对象。
 
assign的内存管理仍然是正确的,因为你使用了shared pointer。你可以使用raw pointer,但是你需要自己写一个setter来删除原来的实例并根据情况设置一个新的值。
 
 注:Objective-C++是有局限性的。C++的类不能继承Objective-C的类,反之亦然。异常处理也是需要注意的地方。现代编译器和运行时确实允许C++异常和Objective-C异常共存,但是仍需要注意。使用异常处理之前一定要阅读相关文档。
 
Objective-C++很有用处,因为很多好的库都是用C++写的。能够在iOS和Mac的应用程序上使用它是很有价值的。
 
需要注意的是,Objective-C++确实有它需要注意的地方。第一个需要注意的地方是内存管理。记住Objective-C的对象都是建立在堆上的,而C++的对象可以建立在栈上也可以是在堆上。如果Objective-C类的对象是建立在栈上的话会很奇怪。必须是在堆上,因为整个Objective-C对象都是建立在堆上的。
 
编译器通过自动在代码中添加alloc和dealloc来构造和析构C++栈对象以确保这种情况。在此过程中,编译器需要创建两个函数“.cxx_construct”和“.cxx_destruct”,这两个函数分别被alloc和delloc调用。在这写方法中,执行所有相关的C++处理是必要的。
 
 注:ARC实际上依托于“.cxx_destruct”,现在它为所有的Objective-C类创建了一个函数来写所有的自动消除代码。
 
这个处理所有基于栈的C++对象,但是你要记住任何基于堆的对象都需要在适当的情况下创建和销毁。你可以在指定的初始化程序中创建对象然后再dealloc中删除。
 
另一个在Objective-C++中需要注意的地方是减少对C++的依赖。这一点要尽量避免。要想明白这是为什么,看看下面这个使用Objective-C++的类。
 
 
  1. // MyClass.h 
  2. #import <Foundation/Foundation.h> 
  3. #include <list> 
  4.   
  5. @interface MyClass : NSObject 
  6.   
  7. @property (nonatomic, assign) std::list<int> listOfIntegers; 
  8.   
  9. @end 
  10.   
  11. // MyClass.mm 
  12. #import “MyClass.h” 
  13.   
  14. @implementation MyClass 
  15. // … 
  16. @end 
MyClass类的实现文件必须是.mm文件,因为它是使用C++编写的。这没有错,但是想一想如果你想要使用MyCLass类的话会发生什么呢。你需要import MyClass.h,但是这样做你引入了一个使用C++编写的文件。所以即使其他的文件不需要用C++编写,也需要使用Objective-C++来进行编译。
 
因此,最好是在公共头文件中减少使用C++。你可以使用在实现文件中声明的私有属性或者实体变量实现这一目的。
 
下一步
C++是一个伟大的语言。它与Objective-C有相似的根源,但是它选择一种很不同的方式去编写程序。总之,学习C++可以很好的理解面向对象程序。而且C++能帮助你在objective - c代码做出更好的设计决策。我鼓励你去学习更多的C++知识并自己写程序。你可以在learncpp.com中找到很多好的资源。如果你有任何评论或者疑问或者C++问题,请留言。
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值