这一节中会详细介绍下ObjectiveC中的复杂数据类型,这些类型不太是太归类。但非常有用,有的用于定义变量、有的则是专门用于方法的返回值。
常用的大概有如下这些:
以上这些特殊的数据类型都可用于变量、方法返回值、方法参数使用,但是不同种类还是有些倾向的用法。
复杂数据类型
数组
数组同其它语言基本一样,也是通过下标等读写,不同的就是定义时如果是个复杂对象则用*定义,字符串数组只能用,数组的语言格式为:
Type name[length] = {};
Type *name[length] = {};
在ObjeC中还有一个名为NSArray的也称为数组的对象,它和本节所讨论的数组有些区别:
- 数组的大小是不可变的,即一旦实例化后,其length不能动态改变,而NSArray则可以;
- NSArray中的元素只能是对象,不能是基本数据类型,如果制定集合类的基本元素集合只能用数组;
数组初始化
一般建议要指定一个大小,也可以不指定。如果不指定的话系统会按照当前实例化后的元素个数自动设置。
//Fraction为自定义的一个类
Fraction *fra[100];
//不指定大小,默认为值的长度;也可以指定大小,多余的全部采用默认值
int a[] = {1,2,4};
int a[12] = {1,2,4};
// \0是一个特殊标识,如果字符数组以这种方式结尾,则可以通过NSLog(@"\s", letters)方式打印,否则只能遍历打印
char letters[5] = {'a', 'b', 'c'}
char letters[5] = {'a', 'b', 'c', '\0'}
数组的遍历
int a[3] = {1,2,4};
NSLog(@"%i", a[0]);
for(int i=0; i<3; i++){
NSLog(@"%i", a[i]);
}
多维数组
int a[4][5] = { //4行5列
{1,2,3,4,5}
.....
}
字符串数组
NSString *strs[2] ={@"a", @"b"};
for(int i=0; i<3; i++){
NSLog(@"%@", strs[i]);
}
在ObjC中建议用NSArray来充当数组功能,它允许大小可变,而数组的大小不可变,因为数组的大小是透明不可变的,所以在ObjC中并没有提供获取数组大小的类似的函数;
枚举
枚举就是把一系列的值指给一个变量,在OjbC中用关键字enum开头,另外需要注意的是在ObjectiveC中也有一个名为NSEnumerator的枚举对象,这两个一个是简单关键字,另一个是对象类,但在使用上区别不大,NSEnumerator会在Function框架时再详细说明,声明enum枚举变量的语法格式如下:
基本语法
enum enumName {enumValue1, ..., enumValueN};
//---示例
enum flag {true, false}; //声明一个枚举类型,true默认值为0, false为1,依次顺延
enum flag startFlag, endFlag; //声明两个flag类型的变量startFlag和endFlag
startFlag = true;
endFlag = false;
指定枚举值
//up=0, down=1, left=10, right=11
enum direction {up, down, left=10, right};
下面是一些特殊的设置
//指定数据类型,unsigned short int 标识主要是为了类型检查
enum iphoneModels : unsigned short int { iphone2, iphone3};
//定义一个名为amouth的枚举类型,包含两个可选值
enum {january=1, february} amonth;
综合示例-在switch中的用法
enum month {january=1, february, march, ...., december};
enum month amonth; //声明了此枚举类型的一个变量
int days;
//从屏幕接收输入值
scanf("%i", &amonth);
switch(amonth){
case january:
days = 31;
break;
case march:
case november:
days=30;
break;
default:
days = 0;
break;
}
NSLog(@" days = %i", days); //输入1,输出days=31
综合示例2-定义复杂的结构体
下面这个例子,做为初学者看起来可能会有点蒙属于正常情况,因为像typedef, struct这些语法还都没有讲,此处也不会详细讲解这些内容,先简单描述一下。可学习完后续课程再回顾下这个例子:
- typedef:重命名,比如typedef a b;意思就是把a 重新命名为b,这样源码中会自动把a替换为b;
- struct:其实这是C语言的用法,在ObjC中也适用,它是把一组不可再分的属性统一起一个名字,这样结构可能更清晰。
#import <Foundation/Foundation.h>
//定义三种图形,每个带一个默认编号,比如circle=0,依次类推
typedef enum{
circle,
rectangle,
egg
}ShapeType;
// 定义图形的颜色
typedef enum{
red,
green,
yellow
}ShapeColor;
//定义图形的坐标和尺寸
typedef struct{
int x, y, width, height;
} ShapeRect;
//组合一个图形的描述:类型+颜色+坐标尺寸
typedef struct{
ShapeType type;
ShapeColor color;
ShapeRect bounds;
}Shape;
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!\n");
Shape shapes[3];
ShapeRect rect_0 = { 0, 0, 30, 30 };
shapes[0].type = circle;
shapes[0].color = red;
shapes[0].bounds = rect_0;
}
return 0;
}
id和动态类型绑定
id是一种通用的数据类型,它可以存储任何对象,也称为动态类型,内容为指向其数据结构的指针。它相当于java中的浮型的概念,比如 id graphicObject,graphicObject可以存储任意数据类型,id是多态和动态类型的基础,可以简单理解为id是一个指针,它可以指向任何对象。其主要特点就是运行时检查,加快了程序编译的速度也带来了运行时的风险。
//做为变量声明
id item;
//做为方法返回值和参数
- (id) register: (id) sender;
注意事项:
- id 不能使用.运算符,必须用[]的方式调用;
- 加快了程序编译的速度也带来了运行时的风险;
更多示例
id shape = [[Shape alloc] init]];//这里的Shape是一个自定义的类;
[shape draws]
id dataValue; //这里没有*号
Fraction *f1 = [[Fraction alloc] init];
Complex *c1 = [[Complex alloc] init];
dataValue = f1;
[dataValue showPage];
dataValue = c1;
[dataValue print];
instancetype
一般用于类的初始化函数的返回值,它表示从方法返回的数据类型与它的初始化类相同。是一种编译器辅助标识,比如NSString类的实现就是如下定义的,如下:
+ (instancetype)string;
+ (instancetype)stringWithString:(NSString *)string;
- (nullable instancetype)initWithUTF8String:(const char *)nullTerminatedCString;
在使用时,一般把可能会被覆盖的初始化函数用此标识来定义,这样可以保证初始化后的类实例的类似不会出现问题。比如下面就是一个比较好的设计模式,把真正实例化的代码放在此标识的函数中:
示例代码如下:
- (id) init1(){
[self initFinally: 0 age: 0];//委托给initFinally来实现
}
- (id) init2:(int) sex{
[self initFinally: sex age: 0];//委托给initFinally来实现
}
- (instancetype) initFinally:(int) sex age(int) age{
self.sex = sex;
self.age = age;
//设置真正的初始化代码;
}
特殊数据类型
Class
类对象的引用,指向其数据结构的指针,示例如下:
@interface Fraction : NSObject
@end
NSLog(@"%@", [Fraction class]); //~~Fraction
SEL
选标,编译器分配用来区分方法名的编码,其实就是方法的定义(方法名+参数),一般用于方法回调使用,在Function框架中会详细介绍其用法,下面给出一个简单的示例:
SEL action = @selector(setTo:over:); //为setTo方法生成一个SEL
Boolean boolean = [Fraction instanceMethodForSelector: action]; //验证类是否响应了setTo方法
id obj;
[obj performSelector: setTo:over:] //调用setTo:over:方法
IMP
函数指针,指向实现方法的指针,返回id;简单来说就是Objective-C 在运行时寻找匹配消息的IMP(implementation),给一个方法起个名字,实现动态调用,不用再使用[对象 message]的方式。
#import <Foundation/Foundation.h>
#import <objc/runtime.h> //一定要引入
@interface MyClass : NSObject
- (void)myMethod;
@end
@implementation MyClass
- (void)myMethod {
NSLog(@"myMethod called");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyClass *obj = [[MyClass alloc] init];
Method method = class_getInstanceMethod([MyClass class], @selector(myMethod));
IMP imp = method_getImplementation(method);
//转为指针
void (*func)(id, SEL) = (void *)imp;
// 调用MyClass.Mythod(),输出 myMethod called
func(obj, @selector(myMethod));
}
return 0;
}
空值的表示
nil
空的对象指针,返回(id)0。用来表示一个对象是空对象,即想要表示此对象不存在。给对象赋值时一般会使用object = nil,表示我想把这个对象释放掉;或者对象引用计数器为0了,系统将这块内存释放掉,这个时候这个对象被置为nil。
//nil是void*类型,可看作id类型的对象
NSLog(@"nil指向的对象:%d,nil自身的地址:%p",nil,nil);
//~~nil指向的对象:0,nil自身的地址:0x0
Nil
空的类指针,返回(Class)0,是用来表示一个类是空类。比如:Class myClass = Nil;。和nil没有明确的区分,也就是说凡是使用nil的地方都可以用Nil来代替,反之亦然。约定俗成地将nil表示一个空对象,Nil表示一个空类。
//NiL是void*类型,在OC中可看作id类型的对象(OC对象就是一个指针)
NSLog(@"NiL指向的对象:%d,Nil自身的地址:%p", Nil,Nil);
//~~NiL指向的对象:0,Nil自身的地址:0x0
NULL
空值,字面量,返回(void *) 0。NULL是在C/C++中的空指针,在C语言中,NULL是无类型的,只是一个宏,它代表空。C语言中,当我们使用完一个指针以后,通常会设置其指向NULL。如果没有设置,这个指针就成了所谓的野指针,然后其它地方不小心访问了这个指针是很容易造成非法访问的,常见的表现就是崩溃了。
既然Objective-C是基于C语言的面向对象语言,那么也会使用到C语言类型的指针,比如使用const char *类型,判断是否为空时,是使用p != NULL来判断的。
//NULL是void*类型,若在OC中可看作id类型的对象(OC对象就是一个指针)
NSLog(@"NULL指向的对象:%d,NULL自身的地址:%p",NULL,NULL);
//~~NULL指向的对象:0,NULL自身的地址:0x0
总结如下:
标识 | 值 | 含义 |
---|---|---|
NULL | (void *)0 | C 指针的字面空值 |
nil | (id)0 | Objective-C 对象的字面空值 |
Nil | (Class)0 | Objective-C 类的字面空值 |
NaN
NaN,是Not a Number的缩写。NaN 用于处理计算中出现的错误情况,这个方法其实是C语言的方法,用的不是太多。
float a = sqrt(2);
NSLog(@"is a number :%@", isnan(a));