上面这个图对上面的存储模式作了很形象的展示。
——————————————————————————————————————————— #pragma mark指令的使用功能:即对代码的分组,方便查找和导航。
上图 No Selection 部分被挡到了,在下面又截了一下
从上面两图可知,我们在No Selection中可以看到我们这个代码的导航,可以精确的找到哪一行的所在位置。但是还是略显不清晰。那么我们就需要用到 #pragma mark 预处理指令。他能在导航里添加必要的文字和横线进行代码块的区分。另外需要小小的提示一点,对于 #pragma mark - 这条预处理指令,显然他是显示一条横线,但是注意不要在这条语句后面加多余的空格,否则表面可能没有变化,但是在查找搜索的时候,会多显示一行横线,如下图:
——————————————————————————————————————————— 函数 和 对象方法 的区别 首先我们先简单认识一下。 对象方法(动态方法): -(void)eat; 1)对象方法的实现只能写在 @implementation … @end 之间,对象方法的声明只能写在 @interface … @end 之间 2)对象方法都以 - 号开头,而类方法(静态方法)都以 + 开头 3)对象方法只能由对象来调用。同理,类方法只能由类来调用,而都不能当作函数一样调用。 4)对象方法归 类/对象 所有 函数: void run() { } 1)函数属于整个文件的,可以写在文件中的任何位置,包括 @implementation … @end 之间,但是写在 @interface … @end 之间无法识别。同C语言一样,函数的声明可以写在main函数的内部也可以写在main函数的外部。 2)所有函数都是平行的 3)使用的时候可以直接调用 4)函数没有隶属关系 5)函数不可以访问对象中的成员变量 这一部分纯理论知识,大家如果对里面的某些地方不懂,或者不信任,那么可以去简单的验证一下,也不麻烦~ ——————————————————————————————————————————— 常见错误的汇总 这一部分初学者常错,可以着重看看,这一部分很简单,主要是用心细心。 重点我已经用“★”标记,读者重点去看看。 1)★★@interface @end 和 @implementation @end 是不能嵌套的,不能把方法的实现关键字夹在声明的关键字中间 2)@end不要漏写 3)成员变量(属性),写在大括号里 4)★方法的声明 不要 写在大括号里 5)★★★声明的同时不要对成员变量初始化,这里怎么理解呢?那就是,成员变量(实例对象的具体属性)不能脱离对象而独立存在 6)★★方法无法和函数一样调用 7)★★★成员变量和方法不能用static等等关键字修饰,修饰他们一般用到的是public、protect这些关键字 8)★★★类的实现部分,也就是@implementation … @end关键字及其中间包含的方法实现部分,可以写在main函数的后面。但是@interface … @end关键字及其中间包含的类的声明部分,是必须写在main函数的前面的 9)★★如果有多个类,声明和实现部分顺序是可以打乱的,但是声明部分一定要在实现部分的前面 10)★★★★★最后一点,也是最最重要我要说明的。就是在应用@try @catch @finally语句中的时候,捕捉错误信息的代码是怎么写的。我们知道,@try关键字后面是写可能会出错的代码,@catch关键字后面是写出错之后我们需要处理执行的代码,@finally后面是写不管错误出错与否,都执行的代码。关键来了,就在catch后面,我们可以在后面加上一句 NSLog(@“%@”,exception); 这样就可以输出出错的信息了! 大家可以去尝试运行一下下面的程序,然后有助于理解第10条。 #import <Foundation/Foundation.h> @interface Car : NSObject { @public NSString *name; } -(void)run; @end @implementation Car //错误是:程序没有实现run这个方法 @end int main() { @autoreleasepool { Car *car=[Car new]; @try { [car run]; } @catch (NSException *exception) { NSLog(@"wrong!"); NSLog(@"%@",exception); } @finally { NSLog(@"123456!"); } } return 0; } 这个程序的输出信息是: 2015-07-30 18:18:14.709 OC_program[9692:1739913] -[Car run]: unrecognized selector sent to instance 0x1002069c0 2015-07-30 18:18:14.710 OC_program[9692:1739913] wrong! 2015-07-30 18:18:14.710 OC_program[9692:1739913] -[Car run]: unrecognized selector sent to instance 0x1002069c0 2015-07-30 18:18:14.710 OC_program[9692:1739913] 123456! 大家可以参考一下! ——————————————————————————————————————————— 对象和方法之间的关系 有两种关系:①对象作为方法的参数 ②对象作为方法的返回值(分别在下面用两个对象方法来进行说明) #import <Foundation/Foundation.h> typedef enum {sMan,sWoman,sYao} Sex; //新建一个枚举类型Sex,那么自然三个对应的表示数字为 0 1 2 @interface Person : NSObject { @public NSString *_name; Sex _sex; } -(void)displayPerson:(Person *)person;//这个方法来验证对象作为方法的参数 -(Person *)changeSex:(Person *)person1;//这个方法来验证对象作为方法的返回值(当然在这个方法里面,对象也作为了此方法的参数) @end @implementation Person -(void)displayPerson:(Person *)person//这个方法来验证对象作为方法的参数 { NSLog(@"Name:%@,Sex:%d",person->_name,person->_sex); } -(Person *)changeSex:(Person *)person//这个方法来验证对象作为方法的返回值(当然在这个方法里面,对象也作为了此方法的参数) { person->_sex=sYao;//将性别_sex都改为sYao return person;//我们不妨让返回值就是person } @end int main() { @autoreleasepool { Person *p=[Person new]; Person *p1=[Person new]; p1->_name=@"Wang"; p1->_sex=sMan; Person *p2=[Person new]; p2->_name=@"Ma"; p2->_sex=sWoman; [p1 displayPerson:p1];//将p1作为方法的参数传进去(p1就是实参),返回p1的信息。这里要注意,对象方法只能由对象来调用,所以要先用p1调用这个方法,然后再将p1的值传进去。看起来有点别扭,但是应该这么写。 [p displayPerson:p2];//当然,我们可以用一个不相干的实例对象p来调用这个对象方法,然后再传入p2,这样也可以的。显然,p只是一个实例对象,而我们并没有对其属性初始化 Person *p3=[p changeSex:p1];//这里实例化了一个实例对象p3来接收返回值,然后用实例对象p去调用了changSex这个对象方法,传入的参数是对象p1,然后对p1进行性别的更改,最后将更改完的新的p1返回,将返回值赋给p3 NSLog(@"Name(p3):%@,Sex(p3):%d",p3->_name,p3->_sex);//输出p3的属性 } return 0; } ——————————————————————————————————————————— 类的对象与对象方法之间关系(题目) //下面的题目结合起来写了一下,有两个地方需要注意: //①在一个类的属性成员声明的时候,里面可以存在其他我们自定义的类类型的实例对象作为其属性成员 //②如果用一个类的实例对象去调用他的对象方法的时候,其中用到了另一个自定义类类型的实例对象但是却没有赋值,那么也是可以调用并不会出错,只是不会得到想要的结果罢了(没有结果),下面有介绍。 /* 1.设计一个”狗“类 1> 属性 * 颜色 * 速度(单位是m/s) * 性别 * 体重(单位是kg) 2> 行为 * 吃:每吃一次,体重增加0.5kg,输出吃完后的体重 * 吠(叫):输出所有的属性 * 跑:每跑一次,体重减少0.5kg,输出速度和跑完后的体重 * 比较颜色:跟别的狗比较颜色,如果一样,两个值做减法得零,返回NO(零值),不一样,做减法得到非零值,返回YES(1) * 比较速度:跟别的狗比较速度,返回速度差(自己的速度 - 其他狗的速度) 2.结合前面的“狗”类,设计一个“人”类 1> 属性 * 姓名 * 狗(养了一条狗) 2> 行为 * 喂狗:每喂一次,狗就会执行“吃”这个行为 * 遛狗:每溜一次,狗就会执行“跑”这个行为 */ #pragma mark 整个程序 #import <Foundation/Foundation.h> typedef enum {dColorBlack,dColorWhite,dColorFlower,dColorGreen}Color; typedef enum {dSexGong,dSexMu,dSexYao}Sex; #pragma mark - #pragma mark Dog类的声明 @interface Dog : NSObject { @public Color _color; int _speed; Sex _sex; float _weight; } -(void)eat:(NSString *)foodName; -(void)bark; -(void)run; -(Boolean)isCompareColorWithOthers:(Dog *)dog2;//这里也可以用BOOL类型的返回值,看两只狗的颜色是否一样,一样返回1,不一样返回0。当然这里传入的参数是类类型(Dog类)的实例对象。 -(int)isCompareSpeedWithOthers:(Dog *)dog2;//题意,比较的是速度,然后返回速度差,自然是int类型的 @end #pragma mark - #pragma mark Dog类的实现 @implementation Dog -(void)eat:(NSString *)foodName { _weight+=0.5; NSLog(@"狗吃了%@,体重变为%.2f",foodName,_weight); } -(void)bark { NSLog(@"color:%d,speed:%d,sex:%d,weight:%.2f",_color,_speed,_sex,_weight); } -(void)run { _weight-=0.5; NSLog(@"遛狗,体重变为:%.2f",_weight); } -(Boolean)isCompareColorWithOthers:(Dog *)dog2 //这里用BOOL类型也是可以的 { if (_color==dog2->_color) { return TRUE; } else return FALSE; } -(int)isCompareSpeedWithOthers:(Dog *)dog2 { return _speed-dog2->_speed; } @end #pragma mark - #pragma mark Person类的声明 //注意一下,我们在写Person这个类的时候,Person这个类中有属性成员是Dog类的实例变量,那么我们需要在这里“认识”Dog这个类,所以要把Person类声明在Dog类的下面,当然只是声明,类的实现先后顺序可以随意。这个知识点在前面一节常见错误的汇总中提到过。 @interface Person : NSObject { @public NSString *_name; Dog *_dog; //在类的声明里面,其属性成员可以是其他我们定义的类类型的实例变量。 } -(void)weigou:(NSString *)foodName; -(void)liugou; @end #pragma mark - #pragma mark Person类的实现 @implementation Person -(void)weigou:(NSString *)foodName { [_dog eat:foodName]; } -(void)liugou { [_dog run]; } @end #pragma mark - #pragma mark main函数 int main() { @autoreleasepool { Dog *dog1=[Dog new]; dog1->_color=dColorBlack; dog1->_sex=dSexGong; dog1->_speed=30; dog1->_weight=60.5; Dog *dog2=[Dog new]; dog2->_color=dColorBlack; dog2->_sex=dSexMu; dog2->_speed=40; dog2->_weight=40; [dog1 eat:@"humbugers"]; [dog1 bark]; [dog1 run]; Boolean b1=[dog1 isCompareColorWithOthers:dog2]; //这个对象方法包括下面的对象方法都需要声明一个与其返回值同类型的变量来接收其返回值,并输出。 NSLog(@"颜色是否一样:%d",b1); int i1=[dog1 isCompareSpeedWithOthers:dog2]; NSLog(@"速度差:%d",i1); #pragma mark 第二个题中的重点注意代码 Person *person1=[Person new]; person1->_name=@"Wang"; person1->_dog=dog1; //我们这里将dog1给了person1这个实例变量,那么下面的person1调用weigou和liugou的成员方法就有意义了。当然如果是不给person1的Dog属性赋值,也没有错,null值也是可以执行的,只是不会输出什么,没有得到预想的结果。 [person1 weigou:@"hotdog"]; [person1 liugou]; } return 0; } ——————————————————————————————————————————— 对象作为对象方法的参数连续传递 //对象作为方法的参数连续传递,一个题来解释这个问题。说实话,这部分很简单,虽然题目啰嗦,但是就是说了一个事儿 //有两个方法,一个是士兵开枪,另外一个是枪射击子弹。士兵开枪需要用到子弹,然后还需要用到枪射击子弹这个方法,而枪射击子弹又要用到子弹,所以说子弹这个对象在两个方法中都用作参数传递了,所以叫对象作为方法的参数连续传递 //再缕一遍,第一个类的对象方法使用了第二个类和第三个类的实例对象作为参数,而在第一个类的对象方法体内让第二个类的实例对象去调用第二个类的对象方法,而第二个类的对象方法又同样使用了第三个类的实例对象作为参数。所以说,这么一个简单的程序,让第三个类的实例对象两次作为对象方法的参数出现,这也就是对象作为方法的参数连续传递 /* 士兵开枪 枪射击子弹 枪类: 名称:Gun 属性:型号(_size),子弹个数(_bulletCount) 行为:射击 人类 名称:Soldier 属性:姓名(_name) life level(等级) 行为:跑 蹲 开枪 跳 子弹类(弹夹) 名称:Bullet 属性:子弹个数,型号(_model) //要求士兵在射击的时候,不但要给一把枪,还要给 一个弹夹才能射击 //子弹不能为负数 */ #import<Foundation/Foundation.h> #pragma mark - #pragma mark Bullet类的声明 @interface Bullet : NSObject { @public NSString *_size; int _bulletCount; } @end #pragma mark - #pragma mark Bullet类的实现 @implementation Bullet //Bullet类没有方法,所以没有类的实现部分,但是最好写上 @end #pragma mark - #pragma mark Gun类的声明 @interface Gun : NSObject { @public NSString *_size; } -(void)shoot:(Bullet *)bullet; @end #pragma mark - #pragma mark Gun类的实现 @implementation Gun -(void)shoot:(Bullet *)bullet //在shoot方法中,要射击必须有子弹,所以有一个子弹的参数,这里是否射击要取决于子弹是否不为0(有子弹才能射击),所以里面有判断 { if(bullet->_bulletCount>0) { bullet->_bulletCount--; NSLog(@"tututututu.....\n还剩下%d颗子弹",bullet->_bulletCount); } else { NSLog(@"没子弹了~"); } } @end #pragma mark - #pragma mark Soldier类的声明 @interface Soldier : NSObject { @public NSString *_name; int _life; int _level; } -(void)fireByGun:(Gun *)gun andBullet:(Bullet *)bullet; //这个方法叫做用“大兵用枪开火射击”,所以说大兵开枪要用枪,然后抢里还得有子弹,所以自然这个方法有两个参数 @end #pragma mark - #pragma mark Soldier类的实现 @implementation Soldier -(void)fireByGun:(Gun *)gun andBullet:(Bullet *)bullet { [gun shoot:bullet]; //这里子弹作为参数传递给shoot方法(这里是第二次子弹作为参数) } @end #pragma mark - #pragma mark main函数 int main() { @autoreleasepool { Soldier *soldier1=[Soldier new]; soldier1->_name=@"Wang"; soldier1->_life=98; soldier1->_life=2; Gun *gun1=[Gun new]; gun1->_size=@"AK-47"; Bullet *bullet1=[Bullet new]; bullet1->_size=@"5.0口径"; bullet1->_bulletCount=4; [soldier1 fireByGun:gun1 andBullet:bullet1];//大兵开枪这个方法,是第一次将子弹作为参数 [soldier1 fireByGun:gun1 andBullet:bullet1]; [soldier1 fireByGun:gun1 andBullet:bullet1]; [soldier1 fireByGun:gun1 andBullet:bullet1]; [soldier1 fireByGun:gun1 andBullet:bullet1];//一共4发子弹,那么打第五次(第五次执行这个方法,自然显示没子弹了) } return 0; } ——————————————————————————————————————————— 在使用类的过程中合理运用结构体 在使用类的过程中合理运用结构体可以节省很多内存空间,比如说一个类的属性中,我们需要另一个类的实例变量作为其属性(之前我们学习过),但是我们一定要再声明一个新类,占用的空间会很多,比如说我们并不会用到的isa指针就得占8个字节。所以说,建立一个枚举类型的结构体就再好不过了,空间占有量小且不会被浪费。下面一个小的练习题来介绍一下这个事情。 /* 设计一个”学生“类 1> 属性 * 姓名 * 生日(我们将生日这个参数设置为结构体类型,而不是单独将生日设置为类类型) */ #import <Foundation/Foundation.h> //定义结构体的方法①:(我们为什么不用这种方法呢,这是因为我们如果这样定义结构体,那么在定义结构体变量的时候就要每次都写上struct这个前缀,这样显得十分的麻烦,我们为了减少工作量可以按照下面的方法②进行定义) //struct MyDate //{ // int year; // int month; // int day; //}; //定义结构体的方法②:(方法②有两个好处:第一我们定义了一个结构体,第二我们为这个结构体命名了一个新的名字“MyDate”,之后我们创建新的变量的时候可以直接用新名称“MyDate”去声明创建) typedef struct { int year; int month; int day; }MyDate; @interface Student : NSObject { @public NSString *_name; MyDate _birthday; } @end @implementation Student @end int main() { @autoreleasepool { Student *stu1=[Student new]; stu1->_name=@"Wang"; //我们知道,为结构体变量初始化需要在声明的时候立即初始化,如果不立即初始化,系统是不认识的。就要一个一个参数的来初始化 //初始化结构体变量方法①:一个一个参数的来分步初始化 stu1->_birthday.year=1993; stu1->_birthday.month=12; stu1->_birthday.day=31; Student *stu2=[Student new]; stu2->_name=@"Lao"; //初始化结构体变量方法②:强制类型转换(系统就明白初始化的是一个结构体变量,而不是一个一维数组什么的。这也就是为什么我们不能直接写成 stu2->_birthday={1993,6,14}; 因为这样写法系统不知道后面的是什么,后面的也可能是一个一维数组,但是前面的类型是确定的,是一个结构体类型) stu2->_birthday=(MyDate){1993,6,14}; Student *stu3=[Student new]; MyDate md1={1993,1,1}; stu3->_name=@"wangwangwanglaolaolao"; stu3->_birthday=md1; //初始化结构体变量方法③:先声明一个结构体变量md1,然后直接将md1赋给stu3->_birthday,这样类型就一致起来了 //输出验证一下正确性: NSLog(@"\nstu1:\nname:%@,birthday:%d年%d月%d日",stu1->_name,stu1->_birthday.year,stu1->_birthday.month,stu1->_birthday.day); NSLog(@"\nstu2:\nname:%@,birthday:%d年%d月%d日",stu2->_name,stu2->_birthday.year,stu2->_birthday.month,stu2->_birthday.day); NSLog(@"\nstu3:\nname:%@,birthday:%d年%d月%d日",stu3->_name,stu3->_birthday.year,stu3->_birthday.month,stu3->_birthday.day); } return 0; } ——————————————————————————————————————————— 创建字符串的几种方法 //创建字符串的几种方法 #import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { @autoreleasepool { //1、直接创建一个字符串(最普遍的方法,也是特殊的一种方法) NSString *s = @"banzhang jiecao diaole "; //特殊用法 NSLog(@"%@",s); //2、用类的思想去创建字符串(我们知道 NSString 是OC中字符串处理的类,那么用的类的思想应该这样创建) NSString *s1 = [NSString new]; s1 =@"jian le ma"; NSLog(@"%@",s1); //3、格式化创建字符串(按照指定的格式创建字符串) // NSString *imgName = [NSString stringWithFormat:@"xxxxxx%02d.jpg",i]; // for (int i=0; i<10; i++) { // NSString *imgName = [NSString stringWithFormat:@"xxxxxx%02d-%02d.jpg",i,i+1]; // NSLog(@"%@",imgName); // } //4、用一个已经存在的字符串创建一个新的字符串(也就是将s1中的值赋给新的字符串s2) NSString *s2 = [[NSString alloc] initWithString:s1]; NSLog(@"s2 = %@",s2); //提一句,其实上面的new,stringWithFormat这些都是类方法,在系统中已经存在的方法。看到知道什么意思就行了 } return 0; } ——————————————————————————————————————————— OC字符串长度的计算方法 //OC字符串长度的计算方法(和C比较来介绍) #import <Foundation/Foundation.h> int main() { @autoreleasepool { NSString *s1=@"王中尧"; //int len;//len来得到长度的值 //len = [s1 length]; // Implicit conversion loses integer precision: 'NSUInteger' (aka 'unsigned long') to 'int' 这句话我们会得到一个这样的错误,其实我们返回的是一个长整型的值给len,所以应该是unsigned long类型的,这个类型在此应写成NSUInteger(无符号的长整型,就是没有负数在里面) NSUInteger len=[s1 length]; NSLog(@"\nlen:%ld",len);//len的值为3,说明汉字在oc字符串长度计算的时候也是占一个字节的,就把中文当作一个字符! char *c1="王中尧"; printf("%ld",strlen(c1));//如果按照c语言的计算方式,中文是占3个字符,所以c1的长度是9 } return 0; } ——————————————————————————————————————————— OC多文件开发介绍 这里有两个类: Person 和 Dog 其实就是将类的声明放在 Person.h 和 Dog.h 文件中,类的实现放在 Person.m 和 Dog.m 中,main.h 文件里面放程序的入口代码,另外主函数和类的实现文件中一定要使用 #import 包含相应的头文件。 .h .m 这两个文件要同名。文件名就是类名。 这是编程思想的一种体现,.h 和 .m 文件是完全独立的,这也是将 接口 和 实现 分离开来。 ——————————————————————————————————————————— 多文件开发的实现步骤 在这个OC_program工程下添加一个新的target添加成功后,我们就拿一个例子来介绍一下 多文件开发的实现步骤。
我们可以右键这个target来创建类的头文件及类的源文件
显然,在这里我们要点击 New File… 标签
在这里我们可以直接点击 Cocoa Class ,这样输入类的名字后,类的 .h 文件和 .m 文件就都创建出来了(经常用的),当然我们也可以去一个一个的创建。显然,创建类的源文件(.m)就要去点击 Objective-C File ,而创建类的头文件(.h)就要去点击 Header File 了。
创建完成之后要引入相应的文件。 比如在头文件中,肯定要加上 #import <Foundation/Foundation.h> ,而如果头文件中有用到别的类类型,那么也要引入别的类类型的头文件。而源文件一般就引入和自己同名的头文件即可。在main.m中也是用到什么文件就引入什么。多引入也不会错,只是多余。 一般我们都用 Cocoa Class 去创建类的源文件和类的头文件。创建完成后自动生成里面相应的代码。比如头文件里面肯定包含 @interface @end…..我们只需在已经给定我们的条条框框中加关键代码就行了。很方便。 在下图中,我们还可以 New Group,来分门别类的存放我们的文件,这也是可以的(比如可以将一个类的头文件和源文件存在一个 Group 下),很清楚。这一部分,大家在以后的学习过程中经常用,用这样的方式简单,清晰。要渐渐摒弃之前将大量代码写在一个文件中的陋习。
———————————————————————————————————————————版权声明:本文为博主原创文章,未经博主允许不得转载。