├── .gitignore
├── GolangStudy.xcodeproj
├── project.pbxproj
└── project.xcworkspace
│ └── contents.xcworkspacedata
├── GolangStudy
├── AppDelegate.swift
├── Base.lproj
│ ├── LaunchScreen.xib
│ └── Main.storyboard
├── Course.swift
├── DetailViewController.swift
├── Images.xcassets
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── Icon-29-1.png
│ │ ├── Icon-29.png
│ │ ├── Icon-29@2x-1.png
│ │ ├── Icon-29@2x.png
│ │ ├── Icon-40.png
│ │ ├── Icon-40@2x-1.png
│ │ ├── Icon-40@2x.png
│ │ ├── Icon-50.png
│ │ ├── Icon-50@2x.png
│ │ ├── Icon-57.png
│ │ ├── Icon-57@2x.png
│ │ ├── Icon-60@2x.png
│ │ ├── Icon-72.png
│ │ ├── Icon-72@2x.png
│ │ ├── Icon-76.png
│ │ └── Icon-76@2x.png
│ └── LaunchImage.launchimage
│ │ ├── Contents.json
│ │ ├── lanuch@1x.png
│ │ ├── lanuch@2x-1.png
│ │ ├── lanuch@2x.png
│ │ ├── launch@r4-1.png
│ │ └── launch@r4.png
├── Info.plist
├── ViewController.swift
├── XmlParseUtil.swift
├── basic_course_list.xml
├── example_course_list.xml
└── tutorial
│ ├── go_by_example
│ ├── default.css
│ ├── go_array.html
│ ├── go_atomic_counter.html
│ ├── go_base64_encoding.html
│ ├── go_buffered_channel.html
│ ├── go_channel_close.html
│ ├── go_channel_direction.html
│ ├── go_channel_range.html
│ ├── go_channel_select.html
│ ├── go_channel_sync.html
│ ├── go_cmd_line_argument.html
│ ├── go_cmd_line_tag.html
│ ├── go_collection_manipulate.html
│ ├── go_constant.html
│ ├── go_customized_sort.html
│ ├── go_defer.html
│ ├── go_environment_variable.html
│ ├── go_error_handle.html
│ ├── go_exit.html
│ ├── go_for_loop.html
│ ├── go_func_callback.html
│ ├── go_func_closure.html
│ ├── go_func_define.html
│ ├── go_func_multiple_ret_value.html
│ ├── go_func_named_ret_value.html
│ ├── go_hello_world.html
│ ├── go_if_elseif.html
│ ├── go_interface.html
│ ├── go_json.html
│ ├── go_line_filters.html
│ ├── go_map.html
│ ├── go_method.html
│ ├── go_mutex.html
│ ├── go_non_blocking_channel.html
│ ├── go_number.html
│ ├── go_number_parse.html
│ ├── go_panic.html
│ ├── go_parallel.html
│ ├── go_parallel_channel.html
│ ├── go_pointer.html
│ ├── go_process_run.html
│ ├── go_process_trigger.html
│ ├── go_rand_number.html
│ ├── go_range.html
│ ├── go_read_file.html
│ ├── go_recursion.html
│ ├── go_regex.html
│ ├── go_req_freq_control.html
│ ├── go_sha1_hash.html
│ ├── go_signal_handle.html
│ ├── go_slice.html
│ ├── go_sort.html
│ ├── go_stateful_goroutine.html
│ ├── go_string_byte_convert.html
│ ├── go_string_format.html
│ ├── go_string_manipulate.html
│ ├── go_struct.html
│ ├── go_switch.html
│ ├── go_ticker.html
│ ├── go_time.html
│ ├── go_time_format_parse.html
│ ├── go_timeout.html
│ ├── go_timer.html
│ ├── go_timestamp.html
│ ├── go_url_parse.html
│ ├── go_variable.html
│ ├── go_variable_argument_list.html
│ ├── go_working_pool.html
│ ├── go_write_file.html
│ └── highlight.pack.js
│ └── go_tutorial
│ ├── default.css
│ ├── go_tutorial_10_use_package_test.html
│ ├── go_tutorial_1_how_to_install_go.html
│ ├── go_tutorial_2_data_type.html
│ ├── go_tutorial_3_variable.html
│ ├── go_tutorial_4_control_structure.html
│ ├── go_tutorial_5_array_slice_map.html
│ ├── go_tutorial_6_func.html
│ ├── go_tutorial_7_pointer.html
│ ├── go_tutorial_8_struct_interface.html
│ ├── go_tutorial_9_parallel_compute.html
│ └── highlight.pack.js
├── LICENSE
├── README.md
└── Screenshots
├── 27D2524682F39141366C816BF4E6D9F3.png
├── 3BA9A25E9C3A79D65C23DB5ED42BEC35.png
├── 92A390D999D952485BE806130899F09B.png
└── golang.gif
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | build/
4 | *.pbxuser
5 | !default.pbxuser
6 | *.mode1v3
7 | !default.mode1v3
8 | *.mode2v3
9 | !default.mode2v3
10 | *.perspectivev3
11 | !default.perspectivev3
12 | xcuserdata
13 | *.xccheckout
14 | *.moved-aside
15 | DerivedData
16 | *.hmap
17 | *.ipa
18 | *.xcuserstate
19 |
20 | # CocoaPods
21 | #
22 | # We recommend against adding the Pods directory to your .gitignore. However
23 | # you should judge for yourself, the pros and cons are mentioned at:
24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
25 | #
26 | # Pods/
27 |
--------------------------------------------------------------------------------
/GolangStudy.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
数组是一个具有相同数据类型
的元素组成的固定长度
的有序集合
。
在Go语言中,数组是值类型,长度是类型的组成部分,也就是说"[10]int
"和“[20]int
”是完全不同的两种数组类型。
同类型的两个数组支持"=="和"!="比较,但是不能比较大小。
15 |数组作为参数时,函数内部不改变数组内部的值,除非是传入数组的指针。
16 |数组的指针:*[3]int
17 |指针数组:[2]*int
18 | 19 |示例1:
20 |package main
21 |
22 | import "fmt"
23 |
24 | func main() {
25 |
26 | // 这里我们创建了一个具有5个元素的整型数组
27 | // 元素的数据类型和数组长度都是数组的一部分
28 | // 默认情况下,数组元素都是零值
29 | // 对于整数,零值就是0
30 | var a [5]int
31 | fmt.Println("emp:", a)
32 |
33 | // 我们可以使用索引来设置数组元素的值,就像这样
34 | // "array[index] = value" 或者使用索引来获取元素值,
35 | // 就像这样"array[index]"
36 | a[4] = 100
37 | fmt.Println("set:", a)
38 | fmt.Println("get:", a[4])
39 |
40 | // 内置的len函数返回数组的长度
41 | fmt.Println("len:", len(a))
42 |
43 | // 这种方法可以同时定义和初始化一个数组
44 | b := [5]int{1, 2, 3, 4, 5}
45 | fmt.Println("dcl:", b)
46 |
47 | // 数组都是一维的,但是你可以把数组的元素定义为一个数组
48 | // 来获取多维数组结构
49 | var twoD [2][3]int
50 | for i := 0; i < 2; i++ {
51 | for j := 0; j < 3; j++ {
52 | twoD[i][j] = i + j
53 | }
54 | }
55 | fmt.Println("2d: ", twoD)
56 | }
57 |
58 | 输出结果为
59 |emp: [0 0 0 0 0]
60 | set: [0 0 0 0 100]
61 | get: 100
62 | len: 5
63 | dcl: [1 2 3 4 5]
64 | 2d: [[0 1 2] [1 2 3]]
65 |
66 | 拥有固定长度
是数组的一个特点,但是这个特点有时候会带来很多不便,尤其在一个集合元素个数不固定的情况下。这个时候我们更多地使用切片
。
示例2:
68 |可以用new创建数组,并返回数组的指针
69 |package main
70 |
71 | import "fmt"
72 |
73 | func main() {
74 | var a = new([5]int)
75 | test(a)
76 | fmt.Println(a, len(a))
77 | }
78 |
79 | func test(a *[5]int) {
80 | a[1] = 5
81 | }
82 |
83 | 输出结果:
84 |&[0 5 0 0 0] 5
85 |
86 | 示例3:
87 |package main
88 |
89 | import "fmt"
90 |
91 | func main() {
92 | a := [...]User{
93 | {0, "User0"},
94 | {8, "User8"},
95 | }
96 | b := [...]*User{
97 | {0, "User0"},
98 | {8, "User8"},
99 | }
100 | fmt.Println(a, len(a))
101 | fmt.Println(b, len(b))
102 |
103 | }
104 |
105 | type User struct {
106 | Id int
107 | Name string
108 | }
109 |
110 | 输出结果:
111 |[{0 User0} {8 User8}] 2
112 | [0x1f216130 0x1f216140] 2
113 |
114 |
115 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_atomic_counter.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Go里面的管理协程状态的主要机制就是通道通讯。这些我们上面的例子介绍过。这里还有一些管理状态的机制,下面我们看看多协程原子访问计数器的例子,这个功能是由sync/atomic包提供的函数来实现的。
13 |package main
14 |
15 | import "fmt"
16 | import "time"
17 | import "sync/atomic"
18 | import "runtime"
19 |
20 | func main() {
21 |
22 | // 我们使用一个无符号整型来代表一个永远为正整数的counter
23 | var ops uint64 = 0
24 |
25 | // 为了模拟并行更新,我们使用50个协程来每隔1毫秒来
26 | // 增加一下counter值,注意这里的50协程里面的for循环,
27 | // 也就是说如果主协程不退出,这些协程将永远运行下去
28 | // 所以这个程序每次输出的值有可能不一样
29 | for i := 0; i < 50; i++ {
30 | go func() {
31 | for {
32 | // 为了能够保证counter值增加的原子性,我们使用
33 | // atomic包中的AddUint64方法,将counter的地址和
34 | // 需要增加的值传递给函数即可
35 | atomic.AddUint64(&ops, 1)
36 |
37 | // 允许其他的协程来处理
38 | runtime.Gosched()
39 | }
40 | }()
41 | }
42 |
43 | //等待1秒中,让协程有时间运行一段时间
44 | time.Sleep(time.Second)
45 |
46 | // 为了能够在counter仍被其他协程更新值的同时安全访问counter值,
47 | // 我们获取一个当前counter值的拷贝,这里就是opsFinal,需要把
48 | // ops的地址传递给函数`LoadUint64`
49 | opsFinal := atomic.LoadUint64(&ops)
50 | fmt.Println("ops:", opsFinal)
51 | }
52 |
53 | 我们多运行几次,结果如下:
54 |ops: 7499289
55 | ops: 7700843
56 | ops: 7342417
57 |
58 |
59 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_base64_encoding.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Go提供了对base64编码和解码的内置支持
13 |package main
14 |
15 | // 这种导入包的语法将默认的base64起了一个别名b64,这样
16 | // 我们在下面就可以直接使用b64表示这个包,省点输入量
17 | import b64 "encoding/base64"
18 | import "fmt"
19 |
20 | func main() {
21 |
22 | // 这里是我们用来演示编码和解码的字符串
23 | data := "abc123!?$*&()'-=@~"
24 |
25 | // Go支持标准的和兼容URL的base64编码。
26 | // 我们这里使用标准的base64编码,这个
27 | // 函数需要一个`[]byte`参数,所以将这
28 | // 个字符串转换为字节数组
29 | sEnc := b64.StdEncoding.EncodeToString([]byte(data))
30 | fmt.Println(sEnc)
31 |
32 | // 解码一个base64编码可能返回一个错误,
33 | // 如果你不知道输入是否是正确的base64
34 | // 编码,你需要检测一些解码错误
35 | sDec, _ := b64.StdEncoding.DecodeString(sEnc)
36 | fmt.Println(string(sDec))
37 | fmt.Println()
38 |
39 | // 使用兼容URL的base64编码和解码
40 | uEnc := b64.URLEncoding.EncodeToString([]byte(data))
41 | fmt.Println(uEnc)
42 | uDec, _ := b64.URLEncoding.DecodeString(uEnc)
43 | fmt.Println(string(uDec))
44 | }
45 |
46 | 运行结果
47 |YWJjMTIzIT8kKiYoKSctPUB+
48 | abc123!?$*&()'-=@~
49 |
50 | YWJjMTIzIT8kKiYoKSctPUB-
51 | abc123!?$*&()'-=@~
52 |
53 | 这两种方法都将原数据编码为base64编码,区别在于标准的编码后面是+
,而兼容URL的编码方式后面是-
。
默认情况下,通道是不带缓冲区的。 13 | 发送端发送数据,同时必须又接收端相应的接收数据。 14 | 而带缓冲区的通道则允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。 15 | 不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。
16 |package main
17 |
18 | import "fmt"
19 |
20 | func main() {
21 |
22 | // 这里我们定义了一个可以存储字符串类型的带缓冲通道
23 | // 缓冲区大小为2
24 | messages := make(chan string, 2)
25 |
26 | // 因为messages是带缓冲的通道,我们可以同时发送两个数据
27 | // 而不用立刻需要去同步读取数据
28 | messages <- "buffered"
29 | messages <- "channel"
30 |
31 | // 然后我们和上面例子一样获取这两个数据
32 | fmt.Println(<-messages)
33 | fmt.Println(<-messages)
34 | }
35 |
36 | 运行结果
37 |buffered
38 | channel
39 |
40 |
41 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_channel_close.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 关闭通道的意思是该通道将不再允许写入数据。这个方法可以让通道数据的接受端知道数据已经全部发送完成了。
13 |package main
14 |
15 | import "fmt"
16 |
17 | // 在这个例子中,我们使用通道jobs在main函数所在的协程和一个数据
18 | // 接收端所在的协程通信。当我们数据发送完成后,我们关闭jobs通道
19 | func main() {
20 | jobs := make(chan int, 5)
21 | done := make(chan bool)
22 |
23 | // 这里是数据接收端协程,它重复使用`j, more := <-jobs`来从通道
24 | // jobs获取数据,这里的more在通道关闭且通道中不再有数据可以接收的
25 | // 时候为false,我们通过判断more来决定所有的数据是否已经接收完成。
26 | // 如果所有数据接收完成,那么向done通道写入true
27 | go func() {
28 | for {
29 | j, more := <-jobs
30 | if more {
31 | fmt.Println("received job", j)
32 | } else {
33 | fmt.Println("received all jobs")
34 | done <- true
35 | return
36 | }
37 | }
38 | }()
39 |
40 | // 这里向jobs通道写入三个数据,然后关闭通道
41 | for j := 1; j <= 3; j++ {
42 | jobs <- j
43 | fmt.Println("sent job", j)
44 | }
45 | close(jobs)
46 | fmt.Println("sent all jobs")
47 |
48 | // 我们知道done通道在接收数据的时候会阻塞,所以在所有的数据发送
49 | // 接收完成后,写入done的数据将在这里被接收,然后程序结束。
50 | <-done
51 | }
52 |
53 | 运行结果
54 |sent job 1
55 | received job 1
56 | sent job 2
57 | sent job 3
58 | sent all jobs
59 | received job 2
60 | received job 3
61 | received all jobs
62 |
63 |
64 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_channel_direction.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 当使用通道作为函数的参数时,你可以指定该通道是只读的还是只写的。这种设置有时候会提高程序的参数类型安全。
13 |package main
14 |
15 | import "fmt"
16 |
17 | // 这个ping函数只接收能够发送数据的通道作为参数,试图从这个通道接收数据
18 | // 会导致编译错误,这里只写的定义方式为`chan<- string`表示这个类型为
19 | // 字符串的通道为只写通道
20 | func ping(pings chan<- string, msg string) {
21 | pings <- msg
22 | }
23 |
24 | // pong函数接收两个通道参数,一个是只读的pings,使用`<-chan string`定义
25 | // 另外一个是只写的pongs,使用`chan<- string`来定义
26 | func pong(pings <-chan string, pongs chan<- string) {
27 | msg := <-pings
28 | pongs <- msg
29 | }
30 |
31 | func main() {
32 | pings := make(chan string, 1)
33 | pongs := make(chan string, 1)
34 | ping(pings, "passed message")
35 | pong(pings, pongs)
36 | fmt.Println(<-pongs)
37 | }
38 |
39 | 运行结果
40 |passed message
41 |
42 | 其实这个例子就是把信息首先写入pings通道里面,然后在pong函数里面再把信息从pings通道里面读出来再写入pongs通道里面,最后在main函数里面将信息从pongs通道里面读出来。 43 | 在这里,pings和pongs事实上是可读且可写的,不过作为参数传递的时候,函数参数限定了通道的方向。不过pings和pongs在ping和pong函数里面还是可读且可写的。只是ping和pong函数调用的时候把它们当作了只读或者只写。
44 | 45 | -------------------------------------------------------------------------------- /GolangStudy/tutorial/go_by_example/go_channel_range.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |我们知道range函数可以遍历数组,切片,字典等。这里我们还可以使用range函数来遍历通道以接收通道数据。
13 |package main
14 |
15 | import "fmt"
16 |
17 | func main() {
18 |
19 | // 我们遍历queue通道里面的两个数据
20 | queue := make(chan string, 2)
21 | queue <- "one"
22 | queue <- "two"
23 | close(queue)
24 |
25 | // range函数遍历每个从通道接收到的数据,因为queue再发送完两个
26 | // 数据之后就关闭了通道,所以这里我们range函数在接收到两个数据
27 | // 之后就结束了。如果上面的queue通道不关闭,那么range函数就不
28 | // 会结束,从而在接收第三个数据的时候就阻塞了。
29 | for elem := range queue {
30 | fmt.Println(elem)
31 | }
32 | }
33 |
34 | 运行结果
35 |one
36 | two
37 |
38 | 这个例子同时说明了,即使关闭了一个非空通道,我们仍然可以从通道里面接收到值。
39 | 40 | -------------------------------------------------------------------------------- /GolangStudy/tutorial/go_by_example/go_channel_select.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |Go的select关键字可以让你同时等待多个通道操作,将协程(goroutine),通道(channel)和select结合起来构成了Go的一个强大特性。
13 |package main
14 |
15 | import "time"
16 | import "fmt"
17 |
18 | func main() {
19 |
20 | // 本例中,我们从两个通道中选择
21 | c1 := make(chan string)
22 | c2 := make(chan string)
23 |
24 | // 为了模拟并行协程的阻塞操作,我们让每个通道在一段时间后再写入一个值
25 | go func() {
26 | time.Sleep(time.Second * 1)
27 | c1 <- "one"
28 | }()
29 | go func() {
30 | time.Sleep(time.Second * 2)
31 | c2 <- "two"
32 | }()
33 |
34 | // 我们使用select来等待这两个通道的值,然后输出
35 | for i := 0; i < 2; i++ {
36 | select {
37 | case msg1 := <-c1:
38 | fmt.Println("received", msg1)
39 | case msg2 := <-c2:
40 | fmt.Println("received", msg2)
41 | }
42 | }
43 | }
44 |
45 | 输出结果
46 |received one
47 | received two
48 |
49 | 如我们所期望的,程序输出了正确的值。对于select语句而言,它不断地检测通道是否有值过来,一旦发现有值过来,立刻获取输出。
50 | 51 | -------------------------------------------------------------------------------- /GolangStudy/tutorial/go_by_example/go_channel_sync.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |我们使用通道来同步协程之间的执行。
13 | 下面的例子是通过获取同步通道数据来阻塞程序执行的方法来等待另一个协程运行结束的。
14 | 也就是说main函数所在的协程在运行到<-done
语句的时候将一直等待worker函数所在的协程执行完成,向通道写入数据才会(从通道获得数据)继续执行。
package main
16 |
17 | import "fmt"
18 | import "time"
19 |
20 | // 这个worker函数将以协程的方式运行
21 | // 通道`done`被用来通知另外一个协程这个worker函数已经执行完成
22 | func worker(done chan bool) {
23 | fmt.Print("working...")
24 | time.Sleep(time.Second)
25 | fmt.Println("done")
26 |
27 | // 向通道发送一个数据,表示worker函数已经执行完成
28 | done <- true
29 | }
30 |
31 | func main() {
32 |
33 | // 使用协程来调用worker函数,同时将通道`done`传递给协程
34 | // 以使得协程可以通知别的协程自己已经执行完成
35 | done := make(chan bool, 1)
36 | go worker(done)
37 |
38 | // 一直阻塞,直到从worker所在协程获得一个worker执行完成的数据
39 | <-done
40 | }
41 |
42 | 运行结果
43 |working...done
44 |
45 | 如果我们从main函数里面移除<-done
语句,那么main函数在worker协程开始运行之前就结束了。
命令行参数是一种指定程序运行初始参数的常用方式。比如go run hello.go
使用run
和hello.go
参数来执行程序。
package main
14 |
15 | import "os"
16 | import "fmt"
17 |
18 | func main() {
19 |
20 | // `os.Args`提供了对命令行参数的访问,注意该
21 | // 切片的第一个元素是该程序的运行路径,而
22 | // `os.Args[1:]`则包含了该程序的所有参数
23 | argsWithProg := os.Args
24 | argsWithoutProg := os.Args[1:]
25 |
26 | // 你可以使用索引的方式来获取单个参数
27 | arg := os.Args[3]
28 |
29 | fmt.Println(argsWithProg)
30 | fmt.Println(argsWithoutProg)
31 | fmt.Println(arg)
32 | }
33 |
34 | 在运行该程序的时候,需要首先用go build
将代码编译为可执行文件,然后提供足够数量的参数。例如
$ go build command-line-arguments.go
36 | $ ./command-line-arguments a b c d
37 | [./command-line-arguments a b c d]
38 | [a b c d]
39 | c
40 |
41 |
42 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_cmd_line_tag.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 命令行参数标记是为命令行程序指定选项参数的常用方法。例如,在命令wc -l
中,-l
就是一个命令行参数标记。
Go提供了flag
包来支持基本的命令行标记解析。我们这里将要使用这个包提供的方法来实现带选项的命令行程序。
package main
15 |
16 | import "flag"
17 | import "fmt"
18 |
19 | func main() {
20 |
21 | // 基础的标记声明适用于string,integer和bool型选项。
22 | // 这里我们定义了一个标记`word`,默认值为`foo`和一
23 | // 个简短的描述。`flag.String`函数返回一个字符串指
24 | // 针(而不是一个字符串值),我们下面将演示如何使
25 | // 用这个指针
26 | wordPtr := flag.String("word", "foo", "a string")
27 |
28 | // 这里定义了两个标记,一个`numb`,另一个是`fork`,
29 | // 使用和上面定义`word`标记相似的方法
30 | numbPtr := flag.Int("numb", 42, "an int")
31 | boolPtr := flag.Bool("fork", false, "a bool")
32 |
33 | // 你也可以程序中任意地方定义的变量来定义选项,只
34 | // 需要把该变量的地址传递给flag声明函数即可
35 | var svar string
36 | flag.StringVar(&svar, "svar", "bar", "a string var")
37 |
38 | // 当所有的flag声明完成后,使用`flag.Parse()`来分
39 | // 解命令行选项
40 | flag.Parse()
41 |
42 | // 这里我们仅仅输出解析后的选项和任何紧跟着的位置
43 | // 参数,注意我们需要使用`*wordPtr`的方式来获取最
44 | // 后的选项值
45 | fmt.Println("word:", *wordPtr)
46 | fmt.Println("numb:", *numbPtr)
47 | fmt.Println("fork:", *boolPtr)
48 | fmt.Println("svar:", svar)
49 | fmt.Println("tail:", flag.Args())
50 | }
51 |
52 | 为了运行示例,你需要先将程序编译为可执行文件。
53 |go build command-line-flags.go
54 |
55 | 下面分别看看给予该命令行程序不同选项参数的例子:
56 |(1) 给所有的选项设置一个参数
57 |$ ./command-line-flags -word=opt -numb=7 -fork -svar=flag
58 | word: opt
59 | numb: 7
60 | fork: true
61 | svar: flag
62 | tail: []
63 |
64 | (2) 如果你不设置flag,那么它们自动采用默认的值
65 |$ ./command-line-flags -word=opt
66 | word: opt
67 | numb: 42
68 | fork: false
69 | svar: bar
70 | tail: []
71 |
72 | (3) 尾部的位置参数可以出现在任意一个flag后面
73 |$ ./command-line-flags -word=opt a1 a2 a3
74 | word: opt
75 | numb: 42
76 | fork: false
77 | svar: bar
78 | tail: [a1 a2 a3]
79 |
80 | (4) 注意flag包要求所有的flag都必须出现在尾部位置参数的前面,否则这些flag将被当作位置参数处理
81 |$ ./command-line-flags -word=opt a1 a2 a3 -numb=7
82 | word: opt
83 | numb: 42
84 | fork: false
85 | svar: bar
86 | trailing: [a1 a2 a3 -numb=7]
87 |
88 | (5) 使用-h
或者--help
这两个flag来自动地生成命令行程序的帮助信息
$ ./command-line-flags -h
90 | Usage of ./command-line-flags:
91 | -fork=false: a bool
92 | -numb=42: an int
93 | -svar="bar": a string var
94 | -word="foo": a string
95 |
96 | (6) 如果你提供了一个程序不支持的flag,那么程序会打印一个错误信息和帮助信息
97 |$ ./command-line-flags -wat
98 | flag provided but not defined: -wat
99 | Usage of ./go_cmd_flag:
100 | -fork=false: a bool
101 | -numb=42: an int
102 | -svar="bar": a string var
103 | -word="foo": a string
104 |
105 |
106 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_collection_manipulate.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 我们经常需要程序去处理一些集合数据,比如选出所有符合条件的数据或者使用一个自定义函数将一个集合元素拷贝到另外一个集合。
13 |在一些语言里面,通常是使用泛化数据结构或者算法。但是Go不支持泛化类型,在Go里面如果你的程序或者数据类型需要操作集合,那么通常是为集合提供一些操作函数。
14 |这里演示了一些操作strings切片的集合函数,你可以使用这些例子来构建你自己的函数。注意在有些情况下,使用内联集合操作代码会更清晰,而不是去创建新的帮助函数。
15 |package main
16 |
17 | import "strings"
18 | import "fmt"
19 |
20 | // 返回t在vs中第一次出现的索引,如果没有找到t,返回-1
21 | func Index(vs []string, t string) int {
22 | for i, v := range vs {
23 | if v == t {
24 | return i
25 | }
26 | }
27 | return -1
28 | }
29 |
30 | // 如果t存在于vs中,那么返回true,否则false
31 | func Include(vs []string, t string) bool {
32 | return Index(vs, t) >= 0
33 | }
34 |
35 | // 如果使用vs中的任何一个字符串作为函数f的参数可以让f返回true,
36 | // 那么返回true,否则false
37 | func Any(vs []string, f func(string) bool) bool {
38 | for _, v := range vs {
39 | if f(v) {
40 | return true
41 | }
42 | }
43 | return false
44 | }
45 |
46 | // 如果分别使用vs中所有的字符串作为f的参数都能让f返回true,
47 | // 那么返回true,否则返回false
48 | func All(vs []string, f func(string) bool) bool {
49 | for _, v := range vs {
50 | if !f(v) {
51 | return false
52 | }
53 | }
54 | return true
55 | }
56 |
57 | // 返回一个新的字符串切片,切片的元素为vs中所有能够让函数f
58 | // 返回true的元素
59 | func Filter(vs []string, f func(string) bool) []string {
60 | vsf := make([]string, 0)
61 | for _, v := range vs {
62 | if f(v) {
63 | vsf = append(vsf, v)
64 | }
65 | }
66 | return vsf
67 | }
68 |
69 | // 返回一个bool类型切片,切片的元素为vs中所有字符串作为f函数
70 | // 参数所返回的结果
71 | func Map(vs []string, f func(string) string) []string {
72 | vsm := make([]string, len(vs))
73 | for i, v := range vs {
74 | vsm[i] = f(v)
75 | }
76 | return vsm
77 | }
78 |
79 | func main() {
80 |
81 | // 来,试试我们的字符串切片操作函数
82 | var strs = []string{"peach", "apple", "pear", "plum"}
83 |
84 | fmt.Println(Index(strs, "pear"))
85 |
86 | fmt.Println(Include(strs, "grape"))
87 |
88 | fmt.Println(Any(strs, func(v string) bool {
89 | return strings.HasPrefix(v, "p")
90 | }))
91 |
92 | fmt.Println(All(strs, func(v string) bool {
93 | return strings.HasPrefix(v, "p")
94 | }))
95 |
96 | fmt.Println(Filter(strs, func(v string) bool {
97 | return strings.Contains(v, "e")
98 | }))
99 |
100 | // 上面的例子都使用匿名函数,你也可以使用命名函数
101 | fmt.Println(Map(strs, strings.ToUpper))
102 | }
103 |
104 | 运行结果
105 |2
106 | false
107 | true
108 | false
109 | [peach apple pear]
110 | [PEACH APPLE PEAR PLUM]
111 |
112 |
113 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_constant.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Go支持定义字符常量,字符串常量,布尔型常量和数值常量。
13 |使用const
关键字来定义常量。
package main
15 |
16 | import "fmt"
17 | import "math"
18 |
19 | // "const" 关键字用来定义常量
20 | const s string = "constant"
21 |
22 | func main() {
23 | fmt.Println(s)
24 |
25 | // "const"关键字可以出现在任何"var"关键字出现的地方
26 | // 区别是常量必须有初始值
27 | const n = 500000000
28 |
29 | // 常量表达式可以执行任意精度数学计算
30 | const d = 3e20 / n
31 | fmt.Println(d)
32 |
33 | // 数值型常量没有具体类型,除非指定一个类型
34 | // 比如显式类型转换
35 | fmt.Println(int64(d))
36 |
37 | // 数值型常量可以在程序的逻辑上下文中获取类型
38 | // 比如变量赋值或者函数调用。
39 | // 例如,对于math包中的Sin函数,它需要一个float64类型的变量
40 | fmt.Println(math.Sin(n))
41 | }
42 |
43 | 输出结果为
44 |constant
45 | 6e+11
46 | 600000000000
47 | -0.28470407323754404
48 |
49 |
50 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_customized_sort.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 有的时候我们希望排序不是仅仅按照自然顺序排序。例如,我们希望按照字符串的长度来对一个字符串数组排序而不是按照字母顺序来排序。这里我们介绍一下Go的自定义排序。
13 |package main
14 |
15 | import "sort"
16 | import "fmt"
17 |
18 | // 为了能够使用自定义函数来排序,我们需要一个
19 | // 对应的排序类型,比如这里我们为内置的字符串
20 | // 数组定义了一个别名ByLength
21 | type ByLength []string
22 |
23 | // 我们实现了sort接口的Len,Less和Swap方法
24 | // 这样我们就可以使用sort包的通用方法Sort
25 | // Len和Swap方法的实现在不同的类型之间大致
26 | // 都是相同的,只有Less方法包含了自定义的排序
27 | // 逻辑,这里我们希望以字符串长度升序排序
28 | func (s ByLength) Len() int {
29 | return len(s)
30 | }
31 | func (s ByLength) Swap(i, j int) {
32 | s[i], s[j] = s[j], s[i]
33 | }
34 | func (s ByLength) Less(i, j int) bool {
35 | return len(s[i]) < len(s[j])
36 | }
37 |
38 | // 一切就绪之后,我们就可以把需要进行自定义排序
39 | // 的字符串类型fruits转换为ByLength类型,然后使用
40 | // sort包的Sort方法来排序
41 | func main() {
42 | fruits := []string{"peach", "banana", "kiwi"}
43 | sort.Sort(ByLength(fruits))
44 | fmt.Println(fruits)
45 | }
46 |
47 | 输出结果
48 |[kiwi peach banana]
49 |
50 | 同样的,对于其他的类型,使用这种方法,我们可以为Go的切片提供任意的排序方法。归纳一下就是:
51 |Defer 用来保证一个函数调用会在程序执行的最后被调用。通常用于资源清理工作。
13 |package main
14 |
15 | import "fmt"
16 | import "os"
17 |
18 | // 假设我们想创建一个文件,然后写入数据,最后关闭文件
19 | func main() {
20 | // 在使用createFile得到一个文件对象之后,我们使用defer
21 | // 来调用关闭文件的方法closeFile,这个方法将在main函数
22 | // 最后被执行,也就是writeFile完成之后
23 | f := createFile("/tmp/defer.txt")
24 | // Windows下面使用这个语句
25 | // f := createFile("D:\\Temp\\defer.txt")
26 | defer closeFile(f)
27 | writeFile(f)
28 | }
29 |
30 | func createFile(p string) *os.File {
31 | fmt.Println("creating")
32 | f, err := os.Create(p)
33 | if err != nil {
34 | panic(err)
35 | }
36 | return f
37 | }
38 |
39 | func writeFile(f *os.File) {
40 | fmt.Println("writing")
41 | fmt.Fprintln(f, "data")
42 |
43 | }
44 |
45 | func closeFile(f *os.File) {
46 | fmt.Println("closing")
47 | f.Close()
48 | }
49 |
50 | 运行结果
51 |creating
52 | writing
53 | closing
54 |
55 | 使用defer来调用closeFile函数可以保证在main函数结束之前,关闭文件的操作一定会被执行。
56 | 57 | -------------------------------------------------------------------------------- /GolangStudy/tutorial/go_by_example/go_environment_variable.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |环境变量是一种很普遍的将配置信息传递给Unix程序的机制。
13 |package main
14 |
15 | import "os"
16 | import "strings"
17 | import "fmt"
18 | func main() {
19 | // 为了设置一个key/value对,使用`os.Setenv`
20 | // 为了获取一个key的value,使用`os.Getenv`
21 | // 如果所提供的key在环境变量中没有对应的value,
22 | // 那么返回空字符串
23 | os.Setenv("FOO", "1")
24 | fmt.Println("FOO:", os.Getenv("FOO"))
25 | fmt.Println("BAR:", os.Getenv("BAR"))
26 |
27 | // 使用`os.Environ`来列出环境变量中所有的key/value对
28 | // 你可以使用`strings.Split`方法来将key和value分开
29 | // 这里我们打印所有的key
30 | fmt.Println()
31 | for _, e := range os.Environ() {
32 | pair := strings.Split(e, "=")
33 | fmt.Println(pair[0])
34 | }
35 | }
36 |
37 | 这里我们设置了FOO环境变量,所以我们取到了它的值,但是没有设置BAR环境变量,所以值为空。另外我们列出了系统的所有环境变量,当然这个输出根据不同的系统设置可能并不相同。
38 |输出结果
39 |FOO: 1
40 | BAR:
41 |
42 | TERM_PROGRAM
43 | TERM
44 | SHELL
45 | TMPDIR
46 | Apple_PubSub_Socket_Render
47 | OLDPWD
48 | USER
49 | SSH_AUTH_SOCK
50 | __CF_USER_TEXT_ENCODING
51 | __CHECKFIX1436934
52 | PATH
53 | PWD
54 | ITERM_PROFILE
55 | SHLVL
56 | COLORFGBG
57 | HOME
58 | ITERM_SESSION_ID
59 | LOGNAME
60 | LC_CTYPE
61 | GOPATH
62 | _
63 | FOO
64 |
65 |
66 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_error_handle.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 在Go里面通常采用显式返回错误代码的方式来进行错误处理。这个和Java或者Ruby里面使用异常或者是C里面运行正常返回结果,发生错误返回错误代码的方式不同。Go的这种错误处理的方式使得我们能够很容易看出哪些函数可能返回错误,并且能够像调用那些没有错误返回的函数一样调用。
13 |package main
14 |
15 | import "errors"
16 | import "fmt"
17 |
18 | // Go语言里面约定错误代码是函数的最后一个返回值,
19 | // 并且类型是error,这是一个内置的接口
20 |
21 | func f1(arg int) (int, error) {
22 | if arg == 42 {
23 |
24 | // errors.New使用错误信息作为参数,构建一个基本的错误
25 | return -1, errors.New("can't work with 42")
26 |
27 | }
28 |
29 | // 返回错误为nil表示没有错误
30 | return arg + 3, nil
31 | }
32 |
33 | // 你可以通过实现error接口的方法Error()来自定义错误
34 | // 下面我们自定义一个错误类型来表示上面例子中的参数错误
35 | type argError struct {
36 | arg int
37 | prob string
38 | }
39 |
40 | func (e *argError) Error() string {
41 | return fmt.Sprintf("%d - %s", e.arg, e.prob)
42 | }
43 |
44 | func f2(arg int) (int, error) {
45 | if arg == 42 {
46 |
47 | // 这里我们使用&argError语法来创建一个新的结构体对象,
48 | // 并且给它的成员赋值
49 | return -1, &argError{arg, "can't work with it"}
50 | }
51 | return arg + 3, nil
52 | }
53 |
54 | func main() {
55 |
56 | // 下面的两个循环例子用来测试我们的带有错误返回值的函数
57 | // 在for循环语句里面,使用了if来判断函数返回值是否为nil是
58 | // Go语言里面的一种约定做法。
59 | for _, i := range []int{7, 42} {
60 | if r, e := f1(i); e != nil {
61 | fmt.Println("f1 failed:", e)
62 | } else {
63 | fmt.Println("f1 worked:", r)
64 | }
65 | }
66 | for _, i := range []int{7, 42} {
67 | if r, e := f2(i); e != nil {
68 | fmt.Println("f2 failed:", e)
69 | } else {
70 | fmt.Println("f2 worked:", r)
71 | }
72 | }
73 |
74 | // 如果你需要使用自定义错误类型返回的错误数据,你需要使用类型断言
75 | // 来获得一个自定义错误类型的实例才行。
76 | _, e := f2(42)
77 | if ae, ok := e.(*argError); ok {
78 | fmt.Println(ae.arg)
79 | fmt.Println(ae.prob)
80 | }
81 | }
82 |
83 | 运行结果为
84 |f1 worked: 10
85 | f1 failed: can't work with 42
86 | f2 worked: 10
87 | f2 failed: 42 - can't work with it
88 | 42
89 | can't work with it
90 |
91 |
92 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_exit.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 使用os.Exit
可以给定一个状态,然后立刻退出程序运行。
package main
14 |
15 | import "fmt"
16 | import "os"
17 |
18 | func main() {
19 | // 当使用`os.Exit`的时候defer操作不会被运行,
20 | // 所以这里的``fmt.Println`将不会被调用
21 | defer fmt.Println("!")
22 |
23 | // 退出程序并设置退出状态值
24 | os.Exit(3)
25 | }
26 |
27 | 注意,Go和C语言不同,main函数并不返回一个整数来表示程序的退出状态,而是将退出状态作为os.Exit
函数的参数。
如果你使用go run
来运行程序,将会有如下输出
exit status 3
30 |
31 | 如果你使用go build
先编译程序,然后再运行可执行文件,程序将不会有输出。
32 | 如果你想查看程序的返回值,*nix系列系统下面使用如下方法:
$ ./go_exit
34 | $ echo $?
35 | 3
36 |
37 |
38 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_for_loop.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | for循环是Go语言唯一的循环结构。这里有三个基本的for循环类型。
13 |package main
14 |
15 | import "fmt"
16 |
17 | func main() {
18 |
19 | // 最基本的一种,单一条件循环
20 | // 这个可以代替其他语言的while循环
21 | i := 1
22 | for i <= 3 {
23 | fmt.Println(i)
24 | i = i + 1
25 | }
26 |
27 | // 经典的循环条件初始化/条件判断/循环后条件变化
28 | for j := 7; j <= 9; j++ {
29 | fmt.Println(j)
30 | }
31 |
32 | // 无条件的for循环是死循环,除非你使用break跳出循环或者
33 | // 使用return从函数返回
34 | for {
35 | fmt.Println("loop")
36 | break
37 | }
38 | }
39 |
40 | 输出结果
41 |1
42 | 2
43 | 3
44 | 7
45 | 8
46 | 9
47 | loop
48 |
49 | 在后面的例子中,你将会看到其他的循环方式,比如使用range函数循环数组,切片和字典,或者用select函数循环channel通道。
50 | 51 | -------------------------------------------------------------------------------- /GolangStudy/tutorial/go_by_example/go_func_callback.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |Go支持函数回调,你可以把函数名称作为参数传递给另外一个函数,然后在别的地方实现这个函数。
13 |package main
14 |
15 | import "fmt"
16 |
17 | type Callback func(x, y int) int
18 |
19 | func main() {
20 | x, y := 1, 2
21 | fmt.Println(test(x, y, add))
22 | }
23 |
24 | //提供一个接口,让外部去实现
25 | func test(x, y int, callback Callback) int {
26 | return callback(x, y)
27 | }
28 |
29 | func add(x, y int) int {
30 | return x + y
31 | }
32 |
33 | 运行结果
34 |3
35 |
36 |
37 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_func_closure.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Go支持匿名函数,匿名函数可以形成闭包。闭包函数可以访问定义闭包的函数定义的内部变量。
13 |示例1:
14 |package main
15 |
16 | import "fmt"
17 |
18 | // 这个"intSeq"函数返回另外一个在intSeq内部定义的匿名函数,
19 | // 这个返回的匿名函数包住了变量i,从而形成了一个闭包
20 | func intSeq() func() int {
21 | i := 0
22 | return func() int {
23 | i += 1
24 | return i
25 | }
26 | }
27 |
28 | func main() {
29 | // 我们调用intSeq函数,并且把结果赋值给一个函数nextInt,
30 | // 这个nextInt函数拥有自己的i变量,这个变量每次调用都被更新。
31 | // 这里i的初始值是由intSeq调用的时候决定的。
32 | nextInt := intSeq()
33 |
34 | // 调用几次nextInt,看看闭包的效果
35 | fmt.Println(nextInt())
36 | fmt.Println(nextInt())
37 | fmt.Println(nextInt())
38 |
39 | // 为了确认闭包的状态是独立于intSeq函数的,再创建一个。
40 | newInts := intSeq()
41 | fmt.Println(newInts())
42 | }
43 |
44 | 输出结果为
45 |1
46 | 2
47 | 3
48 | 1
49 |
50 | 示例2:
51 |package main
52 |
53 | import "fmt"
54 |
55 | func main() {
56 | add10 := closure(10)//其实是构造了一个加10函数
57 | fmt.Println(add10(5))
58 | fmt.Println(add10(6))
59 | add20 := closure(20)
60 | fmt.Println(add20(5))
61 | }
62 |
63 | func closure(x int) func(y int) int {
64 | return func(y int) int {
65 | return x + y
66 | }
67 |
68 | }
69 |
70 | 输出结果为:
71 |15
72 | 16
73 | 25
74 |
75 | 示例3:
76 |package main
77 |
78 | import "fmt"
79 |
80 | func main() {
81 |
82 | var fs []func() int
83 |
84 | for i := 0; i < 3; i++ {
85 |
86 | fs = append(fs, func() int {
87 |
88 | return i
89 | })
90 | }
91 | for _, f := range fs {
92 | fmt.Printf("%p = %v\n", f, f())
93 | }
94 | }
95 |
96 | 输出结果:
97 |0x401200 = 3
98 | 0x401200 = 3
99 | 0x401200 = 3
100 |
101 | 示例4:
102 |package main
103 |
104 | import "fmt"
105 |
106 | func adder() func(int) int {
107 | sum := 0
108 | return func(x int) int {
109 | sum += x
110 | return sum
111 | }
112 | }
113 |
114 | func main() {
115 | result := adder()
116 | for i := 0; i < 10; i++ {
117 | fmt.Println(result(i))
118 | }
119 | }
120 |
121 | 输出结果为:
122 |0
123 | 1
124 | 3
125 | 6
126 | 10
127 | 15
128 | 21
129 | 28
130 | 36
131 | 45
132 |
133 |
134 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_func_define.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 函数是Go语言的重要内容。
13 |package main
14 |
15 | import "fmt"
16 |
17 | // 这个函数计算两个int型输入数据的和,并返回int型的和
18 | func plus(a int, b int) int {
19 | // Go需要使用return语句显式地返回值
20 | return a + b
21 | }
22 |
23 | func main() {
24 | // 函数的调用方式很简单
25 | // "名称(参数列表)"
26 | res := plus(1, 2)
27 | fmt.Println("1+2 =", res)
28 | }
29 |
30 | 输出结果为
31 |1+2 = 3
32 |
33 | Go的函数还有很多其他的特性,其中一个就是多返回值,我们下面会看到。
34 | 35 | -------------------------------------------------------------------------------- /GolangStudy/tutorial/go_by_example/go_func_multiple_ret_value.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |Go语言内置支持多返回值,这个在Go语言中用的很多,比如一个函数同时返回结果和错误信息。
13 |package main
14 |
15 | import "fmt"
16 |
17 | // 这个函数的返回值为两个int
18 | func vals() (int, int) {
19 | return 3, 7
20 | }
21 |
22 | func main() {
23 |
24 | // 获取函数的两个返回值
25 | a, b := vals()
26 | fmt.Println(a)
27 | fmt.Println(b)
28 |
29 | // 如果你只对多个返回值里面的几个感兴趣
30 | // 可以使用下划线(_)来忽略其他的返回值
31 | _, c := vals()
32 | fmt.Println(c)
33 | }
34 |
35 | 输出结果为
36 |3
37 | 7
38 | 7
39 |
40 |
41 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_func_named_ret_value.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 函数接受参数。在 Go 中,函数可以返回多个“结果参数”,而不仅仅是一个值。它们可以像变量那样命名和使用。
13 |如果命名了返回值参数,一个没有参数的return
语句,会将当前的值作为返回值返回。注意,如果遇到if等代码块和返回值同名,还需要显示写出返回值。
package main
15 |
16 | import "fmt"
17 |
18 | func split(sum int) (x, y int) {
19 | x = sum * 4 / 9
20 | y = sum - x
21 | return
22 | }
23 |
24 | func main() {
25 | fmt.Println(split(17))
26 | }
27 |
28 | 运行结果
29 |7 10
30 |
31 |
32 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_hello_world.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 我们的第一个例子是打印经典的“hello world”信息,我们先看下代码。
14 | 15 |package main
16 |
17 | import "fmt"
18 |
19 | func main() {
20 | fmt.Println("hello world")
21 | }
22 |
23 |
24 | 输出结果为:
25 | 26 |hello world
27 |
28 |
29 | 为了使一个go文件
能够编译
为可执行文件
,包名必须是main
,然后我们导入提供格式化输出的fmt
包,该程序的执行入口是func main()
函数,在函数里面,我们使用fmt
包提供的Println
函数来输出"hello world"字符串。
为了运行这个程序,我们可以使用go run hello_world.go
来运行这个例子,这样是直接输出运行结果而不会产生任何中间文件。但是有的时候我们希望能够将程序编译为二进制文件保存起来,我们可以像上面一样使用go build hello_world.go
来将源代码编译为二进制可执行文件。然后我们可以直接运行这个二进制可执行文件。
好了,第一个例子就这样结束了。很简单。
34 | 35 | -------------------------------------------------------------------------------- /GolangStudy/tutorial/go_by_example/go_if_elseif.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |Go语言的条件判断结构也很简单。
13 |package main
14 |
15 | import "fmt"
16 |
17 | func main() {
18 |
19 | // 基本的例子
20 | if 7%2 == 0 {
21 | fmt.Println("7 is even")
22 | } else {
23 | fmt.Println("7 is odd")
24 | }
25 |
26 | // 只有if条件的情况
27 | if 8%4 == 0 {
28 | fmt.Println("8 is divisible by 4")
29 | }
30 |
31 | // if条件可以包含一个初始化表达式,这个表达式中的变量
32 | // 是这个条件判断结构的局部变量
33 | if num := 9; num < 0 {
34 | fmt.Println(num, "is negative")
35 | } else if num < 10 {
36 | fmt.Println(num, "has 1 digit")
37 | } else {
38 | fmt.Println(num, "has multiple digits")
39 | }
40 | }
41 |
42 | 条件判断结构中,条件两边的小括号()是可以省略的,但是条件执行语句块两边的大括号{}不可以。
43 |输出结果为
44 |7 is odd
45 | 8 is divisible by 4
46 | 9 has 1 digit
47 |
48 | 在Go里面没有三元表达式"?:",所以你只能使用条件判断语句。
49 | 50 | -------------------------------------------------------------------------------- /GolangStudy/tutorial/go_by_example/go_interface.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |接口是一个方法签名的集合。 13 | 所谓方法签名,就是指方法的声明,而不包括实现。
14 |package main
15 |
16 | import "fmt"
17 | import "math"
18 |
19 | // 这里定义了一个最基本的表示几何形状的方法的接口
20 | type geometry interface {
21 | area() float64
22 | perim() float64
23 | }
24 |
25 | // 这里我们要让正方形square和圆形circle实现这个接口
26 | type square struct {
27 | width, height float64
28 | }
29 | type circle struct {
30 | radius float64
31 | }
32 |
33 | // 在Go中实现一个接口,只要实现该接口定义的所有方法即可
34 | // 下面是正方形实现的接口
35 | func (s square) area() float64 {
36 | return s.width * s.height
37 | }
38 | func (s square) perim() float64 {
39 | return 2*s.width + 2*s.height
40 | }
41 |
42 | // 圆形实现的接口
43 | func (c circle) area() float64 {
44 | return math.Pi * c.radius * c.radius
45 | }
46 | func (c circle) perim() float64 {
47 | return 2 * math.Pi * c.radius
48 | }
49 |
50 | // 如果一个函数的参数是接口类型,那么我们可以使用命名接口
51 | // 来调用这个函数
52 | // 比如这里的正方形square和圆形circle都实现了接口geometry,
53 | // 那么它们都可以作为这个参数为geometry类型的函数的参数。
54 | // 在measure函数内部,Go知道调用哪个结构体实现的接口方法。
55 | func measure(g geometry) {
56 | fmt.Println(g)
57 | fmt.Println(g.area())
58 | fmt.Println(g.perim())
59 | }
60 |
61 | func main() {
62 | s := square{width: 3, height: 4}
63 | c := circle{radius: 5}
64 |
65 | // 这里circle和square都实现了geometry接口,所以
66 | // circle类型变量和square类型变量都可以作为measure
67 | // 函数的参数
68 | measure(s)
69 | measure(c)
70 | }
71 |
72 | 输出结果为
73 |{3 4}
74 | 12
75 | 14
76 | {5}
77 | 78.53981633974483
78 | 31.41592653589793
79 |
80 | 也就是说如果结构体A实现了接口B定义的所有方法,那么A也是B类型。
81 | 82 | -------------------------------------------------------------------------------- /GolangStudy/tutorial/go_by_example/go_json.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |Go内置了对JSON数据的编码和解码,这些数据的类型包括内置数据类型和自定义数据类型。
13 |package main
14 |
15 | import "encoding/json"
16 | import "fmt"
17 | import "os"
18 |
19 | // 我们使用两个结构体来演示自定义数据类型的JSON数据编码和解码。
20 | type Response1 struct {
21 | Page int
22 | Fruits []string
23 | }
24 | type Response2 struct {
25 | Page int `json:"page"`
26 | Fruits []string `json:"fruits"`
27 | }
28 |
29 | func main() {
30 |
31 | // 首先我们看一下将基础数据类型编码为JSON数据
32 | bolB, _ := json.Marshal(true)
33 | fmt.Println(string(bolB))
34 |
35 | intB, _ := json.Marshal(1)
36 | fmt.Println(string(intB))
37 |
38 | fltB, _ := json.Marshal(2.34)
39 | fmt.Println(string(fltB))
40 |
41 | strB, _ := json.Marshal("gopher")
42 | fmt.Println(string(strB))
43 |
44 | // 这里是将切片和字典编码为JSON数组或对象
45 | slcD := []string{"apple", "peach", "pear"}
46 | slcB, _ := json.Marshal(slcD)
47 | fmt.Println(string(slcB))
48 |
49 | mapD := map[string]int{"apple": 5, "lettuce": 7}
50 | mapB, _ := json.Marshal(mapD)
51 | fmt.Println(string(mapB))
52 |
53 | // JSON包可以自动地编码自定义数据类型。结果将只包括自定义
54 | // 类型中的可导出成员的值并且默认情况下,这些成员名称都作
55 | // 为JSON数据的键
56 | res1D := &Response1{
57 | Page: 1,
58 | Fruits: []string{"apple", "peach", "pear"}}
59 | res1B, _ := json.Marshal(res1D)
60 | fmt.Println(string(res1B))
61 |
62 | // 你可以使用tag来自定义编码后JSON键的名称
63 | res2D := &Response2{
64 | Page: 1,
65 | Fruits: []string{"apple", "peach", "pear"}}
66 | res2B, _ := json.Marshal(res2D)
67 | fmt.Println(string(res2B))
68 |
69 | // 现在我们看看解码JSON数据为Go数值
70 | byt := []byte(`{"num":6.13,"strs":["a","b"]}`)
71 |
72 | // 我们需要提供一个变量来存储解码后的JSON数据,这里
73 | // 的`map[string]interface{}`将以Key-Value的方式
74 | // 保存解码后的数据,Value可以为任意数据类型
75 | var dat map[string]interface{}
76 |
77 | // 解码过程,并检测相关可能存在的错误
78 | if err := json.Unmarshal(byt, &dat); err != nil {
79 | panic(err)
80 | }
81 | fmt.Println(dat)
82 |
83 | // 为了使用解码后map里面的数据,我们需要将Value转换为
84 | // 它们合适的类型,例如我们将这里的num转换为期望的float64
85 | num := dat["num"].(float64)
86 | fmt.Println(num)
87 |
88 | // 访问嵌套的数据需要一些类型转换
89 | strs := dat["strs"].([]interface{})
90 | str1 := strs[0].(string)
91 | fmt.Println(str1)
92 |
93 | // 我们还可以将JSON解码为自定义数据类型,这有个好处是可以
94 | // 为我们的程序增加额外的类型安全并且不用再在访问数据的时候
95 | // 进行类型断言
96 | str := `{"page": 1, "fruits": ["apple", "peach"]}`
97 | res := &Response2{}
98 | json.Unmarshal([]byte(str), &res)
99 | fmt.Println(res)
100 | fmt.Println(res.Fruits[0])
101 |
102 | // 上面的例子中,我们使用bytes和strings来进行原始数据和JSON数据
103 | // 之间的转换,我们也可以直接将JSON编码的数据流写入`os.Writer`
104 | // 或者是HTTP请求回复数据。
105 | enc := json.NewEncoder(os.Stdout)
106 | d := map[string]int{"apple": 5, "lettuce": 7}
107 | enc.Encode(d)
108 | }
109 |
110 | 运行结果
111 |true
112 | 1
113 | 2.34
114 | "gopher"
115 | ["apple","peach","pear"]
116 | {"apple":5,"lettuce":7}
117 | {"Page":1,"Fruits":["apple","peach","pear"]}
118 | {"page":1,"fruits":["apple","peach","pear"]}
119 | map[num:6.13 strs:[a b]]
120 | 6.13
121 | a
122 | &{1 [apple peach]}
123 | apple
124 | {"apple":5,"lettuce":7}
125 |
126 |
127 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_line_filters.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Line Filters翻译一下大概是行数据过滤器。简单一点就是一个程序从标准输入stdin读取数据,然后处理一下,将处理的结果输出到标准输出stdout。grep和sed就是常见的行数据过滤器。 13 | 这里有一个行数据过滤器的例子,是把一个输入文本转换为大写的文本。你可以使用这种方式来实现你自己的Go Line Filters。
14 |package main
15 |
16 | import (
17 | "bufio"
18 | "fmt"
19 | "os"
20 | "strings"
21 | )
22 |
23 | func main() {
24 |
25 | // 使用缓冲scanner来包裹无缓冲的`os.Stdin`可以让我们
26 | // 方便地使用`Scan`方法,这个方法会将scanner定位到下
27 | // 一行的位置
28 | scanner := bufio.NewScanner(os.Stdin)
29 |
30 | for scanner.Scan() {
31 | // `Text`方法从输入中返回当前行
32 | ucl := strings.ToUpper(scanner.Text())
33 |
34 | // 输出转换为大写的行
35 | fmt.Println(ucl)
36 | }
37 |
38 | // 在`Scan`过程中,检查错误。文件结束不会被当作一个错误
39 | if err := scanner.Err(); err != nil {
40 | fmt.Fprintln(os.Stderr, "error:", err)
41 | os.Exit(1)
42 | }
43 | }
44 |
45 | 运行结果
46 |hello world
47 | HELLO WORLD
48 | how are you
49 | HOW ARE YOU
50 |
51 |
52 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_map.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 字典是Go语言内置的关联数据类型。因为数组是索引对应数组元素,而字典是键对应值。
13 |package main
14 |
15 | import "fmt"
16 |
17 | func main() {
18 |
19 | // 创建一个字典可以使用内置函数make
20 | // "make(map[键类型]值类型)"
21 | m := make(map[string]int)
22 |
23 | // 使用经典的"name[key]=value"来为键设置值
24 | m["k1"] = 7
25 | m["k2"] = 13
26 |
27 | // 用Println输出字典,会输出所有的键值对
28 | fmt.Println("map:", m)
29 |
30 | // 获取一个键的值 "name[key]".
31 | v1 := m["k1"]
32 | fmt.Println("v1: ", v1)
33 |
34 | // 内置函数返回字典的元素个数
35 | fmt.Println("len:", len(m))
36 |
37 | // 内置函数delete从字典删除一个键对应的值
38 | delete(m, "k2")
39 | fmt.Println("map:", m)
40 |
41 | // 根据键来获取值有一个可选的返回值,这个返回值表示字典中是否
42 | // 存在该键,如果存在为true,返回对应值,否则为false,返回零值
43 | // 有的时候需要根据这个返回值来区分返回结果到底是存在的值还是零值
44 | // 比如字典不存在键x对应的整型值,返回零值就是0,但是恰好字典中有
45 | // 键y对应的值为0,这个时候需要那个可选返回值来判断是否零值。
46 | _, ok := m["k2"]
47 | fmt.Println("ok:", ok)
48 |
49 | // 你可以用 ":=" 同时定义和初始化一个字典
50 | n := map[string]int{"foo": 1, "bar": 2}
51 | fmt.Println("map:", n)
52 | }
53 |
54 | 输出结果为
55 |map: map[k1:7 k2:13]
56 | v1: 7
57 | len: 2
58 | map: map[k1:7]
59 | ok: false
60 | map: map[foo:1 bar:2]
61 |
62 |
63 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_method.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 一般的函数定义叫做函数,定义在结构体上面的函数叫做该结构体的方法。
13 |示例1:
14 |package main
15 |
16 | import "fmt"
17 |
18 | type rect struct {
19 | width, height int
20 | }
21 |
22 | // 这个area方法有一个限定类型*rect,
23 | // 表示这个函数是定义在rect结构体上的方法
24 | func (r *rect) area() int {
25 | return r.width * r.height
26 | }
27 |
28 | // 方法的定义限定类型可以为结构体类型
29 | // 也可以是结构体指针类型
30 | // 区别在于如果限定类型是结构体指针类型
31 | // 那么在该方法内部可以修改结构体成员信息
32 | func (r rect) perim() int {
33 | return 2*r.width + 2*r.height
34 | }
35 |
36 | func main() {
37 | r := rect{width: 10, height: 5}
38 |
39 | // 调用方法
40 | fmt.Println("area: ", r.area())
41 | fmt.Println("perim:", r.perim())
42 |
43 | // Go语言会自动识别方法调用的参数是结构体变量还是
44 | // 结构体指针,如果你要修改结构体内部成员值,那么使用
45 | // 结构体指针作为函数限定类型,也就是说参数若是结构体
46 | //变量,仅仅会发生值拷贝。
47 | rp := &r
48 | fmt.Println("area: ", rp.area())
49 | fmt.Println("perim:", rp.perim())
50 | }
51 |
52 | 输出结果为
53 |area: 50
54 | perim: 30
55 | area: 50
56 | perim: 30
57 |
58 | 示例2:
59 |从某种意义上说,方法是函数的“语法糖”。当函数与某个特定的类型绑定,那么它就是一个方法。也证因为如此,我们可以将方法“还原”成函数。
60 |instance.method(args)->(type).func(instance,args)
61 |为了区别这两种方式,官方文档中将左边的称为Method Value
,右边则是Method Expression
。Method Value是包装后的状态对象,总是与特定的对象实例关联在一起(类似闭包,拐带私奔),而Method Expression函数将Receiver作为第一个显式参数,调用时需额外传递。
注意:对于Method Expression,T仅拥有T Receiver方法,T拥有(T+T)所有方法。
63 |package main
64 |
65 | import (
66 | "fmt"
67 | )
68 |
69 | func main() {
70 | p := Person{2, "张三"}
71 |
72 | p.test(1)
73 | var f1 func(int) = p.test
74 | f1(2)
75 | Person.test(p, 3)
76 | var f2 func(Person, int) = Person.test
77 | f2(p, 4)
78 |
79 | }
80 |
81 | type Person struct {
82 | Id int
83 | Name string
84 | }
85 |
86 | func (this Person) test(x int) {
87 | fmt.Println("Id:", this.Id, "Name", this.Name)
88 | fmt.Println("x=", x)
89 | }
90 |
91 | 输出结果:
92 |Id: 2 Name 张三
93 | x= 1
94 | Id: 2 Name 张三
95 | x= 2
96 | Id: 2 Name 张三
97 | x= 3
98 | Id: 2 Name 张三
99 | x= 4
100 |
101 | 示例3:
102 |使用匿名字段,实现模拟继承。即可直接访问匿名字段(匿名类型或匿名指针类型)的方法这种行为类似“继承”。访问匿名字段方法时,有隐藏规则,这样我们可以实现override效果。
103 |package main
104 |
105 | import (
106 | "fmt"
107 | )
108 |
109 | func main() {
110 | p := Student{Person{2, "张三"}, 25}
111 | p.test()
112 |
113 | }
114 |
115 | type Person struct {
116 | Id int
117 | Name string
118 | }
119 |
120 | type Student struct {
121 | Person
122 | Score int
123 | }
124 |
125 | func (this Person) test() {
126 | fmt.Println("person test")
127 | }
128 |
129 | func (this Student) test() {
130 | fmt.Println("student test")
131 | }
132 |
133 | 输出结果为:
134 |student test
135 |
136 |
137 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_mutex.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 上面的例子中,我们看过了如何在多个协程之间原子地访问计数器,对于更复杂的例子,我们可以使用Mutex
来在多个协程之间安全地访问数据。
package main
14 |
15 | import (
16 | "fmt"
17 | "math/rand"
18 | "runtime"
19 | "sync"
20 | "sync/atomic"
21 | "time"
22 | )
23 |
24 | func main() {
25 |
26 | // 这个例子的状态就是一个map
27 | var state = make(map[int]int)
28 |
29 | // 这个`mutex`将同步对状态的访问
30 | var mutex = &sync.Mutex{}
31 |
32 | // ops将对状态的操作进行计数
33 | var ops int64 = 0
34 | // 这里我们启动100个协程来不断地读取这个状态
35 | for r := 0; r < 100; r++ {
36 | go func() {
37 | total := 0
38 | for {
39 |
40 | // 对于每次读取,我们选取一个key来访问,
41 | // mutex的`Lock`函数用来保证对状态的
42 | // 唯一性访问,访问结束后,使用`Unlock`
43 | // 来解锁,然后增加ops计数器
44 | key := rand.Intn(5)
45 | mutex.Lock()
46 | total += state[key]
47 | mutex.Unlock()
48 | atomic.AddInt64(&ops, 1)
49 |
50 | // 为了保证这个协程不会让调度器出于饥饿状态,
51 | // 我们显式地使用`runtime.Gosched`释放了资源
52 | // 控制权,这种控制权会在通道操作结束或者
53 | // time.Sleep结束后自动释放。但是这里我们需要
54 | // 手动地释放资源控制权
55 | runtime.Gosched()
56 | }
57 | }()
58 | }
59 | // 同样我们使用10个协程来模拟写状态
60 | for w := 0; w < 10; w++ {
61 | go func() {
62 | for {
63 | key := rand.Intn(5)
64 | val := rand.Intn(100)
65 | mutex.Lock()
66 | state[key] = val
67 | mutex.Unlock()
68 | atomic.AddInt64(&ops, 1)
69 | runtime.Gosched()
70 | }
71 | }()
72 | }
73 |
74 | // 主协程Sleep,让那10个协程能够运行一段时间
75 | time.Sleep(time.Second)
76 |
77 | // 输出总操作次数
78 | opsFinal := atomic.LoadInt64(&ops)
79 | fmt.Println("ops:", opsFinal)
80 |
81 | // 最后锁定并输出状态
82 | mutex.Lock()
83 | fmt.Println("state:", state)
84 | mutex.Unlock()
85 | }
86 |
87 | 运行结果
88 |ops: 3931611
89 | state: map[0:84 2:20 3:18 1:65 4:31]
90 |
91 |
92 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_non_blocking_channel.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 默认情况下,通道发送和接收数据是阻塞的。然而我们可以使用select的一个default的选项来实现无阻塞发送或接收数据,甚至可以将多个select的case选项和default选项结合起来使用。
13 |package main
14 |
15 | import "fmt"
16 |
17 | func main() {
18 | messages := make(chan string)
19 | signals := make(chan bool)
20 |
21 | // 这里是一个非阻塞的从通道接收数据,如果messages通道有数据
22 | // 可以接收,那么select将运行`<-messages`这个case,否则的话
23 | // 程序立刻执行default选项后面的语句
24 | select {
25 | case msg := <-messages:
26 | fmt.Println("received message", msg)
27 | default:
28 | fmt.Println("no message received")
29 | }
30 |
31 | // 非阻塞通道发送数据也是一样的
32 | msg := "hi"
33 | select {
34 | case messages <- msg:
35 | fmt.Println("sent message", msg)
36 | default:
37 | fmt.Println("no message sent")
38 | }
39 |
40 | // 在default前面,我们可以有多个case选项,从而实现多通道
41 | // 非阻塞的选择,这里我们尝试从messages和signals接收数据
42 | // 如果有数据可以接收,那么执行对应case后面的逻辑,否则立刻
43 | // 执行default选项后面的逻辑
44 | select {
45 | case msg := <-messages:
46 | fmt.Println("received message", msg)
47 | case sig := <-signals:
48 | fmt.Println("received signal", sig)
49 | default:
50 | fmt.Println("no activity")
51 | }
52 | }
53 |
54 | 运行结果
55 |no message received
56 | no message sent
57 | no activity
58 |
59 | 这个例子中,由于我们使用了default来实现非阻塞的通道,所以开始的时候messages里面没有数据可以接收,直接输出no message received
,而第二次由于messages通道没有相应的数据接收方,所以也不会写入数据,直接转到default,输出no message sent
,至于第三个就很好理解了,什么也没有,输出no activity
。
60 | 其实,我们可以把这个例子修改一下,让messages通道带缓冲区,这样例子或许更好理解一点。定义messages的时候使用messages := make(chan string, 1)
。
package main
62 |
63 | import "fmt"
64 |
65 | func main() {
66 | messages := make(chan string, 1)
67 | signals := make(chan bool)
68 |
69 | // 这里是一个非阻塞的从通道接收数据,如果messages通道有数据
70 | // 可以接收,那么select将运行`<-messages`这个case,否则的话
71 | // 程序立刻执行default选项后面的语句
72 | select {
73 | case msg := <-messages:
74 | fmt.Println("received message", msg)
75 | default:
76 | fmt.Println("no message received")
77 | }
78 |
79 | // 非阻塞通道发送数据也是一样的,但是由于messages带了缓冲区,
80 | // 即使没有数据接受端也可以发送数据,所以这里的`messages<-msg`
81 | // 会被执行,从而不再跳到default去了。
82 | msg := "hi"
83 | select {
84 | case messages <- msg:
85 | fmt.Println("sent message", msg)
86 | default:
87 | fmt.Println("no message sent")
88 | }
89 |
90 | // 在default前面,我们可以有多个case选项,从而实现多通道
91 | // 非阻塞的选择,这里我们尝试从messages和signals接收数据
92 | // 如果有数据可以接收,那么执行对应case后面的逻辑,否则立刻
93 | // 执行default选项后面的逻辑
94 | select {
95 | case msg := <-messages:
96 | fmt.Println("received message", msg)
97 | case sig := <-signals:
98 | fmt.Println("received signal", sig)
99 | default:
100 | fmt.Println("no activity")
101 | }
102 | }
103 |
104 | 运行结果
105 |no message received
106 | sent message hi
107 | received message hi
108 |
109 |
110 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_number.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Go有很多种数据类型,包括字符串类型,整型,浮点型,布尔型等等,这里有几个基础的例子。
13 |package main
14 |
15 | import "fmt"
16 |
17 | func main() {
18 |
19 | // 字符串可以使用"+"连接
20 | fmt.Println("go" + "lang")
21 |
22 | //整型和浮点型
23 | fmt.Println("1+1 =", 1+1)
24 | fmt.Println("7.0/3.0 =", 7.0/3.0)
25 |
26 | // 布尔型的几种操作符
27 | fmt.Println(true && false)
28 | fmt.Println(true || false)
29 | fmt.Println(!true)
30 | }
31 |
32 | 输出结果为
33 |golang
34 | 1+1 = 2
35 | 7.0/3.0 = 2.3333333333333335
36 | false
37 | true
38 | false
39 |
40 |
41 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_number_parse.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 从字符串解析出数字是一个基本的而且很常见的任务。
13 | Go内置的strconv
提供了数字解析功能。
package main
15 |
16 | import "strconv"
17 | import "fmt"
18 |
19 | func main() {
20 | // 使用ParseFloat解析浮点数,64是说明使用多少位
21 | // 精度来解析
22 | f, _ := strconv.ParseFloat("1.234", 64)
23 | fmt.Println(f)
24 |
25 | // 对于ParseInt函数,0 表示从字符串推断整型进制,
26 | // 则表示返回结果的位数
27 | i, _ := strconv.ParseInt("123", 0, 64)
28 | fmt.Println(i)
29 |
30 | // ParseInt能够解析出16进制的数字
31 | d, _ := strconv.ParseInt("0x1c8", 0, 64)
32 | fmt.Println(d)
33 |
34 | // 还可以使用ParseUint函数
35 | u, _ := strconv.ParseUint("789", 0, 64)
36 | fmt.Println(u)
37 |
38 | // Atoi是解析10进制整型的快捷方法
39 | k, _ := strconv.Atoi("135")
40 | fmt.Println(k)
41 |
42 | // 解析函数在遇到无法解析的输入时,会返回错误
43 | _, e := strconv.Atoi("wat")
44 | fmt.Println(e)
45 | }
46 |
47 | 运行结果
48 |1.234
49 | 123
50 | 456
51 | 789
52 | 135
53 | strconv.ParseInt: parsing "wat": invalid syntax
54 |
55 |
56 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_panic.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Panic表示的意思就是有些意想不到的错误发生了。通常我们用来表示程序正常运行过程中不应该出现的,或者我们没有处理好的错误。
13 |package main
14 |
15 | import "os"
16 |
17 | func main() {
18 |
19 | // 我们使用panic来检查预期不到的错误
20 | panic("a problem")
21 |
22 | // Panic的通常使用方法就是如果一个函数
23 | // 返回一个我们不知道怎么处理的错误的
24 | // 时候,直接终止执行。
25 | _, err := os.Create("/tmp/file")
26 | if err != nil {
27 | panic(err)
28 | }
29 | }
30 |
31 | 运行结果
32 |panic: a problem
33 |
34 | goroutine 1 [running]:
35 | runtime.panic(0x44e060, 0xc0840031b0)
36 | C:/Users/ADMINI~1/AppData/Local/Temp/2/bindist667667715/go/src/pkg/runtime/panic.c:266 +0xc8
37 | main.main()
38 | D:/GoDoc/go_panic.go:8 +0x58
39 | exit status 2
40 |
41 | 和其他的编程语言不同的是,Go并不使用exception来处理错误,而是通过函数返回值返回错误代码。
42 | 43 | -------------------------------------------------------------------------------- /GolangStudy/tutorial/go_by_example/go_parallel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |goroutine是一个轻量级的线程。
13 |package main
14 |
15 | import "fmt"
16 |
17 | func f(from string) {
18 | for i := 0; i < 3; i++ {
19 | fmt.Println(from, ":", i)
20 | }
21 | }
22 |
23 | func main() {
24 |
25 | // 假设我们有一个函数叫做f(s)
26 | // 这里我们使用通常的同步调用来调用函数
27 | f("direct")
28 |
29 | // 为了能够让这个函数以协程(goroutine)方式
30 | // 运行使用go f(s)
31 | // 这个协程将和调用它的协程并行执行
32 | go f("goroutine")
33 |
34 | // 你也可以为匿名函数开启一个协程运行
35 | go func(msg string) {
36 | fmt.Println(msg)
37 | }("going")
38 |
39 | // 上面的协程在调用之后就异步执行了,所以程序不用等待它们执行完成
40 | // 就跳到这里来了,下面的Scanln用来从命令行获取一个输入,然后才
41 | // 让main函数结束
42 | // 如果没有下面的Scanln语句,程序到这里会直接退出,而上面的协程还
43 | // 没有来得及执行完,你将无法看到上面两个协程运行的结果
44 | var input string
45 | fmt.Scanln(&input)
46 | fmt.Println("done")
47 | }
48 |
49 | 运行结果
50 |direct : 0
51 | direct : 1
52 | direct : 2
53 | goroutine : 0
54 | goroutine : 1
55 | goroutine : 2
56 | going
57 | ok
58 | done
59 |
60 |
61 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_parallel_channel.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Channel是连接并行协程(goroutine)的通道。你可以向一个通道写入数据然后从另外一个通道读取数据。
13 |package main
14 |
15 | import "fmt"
16 |
17 | func main() {
18 |
19 | // 使用`make(chan 数据类型)`来创建一个Channel
20 | // Channel的类型就是它们所传递的数据的类型
21 | messages := make(chan string)
22 |
23 | // 使用`channel <-`语法来向一个Channel写入数据
24 | // 这里我们从一个新的协程向messages通道写入数据ping
25 | go func() { messages <- "ping" }()
26 |
27 | // 使用`<-channel`语法来从Channel读取数据
28 | // 这里我们从main函数所在的协程来读取刚刚写入
29 | // messages通道的数据
30 | msg := <-messages
31 | fmt.Println(msg)
32 | }
33 |
34 | 运行结果
35 |ping
36 |
37 | 当我们运行程序的时候,数据ping成功地从一个协程传递到了另外一个协程。
38 | 默认情况下,协程之间的通信是同步的,也就是说数据的发送端和接收端必须配对使用。Channel的这种特点使得我们可以不用在程序结尾添加额外的代码也能够获取协程发送端发来的信息。因为程序执行到msg:=<-messages
的时候被阻塞了,直到获得发送端发来的信息才继续执行。
Go支持指针,可以用来给函数传递变量的引用。
13 |package main
14 |
15 | import "fmt"
16 |
17 | // 我们用两个不同的例子来演示指针的用法
18 | // zeroval函数有一个int类型参数,这个时候传递给函数的是变量的值
19 | func zeroval(ival int) {
20 | ival = 0
21 | }
22 |
23 | // zeroptr函数的参数是int类型指针,这个时候传递给函数的是变量的地址
24 | // 在函数内部对这个地址所指向的变量的任何修改都会反映到原来的变量上。
25 | func zeroptr(iptr *int) {
26 | *iptr = 0
27 | }
28 |
29 | func main() {
30 | i := 1
31 | fmt.Println("initial:", i)
32 |
33 | zeroval(i)
34 | fmt.Println("zeroval:", i)
35 |
36 | // &操作符用来取得i变量的地址
37 | zeroptr(&i)
38 | fmt.Println("zeroptr:", i)
39 |
40 | // 指针类型也可以输出
41 | fmt.Println("pointer:", &i)
42 | }
43 |
44 | 输出结果为
45 |initial: 1
46 | zeroval: 1
47 | zeroptr: 0
48 | pointer: 0xc084000038
49 |
50 |
51 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_process_run.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 在上面的例子中,我们演示了一下如何去触发执行一个外部的进程。我们这样做的原因是我们希望从Go进程里面可以访问外部进程的信息。但有的时候,我们仅仅希望执行一个外部进程来替代当前的Go进程。这个时候,我们需要使用Go提供的exec
函数。
package main
14 |
15 | import "syscall"
16 | import "os"
17 | import "os/exec"
18 |
19 | func main() {
20 |
21 | // 本例中,我们使用`ls`来演示。Go需要一个该命令
22 | // 的完整路径,所以我们使用`exec.LookPath`函数来
23 | // 找到它
24 | binary, lookErr := exec.LookPath("ls")
25 | if lookErr != nil {
26 | panic(lookErr)
27 | }
28 | // `Exec`函数需要一个切片参数,我们给ls命令一些
29 | // 常见的参数。注意,第一个参数必须是程序名称
30 | args := []string{"ls", "-a", "-l", "-h"}
31 |
32 | // `Exec`还需要一些环境变量,这里我们提供当前的
33 | // 系统环境
34 | env := os.Environ()
35 |
36 | // 这里是`os.Exec`调用。如果一切顺利,我们的原
37 | // 进程将终止,然后启动一个新的ls进程。如果有
38 | // 错误发生,我们将获得一个返回值
39 | execErr := syscall.Exec(binary, args, env)
40 | if execErr != nil {
41 | panic(execErr)
42 | }
43 | }
44 |
45 | 运行结果
46 |total 16
47 | drwxr-xr-x 4 mark 136B Oct 3 16:29 .
48 | drwxr-xr-x 91 mark 3.0K Oct 3 12:50 ..
49 | -rw-r--r-- 1 mark 1.3K Oct 3 16:28 execing-processes.go
50 |
51 | 注意,Go没有提供Unix下面经典的fork函数。通常这也不是一个问题,因为进程触发和进程执行已经覆盖了fork的大多数功能。
52 | 53 | -------------------------------------------------------------------------------- /GolangStudy/tutorial/go_by_example/go_process_trigger.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |有的时候,我们需要从Go程序里面触发一个其他的非Go进程来执行。
13 |package main
14 |
15 | import "fmt"
16 | import "io/ioutil"
17 | import "os/exec"
18 |
19 | func main() {
20 |
21 | // 我们从一个简单的命令开始,这个命令不需要任何参数
22 | // 或者输入,仅仅向stdout输出一些信息。`exec.Command`
23 | // 函数创建了一个代表外部进程的对象
24 | dateCmd := exec.Command("date")
25 |
26 | // `Output`是另一个运行命令时用来处理信息的函数,这个
27 | // 函数等待命令结束,然后收集命令输出。如果没有错误发
28 | // 生的话,`dateOut`将保存date的信息
29 | dateOut, err := dateCmd.Output()
30 | if err != nil {
31 | panic(err)
32 | }
33 | fmt.Println("> date")
34 | fmt.Println(string(dateOut))
35 |
36 | // 下面我们看一个需要从stdin输入数据的命令,我们将
37 | // 数据输入传给外部进程的stdin,然后从它输出到stdout
38 | // 的运行结果收集信息
39 | grepCmd := exec.Command("grep", "hello")
40 |
41 | // 这里我们显式地获取input/output管道,启动进程,
42 | // 向进程写入数据,然后读取输出结果,最后等待进程结束
43 | grepIn, _ := grepCmd.StdinPipe()
44 | grepOut, _ := grepCmd.StdoutPipe()
45 | grepCmd.Start()
46 | grepIn.Write([]byte("hello grep\ngoodbye grep"))
47 | grepIn.Close()
48 | grepBytes, _ := ioutil.ReadAll(grepOut)
49 | grepCmd.Wait()
50 |
51 | // 在上面的例子中,我们忽略了错误检测,但是你一样可以
52 | // 使用`if err!=nil`模式来进行处理。另外我们仅仅收集了
53 | // `StdoutPipe`的结果,同时你也可以用一样的方法来收集
54 | // `StderrPipe`的结果
55 | fmt.Println("> grep hello")
56 | fmt.Println(string(grepBytes))
57 |
58 | // 注意,我们在触发外部命令的时候,需要显式地提供
59 | // 命令和参数信息。另外如果你想用一个命令行字符串
60 | // 触发一个完整的命令,你可以使用bash的-c选项
61 | lsCmd := exec.Command("bash", "-c", "ls -a -l -h")
62 | lsOut, err := lsCmd.Output()
63 | if err != nil {
64 | panic(err)
65 | }
66 | fmt.Println("> ls -a -l -h")
67 | fmt.Println(string(lsOut))
68 | }
69 |
70 | 所触发的程序的执行结果和我们直接执行这些程序的结果是一样的。 71 | 运行结果
72 |> date
73 | Wed Oct 10 09:53:11 PDT 2012
74 | > grep hello
75 | hello grep
76 | > ls -a -l -h
77 | drwxr-xr-x 4 mark 136B Oct 3 16:29 .
78 | drwxr-xr-x 91 mark 3.0K Oct 3 12:50 ..
79 | -rw-r--r-- 1 mark 1.3K Oct 3 16:28 spawning-processes.go
80 |
81 |
82 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_rand_number.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Go的math/rand
包提供了伪随机数的生成。
package main
14 |
15 | import "fmt"
16 | import "math/rand"
17 |
18 | func main() {
19 |
20 | // 例如`rand.Intn`返回一个整型随机数n,0<=n<100
21 | fmt.Print(rand.Intn(100), ",")
22 | fmt.Print(rand.Intn(100))
23 | fmt.Println()
24 |
25 | // `rand.Float64` 返回一个`float64` `f`,
26 | // `0.0 <= f < 1.0`
27 | fmt.Println(rand.Float64())
28 |
29 | // 这个方法可以用来生成其他数值范围内的随机数,
30 | // 例如`5.0 <= f < 10.0`
31 | fmt.Print((rand.Float64()*5)+5, ",")
32 | fmt.Print((rand.Float64() * 5) + 5)
33 | fmt.Println()
34 |
35 | // 为了使随机数生成器具有确定性,可以给它一个seed
36 | s1 := rand.NewSource(42)
37 | r1 := rand.New(s1)
38 |
39 | fmt.Print(r1.Intn(100), ",")
40 | fmt.Print(r1.Intn(100))
41 | fmt.Println()
42 |
43 | // 如果源使用一个和上面相同的seed,将生成一样的随机数
44 | s2 := rand.NewSource(42)
45 | r2 := rand.New(s2)
46 | fmt.Print(r2.Intn(100), ",")
47 | fmt.Print(r2.Intn(100))
48 | fmt.Println()
49 | }
50 |
51 | 运行结果
52 |81,87
53 | 0.6645600532184904
54 | 7.1885709359349015,7.123187485356329
55 | 5,87
56 | 5,87
57 |
58 |
59 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_range.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | range函数是个神奇而有趣的内置函数,你可以使用它来遍历数组,切片和字典。
13 |当用于遍历数组和切片的时候,range函数返回索引和元素;
14 |当用于遍历字典的时候,range函数返回字典的键和值。
15 |package main
16 |
17 | import "fmt"
18 |
19 | func main() {
20 |
21 | // 这里我们使用range来计算一个切片的所有元素和
22 | // 这种方法对数组也适用
23 | nums := []int{2, 3, 4}
24 | sum := 0
25 | for _, num := range nums {
26 | sum += num
27 | }
28 | fmt.Println("sum:", sum)
29 |
30 | // range 用来遍历数组和切片的时候返回索引和元素值
31 | // 如果我们不要关心索引可以使用一个下划线(_)来忽略这个返回值
32 | // 当然我们有的时候也需要这个索引
33 | for i, num := range nums {
34 | if num == 3 {
35 | fmt.Println("index:", i)
36 | }
37 | }
38 |
39 | // 使用range来遍历字典的时候,返回键值对。
40 | kvs := map[string]string{"a": "apple", "b": "banana"}
41 | for k, v := range kvs {
42 | fmt.Printf("%s -> %s\n", k, v)
43 | }
44 |
45 | // range函数用来遍历字符串时,返回Unicode代码点。
46 | // 第一个返回值是每个字符的起始字节的索引,第二个是字符代码点,
47 | // 因为Go的字符串是由字节组成的,多个字节组成一个rune类型字符。
48 | for i, c := range "go" {
49 | fmt.Println(i, c)
50 | }
51 | }
52 |
53 | 输出结果为
54 |sum: 9
55 | index: 1
56 | a -> apple
57 | b -> banana
58 | 0 103
59 | 1 111
60 |
61 |
62 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_read_file.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 读写文件是很多程序的基本任务,下面我们看看Go里面的文件读取。
13 |package main
14 |
15 | import (
16 | "bufio"
17 | "fmt"
18 | "io"
19 | "io/ioutil"
20 | "os"
21 | )
22 |
23 | // 读取文件的函数调用大多数都需要检查错误,
24 | // 使用下面这个错误检查方法可以方便一点
25 | func check(e error) {
26 | if e != nil {
27 | panic(e)
28 | }
29 | }
30 |
31 | func main() {
32 |
33 | // 最基本的文件读写任务就是把整个文件的内容读取到内存
34 | dat, err := ioutil.ReadFile("/tmp/dat")
35 | check(err)
36 | fmt.Print(string(dat))
37 |
38 | // 有的时候你想更多地控制到底是读取文件的哪个部分,这个
39 | // 时候你可以使用`os.Open`打开一个文件获取一个`os.File`
40 | // 对象
41 | f, err := os.Open("/tmp/dat")
42 |
43 | // 从这个文件中读取一些字节,并且由于字节数组长度所限,
44 | // 最多读取5个字节,另外还需要注意实际能够读取的字节
45 | // 数量
46 | b1 := make([]byte, 5)
47 | n1, err := f.Read(b1)
48 | check(err)
49 | fmt.Printf("%d bytes: %s\n", n1, string(b1))
50 |
51 | // 你也可以使用`Seek`来跳转到文件中的一个已知位置,并从
52 | // 那个位置开始读取数据
53 | o2, err := f.Seek(6, 0)
54 | check(err)
55 | b2 := make([]byte, 2)
56 | n2, err := f.Read(b2)
57 | check(err)
58 | fmt.Printf("%d bytes @ %d: %s\n", n2, o2, string(b2))
59 |
60 | // `io`包提供了一些帮助文件读取的函数。例如上面的方法如果
61 | // 使用方法`ReadAtLeast`函数来实现,将使得程序更健壮
62 | o3, err := f.Seek(6, 0)
63 | check(err)
64 | b3 := make([]byte, 2)
65 | n3, err := io.ReadAtLeast(f, b3, 2)
66 | check(err)
67 | fmt.Printf("%d bytes @ %d: %s\n", n3, o3, string(b3))
68 |
69 | // 没有内置的rewind方法,但是可以使用`Seek(0,0)`来实现
70 | _, err = f.Seek(0, 0)
71 | check(err)
72 |
73 | // `bufio`包提供了缓冲读取文件的方法,这将使得文件读取更加
74 | // 高效
75 | r4 := bufio.NewReader(f)
76 | b4, err := r4.Peek(5)
77 | check(err)
78 | fmt.Printf("5 bytes: %s\n", string(b4))
79 |
80 | // 最后关闭打开的文件。一般来讲这个方法会在打开文件的时候,
81 | // 使用defer来延迟关闭
82 | f.Close()
83 | }
84 |
85 | 在运行程序之前,你需要创建一个/tmp/dat
文件,然后写入一些测试数据。
86 | 运行结果
hello world
88 | i am jemy
89 | who are you
90 | what do you like
91 | 5 bytes: hello
92 | 2 bytes @ 6: wo
93 | 2 bytes @ 6: wo
94 | 5 bytes: hello
95 |
96 |
97 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_recursion.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Go语言支持递归函数,这里是一个经典的斐波拉切数列的列子。
13 |package main
14 |
15 | import "fmt"
16 |
17 | // fact函数不断地调用自身,直到达到基本状态fact(0)
18 | func fact(n int) int {
19 | if n == 0 {
20 | return 1
21 | }
22 | return n * fact(n-1)
23 | }
24 |
25 | func main() {
26 | fmt.Println(fact(7))
27 | }
28 |
29 | 输出结果为
30 |5040
31 |
32 |
33 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_regex.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Go内置了对正则表达式的支持,这里是一般的正则表达式常规用法的例子。
13 |package main
14 |
15 | import "bytes"
16 | import "fmt"
17 | import "regexp"
18 |
19 | func main() {
20 |
21 | // 测试模式是否匹配字符串,括号里面的意思是
22 | // 至少有一个a-z之间的字符存在
23 | match, _ := regexp.MatchString("p([a-z]+)ch", "peach")
24 | fmt.Println(match)
25 |
26 | // 上面我们直接使用了字符串匹配的正则表达式,
27 | // 但是对于其他的正则匹配任务,你需要使用
28 | // `Compile`来使用一个优化过的正则对象
29 | r, _ := regexp.Compile("p([a-z]+)ch")
30 |
31 | // 正则结构体对象有很多方法可以使用,比如上面的例子
32 | // 也可以像下面这么写
33 | fmt.Println(r.MatchString("peach"))
34 |
35 | // 这个方法检测字符串参数是否存在正则所约束的匹配
36 | fmt.Println(r.FindString("peach punch"))
37 |
38 | // 这个方法查找第一次匹配的索引,并返回匹配字符串
39 | // 的起始索引和结束索引,而不是匹配的字符串
40 | fmt.Println(r.FindStringIndex("peach punch"))
41 |
42 | // 这个方法返回全局匹配的字符串和局部匹配的字符,比如
43 | // 这里会返回匹配`p([a-z]+)ch`的字符串
44 | // 和匹配`([a-z]+)`的字符串
45 | fmt.Println(r.FindStringSubmatch("peach punch"))
46 |
47 | // 和上面的方法一样,不同的是返回全局匹配和局部匹配的
48 | // 起始索引和结束索引
49 | fmt.Println(r.FindStringSubmatchIndex("peach punch"))
50 |
51 | // 这个方法返回所有正则匹配的字符,不仅仅是第一个
52 | fmt.Println(r.FindAllString("peach punch pinch", -1))
53 |
54 | // 这个方法返回所有全局匹配和局部匹配的字符串起始索引
55 | // 和结束索引
56 | fmt.Println(r.FindAllStringSubmatchIndex("peach punch pinch", -1))
57 |
58 | // 为这个方法提供一个正整数参数来限制匹配数量
59 | fmt.Println(r.FindAllString("peach punch pinch", 2))
60 |
61 | //上面我们都是用了诸如`MatchString`这样的方法,其实
62 | // 我们也可以使用`[]byte`作为参数,并且使用`Match`
63 | // 这样的方法名
64 | fmt.Println(r.Match([]byte("peach")))
65 |
66 | // 当使用正则表达式来创建常量的时候,你可以使用`MustCompile`
67 | // 因为`Compile`返回两个值
68 | r = regexp.MustCompile("p([a-z]+)ch")
69 | fmt.Println(r)
70 |
71 | // regexp包也可以用来将字符串的一部分替换为其他的值
72 | fmt.Println(r.ReplaceAllString("a peach", "<fruit>"))
73 |
74 | // `Func`变量可以让你将所有匹配的字符串都经过该函数处理
75 | // 转变为所需要的值
76 | in := []byte("a peach")
77 | out := r.ReplaceAllFunc(in, bytes.ToUpper)
78 | fmt.Println(string(out))
79 | }
80 |
81 | 运行结果
82 |true
83 | true
84 | peach
85 | [0 5]
86 | [peach ea]
87 | [0 5 1 3]
88 | [peach punch pinch]
89 | [[0 5 1 3] [6 11 7 9] [12 17 13 15]]
90 | [peach punch]
91 | true
92 | p([a-z]+)ch
93 | a <fruit>
94 | a PEACH
95 |
96 |
97 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_req_freq_control.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 频率控制是控制资源利用和保证服务高质量的重要机制。Go可以使用goroutine,channel和ticker来以优雅的方式支持频率控制。
13 |package main
14 |
15 | import "time"
16 | import "fmt"
17 |
18 | func main() {
19 |
20 | // 首先我们看下基本的频率限制。假设我们得控制请求频率,
21 | // 我们使用一个通道来处理所有的这些请求,这里向requests
22 | // 发送5个数据,然后关闭requests通道
23 | requests := make(chan int, 5)
24 | for i := 1; i <= 5; i++ {
25 | requests <- i
26 | }
27 | close(requests)
28 |
29 | // 这个limiter的Ticker每隔200毫秒结束通道阻塞
30 | // 这个limiter就是我们频率控制处理器
31 | limiter := time.Tick(time.Millisecond * 200)
32 |
33 | // 通过阻塞从limiter通道接受数据,我们将请求处理控制在每隔200毫秒
34 | // 处理一个请求,注意`<-limiter`的阻塞作用。
35 | for req := range requests {
36 | <-limiter
37 | fmt.Println("request", req, time.Now())
38 | }
39 |
40 | // 我们可以保持正常的请求频率限制,但也允许请求短时间内爆发
41 | // 我们可以通过通道缓存来实现,比如下面的这个burstyLimiter
42 | // 就允许同时处理3个事件。
43 | burstyLimiter := make(chan time.Time, 3)
44 |
45 | // 填充burstyLimiter,先发送3个数据
46 | for i := 0; i < 3; i++ {
47 | burstyLimiter <- time.Now()
48 | }
49 |
50 | // 然后每隔200毫秒再向burstyLimiter发送一个数据,这里是不断地
51 | // 每隔200毫秒向burstyLimiter发送数据
52 | go func() {
53 | for t := range time.Tick(time.Millisecond * 200) {
54 | burstyLimiter <- t
55 | }
56 | }()
57 |
58 | // 这里模拟5个请求,burstyRequests的前面3个请求会连续被处理,
59 | // 因为burstyLimiter被先连续发送3个数据的的缘故,而后面两个
60 | // 则每隔200毫秒处理一次
61 | burstyRequests := make(chan int, 5)
62 | for i := 1; i <= 5; i++ {
63 | burstyRequests <- i
64 | }
65 | close(burstyRequests)
66 | for req := range burstyRequests {
67 | <-burstyLimiter
68 | fmt.Println("request", req, time.Now())
69 | }
70 | }
71 |
72 | 运行结果
73 |request 1 2014-02-21 14:20:05.2696437 +0800 CST
74 | request 2 2014-02-21 14:20:05.4696637 +0800 CST
75 | request 3 2014-02-21 14:20:05.6696837 +0800 CST
76 | request 4 2014-02-21 14:20:05.8697037 +0800 CST
77 | request 5 2014-02-21 14:20:06.0697237 +0800 CST
78 | request 1 2014-02-21 14:20:06.0697237 +0800 CST
79 | request 2 2014-02-21 14:20:06.0697237 +0800 CST
80 | request 3 2014-02-21 14:20:06.0707238 +0800 CST
81 | request 4 2014-02-21 14:20:06.2707438 +0800 CST
82 | request 5 2014-02-21 14:20:06.4707638 +0800 CST
83 |
84 | 我们从输出的结果上可以看出最后的5个输出结果中,前三个的时间是连续的,而后两个的时间是隔了200毫秒。
85 | 86 | -------------------------------------------------------------------------------- /GolangStudy/tutorial/go_by_example/go_sha1_hash.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |SHA1散列经常用来计算二进制或者大文本数据的短标识值。git版本控制系统用SHA1来标识受版本控制的文件和目录。这里介绍Go中如何计算SHA1散列值。
13 | Go在crypto/*
包里面实现了几个常用的散列函数。
package main
15 |
16 | import "crypto/sha1"
17 | import "fmt"
18 |
19 | func main() {
20 | s := "sha1 this string"
21 |
22 | // 生成一个hash的模式是`sha1.New()`,`sha1.Write(bytes)`
23 | // 然后是`sha1.Sum([]byte{})`,下面我们开始一个新的hash
24 | // 示例
25 | h := sha1.New()
26 |
27 | // 写入要hash的字节,如果你的参数是字符串,使用`[]byte(s)`
28 | // 把它强制转换为字节数组
29 | h.Write([]byte(s))
30 |
31 | // 这里计算最终的hash值,Sum的参数是用来追加而外的字节到要
32 | // 计算的hash字节里面,一般来讲,如果上面已经把需要hash的
33 | // 字节都写入了,这里就设为nil就可以了
34 | bs := h.Sum(nil)
35 |
36 | // SHA1散列值经常以16进制的方式输出,例如git commit就是
37 | // 这样,所以可以使用`%x`来将散列结果格式化为16进制的字符串
38 | fmt.Println(s)
39 | fmt.Printf("%x\n", bs)
40 | }
41 |
42 | 运行结果
43 |sha1 this string
44 | cf23df2207d99a74fbe169e3eba035e633b65d94
45 |
46 |
47 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_signal_handle.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 有的时候我们希望Go能够智能地处理Unix信号。例如我们希望一个server接收到一个SIGTERM的信号时,能够自动地停止;或者一个命令行工具接收到一个SIGINT信号时,能够停止接收输入。现在我们来看下如何使用channel来处理信号。
13 |package main
14 |
15 | import "fmt"
16 | import "os"
17 | import "os/signal"
18 | import "syscall"
19 |
20 | func main() {
21 |
22 | // Go信号通知通过向一个channel发送``os.Signal`来实现。
23 | // 我们将创建一个channel来接受这些通知,同时我们还用
24 | // 一个channel来在程序可以退出的时候通知我们
25 | sigs := make(chan os.Signal, 1)
26 | done := make(chan bool, 1)
27 |
28 | // `signal.Notify`在给定的channel上面注册该channel
29 | // 可以接受的信号
30 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
31 |
32 | // 这个goroutine阻塞等待信号的到来,当信号到来的时候,
33 | // 输出该信号,然后通知程序可以结束了
34 | go func() {
35 | sig := <-sigs
36 | fmt.Println()
37 | fmt.Println(sig)
38 | done <- true
39 | }()
40 |
41 | // 程序将等待接受信号,然后退出
42 | fmt.Println("awaiting signal")
43 | <-done
44 | fmt.Println("exiting")
45 | }
46 |
47 | 当运行程序的时候,程序将阻塞等待信号的到来,我们可以使用CTRL+C
来发送一个SIGINT
信号,这样程序就会输出interrupt后退出。
awaiting signal
49 |
50 | interrupt
51 | exiting
52 |
53 |
54 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_slice.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 切片是Go语言的关键类型之一,它提供了比数组更多的功能。
13 |示例1:
14 |package main
15 |
16 | import "fmt"
17 |
18 | func main() {
19 |
20 | // 和数组不同的是,切片的长度是可变的。
21 | // 我们可以使用内置函数make来创建一个长度不为零的切片
22 | // 这里我们创建了一个长度为3,存储字符串的切片,切片元素
23 | // 默认为零值,对于字符串就是""。
24 | s := make([]string, 3)
25 | fmt.Println("emp:", s)
26 |
27 | // 可以使用和数组一样的方法来设置元素值或获取元素值
28 | s[0] = "a"
29 | s[1] = "b"
30 | s[2] = "c"
31 | fmt.Println("set:", s)
32 | fmt.Println("get:", s[2])
33 |
34 | // 可以用内置函数len获取切片的长度
35 | fmt.Println("len:", len(s))
36 |
37 | // 切片还拥有一些数组所没有的功能。
38 | // 例如我们可以使用内置函数append给切片追加值,然后
39 | // 返回一个拥有新切片元素的切片。
40 | // 注意append函数不会改变原切片,而是生成了一个新切片,
41 | // 我们需要用原来的切片来接收这个新切片
42 | s = append(s, "d")
43 | s = append(s, "e", "f")
44 | fmt.Println("apd:", s)
45 |
46 | // 另外我们还可以从一个切片拷贝元素到另一个切片
47 | // 下面的例子就是创建了一个和切片s长度相同的新切片
48 | // 然后使用内置的copy函数来拷贝s的元素到c中。
49 | c := make([]string, len(s))
50 | copy(c, s)
51 | fmt.Println("cpy:", c)
52 |
53 | // 切片还支持一个取切片的操作 "slice[low:high]"
54 | // 获取的新切片包含元素"slice[low]",但是不包含"slice[high]"
55 | // 下面的例子就是取一个新切片,元素包括"s[2]","s[3]","s[4]"。
56 | l := s[2:5]
57 | fmt.Println("sl1:", l)
58 |
59 | // 如果省略low,默认从0开始,不包括"slice[high]"元素
60 | l = s[:5]
61 | fmt.Println("sl2:", l)
62 |
63 | // 如果省略high,默认为len(slice),包括"slice[low]"元素
64 | l = s[2:]
65 | fmt.Println("sl3:", l)
66 |
67 | // 我们可以同时声明和初始化一个切片
68 | t := []string{"g", "h", "i"}
69 | fmt.Println("dcl:", t)
70 |
71 | // 我们也可以创建多维切片,和数组不同的是,切片元素的长度也是可变的。
72 | twoD := make([][]int, 3)
73 | for i := 0; i < 3; i++ {
74 | innerLen := i + 1
75 | twoD[i] = make([]int, innerLen)
76 | for j := 0; j < innerLen; j++ {
77 | twoD[i][j] = i + j
78 | }
79 | }
80 | fmt.Println("2d: ", twoD)
81 | }
82 |
83 | 输出结果为
84 |emp: [ ]
85 | set: [a b c]
86 | get: c
87 | len: 3
88 | apd: [a b c d e f]
89 | cpy: [a b c d e f]
90 | sl1: [c d e]
91 | sl2: [a b c d e]
92 | sl3: [c d e f]
93 | dcl: [g h i]
94 | 2d: [[0] [1 2] [2 3 4]]
95 |
96 | 数组和切片的定义方式的区别在于[]
之中是否有固定长度
或者推断长度标志符...
。
示例2:
98 |package main
99 |
100 | import "fmt"
101 |
102 | func main() {
103 | s1 := make([]int, 0)
104 | test(s1)
105 | fmt.Println(s1)
106 | }
107 |
108 | func test(s []int) {
109 | s = append(s, 3)
110 | //因为原来分配的空间不够,所以在另外一个地址又重新分配了空间,所以原始地址的数据没有变
111 | }
112 |
113 | 输出结果为:
114 |[]
115 |
116 | 若改为:
117 |package main
118 |
119 | import "fmt"
120 |
121 | func main() {
122 | s1 := make([]int, 0)
123 | s1 = test(s1)
124 | fmt.Println(s1)
125 | }
126 |
127 | func test(s []int) []int {
128 | s = append(s, 3)
129 | return s
130 | }
131 |
132 | 输出结果为:
133 |[3]//正确结果
134 |
135 | 示例3:
136 |cap是slice的最大容量,append函数添加元素,如果超过原始slice的容量,会重新分配底层数组。
137 |package main
138 |
139 | import "fmt"
140 |
141 | func main() {
142 | s1 := make([]int, 3, 6)
143 | fmt.Println("s1= ", s1, len(s1), cap(s1))
144 | s2 := append(s1, 1, 2, 3)
145 | fmt.Println("s1= ", s1, len(s1), cap(s1))
146 | fmt.Println("s2= ", s2, len(s2), cap(s2))
147 | s3 := append(s2, 4, 5, 6)
148 | fmt.Println("s1= ", s1, len(s1), cap(s1))
149 | fmt.Println("s2= ", s2, len(s2), cap(s2))
150 | fmt.Println("s3= ", s3, len(s3), cap(s3))
151 |
152 | }
153 |
154 | 输出结果为:
155 |s1= [0 0 0] 3 6
156 | s1= [0 0 0] 3 6
157 | s2= [0 0 0 1 2 3] 6 6
158 | s1= [0 0 0] 3 6
159 | s2= [0 0 0 1 2 3] 6 6
160 | s3= [0 0 0 1 2 3 4 5 6] 9 12
161 |
162 | 示例4:
163 |指向同一底层数组的slice之间copy时,允许存在重叠。copy数组时,受限于src和dst数组的长度最小值。
164 |package main
165 |
166 | import "fmt"
167 |
168 | func main() {
169 | s1 := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
170 | s2 := make([]int, 3, 20)
171 | var n int
172 | n = copy(s2, s1)
173 | fmt.Println(n, s2, len(s2), cap(s2))
174 |
175 | s3 := s1[4:6]
176 | fmt.Println(n, s3, len(s3), cap(s3))
177 |
178 | n = copy(s3, s1[1:5])
179 | fmt.Println(n, s3, len(s3), cap(s3))
180 | }
181 |
182 | 输出结果:
183 |3 [0 1 2] 3 20
184 | 3 [4 5] 2 6
185 | 2 [1 2] 2 6
186 |
187 |
188 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_sort.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Go的sort包实现了内置数据类型和用户自定义数据类型的排序功能。我们先看看内置数据类型的排序。
13 |package main
14 |
15 | import "fmt"
16 | import "sort"
17 |
18 | func main() {
19 |
20 | // 这些排序方法都是针对内置数据类型的。
21 | // 这里的排序方法都是就地排序,也就是说排序改变了
22 | // 切片内容,而不是返回一个新的切片
23 | strs := []string{"c", "a", "b"}
24 | sort.Strings(strs)
25 | fmt.Println("Strings:", strs)
26 |
27 | // 对于整型的排序
28 | ints := []int{7, 2, 4}
29 | sort.Ints(ints)
30 | fmt.Println("Ints: ", ints)
31 |
32 | // 我们还可以检测切片是否已经排序好
33 | s := sort.IntsAreSorted(ints)
34 | fmt.Println("Sorted: ", s)
35 | }
36 |
37 | 输出结果
38 |Strings: [a b c]
39 | Ints: [2 4 7]
40 | Sorted: true
41 |
42 |
43 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_stateful_goroutine.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 在上面的例子中,我们演示了如何通过使用mutex来在多个协程之间共享状态。另外一种方法是使用协程内置的同步机制来实现。这种基于通道的方法和Go的通过消息共享内存,保证每份数据为单独的协程所有的理念是一致的。
13 |package main
14 |
15 | import (
16 | "fmt"
17 | "math/rand"
18 | "sync/atomic"
19 | "time"
20 | )
21 |
22 | // 在这个例子中,将有一个单独的协程拥有这个状态。这样可以
23 | // 保证这个数据不会被并行访问所破坏。为了读写这个状态,其
24 | // 他的协程将向这个协程发送信息并且相应地接受返回信息。
25 | // 这些`readOp`和`writeOp`结构体封装了这些请求和回复
26 | type readOp struct {
27 | key int
28 | resp chan int
29 | }
30 | type writeOp struct {
31 | key int
32 | val int
33 | resp chan bool
34 | }
35 |
36 | func main() {
37 |
38 | // 我们将计算我们执行了多少次操作
39 | var ops int64 = 0
40 |
41 | // reads和writes通道将被其他协程用来从中读取或写入数据
42 | reads := make(chan *readOp)
43 | writes := make(chan *writeOp)
44 |
45 | // 这个是拥有`state`的协程,`state`是一个协程的私有map
46 | // 变量。这个协程不断地`select`通道`reads`和`writes`,
47 | // 当有请求来临的时候进行回复。一旦有请求,首先执行所
48 | // 请求的操作,然后给`resp`通道发送一个表示请求成功的值。
49 | go func() {
50 | var state = make(map[int]int)
51 | for {
52 | select {
53 | case read := <-reads:
54 | read.resp <- state[read.key]
55 | case write := <-writes:
56 | state[write.key] = write.val
57 | write.resp <- true
58 | }
59 | }
60 | }()
61 |
62 | // 这里启动了100个协程来向拥有状态的协程请求读数据。
63 | // 每次读操作都需要创建一个`readOp`,然后发送到`reads`
64 | // 通道,然后等待接收请求回复
65 | for r := 0; r < 100; r++ {
66 | go func() {
67 | for {
68 | read := &readOp{
69 | key: rand.Intn(5),
70 | resp: make(chan int)}
71 | reads <- read
72 | <-read.resp
73 | atomic.AddInt64(&ops, 1)
74 | }
75 | }()
76 | }
77 |
78 | // 我们开启10个写协程
79 | for w := 0; w < 10; w++ {
80 | go func() {
81 | for {
82 | write := &writeOp{
83 | key: rand.Intn(5),
84 | val: rand.Intn(100),
85 | resp: make(chan bool)}
86 | writes <- write
87 | <-write.resp
88 | atomic.AddInt64(&ops, 1)
89 | }
90 | }()
91 | }
92 |
93 | // 让协程运行1秒钟
94 | time.Sleep(time.Second)
95 |
96 | // 最后输出操作数量ops的值
97 | opsFinal := atomic.LoadInt64(&ops)
98 | fmt.Println("ops:", opsFinal)
99 | }
100 |
101 | 运行结果
102 |ops: 880578
103 |
104 | 运行这个程序,我们会看到基于协程的状态管理每秒可以处理800, 000个操作。对于这个例子来讲,基于协程的方法比基于mutex的方法更加复杂一点。当然在某些情况下还是很有用的。例如你有很多复杂的协程,而且管理多个mutex可能导致错误。 105 | 当然你可以选择使用任意一种方法,只要你保证这种方法让你觉得很舒服而且也能保证程序的正确性。
106 | 107 | -------------------------------------------------------------------------------- /GolangStudy/tutorial/go_by_example/go_string_byte_convert.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |String转换到Byte数组时,每个byte(byte类型其实就是uint8)保存字符串对应字节的数值。
13 |注意Go的字符串是UTF-8编码的,每个字符长度是不确定的,一些字符可能是1、2、3或者4个字节结尾。
14 |示例1:
15 |package main
16 |
17 | import "fmt"
18 |
19 | func main() {
20 |
21 | s1 := "abcd"
22 | b1 := []byte(s1)
23 | fmt.Println(b1) // [97 98 99 100]
24 |
25 | s2 := "中文"
26 | b2 := []byte(s2)
27 | fmt.Println(b2) // [228 184 173 230 150 135], unicode,每个中文字符会由三个byte组成
28 |
29 | r1 := []rune(s1)
30 | fmt.Println(r1) // [97 98 99 100], 每个字一个数值
31 |
32 | r2 := []rune(s2)
33 | fmt.Println(r2) // [20013 25991], 每个字一个数值
34 |
35 | }
36 |
37 |
38 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_string_format.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Go对字符串格式化提供了良好的支持。下面我们看些常用的字符串格式化的例子。
13 |package main
14 |
15 | import "fmt"
16 | import "os"
17 |
18 | type point struct {
19 | x, y int
20 | }
21 |
22 | func main() {
23 |
24 | // Go提供了几种打印格式,用来格式化一般的Go值,例如
25 | // 下面的%v打印了一个point结构体的对象的值
26 | p := point{1, 2}
27 | fmt.Printf("%v\n", p)
28 |
29 | // 如果所格式化的值是一个结构体对象,那么`%+v`的格式化输出
30 | // 将包括结构体的成员名称和值
31 | fmt.Printf("%+v\n", p)
32 |
33 | // `%#v`格式化输出将输出一个值的Go语法表示方式。
34 | fmt.Printf("%#v\n", p)
35 |
36 | // 使用`%T`来输出一个值的数据类型
37 | fmt.Printf("%T\n", p)
38 |
39 | // 格式化布尔型变量
40 | fmt.Printf("%t\n", true)
41 |
42 | // 有很多的方式可以格式化整型,使用`%d`是一种
43 | // 标准的以10进制来输出整型的方式
44 | fmt.Printf("%d\n", 123)
45 |
46 | // 这种方式输出整型的二进制表示方式
47 | fmt.Printf("%b\n", 14)
48 |
49 | // 这里打印出该整型数值所对应的字符
50 | fmt.Printf("%c\n", 33)
51 |
52 | // 使用`%x`输出一个值的16进制表示方式
53 | fmt.Printf("%x\n", 456)
54 |
55 | // 浮点型数值也有几种格式化方法。最基本的一种是`%f`
56 | fmt.Printf("%f\n", 78.9)
57 |
58 | // `%e`和`%E`使用科学计数法来输出整型
59 | fmt.Printf("%e\n", 123400000.0)
60 | fmt.Printf("%E\n", 123400000.0)
61 |
62 | // 使用`%s`输出基本的字符串
63 | fmt.Printf("%s\n", "\"string\"")
64 |
65 | // 输出像Go源码中那样带双引号的字符串,需使用`%q`
66 | fmt.Printf("%q\n", "\"string\"")
67 |
68 | // `%x`以16进制输出字符串,每个字符串的字节用两个字符输出
69 | fmt.Printf("%x\n", "hex this")
70 |
71 | // 使用`%p`输出一个指针的值
72 | fmt.Printf("%p\n", &p)
73 |
74 | // 当输出数字的时候,经常需要去控制输出的宽度和精度。
75 | // 可以使用一个位于%后面的数字来控制输出的宽度,默认
76 | // 情况下输出是右对齐的,左边加上空格
77 | fmt.Printf("|%6d|%6d|\n", 12, 345)
78 |
79 | // 你也可以指定浮点数的输出宽度,同时你还可以指定浮点数
80 | // 的输出精度
81 | fmt.Printf("|%6.2f|%6.2f|\n", 1.2, 3.45)
82 |
83 | // To left-justify, use the `-` flag.
84 | fmt.Printf("|%-6.2f|%-6.2f|\n", 1.2, 3.45)
85 |
86 | // 你也可以指定输出字符串的宽度来保证它们输出对齐。默认
87 | // 情况下,输出是右对齐的
88 | fmt.Printf("|%6s|%6s|\n", "foo", "b")
89 |
90 | // 为了使用左对齐你可以在宽度之前加上`-`号
91 | fmt.Printf("|%-6s|%-6s|\n", "foo", "b")
92 |
93 | // `Printf`函数的输出是输出到命令行`os.Stdout`的,你
94 | // 可以用`Sprintf`来将格式化后的字符串赋值给一个变量
95 | s := fmt.Sprintf("a %s", "string")
96 | fmt.Println(s)
97 |
98 | // 你也可以使用`Fprintf`来将格式化后的值输出到`io.Writers`
99 | fmt.Fprintf(os.Stderr, "an %s\n", "error")
100 | }
101 |
102 | 运行结果
103 |{1 2}
104 | {x:1 y:2}
105 | main.point{x:1, y:2}
106 | main.point
107 | true
108 | 123
109 | 1110
110 | !
111 | 1c8
112 | 78.900000
113 | 1.234000e+08
114 | 1.234000E+08
115 | "string"
116 | "\"string\""
117 | 6865782074686973
118 | 0x103a10c0
119 | | 12| 345|
120 | | 1.20| 3.45|
121 | |1.20 |3.45 |
122 | | foo| b|
123 | |foo |b |
124 | a string
125 | an error
126 |
127 |
128 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_string_manipulate.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | strings 标准库提供了很多字符串操作相关的函数。这里提供的几个例子是让你先对这个包有个基本了解。
13 |package main
14 |
15 | import s "strings"
16 | import "fmt"
17 |
18 | // 这里给fmt.Println起个别名,因为下面我们会多处使用。
19 | var p = fmt.Println
20 |
21 | func main() {
22 |
23 | // 下面是strings包里面提供的一些函数实例。注意这里的函数并不是
24 | // string对象所拥有的方法,这就是说使用这些字符串操作函数的时候
25 | // 你必须将字符串对象作为第一个参数传递进去。
26 | p("Contains: ", s.Contains("test", "es"))
27 | p("Count: ", s.Count("test", "t"))
28 | p("HasPrefix: ", s.HasPrefix("test", "te"))
29 | p("HasSuffix: ", s.HasSuffix("test", "st"))
30 | p("Index: ", s.Index("test", "e"))
31 | p("Join: ", s.Join([]string{"a", "b"}, "-"))
32 | p("Repeat: ", s.Repeat("a", 5))
33 | p("Replace: ", s.Replace("foo", "o", "0", -1))
34 | p("Replace: ", s.Replace("foo", "o", "0", 1))
35 | p("Split: ", s.Split("a-b-c-d-e", "-"))
36 | p("ToLower: ", s.ToLower("TEST"))
37 | p("ToUpper: ", s.ToUpper("test"))
38 | p()
39 |
40 | // 你可以在strings包里面找到更多的函数
41 |
42 | // 这里还有两个字符串操作方法,它们虽然不是strings包里面的函数,
43 | // 但是还是值得提一下。一个是获取字符串长度,另外一个是从字符串中
44 | // 获取指定索引的字符
45 | p("Len: ", len("hello"))
46 | p("Char:", "hello"[1])
47 | }
48 |
49 | 运行结果
50 |Contains: true
51 | Count: 2
52 | HasPrefix: true
53 | HasSuffix: true
54 | Index: 1
55 | Join: a-b
56 | Repeat: aaaaa
57 | Replace: f00
58 | Replace: f0o
59 | Split: [a b c d e]
60 | ToLower: test
61 | ToUpper: TEST
62 |
63 | Len: 5
64 | Char: 101
65 |
66 |
67 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_struct.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Go语言结构体数据类是将各个类型的变量定义的集合,通常用来表示记录。
13 |package main
14 |
15 | import "fmt"
16 |
17 | // 这个person结构体有name和age成员
18 | type person struct {
19 | name string
20 | age int
21 | }
22 |
23 | func main() {
24 |
25 | // 这个语法创建一个新结构体变量
26 | fmt.Println(person{"Bob", 20})
27 |
28 | // 可以使用"成员:值"的方式来初始化结构体变量
29 | fmt.Println(person{name: "Alice", age: 30})
30 |
31 | // 未显式赋值的成员初始值为零值
32 | fmt.Println(person{name: "Fred"})
33 |
34 | // 可以使用&来获取结构体变量的地址
35 | fmt.Println(&person{name: "Ann", age: 40})
36 |
37 | // 使用点号(.)来访问结构体成员
38 | s := person{name: "Sean", age: 50}
39 | fmt.Println(s.name)
40 |
41 | // 结构体指针也可以使用点号(.)来访问结构体成员
42 | // Go语言会自动识别出来
43 | sp := &s
44 | fmt.Println(sp.age)
45 |
46 | // 结构体成员变量的值是可以改变的
47 | sp.age = 51
48 | fmt.Println(sp.age)
49 | }
50 |
51 | 输出结果为
52 |{Bob 20}
53 | {Alice 30}
54 | {Fred 0}
55 | &{Ann 40}
56 | Sean
57 | 50
58 | 51
59 |
60 |
61 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_switch.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 当条件判断分支太多的时候,我们会使用switch语句来优化逻辑。
13 |package main
14 |
15 | import "fmt"
16 | import "time"
17 |
18 | func main() {
19 |
20 | // 基础的switch用法
21 | i := 2
22 | fmt.Print("write ", i, " as ")
23 | switch i {
24 | case 1:
25 | fmt.Println("one")
26 | case 2:
27 | fmt.Println("two")
28 | case 3:
29 | fmt.Println("three")
30 | }
31 |
32 | // 你可以使用逗号来在case中分开多个条件。还可以使用default语句,
33 | // 当上面的case都没有满足的时候执行default所指定的逻辑块。
34 | switch time.Now().Weekday() {
35 | case time.Saturday, time.Sunday:
36 | fmt.Println("it's the weekend")
37 | default:
38 | fmt.Println("it's a weekday")
39 | }
40 |
41 | // 当switch没有跟表达式的时候,功能和if/else相同,这里我们
42 | // 还可以看到case后面的表达式不一定是常量。
43 | t := time.Now()
44 | switch {
45 | case t.Hour() < 12:
46 | fmt.Println("it's before noon")
47 | default:
48 | fmt.Println("it's after noon")
49 | }
50 | }
51 |
52 | 输出结果为
53 |write 2 as two
54 | it's a weekday
55 | it's before noon
56 |
57 |
58 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_ticker.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Timer是让你等待一段时间然后去做一件事情,这件事情只会做一次。而Ticker是让你按照一定的时间间隔循环往复地做一件事情,除非你手动停止它。
13 |package main
14 |
15 | import "time"
16 | import "fmt"
17 |
18 | func main() {
19 |
20 | // Ticker使用和Timer相似的机制,同样是使用一个通道来发送数据。
21 | // 这里我们使用range函数来遍历通道数据,这些数据每隔500毫秒被
22 | // 发送一次,这样我们就可以接收到
23 | ticker := time.NewTicker(time.Millisecond * 500)
24 | go func() {
25 | for t := range ticker.C {
26 | fmt.Println("Tick at", t)
27 | }
28 | }()
29 |
30 | // Ticker和Timer一样可以被停止。一旦Ticker停止后,通道将不再
31 | // 接收数据,这里我们将在1500毫秒之后停止
32 | time.Sleep(time.Millisecond * 1500)
33 | ticker.Stop()
34 | fmt.Println("Ticker stopped")
35 | }
36 |
37 | 输出结果
38 |Tick at 2014-02-18 05:42:50.363640783 +0800 CST
39 | Tick at 2014-02-18 05:42:50.863793985 +0800 CST
40 | Tick at 2014-02-18 05:42:51.363532887 +0800 CST
41 | Ticker stopped
42 |
43 | 在这个例子中,我们让Ticker一个独立协程上每隔500毫秒执行一次,然后在main函数所在协程上等待1500毫秒,然后停止Ticker。所以只输出了3次Ticker at
信息。
Go提供了对时间和一段时间的支持。这里有一些例子。
13 |package main
14 |
15 | import "fmt"
16 | import "time"
17 |
18 | func main() {
19 |
20 | // 从获取当前时间开始
21 | var now = time.Now()
22 | fmt.Println(now)
23 |
24 | // 你可以提供年,月,日等来创建一个时间。当然时间
25 | // 总是会和地区联系在一起,也就是时区
26 | var then = time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
27 | fmt.Println(then)
28 |
29 | // 你可以获取时间的各个组成部分
30 | fmt.Println(then.Year())
31 | fmt.Println(then.Month())
32 | fmt.Println(then.Day())
33 | fmt.Println(then.Hour())
34 | fmt.Println(then.Minute())
35 | fmt.Println(then.Second())
36 | fmt.Println(then.Nanosecond())
37 | fmt.Println(then.Location())
38 |
39 | // 输出当天是周几,Monday-Sunday中的一个
40 | fmt.Println(then.Weekday())
41 |
42 | // 下面的几个方法判断两个时间的顺序,精确到秒
43 | fmt.Println(then.Before(now))
44 | fmt.Println(then.After(now))
45 | fmt.Println(then.Equal(now))
46 |
47 | // Sub方法返回两个时间的间隔(Duration)
48 | var diff = now.Sub(then)
49 | fmt.Println(diff)
50 |
51 | // 可以以不同的单位来计算间隔的大小
52 | fmt.Println(diff.Hours())
53 | fmt.Println(diff.Minutes())
54 | fmt.Println(diff.Seconds())
55 | fmt.Println(diff.Nanoseconds())
56 |
57 | // 你可以使用Add方法来为时间增加一个间隔
58 | // 使用负号表示时间向前推移一个时间间隔
59 | fmt.Println(then.Add(diff))
60 | fmt.Println(then.Add(-diff))
61 | }
62 |
63 | 运行结果
64 |2014-03-02 22:54:40.561698065 +0800 CST
65 | 2009-11-17 20:34:58.651387237 +0000 UTC
66 | 2009
67 | November
68 | 17
69 | 20
70 | 34
71 | 58
72 | 651387237
73 | UTC
74 | Tuesday
75 | true
76 | false
77 | false
78 | 37578h19m41.910310828s
79 | 37578.328308419674
80 | 2.2546996985051804e+06
81 | 1.3528198191031083e+08
82 | 135281981910310828
83 | 2014-03-02 14:54:40.561698065 +0000 UTC
84 | 2005-08-05 02:15:16.741076409 +0000 UTC
85 |
86 |
87 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_time_format_parse.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Go使用模式匹配的方式来支持日期格式化和解析。
13 |package main
14 |
15 | import "fmt"
16 | import "time"
17 |
18 | func main() {
19 | // 这里有一个根据RFC3339来格式化日期的例子
20 | t := time.Now()
21 | fmt.Println(t.Format("2006-01-02T15:04:05Z07:00"))
22 |
23 | // Format 函数使用一种基于示例的模式匹配方式,
24 | // 它使用已经格式化的时间模式来决定所给定参数
25 | // 的输出格式
26 | fmt.Println(t.Format("3:04PM"))
27 | fmt.Println(t.Format("Mon Jan _2 15:04:05 2006"))
28 | fmt.Println(t.Format("2006-01-02T15:04:05.999999-07:00"))
29 |
30 | // 对于纯数字表示的时间来讲,你也可以使用标准
31 | // 的格式化字符串的方式来格式化时间
32 | fmt.Printf("%d-%02d-%02dT%02d:%02d:%02d-00:00\n",
33 | t.Year(), t.Month(), t.Day(),
34 | t.Hour(), t.Minute(), t.Second())
35 |
36 | // 时间解析也是采用一样的基于示例的方式
37 | withNanos := "2006-01-02T15:04:05.999999999-07:00"
38 | t1, e := time.Parse(
39 | withNanos,
40 | "2012-11-01T22:08:41.117442+00:00")
41 | fmt.Println(t1)
42 | kitchen := "3:04PM"
43 | t2, e := time.Parse(kitchen, "8:41PM")
44 | fmt.Println(t2)
45 |
46 | // Parse将返回一个错误,如果所输入的时间格式不对的话
47 | ansic := "Mon Jan _2 15:04:05 2006"
48 | _, e = time.Parse(ansic, "8:41PM")
49 | fmt.Println(e)
50 |
51 | // 你可以使用一些预定义的格式来格式化或解析时间
52 | fmt.Println(t.Format(time.Kitchen))
53 | }
54 |
55 | 运行结果
56 |2014-03-03T22:39:31+08:00
57 | 10:39PM
58 | Mon Mar 3 22:39:31 2014
59 | 2014-03-03T22:39:31.647077+08:00
60 | 2014-03-03T22:39:31-00:00
61 | 2012-11-01 22:08:41.117442 +0000 +0000
62 | 0000-01-01 20:41:00 +0000 UTC
63 | parsing time "8:41PM" as "Mon Jan _2 15:04:05 2006": cannot parse "8:41PM" as "Mon"
64 | 10:39PM
65 |
66 |
67 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_timeout.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 超时对那些连接外部资源的程序来说是很重要的,否则就需要限定执行时间。在Go里面实现超时很简单。我们可以使用channel和select很容易地做到。
13 |package main
14 |
15 | import "time"
16 | import "fmt"
17 |
18 | func main() {
19 |
20 | // 在这个例子中,假设我们执行了一个外部调用,2秒之后将结果写入c1
21 | c1 := make(chan string, 1)
22 | go func() {
23 | time.Sleep(time.Second * 2)
24 | c1 <- "result 1"
25 | }()
26 |
27 | // 这里使用select来实现超时,`res := <-c1`等待通道结果,
28 | // `<- Time.After`则在等待1秒后返回一个值,因为select首先
29 | // 执行那些不再阻塞的case,所以这里会执行超时程序,如果
30 | // `res := <-c1`超过1秒没有执行的话
31 | select {
32 | case res := <-c1:
33 | fmt.Println(res)
34 | case <-time.After(time.Second * 1):
35 | fmt.Println("timeout 1")
36 | }
37 |
38 | // 如果我们将超时时间设为3秒,这个时候`res := <-c2`将在
39 | // 超时case之前执行,从而能够输出写入通道c2的值
40 | c2 := make(chan string, 1)
41 | go func() {
42 | time.Sleep(time.Second * 2)
43 | c2 <- "result 2"
44 | }()
45 | select {
46 | case res := <-c2:
47 | fmt.Println(res)
48 | case <-time.After(time.Second * 3):
49 | fmt.Println("timeout 2")
50 | }
51 | }
52 |
53 | 运行结果
54 |timeout 1
55 | result 2
56 |
57 |
58 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_timer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 我们有的时候希望Go在未来的某个时刻执行或者是以一定的时间间隔重复执行。Go内置的timer和ticker功能使得这些任务变得简单了。我们先看看timer的功能,下一节再看看ticker的功能。
13 |package main
14 |
15 | import "time"
16 | import "fmt"
17 |
18 | func main() {
19 | // Timer 代表了未来的一个事件,你告诉timer需要等待多久,然后
20 | // 计时器提供了一个通道,这个通道将在等待的时间结束后得到通知,
21 | // 这里的timer将等待2秒
22 | timer1 := time.NewTimer(time.Second * 2)
23 |
24 | // 这里`<-timer1.C`在timer的通道`C`上面阻塞等待,直到有个值发送给该
25 | // 通道,通知通道计时器已经等待完成。
26 | // timer.NewTimer方法获取的timer1的结构体定义为
27 | // type Ticket struct{
28 | // C <-chan Time
29 | //}
30 | <-timer1.C
31 | fmt.Println("Timer 1 expired")
32 |
33 | // 如果你仅仅需要等待的话,你可以使用`time.Sleep`,而timer的
34 | // 独特之处在于你可以在timer等待完成之前取消等待。
35 | timer2 := time.NewTimer(time.Second)
36 | go func() {
37 | <-timer2.C
38 | fmt.Println("Timer 2 expired")
39 | }()
40 | stop2 := timer2.Stop()
41 | if stop2 {
42 | fmt.Println("Timer 2 stopped")
43 | }
44 | }
45 |
46 | 运行结果
47 |Timer 1 expired
48 | Timer 2 stopped
49 |
50 | 在上面的例子中,第一个timer将在2秒后等待完成而第二个timer则在等待完成之前被取消了。
51 | 52 | -------------------------------------------------------------------------------- /GolangStudy/tutorial/go_by_example/go_timestamp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |程序的一个通常需求是计算从Unix起始时间开始到某个时刻的秒数,毫秒数,微秒数等。 13 | 我们来看看Go里面是怎么做的。
14 |package main
15 |
16 | import "fmt"
17 | import "time"
18 |
19 | func main() {
20 |
21 | // 使用Unix和UnixNano来分别获取从Unix起始时间
22 | // 到现在所经过的秒数和微秒数
23 | now := time.Now()
24 | secs := now.Unix()
25 | nanos := now.UnixNano()
26 | fmt.Println(now)
27 |
28 | // 注意这里没有UnixMillis方法,所以我们需要将
29 | // 微秒手动除以一个数值来获取毫秒
30 | millis := nanos / 1000000
31 | fmt.Println(secs)
32 | fmt.Println(millis)
33 | fmt.Println(nanos)
34 |
35 | // 反过来,你也可以将一个整数秒数或者微秒数转换
36 | // 为对应的时间
37 | fmt.Println(time.Unix(secs, 0))
38 | fmt.Println(time.Unix(0, nanos))
39 | }
40 |
41 | 运行结果
42 |2014-03-02 23:11:31.118666918 +0800 CST
43 | 1393773091
44 | 1393773091118
45 | 1393773091118666918
46 | 2014-03-02 23:11:31 +0800 CST
47 | 2014-03-02 23:11:31.118666918 +0800 CST
48 |
49 |
50 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_url_parse.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | URL提供了一种统一访问资源的方式。我们来看一下Go里面如何解析URL。
13 |package main
14 |
15 | import "fmt"
16 | import "net/url"
17 | import "strings"
18 |
19 | func main() {
20 |
21 | // 我们将解析这个URL,它包含了模式,验证信息,
22 | // 主机,端口,路径,查询参数和查询片段
23 | s := "postgres://user:pass@host.com:5432/path?k=v#f"
24 |
25 | // 解析URL,并保证没有错误
26 | u, err := url.Parse(s)
27 | if err != nil {
28 | panic(err)
29 | }
30 |
31 | // 可以直接访问解析后的模式
32 | fmt.Println(u.Scheme)
33 |
34 | // User包含了所有的验证信息,使用
35 | // Username和Password来获取单独的信息
36 | fmt.Println(u.User)
37 | fmt.Println(u.User.Username())
38 | p, _ := u.User.Password()
39 | fmt.Println(p)
40 |
41 | // Host包含了主机名和端口,如果需要可以
42 | // 手动分解主机名和端口
43 | fmt.Println(u.Host)
44 | h := strings.Split(u.Host, ":")
45 | fmt.Println(h[0])
46 | fmt.Println(h[1])
47 |
48 | // 这里我们解析出路径和`#`后面的片段
49 | fmt.Println(u.Path)
50 | fmt.Println(u.Fragment)
51 |
52 | // 为了得到`k=v`格式的查询参数,使用RawQuery。你可以将
53 | // 查询参数解析到一个map里面。这个map为字符串作为key,
54 | // 字符串切片作为value。
55 | fmt.Println(u.RawQuery)
56 | m, _ := url.ParseQuery(u.RawQuery)
57 | fmt.Println(m)
58 | fmt.Println(m["k"][0])
59 | }
60 |
61 | 运行结果
62 |postgres
63 | user:pass
64 | user
65 | pass
66 | host.com:5432
67 | host.com
68 | 5432
69 | /path
70 | f
71 | k=v
72 | map[k:[v]]
73 | v
74 |
75 |
76 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_variable.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Go是静态类型语言,变量是有明确类型的。编译器会检查函数调用中,变量类型的正确性。
13 |使用var
关键字来定义变量。
Go 的基本类型有:
15 |看看下面的例子
26 |package main
27 |
28 | import "fmt"
29 |
30 | func main() {
31 | // `var` 关键字用来定义一个或者多个变量
32 | var a string = "initial"
33 | fmt.Println(a)
34 |
35 | // 你一次可以定义多个变量
36 | var b, c int = 1, 2
37 | fmt.Println(b, c)
38 |
39 | // Go会推断出具有初始值的变量的类型
40 | var d = true
41 | fmt.Println(d)
42 |
43 | //定义变量时,没有给出初始值的变量被默认初始化为零值
44 | //整型的零值就是0
45 | var e int
46 | fmt.Println(e)
47 |
48 | //":=" 语法是同时定义和初始化变量的快捷方式
49 | f := "short"
50 | fmt.Println(f)
51 | }
52 |
53 | 输出结果为
54 |initial
55 | 1 2
56 | true
57 | 0
58 | short
59 |
60 |
61 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_variable_argument_list.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 支持可变长参数列表的函数可以支持任意个传入参数,比如fmt.Println函数就是一个支持可变长参数列表的函数。
13 |package main
14 |
15 | import "fmt"
16 |
17 | // 这个函数可以传入任意数量的整型参数
18 | func sum(nums ...int) {
19 | fmt.Print(nums, " ")
20 | total := 0
21 | for _, num := range nums {
22 | total += num
23 | }
24 | fmt.Println(total)
25 | }
26 |
27 | func main() {
28 |
29 | // 支持可变长参数的函数调用方法和普通函数一样
30 | // 也支持只有一个参数的情况
31 | sum(1, 2)
32 | sum(1, 2, 3)
33 |
34 | // 如果你需要传入的参数在一个切片中,像下面一样
35 | // "func(slice...)"把切片打散传入
36 | nums := []int{1, 2, 3, 4}
37 | sum(nums...)
38 | }
39 |
40 | 输出结果为
41 |[1 2] 3
42 | [1 2 3] 6
43 | [1 2 3 4] 10
44 |
45 | 需要注意的是,可变长参数应该是函数定义的最右边的参数,即最后一个参数。
46 | 47 | -------------------------------------------------------------------------------- /GolangStudy/tutorial/go_by_example/go_working_pool.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |在这个例子中,我们来看一下如何使用gorouotine和channel来实现工作池。
13 |package main
14 |
15 | import "fmt"
16 | import "time"
17 |
18 | // 我们将在worker函数里面运行几个并行实例,这个函数从jobs通道
19 | // 里面接受任务,然后把运行结果发送到results通道。每个job我们
20 | // 都休眠一会儿,来模拟一个耗时任务。
21 | func worker(id int, jobs <-chan int, results chan<- int) {
22 | for j := range jobs {
23 | fmt.Println("worker", id, "processing job", j)
24 | time.Sleep(time.Second)
25 | results <- j * 2
26 | }
27 | }
28 |
29 | func main() {
30 |
31 | // 为了使用我们的工作池,我们需要发送工作和接受工作的结果,
32 | // 这里我们定义两个通道,一个jobs,一个results
33 | jobs := make(chan int, 100)
34 | results := make(chan int, 100)
35 |
36 | // 这里启动3个worker协程,一开始的时候worker阻塞执行,因为
37 | // jobs通道里面还没有工作任务
38 | for w := 1; w <= 3; w++ {
39 | go worker(w, jobs, results)
40 | }
41 |
42 | // 这里我们发送9个任务,然后关闭通道,告知任务发送完成
43 | for j := 1; j <= 9; j++ {
44 | jobs <- j
45 | }
46 | close(jobs)
47 |
48 | // 然后我们从results里面获得结果
49 | for a := 1; a <= 9; a++ {
50 | <-results
51 | }
52 |
53 | 运行结果
54 |worker 1 processing job 1
55 | worker 2 processing job 2
56 | worker 3 processing job 3
57 | worker 1 processing job 4
58 | worker 3 processing job 5
59 | worker 2 processing job 6
60 | worker 1 processing job 7
61 | worker 3 processing job 8
62 | worker 2 processing job 9
63 |
64 |
65 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_by_example/go_write_file.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Go将数据写入文件的方法和上面介绍过的读取文件的方法很类似。
13 |package main
14 |
15 | import (
16 | "bufio"
17 | "fmt"
18 | "io/ioutil"
19 | "os"
20 | )
21 |
22 | func check(e error) {
23 | if e != nil {
24 | panic(e)
25 | }
26 | }
27 |
28 | func main() {
29 |
30 | // 首先看一下如何将一个字符串写入文件
31 | d1 := []byte("hello\ngo\n")
32 | err := ioutil.WriteFile("/tmp/dat1", d1, 0644)
33 | check(err)
34 |
35 | // 为了实现细颗粒度的写入,打开文件后再写入
36 | f, err := os.Create("/tmp/dat2")
37 | check(err)
38 |
39 | // 在打开文件后通常应该立刻使用defer来调用
40 | // 打开文件的Close方法,以保证main函数结束
41 | // 后,文件关闭
42 | defer f.Close()
43 |
44 | // 你可以写入字节切片
45 | d2 := []byte{115, 111, 109, 101, 10}
46 | n2, err := f.Write(d2)
47 | check(err)
48 | fmt.Printf("wrote %d bytes\n", n2)
49 |
50 | // 也可以使用`WriteString`直接写入字符串
51 | n3, err := f.WriteString("writes\n")
52 | fmt.Printf("wrote %d bytes\n", n3)
53 |
54 | // 调用Sync方法来将缓冲区数据写入磁盘
55 | f.Sync()
56 |
57 | // `bufio`除了提供上面的缓冲读取数据外,还
58 | // 提供了缓冲写入数据的方法
59 | w := bufio.NewWriter(f)
60 | n4, err := w.WriteString("buffered\n")
61 | fmt.Printf("wrote %d bytes\n", n4)
62 |
63 | // 使用Flush方法确保所有缓冲区的数据写入底层writer
64 | w.Flush()
65 | }
66 |
67 | 运行结果
68 |wrote 5 bytes
69 | wrote 7 bytes
70 | wrote 9 bytes
71 |
72 |
73 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_tutorial/default.css:
--------------------------------------------------------------------------------
1 | h1,h2,h3,h4,h5,h6,p,blockquote{margin:0;padding:0}body{font-family:"Helvetica Neue",Helvetica,"Hiragino Sans GB",Arial,sans-serif;font-size:14px;line-height:18px;color:#737373;background-color:white;margin:10px 13px 10px 13px}table{margin:10px 0 15px 0;border-collapse:collapse}td,th{border:1px solid #ddd;padding:3px 10px}th{padding:5px 10px}a{color:#0069d6}a:hover{color:#0050a3;text-decoration:none}a img{border:0}p{margin-bottom:9px}h1,h2,h3,h4,h5,h6{color:#404040;line-height:36px}h1{margin-bottom:18px;font-size:30px}h2{font-size:24px}h3{font-size:18px}h4{font-size:16px}h5{font-size:14px}h6{font-size:13px}hr{margin:0 0 19px;border:0;border-bottom:1px solid #ccc}blockquote{padding:13px 13px 21px 15px;margin-bottom:18px;font-family:georgia,serif;font-style:italic}blockquote:before{content:"\201C";font-size:40px;margin-left:-10px;font-family:georgia,serif;color:#eee}blockquote p{font-size:14px;font-weight:300;line-height:18px;margin-bottom:0;font-style:italic}code,pre{font-family:Monaco,Andale Mono,Courier New,monospace}code{background-color:#fee9cc;color:rgba(0,0,0,0.75);padding:1px 3px;font-size:12px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}pre{display:block;padding:5px;line-height:16px;font-size:11px;white-space:pre-wrap;word-wrap:break-word;border:1px solid #23241f;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}pre code{color:#737373;font-size:11px;padding:0}sup{font-size:.83em;vertical-align:super;line-height:0}*{-webkit-print-color-adjust:exact}@media screen and (min-width:914px){body{width:854px;margin:10px auto}}@media print{body,code,pre code,h1,h2,h3,h4,h5,h6{color:black}table,pre{page-break-inside:avoid}}pre{background:#23241f}pre code{display:block;background:#23241f;padding:0}pre .tag,pre code{color:#f8f8f2}pre .keyword,pre .function,pre .literal,pre .change,pre .winutils,pre .flow,pre .lisp .title,pre .clojure .built_in,pre .nginx .title,pre .tex .special{color:#66d9ef}pre .variable,pre .params{color:#fd9720}pre .constant{color:#66d9ef}pre .title,pre .class .title,pre .css .class{color:#a6e22e}pre .attribute,pre .symbol,pre .symbol .string,pre .tag .title,pre .value,pre .css .tag{color:#f92672}pre .number,pre .preprocessor,pre .pragma,pre .regexp{color:#ae81ff}pre .tag .value,pre .string,pre .css .id,pre .subst,pre .haskell .type,pre .ruby .class .parent,pre .built_in,pre .sql .aggregate,pre .django .template_tag,pre .django .variable,pre .smalltalk .class,pre .django .filter .argument,pre .smalltalk .localvars,pre .smalltalk .array,pre .attr_selector,pre .pseudo,pre .addition,pre .stream,pre .envvar,pre .apache .tag,pre .apache .cbracket,pre .tex .command,pre .prompt{color:#e6db74}pre .comment,pre .javadoc,pre .java .annotation,pre .python .decorator,pre .template_comment,pre .pi,pre .doctype,pre .deletion,pre .shebang,pre .apache .sqbracket,pre .tex .formula{color:#a6e22d;}pre .coffeescript .javascript,pre .javascript .xml,pre .tex .formula{opacity:.5}pre .xml .javascript,pre .xml .vbscript,pre .xml .css,pre .xml .cdata{opacity:.5}
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_tutorial/go_tutorial_10_use_package_test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Go天生就是为了支持良好的项目管理体验而设计的。
14 | 15 |包
16 | 17 |在软件工程的实践中,我们会遇到很多功能重复的代码,比如去除字符串首尾的空格。高质量软件产品的特点就是它的部分代码是可以重用的,比如你不必每次写个函数去去除字符串首尾的空格。
18 | 19 |我们上面讲过变量,结构体,接口和函数等,事实上所谓的包,就是把一些用的多的这些变量,结构体,接口和函数等统一放置在一个逻辑块中。并且给它们起一个名字,这个名字就叫做包名。
20 | 21 |例如我们上面用的最多的fmt包,这个包提供了很多格式化输出的函数,你可以在自己的代码中引用这个包,来做格式化输出,而不用你自己每次去写个函数。一门成熟的语言都会提供齐全的基础功能包供人调用。
22 | 23 |使用包有三个好处
24 | 25 |创建包
33 | 34 |我们现在来举个例子,用来演示Go的项目管理。
35 | 36 |首先我们在目录/Users/jemy/JemyGraw/GoLang
下面创建文件夹pkg_demo
。然后在pkg_demo
里面创建src
文件夹
37 | 。然后再在main
文件夹里面创建main.go
文件。另外为了演示包的创建,我们在src
目录下面创建文件夹net.duokr
,然后再在net.duokr
文件夹里面创建math
文件夹,这个文件夹名称就是这个文件夹下面go文件的包名称。然后我们再创建一个math_pkg.go
文件,之所以取这个名字而不是math.go
只是为了说明这个文件名称和包名不需要一致。然后我们还创建了一个math_pkg_test.go
文件作为包的测试用例文件。整体结构如下:
.
40 | └── src
41 | ├── main
42 | │ ├── build.sh
43 | │ └── main.go
44 | └── net.duokr
45 | └── math
46 | ├── math_pkg.go
47 | └── math_pkg_test.go
48 |
49 |
50 | 其中build.sh是我们为了编译这个项目而写的脚本,因为编译项目需要几条命令,把它写在脚本文件中方便使用。另外为了能够让build.sh能够执行,使用chmod +x build.sh
为它赋予可执行权限。build.bat是Windows下面的编译脚本。
51 | 我们来看一下math_pkg.go
的定义:
package math
54 |
55 | func Add(a, b int) int {
56 | return a + b
57 | }
58 | func Subtract(a, b int) int {
59 | return a - b
60 | }
61 | func Multiply(a, b int) int {
62 | return a * b
63 | }
64 |
65 | func Divide(a, b int) int {
66 | if b == 0 {
67 | panic("Can not divided by zero")
68 | }
69 | return a / b
70 | }
71 |
72 |
73 | 首先是包名,然后是几个函数定义,这里我们会发现这些函数定义首字母都是大写
,Go规定了只有首字母大写的函数才能从包导出使用,即其他调用这个包中函数的代码只能调用那些导出的函数
。
我们再看一下main.go
的定义:
package main
78 |
79 | import (
80 | "fmt"
81 | math "net.duokr/math"
82 | )
83 |
84 | func main() {
85 | var a = 100
86 | var b = 200
87 |
88 | fmt.Println("Add demo:", math.Add(a, b))
89 | fmt.Println("Substract demo:", math.Subtract(a, b))
90 | fmt.Println("Multiply demo:", math.Multiply(a, b))
91 | fmt.Println("Divide demo:", math.Divide(a, b))
92 | }
93 |
94 |
95 | 在main.go里面,我们使用import关键字引用我们自定义的包math,引用的方法是从main包平行的文件夹net.duokr开始,后面跟上包名math。这里面我们给这个长长的包名起了一个别名就叫math。然后分别调用math包里面的函数。
96 | 97 |最后我们看一下我们的编译脚本:
98 | 99 |export GOPATH=$GOPATH:/Users/jemy/JemyGraw/GoLang/pkg_demo
100 | export GOBIN=/Users/jemy/JemyGraw/GoLang/pkg_demo/bin
101 | go build net.duokr/math
102 | go build main.go
103 | go install main
104 |
105 |
106 | 第一行,我们将项目路径加入GOPATH中,这样待会儿编译main.go的时候才能找到我们自定义的包;
107 | 108 |第二行,我们设置本项目的安装目录,第五行的命令将编译好的文件放到这个目录下面;
109 | 110 |第三行,我们编译我们的自定义包;
111 | 112 |第四行,我们编译我们main.go文件;
113 | 114 |第五行,将编译好的文件安装到指定目录下。
115 | 116 |这里还有一个Windows下面的编译脚本build.bat:
117 | 118 |@echo off
119 | set GOPATH=GOPATH;C:\JemyGraw\GoLang\pkg_demo
120 | set GOBIN=C:\JemyGraw\GoLang\pkg_demo\bin
121 | go build net.duokr\math
122 | go build main.go
123 | go install main
124 |
125 |
126 | 好了,运行脚本编译一下,在main文件夹和bin文件夹下面都会生成一个可执行文件。
127 | 128 |这个时候文件夹结构为:
129 | 130 |.
131 | ├── bin
132 | │ └── main
133 | ├── pkg
134 | │ └── darwin_386
135 | │ └── net.duokr
136 | │ └── math.a
137 | └── src
138 | ├── main
139 | │ ├── build.bat
140 | │ ├── build.sh
141 | │ ├── main
142 | │ └── main.go
143 | └── net.duokr
144 | └── math
145 | ├── math_pkg.go
146 | └── math_pkg_test.go
147 |
148 |
149 | 运行一下,输出结果为:
150 | 151 |Add demo: 300
152 | Substract demo: -100
153 | Multiply demo: 20000
154 | Divide demo: 0
155 |
156 |
157 | 好了,包的使用介绍完毕,我们再来看一下测试用例怎么写。
158 | 159 |测试
160 | 161 |在上面的例子中,我们发现我们自定义的包下面还有一个math_pkg_test.go文件,这个文件包含了本包的一些测试用例。而且Go会把以_test.go
结尾的文件当作是测试文件。
测试怎么写,当然是用assert来判断程序的运行结果是否和预期的相同了。
164 | 165 |我们来看看这个math包的测试用例。
166 | 167 |package math
168 |
169 | import (
170 | "testing"
171 | )
172 |
173 | func TestAdd(t *testing.T) {
174 | var a = 100
175 | var b = 200
176 |
177 | var val = Add(a, b)
178 | if val != a+b {
179 | t.Error("Test Case [", "TestAdd", "] Failed!")
180 | }
181 | }
182 |
183 | func TestSubtract(t *testing.T) {
184 | var a = 100
185 | var b = 200
186 |
187 | var val = Subtract(a, b)
188 | if val != a-b {
189 | t.Error("Test Case [", "TestSubtract", "] Failed!")
190 | }
191 | }
192 |
193 | func TestMultiply(t *testing.T) {
194 | var a = 100
195 | var b = 200
196 |
197 | var val = Multiply(a, b)
198 | if val != a*b {
199 | t.Error("Test Case [", "TestMultiply", "] Failed!")
200 | }
201 | }
202 |
203 | func TestDivideNormal(t *testing.T) {
204 | var a = 100
205 | var b = 200
206 |
207 | var val = Divide(a, b)
208 | if val != a/b {
209 | t.Error("Test Case [", "TestDivideNormal", "] Failed!")
210 | }
211 | }
212 |
213 |
214 | 将路径切换到测试文件所在目录,运行go test
命令,go会自动测试所有的测试用例。
在上面的例子中,测试用例的特点是以函数名以Test
开始,而且具有唯一参数t *testing.T
。
小结
219 | 220 |Go提供的包和用例测试是构建优秀的软件产品的基础,只要我们不断学习,努力去做,一定可以做的很好。
221 | 222 | -------------------------------------------------------------------------------- /GolangStudy/tutorial/go_tutorial/go_tutorial_1_how_to_install_go.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |首先来谈谈Go语言的安装,要使用Go来编写程序首先得把环境搭建起来。 16 | Go的语言环境搭建还是比较简单的。Google提供了Windows和Mac的安装包,所以去下载一下安装就可以了。 17 | 对于Linux的系统,可以使用系统提供的包安装工具来安装。
18 | 19 |Go的下载地址
20 | 21 |https://code.google.com/p/go/downloads/list
22 | 23 |Windows
24 | 25 |对于Windows系统,Go提供了两种不同的安装包,分别对应32位的系统和64位的系统,安装的时候根据自己的系统实际情况选择下载包。Windows下面提供的是msi格式的安装包,这种包是可执行文件,直接双击安装就可以了。安装完成之后,安装程序会自动地将安装完的Go的根目录下的bin目录加入系统的PATH环境变量里面。所以直接打开命令行,输入go,就可以看到一些提示信息了。
26 | 27 |Mac
28 | 29 |如果是新买的Mac,里面可能自带了一个go的可执行文件,在路径/etc/paths.d/
下面,就是一个go可执行文件。如果我们需要安装从官网下载的dmg安装包,先要把这个文件删除掉。可以用sudo rm /etc/paths.d/go
来删除。然后自动安装dmg之后,要使用export PATH
的方法将安装好的Go目录下面的bin目录加入PATH中。一般安装完之后路径为/usr/local/go
,所以你可以用下面的方法:
30 | 首先切换到自己的用户目录
cd ~
33 |
34 |
35 | 然后
36 | 37 |vim .profile
38 |
39 |
40 | 加入一行
41 | 42 |export PATH=/usr/local/go/bin:$PATH
43 |
44 |
45 | 就可以了。
46 | 47 |Linux
48 | 49 |Linux的发行版有很多,可以根据不同系统提供的包管理工具来安装Go,不过可能系统包管理工具提供的不是最新的Go版本。在这种情况下,你可以去下载最新的tar包。 50 | 然后使用下面的方法
51 | 52 |sudo tar -C /usr/local -xzf go1.2.linux-386.tar.gz
53 |
54 |
55 | 如果是64位的系统,用下面的方法
56 | 57 |sudo tar -C /usr/local -xzf go1.2.linux-amd64.tar.gz
58 |
59 |
60 | 当然,这样的方式只是将安装包解压拷贝到/usr/local/
下面。你还需要使用export PATH
的方式将Go的bin目录加入PATH。
61 | 方法和上面Mac介绍的一样。
62 | 另外如果你不是将Go安装到/usr/local
目录下面,你还需要设置一个GOROOT环境变量。比如你安装到你自己的文件夹下面,比如叫jemy的用户的路径是/home/jemy
,那么你安装到这个目录的Go路径为/home/jemy/go
,那么在export PATH
之前,你还需要使用下面的命令。
export GOROOT=/home/jemy/go
65 |
66 |
67 | 总结一下,如果你默认安装路径为/usr/local/go
,那么只需要用
export PATH=$PATH:/usr/local/go/bin
70 |
71 |
72 | 就可以了。 73 | 如果不是默认路径则需要这样
74 | 75 |export GOROOT=/home/jemy/go
76 | export PATH=$PATH:/$GROOT/bin
77 |
78 |
79 | 上面的/home/jemy
是根据实际安装的路径情况来确定。
最后说一下go的最基本的三个命令
82 | 83 |1.查看版本号
84 | 85 |go version
86 |
87 |
88 | 结果为
89 | 90 |duokr:~ jemy$ go version
91 | go version go1.2 darwin/386
92 |
93 |
94 | 2.格式化go代码文件
95 | 96 |go fmt file_name.go
97 |
98 |
99 | 3.运行单个go代码文件
100 | 101 |go run file_name.go
102 |
103 |
104 | 生
死
hello world
学习计算机的, 绕不开的三件事。
109 | 110 |有谁安装好语言环境,不试一下hello world的?
111 | 112 |//main包, 凡是标注为main包的go文件都会被编译为可执行文件
113 | package main
114 |
115 | //导入需要使用的包
116 | import (
117 | "fmt" //支持格式化输出的包,就是format的简写
118 | )
119 |
120 | //主函数,程序执行入口
121 | func main() {
122 | /*
123 | 输出一行hello world
124 | Println函数就是print line的意思
125 | */
126 | fmt.Println("hello world")
127 | }
128 |
129 |
130 | 然后使用go run helloworld.go
来运行这个例子。如果安装成功,那么会输出一行hello world
。
PS
133 | 134 |Windows7可以在文件所在目录下面使用Shift+右键,快速打开已定位到所在目录的命令行窗口。直接输入上面命令即可。
在自然界里面,有猫,有狗,有猪。有各种动物。每种动物都是不同的。
14 | 比如猫会喵喵叫,狗会旺旺叫,猪会哼哼叫。。。
15 | Stop!!!
16 | 好了,大家毕竟不是幼儿园的小朋友。介绍到这里就可以了。
论点就是每个东西都有自己归属的类别(Type)。
19 | 那么在Go语言里面,每个变量也都是有类别的,这种类别叫做数据类型(Data Type)
。
20 | Go的数据类型有两种:一种是语言内置的数据类型
,另外一种是通过语言提供的自定义数据类型方法自己定义的自定义数据类型
。
先看看语言内置的基础数据类型
数值型(Number)
25 | 26 |数值型有三种
,一种是整数类型
,另外一种是带小数的类型
(一般计算机里面叫做浮点数类型
),还有一种虚数类型
。
整数类型不用说了,和数学里面的是一样的。和数学里面不同的地方在于计算机里面正整数和零
统称为无符号整型
,而负整数
则称为有符号整型
。
Go的内置整型有uint8
, uint16
, uint32
, uint64
, int8
, int16
, int32
和int64
。其中u
开头的类型就是无符号整型
。无符号类型能够表示正整数和零。而有符号类型除了能够表示正整数和零外,还可以表示负整数。
31 | 另外还有一些别名类型,比如byte
类型,这个类型和uint8
是一样的,表示字节类型
。另外一个是rune类型
,这个类型和int32
是一样的,用来表示unicode的代码点
,就是unicode字符所对应的整数。
Go还定义了三个依赖系统
的类型,uint
,int
和uintptr
。因为在32位系统和64位系统上用来表示这些类型的位数是不一样的。
对于32位系统
36 | 37 |uint=uint32
38 | int=int32
39 | uintptr为32位的指针
对于64位系统
42 | 43 |uint=uint64
44 | int=int64
45 | uintptr为64位的指针
至于类型后面跟的数字8,16,32或是64则表示用来表示这个类型的位不同,位越多,能表示的整数范围越大
。
48 | 比如对于用N位来表示的整数,如果是有符号的整数
,能够表示的整数范围为-2^(N-1) ~ 2^(N-1)-1
;如果是无符号的整数
,则能表示的整数范围为0 ~ 2^N
。
Go的浮点数类型有两种,float32
和float64
。float32又叫单精度浮点型
,float64又叫做双精度浮点型
。其最主要的区别就是小数点后面能跟的小数位数不同
。
另外Go还有两个其他语言所没有的类型,虚数类型
。complex64
和complex128
。
对于数值类型,其所共有的操作为加法(+)
,减法(-)
,乘法(*)
和除法(/)
。另外对于整数类型
,还定义了求余运算(%)
求余运算为整型所独有。如果对浮点数使用求余,比如这样
57 | 58 |package main
59 |
60 | import (
61 | "fmt"
62 | )
63 |
64 | func main() {
65 | var a float64 = 12
66 | var b float64 = 3
67 |
68 | fmt.Println(a % b)
69 | }
70 |
71 |
72 | 编译时候会报错
73 | 74 |invalid operation: a % b (operator % not defined on float64)
75 |
76 |
77 | 所以,这里我们可以知道所谓的数据类型有两层意思
,一个是定义了该类型所能表示的数
,另一个是定义了该类型所能进行的操作
。
78 | 简单地说,对于一只小狗,你能想到的一定是狗的面貌和它会汪汪叫,而不是猫的面容和喵喵叫。
字符串类型(String)
81 | 82 |字符串就是一串固定长度的字符连接起来的字符序列。Go的字符串是由单个字节
连接起来的。(对于汉字,通常由多个字节组成)。这就是说,传统的字符串是由字符组成的,而Go的字符串不同
,是由字节组成
的。这一点需要注意。
字符串的表示很简单。用(双引号"")或者(``号)来描述。
85 | 86 |"hello world"
87 |
88 |
89 | 或者
90 | 91 |`hello world`
92 |
93 |
94 | 唯一的区别是,双引号之间的转义字符会被转义,而``号之间的转义字符保持原样不变。
95 | 96 |package main
97 |
98 | import (
99 | "fmt"
100 | )
101 |
102 | func main() {
103 | var a = "hello \n world"
104 | var b = `hello \n world`
105 |
106 | fmt.Println(a)
107 | fmt.Println("----------")
108 | fmt.Println(b)
109 | }
110 |
111 |
112 | 输出结果为
113 | 114 |hello
115 | world
116 | ----------
117 | hello \n world
118 |
119 |
120 | 字符串所能进行的一些基本操作包括:
121 | (1)获取字符长度
122 | (2)获取字符串中单个字节
123 | (3)字符串连接
package main
126 |
127 | import (
128 | "fmt"
129 | )
130 |
131 | func main() {
132 | var a string = "hello"
133 | var b string = "world"
134 |
135 | fmt.Println(len(a))
136 | fmt.Println(a[1])
137 | fmt.Println(a + b)
138 | }
139 |
140 |
141 | 输出如下
142 | 143 |5
144 | 101
145 | helloworld
146 |
147 |
148 | 这里我们看到a[1]得到的是一个整数,这就证明了上面"Go的字符串是由字节组成的这句话"
。我们还可以再验证一下。
package main
151 |
152 | import (
153 | "fmt"
154 | )
155 |
156 | func main() {
157 | var a string = "你"
158 | var b string = "好"
159 | fmt.Println(len(a))
160 | fmt.Println(len(b))
161 | fmt.Println(len(a + b))
162 | fmt.Println(a[0])
163 | fmt.Println(a[1])
164 | fmt.Println(a[2])
165 | }
166 |
167 |
168 | 输出
169 | 170 |3
171 | 3
172 | 6
173 | 228
174 | 189
175 | 160
176 |
177 |
178 | 我们开始的时候,从上面的三行输出知道,"你"和"好"分别是用三个字节组成的。我们依次获取a的三个字节,输出,得到结果。
179 | 180 |布尔型(Bool)
181 | 182 |布尔型是表示真值
和假值
的类型。可选值为true
和false
。
所能进行的操作如下:
185 | && and 与
186 | || or 或
187 | ! not 非
Go的布尔型取值就是true
或false
。任何空值(nil)或者零值(0, 0.0, "")都不能作为布尔型来直接判断
。
package main
192 |
193 | import (
194 | "fmt"
195 | )
196 |
197 | func main() {
198 | var equal bool
199 | var a int = 10
200 | var b int = 20
201 | equal = (a == b)
202 | fmt.Println(equal)
203 | }
204 |
205 |
206 | 输出结果
207 | 208 |false
209 |
210 |
211 | 下面是错误的用法
212 | 213 |package main
214 |
215 | import (
216 | "fmt"
217 | )
218 |
219 | func main() {
220 | if 0 {
221 | fmt.Println("hello world")
222 | }
223 | if nil {
224 | fmt.Println("hello world")
225 | }
226 | if "" {
227 | fmt.Println("hello world")
228 | }
229 | }
230 |
231 |
232 | 编译错误
233 | 234 |./t.go:8: non-bool 0 (type untyped number) used as if condition
235 | ./t.go:11: non-bool nil used as if condition
236 | ./t.go:14: non-bool "" (type untyped string) used as if condition
237 |
238 |
239 | 上面介绍的是Go语言内置的基础数据类型。
240 | 241 | -------------------------------------------------------------------------------- /GolangStudy/tutorial/go_tutorial/go_tutorial_3_variable.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |现在我们讨论一下Go语言的变量定义。
14 | 15 |变量定义
16 | 17 |所谓的变量就是一个拥有指定名称
和类型
的数据存储位置
。
18 | 在上面我们使用过变量的定义,现在我们来仔细看一个例子。
package main
21 |
22 | import (
23 | "fmt"
24 | )
25 |
26 | func main() {
27 | var x string = "hello world"
28 | fmt.Println(x)
29 | }
30 |
31 |
32 | 变量的定义首先使用var
关键字,然后指定变量的名称x
,再指定变量的类型string
,在本例中,还对变量x
进行了赋值,然后在命令行输出该变量。Go这种变量定义的方式和其他的语言有些不同,但是在使用的过程中,你会逐渐喜欢的。当然上面的变量定义方式还可以如下,即先定义变量,再赋值。
package main
35 |
36 | import (
37 | "fmt"
38 | )
39 |
40 | func main() {
41 | var x string
42 | x = "hello world"
43 | fmt.Println(x)
44 | }
45 |
46 |
47 | 或者是直接赋值,让Go语言推断变量的类型。如下:
48 | 49 |package main
50 |
51 | import (
52 | "fmt"
53 | )
54 |
55 | func main() {
56 | var x = "hello world"
57 | fmt.Println(x)
58 | }
59 |
60 |
61 | 当然,上面变量的定义还有一种快捷方式
。如果你知道变量的初始值,完全可以像下面这样定义变量,完全让Go来推断语言的类型
。这种定义的方式连关键字var
都省略掉了。
package main
64 |
65 | import (
66 | "fmt"
67 | )
68 |
69 | func main() {
70 | x := "hello world"
71 | fmt.Println(x)
72 | }
73 |
74 |
75 | 注意:上面这种使用:=
方式定义变量的方式只能用在函数内部
。
package main
78 |
79 | import (
80 | "fmt"
81 | )
82 |
83 | x:="hello world"
84 | func main() {
85 | y := 10
86 | fmt.Println(x)
87 | fmt.Println(y)
88 | }
89 |
90 |
91 | 对于上面的变量定义x是无效的。会导致编译错误:
92 | 93 |./test_var_quick.go:7: non-declaration statement outside function body
94 |
95 |
96 | 不过我们对上面的例子做下修改,比如这样是可以的。也就是使用var关键字定义的时候,如果给出初始值,就不需要显式指定变量类型。
97 | 98 |package main
99 |
100 | import (
101 | "fmt"
102 | )
103 |
104 | var x = "hello world"
105 |
106 | func main() {
107 | y := 10
108 | fmt.Println(x)
109 | fmt.Println(y)
110 | }
111 |
112 |
113 | 变量
之所以称为变量,就是因为它们的值在程序运行过程中可以发生变化
,但是它们的变量类型是无法改变的
。因为Go语言是静态语言
,并不支持
程序运行过程中变量类型发生变化
。比如如果你强行将一个字符串值赋值给定义为int的变量,那么会发生编译错误。即使是强制类型转换也是不可以的。`强制类型转换只支持同类的变量类型``。比如数值类型之间强制转换。
下面我们看几个例子:
116 | 117 |package main
118 |
119 | import (
120 | "fmt"
121 | )
122 |
123 | func main() {
124 | var x string = "hello world"
125 | fmt.Println(x)
126 | x = "i love go language"
127 | fmt.Println(x)
128 | }
129 |
130 |
131 | 本例子演示变量的值在程序运行过程中发生变化,结果输出为
132 | 133 |hello world
134 | i love go language
135 |
136 |
137 | 我们尝试不同类型的变量之间转换
138 | 139 |package main
140 |
141 | import (
142 | "fmt"
143 | )
144 |
145 | func main() {
146 | var x string = "hello world"
147 | fmt.Println(x)
148 | x = 11
149 | fmt.Println(x)
150 | }
151 |
152 |
153 | 在本例子中,如果试图将一个数值赋予字符串变量x,那么会发生错误:
154 | 155 |./test_var.go:10: cannot use 11 (type int) as type string in assignment
156 |
157 |
158 | 上面的意思就是无法将整型数值11当作字符串赋予给字符串变量。
159 | 160 |但是同类的变量之间是可以强制转换的,如浮点型和整型之间的转换。
161 | 162 |package main
163 |
164 | import (
165 | "fmt"
166 | )
167 |
168 | func main() {
169 | var x float64 = 32.35
170 | fmt.Println(x)
171 | fmt.Println(int(x))
172 | }
173 |
174 |
175 | 输出的结果为
176 | 177 |32.35
178 | 32
179 |
180 |
181 | 变量命名
182 | 183 |上面我们看了一些变量的使用方法,那么定义一个变量名称,有哪些要求呢?
184 | 这里我们要注意,Go的变量名称必须以字母或下划线(_)开头,后面可以跟字母,数字,或者下划线(_)
。除此之外,Go语言并不关心你如何定义变量。我们通用的做法是定义一个用户友好的变量。假设你需要定义一个狗狗的年龄,那么使用dog_age作为变量名称要好于用x来定义变量。
变量作用域
187 | 188 |现在我们再来讨论一下变量的作用域。所谓作用域就是可以有效访问变量的区域。比如很简单的,你不可能在一个函数func_a里面访问另一个函数func_b里面定义的局部变量x。所以变量的作用域目前分为两类,一个是全局变量
,另一个是局部变量
。下面我们看个全局变量的例子:
package main
191 |
192 | import (
193 | "fmt"
194 | )
195 |
196 | var x string = "hello world"
197 |
198 | func main() {
199 | fmt.Println(x)
200 | }
201 |
202 |
203 | 这里变量x定义在main函数之外,但是main函数仍然可以访问x。全局变量的作用域是该包中所有的函数。
204 | 205 |package main
206 |
207 | import (
208 | "fmt"
209 | )
210 |
211 | var x string = "hello world"
212 |
213 | func change() {
214 | x = "i love go"
215 | }
216 | func main() {
217 | fmt.Println(x)
218 | change()
219 | fmt.Println(x)
220 | }
221 |
222 |
223 | 在上面的例子用,我们用了change函数改变了x的值。输出结果如下:
224 | 225 |hello world
226 | i love go
227 |
228 |
229 | 我们再看一下局部变量的例子。
230 | 231 |package main
232 |
233 | import (
234 | "fmt"
235 | )
236 |
237 | func change() {
238 | x := "i love go"
239 | }
240 | func main() {
241 | fmt.Println(x)
242 | }
243 |
244 |
245 | 该例子中main函数试图访问change函数中定义的局部变量x,结果发生了下面的错误(未定义的变量x):
246 | 247 |./test_var.go:11: undefined: x
248 |
249 |
250 | 常量
251 | 252 |Go语言也支持常量定义。所谓常量就是在程序运行过程中保持值不变的变量定义
。常量的定义和变量类似,只是用const
关键字替换了var关键字,另外常量在定义的时候必须有初始值
。
package main
255 |
256 | import (
257 | "fmt"
258 | )
259 |
260 | func main() {
261 | const x string = "hello world"
262 | const y = "hello world"
263 | fmt.Println(x)
264 | fmt.Println(y)
265 | }
266 |
267 |
268 | 这里有一点需要注意,变量定义的类型推断方式:=
不能够用来定义常量。因为常量的值是在编译的时候就已经确定的,但是变量的值则是运行的时候才使用的。这样常量定义就无法使用变量类型推断的方式了。
常量的值在运行过程中是无法改变的,强制改变常量的值是无效的。
271 | 272 |package main
273 |
274 | import (
275 | "fmt"
276 | )
277 |
278 | func main() {
279 | const x string = "hello world"
280 | fmt.Println(x)
281 | x = "i love go language"
282 | fmt.Println(x)
283 | }
284 |
285 |
286 | 比如上面的例子就会报错
287 | 288 |./test_var.go:10: cannot assign to x
289 |
290 |
291 | 我们再看一个Go包math里面定义的常量Pi,用它来求圆的面积。
292 | 293 |package main
294 |
295 | import (
296 | "fmt"
297 | "math"
298 | )
299 |
300 | func main() {
301 | var radius float64 = 10
302 | var area = math.Pow(radius, 2) * math.Pi
303 | fmt.Println(area)
304 | }
305 |
306 |
307 | 多变量或常量定义
308 | 309 |Go还提供了一种同时定义多个变量或者常量
的快捷方式。
package main
312 |
313 | import (
314 | "fmt"
315 | )
316 |
317 | func main() {
318 | var (
319 | a int = 10
320 | b float64 = 32.45
321 | c bool = true
322 | )
323 | const (
324 | Pi float64 = 3.14
325 | True bool = true
326 | )
327 |
328 | fmt.Println(a, b, c)
329 | fmt.Println(Pi, True)
330 | }
331 |
332 |
333 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_tutorial/go_tutorial_4_control_structure.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 虽然剧透可耻,但是为了体现Go语言的设计简洁之处,必须要先剧透一下。
14 | 15 |Go语言的控制结构关键字只有
16 | 17 |if..else if..else
,for
和 switch
。
而且在Go中,为了避免格式化战争,对程序结构做了统一的强制的规定。看下下面的例子。
20 | 21 |请比较一下A程序和B程序的不同之处。
22 | 23 |A程序
24 | 25 |package main
26 |
27 | import (
28 | "fmt"
29 | )
30 |
31 | func main() {
32 | fmt.Println("hello world")
33 | }
34 |
35 |
36 | B程序
37 | 38 |package main
39 |
40 | import (
41 | "fmt"
42 | )
43 |
44 | func main()
45 | {
46 | fmt.Println("hello world")
47 | }
48 |
49 |
50 | 还记得我们前面的例子中,{}
的格式是怎么样的么?在上面的两个例子中只有A例的写法是对的。因为在Go语言中,强制了{}
的格式。如果我们试图去编译B程序,那么会发生如下的错误提示。
./test_format.go:9: syntax error: unexpected semicolon or newline before {
53 |
54 |
55 | if..else if..else
56 | 57 |if..else if..else 用来判断一个或者多个条件,然后根据条件的结果执行不同的程序块。举个简单的例子。
58 | 59 |package main
60 |
61 | import (
62 | "fmt"
63 | )
64 |
65 | func main() {
66 | var dog_age = 10
67 |
68 | if dog_age > 10 {
69 | fmt.Println("A big dog")
70 | } else if dog_age > 1 && dog_age <= 10 {
71 | fmt.Println("A small dog")
72 | } else {
73 | fmt.Println("A baby dog")
74 | }
75 | }
76 |
77 |
78 | 上面的例子判断狗狗的年龄如果(if)
大于10就是一个大狗;否则判断(else if)
狗狗的年龄是否小于等于10且大于1,这个时候狗狗是小狗狗。否则(else)
的话(就是默认狗狗的年龄小于等于1岁),那么狗狗是Baby狗狗。
在上面的例子中,我们还可以发现Go的if..else if..else语句的判断条件一般都不需要使用()
。当然如果你还是愿意写,也是对的。另外如果为了将某两个或多个条件绑定在一起判断的话,还是需要括号()
的。
比如下面的例子也是对的。
83 | 84 |package main
85 |
86 | import (
87 | "fmt"
88 | )
89 |
90 | func main() {
91 | const Male = 'M'
92 | const Female = 'F'
93 |
94 | var dog_age = 10
95 | var dog_sex = 'M'
96 |
97 | if (dog_age == 10 && dog_sex == 'M') {
98 | fmt.Println("dog")
99 | }
100 | }
101 |
102 |
103 | 但是如果你使用Go提供的格式化工具来格式化这段代码的话,Go会智能判断你的括号是否必须有,否则的话,会帮你去掉的。你可以试试。
104 | 105 |go fmt test_bracket.go
106 |
107 |
108 | 然后你会发现,咦?!果真被去掉了。
109 | 110 |另外因为每个判断条件的结果要么是true要么是false,所以可以使用&&
,||
来连接不同的条件。使用!
来对一个条件取反。
switch
113 | 114 |switch的出现是为了解决某些情况下使用if判断语句带来的繁琐之处。
115 | 116 |例如下面的例子:
117 | 118 |package main
119 |
120 | import (
121 | "fmt"
122 | )
123 |
124 | func main() {
125 | //score 为 [0,100]之间的整数
126 | var score int = 69
127 |
128 | if score >= 90 && score <= 100 {
129 | fmt.Println("优秀")
130 | } else if score >= 80 && score < 90 {
131 | fmt.Println("良好")
132 | } else if score >= 70 && score < 80 {
133 | fmt.Println("一般")
134 | } else if score >= 60 && score < 70 {
135 | fmt.Println("及格")
136 | } else {
137 | fmt.Println("不及格")
138 | }
139 | }
140 |
141 |
142 | 在上面的例子中,我们用if..else if..else来对分数进行分类。这个只是一般的情况下if判断条件的数量。如果if..else if..else的条件太多的话,我们可以使用switch来优化程序。比如上面的程序我们还可以这样写:
143 | 144 |package main
145 |
146 | import (
147 | "fmt"
148 | )
149 |
150 | func main() {
151 | //score 为 [0,100]之间的整数
152 | var score int = 69
153 |
154 | switch score / 10 {
155 | case 10:
156 | case 9:
157 | fmt.Println("优秀")
158 | case 8:
159 | fmt.Println("良好")
160 | case 7:
161 | fmt.Println("一般")
162 | case 6:
163 | fmt.Println("及格")
164 | default:
165 | fmt.Println("不及格")
166 | }
167 | }
168 |
169 |
170 | 关于switch的几点说明如下:
171 | 172 |(1) switch的判断条件可以为任何数据类型。
173 | 174 |package main
175 |
176 | import (
177 | "fmt"
178 | )
179 |
180 | func main() {
181 | var dog_sex = "F"
182 | switch dog_sex {
183 | case "M":
184 | fmt.Println("A male dog")
185 | case "F":
186 | fmt.Println("A female dog")
187 | }
188 | }
189 |
190 |
191 | (2) 每个case后面跟的是一个完整的程序块,该程序块不需要{}
,也不需要break结尾
,因为每个case都是独立的。
(3) 可以为switch提供一个默认选项default,在上面所有的case都没有满足的情况下,默认执行default后面的语句。
194 | 195 |for
196 | 197 |for用在Go语言的循环条件里面。比如说要你输出1...100之间的自然数。最笨的方法就是直接这样。
198 | 199 |package main
200 |
201 | import (
202 | "fmt"
203 | )
204 |
205 | func main() {
206 | fmt.Println(1)
207 | fmt.Println(2)
208 | ...
209 | fmt.Println(100)
210 | }
211 |
212 |
213 | 这个不由地让我想起一个笑话。
214 | 215 |216 | 217 |以前一个地主的儿子学习写字,只学了三天就把老师赶走了。因为在这三天里面他学写了一,二,三。他觉得写字真的太简单了,不就是画横线嘛。于是有一天老爹过寿,让他来记送礼的人名单。直到中午还没有记完,老爹很奇怪就去问他怎么了。他哭着说,“不知道这个人有什么毛病,姓什么不好,姓万”。
哈哈,回来继续。我们看到上面的例子也是如地主的儿子那样就不好了。所以,我们必须使用循环结构。我们用for的循环语句来实现上面的例子。
218 | 219 |package main
220 |
221 | import (
222 | "fmt"
223 | )
224 |
225 | func main() {
226 | var i int = 1
227 |
228 | for ; i <= 100; i++ {
229 | fmt.Println(i)
230 | }
231 | }
232 |
233 |
234 | 在上面的例子中,首先初始化变量i为1,然后在for循环里面判断是否小于等于100,如果是的话,输出i,然后再使用i++来将i的值自增1。上面的例子,还有一个更好的写法,就是将i的定义和初始化也放在for里面。如下:
235 | 236 |package main
237 |
238 | import (
239 | "fmt"
240 | )
241 |
242 | func main() {
243 | for i := 1; i <= 100; i++ {
244 | fmt.Println(i)
245 | }
246 | }
247 |
248 |
249 | 在Go里面没有提供while关键字,如果你怀念while的写法也可以这样:
250 | 251 |package main
252 |
253 | import (
254 | "fmt"
255 | )
256 |
257 | func main() {
258 | var i int = 1
259 |
260 | for i <= 100 {
261 | fmt.Println(i)
262 | i++
263 | }
264 | }
265 |
266 |
267 | 或许你会问,如果我要死循环呢?是不是for true
?呵呵,不用了,直接这样。
for{
270 | ...
271 | }
272 |
273 |
274 | 以上就是Go提供的全部控制流程了。
275 | 276 |再复习一下,Go只提供了:
277 | 278 |if
279 | 280 |if ...{
281 | ...
282 | }else if ...{
283 | ...
284 | }else{
285 | ...
286 | }
287 |
288 |
289 | switch
290 | 291 |switch(...){
292 | case ...:
293 | ...
294 | case ...:
295 | ...
296 | ...
297 |
298 | default:
299 | ...
300 | }
301 |
302 |
303 | for
304 | 305 |for ...; ...; ...{
306 | ...
307 | }
308 |
309 | for ...{
310 | ...
311 | }
312 |
313 | for{
314 | ...
315 | }
316 |
317 |
318 |
--------------------------------------------------------------------------------
/GolangStudy/tutorial/go_tutorial/go_tutorial_7_pointer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 不要害怕,Go的指针是好指针。
14 | 15 |定义
16 | 17 |所谓指针其实你可以把它想像成一个箭头,这个箭头指向(存储)一个变量的地址
。
因为这个箭头本身也需要变量来存储,所以也叫做指针变量。
20 | 21 |Go的指针不支持那些乱七八糟的指针移位
。它就表示一个变量的地址
。看看这个例子:
package main
24 |
25 | import (
26 | "fmt"
27 | )
28 |
29 | func main() {
30 | var x int
31 | var x_ptr *int
32 |
33 | x = 10
34 | x_ptr = &x
35 |
36 | fmt.Println(x)
37 | fmt.Println(x_ptr)
38 | fmt.Println(*x_ptr)
39 | }
40 |
41 |
42 | 上面例子输出x的值
,x的地址
和通过指针变量输出x的值
,而x_ptr就是一个指针变量
。
10
45 | 0xc084000038
46 | 10
47 |
48 |
49 | 认真理清楚这两个符号的意思。
50 | 51 |& 取一个变量的地址
* 取一个指针变量所指向的地址的值
考你一下,上面的例子中,如何输出x_ptr的地址呢?
56 | 57 |package main
58 |
59 | import (
60 | "fmt"
61 | )
62 |
63 | func main() {
64 | var x int
65 | var x_ptr *int
66 |
67 | x = 10
68 | x_ptr = &x
69 |
70 | fmt.Println(&x_ptr)
71 | }
72 |
73 |
74 | 此例看懂,指针就懂了。
75 | 76 |永远记住一句话,所谓指针就是一个指向(存储)特定变量地址的变量
。没有其他的特别之处。
再变态一下,看看这个:
79 | 80 |package main
81 |
82 | import (
83 | "fmt"
84 | )
85 |
86 | func main() {
87 | var x int
88 | var x_ptr *int
89 |
90 | x = 10
91 | x_ptr = &x
92 |
93 | fmt.Println(*&x_ptr)
94 | }
95 |
96 |
97 | 指针变量
,它指向(存储)x的地址
;取这个指针变量x_ptr的地址
,这里可以设想有另一个指针变量x_ptr_ptr(指向)存储
这个x_ptr指针的地址
;*x_ptr_ptr
就是取这个x_ptr_ptr指针变量
所指向(存储)
的地址所对应的变量的值
,也就是x_ptr的值
,也就是指针变量x_ptr指向(存储)的地址
,也就是x的地址
。 这里可以看到,其实*&
这两个运算符在一起就相互抵消作用了。用途
105 | 106 |指针的一大用途就是可以将变量的指针作为实参传递给函数,从而在函数内部能够直接修改实参所指向的变量值。
Go的变量传递都是值传递。
109 | 110 |package main
111 |
112 | import (
113 | "fmt"
114 | )
115 |
116 | func change(x int) {
117 | x = 200
118 | }
119 | func main() {
120 | var x int = 100
121 | fmt.Println(x)
122 | change(x)
123 | fmt.Println(x)
124 | }
125 |
126 |
127 | 上面的例子输出结果为
128 | 129 |100
130 | 100
131 |
132 |
133 | 很显然,change函数改变的
仅仅是内部变量x
的值
,而不会改变
传递进去的实参
。其实,也就是说Go的函数一般关心的是输出结果,而输入参数就相当于信使跑到函数门口大叫,你们这个参数是什么值,那个是什么值,然后就跑了。你函数根本就不能修改它的值。不过如果是传递的实参是指针变量,那么函数一看,小子这次你地址我都知道了,哪里跑。那么就是下面的例子:
package main
136 |
137 | import (
138 | "fmt"
139 | )
140 |
141 | func change(x *int) {
142 | *x = 200
143 | }
144 | func main() {
145 | var x int = 100
146 | fmt.Println(x)
147 | change(&x)
148 | fmt.Println(x)
149 | }
150 |
151 |
152 | 上面的例子中,change函数的虚参为整型指针变量
,所以在main中调用的时候传递的是x的地址
。然后在change里面使用*x=200
修改了这个x的地址的值。所以x的值就变了
。这个输出是:
100
155 | 200
156 |
157 |
158 | new
159 | 160 |new这个函数挺神奇,因为它的用处太多了。这里还可以通过new来初始化一个指针
。上面说过指针指向(存储)的是一个变量的地址,但是指针本身也需要地址存储。先看个例子:
package main
163 |
164 | import (
165 | "fmt"
166 | )
167 |
168 | func set_value(x_ptr *int) {
169 | *x_ptr = 100
170 | }
171 | func main() {
172 | x_ptr := new(int)
173 | set_value(x_ptr)
174 | //x_ptr指向的地址
175 | fmt.Println(x_ptr)
176 | //x_ptr本身的地址
177 | fmt.Println(&x_ptr)
178 | //x_ptr指向的地址值
179 | fmt.Println(*x_ptr)
180 | }
181 |
182 |
183 | 上面我们定义了一个x_ptr变量,然后用new申请
了一个存储整型数据的内存地址
,然后将这个地址赋值
给x_ptr指针变量
,也就是说x_ptr指向(存储)的是一个可以存储整型数据的地址
,然后用set_value函数将这个地址中存储的值
赋值为100。所以第一个输出是x_ptr指向的地址
,第二个则是x_ptr本身的地址
,而*x_ptr
则是x_ptr指向的地址中存储的整型数据的值
。
0xc084000040
186 | 0xc084000038
187 | 100
188 |
189 |
190 | 小结
191 | 192 |好了,现在用个例子再来回顾一下指针。
193 | 194 |交换两个变量的值。
195 | 196 |package main
197 |
198 | import (
199 | "fmt"
200 | )
201 |
202 | func swap(x, y *int) {
203 | *x, *y = *y, *x
204 | }
205 | func main() {
206 | x_val := 100
207 | y_val := 200
208 | swap(&x_val, &y_val)
209 | fmt.Println(x_val)
210 | fmt.Println(y_val)
211 | }
212 |
213 |
214 | 很简单吧,这里利用了Go提供的交叉赋值
的功能,另外由于是使用了指针作为参数,所以在swap函数内,x_val和y_val的值就被交换了。