IOS开发入门之写给 iOS 程序员看的 C++(1)
白羽 2018-11-23 来源 :网络 阅读 958 评论 0

摘要:本文将带你了解IOS开发入门写给 iOS 程序员看的 C++(1),希望本文对大家学IOS有所帮助。

    本文将带你了解IOS开发入门写给 iOS 程序员看的 C++(1),希望本文对大家学IOS有所帮助。



        

你是一个 Objective-C 方面的专家吧?你是否正在寻找下一个要学习的目标?如果是,那么这篇文章就是专门为你准备的了,它教你如何在 iOS 开发中使用 C++。

就像我后面将会提到的一样, Objective-C 可以和 C 和 C++ 代码无缝集成。因此,对于 iOS 开发者来说,学习一些 C++ 是有好处的,具体理由如下:

有时候,你需要在你的 app 中调用 C++ 写的库。 可以将 app 中的一部分代码用 C++ 来写,这样便于跨平台。 拥有使用其它语言的背景,有助于从本质上理解编程。

这篇文章是为具备 Objective-C 语言基础的 iOS 程序员而写的。本文假设你已经了解如何编写 Objective-C 代码并熟悉基本的 C 语言知识,比如类型、指针和函数。
准备好学一点 C++ 了吗?让我们开始吧!

开始: C++ 简介

C++ 和 Objective-C 拥有同样的血统:它们同样基于经典 C。也就是说,它们都是 C 语言的后代。而且,在两种语言中,你都可以使用 C 语言提供的功能。

如果你熟悉 Objective-C,也不难理解你所碰到的 C++ 代码。例如,两种语言中都有标量类型 int、float 和 char,它们的特性也完全相同。

Objective-C 和 C++ 都在 C 语言的基础上增加了面向对象的特性。如果你不熟悉“面向对象”,你只需要理解:数据都是通过对象来表示的,而对象是类的实例。实际上,C++ 最初被称作“有类的 C”,这说明让 C++ 面向对象具是一件顺理成章的事情。

你也许会问“它们有什么不一样吗?”。主要的区别是,二者实现面向对象的方式不同。在 C++ 中,许多实现依赖于编译时,而 O-C 更多的依赖于运行时。你也许用过了 O-C 的运行时特性比如方法混合。在 C++ 中这显然是不可能的。

O-C 中大量存在的自省和反射在 C++ 也不存在。在 O-C 中你可以调用实例的 class 方法,但在 C++ 中你无法获得一个 C++ 对象的 class。同样,在 C++ 也没有类似 isMemberOfClass 或者 isKindOfClass 的方法。

这里简单介绍了 C++ 的历史和它与 O-C 的重要区别。历史课就上到这里——接下来介绍 C++ 的语言特性!

C++ 类

对于面向对象的语言来说,你需要知道的第一件事就是如何定义类。在 O-C 中,你需要分别创建类的头文件和实现文件。在 C++ 中也是同样的,它们的语法也非常接近。

举个例子。这是一个 O-C 类:


   

// MyClass.h

 

#import <foundation foundation.h="">

 

@interface MyClass : NSObject

@end

 

// MyClass.m

 

#import “MyClass.h”

 

@implementation MyClass

@end</foundation>

   

如果你是一个熟练的 iOS 程序员,这些代码实在是太简单不过了。但如果用 C++ 来写这个类,则应当是:


   

// MyClass.h

 

class MyClass {

};

 

// MyClass.cpp

#include “MyClass.h”

 

/* Nothing else in here */

   

有几个地方不同。首先在 C++ 实现文件中是空的。因为你没有为这个类定义任何方法。对于一个空类,O-C 中需要写一个空的 @implemenation 和 @end 块,但 C++ 中却不需要。

在 O-C 中,每个类都需要从 NSObject 继承(直接或间接)。你可能想将你的类创建成一个根类,也就是不继承任何父类。但是除非你在运行时中这样做(仅仅是为了好玩),否则你不可能这样做。相反在 C++ 中,创建一个没有父类的类是非常普遍的做法,就如上面的代码所示。

另一个细微的差别是 #include 和 #import。O-C 添加了一个 #import 的预处理指令。在 C++ 中没有这个,因此只能使用标准的 C 语言中的 #include。O-C 的 #import 能够确保一个文件只会包含一次,但是在 C++ 中,你只能自己去手动检查了。

类的成员变量和函数

当然,除了仅仅是声明类本身外,我们还可以做更多的事情。就像在 O-C 中一样,在 C++ 中你可以为类实例成员变量和方法。在 C++ 中它们有另外一种叫法,分别叫做成员变量和成员函数。

 

注意:C++ 中并没有 “方法”一词的说法。注意两者的却别,在 O-C 中的方法是以发送消息的方式进行调用的,而函数是以静态 C 语言函数的方式进行调用。稍后,我会解释关于静态与动态的区别。

如何声明成员变量和成员函数?请看例子:


   

class MyClass {

    int x;

    int y;

    float z;

 

    void foo();

    void bar();

};

   

这里声明了 3 个成员变量和 2 个成员函数。但在 C++ 中还有更多讲究,你可以限制成员变量和成员函数的作用域,将它们声明称公共的或者是私有的。这样就可以限制哪些代码能够访问变量或函数。

例如:



   

class MyClass {

  public:

    int x;

    int y;

    void foo();

 

  private:

    float z;

    void bar();

}

   

这里,x、y 和 foo 是公共的。也就是说这些变量可以在 MyClass 类之外访问。而 z 和 bar 是私有的。也就是说它们只能被 MyClass 自身所使用。成员变量默认就是私有的。

这二者的区别在 O-C 中实例变量上也存在,但很少用到。此外,在 O-C 中不可能限制方法的作用域。哪怕你只在类的实现中定义了一个方法,不将它暴露在接口中,你仍然可以通过某种技术从外部访问这个方法。

在 O-C 中方法是共有还是私有只是一种约定。这就是为什么许多开发者在私有方法前加一个 p_ 前缀以示区别。而 C++ 不同,当你试图在类的外部访问私有方法时,编译器会报错。

那么类是如何使用的呢?和 O-C 非常类似。你可以创建一个实例:



   

MyClass m;

m.x = 10;

m.y = 20;

m.foo();

   

就是这样简单!这里我们创建了一个 MyClass 实例,设置 x 为 10,y 为 20,然后调用 foo 方法。

实现类的成员函数

你已经知道如何定义类的接口了,但如何实现它的函数?实际上也很简单。有几种方法。

第一种方法是在类文件——.cpp 文件里实现这个方法。例如:


// MyClass.h

class MyClass {

    int x;

    int y;

    void foo();

};

 

// MyClass.cpp

#include “MyClass.h”

 

MyClass::foo() {

   // Do something

}

   

这是第一种方法。和 O-C 中非常类似。注意 MyClass:: 的使用;这表明你将 foo() 函数做为 MyClass 类的一个部分来实现。

第二种则是 O-C 无法做到的方法。在 C++ 中,你可以直接在头文件中实现方法:


   

// MyClass.h

class MyClass {

    int x;

    int y;

    void foo() {

        // Do something

    }

};

   

如果你只会 O-C,这种方法看起来有点别扭。它确实有点别扭,但也很有用。当用这种方式声明函数时,编译器能够进行 inlining 优化。也就是说当函数被调用时,不用跳到新的代码块,函数的完整代码会被编译到调用地址。

在使代码变得更快的同时,inlining 还会导致编译后的代码膨大,因为函数调用的次数越多,同样的二进制代码重复的次数也就越多。如果这个函数很大,或者调用的次数非常多,这会导致二进制的尺寸明显增加。也会导致性能下降,因为能够放进缓存中的代码更少,意味着缓存命中率下降。

这里的仅仅是为了演示 C++ 拥有更大的灵活性。作为开发者,你应该理解每种做法的优劣并做出决定。当然,要使用哪一种方法,唯一的标准应根据 instrument 的结果而定!

命名空间

上面的代码介绍了几个你从来没见过的语法——比如双冒号 ::。它表示 C++ 中作用域的概念,上面代码告诉编译器应该在哪里查找到 foo 函数。

另一个使用双冒号的地方是命名空间。命名空间是一种分离代码的方式,它减少了命名冲突的出现。

例如,你实现了一个类叫做 Person,但有一个第三方的库可能也实现了一个同名的类。但是,在你编写 C++ 代码时,你一般会将自己的代码放在一个命名空间,这样命名冲突就不会出现。

命名空间的使用很容易,只需要将每样东西都用命名空间包裹起来,例如:



   

namespace MyNamespace {

    class Person { … };

}

 

namespace LibraryNamespace {

    class Person { … };

}

   

现在,当使用到 Person 类的时候,你可以用双冒号来区分,例如:


   

MyNamespace::Person pOne;

LibraryNamespace::Person pTwo;

   

很简单吧!?

在 O-C 中没有命名空间的概念,你只能在类前面加上一个前缀…你已经在你的类中使用了前缀了?:] 如果你还没有这样做,那么最好现在去做!

 

注意:关于 O-C 有人提过增加命名空间的建议。其中一个在这里可以看到。我不知道 O-C 最终会不会支持命名空间,但我真的希望有这么一天!

内存管理

噢,不… 没那么可怕,内存管理在任何语言中都是必须学习的重中之重。Java 完全依靠垃圾回收器完成这个工作。在 O-C 中你必须学习关于引用计数和 ARC 规则。在 C++ 中… 好吧,C++ 就是一个怪胎。

首先,要理解 C++ 中的内存管理必须先理解栈和堆。如果你已经知道这两个概念,我建议你再看一下;你可能会重新学到点什么。

栈是一块 app 运行时能够使用的内存。它有固定大小,能被应用程序的代码用来存放数据。栈通过出栈/入栈进行工作,当一个函数执行时,它将数据压入栈中,当函数执行完毕,它必须弹出同样的数据。因此,栈不会随时间运行增长。

堆也是程序运行中使用的内存块。它的大小不是固定的,并随着程序的运行而增长。程序用堆存储函数以外的数据。大数据通常会用堆来存储,因为放到栈中会导致堆栈溢出——记住,栈是固定大小。

上面是关于栈和堆的最基本的概念;让我们看几个使用二者的 C 语言例子:


   

int stackInt = 5;

int *heapInt = malloc(sizeof(int));

*heapInt = 5;

free(heapInt);

   

其中,stackInt 使用栈空间,当函数返回之后,这块存放有值 “5” 的内存自动被释放。

但是,heapInt 使用堆空间。malloc 方法负责分配空间,以便能够存下一个 int 值。因为堆必须由你自己管理,因此在使用完数据之后必须调用 free 函数释放它,以防止内存泄漏。

在 O-C 中,你只能在堆上创建对象,如果你试图在栈上创建对象会导致一个编译错误。这是不允许的。

例如:


   

NSString stackString;

// Untitled 32.m:5:18: error: interface type cannot be statically allocated

//         NSString stackString;

//                  ^

//                  *

// 1 error generated.

   

这就是为什么在 O-C 代码中到处都是星号的缘故;所有的对象都是创建在堆里,你通过指针来引用这些对象。O-C 通过这种方法进行内存的管理。引用计数和 O-C 绑定得非常紧密;对象必须放在堆中,这样它们的生命周期才能被严格控制。

在 C++ 中,由你来决定数据放在堆中还是栈中;这个选择权赋给了开发者。因此,在 C++ 中你必须自己管理好内存。如果将数据放到栈中,内存是自动管理的,但如果你使用了堆,你必须自己管理内存——否则到处都会有内存泄漏的风险。

C++ 的 new 和 delete

C++ 中有几个用于堆中对象的内存管理的关键字,它们用于在堆上创建和摧毁对象。

创建对象:


Person *person = new Person();

   

当对象不再需要时,你可以这样摧毁它:


   

delete person;

   

实际在 C++ 中,它们甚至能够在标量类型上使用:



   

int *x = new int();

*x = 5;

delete x;

   

你可以把它们等同于 O-C 中的对象初始化和释放。在 C++ 中的 new Person() 就等于 O-C 中的 [[Person alloc]init]。

在 O-C 中没有和 delete 相同的功能。相信你知道,在 O-C 中有 dealloc 的概念,当一个 O-C 对象的引用计数等于 0 时,运行时会自动将对象 dealloc。但是,C++ 不会为你进行引用计数。你有义务在使用完对象之后 delete 这个对象。

现在你对 C++ 的内存管理有点概念了;C++ 的内存管理要比 O-C 复杂得多。你真的需要考虑到底发生了什么,以及跟踪你创建的所有对象。

访问栈和堆

如你所见,在 C++ 中,对象既可以在栈中创建也可以在堆中创建。但二者有一个细微和重要的区别:每一种方式创建的对象,在访问成员变量和成员函数的方法上有些许的不同。

当使用栈对象时,你需要使用点 . 操作符。而使用堆对象时,你需要使用箭头 -> 操作符,例如:


   

Person stackPerson;

stackPerson.name = “Bob Smith”; ///< Setting a member variable

stackPerson.doSomething(); ///< Calling a member function

 

Person *heapPerson = new Person();

heapPerson->name = “Bob Smith”; ///< Setting a member variable

heapPerson->doSomething(); ///< Calling a member function

   

这种区别很微妙,但非常重要。

在指针上使用了箭头操作符,这和 O-C 中的 self 指针是一回事,在类成员函数访问当前对象时会用到箭头操作符。

下面是箭头操作符的 C++ 例子:


   

Person::doSomething() {

    this->doSomethingElse();

}

   

在 C++ 中这会有些问题。在 O-C 中,如果你在空指针上调用一个方法,不会有任何问题:


   

myPerson = nil;

[myPerson doSomething]; // does nothing

   

但是在 C++ 中,如果试图在空指针上调用方法或者访问实例变量,app 会崩溃:


   

myPerson = NULL;

myPerson->doSomething(); // crash!

   

因此,在 C++ 中,你必须非常小心,千万不要在 NULL 指针上进行任何操作。

引用

当你将一个对象传递给函数时,你传递的是这个对象的拷贝,而不是对象自身。例如:


   

void changeValue(int x) {

    x = 5;

}

 

// …

 

int x = 1;

changeValue(x);

// x still equals 1

   

这很简单,不值一提。但当你用一个对象作为参数传递给这个函数时,会发生什么呢?


   

class Foo {

  public:

    int x;

};

 

void changeValue(Foo foo) {

    foo.x = 5;

<div class="lin    

   

本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标移动开发之IOS频道!


本文由 @白羽 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程