前言:学习视频是iOS开发基础班+就业班(100天完整版)之基础班2:Objective-C学习(10天)_哔哩哔哩_bilibili
学习笔记是转载自我的Objective-C语言学习笔记(四) – BeautyLife Studio
因为本人学习到第三天时才看上面的学习笔记,发现笔记内容和课程顺序是一模一样,本人写的笔记也大差不差,但是我的示例代码会缩减,所以还是转载他人的,以供他人学习
可能会说现在怎么还有人学ios,我也觉得(狗头),但是公司业务有涉及,来看一些快速上手文章也是好的,技多不压身嘛。时间足够的可以结合视频食用效果更佳,时间短也能通过文章有所了解。
2.0 类与对象
2.1 对象在内存中的存储
栈 | 存储局部变量,例如[Person *p1],创建了Person类的指针变量p1,此时p1存储在栈中,指向类对象在堆中的地址 |
堆 | 程序员手动申请的字节空间,例如[Person new],申请创建对象,isa指针指向类在代码段的地址 |
BSS段 | 存储未被初始化的全局变量 静态变量 |
数据段/常量区 | 存储已被初始化的全局 静态变量 常量数据 |
代码段 | 存储代码。存储程序的代码,例如@interface Person |
我们可以想象,在创建对象的时候,肯定是需要访问类的,声明一个类的指针变量也会访问类的。
当程序运行期间,当某个类第一次被访问到的时候,会将这个类存储到内存中的代码段区域,这个过程就叫做类加载。
那么对象在内存中又是如何存储的呢?
Person *p1 = [Person new];
Person *p1 会在栈内存中申请一个空间,在栈内存中声明一个Person类型的指针变量p1。p1是一个指针变量,那么只能存储地址。
[Person new],真正在内存中创建对象的其实是这句代码。
new做的事情,
第二是在这个空间中根据类的模板创建对象,类模板中定义了什么属性,就把这些属性依次的声明在对象之中,对象中还有另外一个属性,叫做isa,它是一个指针,这个指针指向这个对象所属的类在代码段中的地址;
第三是初始化对象的属性。如果属性的类型是基本数据类型,那么赋值为0,如果属性的类型是C语言的指针类型,那么就赋值为NULL,如果属性的类型是OC的类指针类型,那么就赋值为nil;
注意:
对象中只有属性,而没有方法。只有自己所属类的属性外加一个isa指针指向代码段中的类;
指针名(对象名)->属性名;
[指针名(对象名) 方法名];
先根据指针名找到对象,对象发现要调用方法,再根据对象的isa指针找到类,然后调用类里的方法
那为什么不把方法存储在对象中?
因为每一个对象的方法的代码实现都是一模一样的,没有必要为每一个对象都保存一个方法,这样的话就太浪费空间了,既然都一样,那么就保存一份就可以了。
2.2 nil和NULL
NULL
可以作为指针变量的值,如果一个指针变量的值是NULL值代表这个指针不指向内存中的任何空间。NULL其实等价于0,它的本质是一个定义为0的宏。
只能作为指针变量的值。代表这个指针变量不指向内存中的任何空间。nil其实也等价于0,它的本质也是一个宏。
一般来说C指针用NULL,而OC的类指针用nil。
例如:
int *p1 = NULL;
p1指针不指向内存中的任何一块空间
Person *p1 = nil;
p1类指针不指向任何对象。
如果一个类指针的值是nil,而这个时候又给这个类指针的属性赋值,那就会报错。
例如:
Person *p1 = nil;
p1->_name = @“Jack”;
p1->_age = 19;
另外,如果这个时候通过p1类指针去调用对象的方法,运行虽然不会报错,但是方法不会执行,没有任何反应。
2.3 多个指针指向同一个对象
Person *p1 = [Person new];
Person *p2 = p1;
这样做是完全没有问题的,p1和p2的类型都是Person指针类型的,代表将p1的值赋值给p2,而p1的值是对象的地址,所以就把对象的地址赋值给了p2,p1和p2指向了同一个对象。
2.4 分组导航标记
当我们在定义类的时候,如果一个两个还好,一旦如果需要定义很多个类的时候,在寻找类的代码就比较繁琐了。
方法:
#pragma mark 分组名
#pragma mark -
#pragma mark - 分组名
这样就会在导航条对应的位置先产生一条水平分隔线,再显示标题。
2.5 函数与方法的对比
void test()
{
}
-(void)sayHi;
我们会觉得这两种代码有类似的功能,那么它们到底有何异同点呢?
首先,它们都是用来封装一段代码,将一段代码封装在其中,表达一个相对独立的功能。函数或者方法只要被调用,那么封装在其中的代码就会被自动执行。
那么,它们的不同点是:
语法不同
定义的位置不一样
OC方法的声明只能写在@interface的大括号外面,实现只能写在@implementation之中。
函数除了在函数的内部和@interface的大括号之中,,其他的地方都可以写。(虽然函数可以在很多地方写,但是一般来说是不会在类中出现的,虽然不报错,但这样做很不规范)
调用的方法也不一样
2.6 容易犯的错
@interface是类的声明,@implementation是类的实现,它们之间是不能互相嵌套的;
这里多说一句,类的实现可以放在main函数之后,也就是说可以放在使用这个类的代码之后。但是类的声明必须在main函数之前,也就是在使用这个类的代码之前。
属性名一定要以下划线开头,这是规范。否则在学习后面的知识点会出现问题;
OC的方法必须要先创建对象后才能被使用;
OC的方法有声明就必须要有实现。
如果指针指向的对象只有方法的声明,而没有方法的实现的话,那么这个时候通过指针来调用这个方法在运行的时候就会报错。
unrecognized selector sent to instance 内存地址。
2.7 多文件开发
学习OC到现在,我们已经练习了写学多类的声明和实现,我们发现当类的定义很多个的时候,查找代码就比较困难。这个时候我们想起了C语言中的多文件开发,而OC肯定也是有这个功能的。
我们可以把类写在模块中,在主文件中调用即可。即方便了代码的管理,也可以实现多人团队合作开发。
一般来说,把一个类写在一个模块中,我们需要创建两个文件:
用来写类的声明,因为要用到Foundation框架中的类NSObject,所以在这个头文件中要引入Foundation框架的头文件,然后将类的声明部分写在头文件中。
.m 代码文件
用来写类的实现,先引入模块的头文件,这样才会有类的声明,再写上类的实现。
然后在主文件中,要用到这个类的话,只需要引入这个类模块的头文件就可以直接使用了。
在选择创建新文件时,选择Cocoa Class,用这种方式创建的头文件和代码文件会自动将类的声明和实现写进相应的文件中。且类的名字和创建文件的时候建立的文件名一致。(当然,事后也是可以更改名字的)
那么从现在开始,所有的类都要这样创建。
2.8 对象与方法
因为对象在内存中的大小是由我们决定的,多写几个属性,对象占用的空间就大一些,少写几个属性对象就小一些。
那么,既然类是一个数据类型,那么作为类的对象就可以作为方法的参数。
例如可以这么写:
有这样一个类:
@interface Person : NSObject
{
NSString *_name;
int _age;
}
-(void)run;
@end
@implementation Person
-(void)run
{
NSLog(@“跑啊跑啊!”);
}
@end
还有另外一个类:
@interface Girl : NSObject
{
//此处省略属性声明
}
-(void)test:(Person *)person;
@end
@implementation Girl
-(void)test:(Person *)person
{
[person run];
}
@end
这就是利用对象来作为方法的参数。注意:对象作为参数,参数类型应该是类名 *。
Girl *g1 = [Girl new];
Person *p1 = [Person new];
[g1 test:p1];
这样就完成了调用。
当对象作为方法的参数的时候,需要注意:
调用方法的时候,如果方法的参数是一个对象,那么给实参的时候,实参要求也必须是一个符合要求的对象。即在刚才的例子中,作为参数的对象是属于Person类的,那么传入的实参也必须是Person类的对象,而不能是Girl类的对象。
当对象作为方法的参数传递值的时候,是地址传递。所以在方法内部通过形参去修改形参指向的对象的时候,会影响实参变量指向的对象的值。
神类:
人类:
属性:姓名,年龄,性别,剩余寿命
根据这个提示,我们开始创建类。新建两组头文件和实现文件,分别是God和Person。另外,为了方便定义性别的属性,我们还建立一个头文件gender.h来定义枚举。
#ifndef Gender_h
#define Gender_h
typedef enum
{
GenderMale,
GenderFemale
}Gender;
#endif /* Gender_h */
#import <Foundation/Foundation.h>
#import "Gender.h"
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
{
@public
NSString *_name;
int _age;
Gender _gender;
int _leftLife;
}
-(void)show;
@end
NS_ASSUME_NONNULL_END
Person.m的代码如下:
#import "Person.h"
@implementation Person
-(void)show
{
NSLog(@"我叫%@,我还有%d年可以活。",_name,_leftLife);
}
@end
#import <Foundation/Foundation.h>
#import "Gender.h"
#import "Person.h"
NS_ASSUME_NONNULL_BEGIN
@interface God : NSObject
{
@public
NSString *_name;
int _age;
Gender _gender;
}
-(void)addLifeWith:(Person *)person;
-(void)reduceLifeWith:(Person *)person;
@end
NS_ASSUME_NONNULL_END
God.m的代码如下:
#import "God.h"
@implementation God
-(void)addLifeWith:(Person *)person
{
person->_leftLife += 5;
NSLog(@"喝下这杯圣水,凡人你将得到救赎……");
NSLog(@"名字叫做%@的人,寿命增加至%d年",person->_name,person->_leftLife);
}
-(void)reduceLifeWith:(Person *)person
{
person->_leftLife -= 5;
NSLog(@"喝下这杯圣水,凡人你将得到惩罚……");
NSLog(@"名字叫做%@的人,寿命减少至%d年",person->_name,person->_leftLife);
}
@end
#import <Foundation/Foundation.h>
#import "God.h"
int main(int argc, const char * argv[])
{
@autoreleasepool
{
God *g1 = [God new];
g1->_name = @"上帝";
g1->_age = 99999;
g1->_gender = GenderMale;
Person *p1 = [Person new];
p1->_name = @"Bill";
p1->_age = 55;
p1->_leftLife = 6;
p1->_gender = GenderMale;
Person *p2 = [Person new];
p2->_name = @"Jack";
p2->_age = 22;
p2->_gender = GenderMale;
p2->_leftLife = 40;
[g1 addLifeWith:p2];
[g1 reduceLifeWith:p1];
[p1 show];
[p2 show];
}
return 0;
}
以上便是一个利用对象作为方法的参数的示例。大家可以尝试在Xcode中输入查看运行效果。
另外,我们再举一个例子来说明将本类对象作为本类方法的参数,这种情况。
例如,我们想要计算平面上两个点之间的距离,我们可以建立这样的一个类——点,具有的功能(方法)是可以计算两个点之间的距离。
先建立一个类:
@interface Point : NSObject
{
@public
int _x;
int _y;
}
-(double)distanceWithOtherPoint:(Point *)otherPoint;
@end
这个类的实现,可以写成:
#import <math.h>
@implementation Point
-(double)distanceWithOtherPoint:(Point *)otherPoint
{
double res1 = (_x-otherPoint->_x)*(_x-otherPoint->_x);
double res2 = (_y-otherPoint->_y)*(_y-otherPoint->_y);
double res3 = res1 + res2;
return sqrt(res3);
}
@end
下面来谈谈对象作为方法的返回值。
什么时候方法的返回值是一个对象呢?
当方法执行完毕之后,如果有一个对象方法的内部不知道如何处理,并且这个对象是调用者需要使用的,那么这个时候我们就应该将这个对象返回。
那么如何返回对象呢?
再次利用Person和Girl的类的例子,如果Girl类的方法需要返回一个对象,格式应该是:
-(Person *)test1;
实现的格式可以是:
-(Person *)test1
{
Person *Mary = [Person new];
Mary->_name = @“Mary”;
Mary->_age = 19;
return Mary;
}
注意:返回值的类型应该是一个类指针,而return后应该跟对象名。
Person *p1 = [g1 test1]
我们可以在God.h中增加一个方法为造人:
-(Person *)makePerson;
在God.m中增加造人方法的实现:
-(Person *)makePerson
{
Person *p1 = [Person new];
p1->_name = @“Eva”;
p1->_age = 0;
p1->_gender = GenderFemale;
p1->_leftLife = 100;
return p1;
}
在main函数中可以增加以下两句代码来接收这个方法的返回值:
Person *p3 = [g1 makePerson];
[p3 show];
大家可以再尝试一下在原来代码中添加这些代码后运行一下查看结果。
但是我们会发现,刚才代码虽然执行没有问题,但是却有一些局限性,即每次上帝造人都是造的同一个人Eva,不能自定义。那要根据我们的需求造人呢?
对!我们可以把这个代码改造一下,增加一些参数:
-(Person *)makePersonWithName:(NSString *)name andAge:(int)age andGender:(Gender)gender andLeftLife:(int)leftLife;
这个方法的实现可以写成:
-(Person *)makePersonWithName:(NSString *)name andAge:(int)age andGender:(Gender)gender andLeftLife:(int)leftLife
{
Person *p1 = [Person new];
p1->_name = name;
p1->_age = age;
p1->_gender = gender;
p1->_leftLife = leftLife;
return p1;
}
Person *p3 = [g1 makePersonWithName:@"Eva" andAge:0 andGender:GenderFemale andLeftLife:100];
[p3 show];
请大家再尝试一下把刚才的代码替换成这些后,在Xcode中运行看一下结果,是不是更合适了呢?
2.9 类的属性和方法
类的属性代表什么?
类的属性代表这个类所拥有的东西。
例如:
电灯,它拥有品牌、形状、功率、价格、使用寿命等,而这些它拥有的东西也正是它的属性。
类的方法代表什么?
代表这个类所具备的行为,这个类所具备的功能。
例如:
电灯所拥有的功能:发光
现在我们来谈谈对象作为类的属性。
有这样一个案例:
首先人是一个类,而宠物猫咪也是一个类。那么人是可以拥有一只猫咪的,这个时候这个猫咪就是人的一个属性。
首先,我们先创建一个猫咪类。
@interface Cat:NSObject
{
NSString *_name;
NSString *_color;
int _age;
}
@end
@implementation Cat
@end
然后我们再创建一个人类:
@interface Person:NSObject
{
NSString *_name;
int _age;
float _height;
float _weight;
Cat *_cat;
}
@end
@implementation Person
@end
我们在人类的属性里加上了一句代码为Cat *_cat;这表示我们将猫咪类中的一个对象作为了人类中的一个属性。声明的是一个指针,指向的是一个属于猫咪类的对象。
我们可以看出,属性的本质其实是变量。
如果对象的属性是另外一个类的对象,这个属性仅仅是一个指针变量而已,并没有产生具体的对象。所以我们虽然建立了一个猫咪对象作为人类的属性,但是在我们创建一个具体的猫咪对象前,这个属性是不指向任何对象的,它的值是nil。
我们来总结一下:
A类可以作为B类的属性,代表B拥有A。
但这个属性仅仅是一个变量而已,是一个指针变量,默认值是nil,并没有创建对象。
这个时候如果要正常的使用的话,必须要创建A类的一个具体的对象。
2.10 案例——猜拳游戏
有些代码可能因为我们只学习到这个程度的原因而阅读起来有些困难,大家可以不必在意,在学习完本书所有内容后再回过来看就会一看就明白了。
在猜拳游戏中,会设置两个人物,一个是玩家,一个是电脑本身。玩家通过输入来表达自己出石头、剪刀和布,而电脑也会随机产生相应的石头、剪刀或布应对,会有一个裁判来进行每局比赛的判定,然后记录并输出累积比赛结果。
我相信用C语言来写的话,大家应该都没有问题。但是用OC要怎么写呢?
那我们来分析一下要如何用OC语言来完成这个小游戏的程序设计。
玩家类
机器人类
属性:姓名、出的拳头、得分
方法:出拳->随机出拳
裁判类
属性:姓名
先建立玩家类:
我们发现在建立属性中出的拳头的时候,只有剪刀、石头和布三种,且玩家和机器人都有这个属性,所以适合建立一个头文件,把这三种拳头设置为枚举。
于是我们先建立一个叫Quantou.h的头文件,在里面写下如下代码:
#ifndef Quantou_h
#define Quantou_h
typedef enum
{
QuantouJianDao = 2,
QuantouShiTou = 0,
QuantouBu = 1
}Quantou;
#endif /* Quantou_h */
Player.h的内容如下:
#import <Foundation/Foundation.h>
#import "Quantou.h"
NS_ASSUME_NONNULL_BEGIN
@interface Player : NSObject
{
@public
NSString *_name;
Quantou _selectedQuantou;
int _score;
}
-(void)showQuantou;
-(NSString *)QuantouWithNumber:(int)number;
@end
NS_ASSUME_NONNULL_END
Player.m内容:
出拳的方法应该有如下几步:
先提示玩家选择拳头
显示玩家选择的拳头
将玩家选择的拳头存储到当前对象的属性中
另外因为直接输出显示玩家选择的拳头会只显示0、1或2这样的数字,所以我们还需要建一个方法来进行转换。因为在方法中如果想要调用当前类的另外一个方法,需要用到如下语法:
[self 方法名];
另外还要注意,玩家输入的时候可能会输入不符合语法的内容,即输入了除0、1、2以外的数字,所以要加入一句if结构来防止这样的输入。
最终的代码如下:
#import "Player.h"
@implementation Player
-(void)showQuantou
{
loop:
NSLog(@"亲爱的玩家[%@]请选择你要出的拳头:0代表石头,1代表布,2代表剪刀",_name);
int playerSelect = 3;
scanf("%d",&playerSelect);
NSString *result = [self QuantouWithNumber:playerSelect];
if (playerSelect != 0 && playerSelect != 1 && playerSelect != 2)
{
NSLog(@"出拳错误!请重试!");
goto loop;
}
NSLog(@"玩家[%@]出的拳头是:%@",_name,result);
_selectedQuantou = playerSelect;
}
-(NSString *)QuantouWithNumber:(int)number
{
switch (number)
{
case 0:
return @"石头";
case 1:
return @"布";
case 2:
return @"剪刀";
default:
return @"出拳错误!";
}
}
@end
同样是两个文件,一个头文件Robot.h和实现文件Robot.m。
在Robot.h中写下如下代码:
Robot的属性和玩家的属性是一样的,所以可以复制一份,但是方法不一样,随机出拳的代码需要重新写。
另外,机器人随机出拳后得到的结果也是一个0、1、2中的数字,想要显示具体的出拳种类,还需要一个转换的方法,这个转换的方法和玩家是一样的,所以可以借鉴过来。
于是,
Robot.h的内容为:
#import <Foundation/Foundation.h>
#import "Quantou.h"
NS_ASSUME_NONNULL_BEGIN
@interface Robot : NSObject
{
@public
NSString *_name;
Quantou _selectedQuantou;
int _score;
}
-(void)showQuantou;
-(NSString *)QuantouWithNumber:(int)number;
@end
NS_ASSUME_NONNULL_END
Robot.m的内容为:
#import "Robot.h"
#import <stdlib.h>
@implementation Robot
-(void)showQuantou
{
int robotSelect = arc4random_uniform(3);
NSString *result = [self QuantouWithNumber:robotSelect];
NSLog(@"机器人[%@]出的拳头是:%@",_name,result);
_selectedQuantou = robotSelect;
}
-(NSString *)QuantouWithNumber:(int)number
{
switch (number)
{
case 0:
return @"石头";
case 1:
return @"布";
case 2:
return @"剪刀";
default:
return @"出拳错误!";
}
}
@end
最后是裁判类的设计:
当然也是两个文件,一个是Judge.h,另一个是Judge.m。
裁判的属性比较简单,只有一个名字,但是方法比较复杂,需要引入两个参数,分别是玩家的出拳和机器人的出拳。然后比较这两个参数,得到结果,并记录进总分。
于是,
Judge.h的内容如下:
#import <Foundation/Foundation.h>
#import "Player.h"
#import "Robot.h"
NS_ASSUME_NONNULL_BEGIN
@interface Judge : NSObject
{
@public
NSString *_name;
}
-(void)judgeWithPlay:(Player *)player andRobot:(Robot *)robot;
@end
NS_ASSUME_NONNULL_END
Judge.m的内容:
这里需要注意的是判断输赢的条件,我们可以通过判断语句设定“当玩家出布、机器人出石头或者玩家出剪刀、机器人出布或者玩家出石头、机器人出剪刀的情况下玩家赢”,但是这样的语句比较繁琐,而且写起来很长不便于查看,所以为了遵循简化原则,我们分析一下会发现:
在设定枚举的时候,我们定义了剪刀是2,布是1,石头是0。
那么当玩家出剪刀,机器人出布时,计算机拿到的数据其实是2和1;
这三种情况下,两者的差值分别是1、1和-2,这个时候玩家是赢的。
同样道理,我们可以得到,在两者差值为-1、2的情况下,机器人是赢的。
当两者差值为0时,是平局。
所以最终代码可以这么写:
#import "Judge.h"
@implementation Judge
-(void)judgeWithPlay:(Player *)player andRobot:(Robot *)robot
{
Quantou playerSelect = player->_selectedQuantou;
Quantou robotSelect = robot->_selectedQuantou;
int result = playerSelect - robotSelect;
NSLog(@"我是裁判[%@],我来宣布结果……",_name);
if (result == 1 || result == -2)
{
NSLog(@"恭喜玩家[%@]取得了胜利!",player->_name);
player->_score++;
}
else if (result == -1 || result == 2)
{
NSLog(@"恭喜机器人[%@]取得了胜利!",robot->_name);
robot->_score++;
}
else if (result == 0)
{
NSLog(@"很遗憾,这局是平局!");
}
NSLog(@"玩家[%@]胜%d局 VS 机器人[%@]胜%d局",
player->_name,
player->_score,
robot->_name,
robot->_score);
}
@end
main函数比较简单,只需要分别创建玩家、机器人和裁判各一个对象即可。
在完成了对象的创建以后,我们需要使用一个循环结构来实现反复玩这个游戏的目的,最后用一个if结构来完成退出的目的。当然,在一开始别忘记把头文件引入。
代码如下:
#import <Foundation/Foundation.h>
#import "Player.h"
#import "Robot.h"
#import "Judge.h"
int main(int argc, const char * argv[])
{
@autoreleasepool
{
Player *xiaoMing = [Player new];
xiaoMing->_name = @"小明";
Robot *alphaGo = [Robot new];
alphaGo->_name = @"阿尔法?";
Judge *judge = [Judge new];
judge->_name = @"黑哨";
while (1)
{
[xiaoMing showQuantou];
NSLog(@"轮到机器人[%@]出拳……",alphaGo->_name);
[alphaGo showQuantou];
[judge judgeWithPlay:xiaoMing andRobot:alphaGo];
NSLog(@"你还想再玩一把吗?Y/N");
char ans = 'a';
rewind(stdin);
scanf("%c",&ans);
if (ans != 'y' && ans != 'Y' )
{
NSLog(@"游戏已结束,欢迎下次再来!");
break;
}
}
}
return 0;
}
好了,这样就完成了这个简单的猜拳游戏,大家运行起来看看效果吧!
原文地址:https://blog.csdn.net/m0_58753305/article/details/132117961
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_38670.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!