objective-c 语法快速过(4)
oc 里的字符串
字符串的快速创建(最简单的方法)
NSStirng *str = @“Hello”;//oc的字符串都是@“”形式的
oc的字符串也是类的对象,是NSString类的对象,创建没有那么麻烦不用[ ],使用面向对象的思想来操纵字符串。
char *name = "xxxxx";//c风格字符串
oc使用 %@ 输出字符串,不是%s
NSString *name = @”dashuai”;
NSLog(@“我的名字是%@”, name);
c 风格字符串输出解析的用法是%s
NSLog(@"%s", name);//%s是c风格的格式化解析
OC字符串的另一方式,Foundation 框架里的 NSString 类
NSString *newStr = [NSString stringWithFormat:@"My age is %d and no is %d and name is %@", age, no, name]; NSLog(@"---- %ld", [newStr length]);
length方法,返回unsigned long类型
NSString *name = @"哈哈达到"; // length方法算的是字数总数,包含的文字的数目!不是求字符个数!! int size = [name length];
不懂就去查询文档!百度!
// main.m #import <Foundation/Foundation.h> /* NSString : 不可变字符串 NSMutableString : 可变字符串 */ int main() { NSMutableString *s1 = [NSMutableString stringWithFormat:@"my age is 10"]; // 拼接内容到s1的后面 [s1 appendString:@" 11 12"]; // 获取is的范围 NSRange range = [s1 rangeOfString:@"is"]; [s1 deleteCharactersInRange:range]; NSString *s2 = [NSString stringWithFormat:@"age is 10"]; //新建字符串,拼接好的字符串到新建的字符串里,但是原来的字符串没有变!两个方法不一样。 NSString *s3 = [s2 stringByAppendingString:@" 11 12"]; NSLog(@"s1=%@, s2=%@", s1, s2); return 0; } void stringExport() { // 字符串的导出 [@"Jack\nJack" writeToFile:@"/Users/apple/Desktop/my.txt" atomically:YES encoding:NSUTF8StringEncoding error:nil]; NSString *str = @"4234234"; NSURL *url = [NSURL fileURLWithPath:@"/Users/apple/Desktop/my2.txt"]; [str writeToURL:url atomically:YES encoding:NSUTF8StringEncoding error:nil]; } void stringCreate() { /* 1.字符串的创建 */ NSString *s1 = @"jack"; //NSString *s2 = [[NSString alloc] initWithString:@"jack"];这种方法不推荐! NSString *s3 = [[NSString alloc] initWithFormat:@"age is %d", 10]; // C字符串 --> OC字符串 NSString *s4 = [[NSString alloc] initWithUTF8String:"jack"]; // OC字符串 --> C字符串 const char *cs = [s4 UTF8String]; // NSUTF8StringEncoding 用到中文就可以用这种编码 NSString *s5 = [[NSString alloc] initWithContentsOfFile:@"/Users/apple/Desktop/1.txt" encoding:NSUTF8StringEncoding error:nil]; // URL : 资源路径 // 协议头://路径 // 本地文件 file:// // ftp:// // 网页资源 http://weibo.com/a.png // http://www.baidu.com // NSURL *url = [[NSURL alloc] initWithString:@"file:///Users/apple/Desktop/1.txt"]; NSURL *url = [NSURL fileURLWithPath:@"/Users/apple/Desktop/1.txt"]; NSString *s6 = [[NSString alloc] initWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil]; NSLog(@"s6=\n%@", s6); /* 一般都会有一个类方法跟对象方法配对 [NSURL URLWithString:<#(NSString *)#>]; [NSString stringWithFormat:@""]; [NSString stringWithContentsOfFile:<#(NSString *)#> encoding:<#(NSStringEncoding)#> error:<#(NSError *__autoreleasing *)#>]; */ }
OC的 set 和 get 方法
类的成员变量尽量不要@public,保持数据的隐蔽性和封装性,使用对象直接访问类的成员变量也不推荐,因为这样类的成员变量可以被随意的修改,不安全,而是应该间接的调用。并且直接调用会让别人一下子就看到了类的内部结构和信息,失去了封装的意义。
set方法
1) 作用:用来设置成员变量,可以在方法里面过滤掉一些不合理的值
2) 命名规范:
- 方法名都是以set开头,而且后面跟上成员变量名,成员变量名的首字母必须大写
- 一定接受参数,且形参名称不要跟成员变量同名
- 返回值void
get方法
1) 作用:返回对象内部的成员变量
2) 命名规范:get方法的名称一般就跟成员变量同名
/*成员变量的命名规范:一定要以下划线 _ 开头 @interface Student : NSObject { 1.让成员变量和get方法的名称区分开 2.可以跟局部变量区分开,一看到下划线开头的变量,一般都是成员变量 */ int _no; Sex _sex; } // sex的set和get方法 - (void)setSex:(Sex)sex; - (Sex)sex; // no的set和get方法 - (void)setNo:(int)no; - (int)no; @end
oc特有的语法—兼容c系列的点语法
可以使用点语法来代替oc古怪的对象调用的形式。这是oc特有的让其他(主要是习惯了 c 系列编程语言的程序员,比如 c,c++,java等)程序员快速看懂的一个做法。
// 点语法的本质还是方法调用 p.age = 10; // [p setAge:10];
这里还是古怪,到底是get还是set方法,要看是给它赋值了(set),还是被赋值了(get)。还是和java或c++的点语法区分。
oc 中类的成员变量的作用于
@public : 在任何地方都能直接访问对象的成员变量
@private : 只能在当前类的对象方法中直接访问。私有成员只能在本类被直接访问,子类不可见,只能是set和get方法访问(@implementation中默认是@private,因为一般不会包含实现文件,都是包含头文件,故实现文件默认是私有的)
@protected : 可以在当前类及其子类的对象方法中直接访问 (@interface中默认就是@protected)
@package : 只要处在同一个框架中,就能直接访问对象的成员变量,不常用
@interface和@implementation中不能声明同名的成员变量,同名冲突,很好理解
Xcode的一个特性@property和@synthesize
sɪnθɪˌsaɪz v综合合成;(通过化学或生物)合成;(音响)合成
@property
自动生成set和get方法,类似java的eclipse的 setter 和 getter选项
#import <Foundation/Foundation.h> @interface Person : NSObject { int _age; int _height; double _weight; NSString *_name; } // @property:只能用在声明里,可以自动生成某个成员变量的setter和getter声明,并且和java还不一样,xcode把自动的声明隐藏,不可见。这是编译器的特性! 下面这一句,代表注释的那两句。 @property int age; //- (void)setAge:(int)age; //- (int)age; @property int height;//先生成set再get //- (void)setHeight:(int)height; //- (int)height; - (void)test; @property double weight; //如果成员变量类型一样,可以合并在一起写,但是实际开发不这样,分开比较好 @property NSString *name; @end
注意:@property里的变量age如果没有被提前声明,那么也是可以运行的,编译器会自动生成_age成员变量,xcode4.4版本之后有的。这样生成的成员变量,在类声明里是@private。若类声明里存此成员变量,如果@property还是那么写,就不会再自动生成。
@synthesize
只写在实现里。可以自动去类声明里找@property,然后根据它去生成类的成员变量的setter和getter方法的实现,并且会访问这个成员变量
@synthesize age = _age;
如果这样写,默认会访问age这个成员变量,如果没有age,就会自动生成@private类型的age变量,都是自动存在类声明里。
注意:xcode4.4之后,为了让程序员把精力放在业务逻辑上,而不是无谓的垃圾代码的书写上,xcode变得越来越方便,做的东西也越多,现在可以省略类的成员变量的声明,直接@property就可以自动生成变量和配套的set与get方法的声明。同样,4.4之后,类的实现里,@synthesize也不用写了。也就是说,set和get的实现不用写。
意味着,现在的@property 声明,类声明里的@property默认做两个事情,即生成set和get的声明,也生成set和get的实现。当然,如果类声明里没有变量声明,那么还要做第三个事情,自动生成私有的成员变量。
Xcode原则就是:没有,我替你生成,有我就直接用。
这里就可以解释为什么苹果建议类的成员变量前面加下划线?
因为@property 的新特性:自从 xcode4.4之后,@property 就独揽了@synthesize 的功能,也就说,@proeprty 可以同时生成成员变量的 set,get 方法的声明和实现,两个都能。默认的情况下,set 和 get 方法的实现,会去访问下划线的成员变量,故要求类的声明里使用下划线的变量命名方式命名成员变量。说到底:就是因为新特性的@property自动生成set,get方法的声明和实现,默认访问的都是前面带下划线的成员变量。
oc 里的 id 类型(万能指针)
id 是一种类型
类型不能做变量名,好比 int int;是错误的,一个道理。
Id 是万能指针
能指向(操纵)任何的 oc 对象,而且不能加*,只写id就代表指针(因为 id内部已经包含*,见官方文档)。
Person *p = [Person new];//传统的操作人的对象,人类型指针p 指向人的对象
万能指针,id能指向 or 操作任何OC对象,只适合用在 oc 对象上
id d = [Person new];//id 指向人的对象
id 的缺点:
如果同样是指向(调用)类不存在的对象方法,那么编译器直接报错!不会弱语法了
千万别加*
oc 的构造方法和重写
构造方法:用来初始化对象的方法,oc里是个对象方法,-开头 -init
面向对象的语言,都包含的东西---构造方法,用和 c++类似的new 这个类方法创建对象,其实不好。
// Person *p = [Person new];不推荐这样创建对象
new 其实是类方法,是 NSObject 类的方法,可以创建对象,分配内存,但是事实上,我们不常用,因为太死板,做的事情很单调,就是完整的创建一可用对象。
完整地创建一个可用的对象其实分两步:
1.分配存储空间 +alloc
2.初始化 -init
new 方法做这两个事情,new 方法内部,调用两个方法(一个类方法,一个对象方法)完成这个步骤,细节为:
1.调用+alloc分配存储空间
Person *p1 = [Person alloc];//此时对象不可用,没有初始化,返回 id(oc 对象)
2.调用-init进行初始化
Person *p2 = [p1 init];反回 oc 对象(已经初始化之后)本身
说明 new 是两个方法的联合,开发不常用,比较死板,一般分两步做,这样后果是方便选择。因为初始化不一定必须是 init对象方法初始化。。
两步合并的简单写法:掌握,开发常用这个写法,从此不用 new 了。
Person *p4 = [[Person alloc] init];
Oc的构造方法就是-init 方法,用来初始化对象的方法。对象方法,每个对象内部的成员变量默认都初始化为0,
创建 person 对象,保证她的_age 默认都是10,不是0,怎么做?
需要重写 –init方法,覆盖父类 –init方法
重写构造方法的目的:
为了让对象创建出来,成员变量就会有一些固定的值
重写构造方法的注意点
1.先调用父类的构造方法([super init])
2.再进行子类内部成员变量的初始化,和 c++类似。
Person.h #import <Foundation/Foundation.h> @interface Person : NSObject @property int age; @end Person.m #import "Person.h" @implementation Person // 重写-init方法,其实 id 有些类似 c++的模板的应用。 //- (id)init //{ // 1、一定要先调用super的init方法:先初始化父类中声明的一些成员变量和其他属性 self = [super init]; // 返回的是当前oc对象,一定用 self接受,这里需要好好研究,先知道。 // 2.如果父类对象初始化成功,才有必要进行接下来的初始化 // if (self != nil) // { // _age = 10; // } // // 3.返回一个已经初始化完毕的oc对象 // return self; //}
//综合的写法,熟练之后如下这样写: - (id)init { if ( self = [super init] ) { _age = 10; } return self; } @end
要求:
学生类的学生对象,初始化完毕之后,年龄都是10,学号都是1.还是重写构造方法-init
Student.h继承 person 类 #import "Person.h" @interface Student : Person @property int no; @end
Student.m #import "Student.h" @implementation Student // 学生对象初始化完毕后,年龄就是10,学号就是1 - (id)init { if ( self = [super init] ) { _no = 1; } return self; } @end
main.h #import <Foundation/Foundation.h> #import "Person.h" #import "Student.h" int main() { // 每个stu对象创建出来,他的_age都是10,学生学号都是1,isa 指向本身 每个 p4对象创建出来,她的 _age 都是10,isa 指向本身类 Person *p4 = [[Person alloc] init]; Student *stu = [[Student alloc] init]; return 0; }
student 继承了 person ,而 person继承了 NSObject,比如 student 对象内部有三个成员变量,分别为: student 自己的_no,和继承自父类 person 类的_age,还有 person 最开始就继承的父类NSObject 的 isa指针,这个三个成员变量,都是 student 对象 stu 包含的!
因为student 类 创建 student 对象的时候,先进入 student 类查找-init,(因为 oc 里规定,如果父类和子类有同名方法,子类对象优先调用子类同名方法),然后 在 student 类里发现- (id)init方法,之后又发现:
{ if ( self = [super init] )这句话,那么编译器会率先进入到父类 person,因为 super 的作用 { _no = 1; } return self; }
然后先调用直接父类 person 的 –init,而 person 的-init为:
- (id)init { if ( self = [super init] )还是执行到此,我们发现调试就进不去了,因为 super 调用的是 NSObject,苹果是不开源的,但是事实还是进去偷着执行 NSobject 的-init 方法,初始化isa 指针, 把 isa 初始化为指向student 类本身,记住是当前的类,初始化完毕,程序执行回到 person 类,继续向下执行_age=10。(同时student 的成员变量 age 变为10) { _age = 10; } return self;最后返回的 self 指向的是 当前student 的对象 }
当person的-init 调用完毕,最后才回到 子类student 类,继续执行 student 的-init, 执行_no=1,继承来的 isa 初始化为指向本身 student 类,这才初始化完毕。
person 对象初始化的时候,同理,先在本类查找初始化方法,发现 super 语句,然后再去父类 NSObject 类 里查找-init方法,(这里先记住 self=[super init]的写法,下来再深入),NSObject 做一件事,那就是初始化 isa,把 isa 指向 person 类本身,(指向当前类,这个类是毕源的,看不到内部代码)然后回到 person类,_age=10。执行完毕。
再看一看 student 对象,创建的时候,先初始化,同理在本类,然后 发现 super,那么去父类 person 执行-init,还是发现 super,还是去 NSObject执行-init,把 isa 指向 student 类本身,(当前类不是 person,是 student 了)然后回到 person,_age=10.回到student,_no=1,执行完毕,再回main 函数,初始化,完毕。
oc构造方法的自定义
oc自定义构造方法的规范
1.一定是对象方法, 故一定以 - 开头
2.返回值一般是id类型
3.方法名一般以initWith开头
这让我想起了,c++构造函数的含参构造和默认构造,和不带参数的构造,意义是类似的。其实就是带参构造。和 c++大同小异。
Person.h #import <Foundation/Foundation.h> @interface Person : NSObject @property NSString *name; @property int age; - (id)initWithName:(NSString *)name; - (id)initWithAge:(int)age; - (id)initWithName:(NSString *)name andAge:(int)age; @end Person.m #import "Person.h" @implementation Person - (id)initWithName:(NSString *)name { if ( self = [super init] )固定写法,记住 { _name = name; } return self; } - (id)initWithAge:(int)age { if ( self = [super init] ) { _age = age; } return self; } - (id)initWithName:(NSString *)name andAge:(int)age { if ( self = [super init] ) { _name = name; _age = age; } return self; } @end Student.h继承 person 类 #import "Person.h" @interface Student : Person @property int no;//_no 是子类 student 自己的属性 - (id)initWithNo:(int)no; - (id)initWithName:(NSString *)name andAge:(int)age andNo:(int)no; @end student.m #import "Student.h" @implementation Student - (id)initWithNo:(int)no { if ( self = [super init] ) { _no = no; } return self; } // 要理解和接受这个做法和思想: // 父类的属性交给父类方法去处理,子类方法处理子类自己的属性 - (id)initWithName:(NSString *)name andAge:(int)age andNo:(int)no { // 将name、age传递到父类方法中进行初始化,no 在本类初始化 使用了自定义的构造方法 if ( self = [super initWithName:name andAge:age]) { _no = no; } return self; } @end main.m #import <Foundation/Foundation.h> #import "Person.h" #import "Student.h" int main(int argc, const char * argv[]) { @autoreleasepool { Student *p = [[Student alloc] initWithName:@"Jim" andAge:29 andNo:10]; } return 0; }
每个对象创建之后,都有一个默认名,年龄,和学号,但是每个人的结果不一样。此时,需要使用自定义构造方法初始化。否则重写构造方法初始化的话(不带参数)导致名称都是一样的。不符合程序要求。
首先,创建 student 对象,初始化对象指针 p(指向对象),先在本类找 initWithName 方法,确实有,然后进入执行,将name和age(都是继承来的属性)传到父类方法中进行初始化:这样分工明确(其实和 c++一样的思路),自己做自己的事情,体现了良好的架构思想,还有这样做的好处是,如果我们都统一在子类初始化,那么一旦父类的成员变量名称改变了,会导致子类的代码出错,需要修改,一旦工程较大,后果不堪设想,而分工明确,自己做自己的事情,把父类的成员让父类去初始化,子类只管自己的,就能做到不变应万变。
欢迎关注
dashuai的博客是终身学习践行者,大厂程序员,且专注于工作经验、学习笔记的分享和日常吐槽,包括但不限于互联网行业,附带分享一些PDF电子书,资料,帮忙内推,欢迎拍砖!