作為一名 iOS 開發(fā)者,最近由于開發(fā)音頻播放器需要一些 C++ 的知識(shí),便開始學(xué)習(xí) C++ 知識(shí)??赐?本C++的書后,對(duì)它有一些了解,這里分享給有需要的同學(xué),如果寫的有不妥的地方,還望指出。后續(xù)會(huì)利用學(xué)到的C++知識(shí)對(duì) FreeStreamer 這個(gè)音頻播放器寫一篇分析其實(shí)現(xiàn)原理的文章。本文主要圍繞 FreeStreamer 這個(gè)庫用到的 C++ 語法來講解。
iOS 開發(fā)當(dāng)中,偶爾會(huì)使用到 C++ 的知識(shí),然而大多數(shù)同學(xué)每遇到這個(gè)問題時(shí),選擇逃避。如果從頭開始學(xué)習(xí)C++語法,會(huì)花費(fèi)很多時(shí)間,如同筆者一樣,花費(fèi)很多時(shí)間了解基礎(chǔ)的知識(shí)。
Objective-C 和 C++ 都是基于 C 語言而設(shè)計(jì)的,它們都具有面向?qū)ο蟮哪芰ΑT趯W(xué)習(xí) C++ 知識(shí)之前,我們先了解下 Objective-C++,它可以把 C++ 和 Objective-C 結(jié)合起來使用,如果把兩門語言的優(yōu)點(diǎn)結(jié)合起來使用是不是會(huì)更好?騰訊開源的數(shù)據(jù)庫 WCDB 是一個(gè)很好的例子,它不僅有 C++ 而且還有 Objective-C++。
本文主要內(nèi)容:
- 類(Class)的定義與使用
- 命名空間
- 內(nèi)存管理
- 繼承
- 構(gòu)造函數(shù)和析構(gòu)函數(shù)
- virtual 關(guān)鍵字
- 靜態(tài)成員與靜態(tài)函數(shù)
- 運(yùn)算符重載
- 打印日志
- 實(shí)例
類(Class)
類在面向?qū)ο笾蟹浅V匾?Objective-C 中,所有的 Class 必需繼承自 NSObject 類,當(dāng)創(chuàng)建一個(gè)類的時(shí)候,它會(huì)包含一個(gè) .h 和 一個(gè) .m 文件,我們以定義一個(gè) Person 類為例:
/* Person.h */
#import <Foundation/Foundation.h>
@interface Person : NSObject
@end
/* Person.m */
#import "Person.h"
@implementation Person
@end
而 C++ 中,創(chuàng)建一個(gè)類也會(huì)有一個(gè)頭文件 Person.hpp 和實(shí)現(xiàn)文件 Person.cpp 。
/* Person.hpp */
#include <stdio.h>
class Person {
};
/* Person.cpp */
#include "Person.hpp"
從上面的例子我們可以看到,OC 和 C++ 定義類主要有以下不同:
- 在實(shí)現(xiàn)文件中,C++ 中沒有
@implementation Person @end; - OC 中每個(gè)類需要繼承自 NSObject;
- C++ 中使用 #include 導(dǎo)入其它文件中的代碼,而 OC 使用 #import 導(dǎo)入其它文件代碼,使用 #import 保證每個(gè)文件中不會(huì)重復(fù)導(dǎo)入,而使用 #include 需要開發(fā)者保證不要重復(fù)導(dǎo)入。
class Person {
public:
int age;
bool isChild();
private:
float height;
bool isNomalHeight();
};
Person 類中定義了一個(gè)公有的成員變量 age 和 一個(gè)成員函數(shù) isChild。一個(gè)私有的成員變量 height 和一個(gè)成員函數(shù) isNomalHeight 。在 C++ 中可以定義某個(gè)變量或函數(shù)的作用范圍,如果使用時(shí)超出作用范圍編譯器將會(huì)報(bào)錯(cuò)。而在 OC 中既使在 .h 文件中沒有定義某個(gè)函數(shù),我們?nèi)稳豢梢哉{(diào)用,所以在 OC 中經(jīng)常會(huì)出現(xiàn)以 _ 或某個(gè)前綴開頭的私有函數(shù)。
Person 類的實(shí)現(xiàn):
bool Person::isChild(){
return age >= 18;
}
bool Person::isNomalHeight(){
return height >= 170;
}
其中 :: 表示 isChild 函數(shù)屬于 Person 類,它告訴編譯器從哪里找到函數(shù) isChild。
定義了一個(gè) Person 類,使用的時(shí)候和 OC 會(huì)有一些不同。
OC中只能創(chuàng)建堆上的對(duì)象
Person *aPerson = [[Person alloc] init];
aPerson.age = 18;
在棧上創(chuàng)建一個(gè) Person
Person aPerson;
aPerson.age = 18;
NSLog(@"age == %@", @(aPerson.age));
2018-02-19 12:12:20.252108+0800 C++Demo[2480:84138] age == 18
在堆上創(chuàng)建一個(gè) Person
Person *aPerson = new Person();
aPerson->age = 18;
NSLog(@"aPerson age == %@", @(aPerson->age));
delete aPerson;
2018-02-19 12:16:25.432998+0800 C++Demo[2525:88221] aPerson age == 18
命名空間
在 C++ 中有命名空間的概念,可以幫助開發(fā)者在開發(fā)新的軟件組件時(shí)不會(huì)與已有的軟件組件產(chǎn)生命名沖突,而在 OC 中卻沒有命名空間的概念,我們常以前綴來與第三方庫區(qū)分。它的定義為:
namespace 命名空間的名字 { }
我們將上面定義的類加上命名空間:
namespace Lefex {
class Person {
public:
int age;
bool isChild();
private:
float height;
bool isNomalHeight();
};
}
namespace Lefex {
bool Person::isChild(){
return age >= 18;
}
bool Person::isNomalHeight(){
return height >= 170;
}
}
那么使用時(shí)必須加上命名空間:
Lefex::Person aPerson;
aPerson.age = 18;
NSLog(@"age == %@", @(aPerson.age));
內(nèi)存管理
在 OC 中使用引用計(jì)數(shù)來管理內(nèi)存,當(dāng)引用計(jì)數(shù)為 0 時(shí),內(nèi)存空間將被釋放。而 C++ 中需要開發(fā)者自己管理內(nèi)存。理解 C++ 的內(nèi)存管理,我們有必要先了解一下棧內(nèi)存和堆內(nèi)存。
- 棧內(nèi)存:它分配的大小是固定的,當(dāng)一個(gè)函數(shù)執(zhí)行時(shí),將為某些變量分配存儲(chǔ)空間,當(dāng)函數(shù)執(zhí)行完成后將釋放其對(duì)應(yīng)的存儲(chǔ)空間。
- 堆內(nèi)存:它會(huì)隨著應(yīng)用的運(yùn)行,使用的空間逐步增加,分配的存儲(chǔ)空間需要開發(fā)者自己釋放。
void Person::ageMemory(){
int stackAge = 20;
int *heapAge = (int *)malloc(sizeof(int));
*heapAge = 20;
free(heapAge);
}
stackAge 為棧內(nèi)存,不需要開發(fā)者自己釋放內(nèi)存,當(dāng) ageMemory 函數(shù)執(zhí)行完成后 stackAge 將被釋放。heapAge 為堆空間,當(dāng)函數(shù) ageMemory 執(zhí)行完成后,它不會(huì)釋放,需要開發(fā)者手動(dòng)釋放。
下面這個(gè)例子是創(chuàng)建了一個(gè) Person 對(duì)象,它使用的是堆內(nèi)存,需要使用 delete 釋放其內(nèi)存空間。這里需要注意訪問堆對(duì)象時(shí)使用 -> 訪問它的成員或者方法,而訪問棧對(duì)象時(shí)使用 . 訪問它的成員或者方法。
在 OC 中,當(dāng)一個(gè)對(duì)象為 nil 時(shí)調(diào)用一個(gè)方法時(shí) [nil doSomeThing] , 程序并不會(huì)執(zhí)行 doSomeThing 方法,而在 C++ 中,NULL-> doSomeThing ,程序?qū)?crash。
Lefex::Person *aPerson = new Lefex::Person();
aPerson->age = 18;
NSLog(@"age == %@", @(aPerson->age));
NSLog(@"is a child: %@", @(aPerson->isChild()));
delete aPerson;
繼承
C++ 中支持多繼承,也就是說一個(gè)類可以繼承多個(gè)類。而在 OC 中只能使用單繼承。與 OC 中不同的一點(diǎn)就是增加了修飾符(比如:public),這樣用來限制繼承的屬性和方法的范圍。
// 男人
class Man: public Lefex::Person {
};
// 女人
class Woman: public Lefex::Person {
};
// 人妖
class Freak: public Man, public Woman {
};
構(gòu)造函數(shù)和析構(gòu)函數(shù)
構(gòu)造函數(shù)通常在 OC 中使用的是 init,而在 C++ 中默認(rèn)的構(gòu)造函數(shù)是于類名相同的函數(shù)。比如類 Person 的默認(rèn)構(gòu)造函數(shù)是 Person(),自定義一個(gè)構(gòu)造函數(shù) Person(int age, int height) 。在 OC 中析構(gòu)函數(shù)如 dealloc,而在 C++ 中是函數(shù) ~Person(),當(dāng)一個(gè)類被釋放后,析構(gòu)函數(shù)會(huì)自動(dòng)執(zhí)行。
// 默認(rèn)構(gòu)造函數(shù)
Person::Person(){
printf("Init person");
}
// 初始化列表構(gòu)造函數(shù)
Person::Person()
:age(0),
height(0),
m_delegate(0){
printf("Init person\n");
}
// 自定義構(gòu)造函數(shù)
Person::Person(int age, int height){
this->age = age;
this->height = height;
}
// 析構(gòu)函數(shù)
Person::~Person(){
printf("Dealloc person");
}
虛析構(gòu)函數(shù):為了保證析構(gòu)函數(shù)可以正常的被執(zhí)行,引入了虛析構(gòu)函數(shù),一般基類中的析構(gòu)函數(shù)都是虛析構(gòu)函數(shù)。定義方式如下。
virtual ~Person();
Person::~Person(){
printf("person dealloc called \n");
}
virtual 關(guān)鍵字
虛函數(shù)是一種非靜態(tài)的成員函數(shù),定義格式:
virtual <類型說明符> <函數(shù)名> { 函數(shù)體 }
純虛函數(shù):是一種特殊的虛函數(shù),這是一種沒有具體實(shí)現(xiàn)的虛函數(shù),定義格式:
virtual <類型說明符> <函數(shù)名> (<參數(shù)表>)=0;
抽象類:它是一個(gè)不能有實(shí)例的類,這樣的類唯一用途在于被繼承。一個(gè)抽象類中至少具有一個(gè)虛函數(shù),它主要的作用來組織一個(gè)繼承的層次結(jié)構(gòu),并由它提供一個(gè)公共的根。
有了抽象類和純虛函數(shù),就可以實(shí)現(xiàn)類似于 OC 中的 delegate。
// 相當(dāng)于 OC 中的代理
class Person_delegate {
public:
// =0 表示無實(shí)現(xiàn)
virtual void ageDidChange()=0;
};
// 繼承了 Person_delegate,Woman 類真正實(shí)現(xiàn)了 Person_delegate 的純虛函數(shù)
class Woman: public Lefex::Person, public Lefex::Person_delegate {
public:
Woman();
void ageDidChange();
};
綜上可以看到它于 OC 中實(shí)現(xiàn)的思路一致。
靜態(tài)成員與靜態(tài)函數(shù)
靜態(tài)成員使用 static 修飾,只對(duì)其進(jìn)行一次賦值,將一直保留這個(gè)值,直到下次被修改。
在 Person 類中定義一個(gè)靜態(tài)變量 weight。
static int weight;
使用時(shí)直接:Person::weight; 即可訪問靜態(tài)變量。
靜態(tài)成員函數(shù)與靜態(tài)成員的定義一致。
static float currentWeight();
需要注意的是,在靜態(tài)成員函數(shù)中,不可以使用非靜態(tài)成員。
調(diào)用:
Person::currentWeight();
也可以:
aPerson->currentWeight();
運(yùn)算符重載
有時(shí)候利用系統(tǒng)的運(yùn)算符作自定義對(duì)象之間的比較的時(shí)候,不得不對(duì)運(yùn)算符進(jìn)行重載。
定義:
類型 operator op(參數(shù)列表) {}
“類型”為函數(shù)的返回類型,函數(shù)名是 “operator op”,由關(guān)鍵字 operator 和被重載的運(yùn)算符op組成?!皡?shù)列表”列出該運(yùn)算符所需要的操作數(shù)。
例子:
// Person 類中定義 Person 是否相等。
bool operator==(Person &){
if (this->age == person.age) {
return true;
}
return false;
};
+ (void)operatorOverload
{
Lefex::Person aPerson;
aPerson.age = 18;
Lefex::Person aPerson2;
aPerson2.age = 18;
if (aPerson == aPerson2) {
NSLog(@"aPerson is equal to aPerson2");
} else {
NSLog(@"aPerson is not equal to aPerson2");
}
}
打印日志
C++ 中打印日志需要導(dǎo)入庫 #include <iostream>。
- \n 和 endl 效果一樣都是換行;
- 打印多個(gè)使用 << 拼接;
void Woman::consoleLog(){
std::cout<< "Hello Lefe_x\n";
std::cout<< "Hello " << "Lefe_x\n";
int age = 20;
std::cout<< "Lefe_x age is " << age << std::endl;
}
打印結(jié)果:
Hello Lefe_x
Hello Lefe_x
Lefe_x age is 20
實(shí)例
下面這段代碼摘自 FreeStreamer 中的 audio_queue.h,有部分重復(fù)的知識(shí)點(diǎn)有刪減。建議讀者仔細(xì)看看下面的代碼,看看有沒有看不懂的地方。
namespace astreamer {
class Audio_Queue_Delegate;
class Audio_Queue {
public:
Audio_Queue_Delegate *m_delegate;
Audio_Queue();
virtual ~Audio_Queue();
void start();
private:
void cleanup();
static void audioQueueOutputCallback(void *inClientData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer);
};
class Audio_Queue_Delegate {
public:
virtual void audioQueueStateChanged(Audio_Queue::State state) = 0;
virtual void audioQueueFinishedPlayingPacket() = 0;
};
} // namespace astreamer
總結(jié)
總的來說,這些內(nèi)容是最基本的語法知識(shí),希望可以幫你入門 C++。
===== 我是有底線的 ======