【Objective-C】2 类与对象
第二节 类与对象
01 对象在内存中的存储
- 【回顾】内存中的五大区域
- 栈:局部变量
- 堆:用户手动申请的字节空间 ---> malloc calloc realloc
- BSS 段:未被初始化的全局变量 静态变量
- 数据段(常量区):已初始化的全局变量 静态变量 常量数据
- 代码段:代码
1.1 类加载
在创建对象时,必然需要访问类。
在声明一个类的指针变量时,就会访问类。
在程序运行期间,当类被第一次访问时,会将类存储到代码段区域 ---> 这个过程被称为类加载
注意:
- 类只有在第一次被访问时才会进行类加载
- 一旦类被加载到代码段,直到程序结束才会被释放
1.2 对象的存储
Person *p1 = [Person new]; // 【 Person *p1 】:会在栈内存中申请一块空间,声明一个 Person 类型的指针变量 p1 // 【 [Person new] 】---> new 的作用: // 在堆内存中申请一块合适大小的空间,根据 Person 类的模版创建了一个对象 // 对象中还额外有一个属性 isa ,是一个指针,指向 Person 类在代码段中存储的位置 // 给对象中的属性做初始化:基本数据类型 - 0; C 的指针类型 - NULL; OC 的类指针 - nil // 返回对象的地址 ---> p1 指针
注意:
-
对象是通过存储在栈区的指针(p1)来访问的
-
对象存储在堆区,其中只存储属性,不包含类的方法
-
方法调用:对象 ---> 属性 isa 指针 ---> 类 ---> 方法
-
为什么对象中不存储方法?--> 同一个类的多个对象没必要存储相同代码(类的方法)---> 节省空间
-
若在对象创建时没有给其属性做初始化,会自动赋值:
基本数据类型 - 0; C 的指针类型 - NULL; OC 的类指针 - nil
02 nil 与 NULL
- NULL
- 只能作为指针变量的值,表示该指针不指向内存中的任何一个空间
- 本质上是一个宏,其实等价于 0
- nil
- 只能作为指针变量的值,表示该指针不指向内存中的任何一个空间
- 本质上是一个宏,其实等价于 0
---> 因此,二者其实是一样的,可以互换
-
使用建议:一般 C 指针使用 NULL,OC 中的类指针使用 nil
int* p1 = NULL; // 不指向任何空间 Person* p2 = nil; // 不指向任何对象 p2->_name = @"jack"; // 会报错,因为 p2 不指向任何对象,所以其中不包含对象的属性 [p2 eat]; // 不会报错,但方法不会执行
03 多个指针指向同一个对象
Person* p1 = [Person new]; // new 新创建了一个对象,该对象的地址由 p1 指针存储 p1->_name = @"rose"; p1->_age = 18; Person* p2 = p1; // 将 p1 中存储的地址赋值给 p2,所以 p1 p2 指向同一个对象 p2->_name = @"小花"; // 此时 p1->_name = @"小花" // 因为无论是通过 p1 还是 p2 去访问,访问的对象是同一个
04 分组导航标记
//【1】#pragma mark 分组名 #pragma mark 动物类的声明 // 根据标记的位置,在导航中显示分组名【动物类的声明】 //【2】#pragma mark - #pragma mark - // 在导航条中对应的位置显示一条分割线 //【3】#pragma mark - 分组名 #pragma mark - 植物类的声明 // 在导航条中对应的位置显示一条分割线,分割线后显示分组名【植物类的声明】
05 方法与函数
void cFunction(){} // 函数 - (void)ocMethod; // 方法
-
相同点:
都是用来封装代码的 --> 将一段代码封装起来,表示一个独立的功能
-
不同点:
-
语法不同
-
定义的位置不同
- 函数不能定义在函数的内部 / @interface 的大括号中,其他位置均可以定义函数
- 方法的声明只能写在 @interface 的大括号外,方法的实现只能写在 @implementation 之中
--> 即使某个函数写在类中(这种写法是不规范的),这个函数也并不属于类,创建的对象中也不会包含函数
-
调用方式不同
- 函数可以直接调用
- 方法必须通过对象来调用
-
方法属于类,函数是独立的
-
06 易错点归纳
-
@interface / @implementation 不可相互嵌套
-
类必须先声明,再实现,二者缺一不可(特殊情况下,可以只有实现没有声明,不推荐)。
即使没有方法,@implementation 也不可缺失
-
声明必须在使用之前,实现可以放在使用之后
-
命名规范:属性名一定要以 _下划线开头;类名首字母大写
-
属性不允许在声明时初始化,会报错
-
方法一定要通过对象调用
-
若方法只有声明没有实行,只会有语法警告,编译 & 执行不会报错
如果创建了一个对象,当该对象调用的方法只有声明没有实现,会在运行时报错:
unrecognized selector sent to instance 。。。
这条报错信息表示调用的方法不存在 / 方法只有声明无实现
07 多文件开发
-
如果把代码全部写在 main.m 源文件下
---> 不利于后期的维护,也不利于团队的开发
推荐的方式:把一个类写在一个模块中。
一个模块至少包含两个文件:
-
.h 头文件:类的声明.
--> 因为需要使用 Foundation 框架中的类,所以该文件中需要引入 #import <Foundation/Foundation.h>
-
.m 实现的文件:类的实现
需要先引入模块的头文件,使其获得类的声明
---> 如果要使用类,只需引入该模块的头文件即可
创建模块的快捷方式:直接创建 【cocoa class】,自动创建两个文件,且文件中已包含定义类的语法,默认类名 = 文件名(类名可修改,但建议文件名和类名应保持一致)
08 对象与方法
-
类的本质:一个自定义的数据类型
- 数据类型的本质:在内存中开辟空间的模版
---> 类的对象也可以作为方法的参数
8.1 方法的参数是对象
- (void)test1:(Person*)p;
注意:
- 调用时参数对象必须为指定类的对象
- 当对象作为方法的参数时,参数传递为地址传递,因此调用方法有可能会改变对象的属性
8.2 方法的返回值是对象
- (Person*)test2{ Person* p1 = [Person new]; p1->_name = "bob"; p1->_age = 1; return p1; } int main(){ Person* p1 = [Person new]; Person* p2 = [p1 test2]; }
09 将对象作为类的属性
-
一个 target 中的类无法直接在另一个target中访问。
如果想要使用:- 找到需要的类文件 .h & .m,右键选择【show in finder】
- 选中类文件 .h & .m,直接拖拽到当前 target 下
- 在弹窗中只勾选 【copy it if needed】& 【create groups】,拷贝完成
-
类的属性 = 这类事物有什么;类的方法 = 这类事物能干什么/有什么功能
举例:类名 - 人;类的属性 - 姓名,性别,。。---> 人可以有狗,而狗也是一个类
---> 类的对象也可以作为类的属性
-
属性的本质是变量,变量的类型是由类的声明决定的
---> 如果对象 a 的属性是另一个类的对象 b,并不会创建一个对象,因为这个变量的本质是一个指针,只能存储地址
* 练习: 猜拳游戏
- 这个练习题在视频课中有详细讲解,且在后续学习中会不断完善改进
注意:
-
枚举和结构体的定义位置:如果有多个类要使用,建议单独定义在一个头文件中,方便使用
-
类名必须不同 ---> 加大写前缀
-
在方法中调用当前对象的另一个方法:[self 方法名]
self 代表当前对象
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix