-
Notifications
You must be signed in to change notification settings - Fork 0
/
01、Go 简介
830 lines (554 loc) · 16.4 KB
/
01、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
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
Golang -- Go 语言的特点
1、 并发
Go 语言让含糊容易成为轻量的线程
这些线程被称为 goroutines
2、 Channel
goroutines 之间的通信通过 channel 达成
3、 垃圾回收
Go 带有垃圾回收机制,也就是 Go 编译的程序自带 runtime
4、 自带代码格式化工具
Go 自带 gofmt 工具,可以格式化代码,让它符合官方指定的标准
5、 类型后置
在声明变量的时候,变量名处在变量类型的前面
这样避免了很多嵌套时的语句混淆
6、 严格的类型
Go 有着严格的类型限定
类型之间必须显式转换
7、 UTF-8 支持
Go 语言的字符串和代码本身都是 UTF-8 的
也就是字符串和代码都可以书写 UTF-8 支持的字符
Hello,world
新建 helloworld.go 文件
写入以下内容
package main
import "fmt" //格式化 IO
/* 打印 */
func main() {
fmt.Printf("Hello,世界!")
}
注意
1、 文件的首行必然以 package xxx 开头
在 Go 中,每个源码文件都是一个“包”
每个包必须在第一行用 package xxx 表明包的名称
1.1 一个程序的入口,也就是主程序
必须以 main 作为包的名称
2、 默认情况下
Go 不需要在行尾写分号 ;
除非需要将多个语句放在同一行书写
3、 import 语句用于导入其他包
fmt 包是 Go 自带的格式化 IO 包
用于提供可格式化的输出等功能
3.1 import 的包必须被调用过
否则就会在编译时报错
4、 一行中 若出现 //
则后面的内容均为注释
5、 注释也可以以 /* 开头
以 */ 结尾
6、 Go 用函数组织程序
用 func 语句声明一个函数
6.1 每个主程序的入口必须是 main 函数
这个函数不接受任何的传入值
7、 所有的左括号不可以另起一行写
这是由于 Go 是自动添加分号的
若另起一行写左括号
Go 会在上一行加上分号
而断开代码块,产生错误
使用 Go 的两个重要的环境变量
$GOROOT
这个环境变量定义了 Go 的安装路径目录
若是默认安装,可不定义
$GOPATH
这个环境变量定义了 编写代码 的工作路径
需要指定,以方便工作
编译 Go 程序
命令行输入
go build helloworld.go
接着运行
./helloworld
声明变量
eg1.
var a int
a = 1
eg2.
var a int = 2
eg3.
var a = 2
eg4.
a := true
eg5.
var (
a int
b int = 2
)
eg6.
var a,b int
eg7.
a,b := 1,true
eg1 中
var 是关键字表示该行将声明一个变量
a 是变量名
int 是变量 a 的类型
此时 a 的值为 0
a = 1 则是为 a 赋值为 1
eg2 中
变量的声明和赋值可以简写为一行
eg3 中
让 Go 自动判断数据的类型
eg4 中
用 := 快速声明一个变量
此时变量类型会自动判断
这种快速声明的方式只能出现在函数代码块中
eg5 中
可以使用 var(...) 的形式快速声明多个变量
同时也接受声明变量同时赋值
eg6 中
若变量具有相同的类型
可以用这个方法简便声明
eg7 中
也可这样在同一行中
声明不同类型的变量
变量的赋值
var(
a int
b bool
)
eg1.
a,b = 1,true
eg2.
_,b = 1,true
eg1 中
类似变量的声明
可以这样赋值
eg2 中
可以用下划线 _ 舍去一个变量值
这样 1 这个值被舍去了
而 true 被赋予了变量 b
注意,所有声明的变量都必须被使用
否则编译时会报错
常见数据类型
1、布尔类型 bool
布尔类型的值仅为 true 和 false
不包含 0 1 空集 等等
2、数字类型
int 根据所在系统的构架自动选择 32 位或是 64 位
uint 无符号的 int 类型
int8 int16 int32(rune) int64
byte(uint8) uint16 uint32 uint64
注意1 byte 是 uint8 的别名
rune 是 int32 的别名
* rune 的英文含义是:如尼文,神秘的
注意2 上述的 10 中类型是完全不同的十种类型
不可以混用,需要显事转换
也就是说 int 和 int64 即使在 64 位的系统上
也是完全不同的类型
float32 float64
注意 没有 float 浮点型 这个类型
最后 即使在 32 位的系统上 64 位的类型依旧是 64 位的
3、常量 (这不是一种数据类型)
常量用关键字 const 来定义
定义常量时,可以指明类型,也可以不指明类型
const (
a int = 5
b = bool
)
常量的值和类型可以承前省略
const (
a = 5
b // b 为 int 型 值为 5
c // c 同 b
d = true
e // e 为 bool 型 值为 true
f // f 同 e
)
对于未声明类型的整型常量,可以使用 iota 预声明标记 来表示续递增的整型常量
const (
a = iota // a int = 0
b // b = iota --> b int = 1
c // c = iota --> c int = 2
)
iota 永远从 const 声明之后就开始计数,直到遇见下一个 const
但仅有继承了 iota 的值会显示 iota 表示的值
const (
a = 6 // 手动指定
b = iota // b 是这组常量的第二个 --> b int = 1
c // c = iota --> c int = 2
d = false // 手动指定
e // e 同 d 类型为 bool 值为 false
f = iota // f 是这组常量的第六个 --> f int = 5
g // g = iota --> g int = 6
h // h = iota --> h int 7
)
* iota 源自希腊字母 ι 读作 yota 表示微小之物
注意 所有的常量必须具有明确且固定的值
这个值可以来自于一个常量、承前省略或者 iota
但不可以来自一个变量、函数的返回值等等
4、字符串 string
eg1.
s1 := "Hello, 世界!"
s2 := `Hello
世界!` // 注意这里写的是反引号
eg1 中
字符串类型 只能用 双引号 或 反引号 引起来
不可以使用 单引号 引起来
双引号 表示 其中字符 可以转义
反引号 表示 其中字符 不可转义(raw 字符串)
单引号 表示 仅含有一个 Unicode 字符
eg2.
s := "中文字符串"
b := s[0] // 这样是无法取出 中 字的
fmt.Println(b) // 会打印出 228
eg2 中
字符串在 Go 中以连续的 UTF-8 格式保存
如果直接使用 切片 取值,则 Go 会以 1 byte 作为长度切片
比如上述案例会打印出 228,转化为 16 进制则为 e4
也就是 中 这个字的 UTF-8 编码 \xe4\xb8\xad 的第一位
eg3.
s1 := "Starting Part"
+ "Ending Part" // 这样的两行文字会提示错误
s2 := "Starting Part" +
"Ending Part" // 这样的两行文字则不会提示错误
eg3 中
参加 Go 行尾加分号 的问题
eg4.
s1 := "世界" // 字符表示
s2 := "\u4e16\u754c" // 四位 Unicode 表示
s3 := "\U00004e16\U0000754c" // 八位 Unicode 表示
s4 := "\xe4\xb8\x96\xe7\x95\x8c" // 16 进制 UTF-8 表示
eg4 中
上述三种方法都可以表示 Unicode 字符
由于 UTF-8 是变长编码,所以需要一种类型将 UTF-8 解码为 Unicode 编号
这种类型在 Go 中被称为 rune (实际上存储的是 int32)
5、rune
eg1.
var s string = "英文字符" // 原始字符
var c []rune = []rune(s) // 转换为 rune 切片(也可以转化为 int32 切片)
c[0] = '中' // 修改第一个切片,注意使用了单引号
fmt.Println(string(c)) // 打印 rune 切片 c,并通过 string 转化为字符串
eg1
正确的“修改”字符串的方法
6、复数
complex128 具有 64 位实部和 64 位虚部的复数
complex64 具有 32 位实部和 32 位虚部的复数
eg1.
var cp1 complex128 = 1.2+5.3i
注意1 复数的虚部不为 0 一定要写成 <num>i 的形式
不可以写成 1.2+i 或者 1.2+1*i
会与 i 是一个变量混淆
注意2 若使用简便声明复数,即便虚部为 0 也要写出虚部来
否则会被判定为一个实数
7、错误
eg1.
var e error = nil
eg1 中
定义了一个 error 类型的 e 变量,实际上 error 类型是一个接口
运算符与它们的优先级
最高级 * / % << >> & &^
+ - | ^
== != < <= > >=
<-
&&
最低级 ||
其中
& 为 按位与
| 为 按位或
^ 为 按位异或
&^ 为 位清除
未列出的有 非操作符 !
控制语句
1、if 语句
if [初始化语句1;]<判断语句1> {
操作1
} else if [初始化语句2;]<判断语句2> {
操作2
} else {
操作3
}
其中初始化语句 常用于 设置一个作用于为 if 语句的局部变量
if 的判断语句后的正括号必须写在同一行
if 语句常用于查错
f, err := os.Open(name, os.O_RDONLY, 0)
if err != nil {
return err
}
d, err := f.Stat()
if err != nil {
return err
}
2、goto 语句
语法
goto <标签>
例子:用 goto 模拟的循环语句
func myfunc() {
i := 0
Here: //这行声明了一个名为 Here 的标签
fmt.Println(i)
i++
goto Here //这行让执行语句跳转回了 Here 行
}
注意 标签是区分大小写的
3、for 语句
Go 中仅有 for 一种循环语句
由于 for 语句的语法很灵活,所以 Go 的循环控制也很丰富
for [初始化语句;][执行语句的条件;][执行后操作] {
执行语句
}
for <变量或变量组> := range <可迭代实例> {
执行语句
}
eg1.
for i:=0; i<10; i++ {
fmt.Print(i)
}
上述循环也可以写成
i := 0
for i<10 {
fmt.Print(i)
i++ // 注意 i++ 在 Go 中是一个语句而非表达式
// i++ 可以写成 i += 1
}
上述循环还可以写成
for i:=0; ; i++{
if i<10{
fmt.Print(i)
}else{
break
}
}
eg2.
var arr [4]int = [4]int{2,4,6,8}
for i,v := range arr { // 针对有数组具有两个返回值
fmt.Println(i," ",v)
}
eg3.
var str string = "中文字符串"
for p,c := range str { // 自动解析 Unicode 字符
fmt.Println(p,string(c)) // 并输出每个 UTF-8 编码下的字符码点(字符起始字节位置)
}
eg4.
逆序排列一个 数组 A
for i,j := 0, len(A)-1; i<j; i,j = i+1, j-1 { // 复杂 for 条件
a[i], a[j] = a[j], a[i]
}
4、break 和 continue
break 可以提前退出一个循环
eg1.
for i := 0; i < 10; i++ {
if i > 5 {
break // 若执行到 break,则立刻跳出本层循环体
}
}
eg2.
J: // 指定了一个名为 J 的标签
for j :0; j < 5; j++ {
for i := 0; i < 10; i++ {
break J // 执行到 break J 的时候,跳出至 J 这个循环
}
}
continue 可以立刻执行下一个循环
eg1.
for i := 0; i < 10; i++ {
fmt.Print("loop", i)
if i > 5 {
continue
}
fmt.Println(" Printed")
}
5、switch 语句
Go 中的 switch 语句很灵活
switch [初始化语句;][待匹配值] {
case [值1]:
[操作1]
case [值2]:
[操作2]
default:
[默认操作]
}
其中
[初始化语句]
常用于设置一个作用域为 switch 语句的变量
[待匹配值]
设置为某个值,若这个值没有指定,则匹配 true
case 里的 [值1] [值2]...
为要匹配的值,这个可以是明确指定的值
也可以在这里写表达式、语句,只要表达式或语句有一个返回值即可
default
设置一个都不匹配之后的动作
Go 中的 switch 一旦匹配到一个条目之后,就会立刻返回
若要在匹配到一个条目之后直接执行下一个 case 内的操作(同时也跳过 case 判断)
请使用 fallthrough 关键字
eg1.
switch {
case i<0:
fmt.Println("negative")
fallthrough // 向下判定下一个 case 语句
caes i< -100:
fmt.Println("very negative")
default:
fmt.Println("none negative")
}
switch 语句还可以用于类型判断
eg2.
const (
a int = 1
b float64 = 0.5
c rune = 's'
d string = "DD"
)
func main() {
var typeCarrier interface{} // switch 类型判断仅用于 interface
typeCarrier = b
switch typeCarrier.(type) { // 关键字 <接口>.(type)
case int:
fmt.Println("int")
case float64:
fmt.Println("float64")
case rune:
fmt.Println("rune")
case string:
fmt.Println("string")
default:
fmt.Println("???")
}
}
array、slice 和 map
array 有序的值的组
slice 数组的切片(指向 array 的指针列表)
map 散列值表
array
在 Go 中,一个空的数组需要这样声明 [<数组长度>]<元素类型>
eg1.
var arr [10]int
也就是说
1、数组中元素的类型必须相同
2、数组的 长度 同样决定数组的类型,不同长度的数组是不同的类型
3、数组也具有固定的大小,一个数组被声明之后,大小即不可改变
eg2.
var arr1 [2]int
var arr2 [5]int
arr1 == arr2 // 会引发错误, arr1 和 arr2 属于不同类型,不可以比较大小
在声明数组时,可以向其中传入默认值
eg3.
// 用大括号包括起元素,用逗号分隔
arr1 :=[3]string{"Tom","Anna","Bob"}
// 若 输入的元素数量 小于 数组长度
// 则用 元素的类型的默认值 向后填充满
// 下面的代码等价于 [5]int{2,4,6,8,0}
arr2 := [5]int{2,4,6,8}
// 可以用 <索引>.<值> 的方式指定 特定位置的值
// 下面的代码等价于 [10]int{0,0,0,0,0,0,0,0,1,2}
arr3 := [10]int{8:1,9:2}
// 若像自动判断数组长度,可用 ... 代替数组长度
arr4 := [...]float64{1,2,3}
若将一个数组赋值给另一个数组类型的变量的时候
Go 会 复制 整个数组至新的变量中
将数组传入函数的时候,也会复制一份数组,而非引用数组
复合数组的声明
eg4.
matrix := [4][3]float64{
[3]float64{0,0,0}, // offset
[3]float64{1,0,0}, // scale of x
[3]float64{0,1,0}, // scale of y
[3]float64{0,0,1}, // scale of z
}
// 简略写法
matrix := [4][3]float64{
{0,0,0}
{1,0,0}
{0,1,0}
{0,0,1}
}
上述代码就声明了一个三维变换矩阵复合数组
注意,若数组的反大括号没有与最后一个元素在同一行,请在最后一个元素后补充一个逗号
数组值的修改
eg5.
arr := [3]int{1,2,3}
arr[0] = 5 // 通过索引,立刻将 arr 修改为 [3]int{5,2,3}
for 语句遍历数组
eg6.
arr := [3]rune{'a','b','c'}
for i,v := range arr {
fmt.Printf("arr index: %d, value: %c\n",i,v)
}
获取数组的长度
len(<数组变量名>)
eg7.
arr := [...]int{1,2,3,4}
arr[len(arr)-1] = 10 // 将数组的最后一个值修改为 10
slice 切片
slice 是指向数组的指针
由于 slice 是引用类型
针对 slice 的修改会完全反映至所引用的数组
即使是 在函数内修改 slice 也会直接修改底层数组(函数拷贝了指针地址)
对已知的 数组 创建切片
eg1.
arr := [3]string{"a","b","c"}
sl = arr[0:]
直接创建切片
make([]<类型>,<切片长度>[,<切片容量>])
或者
[]<类型>{<元素1>,...}
eg2.
sl := make([]int, 3, 5) // 创建了一个长度为 3 的切片
// 它的底层是一个长度为 5 的数组
sl := []int{1,2,3} // 或者用 不带任何内容的 [] 来表示切片
修改 切片的 元素的值 和修改 数组的 元素的值 类似
eg3.
sl := make([]int, 3, 5)
sl[1] = 10 // 切片为 []int{}
切片的长度是可调的
append(<切片变量名>,<追加元素1>,...)
append(<切片变量名>,<要追加的切片><...>)
eg4.
sl := []int{2,4,6,8}
sl1 := append(sl,1,3) // 得到切片 sl1 = []int{2,4,6,8,1,3}
sl2 := append(sl[2:4],sl1[4:6]...)
// 注意切片后的三个点不可以省略,且仅能追加一个切片
// 取 sl 的索引为 2 至 3 的元素组成 待追加切片
// 取 sl1 的索引为 4 至 5 的元素组成 追加元素
// 得到切片 sl2 := []int{6,8,1,3}
注意,若 append 后的 切片的容量扩大了,也就是底层数组的长度变大了
那么 Go 就会产生一个新的数组
而切片就会指向这个新的数组,而非原来的数组
拷贝一个切片内的元素
copy(<目标切片>,<原始切片>)
能够拷贝的元素数量取决于 <目标切片> 和 <原始切片> 长度的最小值
该命令会返回拷贝的元素的数量
eg5.
var sl1 = []int{2,4,6,8}
var sl2 = make([]int,3,5)
n1 := copy(sl2,sl1) // 返回 n1 = 3, sl2 = []int{2,4,6}
获取 切片的 长度 和 容量
len(<切片变量名>)
cap(<切片变量名>)
map 映射
map 就是 Go 中的散列表类型
map 的表示
map[<键类型>]<值类型>
eg1.
map[int]string // 以 整数 作为键 以 字符串 作为值
map[[2]int]map[interface{}]*[3]float64 // 复杂 map 结构
由于键值必须参与是否相同的比较,所以键类型不可以为 函数、映射和切片
map 的创建
eg2.
map1 := map[string]int{
"A":1, // 用 <键>:<值> 的方式赋值
"B":2,
"C":3,
}
map2 := make(map[string]int, 10) // 用 make 创建一个长度为 10 的 map,其中 长度可以不指定
新增或修改 map 中的数据
eg3.
map1 := map[string]int{"A":1}
map1["B"] = 2 // 新增一个键值对
map1["A"] = 4 // 修改原有的键值对
提取 map 中的数据
eg4.
map1 := map[string]int{
"A":1,
"B":2,
}
v,ok := map1["A"] // v = 1, ok = true // 成功取值,且值为 1
v,ok := map1["C"] // v = 0, ok = false // 取值失败,此时 v 为默认值 0
删除 map 中的数据
delete(<map变量名>, <键名称>)
若存在键名称,则删除,若不存在则不执行操作