gobase

语言结构

main.go

1
2
3
4
5
6
7
8
package main

import "fmt"

func main() {
/* 这是我的第一个简单的程序 */
fmt.Println("Hello, World!")
}

package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。

运行

直接运行

1
go run main.go

生成二进制文件

1
2
3
go build main.go

./main

注意

1
2
3
4
func main()  
{ // 错误,{ 不能在单独的行上
fmt.Println("Hello, World!")
}

变量

1
2
3
4
5
6
7
8
9
10
11
12
13
var a = "initial"

var b, c int = 1, 2

var d = true

var e float64

f := float32(e)

g := a + "foo"

const s string = "constant"

循环

只有for循环,break,continue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func main() {

i := 1
for {
fmt.Println("loop")
break
}

for j := 7; j < 9; j++ {
fmt.Println(j)
}

for n := 0; n < 5; n++ {
if n%2 == 0 {
continue
}
fmt.Println(n)
}
for i <= 3 {
fmt.Println(i)
i = i + 1
}
}

条件

if-else

if-else if-else

1
2
3
4
5
6
7
if num := 9; num < 0 {
fmt.Println(num, "is negative")
} else if num < 10 {
fmt.Println(num, "has 1 digit")
} else {
fmt.Println(num, "has multiple digits")
}

switch

不需要break,直接跳出判断,只能写一句话

可以不需要变量,直接进入case,判断条件True or False

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import (
"fmt"
"time"
)

func main() {

a := 2
switch a {
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
case 3:
fmt.Println("three")
case 4, 5:
fmt.Println("four or five")
default:
fmt.Println("other")
}

t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("It's before noon")
default:
fmt.Println("It's after noon")
}
}

select

超时判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var resChan = make(chan int)
// do request
func test() {
select {
case data := <-resChan:
doData(data)
case <-time.After(time.Second * 3):
fmt.Println("request time out")
}
}

func doData(data int) {
//...
}

退出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//主线程(协程)中如下:
var shouldQuit=make(chan struct{})
fun main(){
{
//loop
}
//...out of the loop
select {
case <-c.shouldQuit:
cleanUp()
return
default:
}
//...
}

//再另外一个协程中,如果运行遇到非法操作或不可处理的错误,就向shouldQuit发送数据通知程序停止运行
close(shouldQuit)

判断channel是否堵塞

1
2
3
4
5
6
7
8
9
//在某些情况下是存在不希望channel缓存满了的需求的,可以用如下方法判断
ch := make (chan int, 5)
//...
data:=0
select {
case ch <- data:
default:
//做相应操作,比如丢弃data。视需求而定
}

数组

1
2
3
4
5
6
7
8
9
var a [5]int
a[4] = 100
fmt.Println("get:", a[2])
fmt.Println("len:", len(a))

b := [5]int{1, 2, 3, 4, 5}
fmt.Println(b)

var twoD [2][3]int

切片

主要使用的数组

1
2
3
4
5
6
7
8
9
10
11
	s := make([]string, 3)
s[0] = "a"
s[1] = "b"
s[2] = "c"
s = append(s, "d")
s = append(s, "e", "f")

c := make([]string, len(s))
copy(c, s)

//切片操作和python类似

len() 和 cap() 函数

len返回当前长度

cap返回最大长度

字典

map

1
2
m := make(map[string]int)
m["one"] = 1

如果没有键值对,

1
2
3
4
5
6
	fmt.Println(m["unknow"]) // 0

r, ok := m["unknow"]
fmt.Println(r, ok) // 0 false

ok为True,表示键值对存在,反之则否

完全无序,遍历时随机输出

遍历

range遍历

遍历数组,i为索引,num为值

遍历map,前为键,后为值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func main() {
nums := []int{2, 3, 4}
sum := 0
for i, num := range nums {
sum += num
if num == 2 {
fmt.Println("index:", i, "num:", num) // index: 0 num: 2
}
}
fmt.Println(sum) // 9

m := map[string]string{"a": "A", "b": "B"}
for k, v := range m {
fmt.Println(k, v) // b 8; a A
}
for k := range m {
fmt.Println("key", k) // key a; key b
}

函数

1
2
3
4
5
6
7
8
9
10
11
12
func add(a int, b int) int {
return a + b
}

func add2(a, b int) int {
return a + b
}

func exists(m map[string]string, k string) (v string, ok bool) {
v, ok = m[k]
return v, ok
}

闭包

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

import "fmt"

func main() {
x := 10

// 创建一个闭包
increment := func() int {
x++
return x
}

fmt.Println(increment()) // 输出 11
fmt.Println(increment()) // 输出 12
fmt.Println(increment()) // 输出 13
}

这种使用方式应该是变量声明在函数声明前,变量作用域的关系应该局部到全局,到内置。

对increment函数来说,x在局部变量中为找到,相对于它本身,在它之前声明的x变量比它函数自身的局部变量更高,或者说是相对与它的全局变量,但并不准确

延迟调用(defer)

1
2
调用直到 return 前才被执行
多个语句,按先进后出的方式执行。哪怕函数或某个延迟调用发生错误,这些调用依旧会被执行。
用处
1
2
3
1. 关闭文件句柄
2. 锁资源释放
3. 数据库连接释放
defer+闭包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import "fmt"

func main() {
var whatever [5]struct{}
for i := range whatever {
defer func() { fmt.Println(i) }()
}
}

output:
4
4
4
4
4

当defer执行时,函数已经准备return,此时i为4,数组的大小

close
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

type Test struct {
name string
}

func (t *Test) Close() {
fmt.Println(t.name, " closed")
}
func main() {
ts := []Test{{"a"}, {"b"}, {"c"}}
for _, t := range ts {
defer t.Close()
}
}
输出结果:

c closed
c closed
c closed

并不是我们想要的结果

多写一个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import "fmt"

type Test struct {
name string
}

func (t *Test) Close() {
fmt.Println(t.name, " closed")
}
func Close(t Test) {
t.Close()
}
func main() {
ts := []Test{{"a"}, {"b"}, {"c"}}
for _, t := range ts {
defer Close(t)
}
}
输出结果:

c closed
b closed
a closed

不用多写函数

1
2
3
4
5
6
7
func main() {
ts := []Test{{"a"}, {"b"}, {"c"}}
for _, t := range ts {
t2 := t
defer t2.Close()
}
}

defer后面的语句在执行的时候,函数调用的参数会被保存起来,但是不执行。也就是复制了一份。

*延迟调用参数在注册时求值或复制,可用指针或闭包 “延迟” 读取。

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

func test() {
x, y := 10, 20

defer func(i int) {
println("defer:", i, y) // y 闭包引用
}(x) // x 被复制

x += 10
y += 100
println("x =", x, "y =", y)
}

func main() {
test()
}

输出结果:

1
2
x = 20 y = 120
defer: 10 120

defer性能问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package main

import (
"fmt"
"sync"
"time"
)

var lock sync.Mutex

func test() {
lock.Lock()
lock.Unlock()
}

func testdefer() {
lock.Lock()
defer lock.Unlock()
}

func main() {
func() {
t1 := time.Now()

for i := 0; i < 10000; i++ {
test()
}
elapsed := time.Since(t1)
fmt.Println("test elapsed: ", elapsed)
}()
func() {
t1 := time.Now()

for i := 0; i < 10000; i++ {
testdefer()
}
elapsed := time.Since(t1)
fmt.Println("testdefer elapsed: ", elapsed)
}()

}

输出结果:

test elapsed:  223.162µs
testdefer elapsed:  781.304µs

defer陷阱

defer和closure
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"errors"
"fmt"
)

func foo(a, b int) (i int, err error) {
defer fmt.Printf("first defer err %v\n", err)
defer func(err error) { fmt.Printf("second defer err %v\n", err) }(err)
defer func() { fmt.Printf("third defer err %v\n", err) }()
if b == 0 {
err = errors.New("divided by zero!")
return
}

i = a / b
return
}

func main() {
foo(2, 0)
}

输出结果:

1
2
3
third defer err divided by zero!
second defer err <nil>
first defer err <nil>

如果 defer 后面跟的不是一个 closure 最后执行的时候我们得到的并不是最新的值。

socond的err是在执行时传入的。闭包调用

defer与return
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import "fmt"

func foo() (i int) {

i = 0
defer func() {
fmt.Println(i)
}()

return 2
}

func main() {
foo()
}
输出结果:

2

在有具名返回值的函数中(这里具名返回值为 i),执行 return 2 的时候实际上已经将 i 的值重新赋值为 2。

也就是说返回值其实是return后的值,

修改i值为1,删除2,

1
2
3
i = 1

return

最终返回值为1

defer nil函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
"fmt"
)

func test() {
var run func() = nil
defer run()
fmt.Println("runs")
}

func main() {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
test()
}

输出结果:

1
2
runs
runtime error: invalid memory address or nil pointer dereference
错误使用defer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "net/http"

func do() error {
res, err := http.Get("http://www.google.com")
defer res.Body.Close()
if err != nil {
return err
}

// ..code...

return nil
}

func main() {
do()
}

输出结果:

1
panic: runtime error: invalid memory address or nil pointer dereference

当它失败的时候,我们访问了 Body 中的空变量 res ,因此会抛出异常

1
2
3
if res != nil {
defer res.Body.Close()
}
错误检查

f.Close() 可能会返回一个错误,可这个错误会被我们忽略掉

1
2
3
4
5
6
7
8
9
10
11
12
f, err := os.Open("book.txt")
if err != nil {
return err
}

if f != nil {
defer func() {
if err := f.Close(); err != nil {
// log etc
}
}()
}

两个相同的变量释放不同的资源,可能无法正常执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
func do() error {
f, err := os.Open("book.txt")
if err != nil {
return err
}
if f != nil {
defer func() {
if err := f.Close(); err != nil {
fmt.Printf("defer close book.txt err %v\n", err)
}
}()
}

// ..code...

f, err = os.Open("another-book.txt")
if err != nil {
return err
}
if f != nil {
defer func() {
if err := f.Close(); err != nil {
fmt.Printf("defer close another-book.txt err %v\n", err)
}
}()
}

return nil
}

func main() {
do()
}

解决方案:传参保存变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
func do() error {
f, err := os.Open("book.txt")
if err != nil {
return err
}
if f != nil {
defer func(f io.Closer) {
if err := f.Close(); err != nil {
fmt.Printf("defer close book.txt err %v\n", err)
}
}(f)
}

// ..code...

f, err = os.Open("another-book.txt")
if err != nil {
return err
}
if f != nil {
defer func(f io.Closer) {
if err := f.Close(); err != nil {
fmt.Printf("defer close another-book.txt err %v\n", err)
}
}(f)
}

return nil
}

func main() {
do()
}

指针

主要是可用于修改函数参数

1
2
3
func add2ptr(n *int) {
*n += 2
}

传入的是指针类型,直接修改内存空间对应的值,如何是数据量较大的结构体,推荐这种方式,可节省消耗

结构体

未初始化为空值

1
2
3
4
5
6
7
8
9
10
type user struct {
name string
password string
}

a := user{name: "wang", password: "1024"}
b := user{"wang", "1024"}
c := user{name: "wang"}
c.password = "1024"
var d user

结构体作为函数参数

指针参数,可修改结构体数据,节约大结构体的开销

1
2
3
func checkPassword2(u *user, password string) bool {
return u.password == password
}

结构体标签

json序列化时会使用定义的标签作为key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//Student 学生
type Student struct {
ID int `json:"id"` //通过指定tag实现json序列化该字段时的key
Gender string //json序列化是默认使用字段名作为key
name string //私有不能被json包访问
}

func main() {
s1 := Student{
ID: 1,
Gender: "女",
name: "pprof",
}
data, err := json.Marshal(s1)
if err != nil {
fmt.Println("json marshal failed!")
return
}
fmt.Printf("json str:%s\n", data) //json str:{"id":1,"Gender":"女"}
}

结构体方法

类似类的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
func (u user) checkPassword(password string) bool {
return u.password == password
}

func (u *user) resetPassword(password string) {
u.password = password
}

func main() {
a := user{name: "wang", password: "1024"}
a.resetPassword("2048")
fmt.Println(a.checkPassword("2048")) // true
}

匿名字段

匿名字段可实现类似继承和覆盖的操作

继承

1
2
3
4
5
6
7
8
9
10
11
12
type User struct {
id int
name string
}

type Manager struct {
User
}

func (self *User) ToString() string { // receiver = &(Manager.User)
return fmt.Sprintf("User: %p, %v", self, self)
}

覆盖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type User struct {
id int
name string
}

type Manager struct {
User
title string
}

func (self *User) ToString() string {
return fmt.Sprintf("User: %p, %v", self, self)
}

func (self *Manager) ToString() string {
return fmt.Sprintf("Manager: %p, %v", self, self)
}

方法集

• 类型 T 方法集包含全部 receiver T 方法。
• 类型 *T 方法集包含全部 receiver T + *T 方法。
• 如类型 S 包含匿名字段 T,则 S 和 *S 方法集包含 T 方法。
• 如类型 S 包含匿名字段 *T,则 S 和 *S 方法集包含 T + *T 方法。
• 不管嵌入 T 或 *T,*S 方法集总是包含 T + *T 方法。

这些描述是正确的,但go语言的类型方法集规则允许方法的自动提升和方法集的继承

1
2
3
4
5
6
7
8
9
10
11
type T struct {
int
}

func (t T) testT() {
fmt.Println("类型 *T 方法集包含全部 receiver T 方法。")
}

func (t *T) testP() {
fmt.Println("类型 *T 方法集包含全部 receiver *T 方法。")
}

当你在一个 T 类型的变量上调用 testP() 方法时,Go 会自动取该变量的地址(将 T 转换为 *T)。

Go允许在值类型上调用指针接收者的方法,并会自动将值转换为指针。

表达式

调用者不同,方法的两种表现形式

1
instance.method(args...) ---> <type>.func(instance, args...)

前:method value,绑定实例

后:method expression,显式传参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type User struct {
id int
name string
}

func (self *User) Test() {
fmt.Printf("%p, %v\n", self, self)
}

func main() {
u := User{1, "Tom"}
u.Test()

mValue := u.Test
mValue() // 隐式传递 receiver

mExpression := (*User).Test
mExpression(&u) // 显式传递 receiver
}
实例绑定
1
2
3
4
5
6
7
8
9
func main() {
u := User{1, "Tom"}
mValue := u.Test // 立即复制 receiver,因为不是指针类型,不受后续修改影响。

u.id, u.name = 2, "Jack"
u.Test()

mValue()
}
值和指针
1
2
3
4
5
值接收者(User)方法会在调用时创建接收者的副本。
指针接收者(*User)方法通过指针引用原始值,不会创建副本。

当通过指针调用值接收者方法时,Go 会自动将指针转换为值。
当通过值调用指针接收者方法时,Go 会自动将值转换为指针。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func (self *User) TestPointer() {
fmt.Printf("TestPointer: %p, %v\n", self, self)
}

func (self User) TestValue() {
fmt.Printf("TestValue: %p, %v\n", &self, self)
}

func main() {
u := User{1, "Tom"}
fmt.Printf("User: %p, %v\n", &u, u)

mv := User.TestValue
mv(u)

mp := (*User).TestPointer
mp(&u)

mp2 := (*User).TestValue // *User 方法集包含 TestValue。签名变为 func TestValue(self *User)。实际依然是 receiver value copy。
mp2(&u)
}

输出:

1
2
3
4
User: 0xc42000a060, {1 Tom}
TestValue: 0xc42000a0a0, {1 Tom}
TestPointer: 0xc42000a060, &{1 Tom}
TestValue: 0xc42000a100, {1 Tom}

第二个和第四个都是副本,第四个进行了指针到值的转换,再进行副本操作

自定义error

抛出
1
panic(err)
返回
1
func () (err error)
自定义
1
2
3
4
5
6
7
8
9
10
11
type PathError struct {
path string
op string
createTime string
message string
}

func (p *PathError) Error() string {
return fmt.Sprintf("path=%s \nop=%s \ncreateTime=%s \nmessage=%s", p.path,
p.op, p.createTime, p.message)
}

错误

在函数的返回值返回错误error类型数据,error是一个接口类型

定义:

1
2
3
type error interface {
Error() string
}

返回值生成错误信息,使用errors.new

1
2
3
4
5
6
func Sqrt(f float64) (float64, error) {
if f < 0 {
return 0, errors.New("math: square root of negative number")
}
// 实现
}

自定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
type DivideError struct {
dividee int
divider int
}

func (de *DivideError) Error() string {
strFormat := `
Cannot proceed, the divider is zero.
dividee: %d
divider: 0
`
return fmt.Sprintf(strFormat, de.dividee)
}

Go语言中,nil 是一个预定义的标识符,用于表示指针、接口、切片、映射、通道和函数类型的零值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func findUser(users []user, name string) (v *user, err error) {
for _, u := range users {
if u.name == name {
return &u, nil
}
}
return nil, errors.New("not found")
}

func main() {
u, err := findUser([]user{{"wang", "1024"}}, "wang")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(u.name) // wang

if u, err := findUser([]user{{"wang", "1024"}}, "li"); err != nil {
fmt.Println(err) // not found
return
} else {
fmt.Println(u.name)
}
}

字符串

中文字符可能占用两个字节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func main() {
a := "hello"
fmt.Println(strings.Contains(a, "ll")) // true
fmt.Println(strings.Count(a, "l")) // 2
fmt.Println(strings.HasPrefix(a, "he")) // true
fmt.Println(strings.HasSuffix(a, "llo")) // true
fmt.Println(strings.Index(a, "ll")) // 2
fmt.Println(strings.Join([]string{"he", "llo"}, "-")) // he-llo
fmt.Println(strings.Repeat(a, 2)) // hellohello
fmt.Println(strings.Replace(a, "e", "E", -1)) // hEllo
fmt.Println(strings.Split("a-b-c", "-")) // [a b c]
fmt.Println(strings.ToLower(a)) // hello
fmt.Println(strings.ToUpper(a)) // HELLO
fmt.Println(len(a)) // 5
b := "你好"
fmt.Println(len(b)) // 6
}

输出

换行输出

1
fmt.Println

格式化

%v代表格式化输出中的位置

%+v输出详细信息

%#v输出包,类型等更详细信息

1
fmt.Printf("s=%v",s)

%.2f两位小数

json

导入

1
2
3
4
import (
"encoding/json"
"fmt"
)

结构体字段首字母大写,或者在数据类型后加

1
`json:"shuxing_name"`
1
2
3
4
5
type userInfo struct {
Name string
Age int `json:"age"`
Hobby []string
}

序列化,返回为byte数据,使用string(return_data)打印查看

1
2
json.Marshal(a)
fmt.Println(string(return_data))

反序列化

1
2
var b userInfo
err = json.Unmarshal(reutrn_data,&b)

时间处理

导入

1
import ("time")

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 获取当前时间
time.NOw()

//构造时区时间
time.Date(2022, 3, 23, 1, 12, 21, 0, time.UTC)

//获取时间信息
t.Year()
Month()
Day()
Hour()
Minute
Second

//时间差
diff := t2.Sub(t)
fmt.Println(diff)
fmt.Println(diff.Minutes(),diff.Seconds())

//格式化时间字符串
t.Format("2003-02-23 12:32:12")

//解释成时间
t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")

func Parse(layout, value string) (Time, error)

//layout 参数是一个格式化字符串,它定义了输入字符串 value 的格式。这个格式化字符串使用的是与 time.Format 函数相同的布局参数。

//时间戳
时间类型数据使用Unix方法
now.Unix()

语言类型转换

类型断言

类型断言用于将接口类型转换为指定类型

1
2
3
value.(type)
or
value.(T)
1
2
var i interface{} = "Hello, World"
str, ok := i.(string)

value是接口类型,type是目的类型

断言成功,返回,值和布尔值表示成功与否

类型转换

数字解析

导入

1
import strconv

类型转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
f, _ := strconv.ParseFloat("1.234", 64)
fmt.Println(f) // 1.234

n, _ := strconv.ParseInt("111", 10, 64)
fmt.Println(n) // 111
//10表示转换结果的进制

n, _ = strconv.ParseInt("0x1000", 0, 64)
fmt.Println(n) // 4096
//八进制

n2, _ := strconv.Atoi("123")
fmt.Println(n2) // 123

n2, err := strconv.Atoi("AAA")
fmt.Println(n2, err)
// 0 strconv.Atoi: parsing "AAA": invalid syntax

num := 123
str := strconv.Itoa(num)

进程信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import (
"os"
"os/exec"
)

os.Args 命令行参数
1:二进制文件路径
2:命令参数

os.Getenv("PATH")
os.Setenv("aa","bb")

exec.Command("grep","127.0.0.1","/etc/hosts").CombinedOutput()
执行获取输出

接口

实现结构体方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* 定义接口 */
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
method_name3 [return_type]
...
method_namen [return_type]
}

/* 定义结构体 */
type struct_name struct {
/* variables */
}

/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
/* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
/* 方法实现*/
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Writer interface {
Write([]byte) (int, error)
}

// 实现 Writer 接口的结构体 StringWriter
type StringWriter struct {
str string
}

// 实现 Write 方法
func (sw *StringWriter) Write(data []byte) (int, error) {
sw.str += string(data)
return len(data), nil
}

指针接收者实现接口

需要传入对应数据类型的指针作为参数调用,

空接口

空接口作为函数的参数

使用空接口实现可以接收任意类型的函数参数。

1
2
3
4
// 空接口作为函数参数
func show(a interface{}) {
fmt.Printf("type:%T value:%v\n", a, a)
}
空接口作为map的值

使用空接口实现可以保存任意值的字典。

1
2
3
4
5
var studentInfo = make(map[string]interface{})
studentInfo["name"] = "李白"
studentInfo["age"] = 18
studentInfo["married"] = false
fmt.Println(studentInfo)

类型断言

1
2
3
4
x.(T)

x:表示类型为interface{}的变量
T:表示断言x可能是的类型。

该语法返回两个参数,第一个参数是x转化为T类型后的变量,第二个值是一个布尔值,若为true则表示断言成功,为false则表示断言失败

输入

fmt.Scan和fmt.Scanln

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

import "fmt"

func main() {
var name string
var age int

fmt.Print("请输入您的名字: ")
fmt.Scan(&name)

fmt.Print("请输入您的年龄: ")
fmt.Scanln(&age)

_, err := fmt.Scanf("%s", &name)

fmt.Printf("您好, %s! 您的年龄是 %d。\n", name, age)
}

bufio.NewReader

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

import (
"bufio"
"fmt"
"os"
"strings"
)

func main() {
reader := bufio.NewReader(os.Stdin)
fmt.Print("请输入一些文本: ")
text, _ := reader.ReadString('\n')
// 处理 Windows 系统下的换行符问题
text = strings.TrimSpace(text)
fmt.Printf("您输入的文本是: %s\n", text)
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)

func main() {
reader := bufio.NewReader(os.Stdin)
fmt.Print("请输入多个数字,用空格分隔: ")
input, _ := reader.ReadString('\n')
numbers := strings.Fields(input)

var sum int
for _, number := range numbers {
n, err := strconv.Atoi(strings.TrimSpace(number))
if err != nil {
fmt.Println("转换错误:", err)
return
}
sum += n
}

fmt.Printf("数字的总和是: %d\n", sum)
}

异常处理

panic抛出错误,recover捕获错误

注意:

1
recover处理panicdeferpanic之前定义,recoverdefer调用的函数中有效

recover捕获

1
2
3
4
5
6
7
8
9
func test() {
defer func() {
if err := recover(); err != nil {
println(err.(string)) // 将 interface{} 转型为具体类型。
}
}()

panic("panic error!")
}

仅最后一个错误可被捕获

1
2
3
4
5
6
7
8
9
10
11
func test() {
defer func() {
fmt.Println(recover())
}()

defer func() {
panic("defer panic")
}()

panic("test panic")
}

输出:

1
defer panic
无效捕获

recover 只有在延迟调用内直接调用才会终止错误,否则返回nil

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
defer func() {
fmt.Println(recover()) //有效
}()

defer recover() //无效!

defer fmt.Println(recover()) //无效!

defer func() {
func() {
println("defer inner")
recover() //无效!
}()
}()

//有效
func except() {
fmt.Println(recover())
}

func test() {
defer except()
panic("test panic")
}

保证后续代码可执行,使用匿名函数调用可保护后续代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func test(x, y int) {
var z int

func() {
defer func() {
if recover() != nil {
z = 0
}
}()
panic("test panic")
z = x / y
return
}()

fmt.Printf("x / y = %d\n", z)
}

导致关键流程出现不可修复性错误的使用 panic,其他使用 error。

获取数据类型

1
reflect.TypeOf()

gobase
https://rpniu.github.io/2025/05/19/gobase/
作者
rPniu
发布于
2025年5月19日
许可协议