基础语法

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

import "fmt"

func main() {
fmt.Println("Hello, World!")
}
//需要注意的是 { 不能单独放在一行,所以以下代码在运行时会产生错误:
/*
package main

import "fmt"

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

格式化输入输出

Go 语言中使用 fmt.Sprintf 或 fmt.Printf 格式化字符串并赋值给新串:

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

import (
"fmt"
)

func main() {
// %d 表示整型数字,%s 表示字符串
var stockcode=123
var enddate="2020-12-31"
var url="Code=%d&endDate=%s"
var target_url=fmt.Sprintf(url,stockcode,enddate)
fmt.Println(target_url)
// 输出:Code=123&endDate=2020-12-31
}

变量

变量声明的两种主要形式——

1
2
var a string = "hello"
a string := "hello" //如果变量已经使用 var 声明过了,再使用 := 声明变量,就产生编译错误

常见变量声明——

1
2
3
4
5
6
7
8
9
10
11
12
13
var (
a string
b int32 = 1
)

var c map[int]string //包级变量

func hi(){
d := 12 //仅限函数内部使用,变量后面不能有类型
var c map[int]string // 函数级变量
var e = "hello world" //自动推断变量类型
}

go 语言跟一般的语言不同,它使用变量字母的大小写来区分变量的可导出性质

  • 大写 (如果使用中文作为变量名称,默认是可导出的) 代表可导出
  • 小写代表仅限包内部使用 (包这一级,多个文件只要是同一个包就可以使用)
1
2
3
4
package Example

var OutPutName = 0o77
var inName = 0x99

其中 OutPutName 是一个可导出的变量,inName 是不可导出变量

小心 shadow 的变量

当有两个两个以上的变量在赋值时,如果其中有一个未被提前声明,那么就需要使用 :=,这个时候系统会自动判断有哪些未提前声明

1
2
3
4
5
6
7
8
9
func WithName(){
var a string
// tracing 为 bool 类型
if tracing{
a,err := example.Method()
} else {
a,err := example.Method1()
}
}

在外层,a 已经提前声明,但是在 if 这个作用域中,由于 err 并未提前声明,所以使用了 :=,由于系统无法获知这里的 a 是否需要再次声明,所以 go 语言默认 a 是一个新的变量,这样外层的 a 就无法得到新的值,外层 a 也就被内层的 a 给 shadow 了

解决——可以将 err 也提前声明,或者也可以改变内部的变量名称,再赋值回去

1
2
ai,err := example.Method()
a = ai

常量

普通常量

常量不可包含计算

在 Go 中,常量必须在编译时就可以确定其值。因此,常量的值必须是一个编译时的常量表达式,不能包含运行时的计算。

1
2
3
4
5
const (
x = 2 + 3 // 常量表达式,结果为 5
y = (x * 2) % 10 // 常量表达式,结果为 0,注意默认x=0,因为是编译时就确定值了!!!
z = x == 5 || y == 0 // 常量表达式,结果为 true,注意默认x,y=0
)

复制与枚举常量 iota

go 语言提供隐式重复前一个非空表达式的机制

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

import "fmt"

const (
PI = 3.14
a
b
c
d
e
f
)

func main() {
fmt.Println(PI, a, b, c, d, e, f)
}

这段代码将会全部输出 3.14,因为a~f未声明,表示其复制了上一个

iota 是 go 语言的一个预定义标识符,它表示 const 声明块中,每一个常量所处位置的偏移量

1
2
3
4
5
6
7
8
const(
a = iota //0
b = 12
c = iota // 2,他表示每一个常量所处位置的**偏移量**,所以是2不是1
d// 3
e// 4
f,g // 同一行的iota一样,所以f、g都是5
)

零值

下面是所有的原生 go 类型的零值:

  • 整数类型:0
  • 浮点数:0.0
  • 布尔类型:false
  • 字符串类型:“”
  • 指针,接口,切片,channel,map,function:nil

一般情况下,0值都是可以直接用的。但是下列情况除外——

  • slice 零值赋值
  • map 零值赋值
  • 互斥锁的值复制
    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
    // slice的例子
    var s slice
    s[0] = 1 // 错误 ❌

    //map的例子
    var m map[int]int
    m[1] = 12 // 错误 ❌

    // 互斥锁的例子
    package main

    import (
    "sync"
    )

    func main() {
    var mu sync.Mutex
    mu.Lock()
    foo(mu)
    mu.Unlock()
    }

    func foo(mu sync.Mutex) {
    mu.Lock()
    mu.Unlock()
    }

分支语句

IF语句

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

import "fmt"

func main() {
/* 局部变量定义 */
var a int = 100;

/* 判断布尔表达式 */
if a < 20 {
/* 如果条件为 true 则执行以下语句 */
fmt.Printf("a 小于 20\n" );
} else {
/* 如果条件为 false 则执行以下语句 */
fmt.Printf("a 不小于 20\n" );
}
fmt.Printf("a 的值为 : %d\n", a);

}

FOR循环

1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"

func main() {
sum := 0
for i := 0; i <= 10; i++ {
sum += i
}
fmt.Println(sum)
}

FOR- EACH类循环

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

func main() {
strings := []string{"google", "runoob"}
// for key value
for i, s := range strings {
fmt.Println(i, s)
}


numbers := [6]int{1, 2, 3, 5}
for i,x:= range numbers {
fmt.Printf("第 %d 位 x 的值 = %d\n", i,x)
}
}

函数

1
2
3
4
5
6
7
8
9
10
11
12
/* 函数返回两个数的最大值 */
func max(num1, num2 int) int {
/* 声明局部变量 */
var result int

if (num1 > num2) {
result = num1
} else {
result = num2
}
return result
}

Go语言支持打包传回多个参数

1
2
3
4
5
6
7
8
9
10
11
12
package main

import "fmt"

func swap(x, y string) (string, string) {
return y, x
}

func main() {
a, b := swap("Google", "Runoob")
fmt.Println(a, b)
}

默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数。

类型

常用类型

数组

数组的格式为

1
2
3
4
5
6
7
8
9
10
11
12
var arrayName [size]dataType

// 例如
var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

// 若数组的元素大小size暂不确定,可以使用...来代替,或省略
var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

// 创建三一维数组,各数组长度不同
row1 := []string{"fish""shark""eel"}
row2 := []string{"bird"}
row3 := []string{"lizard""salamander"}

多维数组

1
2
3
4
5
var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type
var threedim [5][10][4]int

// 创建空的二维数组
animals := [][]string{}

向函数传递数组

如果你想要在函数内修改原始数组,可以通过传递数组的指针来实现。

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
// 对比区分
package main

import "fmt"

// 函数接受一个数组作为参数——不能修改
func modifyArray(arr [5]int) {
for i := 0; i < len(arr); i++ {
arr[i] = arr[i] * 2
}
}

// 函数接受一个数组的指针作为参数——可以修改
func modifyArrayWithPointer(arr *[5]int) {
for i := 0; i < len(*arr); i++ {
(*arr)[i] = (*arr)[i] * 2
}
}

func main() {
// 创建一个包含5个元素的整数数组
myArray := [5]int{1, 2, 3, 4, 5}

fmt.Println("Original Array:", myArray)

// 传递数组给函数,但不会修改原始数组的值
modifyArray(myArray)
fmt.Println("Array after modifyArray:", myArray)

// 传递数组的指针给函数,可以修改原始数组的值
modifyArrayWithPointer(&myArray)
fmt.Println("Array after modifyArrayWithPointer:", myArray)
}

特殊类型

指针

指针声明:var ip *int /* 指向整型*/

简单的指针使用:

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 a int= 20 /* 声明实际变量 */
var ip *int /* 声明指针变量 */

ip = &a /* 指针变量的存储地址 */

fmt.Printf("a 变量的地址是: %x\n", &a )

/* 指针变量的存储地址 */
fmt.Printf("ip 变量储存的指针地址: %x\n", ip )

/* 使用指针访问值 */
fmt.Printf("*ip 变量的值: %d\n", *ip )
}

当一个指针被定义后没有分配到任何变量时,它的值为 nil。

指针数组:var ptr [MAX]*int; 每个元素都是一个指针

结构体

结构体的声明与导出 & 调用

1
2
3
4
5
type People struct {
Addr string
name string
year int
}
  • Peopel 首字母大写,根据我们所学的首字母大写可导出的知识,它是包级可导出结构
  • Addr 首字母大写,所以它是可导出字段,name 和 year 都是小写,所以他们俩不可导出
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
// 结构体的调用

func main(){
// 方式1用&取地址
var p = &People{
Addr: "北京",
name: "小明",
year: 2000,
}
// 这里是语法糖,p虽然是people的指针,
// 但是它却可以直接调用 Addr
// 实际上就是 (*p).Addr 的省略
p.Addr = "上海"
p.name = "小红"
p.year = 2001
fmt.Println(p.Addr, p.name, p.year)

// 方式2:我们还可以使用new来代替&
// 这种写法跟上文中的 var p = &People{}
// 一个意思
var p1 = new(People)
fmt.Println(p1)

// 方式3
var p2 People
p2.Addr="chengdu"


}
type People struct {
Addr string
name string
year int
}

切片Slice

Go 语言切片是对数组的抽象。

切片基本操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 初始化一个切片,设置长度为5,容量为10(容量为可选参数)
a := make([]int,5,10)

// 获取一个新的切片,遵循左闭右开的原则,取值时并且只看长度不能看容量
s1 := s[startIndex:endIndex]
// b切片,长度为1,容量为8(下文有讲,向右原则)
b:= a[2:3]

// 判断切片长度为0
len(a) == 0

// append 数据
a = append(a, 1)

// 切片初始化
s :=[] int {1,2,3}
// 切片初始化,是对arr的【引用】
s := arr[startIndex:endIndex]

// 将切片内容清空(element设置为初始值,比如int就是0,string就是"")
clear(a)

// 将一个切片转为一个数组,注意,新数组不能超过切片的长度,且是数据的【深度拷贝】
var arr = [4]int(a)

一个切片在未初始化之前默认为 nil,长度为 0

切片复制

copy 是 go 语言的内置函数,全局使用,copy(a,b []Type),copy 是深度拷贝,它将后者的数据完全拷贝给前者。

要注意的是,将要被复制的元素能复制的量取决于前者的 length

比如下面这种情况,被复制的元素就是 0,但是并不会 panic

1
2
3
4
5
src := []int{1,2,3}
var d []int
// []
copy(d,src)

一般来说,我们会使用相同的 length:

1
2
3
src := []int{1,2,3}
d := make([]int,len(src))
copy(d,src)

或者直接使用 append 也能做到 copy

1
2
src := []int{1,2,3}
d := append([]int(nil),src...)

切片中的 range 注意事项

range 时,我们直接修改返回的值是不会生效的,因为返回的值并不是原始的数据,而是数据的复制。

hl:year++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Student struct {
year int
}

func main() {
result := []Student{
{12},
{13},
}
for _, student := range result {
student.year++ // ×student实际不会改变!
}
// 12 13
fmt.Println(result)
}

也就是说,这里的 student := student 的改变不会影响 result 中的任何数据,除非这里的 []Student[]*Student

下面我们演示一下,正确的在 range 时的操作方式:

hl:year++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Student struct {
year int
}

func main() {
result := []Student{
{12},
{13},
}
for i := range result {
result[i].year++ // √
}
// 13 14
fmt.Println(result)
}

正确的方式就是直接使用 result 本身进行操作,就可以真正的去改变 result 了。

Map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 创建一个空的 Map
m := make(map[string]int)

// 创建一个初始容量为 10 的 Map
m := make(map[string]int, 10)

// OR
// 使用字面量创建 Map
m := map[string]int{
"apple": 1,
"banana": 2,
"orange": 3,
}

// 获取键值对
v1 := m["apple"]
v2, ok := m["pear"] // 如果键不存在,ok 的值为 false,v2 的值为该类型的零值

// 获取长度
len := len(m)

// 从某map中删除某元素
delete(countryCapitalMap, "France")

通道Chan

理解为队列,先进先出

1
2
3
4
5
6
7
8
9
10
11
// 声明不带缓冲的通道
ch1 := make(chan string)// 一般不这么干,无缓冲的 chan,赋值后就陷入了阻塞

// 声明带10个缓冲的通道
ch2 := make(chan string, 10)

// 声明只读通道
ch3 := make(<-chan string)

// 声明只写通道
ch4 := make(chan<- string)

完整代码

1
2
3
4
5
6
ch1 := make(chan string, 10)
ch1 <- "a" // 写入
val, ok := <- ch1
// 或
val := <- ch1
close(chan)// 关闭
  • close 以后不能再写入,写入会出现 panic
  • 重复 close 会出现 panic
  • 只读的 chan 不能 close
  • close 以后还可以读取数据

类型转换和判断

类型转换

1
2
var a int = 10
var b float64 = float64(a)

字符串转换为整数

注意,strconv.Atoi 函数返回两个值,第一个是转换后的整型值,第二个是可能发生的错误,我们可以使用空白标识符 _ 来忽略这个错误

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

import (
"fmt"
"strconv"
)

func main() {
str := "123"
num, err := strconv.Atoi(str)
// str := strconv.Itoa(num)是整数转字符串
// num, err := strconv.ParseFloat(str, 64) 是str转浮点数
if err != nil {
fmt.Println("转换错误:", err)
} else {
fmt.Printf("字符串 '%s' 转换为整数为:%d\n", str, num)
}
}

类型判断

1
2
3
4
5
6
7
8
9
10
11
func main() {
var x interface{}
x = "pprof.cn" //空接口可以传入任意值,可以据此来判断空接口中的值是啥
v, ok := x.(string)// 判断x是否是string类型
// 返回v:第一个参数是x转化为T类型后的变量;ok:是否是string类型
if ok {
fmt.Println(v)
} else {
fmt.Println("类型断言失败")
}
}

接口

它定义了一个或多个方法签名。接口是 Go 语言中实现多态的关键特性之一。接口在 Go 中的使用与许多其他编程语言中的接口或抽象类相似

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

import "fmt"

// Animal 接口定义了一个方法 Speak
type Animal interface {
Speak() string
}

// Dog 类型实现了 Animal 接口
type Dog struct{}

// Dog 的 Speak 方法
func (d Dog) Speak() string {
return "Woof!"
}

// Cat 类型也实现了 Animal 接口
type Cat struct{}

// Cat 的 Speak 方法
func (c Cat) Speak() string {
return "Meow!"
}

// MakeAnimalSpeak 函数接受一个 Animal 接口类型的参数
func MakeAnimalSpeak(a Animal) {
fmt.Println(a.Speak())
}

func main() {
myDog := Dog{}
myCat := Cat{}

// 这里展示了多态性:我们可以传递 Dog 或 Cat 到同一个函数
MakeAnimalSpeak(myDog) // 输出: Woof!
MakeAnimalSpeak(myCat) // 输出: Meow!
}

空接口

空接口是指没有定义任何方法的接口。因此任何类型都实现了空接口。

空接口类型的变量可以存储任意类型的变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
// 定义一个空接口x
var x interface{}
s := "pprof.cn"
x = s
fmt.Printf("type:%T value:%v\n", x, x)
i := 100
x = i
fmt.Printf("type:%T value:%v\n", x, x)
b := true
x = b
fmt.Printf("type:%T value:%v\n", x, x)
}

  • 空接口与函数传参

可以传入任意的参数,就等同于TS的any

1
2
3
4
// 空接口作为函数参数
func show(a interface{}) {
fmt.Printf("type:%T value:%v\n", a, a)
}
  • 空接口存储任意的map
    1
    2
    3
    4
    5
    6
    // 空接口作为map值
    var studentInfo = make(map[string]interface{})
    studentInfo["name"] = "李白"
    studentInfo["age"] = 18
    studentInfo["married"] = false
    fmt.Println(studentInfo)

其他

defer关键字

1
defer dao.Close() // 程序退出关闭数据库连接

defer关键字用于延迟执行函数。当你在函数中使用defer时,它会将你要执行的函数以及参数暂存起来,等到当前函数执行完毕后再执行这些暂存的函数

这通常用于资源管理,比如打开的文件、网络连接或者数据库连接,你可以在函数结束时确保这些资源被正确关闭。


[1] https://github.com/shgopher/GOFamily
[2] https://draveness.me/golang/docs/part3-runtime/ch06-concurrency/golang-context/
[3] https://www.topgoer.cn/docs/golang/golang-1ccjbpfstsfi1