Compare commits

..

1 Commits

Author SHA1 Message Date
ShaoBo Wan(无极) 0e8732c6bd
Revert "修正和改进"
8 years ago

@ -1,88 +1,80 @@
<div align="center"> # :orange_book: 持续更新中...
<img src="images/logo001.gif"/><br><br>
</div>
#### :orange_book: 持续更新中... ## 第0章 远程安装
#### 第0章 安装
```golang ```golang
$ go get github.com/Tinywan/golang-tutorial $ go get github.com/Tinywan/golang-tutorial
``` ```
#### 第1章 介绍和安装 ## 第1章 介绍和安装
### [1 - 介绍和安装](/docs/golang_tutorial_01.md)
### [2 - Hello World ](/docs/golang_tutorial_02.md)
###### [1 - 介绍和安装](/docs/golang_tutorial_01.md) ## 第2章 变量、类型和常量
###### [2 - Hello World ](/docs/golang_tutorial_02.md)
#### 第2章 变量、类型和常量 ### [3 - 变量](/docs/golang_tutorial_03.md)
### [4 - 类型](/docs/golang_tutorial_04.md)
### [5 - 常量](/docs/golang_tutorial_05.md)
###### [3 - 变量](/docs/golang_tutorial_03.md) ## 第3章 函数和包
###### [4 - 类型](/docs/golang_tutorial_04.md)
###### [5 - 常量](/docs/golang_tutorial_05.md)
#### 第3章 函数和包 ### [6 - 函数](/docs/golang_tutorial_06.md)
### [7 - 包](/docs/golang_tutorial_07.md)
###### [6 - 函数](/docs/golang_tutorial_06.md) ## 第4章 条件和循环语句
###### [7 - 包](/docs/golang_tutorial_07.md)
#### 第4章 条件和循环语句 ### [8 - if else 语句](/docs/golang_tutorial_08.md)
### [9 - 循环语句](/docs/golang_tutorial_09.md)
### [10 - switch语句](/docs/golang_tutorial_10.md)
###### [8 - if else 语句](/docs/golang_tutorial_08.md) ## 第5章 数组、切片和变参函数
###### [9 - 循环语句](/docs/golang_tutorial_09.md)
###### [10 - switch语句](/docs/golang_tutorial_10.md)
#### 第5章 数组、切片和变参函数 ### [11 - 数组和切片](/docs/golang_tutorial_11.md)
### [12 - 变参函数](/docs/golang_tutorial_12.md)
###### [11 - 数组和切片](/docs/golang_tutorial_11.md) ## 第6章 更多的类型
###### [12 - 变参函数](/docs/golang_tutorial_12.md)
#### 第6章 更多的类型 ### [13 - Map](/docs/golang_tutorial_13.md)
### [14 - 字符串](/docs/golang_tutorial_14.md)
###### [13 - Map](/docs/golang_tutorial_13.md) ## 第7章 指针、结构体和方法
###### [14 - 字符串](/docs/golang_tutorial_14.md)
#### 第7章 指针、结构体和方法 ### [15 - 指针](/docs/golang_tutorial_15.md)
### [16 - 结构体](/docs/golang_tutorial_16.md)
### [17 - 方法](/docs/golang_tutorial_17.md)
###### [15 - 指针](/docs/golang_tutorial_15.md) ## 第8章 接口
###### [16 - 结构体](/docs/golang_tutorial_16.md)
###### [17 - 方法](/docs/golang_tutorial_17.md)
#### 第8章 接口 ### 18 - 接口一
### 19 - 接口二
###### 18 - 接口一 ## 第9章 并发
###### 19 - 接口二
#### 第9章 并发 ### 20 - 并发介绍
### [ 21 - 协程](/docs/golang_tutorial_21.md)
### 22 - 管道
### 23 - 缓冲信道和工作池
### 24 - Select
### 25 - Mutex
###### 20 - 并发介绍 ## 第10章 面向对象编程
###### [ 21 - 协程](/docs/golang_tutorial_21.md)
###### [22 - 信道](/docs/golang_tutorial_22.md)
###### 23 - 缓冲信道和工作池
###### 24 - Select
###### 25 - Mutex
#### 第10章 面向对象编程 ### 26 - 结构体和类
### 27 - 构成和继承
### 28 - 多态性
###### 26 - 结构体和类 ## 第11章 延迟和错误处理
###### 27 - 构成和继承
###### 28 - 多态性
#### 第11章 延迟和错误处理 ### 29 - 延迟
### 30 - 错误处理
### 31 - 自定义错误
### 32 -Panic 和 Recover
### 33 - 头等函数
### 34 - 反射
### 35 - 读文件
###### 29 - 延迟 ## 其他文档
###### 30 - 错误处理
###### 31 - 自定义错误
###### 32 -Panic 和 Recover
#### 头等函数
###### 33 - 头等函数
#### 反射
###### 34 - 反射
#### 文件操作
###### [35 - 读文件](/docs/golang_tutorial_35.md)
#### 文档 * [如何编写Go代码](/docs/how_to_write_go_code.md)
* [Go 零基础编程入门教程](http://go-courses.tinywan.com/_book/)
* [Go 语言标准库](http://go-library.tinywan.com/_book/)
* [如何编写 Go代码](/docs/how_to_write_go_code.md)
* [原文](https://golangbot.com/) * [原文](https://golangbot.com/)

@ -1,6 +0,0 @@
package main
func main() {
m := make(chan int)
m <- 5
}

@ -1,18 +0,0 @@
package main
import (
"fmt"
)
func main() {
var c chan int // 信道 c 的值为 nil
if c == nil {
fmt.Println("channel a is nil, going to define it")
c = make(chan int)
fmt.Printf("Type of c is %T", c) // 打印通道类型
}
fmt.Printf("\n")
a := make(chan int)
fmt.Printf("%T", a) // chan int
}

@ -1,27 +0,0 @@
package main
import (
"fmt"
)
func hello(done chan bool) {
fmt.Println("This is hello goroutines")
done <- true
}
func getName(name chan string) {
fmt.Println("This is getName goroutines")
name <- "Tinywan"
}
func main() {
done := make(chan bool)
go hello(done)
<-done
name := make(chan string)
go getName(name)
fmt.Println(<-name)
fmt.Println("This is main function")
}

@ -1,29 +0,0 @@
package main
import (
"fmt"
)
func producer(num chan int) {
for i := 0; i < 10; i++ {
num <- i
}
close(num)
}
func main() {
ch := make(chan int)
go producer(ch)
// for {
// v, ok := <-ch
// if ok == false { // 如果 ok 是 false 表示信道已经被关闭
// break // 通过 break 退出循环
// }
// fmt.Println("Received", v, ok)
// }
for v := range ch {
fmt.Println("Received ", v)
}
}

@ -1,18 +0,0 @@
package main
import ()
import "fmt"
func sendData(d chan<- int) {
d <- 5
}
func main() {
d := make(chan<- int)
go sendData(d)
//fmt.Println(<-d) //试图从一个只写信道中接收数据
m := make(chan int) // 将双向信道转换为只写或只读信道
go sendData(m)
fmt.Println(<-m)
}

@ -1,21 +0,0 @@
package main
import (
"fmt"
"time"
)
func hello(done chan bool) {
fmt.Println("hello go routine is going to sleep")
time.Sleep(4 * time.Second)
fmt.Println("hello go routine awake and going to write to done")
done <- true
}
func main() {
done := make(chan bool)
fmt.Println("Main going to call hello go goroutine")
go hello(done)
<-done
fmt.Println("Main received data")
}

@ -1,36 +0,0 @@
package main
import (
"fmt"
)
// 计算 number 每一位的平方和
func calcSquares(number int, squareop chan int) {
sum := 0
for number != 0 {
digit := number % 10
sum += digit * digit
number /= 10
}
squareop <- sum // 将结果发送给信道 squareop
}
func calcCubes(number int, cubeop chan int) {
sum := 0
for number != 0 {
digit := number % 10
sum += digit * digit * digit
number /= 10
}
cubeop <- sum
}
func main() {
number := 598
squareop := make(chan int)
cubeop := make(chan int)
go calcSquares(number, squareop)
go calcCubes(number, cubeop)
squares, cubes := <-squareop, <-cubeop
fmt.Println("Final output", squares+cubes)
}

@ -1,46 +0,0 @@
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
var (
url1 = "https://www.tinywan.com/api/open/returnUrl"
url2 = "https://restapi.amap.com/v3/weather/weatherInfo?key=d2315f3b0b4e57bbf5428e755a73e692&city=110101"
)
func main() {
res, err := doHttpGetRequest(url2)
if err != nil {
fmt.Println("net req error")
} else {
fmt.Println(res)
}
}
// http get请求函数
// 输入参数:请求url
// 返回值:res,天气数据。err,错误信息
func doHttpGetRequest(url string) (res string, err error) {
// http.Get在net/http中,所以要import "net/http"
resp, err := http.Get(url)
if err != nil {
return "", err
} else {
// 使用efer resp.Body.Close()。当doHttpGetRequest成功return之后,执行此行语句。多用于句柄关闭
defer resp.Body.Close()
// io流数据读取。需要引用io/ioutil
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
} else {
return string(body), err
}
}
}

@ -1,30 +0,0 @@
package main
import (
"fmt"
)
// interface define 元音,接口中只能定义方法哦
type VowelsFinder interface {
FindVowels() []rune
}
type MyString string
// 方法
func (ms MyString) FindVowels() []rune {
var vowels []rune
for _, rune := range ms {
if rune == 'a' || rune == 'e' || rune == 'i' || rune == 'o' || rune == 'u' {
vowels = append(vowels, rune)
}
}
return vowels
}
func main() {
name := MyString("Sam Anderson")
var v VowelsFinder
v = name
fmt.Printf("Vowels are %c", v.FindVowels())
}

@ -4,16 +4,9 @@ import (
"fmt" "fmt"
) )
func main() { func main(){
b := 255 b := 255
var a = &b var a = &b
fmt.Printf("Type of a is %T\n", a) fmt.Printf("Type of a is %T\n",a)
fmt.Println("address of b is", a) fmt.Println("address of b is",a)
s := map[string]int{"age": 24}
var t map[string]string // 正确
t1 := map[string]string // 错误
fmt.Println(s)
fmt.Println(t)
fmt.Println(t1)
} }

@ -1,47 +0,0 @@
package main
import (
"fmt"
"reflect"
)
// import (
// "fmt"
// ) go_basic_courses
func main() {
// 声明一个集合字典,默认为空
a := 15
fmt.Println("address of b is ", a)
fmt.Println("address of b is ", &a)
m := map[string]int{}
fmt.Println("address of b i is ", m)
fmt.Printf("Type of a is %T\n", m)
fmt.Printf("m值是 %v \n", m == nil)
var dir map[string]string
fmt.Printf("dir字典的值是: | %v \n", dir)
fmt.Printf("dir字典是否为nil | %v \n", dir == nil)
// nil map不能赋值,如果直接赋值会报错:“panic: assignment to entry in nil map”,下面语句将会报错
// dir["name"] = "Tinywan"
fmt.Println("----------------------------")
// 使用make函数进行初始化创建一个非nil的map
dir = make(map[string]string)
// map是引用类型,未初始化的是指向nil,初始化了以后应该就有自己的内存空间了,所以不是nil
fmt.Printf("dir字典的值是: | %v \n", dir)
fmt.Printf("dir字典是否为nil | %v \n", dir == nil)
fmt.Println("----------------------------")
// make之后分配内存了,一旦分配了内存地址就不为空了,可以赋值了
dir["name"] = "Tinywan"
fmt.Printf("dir字典的键:值 |%v \n", dir)
fmt.Printf("dir字典的类型 |%v \n", reflect.TypeOf(dir))
fmt.Printf("name的值是 |%v \n", dir["name"])
// value, ok := s["ok"]
// if ok == nil {
// s := make(map[string]int)
// s["ok"] = 19
// fmt.Println(s["ok"])
// }
// fmt.Println(s["ok"])
}

@ -1,7 +0,0 @@
package main
func main() {
var s []int
s = make([]int, 1)
s[0] = 12
}

@ -1,19 +0,0 @@
package main
import (
"fmt"
)
func main() {
byteSlice := []byte{0x43, 0x61, 0x66, 0xC3, 0xA9}
str := string(byteSlice)
fmt.Println(str)
byteSlice2 := []byte{67, 97, 102, 195, 169} //decimal equivalent of {'\x43', '\x61', '\x66', '\xC3', '\xA9'}
str2 := string(byteSlice2)
fmt.Println(str2)
runeSlice := []rune{0x0053, 0x0065, 0x00f1, 0x006f, 0x0072}
str3 := string(runeSlice)
fmt.Println(str3)
}

@ -1,40 +0,0 @@
package main
import (
"fmt"
)
// 打印字符串中的字节
func printBytes(s string) {
for i := 0; i < len(s); i++ {
fmt.Printf("%x ", s[i])
}
}
// 打印字符串中的字符
func printChars(s string) {
for i := 0; i < len(s); i++ {
fmt.Printf("%c", s[i])
}
}
func main() {
name := "Hello World"
printBytes(name)
fmt.Printf("\n")
printChars(name)
fmt.Printf("\n")
// d
name = "Señor"
printBytes(name)
fmt.Printf("\n")
printChars(name)
fmt.Printf("\n")
name = "萬少波"
printBytes(name)
fmt.Printf("\n")
printChars(name)
}

@ -1,17 +0,0 @@
package main
import (
"fmt"
"unicode/utf8"
)
func length(s string) {
fmt.Printf("length of %s is %d\n", s, utf8.RuneCountInString(s))
}
func main() {
word1 := "Señor"
length(word1)
word2 := "Pets"
length(word2)
}

@ -1,16 +0,0 @@
package main
import (
"fmt"
)
func printCharsAndBytes(s string) {
for index, rune := range s {
fmt.Printf("%c starts at byte %d\n", rune, index)
}
}
func main() {
name := "Señor"
printCharsAndBytes(name)
}

@ -1,42 +0,0 @@
package main
import (
"fmt"
)
// 打印字符串中的字节
func printBytes(s string) {
for i := 0; i < len(s); i++ {
fmt.Printf("%x ", s[i])
}
}
// 打印字符串中的字符
func printChars(s string) {
runes := []rune(s)
for i := 0; i < len(runes); i++ {
fmt.Printf("%c", runes[i])
}
}
func main() {
name := "Hello World"
printBytes(name)
fmt.Printf("\n")
printChars(name)
fmt.Printf("\n")
// 特殊字符
name = "Señor"
printBytes(name)
fmt.Printf("\n")
printChars(name)
// 打印汉字
fmt.Printf("\n")
name = "萬少波"
printBytes(name)
fmt.Printf("\n")
printChars(name)
}

@ -1,24 +0,0 @@
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
s := "hello 你好" // 8 = 5个字符 + 1个空格 + 2个汉字
fmt.Println("len(s):", len(s)) // len(s): 12
var str = "hello 你好"
//golang中string底层是通过byte数组实现的,直接求len 实际是在按字节长度计算 所以一个汉字占3个字节算了3个长度
fmt.Println("len(str):", len(str))
//以下两种都可以得到str的字符串长度
//golang中的unicode/utf8包提供了用utf-8获取长度的方法
fmt.Println("RuneCountInString:", utf8.RuneCountInString(str))
// 通过rune类型处理unicode字符
fmt.Println("rune:", len([]rune(str)))
}

@ -2,20 +2,12 @@ package main
import ( import (
"fmt" "fmt"
"os"
"strings"
) )
func main() { var age int
os.Setenv("Name", "Tinywan")
fmt.Println("Name = ", os.Getenv("Name"))
fmt.Println("Bar", os.Getenv("bar"))
fmt.Println()
fmt.Println() func main() {
for _, e := range os.Environ() { name := "Tinywan"
pair := strings.Split(e, "=") fmt.Println(name)
fmt.Println(pair[0]) fmt.Println(name)
}
} }
go

@ -1,30 +0,0 @@
package main
import (
"fmt"
)
type person struct {
name string
age int
sex bool
}
func (p *person) isSex() {
if p.age > 25 {
p.sex = true
}
if p.sex {
fmt.Println("Who is ", p.name)
}
}
func main() {
p1 := person{name: "Tinywan", age: 24}
p1.isSex()
p2 := person{name: "Shaobo", age: 26}
p2.isSex()
p3 := person{name: "MaLi", age: 32}
p3.isSex()
}

@ -165,7 +165,7 @@ new width is 100 new height is 50
有些时候我们需要在一条语句中**声明多个不同类型的变量**。我们可以使用下面的语法达到此目的: 有些时候我们需要在一条语句中**声明多个不同类型的变量**。我们可以使用下面的语法达到此目的:
```golang ```golang
var ( var (
name1 = initialvalue1 name1 = initialvalue1,
name2 = initialvalue2 name2 = initialvalue2
) )
``` ```

@ -66,7 +66,7 @@ error main.go:11: const initializer math.Sqrt(4) is not a constant.
const hello = "Hello World" const hello = "Hello World"
``` ```
上面的代码将 "Hello World" 赋给一个名为 `hello` 的常量。那么现在常量 `hello` 是不是就有了类型?答案是:No。`hello` 仍然没有类型。 上面的代码将 "Hello World" 赋给一个名为 hello 的常量。那么现在常量** hello** 是不是就有了类型?答案是:No。hello 仍然没有类型。
Go是一种强类型语言。 所有变量都需要显式类型。 变量 `name` 被赋值为一个无类型的常量 `"Sam"`,它是如何工作的呢? Go是一种强类型语言。 所有变量都需要显式类型。 变量 `name` 被赋值为一个无类型的常量 `"Sam"`,它是如何工作的呢?
@ -84,7 +84,7 @@ func main() {
} }
``` ```
答案是无类型常量有一个默认的类型,当且仅当代码中需要无类型常量提供类型时,它才会提供该默认类型。在语句 `var name = "Sam" `中,`name`需要一个类型,因此它从常量 `"Sam"` 中获取其默认类型:`string`。 答案是无类型常量有一个默认的类型,当且仅当代码中需要无类型常量提供类型时,它才会提供该默认类型。在语句 `var name = "Sam" `中,`name`需要一个类型,因此它从常量 "Sam" 中获取其默认类型:`string`。
<u>(译者注:这里可以理解为常量是无类型的,但是在需要类型的场合,编译器会根据常量的值和上下文将常量转换为相应的类型。)</u> <u>(译者注:这里可以理解为常量是无类型的,但是在需要类型的场合,编译器会根据常量的值和上下文将常量转换为相应的类型。)</u>
@ -116,9 +116,7 @@ func main() {
接着我们创建了一个名为 `customName` 的 `myString` 类型的变量,并将常量 `"Sam"` 赋给它。因为常量 `"Sam"` 是无类型的所以可以将它赋值给任何字符串变量。因此这个赋值是合法的,`customName` 的类型是 `myString`。 接着我们创建了一个名为 `customName` 的 `myString` 类型的变量,并将常量 `"Sam"` 赋给它。因为常量 `"Sam"` 是无类型的所以可以将它赋值给任何字符串变量。因此这个赋值是合法的,`customName` 的类型是 `myString`。
现在我们有两个变量:string 类型的 `defaultName` 和 `myString` 类型的 `customName`。尽管我们知道 `myString` 是 string 的一个别名,但是Go的强类型机制不允许将一个类型的变量赋值给另一个类型的变量。 现在我们有两个变量:string 类型的 `defaultName` 和 `myString` 类型的 `customName`。尽管我们知道 `myString` 是 string 的一个别名,但是Go的强类型机制不允许将一个类型的变量赋值给另一个类型的变量。<u>**因此,customName = defaultName 这个赋值是不允许的,编译器会报错:main.go:10: cannot use defaultName (type string) as type myString in assignment **</u>
<u>因此,customName = defaultName 这个赋值是不允许的,编译器会报错:main.go:10: cannot use defaultName (type string) as type myString in assignment </u>
## 布尔常量 ## 布尔常量
@ -182,7 +180,7 @@ func main() {
在上面的程序中,所有变量的类型都是由数值常量决定的。在语法上看,`5` 在是一个整数,`5.6 `是一个浮点数,` 5 + 6i `是一个复数。运行上面的程序,输出为:`i's type int, f's type float64, c's type complex128` 在上面的程序中,所有变量的类型都是由数值常量决定的。在语法上看,`5` 在是一个整数,`5.6 `是一个浮点数,` 5 + 6i `是一个复数。运行上面的程序,输出为:`i's type int, f's type float64, c's type complex128`
<u>(译者注:编译器可以根据字面值常量的表现形式来确定它的默认类型,比如 5.6 表现为浮点数,因此它的默认类型为 float64 ,而 "Sam" 表现为字符串,因此它的默认类型为 string。)</u> <u>(译者注:编译器可以根据字面值常量的表现形式来确定它的默认类型,比如 5.6 表现为浮点数,因此它的默认类型为 float64 ,而 "Sam" 表现为字符串,因此它的默认类型为 stirng。)</u>
现在应该很清楚下面的程序是如何工作的了: 现在应该很清楚下面的程序是如何工作的了:

@ -72,7 +72,7 @@ func main() {
} }
``` ```
在上面的程序中, `num` 在 `if `语句中初始化。需要注意的一点是,`num` 只能在 if 和 else 里面进行访问,即 `num` 的范围仅限于 `if else` 块中。如果我们试图在 `if` 或 `else` 之外访问 `num`,编译器将报错。 在上面的程序中, `num` 在 `if `语句中初始化。需要注意的一点是,`num` 只能在 if 和 else 里面进行访问,即 `num` 的范围仅限于` if else `块中。如果我们试图在 `if` 或 `else` 之外访问 `num`,编译器将报错。
让我们用 else if 再写一个程序: 让我们用 else if 再写一个程序:
@ -95,11 +95,11 @@ func main() {
} }
``` ```
在上面的程序 `else if num >= 51 && num <= 100` 为 `true`,因此程序将输出:`number is between 51 and 100` 在上面的程序 `else if num >= 51 && num <= 100`为`true`,因此程序将输出:`number is between 51 and 100`
## 小陷阱 ## 疑难杂症
`else` 语句应该在 `if` 语句的大括号 `}` 之后的同一行中开始,否则编译器会报错。 该`else`声明应该在`if`语句的大括号`}`之后的同一行中开始。如果不是,编译器会报错。
让我们通过一个程序来理解这一点。 让我们通过一个程序来理解这一点。
@ -121,17 +121,17 @@ func main() {
} }
``` ```
在上面的程序中,`else` 语句没有在 `if` 语句 `}` 结束后的同一行中开始,而是从下一行开始的。Go中不允许这样做。如果你运行这个程序,编译器会输出错误: 在上面的程序中,该`else`语句不会在if 语句`}`关闭后的同一行中开始。相反,它从下一行开始。Go中不允许这样做。如果你运行这个程序,编译器会输出错误:
```golang ```golang
main.go:12:5: syntax error: unexpected else, expecting } main.go:12:5: syntax error: unexpected else, expecting }
``` ```
***这是由于Go会自动插入分号,点击链接了解分号插入规则:https://golang.org/ref/spec#Semicolons*** ***原因是Go自动插入分号的方式。你可以在这里阅读https://golang.org/ref/spec#Semicolons 的分号插入规则。***
在分号插入规则中,如果 `}` 是当前一行的最后一个字符,那么 `}` 后面会被插入一个分号。所以在上面的例子中,if语句的 `}` 之后会被自动插入一个分号。 在规则中,指定在`}`后面插入分号,如果这是分号的最后一个标记。所以在if语句`}`之后自动插入一个分号。
所以实际上我们的程序变成了: 所以我们的程序变成了:
```golang ```golang
if num%2 == 0 { if num%2 == 0 {
@ -142,11 +142,11 @@ else {
} }
``` ```
你可以看到第三行中插入了一个分号。 分号插入后。您可以在行号中看到分号插入。上面的代码段中的3个。
由于 `if{...} else {...}` 是一个单独的语句,分号不应该出现在语句的中间。因此,`else` 需要放在 `}` 之后的同一行中。 既然`if{...} else {...}`是一个单一的陈述,分号就不应该出现在它的中间。因此,需要`else`在关闭后放置在同一行中`}`。
我重新改写了程序,将 `else` 移动到if语句结束之后的 `}` 的后面,以防止自动分号插入。 我通过`}`在if语句关闭后移动`else`来重写程序,以防止自动分号插入。
```golang ```golang
package main package main
@ -165,6 +165,6 @@ func main() {
} }
``` ```
现在编译器就可以正常编译我们的程序了。 现在编译器会很开心,我们也是如此。
希望你喜欢阅读。请留下宝贵的意见和反馈:) 希望你喜欢阅读。请留下宝贵的意见和反馈:)

@ -10,9 +10,9 @@
数组是类型相同的元素的集合。例如,整数 5, 8, 9, 79, 76 的集合就构成了一个数组。Go不允许在数组中混合使用不同类型的元素(比如整数和字符串)。 数组是类型相同的元素的集合。例如,整数 5, 8, 9, 79, 76 的集合就构成了一个数组。Go不允许在数组中混合使用不同类型的元素(比如整数和字符串)。
## 声明 ## 申明
数组的类型为 `[n]T`,其中 `n` 表示数组中元素的个数,`T` 表示数组中元素的类型。元素的个数 `n` 也是数组类型的一部分(我们将在稍后详细讨论)。 数组的类型为` [n]T`,其中 `n` 表示数组中元素的个数,`T` 表示数组中元素的类型。元素的个数 `n`也是数组类型的一部分(我们将在稍后详细讨论)。
有很多声明数组的方式,让我们一个一个地介绍。 有很多声明数组的方式,让我们一个一个地介绍。
@ -29,7 +29,7 @@ func main() {
} }
``` ```
`var a [3]int` 声明了一个长度为 3 的整型数组。数组中的所有元素都被自动赋值为元素类型的 0 值。比如这里 a 是一个整型数组,因此 a 中的所有元素都被赋值为 0(即整型的 0 值)。运行上面的程序,输出为:`[0 0 0]`。 `var a [3]int `声明了一个长度为 3 的整型数组。数组中的所有元素都被自动赋值为元素类型的 0 值。比如这里 a 是一个整型数组,因此 a 中的所有元素都被赋值为 0(即整型的 0 值)。运行上面的程序,输出为:`[0 0 0]`。
数组的索引从 0 开始到 `length - 1` 结束。下面让我们给上面的数组赋一些值。 数组的索引从 0 开始到 `length - 1` 结束。下面让我们给上面的数组赋一些值。
@ -49,11 +49,11 @@ func main() {
} }
``` ```
`a[0]` 表示数组中的第一个元素。程序的输出为:`[12 78 50]`。 `a[0]`表示数组中的第一个元素。程序的输出为:`[12 78 50]`。
(译者注:可以用下标运算符(`[]`)来访问数组中的元素,下标从 0 开始,例如 `a[0]` 表示数组 a 的第一个元素,`a[1]` 表示数组 a 的第二个元素,以此类推。) (译者注:可以用下标运算符(`[]`)来访问数组中的元素,下标从 0 开始,例如 `a[0]` 表示数组 a 的第一个元素,`a[1] `表示数组 a 的第二元素,以此类推。)
可以利用 **速记声明(shorthand declaration)** 的方式来创建同样的数组: 可以利用**速记声明(shorthand declaration)**的方式来创建同样的数组:
```golang ```golang
package main package main
@ -87,7 +87,7 @@ func main() {
} }
``` ```
上面程序的第 8 行:`a := [3]int{12}` 声明了一个长度为 3 的数组,但是只提供了一个初值 12。剩下的两个元素被自动赋值为 0。程序 的输出为:`[12 0 0]`。 上面程序的第 8 行:`a := [3]int{12} `声明了一个长度为 3 的数组,但是只提供了一个初值 12。剩下的两个元素被自动赋值为 0。程序 的输出为:`[12 0 0]`。
在声明数组时你可以忽略数组的长度并用` ... `代替,让编译器为你自动推导数组的长度。比如下面的程序: 在声明数组时你可以忽略数组的长度并用` ... `代替,让编译器为你自动推导数组的长度。比如下面的程序:
@ -104,7 +104,7 @@ func main() {
} }
``` ```
上面已经提到,数组的长度是数组类型的一部分。因此 `[5]int` 和 `[25]int` 是两个不同类型的数组。正是因为如此,一个数组不能动态改变长度。不要担心这个限制,因为切片(`slices`)可以弥补这个不足。 上面已经提到,数组的长度是数组类型的一部分。因此 `[5]int` 和 `[25]int`是两个不同类型的数组。正是因为如此,一个数组不能动态改变长度。不要担心这个限制,因为切片(`slices`)可以弥补这个不足。
```golang ```golang
package main package main
@ -116,7 +116,7 @@ func main() {
} }
``` ```
在上面程序的第 6 行,我们试图将一个 `[3]int` 类型的数组赋值给一个 `[5]int` 类型的数组,这是不允许的。编译会报错:`main.go:6: cannot use a (type [3]int) as type [5]int in assignment。` 在上面程序的第 6 行,我们试图将一个` [3]int `类型的数组赋值给一个 `[5]int `类型的数组,这是不允许的。编译会报错:`main.go:6: cannot use a (type [3]int) as type [5]int in assignment。`
## 数组是值类型 ## 数组是值类型
@ -189,7 +189,7 @@ func main() {
## 使用 range 遍历数组 ## 使用 range 遍历数组
`for` 循环可以用来遍历数组中的元素: `for `循环可以用来遍历数组中的元素:
```golang ```golang
package main package main
@ -212,7 +212,7 @@ func main() {
3 th element of a is 78.00 3 th element of a is 78.00
``` ```
Go 提供了一个更简单,更简洁的遍历数组的方法:使用 range 形式的 for 循环。`range` 返回数组的索引和索引对应的值。让我们用 range for 重写上面的程序(除此之外我们还计算了数组元素的总和)。 Go 提供了一个更简单,更简洁的遍历数组的方法:使用 `range for`。range 返回数组的索引和索引对应的值。让我们用 range for 重写上面的程序(除此之外我们还计算了数组元素的总和)。
```golang ```golang
package main package main
@ -247,7 +247,7 @@ for _, v := range a { //ignores index
} }
``` ```
上面的代码忽略了索引。用类似的方法,也可以忽略值。 上面的代码忽略了索引。同样的,也可以忽略值。
## 多维数组 ## 多维数组
@ -288,7 +288,7 @@ func main() {
} }
``` ```
上面的程序中,第 17 行利用速记声明创建了一个二维数组 a。第 20 行的逗号是必须的,因为词法分析器会根据一些简单的规则自动插入分号。如果你想了解更多,请阅读:https://golang.org/doc/effective_go.html#semicolons 。 上面的程序中,第 17 行利用速记声明创建了一个二维数组 a。第 20 行的逗号是必须的,这是因为词法分析器会根据一些简单的规则自动插入分号。如果你想了解更多,请阅读:https://golang.org/doc/effective_go.html#semicolons 。
在第 23 行声明了另一个二维数组 b,并通过索引的方式给数组 b 中的每一个元素赋值。这是初始化二维数组的另一种方式。 在第 23 行声明了另一个二维数组 b,并通过索引的方式给数组 b 中的每一个元素赋值。这是初始化二维数组的另一种方式。
@ -312,7 +312,7 @@ AT&T T-Mobile
## 创建切片 ## 创建切片
元素类型为 `T` 的切片表示为: `[]T`。 元素类型为 `T `的切片表示为: `[]T`。
```golang ```golang
package main package main
@ -328,7 +328,7 @@ func main() {
} }
``` ```
通过 `a[start:end]` 这样的语法创建了一个从 `a[start]` 到 `a[end -1]` 的切片。在上面的程序中,第 9 行 `a[1:4]` 创建了一个从 `a[1]` 到 `a[3]` 的切片。因此 b 的值为:`[77 78 79]`。 通过 `a[start:end] `这样的语法创建了一个从` a[start]` 到 `a[end -1]` 的切片。在上面的程序中,第 9 行` a[1:4]` 创建了一个从 `a[1]` 到 `a[3]` 的切片。因此 b 的值为:`[77 78 79]`。
下面是创建切片的另一种方式: 下面是创建切片的另一种方式:
@ -345,7 +345,7 @@ func main() {
} }
``` ```
在上面的程序中,第 9 行 `c := []int{6, 7, 8}` 创建了一个长度为 3 的 int 数组,并返回一个切片给 c。 在上面的程序中,第 9 行 `c := []int{6, 7, 8} `创建了一个长度为 3 的 int 数组,并返回一个切片给 c。
## 修改切片 ## 修改切片
@ -397,7 +397,7 @@ func main() {
} }
``` ```
可以看到,在第 9 行,`numa[:]` 中缺少了开始和结束的索引值,这种情况下开始和结束的索引值默认为 `0` 和 `len(numa)`。这里 `nums1` 和 `nums2` 共享了同一个数组。程序的输出为: 可以看到,在第 9 行,` numa[:] `中缺少了开始和结束的索引值,这种情况下开始和结束的索引值默认为 `0` 和` len(numa)`。这里 `nums1` 和 `nums2` 共享了同一个数组。程序的输出为:
```golang ```golang
array before change 1 [78 79 80] array before change 1 [78 79 80]
@ -429,11 +429,11 @@ func main() {
} }
``` ```
在上面的程序中,创建了一个以 `fruitarray` 为底层数组,索引从 1 到 2 的切片 `fruitslice`,因此 fruitslice 长度为 2。 在上面的程序中,创建了一个以 fruitarray 为底层数组,索引从 1 到 3 的切片 `fruitslice`。因此 fruitslice 长度为 2。
`fruitarray` 的长度是 7。`fruiteslice` 是从 `fruitarray` 的索引 1 开始的。因此 `fruiteslice` 的容量是从 `fruitarray` 的第 1 个元素开始算起的数组中的元素个数,这个值是 6。因此 `fruitslice` 的容量是 6。程序的输出为:`length of slice 2 capacity 6` `fruitarray` 的长度是 7。`fruiteslice` 是从 `fruitarray` 的索引 1 开始的。因此 `fruiteslice` 的容量是从 `fruitarray` 的第 1 个元素开始算起的数组中的元素个数,这个值是 6。因此 `fruitslice` 的容量是 6。程序的输出为:`length of slice 2 capacity 6 `
切片的长度可以动态地改变(最大长度为其容量)。任何超出切片容量的操作都会发生运行错误。 切片的长度可以动态的改变(最大为其容量)。任何超出最大容量的操作都会发生运行时错误。
```golang ```golang
package main package main
@ -460,7 +460,7 @@ After re-slicing length is 6 and capacity is 6
## 用 make 创建切片 ## 用 make 创建切片
内置函数 `func make([]T, len, cap) []T` 可以用来创建切片,该函数接受**长度**和**容量**作为参数,返回切片。容量是可选的,默认与长度相同。使用 make 函数将会创建一个数组并返回它的切片。 内置函数` func make([]T, len, cap) []T` 可以用来创建切片,该函数接受**长度**和**容量**作为参数,返回切片。容量是可选的,默认与长度相同。使用 make 函数将会创建一个数组并返回它的切片。
```golang ```golang
package main package main
@ -483,7 +483,7 @@ func main() {
`x …T` 表示 append 函数可以接受的参数个数是可变的。这种函数叫做变参函数。 `x …T` 表示 append 函数可以接受的参数个数是可变的。这种函数叫做变参函数。
你可能会问一个问题:如果切片是建立在数组之上的,而数组本身不能改变长度,那么切片是如何动态改变长度的呢?实际发生的情况是,当新元素通过调用 append 函数追加到切片末尾时,如果超出了容量,append 内部会创建一个新的数组。并将原有数组的元素拷贝给这个新的数组,最后返回建立在这个新数组上的切片。这个新切片的容量是旧切片的二倍(译者注:当超出切片的容量时,append 将会在其内部创建新的数组,该数组的大小是原切片容量的 2 倍。最后 append 返回这个数组的全切片,即从 0 到 length - 1 的切片)。下面的程序会帮助你理解这一知识点: 你可能会问一个问题:如果切片是建立在数组之上的,而数组本身不能改变长度,那么切片是如何动态改变长度的呢?实际发生的情况是,当新元素通过调用 append 函数追加到切片末尾时,如果超出了容量,append 内部会创建一个新的数组。并将原有数组的元素被拷贝给这个新的数组,最后返回建立在这个新数组上的切片。这个新切片的容量是旧切片的二倍(译者注:当超出切片的容量时,append 将会在其内部创建新的数组,该数组的大小是原切片容量的 2 倍。最后 append 返回这个数组的全切片,即从 0 到 length - 1 的切片)。下面的程序使事情变得明朗:
```golang ```golang
package main package main
@ -500,7 +500,7 @@ func main() {
} }
``` ```
在上面的程序中,cars 的容量开始时为 3。在第 10 行我们追加了一个新的元素给 `cars`,并将 `append(cars, "Toyota")` 的返回值重新复制给 cars。现在 cars 的容量翻倍,变为 6。上面的程序输出为: 在上面的程序中,cars 的容量开始时为 3。在第 10 行我们追加了一个新的元素给 `cars`,并将 `append(cars, "Toyota") `的返回值重新复制给 cars。现在 cars 的容量翻倍,变为 6。上面的程序输出为:
```golang ```golang
cars: [Ferrari Honda Ford] has old length 3 and capacity 3 cars: [Ferrari Honda Ford] has old length 3 and capacity 3
@ -588,7 +588,7 @@ func main() {
} }
``` ```
在上面的程序中,第 17 行将切片中的每个元素的值减 2。在函数调用之后打印切片的的内容,发现切片内容发生了改变。你可以回想一下,这不同于一个数组,对函数内部的数组所做的更改在函数外不可见。上面的程序输出如下: 在上面的程序中,第 17 行将切片中的每个元素的值减2。在函数调用之后打印切片的的内容,发现切片内容发生了改变。你可以回想一下,这不同于一个数组,对函数内部的数组所做的更改在函数外不可见。上面的程序输出如下:
```golang ```golang
array before function call [8 7 6] array before function call [8 7 6]
@ -634,7 +634,7 @@ Go Rust
切片保留对底层数组的引用。只要切片存在于内存中,数组就不能被垃圾回收。这在内存管理方便可能是值得关注的。假设我们有一个非常大的数组,而我们只需要处理它的一小部分,为此我们创建这个数组的一个切片,并处理这个切片。这里要注意的事情是,数组仍然存在于内存中,因为切片正在引用它。 切片保留对底层数组的引用。只要切片存在于内存中,数组就不能被垃圾回收。这在内存管理方便可能是值得关注的。假设我们有一个非常大的数组,而我们只需要处理它的一小部分,为此我们创建这个数组的一个切片,并处理这个切片。这里要注意的事情是,数组仍然存在于内存中,因为切片正在引用它。
解决该问题的一个方法是使用 copy 函数 `func copy(dst, src []T) int` 来创建该切片的一个拷贝。这样我们就可以使用这个新的切片,原来的数组可以被垃圾回收。 解决该问题的一个方法是使用 copy 函数 `func copy(dst, src []T) int `来创建该切片的一个拷贝。这样我们就可以使用这个新的切片,原来的数组可以被垃圾回收。
```golang ```golang
package main package main
@ -656,7 +656,7 @@ func main() {
} }
``` ```
在上面程序中,第 9 行 `neededCountries := countries[:len(countries)-2]` 创建一个底层数组为 `countries` 并排除最后两个元素的切片。第 11 行将 `neededCountries` 拷贝到 `countriesCpy` 并在下一行返回 `countriesCpy`。现在数组 `countries` 可以被垃圾回收,因为 `neededCountries` 不再被引用。 在上面程序中,第 9 行 `neededCountries := countries[:len(countries)-2] `创建一个底层数组为 `countries` 并排除最后两个元素的切片。第 11 行将 `neededCountries` 拷贝到 `countriesCpy` 并在下一行返回 `countriesCpy`。现在数组 `countries` 可以被垃圾回收,因为 `neededCountries` 不再被引用。
我(原文作者)已经将我们讨论的所有概念汇总到一个程序中,你可以从 [github](https://github.com/golangbot/arraysandslices) 下载。 我(原文作者)已经将我们讨论的所有概念汇总到一个程序中,你可以从 [github](https://github.com/golangbot/arraysandslices) 下载。
@ -666,9 +666,9 @@ func main() {
[深入解析 Go 中 Slice 底层实现](https://halfrost.com/go_slice/) [深入解析 Go 中 Slice 底层实现](https://halfrost.com/go_slice/)
###### 创建(定义)数组 ###### 创建(定义)数组
数组在Go中是值类型,而不是引用(其他语言的数组则是引用类型) 数组在Go中是值类型,而不是引用(其他语言的数组则是引用类型)
PS:切片(slice)是一个引用类型
PS:切片(slice)是一个引用类型。数组不是统一的类型,大小不同的数组是不可以比较的,不同数组类型是不可以比较的 数组不是统一的类型,大小不同的数组是不可以比较的
不同数组类型是不可以比较的
```golang ```golang
var a[2]int var a[2]int
var b[3]string var b[3]string
@ -689,9 +689,9 @@ var d = [...]int{19:90} // 尽可能的满足索引值得数组
a := [...]int{19:100} a := [...]int{19:100}
// 指向数组的指针 // 指向数组的指针
var p *[20]int = &a //长度为20的int型数组,这里的数组长度 `20` 必须和a数组长度相等 var p *[20]int = &a //长度为20的int型数组,这里的数组长度`20`必须和a数组长度相等
fmt.Println(p) //以上表示取这样一个数组的地址 fmt.Println(p) //以上表示取这样一个数组的地址
// 打印结果:&[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 100] // 打印结果:&[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 19]
//指向指针的数组 //指向指针的数组
x , y := 1, 2 x , y := 1, 2
@ -702,7 +702,7 @@ fmt.Println("pp is ",pp) // 打印结果:pp is [0xc420012128 0xc420012130]
```golang ```golang
ppp := new([10]int) ppp := new([10]int)
fmt.Println("----ppp-------",ppp); //输出结果:`----ppp------- &[0 0 0 0 0 0 0 0 0 0]` fmt.Println("----ppp-------",ppp); //输出结果:`&[0 0 0 0 0 0 0 0 0 0]`
//以上为指向数组的指针 //以上为指向数组的指针
``` ```
@ -712,12 +712,12 @@ fmt.Println("----ppp-------",ppp); //输出结果:`----ppp------- &[0 0 0 0 0
// 第一种方式 // 第一种方式
n := [10]int{} n := [10]int{}
n[1] = 10 n[1] = 10
fmt.Println("-----1------",n) // 输出:`-----1------ [0 10 0 0 0 0 0 0 0 0]` fmt.Println("-----1------",n) // 输出:`[0 10 0 0 0 0 0 0 0 0]`
// 第二种方式 // 第二种方式
m := new([10]int) m := new([10]int)
m[1] = 20 m[1] = 20
fmt.Println("-----2------",m) // 输出:`-----2------ &[0 20 0 0 0 0 0 0 0 0]` fmt.Println("-----2------",m) // 输出:`&[0 20 0 0 0 0 0 0 0 0]`
``` ```
###### 多维数组 ###### 多维数组
@ -804,7 +804,7 @@ func main() {
s7 := []byte{'a','b','c','d','e','f','g','h','i','j','k'} // 切片底层对应的数组 s7 := []byte{'a','b','c','d','e','f','g','h','i','j','k'} // 切片底层对应的数组
slice_a := s7[2:5] slice_a := s7[2:5]
fmt.Println(slice_a) // 输出的ascii码 值 [99 100 101] fmt.Println(slice_a) // 输出的assica码 值 [99 100 101]
fmt.Println(string(slice_a)) // 格式化为字符串输出 fmt.Println(string(slice_a)) // 格式化为字符串输出
fmt.Println(len(slice_a),cap(slice_a)) fmt.Println(len(slice_a),cap(slice_a))
@ -817,7 +817,7 @@ func main() {
s8 = append(s8, 12, 48) s8 = append(s8, 12, 48)
fmt.Printf("%v %p", s8, s8) // 格式化打印值和内存地址:[0 0 0 12 48] 0xc042074030 fmt.Printf("%v %p", s8, s8) // 格式化打印值和内存地址:[0 0 0 12 48] 0xc042074030
// 追加的元素如果没有超过切片容量,则切片的地址是不变的,否则内存地址会变 // 追加的元素如果没有超多切片容量,则切片的地址是不变的,否则内存地址会变
s8 = append(s8, 66, 88) s8 = append(s8, 66, 88)
fmt.Printf("%v %p\n", s8, s8) // [0 0 0 12 48 66 88] 0xc042048060 fmt.Printf("%v %p\n", s8, s8) // [0 0 0 12 48 66 88] 0xc042048060
@ -828,7 +828,7 @@ func main() {
fmt.Println(s9) //[33 44 55 4 5 6 7] fmt.Println(s9) //[33 44 55 4 5 6 7]
copy(s10,s9) copy(s10,s9)
fmt.Println(s10) // [1 2 3] fmt.Println(s10) // [33 44 55]
copy(s9[2:4],s10[0:2]) copy(s9[2:4],s10[0:2])
fmt.Println(s9) // [1 2 33 44 5 6 7] fmt.Println(s9) // [1 2 33 44 5 6 7]

@ -1,51 +1,20 @@
12 - 变参函数 11 - 变参函数
======================== ========================
上一节:[第十一篇 数组和切片](/docs/golang_tutorial_11.md) 上一节:[第十篇 if else 语句](/docs/golang_tutorial_10.md)
下一节:[第十三篇 Map](/docs/golang_tutorial_13.md) 下一节:[第十二篇 包](/docs/golang_tutorial_12.md)
这是本Golang系列教程的第12篇。 这是本Golang系列教程的第11篇。
## 什么是变参函数? ## 什么是变参函数?
变参函数是指可以接收可变数量的参数的函数。 变参函数是指可以接受可变数量的参数的函数。
如果一个函数的最后一个参数由 `...T` 表示,则表示该函数可以接收任意数量的类型为 T 的参数。 ## 语法?
请注意只有函数的最后一个参数才能指定为可变参数。 如果一个函数的最后一个参数由 `...T` 表示,则表示该函数可以接受任意数量的类型为 T 的参数。
## 语法
```golang
func hello(a int, b ...int){
}
```
上述函数中,`b` 就是一个可变参数,因为它的类型定义有 `...` 前缀,因而可以接收任意数量的输入。以下语句可以用来调用这个变参函数:
```golang 请注意只有函数的最后一个参数才能指定为可变参数。
hello(1, 2) //passing one argument "2" to b
hello(5, 6, 7, 8, 9) //passing arguments "6, 7, 8 and 9" to b
```
第一行中,我们将 `2` 赋给了 `b` ;第二行中,我们将四个参数 `6, 7, 8, 9` 赋给了 `b`。
我们也可以选择不将参数传入变参函数(即:传入“零”个参数),例如:
```golang
hello(1)
```
我们调用了 `hello` 函数,但是没有为 `b` 传入任何参数,这也是完全可以的。
为什么变参函数中的可变参数必须在定义参数时放在最后?作为反例,我们来尝试将 `hello` 函数的第一个参数变成可变参数:
```golang
func hello(b ...int, a int){
}
```
以上函数中,我们将无法为 `a` 传入参数, 因为无论我们在函数中传入多少参数,这些参数都会被赋给可变参数 `b`。上述函数会出现编译错误:`syntax error: cannot use ... with non-final parameter b`,因此可变参数必须在定义参数时被放在最后。
## 案例 ## 案例
@ -82,9 +51,7 @@ func main() {
} }
``` ```
上面的程序中,`func find(num int, nums ...int)` 可以为 `nums` 接收任意数量的参数。在函数内部,`nums` 的类型为 `[]int`,即整数切片类型。 上面的程序中,`func find(num int, nums ...int) `可以接受任意数量的参数。`...int `在内部表示为切片。在这里 `nums` 的类型为 `[]int`。
**变参函数会将可变数量的参数转换成相应类型的切片,例如上述函数的第22行,传入 `find` 函数的可变参数是 89,90,95,随后这些参数会被编译器转换为整数切片 `[]int{89, 90, 95}` 然后被送入 `find` 函数内。**
第 10 行利用 `range for` 遍历 `nums` 切片,如果找到 num 则打印 num 所在位置,否则打印没有找到。 第 10 行利用 `range for` 遍历 `nums` 切片,如果找到 num 则打印 num 所在位置,否则打印没有找到。
@ -104,52 +71,11 @@ type of nums is []int
87 not found in [] 87 not found in []
``` ```
在第 25 行,`find `只有一个参数。我们没有传递任何参数给 `nums ...int`。这是合法的,(译者注:如果没有给可变参数传递任何值,则可变参数为 `nil` 切片),在这里 `nums` 是一个 `nil` 切片,长度和容量都是`0`。 在第 25 行,`find `只有一个参数。我们没有传递任何参数给 `nums ...int`。这是合法的,(译者注:如果没有给可变参数传递任何值,则可变参数为 `nil `切片),在这里 `nums `是一个 `nil `切片,长度和容量都是`0`。
## 切片 VS 可变参数
我们现在肯定会想到一个问题:既然传入的可变参数被转换成了切片,那么我们为何不直接将一个切片传入函数中呢?我们将之前的函数用切片参数来改写:
```golang
package main
import (
"fmt"
)
func find(num int, nums []int) {
fmt.Printf("type of nums is %T\n", nums)
found := false
for i, v := range nums {
if v == num {
fmt.Println(num, "found at index", i, "in", nums)
found = true
}
}
if !found {
fmt.Println(num, "not found in ", nums)
}
fmt.Printf("\n")
}
func main() {
find(89, []int{89, 90, 95})
find(45, []int{56, 67, 45, 90, 109})
find(78, []int{38, 56, 98})
find(87, []int{})
}
```
用可变参数代替切片的优势如下:
1. 不用在每次调用函数时都创建一个新的切片。上述函数中,我们在第22、23、24、25行都创建了新的切片,如果使用变参函数,这些额外的切片创建就可以省略。
2. 在第25行,为了满足 `find` 函数的参数定义,我们创建了一个空的切片,在变参函数中,我们只需要写 `find(87)` 即可。
3. 我(原文作者)认为带变参函数的程序比带切片的函数可读性强 :)
## 将切片传入可变参数 ## 传递切片给可变参数
我们已经提到 `...T` 在内部表示为类型是 `[]T` 切片。如果真是这样,可以传递一个切片给可变参数吗?让我们从下面的例子中寻找答案: 我们已经提到 `...T `在内部表示为类型是 `[]T` 切片。如果真是这样,可以传递一个切片给可变参数吗?让我们从下面的例子中寻找答案:
```golang ```golang
package main package main
@ -178,9 +104,9 @@ func main() {
} }
``` ```
在第 23 行,我们没有将若干数量的参数传递给 `find` 函数的最后一个参数, 而是传递了一个切片。这是非法的,我们不能传递一个切片给可变参数。上面的程序将报错:`main.go:23: cannot use nums (type []int) as type int in argument to find`。 在第 23 行,我们没有将若干数量的参数传递给 `find` 的最后一个参数, 而是传递了一个切片。这是非法的,我们不能传递一个切片给可变参数。上面的程序将报错:`main.go:23: cannot use nums (type []int) as type int in argument to find`。
**有一个语法糖可以用来将切片传递给变参函数:在切片后面加 `...`,这样会将切片展开为其中的各个元素并将它们传递给变参函数,程序将正常工作。** 这里有一个语法糖可以用来将切片传递给变参函数。可以在切片后面加 `...`,这样会将切片展开为其中的各个元素并将它们传递给变参函数。这样该程序将正常工作。
上面的程序如果将第23行的 `find(89, nums)` 改为` find(89, nums...)`,程序将通过编译,并输出如下: 上面的程序如果将第23行的 `find(89, nums)` 改为` find(89, nums...)`,程序将通过编译,并输出如下:

@ -1,14 +1,14 @@
13 - Map 13 - Map 集合
======================== ========================
上一节:[第十二篇 变参函数](/docs/golang_tutorial_12.md) 上一节:[第十篇 if else 语句](/docs/golang_tutorial_10.md)
下一节:[第十四篇 字符串](/docs/golang_tutorial_14.md) 下一节:[第十二篇 包](/docs/golang_tutorial_12.md)
这是本Golang系列教程的第13篇。 这是本Golang系列教程的第13篇。
## 什么是 map? ## 什么是 map?
Map 是 Go 中的内置类型,它将键与值绑定到一起。可以通过键获取相应的值。Map与其他语言(例如Python)中的dictionary非常相似。 Map 是 Go 中的内置类型,它将键与值绑定到一起。可以通过键获取相应的值。
## 如何创建 map? ## 如何创建 map?
@ -18,9 +18,9 @@ Map 是 Go 中的内置类型,它将键与值绑定到一起。可以通过键
personSalary := make(map[string]int) personSalary := make(map[string]int)
``` ```
上面的代码创建了一个名为 `personSalary` 的 map。其中键的类型为 `string`,值的类型为 `int`。 上面的代码创建了一个名为 `personSalary` 的 map。其中键的类型为 string,值的类型为 int。
**map 的 0 值为 `nil`。试图给一个 nil map 添加元素给会导致运行时错误。因此 map 必须通过 `make` 来初始化** (译者注:也可以使用速记声明来创建 map,见下文)。 **map 的 0 值为 `nil`。试图给一个 nil map 添加元素给会导致运行时错误。因此 map 必须通过 make 来初始化** (译者注:也可以使用速记声明来创建 map,见下文)。
```golang ```golang
package main package main
@ -42,8 +42,7 @@ func main() {
## 向 map 中插入元素 ## 向 map 中插入元素
插入元素给 map 的语法与数组相似。下面的代码插入一些新的元素给名为 `personSalary` 的 map。 插入元素给 map 的语法与数组相似。下面的代码插入一些新的元素给 `map personSalary`。
```golang ```golang
package main package main
@ -62,7 +61,7 @@ func main() {
上面的程序输出:`personSalary map contents: map[steve:12000 jamie:15000 mike:9000]`。 上面的程序输出:`personSalary map contents: map[steve:12000 jamie:15000 mike:9000]`。
也可以在声明时初始化一个 map: 也可以在声明时初始化一个数组:
```golang ```golang
package main package main
@ -87,7 +86,7 @@ func main() {
personSalary map contents: map[steve:12000 jamie:15000 mike:9000] personSalary map contents: map[steve:12000 jamie:15000 mike:9000]
``` ```
`string` 并不是可以作为键的唯一类型,其他所有可以比较的类型,比如,布尔类型,整型,浮点型,复数类型都可以作为键。甚至用户自定义的类型,如 `struct`,也可以作为键。如果你想了解更多关于可比较类型的话,请参阅:http://golang.org/ref/spec#Comparison_operators `string` 并不是可以作为键的唯一类型,其他所有可以比较的类型,比如,布尔类型,整型,浮点型,复数类型都可以作为键。如果你想了解更多关于可比较类型的话,请参阅:http://golang.org/ref/spec#Comparison_operators
## 访问 map 中的元素 ## 访问 map 中的元素
@ -113,7 +112,7 @@ func main() {
上面的程序非常简单。员工 `jamie` 的工资被取出并打印。程序的输出为:`Salary of jamie is 15000`。 上面的程序非常简单。员工 `jamie` 的工资被取出并打印。程序的输出为:`Salary of jamie is 15000`。
如果一个键不存在会发生什么?`map` 会返回值类型的 `0` 值。比如如果访问了 `personSalary` 中的不存在的键,那么将返回 `int` 的 0 值,也就是 0。 如果一个键不存在会发生什么?`map` 会返回值类型的 `0 `值。比如如果访问了 `personSalary` 中的不存在的键,那么将返回 `int` 的 0 值,也就是 0。
```golang ```golang
package main package main
@ -141,7 +140,7 @@ Salary of jamie is 15000
Salary of joe is 0 Salary of joe is 0
``` ```
上面的程序返回 `joe` 的工资为` 0`。我们没有得到任何运行时错误说明键 `joe` 在 `personSalary` 中不存在。 上面的程序返回 `joe` 的工资为` 0`。我们没有得到任何运行时错误说明键 joe 在 `personSalary` 中不存在。
我们如何检测一个键是否存在于一个 map 中呢?可以使用下面的语法: 我们如何检测一个键是否存在于一个 map 中呢?可以使用下面的语法:
@ -181,9 +180,7 @@ func main() {
joe not found joe not found
``` ```
## 遍历 map 中的所有元素 range for 可用于遍历 map 中所有的元素(译者注:这里 range 操作符会返回 map 的键和值)。
`for` 循环的 `range` 形式可用于遍历 map 中所有的元素(译者注:这里 `range` 操作符会返回 map 的键和值)。
```golang ```golang
package main package main
@ -196,8 +193,8 @@ func main() {
personSalary := map[string]int{ personSalary := map[string]int{
"steve": 12000, "steve": 12000,
"jamie": 15000, "jamie": 15000,
"mike": 9000,
} }
personSalary["mike"] = 9000
fmt.Println("All items of a map") fmt.Println("All items of a map")
for key, value := range personSalary { for key, value := range personSalary {
fmt.Printf("personSalary[%s] = %d\n", key, value) fmt.Printf("personSalary[%s] = %d\n", key, value)
@ -214,11 +211,11 @@ personSalary[steve] = 12000
personSalary[jamie] = 15000 personSalary[jamie] = 15000
``` ```
值得注意的是,因为 map 是无序的,因此对于程序的每次执行,不能保证使用 `for range` 遍历 map 的顺序总是一致的,而且遍历的顺序也不完全与元素添加的顺序一致。 值得注意的是,因为 map 是无序的,因此对于程序的每次执行,不能保证使用 range for 遍历 map 的顺序总是一致的。
## 删除元素 ## 删除元素
`delete(map, key)` 用于删除 map 中的键。`delete` 函数没有返回值。 `delete(map, key) `用于删除 map 中的 key。delete 函数没有返回值。
```golang ```golang
package main package main
@ -247,62 +244,6 @@ map before deletion map[steve:12000 jamie:15000 mike:9000]
map after deletion map[mike:9000 jamie:15000] map after deletion map[mike:9000 jamie:15000]
``` ```
如果我们试图删除一个原 map 中不存在的键,运行 `delete` 时仍不会报错。
## 结构体 map (Map of Structs)
目前我们编写了存储员工工资的 map,那么可否将员工的国籍也存储在 map 中呢?这可以通过结构体 map 来完成。每个员工可以用一个包含了“工资”和“国籍”的结构体来表示,这些结构体将被存储在一个键为 `string` 类型,值为 `struct` 类型的 map 中。
```golang
package main
import (
"fmt"
)
type employee struct {
salary int
country string
}
func main() {
emp1 := employee{
salary: 12000,
country: "USA",
}
emp2 := employee{
salary: 14000,
country: "Canada",
}
emp3 := employee{
salary: 13000,
country: "India",
}
employeeInfo := map[string]employee{
"Steve": emp1,
"Jamie": emp2,
"Mike": emp3,
}
for name, info := range employeeInfo {
fmt.Printf("Employee: %s Salary:$%d Country: %s\n", name, info.salary, info.country)
}
}
```
以上程序中,`employee` 结构体包含了 `salary` 和 `country`。
在第25行,我们创建了一个 map,键为字符串类型,值为 `employee` 结构体,并添加了我们创建的三个员工。
在第31行,我们遍历这个 map,打印出所有员工的信息:
```golang
Employee: Mike Salary:$13000 Country: India
Employee: Steve Salary:$12000 Country: USA
Employee: Jamie Salary:$14000 Country: Canada
```
## map 的大小 ## map 的大小
用内置函数 `len` 获取 map 的大小: 用内置函数 `len` 获取 map 的大小:
@ -325,7 +266,7 @@ func main() {
} }
``` ```
上面程序中,`len(personSalary)` 获取 `personSalary` 的大小。上面的程序输出:`length is 3`。 上面程序中,`len(personSalary) `获取 `personSalary` 的大小。上面的程序输出:`length is 3`。
## map 是引用类型 ## map 是引用类型
@ -362,7 +303,7 @@ Person salary changed map[jamie:15000 mike:18000 steve:12000]
## 比较 map ## 比较 map
map 不能通过 `==` 操作符比较是否相等。`==` 操作符只能用来检测 map 是否为 `nil`。 map 不能通过 `== `操作符比较是否相等。`== `操作符只能用来检测 map 是否为 nil。
```golang ```golang
package main package main
@ -382,11 +323,11 @@ func main() {
上面的程序将会报错:`invalid operation: map1 == map2 (map can only be compared to nil)`。 上面的程序将会报错:`invalid operation: map1 == map2 (map can only be compared to nil)`。
比较两个 map 是否相等的方式是一一比较它们的元素是否相等。希望你能自己探索并编写一个达成这一功能的程序 :) 比较两个 map 是否相等的方式是一一比较它们的元素是否相等。我会鼓励你为此编写一个程序,使其工作:)
我(原文作者)已经将我们讨论的所有概念汇总到一个程序中,你可以从 [github](https://github.com/golangbot/arraysandslices) 下载。 我(原文作者)已经将我们讨论的所有概念汇总到一个程序中,你可以从 [github](https://github.com/golangbot/arraysandslices) 下载。
希望你喜欢阅读。请留下宝贵的意见和反馈 :) 希望你喜欢阅读。请留下宝贵的意见和反馈:)
## 以下为扩展知识 ## 以下为扩展知识
@ -422,8 +363,6 @@ func main(){
"school":"BAT_UN" "school":"BAT_UN"
} }
fmt.Println(m4) fmt.Println(m4)
// 方式五
m5 := make(map[string][string]) m5 := make(map[string][string])
m5["name"] = "Linux" m5["name"] = "Linux"
m5["school"] = "Unix" m5["school"] = "Unix"
@ -458,7 +397,7 @@ func main(){
fmt.Println(m_b, ok) // OK b true fmt.Println(m_b, ok) // OK b true
// 迭代操作 // 迭代操作
s_map := make([]map[int]string,5) // 以 map 为元素的 slice 使用 make 创建一个切片,切片内的每个元素为一个 map s_map := make([]map[int]string,5) // 以 map 为元素的slice 使用 make 创建一个切片,元素的slic
for _,v := range s_map { for _,v := range s_map {
v = make(map[int]string) // v 是值的拷贝 v = make(map[int]string) // v 是值的拷贝
v[1] = "OK" v[1] = "OK"

@ -2,16 +2,16 @@
14 - 字符串 14 - 字符串
======================== ========================
上一节:[第十三篇 Map](/docs/golang_tutorial_13.md) 上一节:[第十篇 if else 语句](/docs/golang_tutorial_10.md)
下一节:[第十五篇 指针](/docs/golang_tutorial_15.md) 下一节:[第十二篇 包](/docs/golang_tutorial_12.md)
这是本Golang系列教程的第14篇。 这是本Golang系列教程的第14篇。
`string` 类型单独提取为一篇教程是因为在 Go 中,`string` 的实现方式同其他语言的不同。 string 类型单独提取为一篇教程是因为在 Go 中,string 的实现方式同其他语言的不同。
## 访问字符串中的字节 ## 什么是字符串?
因为字符串是字节数组,因此可以访问一个字符串中的字节。 在 Go 中字符串是 byte 数组。可以通过将内容放在双引号 "" 之间的方式来创建一个字符串。让我们看一个简单的例子,该例子创建并打印一个字符串:
```golang ```golang
package main package main
@ -20,23 +20,26 @@ import (
"fmt" "fmt"
) )
// 打印字符串中的字节
func printBytes(s string) {
for i := 0; i < len(s); i++ {
fmt.Printf("%x ", s[i])
}
}
func main() { func main() {
name := "Hello World" name := "Hello World"
printBytes(name) fmt.Println(name)
} }
``` ```
在上面的程序中,`len(s)` 返回字符串中的字节数,我们用一个 `for` 循环以 16 进制打印这些字节。`%x` 格式化指示符用来以 16 进制打印参数。上面的程序打印:`48 65 6c 6c 6f 20 57 6f 72 6c 64`。它们是 `"Hello World"` 以 `UTF-8` 方式编码的 `Unicode` 值。对 `Unicode` 字符集和 `UTF-8` 编码有一个基本的了解会更好的理解 `string` 类型。我(原文作者)建议大家阅读:https://naveenr.net/unicode-character-set-and-utf-8-utf-16-utf-32-encoding/ 来学习什么是 `Unicode` 和 `UTF-8`。 上面输出:`Hello World`。
Go 中的字符串符合 Unicode 标准,并以 UTF-8 编码。
## 如何创建 map?
可以通过将键和值的类型传递给内置函数 `make` 来创建一个 `map`。语法为:`make(map[KeyType]ValueType)`。(译者注:`map` 的类型表示为 `map[KeyType]ValueType`)例如:
```golang
personSalary := make(map[string]int)
```
## 访问字符串中的字符 上面的代码创建了一个名为 `personSalary` 的 map。其中键的类型为 string,值的类型为 int。
让我们修改上面的程序以打印字符串中的字符: **map 的 0 值为 `nil`。试图给一个 nil map 添加元素给会导致运行时错误。因此 map 必须通过 make 来初始化** (译者注:也可以使用速记声明来创建 map,见下文)。
```golang ```golang
package main package main
@ -45,35 +48,39 @@ import (
"fmt" "fmt"
) )
func printBytes(s string) { func main() {
for i:= 0; i < len(s); i++ { var personSalary map[string]int
fmt.Printf("%x ", s[i]) if personSalary == nil {
fmt.Println("map is nil. Going to make one.")
personSalary = make(map[string]int)
} }
} }
```
上面的程序中,`personSalary` 为 `nil`,因此使用 make 初始化它。程序的输出为:`map is nil. Going to make one`.
func printChars(s string) { ## 向 map 中插入元素
for i:= 0; i < len(s); i++ {
fmt.Printf("%c ",s[i]) 插入元素给 map 的语法与数组相似。下面的代码插入一些新的元素给 `map personSalary`。
} ```golang
} package main
import (
"fmt"
)
func main() { func main() {
name := "Hello World" personSalary := make(map[string]int)
printBytes(name) personSalary["steve"] = 12000
fmt.Printf("\n") personSalary["jamie"] = 15000
printChars(name) personSalary["mike"] = 9000
fmt.Println("personSalary map contents:", personSalary)
} }
``` ```
在第 16 行的 `printChars` 函数中,`%c` 格式化指示符用来打印字符串中的字符。上面的程序输出为: 上面的程序输出:`personSalary map contents: map[steve:12000 jamie:15000 mike:9000]`。
```golang
48 65 6c 6c 6f 20 57 6f 72 6c 64
Hello World
```
虽然上面的程序看起来是一种合法的打印字符串中各个字符的方法,但是这里有一个严重的错误。让我们深入这段代码看看究竟是哪里不对。 也可以在声明时初始化一个数组:
```golang ```golang
package main package main
@ -82,45 +89,27 @@ import (
"fmt" "fmt"
) )
func printBytes(s string) {
for i:= 0; i < len(s); i++ {
fmt.Printf("%x ", s[i])
}
}
func printChars(s string) {
for i:= 0; i < len(s); i++ {
fmt.Printf("%c ",s[i])
}
}
func main() { func main() {
name := "Hello World" personSalary := map[string]int {
printBytes(name) "steve": 12000,
fmt.Printf("\n") "jamie": 15000,
printChars(name) }
fmt.Printf("\n") personSalary["mike"] = 9000
name = "Señor" fmt.Println("personSalary map contents:", personSalary)
printBytes(name)
fmt.Printf("\n")
printChars(name)
} }
``` ```
上面程序的输出为: 上面的程序在声明 personSalary 的同时向其中插入了两个元素。接着插入了一个以 "mike" 为键的元素。程序的输出为:
```golang ```golang
48 65 6c 6c 6f 20 57 6f 72 6c 64 personSalary map contents: map[steve:12000 jamie:15000 mike:9000]
Hello World
53 65 c3 b1 6f 72
Señor
``` ```
我们尝试将 `"Señor"` 中的每个字符打印出来,但是却得到了 `"Señor"`。为什么这个程序在 `"Hello World"` 上运行正常,但是不适用于 `"Señor"` 呢?因为 `ñ` 的 `Unicode` 码点是 `U+00F1`,因而它的 `UTF-8` 编码占了两个字节:`c3` 和 `b1`。上面的程序假定每个码点只有一个字节长度,因此会发生错误。**在 `UTF-8` 编码中,一个码点可能会占一个以上的字节。**在这种情况下,我们需要 `rune` 来帮助解决问题。 `string` 并不是可以作为键的唯一类型,其他所有可以比较的类型,比如,布尔类型,整型,浮点型,复数类型都可以作为键。如果你想了解更多关于可比较类型的话,请参阅:http://golang.org/ref/spec#Comparison_operators
## rune ## 访问 map 中的元素
`rune` 是 Go 中的内置类型,它是 `int32` 的别名。在 Go 中,`rune` 表示一个 `Unicode` 码点。无论一个码点会被编码为多少个字节,它都可以表示为一个 `rune`。让我们修改上面的程序,使用 `rune` 来打印字符串中的字符。 现在我们已经添加了一些元素给 map,现在让我们学习如何从 map 中提取它们。根据键获取值的语法为:`map[key]`,例如:
```golang ```golang
package main package main
@ -129,46 +118,56 @@ import (
"fmt" "fmt"
) )
func printBytes(s string) { func main() {
for i:= 0; i < len(s); i++ { personSalary := map[string]int{
fmt.Printf("%x ", s[i]) "steve": 12000,
"jamie": 15000,
} }
personSalary["mike"] = 9000
employee := "jamie"
fmt.Println("Salary of", employee, "is", personSalary[employee])
} }
```
func printChars(s string) { 上面的程序非常简单。员工 `jamie` 的工资被取出并打印。程序的输出为:`Salary of jamie is 15000`。
runes := []rune(s)
for i:= 0; i < len(runes); i++ { 如果一个键不存在会发生什么?`map` 会返回值类型的 `0 `值。比如如果访问了 `personSalary` 中的不存在的键,那么将返回 `int` 的 0 值,也就是 0。
fmt.Printf("%c ",runes[i])
} ```golang
} package main
import (
"fmt"
)
func main() { func main() {
name := "Hello World" personSalary := map[string]int{
printBytes(name) "steve": 12000,
fmt.Printf("\n") "jamie": 15000,
printChars(name) }
fmt.Printf("\n\n") personSalary["mike"] = 9000
name = "Señor" employee := "jamie"
printBytes(name) fmt.Println("Salary of", employee, "is", personSalary[employee])
fmt.Printf("\n") fmt.Println("Salary of joe is", personSalary["joe"])
printChars(name)
} }
``` ```
在上面的程序中,第 14 行,字符串被转换为 `rune` 切片。然后我们遍历该切片并打印其中的字符。程序的输出如下: 上面的程序输出为:
```golang ```golang
48 65 6c 6c 6f 20 57 6f 72 6c 64 Salary of jamie is 15000
Hello World Salary of joe is 0
53 65 c3 b1 6f 72
Señor
``` ```
上面的输出是正确的,这正是我们想要的结果。 上面的程序返回 `joe` 的工资为` 0`。我们没有得到任何运行时错误说明键 joe 在 `personSalary` 中不存在。
## 使用 for range 遍历字符串 我们如何检测一个键是否存在于一个 map 中呢?可以使用下面的语法:
上面的程序是遍历字符串中字符的一个正确方式。但是 Go 提供了一种更简单的方式来做到这一点:使用 `for range` 循环。 ```golang
value, ok := map[key]
```
上面的语法可以检测一个特定的键是否存在于 map 中。如果 `ok` 是 `true`,则键存在,value 被赋值为对应的值。如果 `ok` 为 `false`,则表示键不存在。
```golang ```golang
package main package main
@ -177,31 +176,30 @@ import (
"fmt" "fmt"
) )
func printCharsAndBytes(s string) { func main() {
for index, rune := range s { personSalary := map[string]int{
fmt.Printf("%c starts at byte %d\n", rune, index) "steve": 12000,
"jamie": 15000,
}
personSalary["mike"] = 9000
newEmp := "joe"
value, ok := personSalary[newEmp]
if ok == true {
fmt.Println("Salary of", newEmp, "is", value)
} else {
fmt.Println(newEmp,"not found")
} }
}
func main() {
name := "Señor"
printCharsAndBytes(name)
} }
``` ```
在上面的程序中,第 8 行通过使用 `for range` 循环遍历字符串。该循环返回一个 `rune` (在 `byte` 数组中)的位置,以及 `rune` 本身。上面的程序输出为: 在上面的程序中,第 15 行,`ok` 应该为 `false` ,因为 `joe` 不存在。因此程序的输出为:
```golang ```golang
S starts at byte 0 joe not found
e starts at byte 1
ñ starts at byte 2
o starts at byte 4
r starts at byte 5
``` ```
从上面的输出可以看到,ñ 占两个字节 :) range for 可用于遍历 map 中所有的元素(译者注:这里 range 操作符会返回 map 的键和值)。
## 通过 `byte` 切片创建字符串
```golang ```golang
package main package main
@ -211,15 +209,32 @@ import (
) )
func main() { func main() {
byteSlice := []byte{0x43, 0x61, 0x66, 0xC3, 0xA9} personSalary := map[string]int{
str := string(byteSlice) "steve": 12000,
fmt.Println(str) "jamie": 15000,
}
personSalary["mike"] = 9000
fmt.Println("All items of a map")
for key, value := range personSalary {
fmt.Printf("personSalary[%s] = %d\n", key, value)
}
} }
``` ```
在上面的程序中,`byteSlice` 是 `"Café"` 经过 `UTF-8` 编码后得到的切片(用 16 进制表示) 。上面的程序输出为:`Café`。 上面的程序输出如下:
```golang
All items of a map
personSalary[mike] = 9000
personSalary[steve] = 12000
personSalary[jamie] = 15000
```
值得注意的是,因为 map 是无序的,因此对于程序的每次执行,不能保证使用 range for 遍历 map 的顺序总是一致的。
如果我们换成对应的十进制数程序会正常工作吗?答案是:`Yes`。让我们测试一下: ## 删除元素
`delete(map, key) `用于删除 map 中的 key。delete 函数没有返回值。
```golang ```golang
package main package main
@ -229,15 +244,28 @@ import (
) )
func main() { func main() {
byteSlice := []byte{67, 97, 102, 195, 169} //decimal equivalent of {'\x43', '\x61', '\x66', '\xC3', '\xA9'} personSalary := map[string]int{
str := string(byteSlice) "steve": 12000,
fmt.Println(str) "jamie": 15000,
}
personSalary["mike"] = 9000
fmt.Println("map before deletion", personSalary)
delete(personSalary, "steve")
fmt.Println("map after deletion", personSalary)
} }
``` ```
上面的程序同样输出:`Café`。 上面的程序删除以 `steve` 为键的元素。程序输出为:
```golang
map before deletion map[steve:12000 jamie:15000 mike:9000]
map after deletion map[mike:9000 jamie:15000]
```
## map 的大小
## 通过 `rune` 切片创建字符串 用内置函数 `len` 获取 map 的大小:
```golang ```golang
package main package main
@ -247,89 +275,164 @@ import (
) )
func main() { func main() {
runeSlice := []rune{0x0053, 0x0065, 0x00f1, 0x006f, 0x0072} personSalary := map[string]int{
str := string(runeSlice) "steve": 12000,
fmt.Println(str) "jamie": 15000,
}
personSalary["mike"] = 9000
fmt.Println("length is", len(personSalary))
} }
``` ```
在上面的程序中,`runeSlice` 包含了字符串 `"Señor"` 的 `Unicode` 码点(以 16 进制表示)。程序的输出为:`Señor`。 上面程序中,`len(personSalary) `获取 `personSalary` 的大小。上面的程序输出:`length is 3`。
## 字符串的长度 ## map 是引用类型
utf8 包 提供了 `func RuneCountInString(s string) (n int)` 来获取字符串的长度,该方法接受一个字符串作为参数,并返回该字符串中 `rune` 的数量。 与切片一样,map 是引用类型。当一个 map 赋值给一个新的变量,它们都指向同一个内部数据结构。因此改变其中一个也会反映到另一个:
(译者注:`RuneCountInString` 返回字符串中 `Unicode` 字符的个数,而 `len` 返回字符串中 `byte` 的个数。因此用 `len` 来获取字符串长度可能会导致错误。)
```golang ```golang
package main package main
import ( import (
"fmt" "fmt"
"unicode/utf8"
) )
func length(s string) {
fmt.Printf("length of %s is %d\n", s, utf8.RuneCountInString(s))
}
func main() { func main() {
personSalary := map[string]int{
word1 := "Señor" "steve": 12000,
length(word1) "jamie": 15000,
word2 := "Pets" }
length(word2) personSalary["mike"] = 9000
fmt.Println("Original person salary", personSalary)
newPersonSalary := personSalary
newPersonSalary["mike"] = 18000
fmt.Println("Person salary changed", personSalary)
} }
``` ```
上面程序的输出为: 上面的程序中,第 14 行,`personSalary` 赋值给 `newPersonSalary`。下一行,将 `newPersonSalary` 中 `mike` 的工资改为 `18000`。那么在 `personSalary` 中 `mike` 的工资也将变为 `18000`。程序的输出如下:
```golang ```golang
length of Señor is 5 Original person salary map[steve:12000 jamie:15000 mike:9000]
length of Pets is 4 Person salary changed map[jamie:15000 mike:18000 steve:12000]
``` ```
## 字符串是不可变的 将 map 作为参数传递给函数也是一样的。在函数中对 map 的任何修改都会影响在调用函数中看到。
## 比较 map
在 Go 中字符串是不可变的。字符串一旦被创建就无法改变。 map 不能通过 `== `操作符比较是否相等。`== `操作符只能用来检测 map 是否为 nil。
```golang ```golang
package main package main
import (
"fmt"
)
func mutate(s string)string {
s[0] = 'a'//any valid unicode character within single quote is a rune
return s
}
func main() { func main() {
h := "hello" map1 := map[string]int{
fmt.Println(mutate(h)) "one": 1,
"two": 2,
}
map2 := map1
if map1 == map2 {
}
} }
``` ```
上面的程序中,第 8 行我们试图改变字符串的第一个字符为 `a`。任何在单引号内的 `Unicode` 字符都是 `rune`。由于字符串是不可变的,因此这是非法的,将会报错:`main.go:8: cannot assign to s[0]`。 上面的程序将会报错:`invalid operation: map1 == map2 (map can only be compared to nil)`。
比较两个 map 是否相等的方式是一一比较它们的元素是否相等。我会鼓励你为此编写一个程序,使其工作:)
**为了改变一个字符串中的字符,我们需要先把字符串转换为 `rune` 切片,然后修改切片中的内容,最后将这个切片转换回字符串**。 我(原文作者)已经将我们讨论的所有概念汇总到一个程序中,你可以从 [github](https://github.com/golangbot/arraysandslices) 下载。
## 知识扩展
[Go编程基础视频教程笔记](https://study.163.com/course/courseLearn.htm?courseId=306002#/learn/video?lessonId=421019&courseId=306002)
```golang ```golang
package main package main
import ( import (
"fmt" "fmt"
"sort"
) )
func mutate(s []rune) string { func main(){
s[0] = 'a' // 方式一
return string(s) var m map[int]string // 声明一个map
} fmt.Println(m)
func main() { m = map[int]string{} // 初始化一个map
h := "hello" fmt.Println(m)
fmt.Println(mutate([]rune(h)))
// 方式二
var m2 map[int]string = map[int]string{}
fmt.Println(m2)
// 方式三
m3 := map[int]string{}
fmt.Println(m3)
// 设置、获取、删除
m3[1] = "Tinywan"
a := m3[1]
fmt.Println(m3) // map[1:Tinywan]
fmt.Println(a) // Tinywan
delete(m3,1) // 删除一个map
fmt.Println(m3) // map[]
// 复杂map 的操作
var m5 map[int]map[int]string // 定义
m5 = make(map[int]map[int]string) // 通过 make 初始化 最外层的 map
m5[1] = make(map[int]string) // 针对外层value 的map进行初始化
m5[1][1] = "OK"
m_a := m5[1][1] // 取出map 的值赋予一个变量
fmt.Println(m_a) // OK
// 判断一个map 有没有被初始化,使用多返回值判断
m_b, ok := m5[2][1]
// 判断是否被初始化操作
if !ok {
m5[2] = make(map[int]string)
}
m5[2][1] = "OK b"
m_b,ok = m5[2][1]
fmt.Println(m_b, ok) // OK b true
// 迭代操作
s_map := make([]map[int]string,5) // 以 map 为元素的slice 使用 make 创建一个切片,元素的slic
for _,v := range s_map {
v = make(map[int]string) // v 是值的拷贝
v[1] = "OK"
fmt.Println(v);
}
fmt.Println(s_map)
// 针对一个 map 直接操作
for i := range s_map {
s_map[i] = make(map[int]string)
s_map[i][1] = "OK"
fmt.Println(s_map[i]);
}
fmt.Println(s_map)
// map 的间接排序
// map 集合
map01 := map[int]string{1:"a", 2:"b", 3:"n", 4:"c", 5:"p", 6:"f"}
// 切片
slice01 := make([]int, len(map01))
i := 0
for k, _ := range map01 {
slice01[i] = k
i++
}
fmt.Println(slice01) // 返回的是一个无序的数组:[5 6 1 2 3 4] [3 4 5 6 1 2]
sort.Ints(slice01)
fmt.Println(slice01) // 有序的数组:[1 2 3 4 5 6]
} }
``` ```
在上面的程序中,第 7 行 `mutate` 函数接收一个 `rune` 切片作为参数。然后将该切片的第一个元素改为 `a`,最后再转换回字符串并返回。该函数在程序中的第 13 行被调用。`h` 被转换为一个 `rune` 切片传递给 `mutate`。程序的输出为:`aello`。 希望你喜欢阅读。请留下宝贵的意见和反馈:)
字符串的介绍到此为止。感谢阅读。

@ -1,19 +0,0 @@
18-1 - 接口1
========================
上一节:[第十六篇 结构体](/docs/golang_tutorial_16.md)
下一节:[第十八篇 接口一](/docs/golang_tutorial_18.md)
这是本Golang系列教程的第17篇。
## 什么是方法?
一个方法只是一个函数,它有一个特殊的接收者(`receiver`)类型,该接收者放在 `func` 关键字和函数名之间。接收者可以是结构体类型或非结构体类型。可以在方法内部访问接收者。
通过下面的语法创建一个方法:
```golang
func (t Type) methodName(parameter list) {
}
```

@ -1,441 +0,0 @@
22 - 信道
========================
上一节:[第十六篇 结构体](/docs/golang_tutorial_16.md)
下一节:[第十八篇 接口一](/docs/golang_tutorial_18.md)
这是本Golang系列教程的第22篇。
在[上一篇教程](http://blog.csdn.net/u011304970/article/details/75096323)中,我们讨论了如何使用协程实现并发。在这篇教程中,我们将讨论信道以及如何使用信道实现协程间通信。
## 什么是信道?
信道`(Channel)`可以被认为是协程之间通信的管道。与水流从管道的一端流向另一端一样,数据可以从信道的一端发送并在另一端接收。
## 声明信道
每个信道都有一个与之关联的类型。此类型是允许信道传输的数据类型,除此类型外不能通过信道传输其他类型。
`chan T` 是一个 `T` 类型的信道。
信道的 `0` 值为 `nil`。值为 `nil` 的信道变量没有任何用处,我们需要通过内置函数 `make` 来创建一个信道,就像创建 `map` 和 `slice` 一样。
下面的代码声明了一个信道:
```golang
package main
import "fmt"
func main() {
var a chan int
if a == nil {
fmt.Println("channel a is nil, going to define it")
a = make(chan int)
fmt.Printf("Type of a is %T", a)
}
}
```
因为信道的 `0` 值为 `nil`,因此第 6 行声明的信道 `a` 的值为 `nil`。因此执行 `if` 里面的语句创建信道。上面的程序中 `a` 是一个 `int` 类型的信道。程序的输出为:
```golang
channel a is nil, going to define it
Type of a is chan int
```
像往常一样,速记声明也是定义信道的一种有效而简洁的方式:
```golang
a := make(chan int)
```
上面的这行代码同样定义了一个 `int` 型的**信道**。
## 通过信道发送和接收数据
通过信道发送和接收数据的语法如下:
```golang
data := <- a // read from channel a
a <- data // write to channel a
```
箭头的指向说明了数据是发送还是接收。
在第一行,**箭头**的方向是从 `a` 向外指,因此我们正在从信道 `a` 中读取数据并将读取的值赋值给变量 `data` 。
在第二行,**箭头**的方式是指向 `a` ,因此我们正在向信道 `a` 中写入数据。
## 发送和接收默认是阻塞的
通过信道发送和接收数据默认是阻塞的。这是什么意思呢?当数据发送给信道后,程序流程在发送语句处阻塞,直到其他协程从该信道中读取数据。
同样地,当从信道读取数据时,程序在读取语句处阻塞,直到其他协程发送数据给该信道。
信道的这种特性使得协程间通信变得高效,而不是向其他编程语言一样,显式的使用锁和条件变量来达到此目的。
## 信道的一个例子
理论到此为止:) 让我们通过一个程序来理解协程之间如何使用信道进行通信。
我们将用信道来重写在上一篇教程协程中的一个例子。
如下是那篇教程中的一个例子:
```golang
package main
import (
"fmt"
"time"
)
func hello() {
fmt.Println("Hello world goroutine")
}
func main() {
go hello()
time.Sleep(1 * time.Second)
fmt.Println("main function")
}
```
这是上一篇教程中的例子,我们通过使用 `Sleep` 来使主协程休眠,以等待 `hello` 协程执行结束。如果你不明白这是为什么,请阅读上一篇教程:`协程` 。
我们用信道重写上面的程序,如下:
```golang
package main
import (
"fmt"
)
func hello(done chan bool) {
fmt.Println("Hello world goroutine")
done <- true
}
func main() {
done := make(chan bool)
go hello(done)
<-done
fmt.Println("main function")
}
```
在上面的程序中,我们在第 12 行定义了一个 `bool` 类型的信道 `done`,然后将它作为参数传递给 `hello` 协程。在第 14 行,我们从信道 `done` 中读取数据。程序将在这一行被阻塞直到其他协程向信道 `done` 里写入数据,在未读取到数据之前程序将在这一行一直等待而不会执行下一行语句。因此这里消除了在原程序中使用 `time.Sleep` 来阻止主协程退出的必要。
`<-done` 这一行从信道 `done` 中读取数据,但是没有使用该数据,也没有将它赋值给其他变量,这是完全合法的。
现在我们的 `main` 协程被阻塞,等待从信道 `done` 中读取数据。`hello` 协程接受信道 `done` 作为参数,打印 `Hello world goroutine` 然后将数据写入信道 `done` 中。当写入完毕后,`main` 协程从信道 `done` 中接收到数据,`main` 协程解除阻塞,继续执行下一条语句,打印:`main function`。
程序的输出为:
```golang
Hello world goroutine
main function
```
让我们修改上面程序,在 `hello` 协程中加入一个休眠,来更好的理解阻塞的概念。
```golang
package main
import (
"fmt"
"time"
)
func hello(done chan bool) {
fmt.Println("hello go routine is going to sleep")
time.Sleep(4 * time.Second)
fmt.Println("hello go routine awake and going to write to done")
done <- true
}
func main() {
done := make(chan bool)
fmt.Println("Main going to call hello go goroutine")
go hello(done)
<-done
fmt.Println("Main received data")
}
```
在上面程序中的第 10 行,我们在 `hello` 函数中增加了`4` 秒钟的休眠。
该程序首先打印 `Main going to call hello go goroutine` 。然后 `hello` 协程开始执行,它将打印 `hello go routine is going to sleep`,然后 `hello` 协程休眠 `4` 秒,在这期间, `main` 协程由于在等待从信道 `done` 中读取数据而始终阻塞(在 `<-done` 这一行)。`4` 秒中之后, `hello` 协程打印:`hello go routine awake and going to write to done`,接着 `main` 协程打印:`Main received data` 。
## 信道的另一个例子
让我们再写一个例子来更好的理解信道。该程序打印一个数字的每一位的**平方和**与**立方和**,并将平方和与立方和相加得出最后的结果。
例如,输入`123` ,程序将做如下计算以得出最后结果:
```golang
squares = (1 * 1) + (2 * 2) + (3 * 3)
cubes = (1 * 1 * 1) + (2 * 2 * 2) + (3 * 3 * 3)
output = squares + cubes = 49
```
我们将平方和的计算与立方和的计算分别放在一个协程中执行,最后在主协程中将它们的计算结果求和。
```golang
package main
import (
"fmt"
)
func calcSquares(number int, squareop chan int) {
sum := 0
for number != 0 {
digit := number % 10
sum += digit * digit
number /= 10
}
squareop <- sum
}
func calcCubes(number int, cubeop chan int) {
sum := 0
for number != 0 {
digit := number % 10
sum += digit * digit * digit
number /= 10
}
cubeop <- sum
}
func main() {
number := 589
sqrch := make(chan int)
cubech := make(chan int)
go calcSquares(number, sqrch)
go calcCubes(number, cubech)
squares, cubes := <-sqrch, <-cubech
fmt.Println("Final output", squares + cubes)
}
```
在第 7 行,函数 `calcSquares` 计算 `number` 每一位的平方和,并将结果发送给信道 `squareop`。
同样地,在第 17 行,函数 `calcCubes` 计算 `number` 每一位的立方和,并将结果发送给信道 `cubeop`。
这两个函数接受不同的信道作为参数,并分别运行在各自的协程中(第31行和32行),最后将结果写入各自的信道。主协程在第 33 行同时等待这两个信道中的数据。一旦从这两个信道中接收到数据,它们分别被存放在变量 `squares` 和 `cubes` 中,最后将它们的和打印出来。程序的输出为:
```golang
Final output 1536
```
## 死锁
使用信道是要考虑的一个重要因素是死锁`(Deadlock`)。如果一个协程发送数据给一个信道,而没有其他的协程从该信道中接收数据,那么程序在运行时会遇到死锁,并触发 `panic` 。
同样地,如果一个协程从一个信道中接收数据,而没有其他的协程向这个信道发送数据,那么程序同样造成死锁,触发 `panic` 。
```golang
package main
func main() {
ch := make(chan int)
ch <- 5
}
```
上面的程序中,创建了一个信道 `ch`,并通过 `ch <- 5` 向其中写入 `5` 。这个程序中没有其他协程从 `ch` 中接收数据,因此程序在运行时触发 `panic`,错误如下:
```golang
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main()
/tmp/sandbox249677995/main.go:6 +0x80
```
## 单向信道
目前我们讨论的信道都是双向信道,数据既可以发送到双向信道,也可以从双向信道中读取。同样也可以创建单向信道,即只能发送数据或只能接收数据的信道。
```golang
package main
import "fmt"
func sendData(sendch chan<- int) {
sendch <- 10
}
func main() {
sendch := make(chan<- int)
go sendData(sendch)
fmt.Println(<-sendch)
}
```
在上面程序中的第 10 行,我们创建了一个只写`(send only)`信道 `sendch` 。`chan<- int` 表示只能发送数据,因为箭头的方向指向 `chan`。在第 12 行,我们试图从一个只写信道中接收数据,这是非法的,程序将无法通过编译,编译器报错如下:
```golang
main.go:11: invalid operation: <-sendch (receive from send-only type chan<- int)
```
一切都很好,但是如果无法读取,创建一个只写通道有什么用呢?
这就是信道转型的用途。可以将双向信道转换为只写或只读信道,但是反过来却不行。
```golang
package main
import "fmt"
func sendData(sendch chan<- int) {
sendch <- 10
}
func main() {
chnl := make(chan int)
go sendData(chnl)
fmt.Println(<-chnl)
}
```
在上面程序中的第 10 行,创建了一个双向信道 `chnl`。在第 11 行它被作为参数传递给协程 `sendData`。第 5 行,`sendData` 函数通过形参 `sendch chan<- int` 将该信道转换成只写信道。因此在 `sendData` 中该信道为只写信道,而在主协程中该信道为双向信道。程序将打印:`10`。
## 关闭信道以及使用 range for 遍历信道
发送者可以关闭信道以通知接收者将不会再发送数据给信道。
在从信道接收数据时,接收者可以通过一个额外的变量来检测信道是否已经被关闭。
```golang
v, ok := <- ch
```
上面的语句中 `ok` 返回 `true` 表示成功的接收到了发送的数据,如果 `ok` 返回 `false` 则表示信道已经被关闭。从已关闭的信道中读取到的数据为信道类型的 `0` 值。比如从一个被关闭的 `int` 信道中读取数据,那么将得到 `0` 。
```golang
package main
import (
"fmt"
)
func producer(chnl chan int) {
for i := 0; i < 10; i++ {
chnl <- i
}
close(chnl)
}
func main() {
ch := make(chan int)
go producer(ch)
for {
v, ok := <-ch
if ok == false {
break
}
fmt.Println("Received ", v, ok)
}
}
```
上面的程序中,协程 `producer` 向信道 `chnl` 中写入 `0` 到 `9` 并关闭该信道。主协程在第 16 行进行无限 `for` 循环,并在循环中检测 `ok` 的值判断信道是否已经被关闭(第 18 行)。如果 `ok` 是 `false` 表示信道已经被关闭,则通过 `break` 退出循环。否则接收数据并打印 `ok` 的值。程序的输出为:
```golang
Received 0 true
Received 1 true
Received 2 true
Received 3 true
Received 4 true
Received 5 true
Received 6 true
Received 7 true
Received 8 true
Received 9 true
```
`range for` 可以用来接收一个信道中的数据,直到该信道关闭。
让我们用 `range for` 重写上面的程序:
```golang
package main
import (
"fmt"
)
func producer(chnl chan int) {
for i := 0; i < 10; i++ {
chnl <- i
}
close(chnl)
}
func main() {
ch := make(chan int)
go producer(ch)
for v := range ch {
fmt.Println("Received ",v)
}
}
```
在第6 行,`for range` 不断从信道 `ch` 中接收数据,直到该信道关闭。一旦 `ch` 关闭,循环自动退出。程序的输出如下:
```golang
Received 0
Received 1
Received 2
Received 3
Received 4
Received 5
Received 6
Received 7
Received 8
Received 9
```
如果仔细观察该程序,你可以注意到,在 `calcSquares` 和 `calcCubes` 函数中查找一个数的每一位的代码重复了。我们将这段代码提取到一个单独的函数,并异步调用它。
```golang
package main
import (
"fmt"
)
func digits(number int, dchnl chan int) {
for number != 0 {
digit := number % 10
dchnl <- digit
number /= 10
}
close(dchnl)
}
func calcSquares(number int, squareop chan int) {
sum := 0
dch := make(chan int)
go digits(number, dch)
for digit := range dch {
sum += digit * digit
}
squareop <- sum
}
func calcCubes(number int, cubeop chan int) {
sum := 0
dch := make(chan int)
go digits(number, dch)
for digit := range dch {
sum += digit * digit * digit
}
cubeop <- sum
}
func main() {
number := 589
sqrch := make(chan int)
cubech := make(chan int)
go calcSquares(number, sqrch)
go calcCubes(number, cubech)
squares, cubes := <-sqrch, <-cubech
fmt.Println("Final output", squares+cubes)
}
```
在上面的程序中,函数 `digits` 包含查找一个数的每一位的逻辑,该函数在 `calcSquares` 和 `calcCubes` 中异步调用。在第 13 行,当数字中没有更多的位数时,信道被关闭。`calcSquares` 和 `calcCubes` 分别在各自的信道上使用 `range for` 直到信道被关闭。程序中的其他部分都是一样的。程序的输出依然是:
```golang
Final output 1536
```
这就来到了本教程的最后。信道中还有更多的概念,比如缓冲信道,工作池和 `select` 。我们将在单独的教程中讨论它们。感谢阅读,祝你有美好的一天。
希望你喜欢阅读。请留下宝贵的意见和反馈:)

@ -1,100 +0,0 @@
35 - 读取文件
========================
<div align="center">
<img src="../images/golang-read-files.png"/><br><br>
</div>
上一节:[第十六篇 结构体](/docs/golang_tutorial_16.md)
下一节:[第十八篇 接口一](/docs/golang_tutorial_18.md)
这是本Golang系列教程的第35篇。
文件读取是在任何编程语言中执行的最常见操作之一。在本教程中,我们将了解如何使用Go读取文件。
本教程包含以下部分
* 将整个文件读入内存
* 使用绝对文件路径
* 将文件路径作为命令行标志传递
* 将文件捆绑在二进制文件中
* 以小块读取文件
* 逐行读取文件
## 将整个文件读入内存
最基本的文件操作之一是将整个文件读入内存。这是在[ioutil](https://golang.org/pkg/io/ioutil/)包的[ReadFile](https://golang.org/pkg/io/ioutil/#ReadFile)函数的帮助下完成的。
让我们从go程序所在的目录中读取一个文件。在`GOROOT`内部创建了一个文件夹,里面有一个文本文件`test.txt`,可以从我们的Go程序中读取`filehandling.go`。`test.txt`包含文本`“Hello World. Welcome to file handling in Go“`。这是我的文件夹结构。
```golang
src
filehandling
filehandling.go
test.txt
```
让我们马上看看代码吧。
```golang
package main
import (
"fmt"
"io/ioutil"
)
func main() {
data, err := ioutil.ReadFile("test.txt")
if err != nil {
fmt.Println("File reading error", err)
return
}
fmt.Println("Contents of file:", string(data))
}
```
请从当地环境运行此程序,因为无法在操场上阅读文件。
上面的程序中的9行,读取文件并返回存储在其中的字节片`data`。排队号码 14我们转换`data`为a string并显示文件的内容。
请从test.txt所在的位置运行该程序。
例如,在`linux / mac`的情况下,如果`test.txt`位于`home /naveen/go/src/filehandling`,则使用以下步骤运行该程序,
```golang
$]cd /home/naveen/go/src/filehandling/
$]go install filehandling
$]workspacepath/bin/filehandling
```
对于`windows`,如果`test.txt`位于`C:\ Users \ naveen.r \ go \ src \ filehandling`,则使用以下步骤运行此程序。
```golang
> cd C:\Users\naveen.r\go\src\filehandling
> go install filehandling
> workspacepath\bin\filehandling.exe
```
程序的输出为:
```golang
Contents of file: Hello World. Welcome to file handling in Go.
```
如果此程序从任何其他位置运行,例如尝试运行该程序`/home/userdirectory`,它将打印以下错误。
```golang
File reading error open test.txt: The system cannot find the file specified.
```
原因是Go是一种编译语言。`go install`它是什么,它从源代码创建二进制文件。二进制文件独立于源代码,可以从任何位置运行。由于`test.txt`在运行二进制文件的位置找不到,程序会抱怨它无法找到指定的文件。
有三种方法可以解决这个问题,
* 1、使用绝对文件路径
* 2、将文件路径作为命令行标志传递
* 3、将文本文件与二进制文件捆绑在一起
让我们一个一个讨论。
Go协程的介绍就到这里。祝你有美好的一天!
希望你喜欢阅读。请留下宝贵的意见和反馈:)

@ -1,3 +0,0 @@
module golang-tutorial
go 1.17

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 250 KiB

Loading…
Cancel
Save