iOS 开发者应该掌握些 C++ 知识

最近打算看一下runtime的源码,发现很多核心类都是.mm也就是objective-c++写的,所以这里需要补一下C++的知识。废话不多说,直接进主题。

iOS 开发当中,偶尔会使用到 C++ 的知识,然而大多数同学每遇到这个问题时,选择逃避。如果从头开始学习C++语法,会花费很多时间,所以这里打算归纳一下,既不花费太多的精力,又掌握必要的C++的知识。

Objective-C 和 C++ 都是基于 C 语言而设计的,它们都具有面向对象的能力。在学习 C++ 知识之前,我们先了解下 Objective-C++,它可以把 C++ 和 Objective-C 结合起来使用,如果把两门语言的优点结合起来使用是不是会更好?腾讯开源的数据库 WCDB 是一个很好的例子,它不仅有 C++ 而且还有 Objective-C++。

本文主要内容:
  • 类(Class)的定义与使用
  • 命名空间
  • 内存管理
  • 继承
  • 构造函数和析构函数
  • virtual 关键字
  • 静态成员与静态函数
  • 运算符重载
  • 打印日志
  • 实例
类(Class)

类在面向对象中非常重要,在 Objective-C 中,所有的 Class 必需继承自 NSObject 类,当创建一个类的时候,它会包含一个 .h 和 一个 .m 文件,我们以定义一个 Person 类为例:

/* Person.h */
#import <Foundation/Foundation.h>
@interface Person : NSObject
@end
/* Person.m */
#import "Person.h"
@implementation Person
@end

而 C++ 中,创建一个类也会有一个头文件 Person.hpp 和实现文件 Person.cpp 。

/* Person.hpp */
#include <stdio.h>
class Person {
};
/* Person.cpp */
#include "Person.hpp"

从上面的例子我们可以看到,OC 和 C++ 定义类主要有以下不同:

  • 在实现文件中,C++ 中没有 @implementation Person @end;
  • OC 中每个类需要继承自 NSObject;
  • C++ 中使用 #include 导入其它文件中的代码,而 OC 使用 #import 导入其它文件代码,使用 #import 保证每个文件中不会重复导入,而使用 #include 需要开发者保证不要重复导入。
class Person {
public:
    int age;
    bool isChild();
private:
    float height;
    bool isNomalHeight();
};

Person 类中定义了一个公有的成员变量 age 和 一个成员函数 isChild。一个私有的成员变量 height 和一个成员函数 isNomalHeight 。在 C++ 中可以定义某个变量或函数的作用范围,如果使用时超出作用范围编译器将会报错。而在 OC 中既使在 .h 文件中没有定义某个函数,我们任然可以调用,所以在 OC 中经常会出现以 _ 或某个前缀开头的私有函数。

Person 类的实现

bool Person::isChild(){
    return age >= 18;
}
bool Person::isNomalHeight(){
    return height >= 170;
}

OC中只能创建堆上的对象

Person *aPerson = [[Person alloc] init];
aPerson.age = 18;

C++在栈上创建一个 Person

Person aPerson;
aPerson.age = 18;
NSLog(@"age == %@", @(aPerson.age));    
2018-02-19 12:12:20.252108+0800 C++Demo[2480:84138] age == 18

C++在堆上创建一个 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++ 中有命名空间的概念,可以帮助开发者在开发新的软件组件时不会与已有的软件组件产生命名冲突,而在 OC 中却没有命名空间的概念,我们常以前缀来与第三方库区分。它的定义为:
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;
    }
}

那么使用时必须加上命名空间:

Lefex::Person aPerson;
aPerson.age = 18;   
NSLog(@"age == %@", @(aPerson.age));
内存管理

在 OC 中使用引用计数来管理内存,当引用计数为 0 时,内存空间将被释放。而 C++ 中需要开发者自己管理内存。理解 C++ 的内存管理,我们有必要先了解一下栈内存和堆内存。

  • 栈内存:它分配的大小是固定的,当一个函数执行时,将为某些变量分配存储空间,当函数执行完成后将释放其对应的存储空间。
  • 堆内存:它会随着应用的运行,使用的空间逐步增加,分配的存储空间需要开发者自己释放。
void Person::ageMemory(){
  int stackAge = 20;
  int *heapAge = (int *)malloc(sizeof(int));
  *heapAge = 20;
  free(heapAge);
}

stackAge 为栈内存,不需要开发者自己释放内存,当 ageMemory 函数执行完成后 stackAge 将被释放。heapAge 为堆空间,当函数 ageMemory 执行完成后,它不会释放,需要开发者手动释放。

下面这个例子是创建了一个 Person 对象,它使用的是堆内存,需要使用 delete 释放其内存空间。这里需要注意访问堆对象时使用 -> 访问它的成员或者方法,而访问栈对象时使用 . 访问它的成员或者方法。

在 OC 中,当一个对象为 nil 时调用一个方法时 [nil doSomeThing] , 程序并不会执行 doSomeThing 方法,而在 C++ 中,NULL-> doSomeThing ,程序将 crash。

Lefex::Person *aPerson = new Lefex::Person();
aPerson->age = 18;
NSLog(@"age == %@", @(aPerson->age));
NSLog(@"is a child: %@", @(aPerson->isChild()));
delete aPerson;
继承

C++ 中支持多继承,也就是说一个类可以继承多个类。而在 OC 中只能使用单继承。与 OC 中不同的一点就是增加了修饰符(比如:public),这样用来限制继承的属性和方法的范围。

// 男人
class Man: public Lefex::Person {
     
};
// 女人
class Woman: public Lefex::Person {
     
};
// 人妖
class Freak: public Man, public Woman {
};
构造函数和析构函数

构造函数通常在 OC 中使用的是 init,而在 C++ 中默认的构造函数是于类名相同的函数。比如类 Person 的默认构造函数是 Person(),自定义一个构造函数 Person(int age, int height) 。在 OC 中析构函数如 dealloc,而在 C++ 中是函数 ~Person(),当一个类被释放后,析构函数会自动执行。

// 默认构造函数
Person::Person(){
    printf("Init person");
}
// 初始化列表构造函数
Person::Person()
    :age(0),
    height(0),
    m_delegate(0){
      printf("Init person\n");
}
// 自定义构造函数   
Person::Person(int age, int height){
    this->age = age;
    this->height = height;
}
  
// 析构函数   
Person::~Person(){
    printf("Dealloc person");
}

虚析构函数:为了保证析构函数可以正常的被执行,引入了虚析构函数,一般基类中的析构函数都是虚析构函数。定义方式如下。

virtual ~Person();
Person::~Person(){
    printf("person dealloc called \n");
}
virtual 关键字

虚函数是一种非静态的成员函数,定义格式:
virtual <类型说明符> <函数名> { 函数体 }
纯虚函数:是一种特殊的虚函数,这是一种没有具体实现的虚函数,定义格式:
virtual <类型说明符> <函数名> (<参数表>)=0;
抽象类:它是一个不能有实例的类,这样的类唯一用途在于被继承。一个抽象类中至少具有一个虚函数,它主要的作用来组织一个继承的层次结构,并由它提供一个公共的根。

有了抽象类和纯虚函数,就可以实现类似于 OC 中的 delegate。

// 相当于 OC 中的代理
class  Person_delegate {
public:
    // =0 表示无实现
    virtual void ageDidChange()=0;
};
// 继承了 Person_delegate,Woman 类真正实现了 Person_delegate 的纯虚函数
class Woman: public Lefex::Person, public Lefex::Person_delegate {
public:
    Woman();
    void ageDidChange();
};

综上可以看到它于 OC 中实现的思路一致。
跟OC的区别: OC中由于没有virtual关键字,所以没有真正的抽象类。所以在OC中为了实现类似的抽象类的功能,一般在文档里说明使用其子类(如NSOperation)。

静态成员与静态函数

静态成员使用 static 修饰,只对其进行一次赋值,将一直保留这个值,直到下次被修改。

在 Person 类中定义一个静态变量 weight。
static int weight;
使用时直接:Person::weight; 即可访问静态变量。
静态成员函数与静态成员的定义一致。
static float currentWeight();
需要注意的是,在静态成员函数中,不可以使用非静态成员。
调用:

Person::currentWeight();
//也可以:
aPerson->currentWeight();
运算符重载

有时候利用系统的运算符作自定义对象之间的比较的时候,不得不对运算符进行重载。
类型 operator op(参数列表) {}
“类型”为函数的返回类型,函数名是 “operator op”,由关键字 operator 和被重载的运算符op组成。“参数列表”列出该运算符所需要的操作数。

例子:

// 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++ 中打印日志需要导入库 #include 。

  • \n 和 endl 效果一样都是换行;
  • 打印多个使用 << 拼接;
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;
}

打印结果:

Hello Lefe_x
Hello Lefe_x
Lefe_x age is 20
实例

下面这段代码摘自 runtime中的 objc-runtime-new.h,有部分重复的知识点有删减。建议读者仔细看看下面的代码,看看有没有看不懂的地方。
typedef struct objc_class *Class; // OC2.0中的Class其实底层是由C++的结构体实现的

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }

    void setInfo(uint32_t set) {
        assert(isFuture()  ||  isRealized());
        data()->setFlags(set);
    }

    void clearInfo(uint32_t clear) {
        assert(isFuture()  ||  isRealized());
        data()->clearFlags(clear);
    }

    // set and clear must not overlap
    void changeInfo(uint32_t set, uint32_t clear) {
        assert(isFuture()  ||  isRealized());
        assert((set & clear) == 0);
        data()->changeFlags(set, clear);
    }

    bool hasCustomRR() {
        return ! bits.hasDefaultRR();
    }
    void setHasDefaultRR() {
        assert(isInitializing());
        bits.setHasDefaultRR();
    }
    void setHasCustomRR(bool inherited = false);
    void printCustomRR(bool inherited);

    bool hasCustomAWZ() {
        return ! bits.hasDefaultAWZ();
    }
    void setHasDefaultAWZ() {
        assert(isInitializing());
        bits.setHasDefaultAWZ();
    }
    void setHasCustomAWZ(bool inherited = false);
    void printCustomAWZ(bool inherited);

    bool instancesRequireRawIsa() {
        return bits.instancesRequireRawIsa();
    }
    void setInstancesRequireRawIsa(bool inherited = false);
    void printInstancesRequireRawIsa(bool inherited);

    bool canAllocNonpointer() {
        assert(!isFuture());
        return !instancesRequireRawIsa();
    }
    bool canAllocFast() {
        assert(!isFuture());
        return bits.canAllocFast();
    }


    bool hasCxxCtor() {
        // addSubclass() propagates this flag from the superclass.
        assert(isRealized());
        return bits.hasCxxCtor();
    }
    void setHasCxxCtor() { 
        bits.setHasCxxCtor();
    }

    bool hasCxxDtor() {
        // addSubclass() propagates this flag from the superclass.
        assert(isRealized());
        return bits.hasCxxDtor();
    }
    void setHasCxxDtor() { 
        bits.setHasCxxDtor();
    }


    bool isSwift() {
        return bits.isSwift();
    }


    // Return YES if the class's ivars are managed by ARC, 
    // or the class is MRC but has ARC-style weak ivars.
    bool hasAutomaticIvars() {
        return data()->ro->flags & (RO_IS_ARC | RO_HAS_WEAK_WITHOUT_ARC);
    }

    // Return YES if the class's ivars are managed by ARC.
    bool isARC() {
        return data()->ro->flags & RO_IS_ARC;
    }


#if SUPPORT_NONPOINTER_ISA
    // Tracked in non-pointer isas; not tracked otherwise
#else
    bool instancesHaveAssociatedObjects() {
        // this may be an unrealized future class in the CF-bridged case
        assert(isFuture()  ||  isRealized());
        return data()->flags & RW_INSTANCES_HAVE_ASSOCIATED_OBJECTS;
    }

    void setInstancesHaveAssociatedObjects() {
        // this may be an unrealized future class in the CF-bridged case
        assert(isFuture()  ||  isRealized());
        setInfo(RW_INSTANCES_HAVE_ASSOCIATED_OBJECTS);
    }
#endif

    bool shouldGrowCache() {
        return true;
    }

    void setShouldGrowCache(bool) {
        // fixme good or bad for memory use?
    }

    bool isInitializing() {
        return getMeta()->data()->flags & RW_INITIALIZING;
    }

    void setInitializing() {
        assert(!isMetaClass());
        ISA()->setInfo(RW_INITIALIZING);
    }

    bool isInitialized() {
        return getMeta()->data()->flags & RW_INITIALIZED;
    }

    void setInitialized();

    bool isLoadable() {
        assert(isRealized());
        return true;  // any class registered for +load is definitely loadable
    }

    IMP getLoadMethod();

    // Locking: To prevent concurrent realization, hold runtimeLock.
    bool isRealized() {
        return data()->flags & RW_REALIZED;
    }

    // Returns true if this is an unrealized future class.
    // Locking: To prevent concurrent realization, hold runtimeLock.
    bool isFuture() { 
        return data()->flags & RW_FUTURE;
    }

    bool isMetaClass() {
        assert(this);
        assert(isRealized());
        return data()->ro->flags & RO_META;
    }

    // NOT identical to this->ISA when this is a metaclass
    Class getMeta() {
        if (isMetaClass()) return (Class)this;
        else return this->ISA();
    }

    bool isRootClass() {
        return superclass == nil;
    }
    bool isRootMetaclass() {
        return ISA() == (Class)this;
    }

    const char *mangledName() { 
        // fixme can't assert locks here
        assert(this);

        if (isRealized()  ||  isFuture()) {
            return data()->ro->name;
        } else {
            return ((const class_ro_t *)data())->name;
        }
    }
    
    const char *demangledName(bool realize = false);
    const char *nameForLogging();

    // May be unaligned depending on class's ivars.
    uint32_t unalignedInstanceStart() {
        assert(isRealized());
        return data()->ro->instanceStart;
    }

    // Class's instance start rounded up to a pointer-size boundary.
    // This is used for ARC layout bitmaps.
    uint32_t alignedInstanceStart() {
        return word_align(unalignedInstanceStart());
    }

    // May be unaligned depending on class's ivars.
    uint32_t unalignedInstanceSize() {
        assert(isRealized());
        return data()->ro->instanceSize;
    }

    // Class's ivar size rounded up to a pointer-size boundary.
    uint32_t alignedInstanceSize() {
        return word_align(unalignedInstanceSize());
    }

    size_t instanceSize(size_t extraBytes) {
        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;
        return size;
    }

    void setInstanceSize(uint32_t newSize) {
        assert(isRealized());
        if (newSize != data()->ro->instanceSize) {
            assert(data()->flags & RW_COPIED_RO);
            *const_cast<uint32_t *>(&data()->ro->instanceSize) = newSize;
        }
        bits.setFastInstanceSize(newSize);
    }

    void chooseClassArrayIndex();

    void setClassArrayIndex(unsigned Idx) {
        bits.setClassArrayIndex(Idx);
    }

    unsigned classArrayIndex() {
        return bits.classArrayIndex();
    }

};

这个例子只是为了检验大家对于基本的C++知识的掌握程度,所以大家如果看不懂也没关系。大家想一想,这个例子用到了上面提到的哪些知识点。

总结

总的来说,这些内容是最基本的语法知识,希望可以帮你入门 C++。

===== 我是有底线的 ======
喜欢我的文章,欢迎关注我Jack_deng
,我会不定期的分享一些开发相关的文章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343