Go 指针
最后更新于:2022-04-01 23:09:49
# Go 指针
Go支持指针,可以用来给函数传递变量的引用。
```go
package main
import "fmt"
// 我们用两个不同的例子来演示指针的用法
// zeroval函数有一个int类型参数,这个时候传递给函数的是变量的值
func zeroval(ival int) {
ival = 0
}
// zeroptr函数的参数是int类型指针,这个时候传递给函数的是变量的地址
// 在函数内部对这个地址所指向的变量的任何修改都会反映到原来的变量上。
func zeroptr(iptr *int) {
*iptr = 0
}
func main() {
i := 1
fmt.Println("initial:", i)
zeroval(i)
fmt.Println("zeroval:", i)
// &操作符用来取得i变量的地址
zeroptr(&i)
fmt.Println("zeroptr:", i)
// 指针类型也可以输出
fmt.Println("pointer:", &i)
}
```
输出结果为
```
initial: 1
zeroval: 1
zeroptr: 0
pointer: 0xc084000038
```
';
Go 正则表达式
最后更新于:2022-04-01 23:09:47
# Go 正则表达式
Go内置了对正则表达式的支持,这里是一般的正则表达式常规用法的例子。
```go
package main
import "bytes"
import "fmt"
import "regexp"
func main() {
// 测试模式是否匹配字符串,括号里面的意思是
// 至少有一个a-z之间的字符存在
match, _ := regexp.MatchString("p([a-z]+)ch", "peach")
fmt.Println(match)
// 上面我们直接使用了字符串匹配的正则表达式,
// 但是对于其他的正则匹配任务,你需要使用
// `Compile`来使用一个优化过的正则对象
r, _ := regexp.Compile("p([a-z]+)ch")
// 正则结构体对象有很多方法可以使用,比如上面的例子
// 也可以像下面这么写
fmt.Println(r.MatchString("peach"))
// 这个方法检测字符串参数是否存在正则所约束的匹配
fmt.Println(r.FindString("peach punch"))
// 这个方法查找第一次匹配的索引,并返回匹配字符串
// 的起始索引和结束索引,而不是匹配的字符串
fmt.Println(r.FindStringIndex("peach punch"))
// 这个方法返回全局匹配的字符串和局部匹配的字符,比如
// 这里会返回匹配`p([a-z]+)ch`的字符串
// 和匹配`([a-z]+)`的字符串
fmt.Println(r.FindStringSubmatch("peach punch"))
// 和上面的方法一样,不同的是返回全局匹配和局部匹配的
// 起始索引和结束索引
fmt.Println(r.FindStringSubmatchIndex("peach punch"))
// 这个方法返回所有正则匹配的字符,不仅仅是第一个
fmt.Println(r.FindAllString("peach punch pinch", -1))
// 这个方法返回所有全局匹配和局部匹配的字符串起始索引
// 和结束索引
fmt.Println(r.FindAllStringSubmatchIndex("peach punch pinch", -1))
// 为这个方法提供一个正整数参数来限制匹配数量
fmt.Println(r.FindAllString("peach punch pinch", 2))
//上面我们都是用了诸如`MatchString`这样的方法,其实
// 我们也可以使用`[]byte`作为参数,并且使用`Match`
// 这样的方法名
fmt.Println(r.Match([]byte("peach")))
// 当使用正则表达式来创建常量的时候,你可以使用`MustCompile`
// 因为`Compile`返回两个值
r = regexp.MustCompile("p([a-z]+)ch")
fmt.Println(r)
// regexp包也可以用来将字符串的一部分替换为其他的值
fmt.Println(r.ReplaceAllString("a peach", ""))
// `Func`变量可以让你将所有匹配的字符串都经过该函数处理
// 转变为所需要的值
in := []byte("a peach")
out := r.ReplaceAllFunc(in, bytes.ToUpper)
fmt.Println(string(out))
}
```
运行结果
```
true
true
peach
[0 5]
[peach ea]
[0 5 1 3]
[peach punch pinch]
[[0 5 1 3] [6 11 7 9] [12 17 13 15]]
[peach punch]
true
p([a-z]+)ch
a
a PEACH
```
';
Go 原子计数器
最后更新于:2022-04-01 23:09:44
# Go 原子计数器
Go里面的管理协程状态的主要机制就是通道通讯。这些我们上面的例子介绍过。这里还有一些管理状态的机制,下面我们看看多协程原子访问计数器的例子,这个功能是由sync/atomic包提供的函数来实现的。
```go
package main
import "fmt"
import "time"
import "sync/atomic"
import "runtime"
func main() {
// 我们使用一个无符号整型来代表一个永远为正整数的counter
var ops uint64 = 0
// 为了模拟并行更新,我们使用50个协程来每隔1毫秒来
// 增加一下counter值,注意这里的50协程里面的for循环,
// 也就是说如果主协程不退出,这些协程将永远运行下去
// 所以这个程序每次输出的值有可能不一样
for i := 0; i < 50; i++ {
go func() {
for {
// 为了能够保证counter值增加的原子性,我们使用
// atomic包中的AddUint64方法,将counter的地址和
// 需要增加的值传递给函数即可
atomic.AddUint64(&ops, 1)
// 允许其他的协程来处理
runtime.Gosched()
}
}()
}
//等待1秒中,让协程有时间运行一段时间
time.Sleep(time.Second)
// 为了能够在counter仍被其他协程更新值的同时安全访问counter值,
// 我们获取一个当前counter值的拷贝,这里就是opsFinal,需要把
// ops的地址传递给函数`LoadUint64`
opsFinal := atomic.LoadUint64(&ops)
fmt.Println("ops:", opsFinal)
}
```
我们多运行几次,结果如下:
```
ops: 7499289
ops: 7700843
ops: 7342417
```
';
Go 信号处理
最后更新于:2022-04-01 23:09:42
# Go 信号处理
有的时候我们希望Go能够智能地处理Unix信号。例如我们希望一个server接收到一个SIGTERM的信号时,能够自动地停止;或者一个命令行工具接收到一个SIGINT信号时,能够停止接收输入。现在我们来看下如何使用channel来处理信号。
```go
package main
import "fmt"
import "os"
import "os/signal"
import "syscall"
func main() {
// Go信号通知通过向一个channel发送``os.Signal`来实现。
// 我们将创建一个channel来接受这些通知,同时我们还用
// 一个channel来在程序可以退出的时候通知我们
sigs := make(chan os.Signal, 1)
done := make(chan bool, 1)
// `signal.Notify`在给定的channel上面注册该channel
// 可以接受的信号
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
// 这个goroutine阻塞等待信号的到来,当信号到来的时候,
// 输出该信号,然后通知程序可以结束了
go func() {
sig := <-sigs
fmt.Println()
fmt.Println(sig)
done <- true
}()
// 程序将等待接受信号,然后退出
fmt.Println("awaiting signal")
<-done
fmt.Println("exiting")
}
```
当运行程序的时候,程序将阻塞等待信号的到来,我们可以使用`CTRL+C`来发送一个`SIGINT`信号,这样程序就会输出interrupt后退出。
```
awaiting signal
interrupt
exiting
```
';
Go 写入文件
最后更新于:2022-04-01 23:09:40
# Go 写入文件
Go将数据写入文件的方法和上面介绍过的读取文件的方法很类似。
```go
package main
import (
"bufio"
"fmt"
"io/ioutil"
"os"
)
func check(e error) {
if e != nil {
panic(e)
}
}
func main() {
// 首先看一下如何将一个字符串写入文件
d1 := []byte("hello\ngo\n")
err := ioutil.WriteFile("/tmp/dat1", d1, 0644)
check(err)
// 为了实现细颗粒度的写入,打开文件后再写入
f, err := os.Create("/tmp/dat2")
check(err)
// 在打开文件后通常应该立刻使用defer来调用
// 打开文件的Close方法,以保证main函数结束
// 后,文件关闭
defer f.Close()
// 你可以写入字节切片
d2 := []byte{115, 111, 109, 101, 10}
n2, err := f.Write(d2)
check(err)
fmt.Printf("wrote %d bytes\n", n2)
// 也可以使用`WriteString`直接写入字符串
n3, err := f.WriteString("writes\n")
fmt.Printf("wrote %d bytes\n", n3)
// 调用Sync方法来将缓冲区数据写入磁盘
f.Sync()
// `bufio`除了提供上面的缓冲读取数据外,还
// 提供了缓冲写入数据的方法
w := bufio.NewWriter(f)
n4, err := w.WriteString("buffered\n")
fmt.Printf("wrote %d bytes\n", n4)
// 使用Flush方法确保所有缓冲区的数据写入底层writer
w.Flush()
}
```
运行结果
```
wrote 5 bytes
wrote 7 bytes
wrote 9 bytes
```
';
Go 通道选择Select
最后更新于:2022-04-01 23:09:38
# Go 通道选择Select
Go的select关键字可以让你同时等待多个通道操作,将协程(goroutine),通道(channel)和select结合起来构成了Go的一个强大特性。
```go
package main
import "time"
import "fmt"
func main() {
// 本例中,我们从两个通道中选择
c1 := make(chan string)
c2 := make(chan string)
// 为了模拟并行协程的阻塞操作,我们让每个通道在一段时间后再写入一个值
go func() {
time.Sleep(time.Second * 1)
c1 <- "one"
}()
go func() {
time.Sleep(time.Second * 2)
c2 <- "two"
}()
// 我们使用select来等待这两个通道的值,然后输出
for i := 0; i < 2; i++ {
select {
case msg1 := <-c1:
fmt.Println("received", msg1)
case msg2 := <-c2:
fmt.Println("received", msg2)
}
}
}
```
输出结果
```
received one
received two
```
如我们所期望的,程序输出了正确的值。对于select语句而言,它不断地检测通道是否有值过来,一旦发现有值过来,立刻获取输出。
';
Go 通道缓冲
最后更新于:2022-04-01 23:09:35
# Go通道缓冲
默认情况下,通道是不带缓冲区的。
发送端发送数据,同时必须又接收端相应的接收数据。
而带缓冲区的通道则允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。
不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。
```go
package main
import "fmt"
func main() {
// 这里我们定义了一个可以存储字符串类型的带缓冲通道
// 缓冲区大小为2
messages := make(chan string, 2)
// 因为messages是带缓冲的通道,我们可以同时发送两个数据
// 而不用立刻需要去同步读取数据
messages <- "buffered"
messages <- "channel"
// 然后我们和上面例子一样获取这两个数据
fmt.Println(<-messages)
fmt.Println(<-messages)
}
```
运行结果
```
buffered
channel
```
';
Go 通道方向
最后更新于:2022-04-01 23:09:33
# Go通道方向
当使用通道作为函数的参数时,你可以指定该通道是只读的还是只写的。这种设置有时候会提高程序的参数类型安全。
```go
package main
import "fmt"
// 这个ping函数只接收能够发送数据的通道作为参数,试图从这个通道接收数据
// 会导致编译错误,这里只写的定义方式为`chan<- string`表示这个类型为
// 字符串的通道为只写通道
func ping(pings chan<- string, msg string) {
pings <- msg
}
// pong函数接收两个通道参数,一个是只读的pings,使用`<-chan string`定义
// 另外一个是只写的pongs,使用`chan<- string`来定义
func pong(pings <-chan string, pongs chan<- string) {
msg := <-pings
pongs <- msg
}
func main() {
pings := make(chan string, 1)
pongs := make(chan string, 1)
ping(pings, "passed message")
pong(pings, pongs)
fmt.Println(<-pongs)
}
```
运行结果
```
passed message
```
其实这个例子就是把信息首先写入pings通道里面,然后在pong函数里面再把信息从pings通道里面读出来再写入pongs通道里面,最后在main函数里面将信息从pongs通道里面读出来。
在这里,pings和pongs事实上是可读且可写的,不过作为参数传递的时候,函数参数限定了通道的方向。不过pings和pongs在ping和pong函数里面还是可读且可写的。只是ping和pong函数调用的时候把它们当作了只读或者只写。
';
Go 通道的同步功能
最后更新于:2022-04-01 23:09:31
# Go通道的同步功能
我们使用通道来同步协程之间的执行。
下面的例子是通过获取同步通道数据来阻塞程序执行的方法来等待另一个协程运行结束的。
也就是说main函数所在的协程在运行到`<-done`语句的时候将一直等待worker函数所在的协程执行完成,向通道写入数据才会(从通道获得数据)继续执行。
```go
package main
import "fmt"
import "time"
// 这个worker函数将以协程的方式运行
// 通道`done`被用来通知另外一个协程这个worker函数已经执行完成
func worker(done chan bool) {
fmt.Print("working...")
time.Sleep(time.Second)
fmt.Println("done")
// 向通道发送一个数据,表示worker函数已经执行完成
done <- true
}
func main() {
// 使用协程来调用worker函数,同时将通道`done`传递给协程
// 以使得协程可以通知别的协程自己已经执行完成
done := make(chan bool, 1)
go worker(done)
// 一直阻塞,直到从worker所在协程获得一个worker执行完成的数据
<-done
}
```
运行结果
```
working...done
```
如果我们从main函数里面移除`<-done`语句,那么main函数在worker协程开始运行之前就结束了。
';
Go 随机数
最后更新于:2022-04-01 23:09:29
# Go 随机数
Go的`math/rand`包提供了伪随机数的生成。
```go
package main
import "fmt"
import "math/rand"
func main() {
// 例如`rand.Intn`返回一个整型随机数n,0<=n<100
fmt.Print(rand.Intn(100), ",")
fmt.Print(rand.Intn(100))
fmt.Println()
// `rand.Float64` 返回一个`float64` `f`,
// `0.0 <= f < 1.0`
fmt.Println(rand.Float64())
// 这个方法可以用来生成其他数值范围内的随机数,
// 例如`5.0 <= f < 10.0`
fmt.Print((rand.Float64()*5)+5, ",")
fmt.Print((rand.Float64() * 5) + 5)
fmt.Println()
// 为了使随机数生成器具有确定性,可以给它一个seed
s1 := rand.NewSource(42)
r1 := rand.New(s1)
fmt.Print(r1.Intn(100), ",")
fmt.Print(r1.Intn(100))
fmt.Println()
// 如果源使用一个和上面相同的seed,将生成一样的随机数
s2 := rand.NewSource(42)
r2 := rand.New(s2)
fmt.Print(r2.Intn(100), ",")
fmt.Print(r2.Intn(100))
fmt.Println()
}
```
运行结果
```
81,87
0.6645600532184904
7.1885709359349015,7.123187485356329
5,87
5,87
```
';
Go 数字解析
最后更新于:2022-04-01 23:09:26
# Go 数字解析
从字符串解析出数字是一个基本的而且很常见的任务。
Go内置的`strconv`提供了数字解析功能。
```go
package main
import "strconv"
import "fmt"
func main() {
// 使用ParseFloat解析浮点数,64是说明使用多少位
// 精度来解析
f, _ := strconv.ParseFloat("1.234", 64)
fmt.Println(f)
// 对于ParseInt函数,0 表示从字符串推断整型进制,
// 则表示返回结果的位数
i, _ := strconv.ParseInt("123", 0, 64)
fmt.Println(i)
// ParseInt能够解析出16进制的数字
d, _ := strconv.ParseInt("0x1c8", 0, 64)
fmt.Println(d)
// 还可以使用ParseUint函数
u, _ := strconv.ParseUint("789", 0, 64)
fmt.Println(u)
// Atoi是解析10进制整型的快捷方法
k, _ := strconv.Atoi("135")
fmt.Println(k)
// 解析函数在遇到无法解析的输入时,会返回错误
_, e := strconv.Atoi("wat")
fmt.Println(e)
}
```
运行结果
```
1.234
123
456
789
135
strconv.ParseInt: parsing "wat": invalid syntax
```
';
Go 时间格式化和解析
最后更新于:2022-04-01 23:09:24
# Go 时间格式化和解析
Go使用模式匹配的方式来支持日期格式化和解析。
```go
package main
import "fmt"
import "time"
func main() {
p := fmt.Println
// 这里有一个根据RFC3339来格式化日期的例子
t := time.Now()
p(t.Format("2006-01-02T15:04:05Z07:00"))
// Format 函数使用一种基于示例的模式匹配方式,
// 它使用已经格式化的时间模式来决定所给定参数
// 的输出格式
p(t.Format("3:04PM"))
p(t.Format("Mon Jan _2 15:04:05 2006"))
p(t.Format("2006-01-02T15:04:05.999999-07:00"))
// 对于纯数字表示的时间来讲,你也可以使用标准
// 的格式化字符串的方式来格式化时间
fmt.Printf("%d-%02d-%02dT%02d:%02d:%02d-00:00\n",
t.Year(), t.Month(), t.Day(),
t.Hour(), t.Minute(), t.Second())
// 时间解析也是采用一样的基于示例的方式
withNanos := "2006-01-02T15:04:05.999999999-07:00"
t1, e := time.Parse(
withNanos,
"2012-11-01T22:08:41.117442+00:00")
p(t1)
kitchen := "3:04PM"
t2, e := time.Parse(kitchen, "8:41PM")
p(t2)
// Parse将返回一个错误,如果所输入的时间格式不对的话
ansic := "Mon Jan _2 15:04:05 2006"
_, e = time.Parse(ansic, "8:41PM")
p(e)
// 你可以使用一些预定义的格式来格式化或解析时间
p(t.Format(time.Kitchen))
}
```
运行结果
```
2014-03-03T22:39:31+08:00
10:39PM
Mon Mar 3 22:39:31 2014
2014-03-03T22:39:31.647077+08:00
2014-03-03T22:39:31-00:00
2012-11-01 22:08:41.117442 +0000 +0000
0000-01-01 20:41:00 +0000 UTC
parsing time "8:41PM" as "Mon Jan _2 15:04:05 2006": cannot parse "8:41PM" as "Mon"
10:39PM
```
';
Go 时间戳
最后更新于:2022-04-01 23:09:22
# Go 时间戳
程序的一个通常需求是计算从Unix起始时间开始到某个时刻的秒数,毫秒数,微秒数等。
我们来看看Go里面是怎么做的。
```go
package main
import "fmt"
import "time"
func main() {
// 使用Unix和UnixNano来分别获取从Unix起始时间
// 到现在所经过的秒数和微秒数
now := time.Now()
secs := now.Unix()
nanos := now.UnixNano()
fmt.Println(now)
// 注意这里没有UnixMillis方法,所以我们需要将
// 微秒手动除以一个数值来获取毫秒
millis := nanos / 1000000
fmt.Println(secs)
fmt.Println(millis)
fmt.Println(nanos)
// 反过来,你也可以将一个整数秒数或者微秒数转换
// 为对应的时间
fmt.Println(time.Unix(secs, 0))
fmt.Println(time.Unix(0, nanos))
}
```
运行结果
```
2014-03-02 23:11:31.118666918 +0800 CST
1393773091
1393773091118
1393773091118666918
2014-03-02 23:11:31 +0800 CST
2014-03-02 23:11:31.118666918 +0800 CST
```
';
Go 时间
最后更新于:2022-04-01 23:09:20
# Go 时间
Go提供了对时间和一段时间的支持。这里有一些例子。
```go
package main
import "fmt"
import "time"
func main() {
p := fmt.Println
// 从获取当前时间开始
now := time.Now()
p(now)
// 你可以提供年,月,日等来创建一个时间。当然时间
// 总是会和地区联系在一起,也就是时区
then := time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
p(then)
// 你可以获取时间的各个组成部分
p(then.Year())
p(then.Month())
p(then.Day())
p(then.Hour())
p(then.Minute())
p(then.Second())
p(then.Nanosecond())
p(then.Location())
// 输出当天是周几,Monday-Sunday中的一个
p(then.Weekday())
// 下面的几个方法判断两个时间的顺序,精确到秒
p(then.Before(now))
p(then.After(now))
p(then.Equal(now))
// Sub方法返回两个时间的间隔(Duration)
diff := now.Sub(then)
p(diff)
// 可以以不同的单位来计算间隔的大小
p(diff.Hours())
p(diff.Minutes())
p(diff.Seconds())
p(diff.Nanoseconds())
// 你可以使用Add方法来为时间增加一个间隔
// 使用负号表示时间向前推移一个时间间隔
p(then.Add(diff))
p(then.Add(-diff))
}
```
运行结果
```
2014-03-02 22:54:40.561698065 +0800 CST
2009-11-17 20:34:58.651387237 +0000 UTC
2009
November
17
20
34
58
651387237
UTC
Tuesday
true
false
false
37578h19m41.910310828s
37578.328308419674
2.2546996985051804e+06
1.3528198191031083e+08
135281981910310828
2014-03-02 14:54:40.561698065 +0000 UTC
2005-08-05 02:15:16.741076409 +0000 UTC
```
';
Go 请求处理频率控制
最后更新于:2022-04-01 23:09:17
# Go 请求处理频率控制
频率控制是控制资源利用和保证服务高质量的重要机制。Go可以使用goroutine,channel和ticker来以优雅的方式支持频率控制。
```go
package main
import "time"
import "fmt"
func main() {
// 首先我们看下基本的频率限制。假设我们得控制请求频率,
// 我们使用一个通道来处理所有的这些请求,这里向requests
// 发送5个数据,然后关闭requests通道
requests := make(chan int, 5)
for i := 1; i <= 5; i++ {
requests <- i
}
close(requests)
// 这个limiter的Ticker每隔200毫秒结束通道阻塞
// 这个limiter就是我们频率控制处理器
limiter := time.Tick(time.Millisecond * 200)
// 通过阻塞从limiter通道接受数据,我们将请求处理控制在每隔200毫秒
// 处理一个请求,注意`<-limiter`的阻塞作用。
for req := range requests {
<-limiter
fmt.Println("request", req, time.Now())
}
// 我们可以保持正常的请求频率限制,但也允许请求短时间内爆发
// 我们可以通过通道缓存来实现,比如下面的这个burstyLimiter
// 就允许同时处理3个事件。
burstyLimiter := make(chan time.Time, 3)
// 填充burstyLimiter,先发送3个数据
for i := 0; i < 3; i++ {
burstyLimiter <- time.Now()
}
// 然后每隔200毫秒再向burstyLimiter发送一个数据,这里是不断地
// 每隔200毫秒向burstyLimiter发送数据
go func() {
for t := range time.Tick(time.Millisecond * 200) {
burstyLimiter <- t
}
}()
// 这里模拟5个请求,burstyRequests的前面3个请求会连续被处理,
// 因为burstyLimiter被先连续发送3个数据的的缘故,而后面两个
// 则每隔200毫秒处理一次
burstyRequests := make(chan int, 5)
for i := 1; i <= 5; i++ {
burstyRequests <- i
}
close(burstyRequests)
for req := range burstyRequests {
<-burstyLimiter
fmt.Println("request", req, time.Now())
}
}
```
运行结果
```
request 1 2014-02-21 14:20:05.2696437 +0800 CST
request 2 2014-02-21 14:20:05.4696637 +0800 CST
request 3 2014-02-21 14:20:05.6696837 +0800 CST
request 4 2014-02-21 14:20:05.8697037 +0800 CST
request 5 2014-02-21 14:20:06.0697237 +0800 CST
request 1 2014-02-21 14:20:06.0697237 +0800 CST
request 2 2014-02-21 14:20:06.0697237 +0800 CST
request 3 2014-02-21 14:20:06.0707238 +0800 CST
request 4 2014-02-21 14:20:06.2707438 +0800 CST
request 5 2014-02-21 14:20:06.4707638 +0800 CST
```
我们从输出的结果上可以看出最后的5个输出结果中,前三个的时间是连续的,而后两个的时间是隔了200毫秒。
';
Go 切片
最后更新于:2022-04-01 23:09:15
# Go 切片
切片是Go语言的关键类型之一,它提供了比数组更多的功能。
示例1:
```go
package main
import "fmt"
func main() {
// 和数组不同的是,切片的长度是可变的。
// 我们可以使用内置函数make来创建一个长度不为零的切片
// 这里我们创建了一个长度为3,存储字符串的切片,切片元素
// 默认为零值,对于字符串就是""。
s := make([]string, 3)
fmt.Println("emp:", s)
// 可以使用和数组一样的方法来设置元素值或获取元素值
s[0] = "a"
s[1] = "b"
s[2] = "c"
fmt.Println("set:", s)
fmt.Println("get:", s[2])
// 可以用内置函数len获取切片的长度
fmt.Println("len:", len(s))
// 切片还拥有一些数组所没有的功能。
// 例如我们可以使用内置函数append给切片追加值,然后
// 返回一个拥有新切片元素的切片。
// 注意append函数不会改变原切片,而是生成了一个新切片,
// 我们需要用原来的切片来接收这个新切片
s = append(s, "d")
s = append(s, "e", "f")
fmt.Println("apd:", s)
// 另外我们还可以从一个切片拷贝元素到另一个切片
// 下面的例子就是创建了一个和切片s长度相同的新切片
// 然后使用内置的copy函数来拷贝s的元素到c中。
c := make([]string, len(s))
copy(c, s)
fmt.Println("cpy:", c)
// 切片还支持一个取切片的操作 "slice[low:high]"
// 获取的新切片包含元素"slice[low]",但是不包含"slice[high]"
// 下面的例子就是取一个新切片,元素包括"s[2]","s[3]","s[4]"。
l := s[2:5]
fmt.Println("sl1:", l)
// 如果省略low,默认从0开始,不包括"slice[high]"元素
l = s[:5]
fmt.Println("sl2:", l)
// 如果省略high,默认为len(slice),包括"slice[low]"元素
l = s[2:]
fmt.Println("sl3:", l)
// 我们可以同时声明和初始化一个切片
t := []string{"g", "h", "i"}
fmt.Println("dcl:", t)
// 我们也可以创建多维切片,和数组不同的是,切片元素的长度也是可变的。
twoD := make([][]int, 3)
for i := 0; i < 3; i++ {
innerLen := i + 1
twoD[i] = make([]int, innerLen)
for j := 0; j < innerLen; j++ {
twoD[i][j] = i + j
}
}
fmt.Println("2d: ", twoD)
}
```
输出结果为
```
emp: [ ]
set: [a b c]
get: c
len: 3
apd: [a b c d e f]
cpy: [a b c d e f]
sl1: [c d e]
sl2: [a b c d e]
sl3: [c d e f]
dcl: [g h i]
2d: [[0] [1 2] [2 3 4]]
```
数组和切片的定义方式的区别在于`[]`之中是否有`固定长度`或者推断长度标志符`...`。
示例2:
```go
package main
import "fmt"
func main() {
s1 := make([]int, 0)
test(s1)
fmt.Println(s1)
}
func test(s []int) {
s = append(s, 3)
//因为原来分配的空间不够,所以在另外一个地址又重新分配了空间,所以原始地址的数据没有变
}
```
输出结果为:
```
[]
```
若改为:
```go
package main
import "fmt"
func main() {
s1 := make([]int, 0)
s1 = test(s1)
fmt.Println(s1)
}
func test(s []int) []int {
s = append(s, 3)
return s
}
```
输出结果为:
```
[3]//正确结果
```
示例3:
cap是slice的最大容量,append函数添加元素,如果超过原始slice的容量,会重新分配底层数组。
```
package main
import "fmt"
func main() {
s1 := make([]int, 3, 6)
fmt.Println("s1= ", s1, len(s1), cap(s1))
s2 := append(s1, 1, 2, 3)
fmt.Println("s1= ", s1, len(s1), cap(s1))
fmt.Println("s2= ", s2, len(s2), cap(s2))
s3 := append(s2, 4, 5, 6)
fmt.Println("s1= ", s1, len(s1), cap(s1))
fmt.Println("s2= ", s2, len(s2), cap(s2))
fmt.Println("s3= ", s3, len(s3), cap(s3))
}
```
输出结果为:
```
s1= [0 0 0] 3 6
s1= [0 0 0] 3 6
s2= [0 0 0 1 2 3] 6 6
s1= [0 0 0] 3 6
s2= [0 0 0 1 2 3] 6 6
s3= [0 0 0 1 2 3 4 5 6] 9 12
```
示例4:
指向同一底层数组的slice之间copy时,允许存在重叠。copy数组时,受限于src和dst数组的长度最小值。
```go
package main
import "fmt"
func main() {
s1 := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s2 := make([]int, 3, 20)
var n int
n = copy(s2, s1)
fmt.Println(n, s2, len(s2), cap(s2))
s3 := s1[4:6]
fmt.Println(n, s3, len(s3), cap(s3))
n = copy(s3, s1[1:5])
fmt.Println(n, s3, len(s3), cap(s3))
}
```
输出结果:
```
3 [0 1 2] 3 20
3 [4 5] 2 6
2 [1 2] 2 6
```
';
Go 排序
最后更新于:2022-04-01 23:09:13
# Go 排序
Go的sort包实现了内置数据类型和用户自定义数据类型的排序功能。我们先看看内置数据类型的排序。
```go
package main
import "fmt"
import "sort"
func main() {
// 这些排序方法都是针对内置数据类型的。
// 这里的排序方法都是就地排序,也就是说排序改变了
// 切片内容,而不是返回一个新的切片
strs := []string{"c", "a", "b"}
sort.Strings(strs)
fmt.Println("Strings:", strs)
// 对于整型的排序
ints := []int{7, 2, 4}
sort.Ints(ints)
fmt.Println("Ints: ", ints)
// 我们还可以检测切片是否已经排序好
s := sort.IntsAreSorted(ints)
fmt.Println("Sorted: ", s)
}
```
输出结果
```
Strings: [a b c]
Ints: [2 4 7]
Sorted: true
```
';
Go 命令行参数标记
最后更新于:2022-04-01 23:09:11
# Go 命令行参数标记
命令行参数标记是为命令行程序指定选项参数的常用方法。例如,在命令`wc -l`中,`-l`就是一个命令行参数标记。
Go提供了`flag`包来支持基本的命令行标记解析。我们这里将要使用这个包提供的方法来实现带选项的命令行程序。
```go
package main
import "flag"
import "fmt"
func main() {
// 基础的标记声明适用于string,integer和bool型选项。
// 这里我们定义了一个标记`word`,默认值为`foo`和一
// 个简短的描述。`flag.String`函数返回一个字符串指
// 针(而不是一个字符串值),我们下面将演示如何使
// 用这个指针
wordPtr := flag.String("word", "foo", "a string")
// 这里定义了两个标记,一个`numb`,另一个是`fork`,
// 使用和上面定义`word`标记相似的方法
numbPtr := flag.Int("numb", 42, "an int")
boolPtr := flag.Bool("fork", false, "a bool")
// 你也可以程序中任意地方定义的变量来定义选项,只
// 需要把该变量的地址传递给flag声明函数即可
var svar string
flag.StringVar(&svar, "svar", "bar", "a string var")
// 当所有的flag声明完成后,使用`flag.Parse()`来分
// 解命令行选项
flag.Parse()
// 这里我们仅仅输出解析后的选项和任何紧跟着的位置
// 参数,注意我们需要使用`*wordPtr`的方式来获取最
// 后的选项值
fmt.Println("word:", *wordPtr)
fmt.Println("numb:", *numbPtr)
fmt.Println("fork:", *boolPtr)
fmt.Println("svar:", svar)
fmt.Println("tail:", flag.Args())
}
```
为了运行示例,你需要先将程序编译为可执行文件。
```
go build command-line-flags.go
```
下面分别看看给予该命令行程序不同选项参数的例子:
(1) 给所有的选项设置一个参数
```
$ ./command-line-flags -word=opt -numb=7 -fork -svar=flag
word: opt
numb: 7
fork: true
svar: flag
tail: []
```
(2) 如果你不设置flag,那么它们自动采用默认的值
```
$ ./command-line-flags -word=opt
word: opt
numb: 42
fork: false
svar: bar
tail: []
```
(3) 尾部的位置参数可以出现在任意一个flag后面
```
$ ./command-line-flags -word=opt a1 a2 a3
word: opt
numb: 42
fork: false
svar: bar
tail: [a1 a2 a3]
```
(4) 注意flag包要求所有的flag都必须出现在尾部位置参数的前面,否则这些flag将被当作位置参数处理
```
$ ./command-line-flags -word=opt a1 a2 a3 -numb=7
word: opt
numb: 42
fork: false
svar: bar
trailing: [a1 a2 a3 -numb=7]
```
(5) 使用`-h`或者`--help`这两个flag来自动地生成命令行程序的帮助信息
```
$ ./command-line-flags -h
Usage of ./command-line-flags:
-fork=false: a bool
-numb=42: an int
-svar="bar": a string var
-word="foo": a string
```
(6) 如果你提供了一个程序不支持的flag,那么程序会打印一个错误信息和帮助信息
```
$ ./command-line-flags -wat
flag provided but not defined: -wat
Usage of ./go_cmd_flag:
-fork=false: a bool
-numb=42: an int
-svar="bar": a string var
-word="foo": a string
```
';
Go 命令行参数
最后更新于:2022-04-01 23:09:08
# Go 命令行参数
命令行参数是一种指定程序运行初始参数的常用方式。比如`go run hello.go`使用`run`和`hello.go`参数来执行程序。
```go
package main
import "os"
import "fmt"
func main() {
// `os.Args`提供了对命令行参数的访问,注意该
// 切片的第一个元素是该程序的运行路径,而
// `os.Args[1:]`则包含了该程序的所有参数
argsWithProg := os.Args
argsWithoutProg := os.Args[1:]
// 你可以使用索引的方式来获取单个参数
arg := os.Args[3]
fmt.Println(argsWithProg)
fmt.Println(argsWithoutProg)
fmt.Println(arg)
}
```
在运行该程序的时候,需要首先用`go build`将代码编译为可执行文件,然后提供足够数量的参数。例如
```
$ go build command-line-arguments.go
$ ./command-line-arguments a b c d
[./command-line-arguments a b c d]
[a b c d]
c
```
';
Go 可变长参数列表
最后更新于:2022-04-01 23:09:06
# Go 可变长参数列表
支持可变长参数列表的函数可以支持任意个传入参数,比如fmt.Println函数就是一个支持可变长参数列表的函数。
```go
package main
import "fmt"
// 这个函数可以传入任意数量的整型参数
func sum(nums ...int) {
fmt.Print(nums, " ")
total := 0
for _, num := range nums {
total += num
}
fmt.Println(total)
}
func main() {
// 支持可变长参数的函数调用方法和普通函数一样
// 也支持只有一个参数的情况
sum(1, 2)
sum(1, 2, 3)
// 如果你需要传入的参数在一个切片中,像下面一样
// "func(slice...)"把切片打散传入
nums := []int{1, 2, 3, 4}
sum(nums...)
}
```
输出结果为
```
[1 2] 3
[1 2 3] 6
[1 2 3 4] 10
```
需要注意的是,可变长参数应该是函数定义的最右边的参数,即最后一个参数。
';