
// 这是一段 swift 代码 var hello = [10, 20, 30, 40, 50, 60] var foo = hello[2..<5] // 表示索引范围是 2 ,3 ,4 foo[2] = 0 print(foo) // 先看看你的程序员直觉,输出结果是什么 然后随便 google 找个 swift playground 运行这段代码,你就知道为什么苹果现在 bug 越来越多了。
1 ourstars 1 天前 直觉理解是[30,40,0],但是运行之后是[0,40,50]。 foo 用的是 hello 的索引范围造成了[0,40,50]的结果。 |
3 TianDogK48 23 小时 24 分钟前 via iPhone 目测这个语法不常用,所以问题应该不大; go 里面 slice 扩容,也有坑,但是目测 go 的 slice 很少用,都是直接 make |
4 raycool 23 小时 23 分钟前 看来都是 slice 的不同理解导致的。 |
5 smlcgx 23 小时 20 分钟前 via iPhone 这样看起来 swift 更偏向自然语言一点,几就是几 |
6 butanediol2d 22 小时 42 分钟前 hello 是 Array<Int> foo 是 ArraySlice<Int> |
7 craftsmanship 22 小时 28 分钟前 via Android 如此说来 JS 早先那些垃圾设计可能导致的 bug 只会更多。。 |
8 xuejianxianzun 20 小时 50 分钟前 @craftsmanship 怎么又扯到 js 了,js 在这个场景里没有反直觉的行为。 |
9 hash 17 小时 9 分钟前 系统层核心实现是一坨屎,与语言设计无关 |
10 Building 16 小时 10 分钟前 都按直觉那不同语言反人类的语法简直不要太多,当然不影响 Swift String 的设计才是最为逆天的东西 |
11 cpstar 15 小时 31 分钟前 @xuejianxianzun 8# 以我不动 swift ,和 1#的情况,我觉得基本上就是 js 的逻辑: foo_js[2]=hello[2]/*30*/,foo_js[3]=hello[3]/*40*/,foo_js[4]=hello[4]/*50*/ 而 foo_js[0]=undefined,foo_js[1]=undefined 于是 foo_js[2]=0 之后,foo_js={"2":0,"3":40,"4":50} |
12 Greendays 15 小时 27 分钟前 和语言关系不大的,既然做了开发掌握这个语言的语法是基本要求。Bug 多只是需求超过了能力,不得不赶工和减少测试流程造成的。 |
13 Lin0936 15 小时 20 分钟前 再逆天的语言也该经过测试啊 |
14 june4 15 小时 16 分钟前 @craftsmanship js 真没有啥绕不过去的垃圾设计,特别是 ts 上身后,底子非常干净纯洁,现在只有一个日常使用我觉得不爽,就是搞出二个 null/undefined 类型。 |
15 xiangyuecn 15 小时 8 分钟前 苹果拉的屎就是香,没啥毛病 能咽下去的 |
16 freeloop1 14 小时 57 分钟前 那个 foreach 的语法我是最蚌埠住的,我总感觉是少写了括号啥的,太难受了。 |
17 a33291 14 小时 52 分钟前 foo 是 hello 的视图吧? 现在很多语言/库都提供这种东西,减少 copy,不过大部分是只读的比较合理 |
19 fadaixiaohai 14 小时 33 分钟前 从 swift6 开始越来越复杂,关键词越搞越多,问题是有些关键词编译器都支持不太好,搞不好就容易 crash ,对新人也不太友好 |
20 PlG5sBkXD1ziLeGB 14 小时 30 分钟前 via iPhone swift 的语法真是丑到爆,OC 的语法虽然跟裹脚布一样长,但是可读性非常强 |
21 deplives 14 小时 27 分钟前 |
22 kfpenn 14 小时 24 分钟前 @PlG5sBkXD1ziLeGB 赞同 |
23 liuhan907 14 小时 11 分钟前 这个输出 [0, 40, 50] 不才是符合直觉的? |
24 cpper 14 小时 8 分钟前 每个语言有每个语言的用法,不要陷入奇巧淫技的细节中 |
25 liuhan907 14 小时 3 分钟前 哦,大意了,foo[2] 啊。那这是傻逼设计,我看的不够仔细啊 -_- |
26 IDAEngine 14 小时 2 分钟前 这是一个关于 Swift 数组切片( Array Slice )行为的有趣问题。 我的“程序员直觉”告诉我,输出结果是: [0, 40, 50] 解释(为什么我的直觉是这样): 切片操作: var hello = [10, 20, 30, 40, 50, 60] var foo = hello[2..<5] 创建了一个数组切片( Array Slice )。这个切片包含 hello 中索引 2,3,4 的元素。 foo 此时是 [30, 40, 50]。 关键点:数组切片的索引继承了原数组的索引。所以 foo 内部元素的索引是 2,3,4 ,而不是从 0 开始。 修改切片: foo[2] = 0 尝试修改 foo 中索引为 2 的元素。 在 foo 中,索引 2 对应的值是 30 。 修改后,foo 变成了 [0, 40, 50](注意,尽管内部值变了,它的索引结构仍然是 2,3,4 )。 打印切片: print(foo) 打印切片的内容。Swift 在打印数组切片时,会打印其包含的元素值。 因此,输出是 [0, 40, 50]。 |
29 yuanxing008 13 小时 53 分钟前 可能 go 和 python 写的太久了 我的第一直觉就是[0,40,50] 后面看你们讨论才发现这个设计很扯,但是写习惯了就好了 |
30 2en 13 小时 51 分钟前 居然不是 30,40,0 |
31 leeg810312 13 小时 47 分钟前 @IDAEngine 你的直觉才有问题。无论是创建新数组,还是视图,都不该是这样的行为。按业务逻辑,这段代码描述应该是这样,从 A 数组中取第 2-4 (从 0 开始)的元素,作为 B 数组,B 数组的第 2 个元素值改为 0 。关键点,B 的第 2 个元素,实际行为却去改 A 的第 2 个,这完全是反直觉的,即使 B 是视图,非实际创建的新数组,也应当把 B 当作一个逻辑上独立的数组去使用。 |
32 PlG5sBkXD1ziLeGB 13 小时 45 分钟前 via iPhone @yuanxing008 啊?你用过 go 的 slice 吗兄弟,还是你把 `foo[2] = 0` 看成 `hello[2] = 0`了 |
33 cpstar 13 小时 43 分钟前 @november 27# 如果是 js ,那会生成一个新的副本,foo_js 是{"0":30,"1":40,"2":50},然而按照 21#,更像是 foo 就是 hello ,修改了 foo 一并修改 hello ,只不过限定了 foo 的游标只有 2 、3 、4 取值,如果 foo[1]是不是要报错。 |
34 ripperdev 13 小时 42 分钟前 @yuanxing008 go 不是这样啊,输出肯定是[30 40 0] |
35 superrichman 13 小时 41 分钟前 @yuanxing008 #28 你这 python 没学好啊 python 里面是 [30, 40, 0] 一行命令跑出来就知道了 python -c "a=[10,20,30,40,50,60];b=a[2:5];b[2]=0;print(b)" |
36 guanhui07 13 小时 40 分钟前 之前我也学了下 swift 的语法.. |
37 leeg810312 13 小时 39 分钟前 @yuanxing008 你的直觉错了。go 我不知道,python 的切片是创建一个新 list a = [0, 2, 4, 6, 8, 10] b = a[2:5] b[0] = 3 b[2] = 9 print(a) print(b) 输出: [0, 2, 4, 6, 8, 10] [3, 6, 9] |
38 wangkun2012324 13 小时 37 分钟前 正确的用法永远是使用 startIndex 和 offset/advanced ``` var hello = [10, 20, 30, 40, 50, 60] var foo = hello[2..<5] // 表示索引范围是 2 ,3 ,4 foo[foo.startIndex.advanced(by: 2)] = 0 print(foo) // [30, 40, 0] ``` |
39 junj2121 13 小时 37 分钟前 C 出生的人 一看就知道结果啊。 这种赋值逻辑肯定是从指针赋值上考虑。 都觉得 C 垃圾,其实并不然 |
40 noErr 13 小时 36 分钟前 js 仔采用 switft |
41 Torpedo 13 小时 33 分钟前 @june4 #14 这两个可以不分的。主流库里,常见的只有 react 组件的返回和 json 严格区分了这两个。js 判断 null 和 undefined 如果不分,直接用 a!=null 判断就行了 |
42 idonttellyou 13 小时 28 分钟前 为什么不是[30, 40, 0]呢... |
43 V2Try 13 小时 26 分钟前 via iPhone 这个确实奇怪,如果 foo[2] = 0 换成 hello[2] = 0 结果是不是一样的? |
44 Wanex 13 小时 25 分钟前 先别管 swift 符不符合直觉,也别管 js 有没有同样的问题,总之先骂 js 再说 |
45 november 13 小时 23 分钟前 via iPhone @cpstar 其实 #27 的答案是 ai 结果,实际运行结果是 hello 没变。 所以更验证了楼主说的“一堆 ai 回答错”。233333 |
46 ChrisFreeMan 13 小时 22 分钟前 @idonttellyou 因为很多人理所当然的认为 foo 是拷贝了一个新的数组,实际上 foo[2]指向了原来的数组,foo 只是引用了愿数组 hello 。 |
48 PlG5sBkXD1ziLeGB 13 小时 18 分钟前 |
49 Huelse 13 小时 17 分钟前 确实反直觉 |
50 beimenjun PRO 这个并不是苹果现在 bug 越来越多的原因。 我还以为是讲技术债之类的,结果居然是吐槽 Swift 的语法。真的是浪费时间。 |
51 ChrisFreeMan 13 小时 17 分钟前 @idonttellyou 好吧,我搞错了,自己试了下,原数组并没有改变值 |
52 ChrisFreeMan 13 小时 16 分钟前 @PlG5sBkXD1ziLeGB 我刚刚试了下确实是,没有改变,我收回我的傻逼言论 |
53 lihanst 13 小时 15 分钟前 看到这种结果,应该思考为什么要设计一个 ArraySlice ?目的是什么?代价是什么? |
54 VXF2016 13 小时 15 分钟前 真离谱 |
55 caiqichang 13 小时 14 分钟前 |
56 usVexMownCzar 13 小时 14 分钟前 via iPhone 没啥好吵的,swift 的 Array 并不是传统概念的数组,真正的数组(连续存储)是最近版本才添加的,InlineArray 。 话说目前还没看到从源代码来分析上面的代码为什么这么设计 |
57 mizuki9 13 小时 3 分钟前 好奇葩的设计,好奇葩的行为 |
59 FlashEcho 12 小时 58 分钟前 @junj2121 #39 c 根本就没有数组切片,得自己手写,天然就会知道这个是一个视图还是拷贝出来一个新的,不参与讨论。c++的数组拷贝是默认拷贝出来一个新的 |
60 whitefable 12 小时 49 分钟前 @junj2121 #39 即使是 C 出身也很反直觉好吧。 要说从指针赋值上去考虑,那 foo 不应该也是指向了 hello[2]么,那此时 foo[2]也是对应 hello[4]才对 |
61 beimenjun PRO @usVexMownCzar 苹果关于 ArraySlice 的文档( https://developer.apple.com/documentation/swift/arrayslice )原话是这么说的“Sharing indices between collections and their subsequences is an important part of the design of Swift’s collection algorithms.” 除了 Array 和 ArraySlice ,甚至 String 和 Substring ,Data 和 Data.SubSequence 都是这样子的。 感觉最直观的好处就是可以在原对象和切片之间需要频繁调整 index 的步骤简化掉这一步骤。 比如 Apple 的示例: ``` let absences = [0, 2, 0, 4, 0, 3, 1, 0] if let i = absences.firstIndex(where: { $0 > 0 }) { // 1 let absencesAfterFirst = absences[(i + 1)...] // 2 if let j = absencesAfterFirst.firstIndex(where: { $0 > 0 }) { // 3 print("The first day with absences had \(absences[i]).") // 4 print("The second day with absences had \(absences[j]).") } } // Prints "The first day with absences had 2." // Prints "The second day with absences had 4." ``` |
62 xz410236056 12 小时 27 分钟前 @ourstars @qdwang 因为你们没看文档,foo 并不是个 array ,而是一个切片(ArraySlice),他的索引并不从 0 开始(取决于你创建方式,大多数共享)。你需要使用特定的函数达到 array 的效果。 https://developer.apple.com/documentation/swift/arrayslice |
63 xz410236056 12 小时 26 分钟前 @fadaixiaohai 关键词现在能搞本书了,恶心的要死。人类根本记不全 |
64 Leeeeex PRO |
65 ikw 11 小时 48 分钟前 @PlG5sBkXD1ziLeGB #48 前面保留 index 我都能认为是语言的特性,找到对应的文档并且接受了,但是不修改原数组我就懵了。 Slice 作为保留原 index 的轻量化视图,修改操作不影响原数组,那修改部分去哪里了?总不能是 Copy on Write 新建了一个 Array 吧,而且这么奇怪的逻辑没有文档强调? https://developer.apple.com/documentation/swift/arrayslice |
66 qdwang OP @wangkun2012324 老哥厉害 |
67 coudey 11 小时 38 分钟前 有够奇葩的,普通 gpt5 也搞错了。hello 被修改能理解,foo[2]用 hello 的索引真是逆大天 |
68 sankemao 11 小时 16 分钟前 太奇葩了,就你 swift 要搞特殊 |
69 butanediol2d 11 小时 3 分钟前 @PlG5sBkXD1ziLeGB COW 生效了,在修改之前,底层使用的 array 是同一块内存,修改时 foo 进行了复制,不是同一块内存了 |
70 yolee599 10 小时 26 分钟前 via Android |
71 wangkun2012324 10 小时 13 分钟前 swift 的数组,包括之前吐槽的 string, 一切源于 collection 的设计,collection 的 startIndex 和 endIndex, swift 一致期望用户不使用整数索引,你查看数组的一些排序算法等你会发现根本不是数组的方法,而是 collection 甚至别的 sequence 的各种方法,并且不是所有。 设计者在做通用的 collection 算法时,https://forums.swift.org/t/rant-indexing-into-arrayslice/14105/14 决定了 Array Slice 这么做。其实我认为这么做之后,array[1]这种风格的写法, 只是 swift 数组只是恰好对的写法,因为 Array.Index 是 Int 。string[1]就不对了。吐槽也是合理的。毕竟是常见的语法,违反直觉的语义 |
72 Azone 10 小时 11 分钟前 @PlG5sBkXD1ziLeGB foo[2] 指向的是元素组的索引,不是指向的是原数组。Swift 所有的值类型都是 COW 技术,当你把一个变量赋值给另一个变量并且改变它的值的时候,他们指向的就不是同一块内存了。 |
73 laikicka 10 小时 3 分钟前 @craftsmanship js 的 bug 还少吗 |
74 decken 9 小时 50 分钟前 语法问题导致的 bug 应该极少吧 |
75 clarkethan 9 小时 21 分钟前 想了一下,有可能是出于性能考虑吧,减少不必要的深拷贝和内存分配 再深入想一想,如果我是经常使用 swift 的人,这个设计好像又是可以接受的,语法上这种写法就相当于是一种借用切片,语以上只是记录一下借用范围而已。如果需要在 var foo 的时候直接深拷贝内容,直接用 Array 构造一个新的数据,应该是语言推荐的做法吧。如果有以上心智在前,好像倒不会有太多问题了,不少场合确实能节约一些性能,尤其是资源能耗敏感的场景 我实际上不写 swift ,仅猜测 |
76 ikw 9 小时 9 分钟前 @butanediol2d #69 这样的话,又有几个问题, 既然设计了 CoW ,那 foo 预期是会作为独立对象存在的,为什么还要用原数组的索引呢? CoW 是复制整个 Array 还是复制 slice 对应部分呢? foo 有了自己的内存,按说讲他和 hello 已经没什么关系了,这个时候 foo 还要一直用 hello 的索引,似乎也很奇怪吧? |
77 tiancaixiaoshuai 9 小时 0 分钟前 直觉是重置索引,我没学过 swift ,如果这是语言特性,也能接受,就像 php 也可以使用 array_slice()里面的 preserve_keys 设为 true 保留原索引,并且这个跟 bug 多也没什么关系吧 |
78 AV1 8 小时 54 分钟前 @clarkethan 感觉你没看懂 OP 表达的,这问题跟“深拷贝”没有任何关系。 我把他的问题再化简一点,你来猜猜结果: var hello = [10, 20, 30, 40, 50, 60] var foo = hello[2..<5] // 截取了 hello[2], hello[3], hello[4] print(foo[0]) print(foo[2]) 你猜猜此时 foo[0]和 foo[2]的值分别是多少? |
79 clarkethan 8 小时 37 分钟前 @AV1 感觉你没仔细看我的话,哈哈,这就是不同语言的语法设定,如果 swift 中确实是这样的设定,这个语法我个人觉得可以接受,并且确实有一些利好的地方。 多嘴一句,你说的 “ var foo = hello[2..<5] // 截取了 hello[2], hello[3], hello[4] ”,这里为什么一定是截取呢?你有点先入为主了,还是要先尊重语言本身的基本设定,如果没有深度学习和使用过这门语言,没有相关心智,何止是这一个点,几乎每个语言都有一堆值得吐槽的设定,客观的去看待类似这种东西吧 转回来,我只是对 swift 中这地方的语法和底层逻辑简单的加以猜测了,没有强依据,本身我也不熟悉 swift ,也没有说这么做很对,只是感觉深入思考一下,我个人表示能接受,仅此而已 |
80 diivL 8 小时 28 分钟前 @clarkethan #79 你这就像说,只要 swift 设定屎能吃,你就吃屎,并得出一堆吃屎的好处。 |
81 unused 8 小时 17 分钟前 不得不说苹果的东西在提高使用者自适应能力方面是真的牛 B |
82 butanediol2d 8 小时 17 分钟前 @ikw 我个人理解,先不考虑修改的问题,使用原数组的索引有其一定的合理性,这个在 Arrayslice 的文档里也有解释。然后如果 foo 被修改,虽然发生了复制,但 Array 和 Arrayslice 是值语义,所以继续使用原数组的索引。 CoW 复制的是 slice 对应部分: ```swift var array = [1, 2, 3, 4, 5] var slice = array[1..<4] slice.withUnsafeBufferPointer { ptr in for i in 0..<4 { let val = ptr.baseAddress!.advanced(by: i).pointee print(val, terminator: " ") } print() } slice[3] = 6 slice.withUnsafeBufferPointer { ptr in for i in 0..<4 { let val = ptr.baseAddress!.advanced(by: i).pointee print(val, terminator: " ") } print() } ``` 输出是 ``` 2 3 4 5 2 3 6 xxx ``` xxx 是个“随机”数 |
83 Geon97 7 小时 35 分钟前 远古时期是 c 艹也是这样 |
84 没学过 swift 真不好评价,不知道这种是否会出现在正常项目中。但如果就论这段代码,我很好奇如果使用 foo 的时候偏移值依旧需要使用 hello 的,那我干嘛不直接用 hello ?如果是为了创建一个视图,那么使用这个视图还必须得把起始位置和长度都一并传过去? |
85 Rickkkkkkk 7 小时 18 分钟前 不符合直觉的设计都是烂设计,说破天也是烂设计。 |
86 qdwang OP @billbur 会出现的。比如我有一段二进制数据 A ,我正好需要其中一段数据`B = A[x..<x + size]`,然后我正好要把这段数据最后 4 个字节改为 0 。 那么按照一般程序员逻辑,用最朴素的写法就是`B[size - 4] = 0; B[size - 3] = 0; B[size - 2] = 0; B[size - 1] = 0`。然后编译不会报错,程序运行也不会报错。只有你检查结果,会发现不对。 只有认真学过 swift 的人才知道,应该用`startIndex.advanced(by: )` |
87 butanediol2d 5 小时 58 分钟前 @qdwang 你说的有道理,但我感觉可能稍微有点钻牛角尖了。例如你说的这个例子,实际情况这段二进制数据很有可能是 Data 类型,那么就直接用 Data.replaceSubrange(_:with:).subdata(in:) 了,不会直接操作 Array<UInt8>。其次就是,由于对 Array<UInt8> 进行切片后类型是 ArraySlice<UInt8>,但是很有可能你下一步做的操作需要的是一个 Array<UInt8>,所以你大概会选择在切片的时候就直接把它转换成 Array<UInt8>,规避了这个问题。 所以虽然理论上会有这个坑(例如 Data 的 initializer 就允许 ArraySlice ),但大多数时候用包装好的工具不太会踩。但也可能是我见识的比较少,或许 c/cpp interop 的时候用得多? |
88 qdwang OP @butanediol2d 巧了,我就是在和 c 交互时候发现这个问题的 |
89 volvo007 5 小时 2 分钟前 via iPhone @liuhan907 你别说,我 python 跟本不敢说话,py 也是 0 打头,因为列表是“可更改对象”,其切片只是引用。如果想切片里改原来的不动,需要 foo = list[slice].copy() 显式拷贝一份 |
90 clarkethan 4 小时 55 分钟前 @diivL 我过去写过编译器,也参与过脚本语言的语法/语义设计,清楚语言设计并不是“我不爽就改一下”这么简单:性能约束、开发体验、可读性、兼容性,每一项都要用代价换平衡。 所以我更习惯先问一句“它为什么这样”,而不是先喊“它怎么敢这样”。发泄情绪很容易,真的做技术的人不会把那当成成就感来源。 |
92 XIVN1987 4 小时 24 分钟前 python 里 slicing 得到的是一个新对象,,所有如果你想将 hello 完整复制一份,,可以用 hello[:] In [7]: hello = [10, 20, 30, 40, 50, 60] In [8]: foo = hello[:] In [9]: foo Out[9]: [10, 20, 30, 40, 50, 60] In [10]: foo == hello Out[10]: True In [11]: foo is hello Out[11]: False |
93 geminikingfall 4 小时 17 分钟前 看来我的直觉还是比较符合苹果设计哲学,难怪别人觉得怪的 Mac 上的特性,我觉得很自然。。。当然,swift 这个设计还是太反人类,没找到第二个这样设计的语言。 |
94 june4 3 小时 58 分钟前 @Torpedo 问题是,你定义声明的时候是用 null 呢还是 undefined ?还是不嫌麻烦二个都加上?虽然我选择只用 undefined,但很多时候也碰到把 null 转成 undefined 这种操蛋操作,比如 DOM API 基本上都返回 null 而不是 undefined,或有些第三方库选择用 null 。 虽然这事不大,但这是我碰到的唯一不能被 ts 解决的 js 遗留问题,可能会贯穿整个 js 生命周期了。 |
95 onevcat 2 小时 26 分钟前 @wangkun2012324 的理解很正确。在一个 RandomAccessCollection 中,既没有人规定,我们也不应该假设 startIndex 必须等于 0 (实际上我们甚至不应该假设它是 Int )。能写成 foo[2] 只不过是“不幸的巧合”罢了。如果想要表示“序列中位于第三个的元素”,那应该写成:foo[foo.startIndex + 2],而不是一个单纯的 2 。 |