《企业级ios应用开发实战》一3.1 Objective-C的C语言特性

简介: 本节书摘来自华章出版社《企业级ios应用开发实战》一 书中的第3章,第3.1节,作者:杨宏焱,更多章节内容可以访问云栖社区“华章计算机”公众号查看。

3.1 Objective-C的C语言特性

Objective-C源自C,它是C语言特性和Smalltalk语法的集合。从20世纪80年代开始,Objective-C对C语言进行了大量的扩展,直至30年后的今天,Objective-C已经发展成为当下最流行的编程语言之一。
Objective-C全面支持C99标准。对于C这种程序员早已熟知熟悉的经典语言,作者在此并不准备多做介绍,你可以阅读大学计算机课程中使用的C语言教程,或者Dave Mark编写的经典专著《Learn C on the Mac》。本章只简单介绍Objective-C中一些主要的C语言特性,比如Objective-C支持的C语言数据类型、常量/变量和函数、条件和循环等,以及Objective-C的一些非C语言特性(面向对象特性)。

3.1.1 一个简单的 Hello World

了解一种新语言是从一个最简单的程序开始,Hello World程序无疑是最简单的,让我们从它开始。
打开Xcode,使用菜单“New Project”打开新建项目向导,在项目模板中选择“Mac OS X → Application → Command Line Utility → Foundation Tool”,并可以将项目命名为HelloWorld。为了能更清晰地了解Objective-C这门语言,我们特意选择了最简单的命令行项目,这种类型的项目并不能在iPhone上运行,它是在Mac命令终端环境中运行的。要知道,原本Objective-C就是用来设计Mac程序的,后来才扩展使用到iPhone程序。因此,我们避免了使用Cocoa框架带来的一些复杂性。
Xcode为我们生成了一系列文件,打开其中HelloWorld.m,查看Xcode自动产生的代码如下所示:

#import <Foundation/Foundation.h>
int main (int argc, const char  argv[]) {
    pool = [[NSAutoreleasePool alloc] init];
    //插入代码
    NSLog(@"Hello, World!");
    [pool drain];
    return 0;
}

整个代码包括注释不超过10行。然而对于从未接触过这门语言的人来说,可以从中学习到许多东西。

3.1.2 Objetive-C是另一种C

C语言作为传统的计算机教学语言,对许多接受过大学理工科教育的人来说,并不陌生。从上面的代码中,我们看到了许多C语言的特征,比如语句用分号结束,程序开头同样需要导入头文件,还有一模一样的main函数作为程序的入口,等等。实际上,Objective-C完全兼容标准C,它同样采用gcc编译器,可以在Objective-C程序中随意嵌入C代码。对于许多推崇标准C的iPhone程序员来说,他们更倾向于使用C代码而不是Objective-C代码来写iOS应用程序。
因此,虽然Hello World是一个Objective-C程序,但是我们却可以把它变成C程序,将代码修改为:

#include <stdio.h>
int main (int argc, const char  argv[]) {
   printf("Hello World");
   return 0;
}

编译运行程序,黑黑的控制台窗口输出了同样的文字内容。两段代码的区别仅在于:HelloWorld的C语言版没有使用任何框架,而Objective-C版本则使用了Foundation框架。
同C一样,Objective-C源代码也分成了两部分:接口和实现。Objective-C的接口文件采用.h作为后缀名;实现文件的后缀则采用.m,而不是.c(C)或者.cc(C++)。

3.1.3 数据类型

在第2章的Cocoa框架介绍中,提到过Foundation框架是Cocoa框架的基本框架之一(见图2-2 Cocoa框架)。而Foundation框架中就定义了数量众多的数据类型和底层类,例如NSString、NSArray、NSEnumerator和NSNumber。我们不可能全部介绍它们,整个Foundation框架定义了上百个类。但我们会讨论其中最有用的一些。
前面讨论的HelloWorld程序是很重要的,起码对于这一章来说。因为我们后面学习的代码都会基于这个main函数,只需在这个函数中加入少量的代码,程序就可以运行。
1.C基本数据类型
由于Objective-C和C兼容,C语言中的5种基本类型:char、int、float、double、void在Objective-C中仍然可用,并且这些基本类型可以使用的修饰符unsigned、signed、short、long进行修饰。
2.C复合类型
C语言中的复合类型:数组、指针、结构、联合、枚举仍然得到了支持。此外,Objective-C中特有的几种复合类型,就是id、SEL、nil和NULL。
id是指向任意对象的指针,类似C语言中的void*。这里的任意对象是指Objective-C中任何继承自NSObject类的实例。在Cocoa中,NSObject是所有类的根类。所以,可以用id指向任何类型的Objective-C对象。
选择器SEL实际上被定义为const char*,在Objective-C中,用它来指向任何方法的定义,等同于C语言中的函数指针。要创建SEL类型有两种方式,一种是使用@selector()关键字,并在括号中传递一个方法签名作为参数,通过这种方式你可以调用一个Objective-C对象的指定方法:

[object performSelector:@selecotr(doSomething)];

另一种就是通过NSSelectorFromString()函数,把方法签名以字符串的方式作为参数传递,这种方式类似于Objective-C的反射(关于选择器,后面还会介绍)。
nil和NULL都代表了空指针。nil用于Objective-C对象,而NULL则用于指针类型,并且二者不可互换。任何Objective-C对象都是NSObject类的子类,它们都可以用id来指向,因此可以使用nil指针来初始化为空。而C指针类型指向的数据是结构体,而不是对象,因此不能用nil指针初始化为空,但可以用NULL指针初始化为空。此外,NSNull用于指向集合对象,表示集合为空,没有任何有效的对象。
3.布尔类型
由于C没有布尔类型,你可以使用C++中的BOOL类型。但Objective-C提供了自己的布尔类型BOOL,它提供了两个表示真和假的名义值:YES和NO,类似于True和False。让人奇怪的是,它虽然属于Cocoa,却没有使用NS作为前缀(如后所述)。
然而使用BOOL类型时要小心,否则可能会导致出人意料的事情发生。
因为BOOL的定义其实是带符号字符,占1个字节的存储空间。我们实际上可以把一个字节的数据放到BOOL中去,如果把一个超过1字节的整数(如short或int)放进去,那么只有低位字节会被放进BOOL中去。在其他语言中,0定义为假,非0定义为真。而在Objective-C中不同,1定义为YES,0定义为NO。0和1以外的数据即不是YES,也不是NO。例如,我们习惯于这样的语句:

if(isNotEquals(a,b)==YES){// 如果a,b不相等
    ...
    }else{// 如果a,b相等
    ...
} 

isNotEquals (a,b)是一个函数,无论它的返回结果是什么,在if的条件表达式中,它首先会被转换为BOOL类型。但是即使isNotEquals (a,b)函数真的被定义为BOOL类型,它也不一定只返回YES和NO(1和0)。原因如前面所述,如果在isNotEquals函数中返回其他类型,比如int,仍然可以正常转换为BOOL,Objective-C并不会检查出错误。实际上,在isNotEquals函数中很可能是这样返回的:

return a-b;

如果a和b相等,那么返回的实际是NO,即0。如果a为4,b为3,则返回的是1,即YES。这都没有什么问题。但如果a为5,b为2,那么实际上返回的这个布尔值即不是YES,也不是NO,而是整型3。那么接下来的if语句中,3自然不等于YES(YES=1),你会发现程序的结果是a与b相等。问题是,你觉得5和2应该相等吗?
解决问题的方式有两种,要么你要确保每一个BOOL值返回的除了YES和NO外不能有其他值,比如上面的返回语句应该修改为:

Return a-b!=0;

要么,在条件表达式中,永远只和NO进行比较,而不要和YES进行比较,比如:

If(isNotEquals(a,b)==NO){// 如果a,b相等
    ...
    }else{// 如果a,b不相等
    ...
}

喜欢使用哪种方式,完全由你个人的喜好来定。
4.NS结构体
Cocoa的Foundation框架中定义了大量的结构体,如NSRange、NSPoint、NSSize等,这些结构体统一用NS作为前缀,在后面的章节中你还会见到一些其他的NS结构体。
下面介绍一些有用的NS结构体。
(1)NSRange
NSRange的定义是:

typedef struct _NSRange{
         unsigned int location;
         unsigned int length;
}NSRange;

它有两个有用的成员,location表示范围从哪个数字开始,默认为0,length表示范围的大小,默认值是NSNotFound,表示不存在的范围。比如:

NSRange range={3,6};

这个语句用C语言的风格创建了一个NSRange变量,这个NSRange表示了从3开始、总长度为6的数值范围。
或者用给结构成员赋值的方式初始化NSRange:

NSRange range;
range.location=3;
range.length=6;

但是Cocoa还为所有的结构提供了便利函数NSMakeXXX:

NSRange range=NSMakeRange(3,6);

可以很方便地创建一个NSRange。
(2)NSPoint、NSSize和NSRect
结构体可能是最方便表述几何图形的数据类型了,比如NSPoint、NSSize和NSRect。这3个结构体构成了一个二维平面中最基本的数据类型。
NSPoint表示物体在二维坐标中的x位置和y位置:

typedef struct _NSPoint{
float x;
float y;
}NSPoint;

NSSize用于表示一个形状的大小,任何占据一定二维空间的形状,总是离不开宽和高这两个属性:

typedef struct _NSSize{
        float width;
        float height;
}NSSize;

NSRect用于表示一个矩形,它由矩形的坐标位置和大小共同构成,很显然位置可以用NSPoint表示,而大小就用NSSize表示:

typedef struct _NSRect{
         NSPoint origin;
         NSSize size;
}NSRect;

5.NS类
既然Objective-C是“面向对象的C”,那么除了对C的基本数据类型进行全面支持外,当然也对这些类型进行了面向对象的封装和扩展。Cocoa框架把所有的类都加了“NS”前缀——来自于Cocoa前身NextSTEP的缩写,以显示和Core Foundation类的区别(以CF为前缀),比如NSString、NSNumber、NSArray、NSDictionary。本书中会大量使用这些NS类。
与C语言寥寥数种的基本数据类型不同,NS类家族可谓种类繁多,本书不可能逐一讨论。在此,我们将只介绍几种最常用的NS类。
(1)NSString和NSMutableString
在HelloWorld程序中有一个打印语句:
NSLog(@"Hello, World!");
其中@"Hello, World!"就是一个字符串,即NSString。注意,使用@和双引号来括住字符串,而不是像C++一样只使用双引号来括住字符串,这样就是一个NSString类型的字符串常量。
NSString有许多封装的特性,比如统计字符串的长度,将自身与其他字符串进行比较,方便地将自身转换为整数或浮点数。
出于性能上的考虑,你不能改变NSString的值,比如在NSString对象中临时插入其他字符或者删除某些内容,这跟Java语言中的String类型是一样的。当然当你确实需要这样做的时候,可以使用NSString的可变版本,NSMutableString。后者和前者几乎一模一样,除了可以对其储存的字符串进行改变操作。
创建一个NSMutableString可以用以下代码:

NSMutableString mutableStr=[NSMutableString stringWithString:@”Following me”];

现在,我们可以任意替换其中的字符内容:

NSRange range={10,2};
[mutableStr replaceCharactersInRange:range withString:@”you”];

于是,这个字符串的内容变成了“Following you”。如果想把这个字符串中的“me”删除,可以用NSString的deleteCharatersInRange方法:

[ mutableStr deleteCharactersInRange:range];

当然NSMutableString类最常用的方法还是appendString方法,在字符串的末尾加上另一个字符串:

-(void) appendString:(NSString)nsstring;

(2)NSNumber
NSNumber用于处理所有与数字相关的数据类型,从整型到浮点型的数据,也包括前面提到的BOOL类型。我们可以通过一系列的方法把基本数据类型包装为NSNumber:

+(NSNumber) numberWithChar:(char)value;
+(NSNumber) numberWithInt:(int)value;
+(NSNumber) numberWithFloat:(float)value;
+(NSNumber) numberWithBool:(BOOL)value;

类似的方法还有很多,包括各种unsigned和long型数据的版本,我们不能一一列举。创建了NSNumber对象之后,我们就可以把它存储到NSArray和NSDictionary集合中去,而此前这两种集合对象是无法存放基本类型数据的。当我们从集合中把NSNumber取出来后,又可以通过以下方法将其还原为原来的基本类型:

-(char)charValue;
-(int)intValue;
-(float)floatValue;
-(BOOL)boolValue;
...

很显然,这些方法是和创建NSNumber的方法一一对应的。
(3)NSArray和NSMutableArray
NSArray与C语言中的数组是类似的,但是它还封装了很多C数组不具备的特性。比如,它不需要像数组一样,随时随地都需要程序员对数组下标越界进行检查和判断。它其实更像Java语言中的ArrayList。
但是正如前面提到过的,NSArray只能存储Objective-C对象,而不能存放基本数据类型,如int、float、char、struct、enum等。
提示:NSDictionary也有同样的限制。
此外,NSArray也不能存放空值nil,因为nil用作NSArray的结束符。NSArray的最后一个元素永远是nil对象。
NSArray有一个便利的创建方法,可以一次性创建一个包括了许多元素的NSArray对象,如下所示:

NSArray array=[NSArray arrayWithObjects:@”one”,@”two”,@”three”,nil];

这个方法提供了数目不定的参数作为其初始的创建时就包含的元素。牢记前面的两个限制:1)其元素只能是对象;2)最终要以一个nil结束。
要获得NSArray的元素数目很容易,访问其count属性即可。
使用以下方法访问指定索引的对象:

-(id)objectAtIndex:(unsigned int)index;

如果索引大于数组元素中的个数,Cocoa会抛出NSRangeException异常。
然而,NSArray是不可变的对象数组,一旦创建就不可能增加和删除其中包含的对象——当然对象自身还是可以改变的。但是数组的元素个数不会改变,其中的元素也不能替换。
如果你需要一个在创建后仍然可以任意增加和删除元素的数组,可以使用NSArray的可变版本NSMutableArray。
通过类方法arrayWithCapacity和arrayWithArray来创建一个可变数组:

+(id)arrayWithCapacity:(unsigned)numItems;
+(id)arrayWithArray:(NSArray)array;
...

创建NSMutableArray的类方法有很多,但最常见的是这两个。创建之后就可以用addObject方法从数组末尾添加对象:

-(void)addObject:(id)anObject;

同样,获取某个索引处的对象使用继承自NSArray的objectAtIndex方法。
NSMutableArray还可以删除某个索引处的对象:

-(void)removeObjectAtIndex:(unsigned)index;

同C数组一样,NSArray和NSMutableArray也是从0开始索引的。此外,可变数组可以在特定索引处插入/替换某个对象,进行数组排序等。
(4)NSDictionary和NSMutableDictionary
字典也称散列表(Java语言中)或者关联数组(C语言中),在Objective-C中用NSDictionary表示字典。创建一个NSDictionary通常是使用类方法:

+(id)dictionaryWithObjectsAndKeys:(id)firstObject,...;

…代表了可变参数数组,一系列个数不能确定的同类型参数的集合。跟NSArray一样,一旦创建并初始化了一个NSDictionary,其包含的对象就不能改变:

NSDictionary d=[NSDictionary dictionaryWithObjectsAndKeys:
        obj1,@”first object”,obj2,@”second object”,nil];

同样,在字典的最后以nil标志结束。在初始化方法的参数列表中,除了nil外,所有的参数都是成对的,比如:对象obj1和字符串“fist object”是一对,对象obj2和字符串“second object”是一对。每对都代表字典集合中所存储的一个对象和它对应的索引键,当然,索引键在字典中是唯一的。我们可以通过索引键来检索字典中存放的对象:

-(id)objectForKey:(id)aKey;

这样,通过键“first object”查找字典d中存储的对象obj1:

id something=[d objectForKey:@”first object”];

NSMutableDictionary是可变字典。假设需要在字典d中增加新的对象,我们可以创建NSMutableDictionary:

NSMutableDictionary mutableD=[NSMutableDictionary 
        dictionaryWithDictionary:d];
[d setObject:obj3 forKey:@”third object”];

如果新加入的对象所采用的索引键与字典中已存在的键相同,则原来的对象会被覆盖。这就是为什么方法名采用了setObject而不是addObject的原因。
要删除字典中的对象,可采用如下方法:

-(void)removeObjectForKey:(id)aKey;

(5)NSDate和NSDateFormatter
日期的显示非常重要,我们最常用到日期的地方就是获取当前时间,这也是为什么可以使用iPhone手机用来取代手表,并用它来做烦人的闹钟。
NSDate的date类方法创建了一个临时的NSDate对象,它代表了当前时间:

NSLog(@”当前时间:%@”,[NSDate date]);

这会在控制台输出如下内容:
当前时间:2011-7-30 15:37:02 -0570
NSDateFormatter类用于和NSDate配合,将各种格式的字符串转换为NSDate或从NSDate转换为指定格式的文本。
日期转换为文本:

NSDateFormatter df=[[NSDateFormatter alloc]init];
[df setDateFormat:@”yyyy/MM/dd”];
NSLog(@”%d”,[df stringFromDate:[NSDate date]];

从文本转换为日期使用:

NSDate date=[df dateFromeString:@”2011-7-30”];

3.1.4 常量、变量和宏

Objective-C的变量声明和C完全相同:类型<变量名>。如果是声明对象的话,需要使用指针符号“*”,例如前面曾经见到过的许多代码:

NSDate date;
NSString string;
NSArray array;
...

常量可以使用static关键字声明:

static NSString string=@”abc”;

也可以通过预定义宏来声明:

#define PI 3.14
#define PATH @”images”;
3.1.5  #include和#import

C和Objective-C都使用头文件来声明类型、结构体、符号常量和函数原型等,因此需要通知编译器去头文件中查找相应的定义。为了实现这个目的,你可以使用#include和#import语句,二者的区别仅在于#import是GCC编译器提供的,它可保证同一个头文件无论被包含多少次始终只导入一次。由于这个特性,你完全可以在Objective-C程序中用#import取代#include。
HelloWorld程序中的#import 文件语句中的第一个Foundation并不代表目录,而是指我们在第2章中提到过的Foundation框架。
提示:包含头文件时,框架和库中的头文件用尖括号引住,项目中的头文件用双引号引住。如#import 和 #import“ViewController.h”。

3.1.6 函数

Objective-C支持函数的声明和定义。但函数不是类的一部分。函数可以在.h头文件中进行声明,也可以直接在.m文件中定义(调用前)。关于函数的声明和定义,请遵循标准C的语法规范。

3.1.7 分支和循环

Objective-C支持C的所有分支语句和循环语句:switch语句、判断、while循环、do…while循环和for循环。除此之外,从Objective-C 2.0开始,支持快速迭代,类似VB 中的For…each语法。使用快速迭代,可以简单地遍历数组元素:

For(NSString each in array){
        NSLog(@”%@”,each);
}

其中,array是一个字符串数组。显然,这样的语法显得更加简洁明快。快速迭代不仅仅可用于数组,也可以用于遍历任何集合对象。

相关文章
|
1月前
|
C语言
在C语言中数组作为函数参数的应用与示例
在C语言中数组作为函数参数的应用与示例
15 0
|
1月前
|
算法 C语言
在C语言中函数的递归调用及应用示例
在C语言中函数的递归调用及应用示例
15 1
|
1月前
|
C语言
在C语言中多维数组名作为函数参数的应用与示例
在C语言中多维数组名作为函数参数的应用与示例
12 0
|
1月前
|
存储 程序员 C语言
深入探讨C语言中的字符型数据类型及其应用
深入探讨C语言中的字符型数据类型及其应用
13 0
|
3天前
|
存储 编解码 JSON
利用SwiftUI构建高效iOS天气应用
【4月更文挑战第21天】 在本文中,我们将深入探讨如何运用SwiftUI框架打造一个响应迅速且用户友好的iOS天气应用程序。我们将重点放在利用SwiftUI的声明式语法简化界面开发,并通过结合Core Location和Networking APIs实现实时天气数据的获取与展示。文章将详细阐述整个开发过程,包括API集成、数据模型设计、用户界面布局以及动态适配不同屏幕尺寸的策略。
|
22天前
|
C语言 索引
基于C语言的函数指针应用-消息命令处理框架
基于C语言的函数指针应用-消息命令处理框架
11 0
|
23天前
|
Linux 测试技术 C语言
【Linux】应用编程之C语言文件操作
【Linux】应用编程之C语言文件操作
|
1月前
|
安全 数据安全/隐私保护 虚拟化
iOS应用加固方案解析:ipa加固安全技术全面评测
iOS应用加固方案解析:ipa加固安全技术全面评测
37 3
|
1月前
|
C语言
在C语言中内部函数的应用与示例
在C语言中内部函数的应用与示例
11 2
|
1月前
|
C语言
在C语言中函数的返回值及其应用示例
在C语言中函数的返回值及其应用示例
19 2