![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZwpmLx8lN1UzM5AzNxQDM0EzX2kTM08CX3EDNwQTMvw1ZtlGbsF2LcNHZh9GbwV3LcNXbj9CXt92YuEmbph2Yh92YvNmL3d3dvw1LcpDc0RHaiojIsJye.jpg)
歡迎回到向iOS開發者介紹C++系列的第二部分 ( 向iOS開發者介紹C++(一) ) !在第一部分,我們了解了類和記憶體管理。在第二部分部分我們将深入了解類以及其他有意思的特征。你将會了解到什麼是“模闆”以及标準模闆庫。 多态性 簡單地說,多态性是一個重載子類中函數的概念。在Objective-C中,你可能已經做過很多次,例如,子類化UIViewController和重載viewDidLoad。
C++的多态性比Objective-C的多态性更進一層。是以,當我解釋這個強大的功能時要緊跟我的思路。 首先,以下為在類中重載成員函數的例子:
- class Foo {
- public:
- int value() { return 5; }
- };
- class Bar : public Foo {
- public:
- int value() { return 10; }
- };
但是,如果你這樣做會發生什麼呢:
- Bar *b = new Bar();
- Foo *f = (Foo*)b;
- printf(“%i”, f->value());
- // 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”添加到談及的函數。例如上面的例子用虛函數方式寫的話,如下:
- class Foo {
- public:
- virtual int value() { return 5; }
- };
- class Bar : public Foo {
- public:
- virtual int value() { return 10; }
- };
現在想一想運作同樣的代碼會發生什麼:
- Bar *b = new Bar();
- Foo *f = (Foo*)b;
- printf(“%i”, f->value());
- // Output = 10
這正是前面所預期的輸出值,對吧?是以在C++中可以用動态綁定,但是你需要根據遇到的情況決定是用靜态綁定還是動态綁定。 在C++中這種類型的靈活性是司空見慣的,這使C++成為一種多範型的語言。Objective-C很大程度上迫使你進入嚴格的模式,尤其是用Cocoa架構時。而C++中,很多都是由開發者決定的。 現在開始了解虛拟函數是如何發揮作用的吧!
虛函數的内部功能 在你明白虛函數是怎樣工作之前,你需要知道非虛函數是如何工作的。 想一想下面的代碼:
- MyClass a;
- a.foo();
如果foo()是個非虛函數,那麼編譯器将會把它轉換成代碼,直接跳到MyClass類的foo()函數。 但是記住,這就是非虛函數的問題所在。回想之前的例子,如果這個類是多态的,那麼編譯器由于不知道變量的全部類型,也就不知道應該跳到哪個函數。這就需要一種方法在運作時查找到正确的函數。 要完成這種查找,虛函數要使用“virtual table”(也稱“v-table”,虛表)。虛表是一個查找表來将函數映射到其實作上,并且每個類都通路一個表。當一個虛函數被調用時,編譯器發出代碼來檢索對象的虛表進而查找到正确的函數。 回顧上面的例子來看看這是如何工作的:
- class Foo {
- public:
- virtual int value() { return 5; }
- };
- class Bar : public Foo {
- public:
- virtual int value() { return 10; }
- };
- Bar *b = new Bar();
- Foo *f = (Foo*)b;
- printf(“%i”, f->value());
- // Output = 10
當你建立一個類指針b和一個Bar類的執行個體,那麼它的虛表将是Bar類的虛表。當b指針轉換為Foo類的一個指針時,它并沒有改變對象的内容,虛表仍然是Bar類的虛表而不是Foo類的。是以當查找v-table以調用value()時,結果是将調用Bar::value()。 構造函數和析構函數 每個對象在其生命周期中都要經曆兩個重要階段:構造函數和析構函數。C++允許你同時控制這兩個階段。在Objective-C中與這兩階段相同的是初始化方法(例如,init或者以init開頭的其他方法)和dealloc(釋放記憶體)。 C++中定義構造函數時與類同名。正如在Objective-C中有多個初始化方法,你也可以定義多個構造函數。 例如,下面這個類中有兩個不同的構造函數:
- class Foo {
- private:
- int x;
- public:
- Foo() {
- x = 0;
- }
- Foo(int x) {
- this->x = x;
- }
- };
這就是兩個構造函數,一個是預設構造函數Foo(),另一個構造函數含有一個參數來設定成員變量。 如上例中,如果在構造函數中給成員變量初始化,有用少量代碼實作的方法。不需要自己去設定成員變量的值,你可以用下面的文法:
- class Foo {
- private:
- int x;
- public:
- Foo() : x(0) {
- }
- Foo(int x) : x(x) {
- }
- };
通常來講,如果僅僅是給成員變量指派的話可以用上面這種方式。但是如果你需要用到邏輯或者調用其他函數的話,那麼你就要實作函數主體。你也可以結合以上兩種方式。 當用繼承時,你需要調用父類的構造函數。在Objective-C中,你通常采用先調用父類指定的初始化程式的方法。 在C++中,你可以這樣做:
- class Foo {
- private:
- int x;
- public:
- Foo() : x(0) {
- }
- Foo(int x) : x(x) {
- }
- };
- class Bar : public Foo {
- private:
- int y;
- public:
- Bar() : Foo(), y(0) {
- }
- Bar(int x) : Foo(x), y(0) {
- }
- Bar(int x, int y) : Foo(x), y(y) {
- }
- };
函數簽名後,清單中的第一個元素表示對父類構造函數的調用。你可以調用任何一個你想要的超類構造函數。 C++沒有一個指定的初始化程式。目前,沒有辦法調用同一個類的構造函數。在Objective-C中,有一個指定的初始化程式可以被其他初始化程式調用,并且隻有這個指定的初始化程式去調用超類的指定初始化程式,例如:
- @interface Foo : NSObject
- @end
- @implementation Foo
- - (id)init {
- if (self = [super init]) { ///< Call to super’s designated initialiser
- }
- return self;
- }
- - (id)initWithFoo:(id)foo {
- if (self = [self init]) { ///< Call to self’s designated initialiser
- // …
- }
- return self;
- }
- - (id)initWithBar:(id)bar {
- if (self = [self init]) { ///< Call to self’s designated initialiser
- // …
- }
- return self;
- }
- @end
在C++中,雖然你可以調用父類的構造函數,但是目前調用自己的構造函數仍是不合法的。是以,下面的解決方案很常見:
- class Bar : public Foo {
- private:
- int y;
- void commonInit() {
- // Perform common initialisation
- }
- public:
- Bar() : Foo() {
- this->commonInit();
- }
- Bar(int y) : Foo(), y(y) {
- this->commonInit();
- }
- };
-
然而,這十分麻煩。為什麼你不能用Bar(int y)調用Bar(),然後在Bar()中這樣寫“Bar::commonInit()”呢?畢竟,Objective-C中恰恰是這樣寫的。 2011年釋出了最新版的C++标準:C++11。在這個更新的标準中确實可以這樣做。目前仍有許多C++代碼還沒有按C++11标準來更新,是以知道這兩種方法很重要。任何2011年前标準的C++代碼都按以下這種方式:
- class Bar : public Foo {
- private:
- int y;
- public:
- Bar() : Foo() {
- // Perform common initialisation
- }
- Bar(int y) : Bar() {
- this->y = y;
- }
- };
這種方法唯一一個不足的地方是,你不能在同一個類中調用構造函數的同時設定一個成員變量。上面的例子中,成員變量y在構造函數主體中設定。 注意:在2011年C++11标準成為一個完整的标準,起初稱為C++ 0x。意思是在2000年至2009年之間這項标準成熟的話,x可以替換為這一年的最後一個數字。然而比預期的時間要晚,是以以11為結尾!所有的現代編譯器,包括clang,現在都支援C++11标準。 以上為構造函數,那麼析構函數呢?當一個堆對象被删除或者一個棧函數溢出時會調用析構函數。在析構函數中你需要做的事情就是清理對象。 析構函數中不能有任何參數。同樣,在Objective-C中dealloc也不需要任何參數。是以每個類中隻有一個析構函數。 在類中定義析構函數時在函數名字前要加字首--波浪号(~)。如下:
- class Foo {
- public:
- ~Foo() {
- printf(“Foo destructor\n”);
- }
- };
看一下當你的類被繼承時,會發生什麼:
- class Bar : public Foo {
- public:
- ~Bar() {
- printf(“Bar destructor\n”);
- }
- };
如果你不這樣寫的話,當通過Foo指針删除Bar類的一個執行個體的時候将會發生異常,如下:
- Bar *b = new Bar();
- Foo *f = (Foo*)b;
- delete f;
- // Output:
- // Foo destructor
這樣是錯誤的。删除的應該是Bar類的執行個體,但是為什麼是去調用Foo類的析構函數呢? 回想一下,之前發生的同樣的問題,你是使用虛函數解決的。這個正是同樣的問題。編譯器看到是一個Foo需要被删除,因為Foo的析構函數并不是虛函數,是以編譯器認為要調用的是Foo的析構函數。 解決這個問題的辦法就是将析構函數定義為虛函數,如下:
- class Foo {
- public:
- virtual ~Foo() {
- printf(“Foo destructor\n”);
- }
- };
- class Bar : public Foo {
- public:
- virtual ~Bar() {
- printf(“Bar destructor\n”);
- }
- };
- Bar *b = new Bar();
- Foo *f = (Foo*)b;
- delete f;
- // Output:
- // Bar destructor
- // Foo destructor
這就接近了期望的結果,但最終結果不同于之前使用虛函數得到的結果。在這裡,兩個函數都被調用了。首先Bar的析構函數被調用,然後Foo的析構函數被調用。為什麼呢? 這是因為析構函數比較特殊。由于Foo的析構函數是父類的析構函數,是以Bar的析構函數自動調用Foo的析構函數。 這正是所需要的,正如Objective-c中的ARC方法中,你調用的是父類的dealloc。
你可能在想這個:你認為編譯器會為你做這個事情,但是并不是在所有類中都是最佳方法。 例如,如果你沒有從某個類繼承呢?如果析構函數是虛函數,那麼每次都要通過虛表來删除一個執行個體,或許這種間接方法并不是你需要的。C++中你可以自己做決定,另一個方法很強大,但是開發者必須清楚發生了什麼。
注意:除非你确定你不需要繼承一個類,否則一定要定義析構函數為虛函數。 |
運算符重載 在Objective-C中沒有運算符重載的概念,但是這并不複雜。 操作符是實體,如我們熟悉的+,-,*,/。例如,你可以用“+”運算符與标準常量(操作數)做如下運算:
- int x = 5;
- int y = x + 5; ///< y = 10
運算符“+”在這裡的作用顯而易見,将x加上5然後傳回一個值。或許這個還不夠明顯,如果以函數的形式就很清楚了:
- int x = 5;
- int y = add(x, 5);
在函數add()中,兩個參數相加并傳回一個值。 在C++中,在類中使用操作符時是可以定義功能函數的。這一功能很強大。當然,這也不是總能行得通的。例如,将兩個Person類相加就無任何實際意義。 然而,這一特性很有用處。考慮下面的類:
- class DoubleInt {
- private:
- int x;
- int y;
- public:
- DoubleInt(int x, int y) : x(x), y(y) {}
- };
這樣做可能更好一些:
- DoubleInt a(1, 2);
- DoubleInt b(3, 4);
- DoubleInt c = a + b;
我們想要将DoubleInt(4, 6)的值指派給變量c,即将兩個DoubleInt的執行個體x和y相加,然後指派給c。事實證明這很簡單。你需要做的就是給DoubleInt類添加一個方法,即:
- DoubleInt operator+(const DoubleInt &rhs) {
- return DoubleInt(x + rhs.x, y + rhs.y);
- }
函數operator+很特别。編譯器将使用這個函數,當它看到“+”運算符任一側的DoubleInt時。“+”運算符左邊的對象将調用這個函數,将“+”運算符右邊的對象作為參數進行傳遞。是以,經常命名參數為“rhs”,意思是“右邊”。 由于使用實參的副本不僅沒必要還可能會改變值,函數的參數将作為引用,可能會建立一個新的對象。此外,這個對象将是常量,這是因為在相加的過程中,對于“+”運算符的右邊來講這是非法的。 C++能做的不僅是這些。你可能不僅僅想把DoubleInt添加至DoubleInt。你可能想要給DoubleInt添加一個整數。這些都是可能實作的! 為實作此操作,你需要實作如下成員函數:
- DoubleInt operator+(const int &rhs) {
- return DoubleInt(x + rhs, y + rhs);
- }
然後你可以這樣做:
- DoubleInt a(1, 2);
- DoubleInt b = a + 10;
- // b = DoubleInt(11, 12);
很有用吧! 除了加法運算,其他運算也可以這樣做。你可以重載++, --, +=, -=, *, ->等等。這裡就不一一列舉了。如果想要對運算符重載做更多了解,你可以通路learncpp.com,這裡有整個章節在介紹運算符重載。 模闆 在C++中,模闆很有意思。 你是否發現你經常會重複寫相同的函數或者類,但隻是函數或者類的類型不同呢?例如,交換兩個值的函數:
- void swap(int &a, int &b) {
- int temp = a;
- a = b;
- b = temp;
- }
注:這裡是對參數做引用傳遞,以確定是對函數的實參作交換。如果兩個參數是用值傳遞,那麼所交換的值隻是實參的副本。這個例子很好的說明了C++中引用好處。 |
上面的例子隻适用于整數類型。如果是浮點數類型,那麼你需要寫另一個函數:
- void swap(float &a, float &b) {
- float temp = a;
- a = b;
- b = temp;
- }
如果你重複寫函數的主體,這樣很不明智。C++介紹一種文法可以有效的忽略變量的類型。你可以通過模闆這個特性來實作這一功能。取代上面的兩種方法,在C++中,你可以這樣寫:
- template <typename T>
- void swap(T a, T b) {
- T temp = a;
- a = b;
- b = temp;
- }
是以,你的函數可以交換任何類型的參數。你可以用以下任一種方式來調用函數:
- int ix = 1, iy = 2;
- swap(ix, iy);
- float fx = 3.141, iy = 2.901;
- swap(fx, fy);
- Person px(“Matt Galloway”), py(“Ray Wenderlich”);
- swap(px, py);
但是,你在用模闆的時候仍需仔細。隻有在頭檔案中實作模闆函數,這種方法才能起作用。這是由模闆的編譯方式決定的。使用模闆函數時,如果函數類型不存在,編譯器會根據類型執行個體化一個函數模闆。 考慮到編譯器需要知道模闆函數的實作,你需要在頭檔案中定義一個實作,并且在使用的時候必須要包含這個頭檔案。 同理,如果要修改模闆函數中的實作,所有用到這個函數的檔案都需要重編譯。相比之下,如果在實作檔案中修改函數或者實作類成員函數,那麼隻有這個實作檔案需要重編譯。 是以,過度地使用模闆會使應用程式很繁瑣。但是正如C++中很多方法,模闆的作用很大。 模闆類 不僅僅有模闆函數,還可以在整個類中使用模闆。 假設你的類中有三個值,這三個值用來存儲一些資料。首先,你想用整數類型,是以你要這樣寫:
- class IntTriplet {
- private:
- int a, b, c;
- public:
- IntTriplet(int a, int b, int c) : a(a), b(b), c(c) {}
- int getA() { return a; }
- int getB() { return b; }
- int getC() { return c; }
- };
但是,你繼續寫程式時發現你需要三個浮點型資料。這是你又要寫一個類,如下:
- class FloatTriplet {
- private:
- float a, b, c;
- public:
- FloatTriplet(float a, float b, float c) : a(a), b(b), c(c) {}
- float getA() { return a; }
- float getB() { return b; }
- float getC() { return c; }
- };
這裡,模闆就會很有用。與模闆函數相同,可以在類中使用模闆。文法是一樣的。上面的兩個類可以寫成這樣:
- template <typename T>
- class Triplet {
- private:
- T a, b, c;
- public:
- Triplet(T a, T b, T c) : a(a), b(b), c(c) {}
- T getA() { return a; }
- T getB() { return b; }
- T getC() { return c; }
- };
但是,用模闆類需要做一些細微的改動。使用模闆函數不會改變代碼,這是因為參數類型允許模闆推斷需要做什麼。然而,使用模闆類時,你要告訴編譯器你需要模闆類使用什麼類型。 幸運的是,這個很簡單。用上面的模闆類也很簡單:
- Triplet<int> intTriplet(1, 2, 3);
- Triplet<float> floatTriplet(3.141, 2.901, 10.5);
- Triplet<Person> personTriplet(Person(“Matt”), Person(“Ray”), Person(“Bob”));
很強大,對吧?
此外,模闆函數和模闆類并不局限于單個未知類型。三重态的類可以被擴充以支援任何三種類型,而不是每個值必須是同樣的類型。 要做到這一點,隻需要擴充提供更多類型的模闆定義,如下:
- template <typename TA, typename TB, typename TC>
- class Triplet {
- private:
- TA a;
- TB b;
- TC c;
- public:
- Triplet(TA a, TB b, TC c) : a(a), b(b), c(c) {}
- TA getA() { return a; }
- TB getB() { return b; }
- TC getC() { return c; }
- };
以上模闆中有三個不同類型,每個類型都在代碼中的适當位置被使用。 使用這樣的模闆也很簡單,如下所示:
- Triplet<int, float, 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的用法:
- #include <vector>
- std::vector<int> v;
- v.push_back(1);
- v.push_back(2);
- v.push_back(3);
- v.push_back(4);
- v.push_back(5);
注意std::的用法,這是因為大部分STL位于命名空間内。STL将其所有的類放在自己的名為"std"的命名空間中以避免潛在的命名沖突。 |
上面的代碼中,首先你建立一個vector來存放整型資料(int),然後五個整數被依次壓入vector的棧頂。操作完成後,vector中将是從1到5的有序序列。 這裡需要注意的是,正如Objective-C中,所有的容器都是可變的,沒有可變或者不可變的變量。 通路一個vector的元素是這樣完成的:
- int first = v[1];
- 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的用法很相似:
- #include <list>
- std::list<int> l;
- l.push_back(1);
- l.push_back(2);
- l.push_back(3);
- l.push_back(4);
- l.push_back(5);
正如上面的vector例子,這将建立一個從1到5的有序序列。但是,在list中你不能使用方括号或者at()成員函數去通路一個指定元素。你需要用一個疊代器(iterators)去周遊list。 你可以這樣周遊list中的每個元素:
- std::list<int>::iterator i;
- for (i = l.begin(); i != l.end(); i++) {
- int thisInt = *i;
- // 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下使用指針是相同的。例如,以下例子展示了如何用智能指針來定義一個指針去指向一個整數:
- std::shared_ptr<int> p1(new int(1));
- std::shared_ptr<int> p2 = p1;
- std::shared_ptr<int> p3 = p1;
運作這三行代碼後,每個shared pointer的引用計數為3。當每個shared pointer被删除或者被釋放後,引用指數減少。直到最後一個包含underlying pointer的shared pointer被删除後,底層指針被删除。 由于shared pointer本身就是棧對象,溢出時就會被删除。是以,shared pointer與Objective-C中的自動引用計數(ARC)下的對象指針的限制規則相同。 下面的例子為shared pointer建立和删除的全過程:
- std::shared_ptr<int> p1(new int(1)); ///< Use count = 1
- if (doSomething) {
- std::shared_ptr<int> p2 = p1; ///< Use count = 2;
- // Do something with p2
- }
- // p2 has gone out of scope and destroyed, so use count = 1
- p1.reset();
- // p1 reset, so use count = 0
- // The underlying int* is deleted
把p1配置設定給p2是将p1的副本配置設定給p2。記住當一個函數參數是按值傳遞的話,是将參數的副本傳給了函數。這一點是很有用處的,因為如果你将一個shared pointer傳給一個函數,傳遞給這個函數的是一個新的shared pointer。當然,在函數結束時就會發生越界,進而被删除。是以在函數周期中,underlying pointer的使用數量将會增加。這與在Objective-C中的自動引用計數(ARC)下的引用計數功能相同。 當然,你需要能夠獲得或者使用underlying pointer,有兩種方式可以實作這一操作。重載解引用操作符(*)和箭頭操作符(->)以使shared pointer的工作方式本質上與一個正常的指針相同。如下:
- std::shared_ptr<Person> p1(new Person(“Matt Galloway”));
- Person *underlyingPointer = *p1; ///< Grab the underlying pointer
- 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++。 以下為如何在兩者間使用對象的例子:
- // Forward declare so that everything works below
- @class ObjcClass;
- class CppClass;
- // C++ class with an Objective-C member variable
- class CppClass {
- public:
- ObjcClass *objcClass;
- };
- // Objective-C class with a C++ object as a property
- @interface ObjcClass : NSObject
- @property (nonatomic, assign) std::shared_ptr<CppClass> cppClass;
- @end
- @implementation ObjcClass
- @end
- // Using the two classes above
- std::shared_ptr<CppClass> cppClass(new CppClass());
- ObjcClass *objcClass = [[ObjcClass alloc] init];
- cppClass->objcClass = objcClass;
- 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++的類。
- // MyClass.h
- #import <Foundation/Foundation.h>
- #include <list>
- @interface MyClass : NSObject
- @property (nonatomic, assign) std::list<int> listOfIntegers;
- @end
- // MyClass.mm
- #import “MyClass.h”
- @implementation MyClass
- // …
- @end
MyClass類的實作檔案必須是.mm檔案,因為它是使用C++編寫的。這沒有錯,但是想一想如果你想要使用MyCLass類的話會發生什麼呢。你需要import MyClass.h,但是這樣做你引入了一個使用C++編寫的檔案。是以即使其他的檔案不需要用C++編寫,也需要使用Objective-C++來進行編譯。 是以,最好是在公共頭檔案中減少使用C++。你可以使用在實作檔案中聲明的私有屬性或者實體變量實作這一目的。 下一步 C++是一個偉大的語言。它與Objective-C有相似的根源,但是它選擇一種很不同的方式去編寫程式。總之,學習C++可以很好的了解面向對象程式。而且C++能幫助你在objective - c代碼做出更好的設計決策。我鼓勵你去學習更多的C++知識并自己寫程式。你可以在learncpp.com中找到很多好的資源。如果你有任何評論或者疑問或者C++問題,請留言。 本文譯自: Introduction to C++ for iOS Developers: Part 2
相關閱讀: 向iOS開發者介紹C++(一)