【Objective-C】2 类与对象

第二节 类与对象


01 对象在内存中的存储

  • 【回顾】内存中的五大区域
    1. 栈:局部变量
    2. 堆:用户手动申请的字节空间 ---> malloc calloc realloc
    3. BSS 段:未被初始化的全局变量 静态变量
    4. 数据段(常量区):已初始化的全局变量 静态变量 常量数据
    5. 代码段:代码

1.1 类加载

在创建对象时,必然需要访问类。
在声明一个类的指针变量时,就会访问类。
在程序运行期间,当类被第一次访问时,会将类存储到代码段区域 ---> 这个过程被称为类加载

注意:

  1. 类只有在第一次被访问时才会进行类加载
  2. 一旦类被加载到代码段,直到程序结束才会被释放

1.2 对象的存储

Person *p1 = [Person new];
// 【 Person *p1 】:会在栈内存中申请一块空间,声明一个 Person 类型的指针变量 p1
// 【 [Person new] 】---> new 的作用:
// 在堆内存中申请一块合适大小的空间,根据 Person 类的模版创建了一个对象
// 对象中还额外有一个属性 isa ,是一个指针,指向 Person 类在代码段中存储的位置
// 给对象中的属性做初始化:基本数据类型 - 0; C 的指针类型 - NULL; OC 的类指针 - nil
// 返回对象的地址 ---> p1 指针

注意:

  1. 对象是通过存储在栈区的指针(p1)来访问的

  2. 对象存储在堆区,其中只存储属性,不包含类的方法

  3. 方法调用:对象 ---> 属性 isa 指针 ---> 类 ---> 方法

  1. 为什么对象中不存储方法?--> 同一个类的多个对象没必要存储相同代码(类的方法)---> 节省空间

  2. 若在对象创建时没有给其属性做初始化,会自动赋值:

    基本数据类型 - 0; C 的指针类型 - NULL; OC 的类指针 - nil


02 nil 与 NULL

  • NULL
  1. 只能作为指针变量的值,表示该指针不指向内存中的任何一个空间
  2. 本质上是一个宏,其实等价于 0
  • nil
  1. 只能作为指针变量的值,表示该指针不指向内存中的任何一个空间
  2. 本质上是一个宏,其实等价于 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; // 方法
  • 相同点:

    都是用来封装代码的 --> 将一段代码封装起来,表示一个独立的功能

  • 不同点:

    1. 语法不同

    2. 定义的位置不同

      • 函数不能定义在函数的内部 / @interface 的大括号中,其他位置均可以定义函数
      • 方法的声明只能写在 @interface 的大括号外,方法的实现只能写在 @implementation 之中

      --> 即使某个函数写在类中(这种写法是不规范的),这个函数也并不属于类,创建的对象中也不会包含函数

    3. 调用方式不同

      • 函数可以直接调用
      • 方法必须通过对象来调用
    4. 方法属于类,函数是独立的


06 易错点归纳

  1. @interface / @implementation 不可相互嵌套

  2. 类必须先声明,再实现,二者缺一不可(特殊情况下,可以只有实现没有声明,不推荐)。

    即使没有方法,@implementation 也不可缺失

  3. 声明必须在使用之前,实现可以放在使用之后

  4. 命名规范:属性名一定要以 _下划线开头;类名首字母大写

  5. 属性不允许在声明时初始化,会报错

  6. 方法一定要通过对象调用

  7. 若方法只有声明没有实行,只会有语法警告,编译 & 执行不会报错

    如果创建了一个对象,当该对象调用的方法只有声明没有实现,会在运行时报错:

    unrecognized selector sent to instance 。。。

    这条报错信息表示调用的方法不存在 / 方法只有声明无实现


07 多文件开发

  • 如果把代码全部写在 main.m 源文件下

    ---> 不利于后期的维护,也不利于团队的开发

推荐的方式:把一个类写在一个模块中。

一个模块至少包含两个文件:

  1. .h 头文件:类的声明.

    --> 因为需要使用 Foundation 框架中的类,所以该文件中需要引入 #import <Foundation/Foundation.h>

  2. .m 实现的文件:类的实现

    需要先引入模块的头文件,使其获得类的声明

---> 如果要使用类,只需引入该模块的头文件即可

创建模块的快捷方式:直接创建 【cocoa class】,自动创建两个文件,且文件中已包含定义类的语法,默认类名 = 文件名(类名可修改,但建议文件名和类名应保持一致)


08 对象与方法

  • 类的本质:一个自定义的数据类型

    • 数据类型的本质:在内存中开辟空间的模版

    ---> 类的对象也可以作为方法的参数

8.1 方法的参数是对象

- (void)test1:(Person*)p;

注意:

  1. 调用时参数对象必须为指定类的对象
  2. 当对象作为方法的参数时,参数传递为地址传递,因此调用方法有可能会改变对象的属性

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中访问。
    如果想要使用:

    1. 找到需要的类文件 .h & .m,右键选择【show in finder】
    2. 选中类文件 .h & .m,直接拖拽到当前 target 下
    3. 在弹窗中只勾选 【copy it if needed】& 【create groups】,拷贝完成
  • 类的属性 = 这类事物有什么;类的方法 = 这类事物能干什么/有什么功能

    举例:类名 - 人;类的属性 - 姓名,性别,。。---> 人可以有狗,而狗也是一个类

    ---> 类的对象也可以作为类的属性

  • 属性的本质是变量,变量的类型是由类的声明决定的

    ---> 如果对象 a 的属性是另一个类的对象 b,并不会创建一个对象,因为这个变量的本质是一个指针,只能存储地址


* 练习: 猜拳游戏

  • 这个练习题在视频课中有详细讲解,且在后续学习中会不断完善改进

注意:

  1. 枚举和结构体的定义位置:如果有多个类要使用,建议单独定义在一个头文件中,方便使用

  2. 类名必须不同 ---> 加大写前缀

  3. 在方法中调用当前对象的另一个方法:[self 方法名]

    self 代表当前对象

posted @   Z/z  阅读(45)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示