参数为可变对象引发的问题

先来看一个结果符合绝大多数人预期的例子🌰

1
2
3
4
5
6
7
8
9
10
11
12
13
class Company:
def __init__(self, name: str, staffs: list=[]):
self.name: str = name
self.staffs: list = staffs

def add(self, staff_name: str):
self.staffs.append(staff_name)


if __name__ == "__main__":
co = Company("xx", ["A", "B"])
co.add("C")
print(co.staffs)

执行结果:

1
['A', 'B', 'C']

这个结果想必大家都能猜得到, 接下来看下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Company:
def __init__(self, name: str, staffs: list=[]):
self.name: str = name
self.staffs: list = staffs

def add(self, staff_name: str):
self.staffs.append(staff_name)


if __name__ == "__main__":
co1 = Company("yy")
co1.add("D")
print("co1's staffs: {0}".format(co1.staffs))

co2 = Company("oo")
co2.add("E")
print("co1's staffs: {0}".format(co1.staffs))
print("co2's staffs: {0}".format(co2.staffs))

执行结果:

1
2
3
co1's staffs: ['D']
co1's staffs: ['D', 'E']
co2's staffs: ['D', 'E']

第一行的结果, 应该是符合大家的预期的, 但是第二行第三行的结果可以看出, 虽然是两个完全不同的对象, 但是互相受到了影响! 对co2添加员工的操作影响了co1中的成员, 而最开始co1中添加成员的操作也默认影响到了co2的员工列表

这是为什么呢?

如果你用的PyCharm IDE在你定义函数/方法参数的时候, 如果定义的默认参数为列表, 那么IDE会提示你: Default argument value is mutable, 不推荐在函数的参数中直接接收列表类型的数据(不推荐使用可变对象).

那么抛开建议, 为什么会出现上面两个对象之间的值互相影响的情况呢?

是因为co1co2两个对象在创建的时候, 都没有明确指定staffs列表, 而是都使用了默认的空列表, 在这种情况下他们实际就共用了一个对象, 而恰巧该对象又是可变对象, 所以创建对象后, 任意一个对象对该可变对象的操作都会影响到另一个对象, 在Python代码中, 可以使用is语句来验证两个对象的两个字段是否都引用了同一个对象

1
print(co1.staffs is co2.staffs)

执行结果:

1
True

验证结果也明确显示了, 两个对象的两个字段, 其实是引用了同一个对象, 同一块内存, 我们也可以通过特定函数查看类中构造方法默认值的使用情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Company:
def __init__(self, name: str, staffs: list=[]):
self.name: str = name
self.staffs: list = staffs

def add(self, staff_name: str):
self.staffs.append(staff_name)


if __name__ == "__main__":
print(Company.__init__.__defaults__) # ([],)
co1 = Company("yy")
co1.add("D")
print("co1's staffs: {0}".format(co1.staffs)) # co1's staffs: ['D']
print(Company.__init__.__defaults__) # (['D'],)
co2 = Company("oo")
co2.add("E")
print("co1's staffs: {0}".format(co1.staffs)) # co1's staffs: ['D', 'E']
print("co2's staffs: {0}".format(co2.staffs)) # co2's staffs: ['D', 'E']
print(Company.__init__.__defaults__) # (['D', 'E'],)
print(co1.staffs is co2.staffs) # True

当函数的参数没有指定值的时候, 就会指向Company.__init__.__defaults__这里的默认值

如果解决这类问题

  • 第一: 最直接的解决之道就是不要使用列表和字典这种可变对象作为参数传递
  • 第二: 可以使用元组传递
  • 第三: 参数的默认值不要使用列表或字典, 让调用者每次都显式传递参数
  • 第四: 使用 def func(args, *kwargs) 的方式传递

Python中的垃圾回收算法是采用引用计数, 当一个对象的引用计数为0时, Python的垃圾回收机制就会将对象回收

1
2
a = "larry"
b = a

larry这个字符串对象, 在第一行被贴了a标签后, 引用计数为1, 之后在第二行, 由贴上了b标签, 此时, 该字符串对象的引用计数为2

1
2
3
a = "larry"
b = a
del a

注意: 在Python语言中, del语句操作某个对象的时候, 并不是直接将该对象在内存中删除, 而是将该对象的引用计数-1

1
2
3
4
5
6
7
8
9
10
>>> a = "larry"
>>> b = a
>>> del a
>>> id(b)
4572141808
>>> id(a)
Traceback (most recent call last):
File "<input>", line 1, in <module>
id(a)
NameError: name 'a' is not defined

从以上示例中可以看出, larry这个字符串对象在第一行被贴上了a标签, 此时字符串对象的引用计数为1, 接着第二行又被贴上了b标签, 此时该字符串对象的引用计数为2, 在第三行中, del语言删除了a变量(标签), 在后续的print中可以看出, 内存中实际的字符串对象并没有被删除, del语言只是删除了一个变量对该字符串对象的引用, 所以对于larry这个字符串对象来说, 效果只是引用计数-1

魔法函数之__del__

类中的__del__魔法函数, 支持我们自定义清理对象的逻辑, 当Python解释器使用del语言删除类的对象的时候, 会自动调用类中的__del__函数, 我们可以对其进行重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
>>> class Ref:
...
... def __init__(self, name):
... self.name = name
...
... def __del__(self):
... print("删除对象")
... del self.name
...
>>>
>>> r = Ref(name="larry")
>>> print(r.name)
larry
>>>
>>> del r
删除对象
>>>
>>> print(r.name)
Traceback (most recent call last):
File "<input>", line 1, in <module>
print(r.name)
NameError: name 'r' is not defined

我们可以通过重载__del__魔法函数, 自己灵活控制在del 对象的时候执行哪些善后操作

Python中is

Python中的is用来判断两个对象是否是同一个对象, 简单来说, 就是你通过使用Python中的id()函数去获取两个对象的内存地址, 如果地址相同, 即为同一个对象

1
2
3
4
5
6
7
8
>>> a = "abc"
>>> b = a
>>> id(a)
4363614224
>>> id(b)
4363614224
>>> a is b
True
1
2
3
4
5
6
7
8
>>> a = "abc"
>>> b = "abc"
>>> id(a)
4408735760
>>> id(b)
4408735760
>>> a is b
True

在Python的内部有一种intern机制, 在创建小段字符串和一定范围内的整型对象的时候, 会去建立一个全局唯一的对象, 当有其他”标签”(变量)引用到这个对象的时候, 不会重新生成一个新的对象, 而是会复用这个对象, 所以上面的例子🌰中, 虽然单独为两个变量赋值了短字符串, 但是他们实际指向的还是同一个对象, 同一块内存

1
2
3
4
5
6
7
8
>>> a = 1.1
>>> b = 1.1
>>> id(a)
4420807080
>>> id(b)
4420807488
>>> a is b
False

可以看到, Python中的intern机制只适用于小整数或短字符串, 同样为基础数据类型的浮点型, 也是不支持的

1
2
3
4
5
6
7
8
>>> a = [1, 2]
>>> b = [1, 2]
>>> id(a)
4428278664
>>> id(b)
4428154376
>>> a is b
False
1
2
3
4
5
6
7
8
>>> a = [1, 2]
>>> b = a
>>> id(a)
4432739208
>>> id(b)
4432739208
>>> a is b
True

Python中的==

Python中的==用来判断两个变量的值是否相等, 而不会纠结于两个变量的对象是否为同一个对象

1
2
3
4
>>> a = 1.1
>>> b = 1.1
>>> a == b
True

上面的例子中, 使用is判断的话, 两个对象是两个单独的对象, 但是使用==判断的话, 只是判断他们的值是否相等, 显而易见, 虽然存放在不同的内存地址上, 但是他们的值是相等的

魔法函数__eq__

==的判断实际上是由类中的__eq__这个魔法函数来实现的

1
2
3
4
5
6
7
8
9
10
11
12
class A:
def __init__(self, name):
self.name = name

def __eq__(self, obj):
return self.name == obj.name


a = A(name="larry")
aa = A(name="larry")

print(a == aa)

执行结果:

1
True

Python和Java中变量的本质不一样. Java在定义变量的时候, 就已经确定了该变量的数据类型且不可改变. Python的变量实质上是一个指针, 指针本身的大小是固定的, 它可以指向一个int, 也可以指向str或其他任何数据类型. 在Python中查找数据, 只需要找到指针即可找到对应的数据. 可以简单把Python变量理解为一个标签, 这个标签的大小是固定的, 标签可以贴在任何对象上

创建变量的过程

1
2
a = 1
a = "abc"
  • 首先, 在内存中声明一个int对象, 用来放数据
  • 然后给对象贴上标签

注意: 对于Python来说, 是a标签贴在了对象上, 而不是将对象分配给a. 在Java来说, 才是将对象分配到了a的”盒子”里. “标签”是动态语言的核心

1
2
3
4
a = [1, 2, 3]
b = a
b.append(d)
print(a)

执行结果:

1
[1, 2, 3, 4]

可以看到, a标签贴在了一个列表对象上, 然后将b也贴在了这个列表上, 所以后面对b标签的操作, 实际也是在操作a标签下的数据, 因为a, b两个标签此时对应的是同一对象

此时可能有人会有疑问, 上面是引用数据类型, 如果我操作的是基本数据类型呢? 请看如下实验

1
2
3
4
5
a = "abc"
b = a
b.replace("c", "x")
print(a)
print(b)

执行结果:

1
2
'abc'
'abc'

在第三行中, 我对字符串进行了替换操作, 为什么没有影响到最终的结果呢? 因为在字符串.replace()的操作后, 会返回一个新的字符串对象, 该字符串对象才是符合预期修改的字符串, 该操作新生成了对象返回, 没有影响到原始对象的数据. 而且我们可以通过id()函数来观察

1
2
id(a)
id(b)

执行结果:

1
2
4554176528
4554176528

从执行结果可以看出, 即使对象是基础数据类型, a, b两个标签依然是贴在同一个对象上, 没有生成新的对象

Go 语言的指针

Go 语言中获取内存地址

Go语言使用&符号来取地址

1
2
3
4
5
6
7
8
9
10

package main

import "fmt"

func main() {
var lv int = 10

fmt.Println(&lv)
}

执行结果为:

1
0xc420080030

Go语言中的指针

一个指针变量指向了一个值的内存地址, 指针也是一种数据类型, 在使用之前, 你需要先声明指针

1
2
var age *int  // 指向整型的指针
var name *string // 指向字符串的指针

*变量: * 变量是一种占位符, 用于引用计算机内存的地址, 可以理解为内存地址的标签

*指针: * 指向其他变量内存地址的值

*&: * 取变量的内存地址

*\: ** 取指针指向的内存地址的值

比喻: 现屋里有一个2*2的箱子, 左下角箱子的编号是1-1标签贴了苹果, 右下角箱子的编号是1-2标签贴了玉米, 左上角箱子的编号是2-1标签贴了桃子, 右上角箱子的编号是2-2标签贴了土豆, 房间里还立着一个大指示牌. 🔜 如下图所示

1
2
3
4
5
6
7
8
9
10

-----------------
|桃子🍑 | 土豆🥔 |
|2-1 | 2-2 |
----------------|
|苹果🍎 | 玉米🌽 |
|1-1 |1-2 |
-----------------

🔜 指示牌
  • 变量: 变量就是箱子上的标签, 桃子就是一个变量名, 而箱子里的桃子🍑则是值
  • 值: 实际保存内容的地方, 也就是实际放在盒子里的东西
  • 指针: 指针就理解为摆在房间里的指示牌, 指示牌可以指向任意一个箱子的地址, 也就是可以指向1-1 1-2 2-12-2
  • &: 获取变量的地址, 用在变量名前面, 理解为获取标签的地址, 比如&玉米, 意思就是获取玉米所在箱子的地址, 也就是1-2
  • *: 获取指针指向内存地址的值, 假如该指针指向了玉米的地址, 则使用*获取该指针的值即为箱子里所有的玉米🌽

*注意: * 指针是有数据类型的, 比如你声明了一个水果类型的指针, 那么这个指针(这个指示牌)就只能指向1-12-1 这两个地址, 因为只有这两个地址的值类型为水果; 同理, 如果你声明了一个蔬菜类型的指针, 也只能指向 1-22-2 这两块内存地址, 下面介绍指针的使用时, 仍将使用到该比喻

指针的使用

  • 定义指针变量
  • 为指针赋值
  • 访问指针变量中指向地址的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "fmt"

func main() {

// 声明一个水果变量, 假设int类型为水果类型
// 假设59为苹果🍎, 59即为盒子实际存放的东西
var fruit int = 59

// 声明一个int类型(水果类型)的指针变量
var indicator *int

// 将fruit变量的地址赋值给指针
// (将苹果盒子的地址1-1赋值给指示牌, 指示牌上写了/指向了1-1)
indicator = &fruit

// 访问指针指向的值
// 打开指示牌指向的盒子, 打开1-1盒子发现了真正存放的苹果🍎
fmt.Println(*indicator)
}

执行结果为:

1
59

空指针

当一个指针被声明后, 却没有被赋予任何变量的内存地址, 此时, 该指针的值为nil, nil指针也被称作空指针

nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值, 一个指针变量通常缩写为 ptr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import "fmt"

func main() {
var ptr *int
fmt.Println(ptr)

if ptr == nil {
fmt.Println("ptr为空指针")
} else {
fmt.Println("ptr为非空指针")
}
}

执行结果为:

1
2
<nil>
ptr为空指针

Go指针引用之向函数传递指针参数

值传递? 引用传递?

在Python中, 除了像int, str这样基本数据类型是值传递外, 其余绝大部分都是引用传递.
而在Go语言中, 只有值传递一种方式, Go语言中是通过传递指针来实现引用传递的

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
package main

import "fmt"

//交换值的函数
func swap1(a, b int) {
b, a = a, b
}

// *int 指向整型的指针
func swap2(a, b *int) {
// b指向的值赋值给a, a指向的值赋值给b
// *b, *a = *a, *b
// *a 取出 a 指针指向的值赋值给 c 变量(中间变量)
c := *a
// *b 取出 b 指针的指向的值赋值给 a指针的值
// 注意, 不能把值直接赋值给指针
// 指针只是保存值的地址
// 所以这里将 b 指针指向的值赋值给 a 指针指向的值, 即用 b 值覆盖 a 值
*a = *b
// 最后用 c 变量的值, 覆盖 b 值
*b = c
}

func swap3(a, b *int) {
*a, *b = *b, *a
}

func swap4(a, b int) (int, int) {
// 使用值传递的方式, 利用返回值来实现值的交换
return b, a
}

func main() {
a, b := 5, 9

// 值传递, 交换只在函数内生效, 不能改变外面的值
swap1(a, b)
fmt.Println(a, b) // 结果为: 5 9
// 从结果中可以看出, a, b两个变量进入到函数是值拷贝, 在函数内的操作并没有改变函数外a, b实际的值

// 交换地址(引用传递)
swap2(&a, &b)
fmt.Println(a, b)

// 再次交换地址(引用传递)
swap3(&a, &b)
fmt.Println(a, b)

// 值传递, 利用返回值交换值
// 注意返回值的顺序, 返回值里交换了, 接收返回值的顺序就不要变
// 如果return是 a,b 那么接收的时候就要变成 b, a 去接收
a, b = swap4(a, b)
fmt.Println(a, b)
}

以上交换的场景中, swap4为最佳方案, 简单有效! 指针在实际使用中也要充分考虑使用场景, 是否非用指针不可

Go指针引用之指向指针的指针

待续…

本文Demo介绍Go语言函数的使用, 包含多返回值, 闭包, 带参闭包, 函数作为变量的值, 函数的参数为函数, 函数参数为可变参数列表的这几种情况

在Go语言的函数的参数中, 没有Python中类似的默认参数, 可变参数等概念, 只有一个可变参数列表的功能

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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
package main

import (
"fmt"
)

// 函数的用法

// Go语言支持多返回值, 大多数情况下, 返回两个值, 一个值是结果, 一个值是error
// 如果函数执行正确, error值返回为nil
// 这样在if判断的时候, 就可以判断函数的第二个返回值是否非nil
func FuncDemo5(arg1, arg2 float64) (float64, error) {
if arg2 == 0 {
return float64(0), fmt.Errorf("被除数不能为0")
} else {
return arg1/arg2, nil
}
}

// 函数的用法之闭包
// 闭包的表现形式为函数里返回另外一个匿名函数, 该匿名函数为一个内联的语句或表达式
// func 函数名() 匿名函数() 匿名函数返回值
func FuncDemo6() func() int {
// 在命名函数内声明变量
i := 0
return func() int {
// 在匿名函数中可以直接访问或操作命名函数中定义的变量
i++
return i
}
}

// 带参闭包
func FuncDemo7(arg1, arg2 int) func(arg3, arg4 int) (int, error) {
i := 0
return func(arg3, arg4 int) (int, error) {
i++
sum := arg1 + arg2 + arg3 + arg4 + i
return sum, nil
}
}

// 函数还有一种用法: 作为变量的值
func FuncDemo8(arg1, arg2 int) {

// 声明一个普通变量, 该变量的值为一个函数
result := func(arg3, arg4 int) int {
return arg1 * arg2
}

// 该变量被赋值为一个函数后, 该变量即可被当做其函数调用
r := result(arg1, arg2)
fmt.Println(r)

}

// 函数的参数为函数
func FuncDemo9(arg1, arg2 int) (int, error) {
return arg1 + arg2, nil
}
func FuncDemo10(op func(arg1, arg2 int) (int, error), arg3, arg4 int) (int, error) {
result, err := op(arg3, arg4)
return result, err
}

// 函数的参数为可变参数列表(参数传多少个值都可以)
func FuncDemo11(numbers ...int) (int, error) {
s := 0
for i := range numbers {
s += numbers[i]
}
return s, nil
}

func main() {
if result, err := FuncDemo5(13,6); err != nil {
fmt.Println("Error")
} else {
fmt.Println(result)
}

if result, err := FuncDemo5(13,0); err != nil {
fmt.Println("Error")
} else {
fmt.Println(result)
}

nextValue := FuncDemo6()
fmt.Println(nextValue())
fmt.Println(nextValue())
fmt.Println(nextValue())

nextValue2 := FuncDemo6()
fmt.Println(nextValue2())
fmt.Println(nextValue2())

demo7 := FuncDemo7(5,9)
fmt.Println(demo7(6,13)) // i==1 5+9+6+13+1
fmt.Println(demo7(6,13)) // i==2 5+9+6+13+2
fmt.Println(demo7(10,10)) // i==3 5+9+10+10+3

FuncDemo8(5,6)

fmt.Println(FuncDemo10(FuncDemo9, 5,9))

fmt.Println(FuncDemo11(1,2,3,4,5))
}

执行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
2.1666666666666665
Error
1
2
3
1
2
34 <nil>
35 <nil>
37 <nil>
30
14 <nil>
15 <nil>

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
package main

import "fmt"

// func 函数名(参数1 参数类型, 参数2 参数类型....) 返回值类型
func FuncName(arg1 int, arg2 int, arg3 string) int {
switch arg3 {
case "+": return arg1 + arg2
case "-": return arg1 - arg2
default:
return 0
}
}

// 参数列表中, 相同类型的参数可以统一声明
func FuncDemo2(arg1, arg2 int, arg3 string) int {
switch arg3 {
case "+": return arg1 + arg2
case "-": return arg1 - arg2
default:
return 0
}
}

// 多返回值
func FuncDemo3(arg1, arg2 int, arg3 string) (int, int, string) {
return arg1, arg2, arg3
}

// 命名返回值
func FuncDemo4(arg1, arg2 int, arg3 string) (a, b int, c string) {
a = arg1
b = arg2
c = arg3
// 由于上面已经为返回值变量赋值, return时后面可以省略不写
return
}

// 函数参数
// 值传递: 指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数
// 引用传递: 指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数

func main() {
result := FuncName(5, 9, "+")
result2 := FuncDemo2(9, 5, "-")
fmt.Println(result, result2)

a, b, c := FuncDemo3(5,9,"*")
fmt.Println(a, b, c)

d, e, f := FuncDemo4(5,6, "/")
fmt.Println(d, e, f)

}

执行结果:

1
2
3
14 4
5 9 *
5 6 /

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
package main

import (
"fmt"
)

func ForDemo1() {
// 初始值; 循环条件表达式; 赋值
for i:=1; i<=5 ; i++ {
fmt.Println("-->", i)
}
}

func ForDemo2() {
var i int
// 也可以只写一个循环条件表达式, 类似于Python中的while 条件表达式循环
for i <= 5 {
fmt.Println("--->", i)
i++
}
}

func ForDemo3() {
// 死循环, 类似于Python中的while True
for {
fmt.Println("这是个死循环")
}
}

func main() {
ForDemo1()
ForDemo2()
ForDemo3()
}

执行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
--> 1
--> 2
--> 3
--> 4
--> 5
---> 0
---> 1
---> 2
---> 3
---> 4
---> 5
这是个死循环
这是个死循环
这是个死循环
...

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
package main

import (
"runtime"
"fmt"
)

func IfDemo() {
OSName := runtime.GOOS
// 现在if条件外面赋值, 再进行判断
if OSName == "windows" {
fmt.Println("It's Windows")
} else if OSName == "linux" {
fmt.Println("It's Linux🐧")
} else if OSName == "darwin" {
fmt.Println("It's MacOS🍎")
} else {
fmt.Println("Other OS")
}

// 也可以在if条件语句中先赋值再判断
// if条件里的变量作用域只在if代码块中
if OSArch := runtime.GOARCH; OSArch == "amd64" {
fmt.Println("64位操作系统❗️")
} else if OSArch == "amd32" {
fmt.Println("32位操作系统❗️️")
} else {
fmt.Println("什么鬼❓")
}
}

func SwitchDemo() {
OSName := runtime.GOOS
// switch语句, switch后可以跟任何类型的变量, case后跟该变量相同类型的值
switch OSName {
case "windows": fmt.Println("It's Windows")
case "linux": fmt.Println("It's Linux🐧")
case "darwin": fmt.Println("It's MacOS🍎")
case "unix", "ios", "android": fmt.Println("🙃")
default:
fmt.Println("什么鬼❓")
}

OSArch := runtime.GOARCH
// switch语句, switch后面可以不跟任何类型的变量, 由case后定义表达式
switch {
case OSArch == "amd64": fmt.Println("64位操作系统❗️")
case OSArch == "amd32": fmt.Println("32位操作系统❗️")
default:
fmt.Println("什么鬼❓")
}
}



func main() {
IfDemo()
SwitchDemo()
}

执行结果:

1
2
3
4
It's MacOS🍎
64位操作系统❗️
It's MacOS🍎
64位操作系统❗

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
package main

import "fmt"

//枚举
//GO语言没有枚举的关键字
//而是通过定义一组常量来实现

func enums() {
const (
cpp = 0
java = 1
python = 2
golang = 3
)

const (
iPhone = 2 * iota //iota表示自增值
_
iPad
iMac
iPod
)

const (
b = 1 << (10 * iota)
kb
mb
gb
tb
pb
)

fmt.Println(cpp, java, python, golang)
fmt.Println(iPhone, iPad, iMac, iPod)
fmt.Println(b, kb, mb, gb, tb, pb)
}

func main() {
enums()
}

执行结果:

1
2
3
0 1 2 3
0 4 6 8
1 1024 1048576 1073741824 1099511627776 1125899906842624