数据类型 Go语言(Golang)是一种静态类型语言,变量的类型在编译时就已经确定。
示例
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 42 43 44 package mainimport ( "fmt" ) func main () { var b bool = true var i int = 42 var f float64 = 3.14 var s string = "Hello, Go!" var arr [3 ]int = [3 ]int {1 , 2 , 3 } var sl []int = []int {4 , 5 , 6 } var m map [string ]int = map [string ]int {"foo" : 1 , "bar" : 2 } type Person struct { Name string Age int } var p Person = Person{"Alice" , 30 } var ptr *int = &i var any interface {} = "can be any type" fmt.Println(b, i, f, s, arr, sl, m, p, *ptr, any) }
数字字面量语法
Go1.13版本之后引入了数字字面量语法,这样便于开发者以二进制、八进制或十六进制浮点数的格式定义数字,例如:
v := 0b00101101
, 代表二进制的 101101,相当于十进制的 45。
v := 0o377
,代表八进制的 377,相当于十进制的 255。
v := 0x1p-2
,代表十六进制的 1 除以 2²,也就是 0.25。
而且还允许用 _
来分隔数字, v := 123_456
表示 v 的值等于 123456。
数组(Array) 数组是值类型,赋值和传参会复制整个数组。因此改变副本的值,不会改变本身的值。
数组定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 var testArray [3 ]int var numArray = [3 ]int {1 , 2 }var cityArray = [3 ]string {"北京" , "上海" , "深圳" }var cityArray = [...]string {"北京" , "上海" , "深圳" }a := [...]int {1 : 1 , 3 : 5 } a := [3 ][2 ]string { {"北京" , "上海" }, {"广州" , "深圳" }, {"成都" , "重庆" }, }
数组遍历
1 2 3 4 5 6 7 8 9 10 11 12 func main () { var a = [...]string {"北京" , "上海" , "深圳" } for i := 0 ; i < len (a); i++ { fmt.Println(a[i]) } for index, value := range a { fmt.Println(index, value) } }
切片(Slice) 切片(Slice)是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。切片是一个引用类型,它的内部结构包含地址
、长度
和容量
。切片一般用于快速地操作一块数据集合。
切片定义示例
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 42 43 44 45 46 47 48 49 func main () { var arr [5 ]int = [...]int {1 , 2 , 3 , 4 , 5 } arrRef := arr[0 :3 ] fmt.Printf("arr: %v\n" , arr) fmt.Printf("arrRef: %v\n" , arrRef) modifyArrItem(arr) fmt.Printf("after arr modify: %v\n" , arr) modifyByPointer(&arr) fmt.Printf("use pointer modify after arr: %v\n" , arr) arrRef[0 ] = 45 fmt.Printf("modify origin arr after slice: %v\n" , arr) arr[0 ] = 34 fmt.Printf("arrRef: %v\n" , arrRef) var slice []int = make ([]int , 5 , 10 ) fmt.Printf("slice: %v\n" , slice) var slice2 []int = []int {1 ,2 ,3 ,4 ,5 } fmt.Printf("slice2: %v\n" , slice2) } func modifyArrItem (arr [5]int ) { arr[0 ] = 123 } func modifyByPointer (arr *[5]int ) { (*arr)[0 ] = 123 }
切片的本质就是对底层数组的封装,它包含了三个信息:底层数组的指针、切片的长度(len)和切片的容量(cap)。
数组a := [8]int{0, 1, 2, 3, 4, 5, 6, 7}
,切片s1 := a[:5]
,相应示意图:
切片s2 := a[3:6]
,相应示意图:
切片之间是不能比较的,不能使用==
操作符来判断两个切片是否含有全部相等元素。 切片唯一合法的比较操作是和nil
比较。 一个nil
值的切片并没有底层数组,一个nil
值的切片的长度和容量都是0。但是一个长度和容量都是0的切片不一定是nil
。
1 2 3 var s1 []int s2 := []int {} s3 := make ([]int , 0 )
映射(Map) map是一种无序的基于key-value
的数据结构,Go语言中的map是引用类型,必须初始化才能使用。定义语法:make(map[KeyType]ValueType, [cap])
1 map := make (map [string ]int , 8 )
结构体(Struct) 结构体定义
1 2 3 4 5 6 7 8 type person struct { name string city string age int8 } var user struct {Name string ; Age int }
创建结构体
1 2 3 4 5 var s1 studentvar s2 student = student{}var s3 student = student{"wangwu" ,15 }var s4 *student = new (student)var s5 *student = &student{}
结构体初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 p1 := person{ name: "小王子" , city: "北京" , age: 18 , } p2 := &person{ name: "小王子" , city: "北京" , age: 18 , } p3 := &person{ "小王子" , "北京" , 28 , }
结构体方法定义
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 type Person struct { Name string Age int } func (person Person) sayHello() { fmt.Println("hello," , person.Name) } func (person *Person) setName(name string ) { person.Name = name } func (person Person) String() string { return fmt.Sprintf("person = {Name : %s, Age : %d}" , person.Name, person.Age) } func main () { person := Person{"zhansan" , 12 } person.sayHello() fmt.Println(person) person.setName("lisi" ) fmt.Println(person) fmt.Println(&person) }
结构体序列化
1 2 3 4 5 6 7 8 type person struct { Name string `json:"name"` Age int `json:"age"` } var person person = person{"tom" ,23 }jsonStr, _ := json.Marshal(person) fmt.Println(string (jsonStr))
结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)。Tag
是结构体的元信息,可以在运行的时候通过反射的机制读取出来。 Tag
在结构体字段的后方定义,由一对反引号 包裹起来。**为结构体编写Tag
时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。 **
Go结构体的内存对齐
指针(Pointer) Go语言中的指针不能进行偏移和运算,只需记住两个符号:&
(取地址)和*
(根据地址取值)。
示例
1 2 3 4 5 6 7 8 9 10 func main () { a := 10 b := &a fmt.Printf("a:%d ptr:%p\n" , a, &a) fmt.Printf("b:%p type:%T\n" , b, b) fmt.Println(&b) c := *b fmt.Printf("type of c:%T\n" , c) fmt.Printf("value of c:%v\n" , c) }
对变量进行取地址(&)操作,可以获得这个变量的指针变量。
指针变量的值是指针地址。
对指针变量进行取值(*)操作,可以获得指针变量指向的原变量的值。
b := &a
内存示意图
new
new(T)
分配T类型的零值内存空间并返回指向该内存空间的指针,即返回*T
类型的值。
new
用于分配值类型(如:数组、结构体等),并将其初始化为零值。
new
不会对内存进行额外的初始化操作,仅仅是分配内存。
示例
1 2 3 4 5 6 7 type MyStruct struct { a int b string } p := new (MyStruct) fmt.Println(p)
make
make(T, args)
用于创建并初始化slice、map或channel类型的数据结构。
make
返回的是T类型的值(而不是指针),这些值已经被初始化过,可以直接使用。
make
会进行额外的初始化操作,具体取决于传递的参数。
示例
1 2 3 4 5 6 7 8 9 10 11 s := make ([]int , 10 ) fmt.Println(s) m := make (map [string ]int , 10 ) fmt.Println(m) c := make (chan int , 10 ) fmt.Println(c)
new
用于分配内存,返回指向新分配的零值内存的指针,适用于值类型。
make
用于创建和初始化slice、map和channel,返回的是这些类型的值,并进行了必要的初始化。
接口(Interface) go 语言实现接口没有 implement 关键字,只需要定义与接口名称一致,且定义与所实现接口的全部抽象方法即可,鸭子类型。
定义
1 2 3 4 5 6 7 8 9 10 type 接口类型名 interface { 方法名1 ( 参数列表1 ) 返回值列表1 方法名2 ( 参数列表2 ) 返回值列表2 … } type game interface { start() end() }
示例
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 type game interface { start() end() } type lol struct {} func (lol lol) start() { fmt.Println("开始游戏" ) } func (lol lol) end() { fmt.Println("结束游戏" ) } type computer struct {} func (computer *computer) work(game game) { game.start() game.end() } func work (game game) { game.start() game.end() } func main () { lol := lol{} computer := computer{} computer.work(lol) work(lol) }
函数(Function) 定义
1 2 3 func 函数名(参数) (返回值){ 函数体 }
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 func calc (x int , y int ) int { return x + y } func calc (x, y int ) int { return x + y } func calc (x ...int ) int {} func calc (x, y int ) (int , int ) { sum := x + y sub := x - y return sum, sub } func calc (x, y int ) (sum, sub int ) { sum = x + y sub = x - y return }
函数类型 可以使用type
关键字来定义一个函数类型
1 type calculation func (int , int ) int
上面语句定义了一个calculation
类型,它是一种函数类型,这种函数接收两个int类型的参数并且返回一个int类型的返回值。凡是满足这个条件的函数都是calculation类型的函数。
1 2 3 4 5 6 7 func add (x, y int ) int { return x + y } func sub (x, y int ) int { return x - y }
add和sub都能赋值给calculation类型的变量。
1 2 3 4 5 6 7 8 9 10 func main () { var c calculation c = add fmt.Printf("type of c:%T\n" , c) fmt.Println(c(1 , 2 )) f := add fmt.Printf("type of f:%T\n" , f) fmt.Println(f(10 , 20 )) }
高阶函数 函数作为参数
1 2 3 4 5 6 7 8 9 10 func add (x, y int ) int { return x + y } func calc (x, y int , op func (int , int ) int ) int { return op(x, y) } func main () { ret2 := calc(10 , 20 , add) fmt.Println(ret2) }
函数作为返回值
1 2 3 4 5 6 7 8 9 10 11 func do (s string ) (func (int , int ) int , error ) { switch s { case "+" : return add, nil case "-" : return sub, nil default : err := errors.New("无法识别的操作符" ) return nil , err } }
闭包 参考博文:https://juejin.cn/post/6844903793771937805
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func addUpper () func () int { var num int return func () int { num++ return num } } func makeSuffix (suffix string ) func (string ) string { return func (name string ) string { if strings.HasSuffix(name, suffix) { return name } return name + suffix } }
内存逃逸 参考博文:https://zhuanlan.zhihu.com/p/672733054
defer Go语言中的defer
语句会将其后面跟随的语句进行延迟处理。在defer
归属的函数即将返回时,将延迟处理的语句按defer
定义的逆序进行执行,先被defer
的语句最后被执行,最后被defer
的语句,最先被执行。
defer
panic
recover
使用示例
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 func main () { throwError() } func catchError () int { defer func () { err := recover () if err != nil { fmt.Println(err) } }() num1 := 1 num2 := 0 return num1 / num2 } func customizeError (name string ) error { if name == "" { return errors.New("name is null" ) } return nil } func throwError () { err := customizeError("" ) if err != nil { fmt.Println(err) panic (err) } }
流程控制 Go语言中最常用的流程控制有if
和for
,没有while
语法,而switch
和goto
主要是为了简化代码、降低重复代码而生的结构,属于扩展类的流程控制。Go语言规定与if
匹配的左括号{
必须与if和表达式
放在同一行,{
放在其他位置会触发编译错误,其他流程控制也是如此。
if 条件判断还有一种特殊的写法,可以在 if 表达式之前添加一个执行语句,再根据变量值进行判断
1 2 3 if score := 65 ; score >= 90 { fmt.Println("A" ) }
for range
1 2 3 for i := range 5 { fmt.Println(i) }
switch 每个case
默认存在break
,一个分支可以有多个值,多个case值中间使用英文逗号分隔。分支还可以使用表达式,这时候switch语句后面不需要再跟判断变量。
1 2 3 4 5 6 7 8 9 10 func switchTest () { switch n := 7 ; n { case 1 , 3 , 5 , 7 , 9 : fmt.Println("奇数" ) case n < 1 : fmt.Println(n) default : fmt.Println(n) } }
fallthrough
语法可以执行满足条件的case的下一个case
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func switchTest () { s := "a" switch { case s == "a" : fmt.Println("a" ) fallthrough case s == "b" : fmt.Println("b" ) default : fmt.Println("..." ) } }
flag Go语言内置的flag
包实现了命令行参数的解析。
只是简单的想要获取命令行参数,可以使用os.Args
来获取命令行参数,在使用 go run main.go
时,在命令后面添加参数以空格隔开,输入回车键结束。
1 2 3 4 5 6 func args () { args := os.Args for _, str := range args { fmt.Println(str) } }
flag包支持的命令行参数类型有bool
、int
、int64
、uint
、uint64
、float
float64
、string
、duration
。
1 2 3 4 5 6 7 8 9 10 11 12 func main () { var name string var pwd string var port int flag.StringVar(&name, "u" , "" , "用户名为空" ) flag.StringVar(&pwd, "pwd" , "" , "密码为空" ) flag.IntVar(&port, "p" , 0 , "端口" ) flag.Parse() fmt.Printf("用户名: %s,用户密码: %s,端口号: %d\n" , name, pwd, port) }
log log包定义了Logger类型,该类型提供了一些格式化输出的方法。本包也提供了一个预定义的“标准”logger,可以通过调用函数Print系列
(Print|Printf|Println)、Fatal系列
(Fatal|Fatalf|Fatalln)、和Panic系列
(Panic|Panicf|Panicln)来使用。logger会打印每条日志信息的日期、时间,默认输出到系统的标准错误。Fatal系列函数会在写入日志信息后调用os.Exit(1)。Panic系列函数会在写入日志信息后panic。
1 2 3 4 5 6 7 8 9 10 11 func main () { log.Println("这是一条很普通的日志。" ) log.Printf("这是一条%s日志。\n" , "普通" ) log.Fatalln("这是一条会触发fatal的日志。" ) log.Panicln("这是一条会触发panic的日志。" ) }
配置logger log
标准库中的Flags
函数会返回标准logger的输出配置,而SetFlags
函数用来设置标准logger的输出配置。
1 2 func Flags () int func SetFlags (flag int )
flag选项
1 2 3 4 5 6 7 8 9 10 11 const ( Ldate = 1 << iota Ltime Lmicroseconds Llongfile Lshortfile LUTC LstdFlags = Ldate | Ltime )
配置日志前缀
1 2 func Prefix () string func SetPrefix (prefix string )
配置日志输出位置
1 func SetOutput (w io.Writer)
创建logger
1 func New (out io.Writer, prefix string , flag int ) *Logger
示例
1 2 3 4 5 6 7 8 9 10 func logPrintf () { log.Println("这是一条很普通的日志。" ) log.Printf("这是一条%s日志。\n" , "普通" ) log.Fatalln("这是一条会触发fatal的日志。" ) log.Panicln("这是一条会触发panic的日志。" ) }
1 2 3 4 5 6 7 8 func flagLogPrintf() { log.SetFlags(log.Llongfile | log.Lmicroseconds | log.Ldate) log.Println("这是一条很普通的日志。") log.SetPrefix("golang-log") log.Println("这是一条很普通的日志。") // 2024/06/16 15:35:32.254653 /Users/hougen/code/golang/learn/log/main.go:11: 这是一条很普通的日志。 //golang-log2024/06/16 15:36:40.563437 /Users/hougen/code/golang/learn/log/main.go:14: 这是一条很普通的日志。 }
1 2 3 4 5 6 7 8 9 10 func outLog2File () { logFile, err := os.OpenFile("./xx.log" , os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644 ) if err != nil { fmt.Println("open log file failed, err:" , err) return } log.SetOutput(logFile) log.SetFlags(log.Llongfile | log.Lmicroseconds | log.Ldate) log.Println("这是一条输出到文件的日志。" ) }
1 2 3 4 5 func createLogPrintf () { logger := log.New(os.Stdout, "go-log-" , log.Lshortfile|log.Ldate|log.Ltime) logger.Println("这是自定义的logger记录的日志。" ) }
time 参考博文:https://www.liwenzhou.com/posts/Go/go-time/
file 参考博文:https://www.liwenzhou.com/posts/Go/file/
包管理 参考博文:https://www.liwenzhou.com/posts/Go/package/
net/http Hello world
1 2 3 4 5 6 7 8 9 10 11 12 func greet (w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello World! %s" , time.Now()) } func main () { http.HandleFunc("/" , greet) err := http.ListenAndServe(":8080" , nil ) if err != nil { fmt.Println(err) return } }
基本的http/https请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 http.Get("http://example.com/" ) http.Post("http://example.com/upload" , "image/jpeg" , &buf) http.PostForm("http://example.com/form" , url.Values{"key" : {"Value" }, "id" : {"123" }}) func sendGetRequest () { resp, err := http.Get("https://www.github.com" ) if err != nil { fmt.Println(err) } defer resp.Body.Close() respBoby := make ([]byte , 1024 ) resp.Body.Read(respBoby) fmt.Println(string (respBoby)) }
Get请求收发示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func sendGetRequestWithParam () { param := url.Values{} param.Set("uid" ,"13" ) url, err := url.ParseRequestURI("http://127.0.0.1:8080/get" ) if err != nil { fmt.Println(err) } url.RawQuery = param.Encode() resp, err := http.Get(url.String()) if err != nil { fmt.Println(err) } defer resp.Body.Close() res, _ := ioutil.ReadAll(resp.Body) fmt.Printf(string (res)) }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func get () { http.HandleFunc("/get" , func (w http.ResponseWriter, r *http.Request) { defer r.Body.Close() fmt.Printf("r.Method: %v\n" , r.Method) query := r.URL.Query() fmt.Printf("query.Get(\"uid\"): %v\n" , query.Get("uid" )) answer := `{"status": "ok"}` w.Write([]byte (answer)) }) err := http.ListenAndServe(":8080" , nil ) if err != nil { fmt.Printf("err: %v\n" , err) } }
Post请求收发示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func sendPostRequest () { url := "http://127.0.0.1:8080/post" contentType := "application/json" data := `{"name":"张三","age":18}` resp, err := http.Post(url, contentType, strings.NewReader(data)) if err != nil { fmt.Printf("post failed, err:%v\n" , err) return } defer resp.Body.Close() b, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Printf("get resp failed, err:%v\n" , err) return } fmt.Println(string (b)) }
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 func post () { http.HandleFunc("/post" ,func (w http.ResponseWriter, r *http.Request) { defer r.Body.Close() b, err := ioutil.ReadAll(r.Body) if err != nil { fmt.Printf("read request.Body failed, err:%v\n" , err) return } fmt.Println(string (b)) answer := `{"status": "ok"}` w.Write([]byte (answer)) }) err := http.ListenAndServe(":8080" , nil ) if err != nil { fmt.Printf("err: %v\n" , err) return } }
自定义 管理HTTP客户端的头域、重定向策略和其他设置。自定义Client。
1 2 3 4 5 6 7 8 9 client := &http.Client{ CheckRedirect: redirectPolicyFunc, } resp, err := client.Get("http://example.com" ) req, err := http.NewRequest("GET" , "http://example.com" , nil ) req.Header.Add("If-None-Match" , `W/"wyzzy"` ) resp, err := client.Do(req)
管理代理、TLS配置、keep-alive、压缩和其他设置。自定义Transport。
1 2 3 4 5 6 tr := &http.Transport{ TLSClientConfig: &tls.Config{RootCAs: pool}, DisableCompression: true , } client := &http.Client{Transport: tr} resp, err := client.Get("https://example.com" )
Client和Transport类型都可以安全的被多个goroutine同时使用。应该一次建立、多次重用。
自定义server。
1 2 3 4 5 6 7 8 s := &http.Server{ Addr: ":8080" , Handler: myHandler, ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, MaxHeaderBytes: 1 << 20 , } log.Fatal(s.ListenAndServe())
参考博客:
思维导图
golang基础学习
李文周的博客
golang标准库
框架实现系列:极客兔兔