现在slices包已经有很多有用的func,包括reverse
能在此查看某个func加入的时间
https://pkg.go.dev/slices?tab=versions
BinarySearch和 BinarySearchFunc
二分查找
Clip
1 | func Clip[S ~[]E, E any](s S) S { |
什么意思
这个代码片段是一个名为 Clip 的函数,它是一个泛型函数,使用了 Go 语言的类型参数语法。
函数签名中的 S ~[]E 表示 S 是一个切片类型,并且 E 是 S 中元素的类型。这个语法使用了泛型类型约束(type constraint),它要求 S 必须是一个切片类型,而 E 是 S 中元素的类型。E any 表示 E 可以是任意类型。
函数的目的是对给定的切片 s 进行修剪(clip),即截取切片的一部分。具体地,它使用切片操作符 s[:len(s):len(s)] 对切片进行修剪。
在这个切片操作符中,s[:len(s)] 表示从切片的开头到结尾的子切片,而 [:len(s):len(s)] 则表示复制整个子切片,并设置新切片的容量和长度都等于原始子切片的长度。这样做的目的是创建一个新的切片,其容量和长度与原始切片相等,但底层数组是原始切片的一部分,而不是整个底层数组。
换句话说,Clip 函数返回一个新切片,该切片与输入切片 s 具有相同的元素和长度,但使用了更小的底层数组。这可以用于减少内存使用,尤其在处理大型切片时。
如下是一个使用Clip函数的例子:
当有一个切片,并且只需要其中的一部分元素时,可以使用 Clip 函数来修剪切片并获得一个新的切片。下面是一个使用 Clip 函数的示例:
1 | package main |
在上述示例中,我们定义了一个整数类型的切片 slice,其中包含了数字 1 到 5。然后,我们调用了 Clip 函数,并将 slice 作为参数传递给它。Clip 函数返回一个新的切片 clipped,其中包含了与输入切片 slice 相同的元素和长度。
最后,我们打印了修剪后的切片 clipped 的内容、长度和容量。由于 Clip 函数内部使用了切片操作符 [:len(s):len(s)],新切片 clipped 具有与原始切片 slice 相同的长度和容量。
所以,clipped 与 slice 是相等的切片,它们共享相同的元素和相同的底层数组,但是 clipped 使用了更小的底层数组。
Clone
浅拷贝
克隆返回切片的副本。元素是使用赋值复制的,因此这是一个浅层克隆。
1 | func Clone[S ~[]E, E any](s S) S { |
什么意思?
这个代码片段是一个名为 Clone 的函数,它也是一个泛型函数,使用了 Go 语言的类型参数语法。
函数签名中的 S ~[]E 表示 S 是一个切片类型,并且 E 是 S 中元素的类型。这个语法使用了泛型类型约束(type constraint),它要求 S 必须是一个切片类型,而 E 是 S 中元素的类型。E any 表示 E 可以是任意类型。
函数的目的是创建一个切片的副本(clone)。具体地,它使用了 append 函数来将切片 s 的所有元素追加到一个新创建的空切片中。
在函数内部,首先进行了一个条件判断 if s == nil,用于检查 s 是否为 nil。如果是 nil,表示原始切片为空,那么函数直接返回 nil,以保持原始切片的空状态。
如果原始切片不为空,函数会先创建一个空切片 []E{}([]E 表示空切片),然后使用 S([]E{}) 进行类型转换,将其转换为与输入切片 s 相同类型的切片。最后,通过 append 函数将原始切片 s 的所有元素追加到新切片中,从而创建了原始切片的副本。
最终,函数返回新创建的切片作为原始切片的副本。
换句话说,Clone 函数用于创建一个与输入切片 s 具有相同元素的新切片,这样你就可以对新切片进行修改,而不会影响到原始切片。注意,如果输入切片是 nil,则函数会直接返回 nil。
Compact和CompactFunc
1 | // Compact replaces consecutive runs of equal elements with a single copy. |
这个代码片段是一个名为 Compact 的函数,它也是一个泛型函数,使用了 Go 语言的类型参数语法。
函数的目的是将切片 s 中连续相同的元素替换为单个副本。类似于 Unix 中的 uniq 命令。
函数的注释提供了更多的解释:
Compact函数修改切片s的内容,并返回修改后的切片,该切片可能具有更小的长度。- 当
Compact总共丢弃了m个元素时,它可能不会修改切片s[len(s)-m:len(s)]中的元素。如果这些元素包含指针,你可能考虑将这些元素清零(zeroing),以便它们引用的对象可以被垃圾回收。
函数的实现如下:
- 首先,函数检查切片的长度是否小于 2,如果是,则直接返回原始切片
s,因为不需要进行压缩。 - 接下来,函数使用两个指针
i和k进行迭代,从索引 1 开始。 - 在迭代过程中,函数比较当前元素
s[k]与前一个元素s[k-1]是否相等。- 如果不相等,表示遇到了新的元素,需要将其复制到
s[i]的位置,并将i向前移动一位。 - 如果相等,则说明当前元素与前一个元素相同,可以跳过重复的元素。
- 如果不相等,表示遇到了新的元素,需要将其复制到
- 最后,函数返回
s[:i],即压缩后的切片,其中包含了去除了连续重复元素的内容。
这样,Compact 函数可以用于对切片中的连续重复元素进行压缩,以减少切片的长度,并保留唯一的元素副本。
这个函数在处理切片时是否会改变原始切片的内容?
是的,Compact 函数会修改输入切片 s 的内容。它会将连续相同的元素替换为单个副本,并将修改后的内容存储回原始切片。
在函数内部,当发现连续相同的元素时,它会将后续的元素复制到前面的位置,以覆盖重复的元素。最后,函数返回的是原始切片 s 的一个子切片,其中只包含了压缩后的内容。
因此,调用 Compact 函数后,原始切片 s 的内容将被修改。如果你希望保留原始切片,可以在调用函数之前创建切片的副本,然后将副本传递给 Compact 函数进行处理。
即 去除重复元素
新的切片,容量和长度和原切片可能不同
Compact 做形容词意思是 紧凑的; 小型的; 紧密的; 体积小的; 袖珍的; 坚实的; 矮小而健壮的;
其实感觉名字和作用不完全匹配
如果有 []string{"bob", "Bob", "alice", "Vera", "VERA"}, 忽略大小写,相同的只保留一个,可以用CompactFunc (当然也可以先把全部元素转成小写,再用Compact)
CompactFunc 类似于 Compact,但使用相等函数来比较元素。对于比较相等的元素运行,CompactFunc 保留第一个。
Compare和CompareFunc
Compare 对每对元素使用 [cmp.Compare] 来比较 s1 和 s2 的元素。 从索引 0 开始按顺序比较元素,直到一个元素不等于另一个元素。 返回第一个不匹配元素的比较结果。 如果两个切片在其中一个结束之前都相等,则认为较短的切片小于较长的切片。 如果 s1 == s2,结果为 0;如果 s1 < s2,结果为 -1;如果 s1 > s2,结果为 1。
有点意思…
Contains和ContainsFunc
和strings.Contains一样,底层是Index方法
Delete和DeleteFunc
Delete 函数会在切片 s 中删除 s[i:j] 的元素,并返回修改后的切片。
Delete 从 s 中删除元素 s[i:j],返回修改后的切片。 如果 s[i:j] 不是 s 的有效切片,则panic。 Delete 的时间复杂度为 O(len(s)-j),因此如果必须删除许多项,最好通过一次调用将它们全部删除,而不是一次删除一项。 Delete 可能不会修改元素 s[len(s)-(j-i):len(s)]。 如果这些元素包含指针,您可能会考虑将这些元素归零,以便它们引用的对象可以被垃圾收集。
1 | // Delete removes the elements s[i:j] from s, returning the modified slice. |
这个代码片段是一个名为 Delete 的函数,同样是一个泛型函数,使用了 Go 语言的类型参数语法。
函数的目的是从切片 s 中删除 s[i:j] 的元素,并返回修改后的切片。
函数的注释提供了更多的解释:
Delete函数会在切片s中删除s[i:j]的元素,并返回修改后的切片。- 如果
s[i:j]不是s的有效切片,函数会引发 panic(异常)。 Delete的时间复杂度为 O(len(s)-j),因此如果需要删除多个元素,最好一次性将它们全部删除,而不是逐个删除。Delete可能不会修改切片s[len(s)-(j-i):len(s)]中的元素。如果这些元素包含指针,你可能考虑将这些元素清零(zeroing),以便它们引用的对象可以被垃圾回收。
函数的实现如下:
- 首先,函数执行一个边界检查
_ = s[i:j],用于确保s[i:j]是s的有效切片。这里使用了_来忽略返回值,主要是为了进行边界检查而不使用实际的返回值。 - 接下来,函数使用
append函数来构建一个新的切片。s[:i]表示s的开头部分,即索引小于i的元素。s[j:]表示s的剩余部分,即索引大于等于j的元素。
- 通过将这两部分切片连接起来,即
append(s[:i], s[j:]...),函数创建了一个新的切片,其中包含了从s中删除了s[i:j]元素后的内容。
最后,函数返回新创建的切片作为修改后的切片。
这样,Delete 函数可以用于从切片中删除指定范围的元素,并返回修改后的切片。需要注意的是,函数不会创建新的底层数组,而是在原始切片上进行修改。
Equal和EqualFunc
Equal比较直观,就是每个元素挨个比较
1 | func Equal[S ~[]E, E comparable](s1, s2 S) bool { |
Grow
如有必要,Grow 会增加切片的容量,以保证另外 n 个元素的空间。 在 Grow(n) 之后,至少可以将 n 个元素附加到切片,而无需再次分配。 如果 n 为负数或太大而无法分配内存,Grow 会出现恐慌。
1 | // Grow increases the slice's capacity, if necessary, to guarantee space for |
Index和IndexFunc
Index 返回 s 中第一次出现的 v 的索引,如果不存在,则返回 -1。
这里就是暴力比较了,用不了啥Rabin-Karp算法了..
1 | // Index returns the index of the first occurrence of v in s, |
Insert 和 Replace
Insert
实现挺复杂的,但意思很好理解。 即在指定位置插入指定元素
Insert 将值 v… 插入到索引 i 处的 s 中,返回修改后的切片。 s[i:] 处的元素向上移动以腾出空间。 在返回的切片 r 中,r[i] == v[0],并且 r[i+len(v)] == 最初位于 r[i] 的值。 如果 i 超出范围,则Insert将panic。 该函数的复杂度为 O(len(s) + len(v))。
1 | // Insert inserts the values v... into s at index i, |
Replace
Replace 将元素 s[i:j] 替换为给定的 v,并返回修改后的切片。 如果 s[i:j] 不是 s 的有效切片,则替换恐慌。
和Insert一样,也是实现很复杂,但作用很容易理解。但又因为有扩容等原因,最终的结果,其实并不直观…很有潜在可能作为面试八股(主要还是append扩容,其实非常复杂,不同类型啥的不同。可以说99% Gopher没法完完全全搞清楚,也没有必要)
1 | if i == j { |
如果 i 和 j 相等,表示要替换的范围为空,此时调用 Insert 函数将元素 v 插入到索引 i 的位置,并返回修改后的切片。
1 | if j == len(s) { |
如果 j 等于切片 s 的长度,表示要替换的范围是切片的末尾部分,此时使用 append 函数将切片 s[:i] 和元素 v 连接起来,返回修改后的切片。
** 对于其他情况,函数会计算替换后的切片的总长度 tot,包括替换部分之前、替换部分之后和元素 v 的长度。
如果 tot 大于切片 s 的容量,说明替换后的切片无法放入原始切片中,需要先分配一个新的切片,并将内容复制过去。
如果 tot 小于等于切片 s 的容量,函数会在原始切片 s 上进行修改。**
1 | // Replace replaces the elements s[i:j] by the given v, and returns the |
这个代码片段是一个名为 Replace 的函数,同样是一个泛型函数,使用了 Go 语言的类型参数语法。
函数的目的是用给定的元素 v 替换切片 s 中的 s[i:j] 部分,并返回修改后的切片。
函数的注释提供了更多的解释:
Replace函数会用给定的元素v替换切片s中的s[i:j]部分,并返回修改后的切片。- 如果
s[i:j]不是s的有效切片,函数会引发 panic(异常)。
函数的实现如下:
- 首先,函数执行一个边界检查
_ = s[i:j],用于确保s[i:j]是s的有效切片。这里同样使用了_来忽略返回值,主要是为了进行边界检查而不使用实际的返回值。 - 接下来,函数根据不同的情况进行处理。
- 如果
i和j相等,表示要替换的范围为空,此时调用Insert函数将元素v插入到索引i的位置,并返回修改后的切片。 - 如果
j等于切片s的长度,表示要替换的范围是切片的末尾部分,此时使用append函数将切片s[:i]和元素v连接起来,返回修改后的切片。 - 对于其他情况,函数会计算替换后的切片的总长度
tot,包括替换部分之前、替换部分之后和元素v的长度。- 如果
tot大于切片s的容量,说明替换后的切片无法放入原始切片中,需要先分配一个新的切片,并将内容复制过去。 - 如果
tot小于等于切片s的容量,函数会在原始切片s上进行修改。
- 如果
接下来,函数根据不同的情况进行处理:
- 如果替换后的元素
v恰好可以放入被删除的部分s[i:j]中,直接使用copy函数将v复制到正确的位置上,并返回修改后的切片。 - 如果替换后的元素
v需要扩展删除的范围j-i,函数会根据情况进行处理。- 如果替换后的元素
v不与被删除的部分s[i:j]重叠,可以直接进行复制和旋转操作,将v复制到正确的位置,并将剩余的部分复制到合适的位置上,然后返回修改后的切片。 - 如果替换后的元素
v与被删除的部分s[i:j]重叠,需要进行一些特殊处理。函数将分配一个新的切片r,用于存储替换后的结果。它将根据v与s[j:]的重叠情况,将v的前缀部分复制到s的末尾,然后使用rotateRight函数将切片r[i:]向右旋转y个位置,最后返回修改后的切片。
- 如果替换后的元素
这样,Replace 函数可以用于替换切片中指定范围的元素,并返回修改后的切片。需要注意的是,函数可能会在原始切片上进行修改,或者在超出容量时创建一个新的切片进行替换。
IsSorted和IsSortedFunc,Sort,SortFunc及SortStableFunc
都是排序相关
IsSort 报告 x 是否按升序排序。(如果是字符串,则按照ASCII码顺序)
Sort按升序对任何有序类型的切片进行排序。对浮点数进行排序时,NaN 的顺序排在其他值之前。
Max和MaxFunc,Min和MinFunc
Max 返回 x 中的最大值。 如果 x 为空,它会发生panic。 对于浮点 E,Max 传播 NaN(x 中的任何 NaN 值都会强制输出为 NaN)。
Reverse
非常实用~
反转切片中的元素
1 | // Reverse reverses the elements of the slice in place. |
另:
相比于网页版,推荐使用Dash作为文档查阅工具
2024.08.13 go1.23
rsc负责的最后一个版本~
https://github.com/golang/go/issues/61899
slices库新增了几个func
目前还没有相关example~
我来写!
chunk
1 | // Chunk returns an iterator over consecutive sub-slices of up to n elements of s. |
根据定义,帮我写个简单的demo
当然,我可以帮你写一个简单的demo来演示这个Chunk函数的使用。首先,让我解释一下这个函数的作用:
Chunk函数接受一个切片和一个整数n作为参数,返回一个迭代器,该迭代器会产生原切片的连续子切片,每个子切片最多包含n个元素。这个函数在处理大型切片时特别有用,可以将其分割成更小的块进行处理。
现在,让我们编写一个demo来展示如何使用这个函数:
1 | package main |
这个demo展示了以下几点:
- 如何使用
Chunk函数将一个切片分割成多个子切片。 - 如何遍历
Chunk函数返回的迭代器。 - 演示了修改chunk不会影响原始切片,这是因为每个chunk的容量被限制为其长度。
要运行这个demo,你需要安装Go并导入必要的包(golang.org/x/exp/slices和golang.org/x/exp/iter)。你可以使用以下命令安装这些包:
1 | go get golang.org/x/exp/slices |
运行这个程序后,你应该会看到类似下面的输出:
1 | Chunk: [1 2 3] |
这个输出显示了切片被成功分割成了大小为3的块(最后一个块可能更小),并且尝试修改chunk并没有影响原始切片。
你可以通过修改numbers切片或Chunk函数的第二个参数来进一步实验这个函数的行为。
All
1 | // All returns an iterator over index-value pairs in the slice |
好的,我来为你编写一个简单的demo来展示All函数的使用。这个函数创建一个迭代器,可以遍历切片中的所有元素,返回每个元素的索引和值。
1 | package main |
这个demo展示了以下几点:
- 如何使用
All函数创建一个迭代器,该迭代器可以遍历切片中的所有元素。 - 如何使用
iter.For2函数来遍历All函数返回的迭代器,并访问每个元素的索引和值。 - 如何利用
All函数来实现一个简单的查找功能,展示了如何在遍历过程中提前终止迭代。
要运行这个demo,你需要安装Go并导入必要的包(golang.org/x/exp/iter)。你可以使用以下命令安装这个包:
1 | go get golang.org/x/exp/iter |
运行这个程序后,你应该会看到类似下面的输出:
1 | Index: 0, Value: apple |
这个输出显示了:
- 切片中每个元素的索引和值
- 成功找到了 “cherry” 及其索引
- 未能找到不存在的 “mango”
你可以通过修改fruits切片或尝试查找不同的水果来进一步实验这个函数的行为。这个demo展示了All函数如何提供一种灵活的方式来遍历和操作切片数据。
// All returns an iterator over index-value pairs in the slice in the usual order.这段注释描述的是 Go 语言标准库中 slices 包提供的一个方法 All。
具体来说:
All返回一个迭代器(iterator)。这个迭代器可以用来遍历切片(slice)中的所有元素。这个迭代器会按照切片中元素的”常规顺序”返回索引-值对(index-value pair)。
所谓”常规顺序”,就是切片中元素的自然顺序。对于切片而言,通常就是从索引 0 开始,一个一个返回元素。
举个例子:
1 | slice := []int{10, 20, 30, 40} |
通过使用 All 返回的迭代器,可以方便地遍历切片中的所有元素,并获取到它们的索引和值。这种遍历方式与直接使用 for range 循环遍历切片的效果是等同的。
总之,这个注释是在说明 All 方法的作用,即以”常规顺序”返回切片中的所有元素。这为切片的遍历操作提供了一种便捷的方式。
我明白了,您希望看到一个更简单的演示。我为之前的复杂示例道歉。让我为您提供一个非常简单的demo,仅展示All函数的基本用法。
1 | package main |
这个简单的demo展示了:
- 如何定义
All函数。 - 如何创建一个简单的整数切片。
- 如何使用
All函数和iter.For2来遍历切片,打印每个元素的索引和值。
要运行这个demo,您需要安装Go并导入golang.org/x/exp/iter包。可以使用以下命令安装:
1 | go get golang.org/x/exp/iter |
运行这个程序后,您将看到类似下面的输出:
1 | Index: 0, Value: 10 |
这个简单的demo直接展示了All函数的基本功能:它允许我们遍历一个切片,同时获取每个元素的索引和值。
AppendSeq
1 | // AppendSeq appends the values from seq to the slice and |
好的,我理解您需要一个简单的演示来展示 AppendSeq 函数的用法。我会为您创建一个简洁的 demo。
1 | package main |
这个简单的 demo 展示了:
- 如何定义
AppendSeq函数。 - 如何创建一个初始切片和一个简单的
iter.Seq。 - 如何使用
AppendSeq函数将iter.Seq中的元素追加到切片中。 - 如何打印结果以验证操作。
要运行这个 demo,您需要安装 Go 并导入 golang.org/x/exp/iter 包。可以使用以下命令安装:
1 | go get golang.org/x/exp/iter |
运行这个程序后,您将看到类似下面的输出:
1 | 结果: [1 2 3 4 5 6] |
这个输出显示了初始切片 [1, 2, 3] 被成功扩展,包含了来自 seq 的额外元素 [4, 5, 6]。
这个简单的 demo 直接展示了 AppendSeq 函数的基本功能:它将一个 iter.Seq 中的所有元素追加到一个已存在的切片中,并返回扩展后的切片。
// AppendSeq appends the values from seq to the slice and returns the extended slice. 这段注释描述的是 Go 语言标准库中 slices 包提供的一个方法 AppendSeq。
具体来说:
AppendSeq函数接受两个参数:- 一个切片(slice)
- 一个值序列(sequence)
这个函数会将
seq中的所有值追加到输入的切片末尾,形成一个新的、更长的切片。这个函数会返回这个新的、已被扩展的切片。
举个例子:
1 | slice := []int{10, 20, 30} |
在这个例子中:
- 初始切片是
[10, 20, 30] - 调用
AppendSeq函数,将值40、50、60追加到切片末尾 - 函数返回了一个新的、更长的切片
[10, 20, 30, 40, 50, 60]
总之,AppendSeq 函数提供了一种简单高效的方式来扩展一个切片。它将新的值序列附加到切片的末尾,并返回这个新的、扩展后的切片。这在需要动态构建切片时非常有用。
看起来poe给的更适合这里,更简单..claude搞得太复杂了~
这个先略过
Seq和Seq2的区别
1 | // Seq is an iterator over sequences of individual values. |
这两个类型 Seq 和 Seq2 都是用于定义迭代器的函数类型,但它们在处理的数据结构上有所不同。让我解释一下它们的区别:
Seq[V any]:- 这是一个单值序列的迭代器。
- 它迭代的每个元素是单个值。
- 适用于简单的列表或集合,如切片、数组等。
Seq2[K, V any]:- 这是一个键值对序列的迭代器。
- 它迭代的每个元素是一对值,通常是键值对。
- 适用于映射(map)或需要同时处理两个相关值的情况。
主要区别:
参数数量:
Seq的 yield 函数接受一个参数func(V) bool。Seq2的 yield 函数接受两个参数func(K, V) bool。
使用场景:
Seq适用于单一值的集合,如切片[]int,[]string等。Seq2适用于键值对的集合,如 map 或需要同时处理索引和值的情况。
灵活性:
Seq2更灵活,可以用于表示索引-值对(如切片的遍历)或键-值对(如map的遍历)。
让我给你一个简单的例子来说明它们的用法区别:
1 | package main |
在这个例子中:
Seq用于遍历一个简单的整数切片,每次迭代只处理一个值。Seq2用于遍历一个 map,每次迭代处理一个键值对。
运行这个程序,你会看到类似这样的输出:
1 | Using Seq: |
这个例子展示了 Seq 和 Seq2 在实际使用中的主要区别:Seq 处理单个值,而 Seq2 处理键值对。这使得 Seq2 更适合处理像 map 这样的数据结构或需要同时获取索引和值的情况。
Backward
1 | // Backward returns an iterator over index-value pairs in the slice, |
这个 Backward 函数是一个返回迭代器的函数,它可以用来以倒序的方式遍历一个切片。
我们来看一个简单的示例代码:
1 | package main |
在这个示例中:
- 首先创建了一个切片
numbers := []int{10, 20, 30, 40, 50}。 - 然后使用
slices.Backward(numbers)函数创建了一个倒序遍历该切片的迭代器backwardIter。 - 接下来,我们使用一个
for循环来遍历这个迭代器。在每次迭代中,会传递一个匿名函数作为参数,这个匿名函数会被迭代器调用,并接收当前的索引和元素值。 - 在匿名函数中,我们简单地打印出当前的索引和元素值。你可以在这里执行任何你想要的操作。
- 如果匿名函数返回
false,则迭代器会停止遍历。
当你运行这个程序时,它会输出:
1 | Index: 4, Value: 50 |
可以看到,元素是按照倒序的顺序被遍历的。
slices.Backward 函数返回的是一个 iter.Seq2[int, E] 类型的迭代器。这个迭代器会在每次迭代时,通过调用传入的匿名函数来返回当前的索引和元素值。如果匿名函数返回 false,则迭代器会停止遍历。
这个示例展示了如何使用 Backward 函数来创建一个倒序遍历切片的迭代器,并通过遍历该迭代器来实现对切片的倒序遍历。如果你有任何其他问题,欢迎继续询问。
看起来要先搞懂iter.Seq怎么用..
Python详解可迭代对象(Iterable)、序列(Sequence)、迭代器(Iterator)、生成器(generator)
写出来了哈哈哈
晚了一步…哎
https://github.com/golang/go/commit/3c170ac018422fce5811f23455c89fe7630a4156
https://go-review.googlesource.com/c/go/+/603735
wangjiahua 提交了.. 这哥们的提交 https://go-review.googlesource.com/q/owner:wjh180909@gmail.com weixie老哥有关注他的github
不过看时间,他比我早..也就算了~
所以我的放弃了 https://go-review.googlesource.com/c/go/+/605357
其实还有大量的可以加的example~
原文链接: https://dashen.tech/2020/10/07/Go-slices库中的方法/
版权声明: 转载请注明出处.