惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Cisco Blogs
Schneier on Security
Schneier on Security
T
Tor Project blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Tenable Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
T
Threat Research - Cisco Blogs
C
CERT Recently Published Vulnerability Notes
Security Latest
Security Latest
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
NISL@THU
NISL@THU
L
Lohrmann on Cybersecurity
Scott Helme
Scott Helme
Webroot Blog
Webroot Blog
Project Zero
Project Zero
Google Online Security Blog
Google Online Security Blog
The Last Watchdog
The Last Watchdog
Spread Privacy
Spread Privacy
Hacker News: Ask HN
Hacker News: Ask HN
PCI Perspectives
PCI Perspectives
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
W
WeLiveSecurity
Attack and Defense Labs
Attack and Defense Labs
D
Darknet – Hacking Tools, Hacker News & Cyber Security
N
News | PayPal Newsroom
Help Net Security
Help Net Security
The Hacker News
The Hacker News
H
Heimdal Security Blog
O
OpenAI News
S
Security @ Cisco Blogs
N
News and Events Feed by Topic
Cyberwarzone
Cyberwarzone
Simon Willison's Weblog
Simon Willison's Weblog
G
GRAHAM CLULEY
www.infosecurity-magazine.com
www.infosecurity-magazine.com
博客园 - 叶小钗
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Hacker News - Newest:
Hacker News - Newest: "LLM"
T
Tailwind CSS Blog
大猫的无限游戏
大猫的无限游戏
A
Arctic Wolf
I
Intezer
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
S
Security Affairs
P
Proofpoint News Feed
S
Secure Thoughts
腾讯CDC
Google DeepMind News
Google DeepMind News
量子位
罗磊的独立博客

一个工匠

AI 发展临界点 - 快上车 流式响应(Chat):HTTP 响应体字节流的读取与解析 iPhone 截屏高效翻译 Flash Open Terminal iPhone 侧载安装 Xcode Symbolic Debug AI 指北 投资风险收益评估 Mac 快速修改系统快捷键 Git Record passkey How to pay apple frameworks Mac - Command Line Tools DNS 的 CNAME 是如何工作的 WebRTC 的一些解释 SSE 指南 1.1.1.1、WARP 和 MASQUE 移动端换端方案与场景还原 设备发现 分辨率 - 像素密度 - HiDPI Mac 字体 ESP32 trump - 川普 币圈 - 虚拟货币 【旅游】富士山 【旅游】sakura(樱花) 【旅游】千岛湖 我的 2023 【Swift】中文词语纠错 计算机字符编码与内存编码 - Unicode、UTF8、String 从汇编角度理解 “函数调用栈” 和 “有栈协程” 在 Xcode 中更好的使用 Swift 通过 Swift Package 制作二进制库 Arc 浏览器 - 意外的惊喜 【Swift 三方源码 1】SwiftShell 高效的命令行工具 Swift 脚本开发环境搭建 rime - 小鹤双拼 【旅游】沿海旅游 (威海、青岛) 好记性远远不如烂笔头 高级语言小记 又一年高考 Vision 未来已来 致敬耗子哥 Socket 与 ICMP 对互联网隐私的一些想法 网络代理是如何工作的 (致敬 Surge) 又十年 - 至三十而立 双拼 - 少有的高回报效率技能 Vim 技巧 我的 2022 三年疫情五味杂陈 - 下 老梗 - 浏览器按回车后发生了什么 三年疫情五味杂陈 生活推荐清单 Shell 和进程 有计划的努力 内卷 不确定性 数学可以保障逻辑自洽 这些年我的软件清单 决定做一些改变 函数调用栈之彻底理解 锁 - 共享数据安全指↑ 江苏女有感 碎碎念 - 2022 碎碎念 - 2021 打算记录一些碎碎念 多重性格就是逆天改命 从 Core Foundation 看更大世界 TCP 数据传输过程分析 iOS main 文件中的 @autoreleasepool 的真实用途 多线程的难点在哪里 Objective-C 和 Runtime 早起打卡计划 ing 打比方 IM 和 Socket 的关系及 Heart 的必要性 如果我的人生突然只剩下 8 小时... 胡说八道之雪粉 Array 和 LinkedList 的深刻认知 Swift struct (值类型) 与 class (引用类型) 区别及三种传参机制深刻关系和使用 逃离信用卡和花呗 网聚人的力量 - 网易 绿皮火车 喝酒十之八九会误事 HKong 胡说八道之云服务 互联网网络传输协议理解坑点 CPU 的制作 我心里一直有一个念想 网络基础协议 为什么 32 位操作系统只能用 4G 内存空间 努力和天赋 洗牙 RAC-Api-Summer iOS 内存原理 Block 闭包简查记录 《白夜行》 谈谈事件驱动 老博新开 祭首
Swift_let_var_struct_class
海驴 · 2019-12-05 · via 一个工匠

0x01 问题描述和表现

对于 let 和 var,struct 和 class,分成两类来说,很多人比较容易理解。let:不可变,var:可变,struct:栈空间,class:堆空间。
当我想要确认他们的原理的时候,截止发文为止,我没有在中文互联网上搜索到相关信息。所以我把他们的原理写在下面。

下面的示例,你能够理清楚多少?

1
2
3
4
5
6
7
8
struct Person1 {
let name1: String = "name1"
var name2: String = "name2"
}
class Person2 {
let name1: String = "name1"
var name2: String = "name2"
}

上面定义了一个 struct,一个 class,我们下面的代码会有什么结果?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let test1 = Person1()
test1.name1 = "t"
test1.name2 = "t"
test1 = Person1()

var test2 = Person1()
test2.name1 = "t"
test2.name2 = "t"
test2 = Person1()

let test3 = Person2()
test3.name1 = "t"
test3.name2 = "t"
test3 = Person2()

var test4 = Person2()
test4.name1 = "t"
test4.name2 = "t"
test4 = Person2()

这里我们定义了四个对象变量,两个 let,两个 var。两个 let 里面一个是 struct 对象,一个是 class 对象。两个 var 也是一样。然后 struct 里面有一个 let 和一个 var 属性,class 也是一样。

上面的代码会报错,你知道的。但是你知道会是什么样的报错吗?

如上图所示, 同样的 let 对象变量 (test1/test3),struct 和 class 有不同的错误。 同样的 struct,let 和 var 也有不同的错误。

重点来说,

  1. 如果我们用 let 声明了一个 struct 对象,那么基本可以确定内部属性一个都不能改变了(不管内部属性是用 let 或者 var 修饰)。如果我们用 let 声明一个 class 的对象,那么内部属于 var 的属性还是可以改变的。
  2. 如果我们用 var 声明一个 struct 对象,那么表现同 class 对象一样。

通过上面两点,我们可以发现,struct 和 class,他们之间的差别,肯定不仅仅是栈和堆内存的关系,不是吗?

实际上,struct 比 class 有更严格的约束。而 let 和 var 的可变和不可变也有更深的原理。

0x02 let 和 var 到底是什么?

如果理解 C 里面的指针和 const 不变量,那么非常容易理解了。
我们在 C 里面定义下面四个变量,

1
2
(1)
const int age1 = 21;
1
2
3
(2)
int temp = 22;
const int *age2 = &temp;
1
2
3
(3)
int temp = 23;
int * const age3 = &temp;
1
2
3
(4)
int temp = 24;
const int * const age4 = &temp;

这四个常量 age 的存储,如下图所示:

对于 age1,我们就不能通过 age1 = 0;这样的语句修改值了。
对于 age2,因为 const 标记的是值,所以
*age2 = 0;是不行的,会报错。但是我们可以通过 age2 = &other;这样的语句修改 age2 对应的指针值。
对于 age3,因为 const 标记的是指针,所以
age3 = &other;* 是不行的,会报错。但是我们可以通过
age3 = 0;** 这样的语句修改 age3 对应的数值。
对于 age4,因为数值和指针都是只读的,都是常量,什么也修改不了。

我们发现,其实 Swift 里的 let,就是 C 里的 const。** 但是在 struct 和 class 中表现是不一致的。struct 类型的 let 变量就是“(4)int temp = 24;const int * const age4 = &temp;”类型,什么都修改不了。而 class 类型的 let 变量,就是“(3)int temp = 23;int * const age3 = &temp;”** 类型,本身对象指针无法修改,但是内部属性在修饰符允许的情况下是可以修改的。

我们通过上面的报错信息验证一下刚才的观点。
在代码的第 15 行,test1.name2 = “t”报错为 Cannot assign to property: ‘test1’ is a ‘let’ constant,代码的第 16 行,test1 = Person1() 报错为 Cannot assign to value: ‘test1’ is a ‘let’ constant。他们的报错原因完全相同,而 name2 本身是 var 属性。所以 15 行和 16 行的报错不是 struct 内部决定的,而是由 let test1 这个 let 属性决定的。
同样的,我们看到代码第 25 行,test3.name2 = “t”没有报错,和第 15 行出现报错对比,可以确定同样的 let 类型的对象变量,在修饰 struct 和 class 的时候,他们的行为却是不一致的。

(我相信有不少朋友,这里会饶进入一个洞里,那就是 C 里面的 const 修饰的是 Int 基础类型,而这里 let 修饰的是 struct 对象,struct 可不是 Int 或者 String,struct 可是对象啊!他们是否有可比性?如果没有可比性,那么上面的结论是否还能成立?这里做说明:Int、Char 是啥?在内存里面就是一块存储区域。struct 呢?也是一块存储区域。对象是啥?是方便我们开发所汇总出来的一套编程思想,即面向对象编程。过了 IDE,解析器和编译器还会管你是 Int 基础类型或者对象类型?不会管的。在广东人面前,什么都是肉,即使你是福建人。退一万步来说,Swift 里面,Int、String 也是通过 struct 来实现的)

那我们在看看 var。test2 和 test4 两个 var 变量,虽然一个是 struct 对象,一个是 class 对象,但是他们的表现却是一致的。其实 var 就是非 let 修饰符。let 修饰符和 const 类似,那么 var 其实就是省略了 const 的修饰符。我们举下面例子

1
2
(1)
const int age = 10;
1
2
(2)
not const int age = 10;
1
2
(3)
int age = 10;

上面例子中,not const 修饰符是我杜撰的。意思就是非 const。那么我们可以发现,not const 就是 var
我们还可以发现,(2)和(3)其实是等价的。而(3)仅仅是省略了 const 修饰符,所以苹果一开始发布 Swift 语言的时候,开始完全可以指定下面的规则:

Swift 文档官方指南:
在 Swift 中,let 表示不可变。如果什么都不写,那么默认可变。如:
const age = 10 age = 10
编译器会根据语意自动识别 age 为 Int 类型,所以您无需显示添加 Int,如:const age: Int = 10 age: Int = 10

苹果没有这么做,或许是 var 和 let 一起写出来的代码,一来易于识别,二来看起来又有美感。而且天下语言基础语法一大抄,JS 也是同时有 let 和 var。而 Dart 就是有 var 和 const。

0x03 struct 和 class 的区别到底是什么?

网上已经能够看到很多两者的区别,如栈,堆,继承等。
这里,我想从属性约束上说一点,这点网上说的不多。

对于 class,我们都是非常熟悉的了,毕竟面向对象编程这么久,也无需多言。属性是 var,那么就是完全可变的。如果是 let,那么回到上一个论题。
对于 struct,我们看下面的代码截图:

我们发现,在 struct 里面,不管属性是 let 还是 var,方法(内部函数)都是无法直接修改该属性的。如代码行 10 和 13 行所示。
如果我们需要修改内部属性,需要在方法前面加上 mutating 关键字。这在 class 里面是不存在的,说明 struct 相比 class,属性约束要强一些。
当然,我们看代码 16 行,对于 let 属性,mutating 也无能为力,这个和 class 倒是一样的。毕竟 let 本身就是完全不可变的。

所以,我们得出一个结论,struct 相比 class 来说,在属性约束上,是要强一些的。

0x04 一个蹊跷点

这里给大家看一个蹊跷的地方,如下图所示:

上面的代码,编译和运行都不会有问题。
我们来分析一下问题点:

  1. 对于 Person1 内部来说,var 修饰的 name2 属性,是不能随便改变的。如果要改变,必须要通过 func 前面添加 mutating 来实现。本质上这是一种内部约束。
  2. 对于 var 修饰的 test2 对象来说,可以直接通过变量修改 name2 属性。就是说,可以越过 struct 内部约束直接修改变量值。

这里的蹊跷点就在于,外部约束大于内部约束了。我感觉不可思议,但这的确是一个事实。

0x05 总结

  1. Swift 里的 let,就是 C 里的 const。** 但是在 struct 和 class 中表现是不一致的。struct 类型的 let 变量就是“(4)int temp = 24;const int * const age4 = &temp;”类型,什么都修改不了。而 class 类型的 let 变量,就是“(3)int temp = 23;int * const age3 = &temp;”** 类型,本身对象指针无法修改,但是内部属性在修饰符允许的情况下是可以修改的。
  2. struct 相比 class,内部属性约束会更强一些。

下面给出我这边完整测试代码(可通过代码右上方复制按钮直接复制):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import Foundation

struct Person1 {
let name1: String = "name1"
var name2: String = "name2"
let arr1: [Int] = []
var arr2: [Int] = []

func change1() {
name1 = "test"
}
func change2() {
name2 = "test"
}
mutating func change3() {
name1 = "test"
}
mutating func change4() {
name2 = "test"
}
}

class Person2 {
let name1: String = "name1"
var name2: String = "name2"
let arr1: [Int] = []
var arr2: [Int] = []

func change1() {
name1 = "test"
}
func change2() {
name2 = "test"
}
mutating func change3() {
name1 = "test"
}
mutating func change4() {
name2 = "test"
}
}

let test1 = Person1()
test1.name1 = "t"
test1.name2 = "t"
test1.arr1.append(1)
test1.arr2.append(1)
test1 = Person1()

var test2 = Person1()
test2.name1 = "t"
test2.name2 = "t"
test2.arr1.append(1)
test2.arr2.append(1)
test2 = Person1()

let test3 = Person2()
test3.name1 = "t"
test3.name2 = "t"
test3.arr1.append(1)
test3.arr2.append(1)
test3 = Person2()

var test4 = Person2()
test4.name1 = "t"
test4.name2 = "t"
test4.arr1.append(1)
test4.arr2.append(1)
test4 = Person2()


大千世界,真的无奇不有。很多自然科学无法解答的问题,都是奇闻逸事。
如果有人说自己观察到尸体动了一下,但是本该绝对不可能动。这怎么用科学来解释?说这个人撒谎?但是这是一个正直刚正的人,又绝对不可能撒谎!
我老家的老人们说自己看到了已经离开了很久的村里人在路上走路,这又该如何解释?他们可能撒谎了,也可能没有撒谎。
我们可以用压力过大、悲伤过度、思念强烈等客观原因来分析,但也没有科学依据来证实这些客观原因能够导致上面的现象。
世界本就是无奇不有,对我们有恶意的,我们不要贸然进入。对我们没有恶意的,我们也无需害怕和担忧。
海底的生物有多奇特,人类不知道。地下深处结构如何,人类也未可知。
很多人认为宇宙飞船都上天了,在这么发达的社会,无法用科学来解释的问题,都是恐怖的。
但是科学才触及到真理的哪个边界啊!或许科学连真理的边缘还没有触及到呢。
当前人类最应该害怕的,难道不应该是人类本身么?