Swift(一)-类与结构体

615 阅读6分钟

「这是我参与2022首次更文挑战的第9天,活动详情查看:2022首次更文挑战

是面向对象变成思想的核心,在面向对象编程思想中万物皆为对象,类便是描述对象的一种方式;在Object-C语言中,结构体只能定义属性,而不能定义方法;而在Swift语言中,结构体十分强大,它可以进行属性方法的封装。在某些情况下,结构体可以代替来使用;

的某些高级特性是结构体所不具备的,需要根据实际情况进行选择;

初识类与结构体

我们先来看如下代码:

image.png

在上述代码中,我们创建了一个Teacher类,类中有两个属性agename,并创建了一个初始化器init(age: Int, name: String);

接下来,我们直接将class关键字改为struct

image.png

此时,Teacher从一个变成了一个结构体,并且依然能够通过编译,说明在某种程度上,结构体具有相似性;

类与结构体的异同

相同点:

  • 定义存储值的属性;age name
  • 定义方法;func
  • 定义下标,用来使用下标语法提供对其值的访问;subscript
  • 定义初始化器;init
  • 使用extension来拓展功能
  • 遵循协议来提供某种功能

不同点:

  • 有继承的特性,而结构体无法继承;
  • 类型转换能够在运行时检查和解释类实例的类型;
  • 类有析构函数用来释放其分配的资源;deinit
  • 引用计数允许对一个类实例有多个引用;

引用类型和值类型

在使用结构体时,我们首先需要注意的是:

是引用类型,一个类的变量并不直接储存具体的实例对象,而是对存储具体实例内存地址的引用;结构体是值类型

引用类型

image.png

如上图代码所示:我们创建一个t来接收类Teacher的实例对象,但是t并没有直接存储Teacher的实例对象,而是存储对Teacher实例对象内存地址的引用;同样的,如果我们将t赋值给其他变量,比如t1,那么tt1存储的都是Teacher实例变量内存地址的引用;

image.png

我们可以通过**withUnsafePointer**来查看tt1的内存地址,注意tt1需要使用var,如果使用let则无法使用**withUnsafePointer**来分析:

image.png

我们看到,tt1在内存中的地址是挨着的,中间相差8字节;接下来我们通过**x/8g**来查看t的内存地址的存储信息:

image.png

可以看到t对应的内存地址上存储的8个字节的数据是**0x00000001072213a0**,刚好对应了Teacher实例对象的内存地址**0x1072213a0**;也就是说tt1中间相差的8字节数据存储的是对应失恋对象的内存地址;

pop命令的区别在于使用po只会输出对应的值,而p则会返回值的类型以及命令结果的引用; x/8g:读取内存中的值(8g是以8字节格式输出);

image.png

值类型

相比较于引用类型的变量中存储的是地址,值类型存储的就是具体的实例或者说具体的值;

image.png

如上述代码所示:ss1是两个不同的变量,但是他们存储的是否是相同的内容呢?我们通过po执行来查看一下:

image.png

可以看到在ss1中存储的是agename的值,并不是地址;此时,我们修改s1age属性的值,来看一下s中是否会同样发生变化:

image.png

修改s1中的数据,s中的数据并没有发生变化,说明ss1中存储的是不同的数据;

一般情况下:引用类型存储在上,值类型存储在上;

内存区域基本概念图如下:

image.png

  • 图中所示:低地址在下,高地址在上;
  • MachO文件从一个虚拟地址开始,只有一部分内存地址可供我们操作;
  • 可操作的内存区具体分为:栈区堆区全局区常量区_text指令区;(全局区常量区_text指令区也可统称为全局区);
  • 栈区的拉伸从高地址低地址拉伸;堆区低地址高地址拉伸;当栈区堆区重合将会发生堆栈溢出
  • 栈区:存放局部变量函数运行过程中的上下文
  • 堆区:存放所有对象
  • 全局区Global存放全局变量常量代码区

那么如何查看内存地址是在还是上呢?这里有个工具可以帮我们打印出内存地址的情况;

image.png

如上述代码,我们可以通过cat address指令来分析得出,局部变量age存放在栈区

在我们的MachO文件中有多个段Segment,每个段有不同的功能;每个段又分为很多个小的Section

  • TEXT.text:机器码;
  • TEXT.cstring:硬编码的字符串;
  • TEXT.const:初始化过的常量
  • DATA.data:初始化过的可变的静态/全局数据;
  • DATA.const:没有初始化过的常量;
  • DATA.bss:没有初始化的静态/局部变量;
  • DATA.common:没有初始化过的符号声明;

image.png

如上述代码,根据打印结果:a是初始化过的全局数据,存放在Data.dataage还没有初始化,存放在Data.common

类与结构体的内存分布

可以使用frame variable -L指令来查看内存分布情况

结构体的内存分布

image.png

  • 结构体内存空间在上;
  • age在低地址,name在高地址;

类的内存分布

接下里,我们将Struct修改为class,来看一下分析结果:

image.png

  • 内存空间在上;
  • age在低地址,name在高地址;

内存分配流程:

上分配8字节的内存大小,用来存放s;在Student初始化的过程中,在上寻找合适的内存区域,将该内存区域的地址返回,然后将agename的值拷贝到堆的内存空间上;最后将上的内存地址,指向堆区

类与结构体混用时内存分布

image.png

  • 结构体s内存地址在栈区
  • 结构体中的类p的内存地址在堆区

再分配内存的过程中,会有时间速度的区别。我们可以通过GitHub上的StructVsClassPerformance工程来分析;

在选择数据结构类型时,尽可能优先选用结构体结构体上,是线程安全的;在内存分配上,栈区也要比堆区快;