Go 语言入门指南:基础语法和常用特性解析| 青训营

79 阅读6分钟

Go语言基础语法

一、Go语言开发环境搭建

1.安装编译器,go.dev/

2.配置Go语言开发环境,这里用的是JetBrains的Goland

二、基础语法

2.1 - Hello World

 package main  //定义了包名,程序的入口文件
 ​
 import (
     "fmt"  //导入了fmt包
 )
 ​
 func main(){
     fmt.Println("hello world")
 }

2.2 - 变量

Go 语言变量名由字母、数字、下划线组成,其中首个字符不能为数字。

声明变量的一般形式是使用 var 关键字:

 var a = "initial"
 var b,c int = 1,2
 var d = true  //根据值自行判定变量类型。
 var e float64 //指定变量类型,如果没有初始化,则变量默认为零值。
 f := float32(e) //简短形式,使用 := 赋值操作符

定义常量

 const s string = "constant"
 const h = 100
 const i = 3e20 / h

iota,特殊常量,可以认为是一个可以被编译器修改的常量。

 const (
     a = iota
     b = iota
     c = iota
 )

2.3 - 基础语法

1. if-else循环

 if 布尔表达式 {
    /* 在布尔表达式为 true 时执行 */
 } else {
   /* 在布尔表达式为 false 时执行 */
 }

例如:

 package main
 import "fmt"
 func main(){  //大括号不能单独一行
     if 7%2==0{
         fmt.Println("7 is even")
     } else {
         fmt.Println("7 is odd")
     }
 ​
 }

注意:和C语言不同,Go中大的左括号{不能单独一行

2. for循环

 package main
 import "fmt"
 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
     }
 ​
 }

3. switch语句

语法格式:

 switch var1 {
     case val1:
         ...
     case val2:
         ...
     default:
         ...
 }

变量 var1 可以是任何类型,而 val1 和 val2 则可以是同类型的任意值。类型不被局限于常量或整数,但必须是相同的类型;或者最终结果为相同类型的表达式。

和c/c++的不同点: 1.每一条case语句后不用加break,执行完后直接跳出switch语句

2.可以使用任意的变量类型,比如字符串、结构体等

 package main
 import (
     "fmt"
     "time"
 )
 func main(){  //大括号不能单独一行
     t := time.Now()
     switch {
     case t.Hour()<12:
         fmt.Println("It's before noon")
     default:
         fmt.Println("It's after noon")
     }
 ​
 }

4. 数组

Go 语言提供了数组类型的数据结构。

数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整型、字符串或者自定义类型。

相对于去声明 number0, number1, ..., number99 的变量,使用数组形式 numbers[0], numbers[1] ..., numbers[99] 更加方便且易于扩展。

数组元素可以通过索引(位置)来读取(或者修改),索引从 0 开始,第一个元素索引为 0,第二个索引为 1,以此类推。

声明数组:

 /* var arrayName [size]dataType */
 var numbers [5]int

初始化数组:

 var numbers = [5]int{1, 2, 3, 4, 5}
 //或者是
 numbers := [5]int{1, 2, 3, 4, 5}

如果数组长度不确定,可以使用 ... 代替数组的长度,编译器会根据元素个数自行推断数组的长度:

 var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
 //或
 balance := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

初始化数组中 {} 中的元素个数不能大于 [] 中的数字

在实际的业务代码中,我们很少直接使用数组,因为数组的长度基本上是固定的,我们更多的时候用的是切片。

5. 切片(Slice)

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

Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

代码示例:

 package main
 import "fmt"
 func main(){
     s := make([]string, 3)
     s[0] = "a"
     s[1] = "b"
     s[2] = "c"
     fmt.Println("get:",s[2])  //c
     fmt.Println("len:",len(s))//3
     
     s = append(s, "d")
     s = append(s, "e", "f")
     fmt.Println(s) //[a b c d e f]
     
     c := make([]string, len(s))
     copy(c,s)
     fmt.Println(c) //[a b c d e f]
     
     fmt.Println(s[2:5]) //[c d e]
     fmt.Println(s[:5])  //[a b c d e]
     fmt.Println(s[2:])  //[c d e f]
     
     good := []string{"g","o","o","d"}
     fmt.Println(good) //[g o o d]
 }

6. map

在其他语言中,可能叫做哈希或者是字典。

Map 是一种无序的键值对的集合。

Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。

Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,遍历 Map 时返回的键值对的顺序是不确定的。

在获取 Map 的值时,如果键不存在,返回该类型的零值,例如 int 类型的零值是 0,string 类型的零值是 ""。

Map 是引用类型,如果将一个 Map 传递给一个函数或赋值给另一个变量,它们都指向同一个底层数据结构,因此对 Map 的修改会影响到所有引用它的变量。

代码示例:

 package main
 import "fmt"
 func main(){
     m := make(map[string]int)
     m["one"] = 1
     m["two"] = 2
     fmt.Println(m)       //输出:map[one:1 two:2]
     fmt.Println(len(m))  //2
     fmt.Println(m["one"])//1
     
     r,ok := m["unknow"]
     fmt.Println(r,ok)    //0 false
     delete(m, "one")
     m2 := map[string]int{"one":1,"two":2}
     var m3=map[string]int{"one":1,"two":2}
     fmt.Println(m2,m3)
 }

Golang 中map的元素是完全无序的,遍历的时候是随机输出的

7. range

对于一个slice或者是map等,我们可以通过range来进行快速遍历,过程更加简洁。在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对。

代码示例:

 package main
 import "fmt"
 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
     }
 }

8. 函数

函数的定义格式如下:

 func function_name( [parameter list] ) [return_types] {
    函数体
 }
  • func:函数由 func 开始声明
  • function_name:函数名称,参数列表和返回值类型构成了函数签名。
  • parameter list:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
  • return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
  • 函数体:函数定义的代码集合。

代码示例:

 package main
 import "fmt"
 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
 }
 func main() {
     res := add(1, 2)
     fmt.Println(res) // 3
     v, ok := exists(map[string]string{"a": "A"}, "a")
     fmt.Println(v, ok) // A True
 }

9. 指针

一个指针变量指向了一个值的内存地址。

类似于变量和常量,在使用指针前你需要声明指针。指针声明格式如下:

 var var_name *var-type

代码示例:

 package main
 import "fmt"
 func add2(n int) {
     n += 2   //浅拷贝,对n不起作用
 }
 func add2ptr(n *int) {
     *n += 2
 }
 func main() {
     n := 5
     add2(n)
     fmt.Println(n) // 5
     add2ptr(&n)
     fmt.Println(n) // 7
 }

10. 结构体

Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型。

结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。

代码示例:

 package main
 import "fmt"
 type user struct {
     name     string
     password string
 }
 func main() {
     a := user{name: "wang", password: "1024"}
     b := user{"wang", "1024"}
     c := user{name: "wang"}
     c.password = "1024"
     var d user
     d.name = "wang"
     d.password = "1024"
     fmt.Println(a, b, c, d)                 // {wang 1024} {wang 1024} {wang 1024} {wang 1024}
     fmt.Println(checkPassword(a, "haha"))   // false
     fmt.Println(checkPassword2(&a, "haha")) // false
 }
 func checkPassword(u user, password string) bool {
     return u.password == password
 }
 func checkPassword2(u *user, password string) bool {
     return u.password == password
 }

11.结构体方法

Go语言中可以用结构体去定义一些方法,这非常类似于其它语言中的类函数,

代码示例:

 package main
 import "fmt"
 type user struct {
     name     string
     password string
 }
 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
 }

12. 错误处理

Go 语言通过内置的错误接口提供了非常简单的错误处理机制。

我们可以在编码中通过实现 error 接口类型来生成错误信息。

函数通常在最后的返回值中返回错误信息。使用 errors.New 可返回一个错误信息

代码示例:

 package main
 import (
     "errors"
     "fmt"
 )
 type user struct {
     name     string
     password string
 }
 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)
     }
 }

13. 字符串操作

1、统计字符串的长度,按字节len(str)

2、字符串遍历,同时处理有中文的问题r := []rune(str)

3、字符串转整数:n, err := strconv.Atoi("12")

4、整数转字符串 str = strconv.ltoa(12345)

5、字符串转 []byte: var bytes= []byte("hello go")

6、[]byte转字符串: str = string([]byte{97, 98, 99})

7、10进制转2, 8, 16进制: str = strconv.Formatlnt(123, 2)//2->8, 16

8、查找子串是否在指定的字符串中: strings.Contains("seafood", "oo") //true

9、统计一个字符串有几个指定的子串: num := strings.Count("hello","l")

10、不区分大小写的字符串比较(==是区分字母大小写的): fmt.Println(strings.EqualFoldl"abc", "Abc")

11、返回子串在字符串第一次出现的index值,如果没有返回-1 :index := strings.Index("abcHello","ll")

12、返回子串往字符串最后一次出现的index, 如没有返回-1:index := strings.LastIndex("abcHelloll","ll")

13、将指定的子事替换成另外一-个子串: strings Replace("go go hll", "go". "go语言", n) n可以指定你希望替换几个,如果n=-1表示全部替换

14、按照指定的某个字符,为分割标识,将一个字符串拆分成字符串数组:

 //常用字符串
 package main
  
 import (
     "fmt"
   "strings" 
 )
 func main() {
   strArr := strings.Split("hello,hello,hello",",")
   for i := 0; i < len(strArr); i++ {
     fmt.Println(i,strArr[i])
   }
   fmt.Println("分割的结果为:",strArr)
 }

15、将字符串的字母进行大小写的转换:str := strings.ToLower("GOLANG") str1 := strings.ToUpper("golang")

16、将字符串左右两边的空格去掉 str := strings.TrimSpace(" hello,cat ")

17、将字符串左右两边指定的字符去掉: str := strings.Trim("?hello,cat?","?")

14. 字符串格式化

Go提供了几种打印格式,用来格式化一般的Go值,例如 下面的%v打印了一个point结构体的对象的值:

 p := point{1, 2}
 fmt.Printf("%v\n", p)

可以用 %v 来打印任意类型的变量

可以用 %+v 来打印更加详细的内容

 package main
 import "fmt"
 type point struct {
     x, y int
 }
 func main() {
     s := "hello"
     n := 123
     p := point{1, 2}
     fmt.Println(s, n) // hello 123
     fmt.Println(p)    // {1 2}
     fmt.Printf("s=%v\n", s)  // s=hello
     fmt.Printf("n=%v\n", n)  // n=123
     fmt.Printf("p=%v\n", p)  // p={1 2}
     fmt.Printf("p=%+v\n", p) // p={x:1 y:2}
     fmt.Printf("p=%#v\n", p) // p=main.point{x:1, y:2}
     f := 3.141592653
     fmt.Println(f)          // 3.141592653
     fmt.Printf("%.2f\n", f) // 3.14
 }

15. JSON处理

JSON (JavaScript Object Notation)是一种比XML更轻量级的数据交换格式,在易于人们阅读和编写的同时,也易于程序解析和生成。尽管JSON是JavaScript的一个子集,但JSON采用完全独立于编程语言的文本格式,且表现为键/值对集合的文本描述形式(类似一些编程语言中的字典结构),这使它成为较为理想的、跨平台、跨语言的数据交换语言。

使用json.Marshal()函数可以对一组数据进行JSON格式的编码。

输出字段名的首字母都是大写的,如果你想用小写的首字母怎么办呢?把结构体的字段名改成首字母小写的?JSON输出的时候必须注意,只有导出的字段(首字母是大写)才会被输出,如果修改字段名,那么就会发现什么都不会输出,所以必须通过struct tag定义来实现。

代码示例:

 package main
 import (
     "encoding/json"
     "fmt"
 )
 type userInfo struct {
     Name  string
     Age   int `json:"age"`  //把字段名称改为age
     Hobby []string
 }
 func main() {
     a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
     buf, err := json.Marshal(a)
     if err != nil {
         panic(err)
     }
     fmt.Println(buf)         // [123 34 78 97...]
     fmt.Println(string(buf)) // {"Name":"wang","age":18,"Hobby":["Golang","TypeScript"]} 序列化
     buf, err = json.MarshalIndent(a, "", "\t")
     if err != nil {
         panic(err)
     }
     fmt.Println(string(buf))
     var b userInfo
     err = json.Unmarshal(buf, &b) 
     if err != nil {
         panic(err)
     }
     fmt.Printf("%#v\n", b) // main.userInfo{Name:"wang", Age:18, Hobby:[]string{"Golang", "TypeScript"}}
 }

16. 时间处理

时间包括时间值和时区, 没有包含时区信息的时间是不完整的、有歧义的. 和外界传递或解析时间数据时, 应当像HTTP协议或unix-timestamp那样, 使用没有时区歧义的格式, 如果使用某些没有包含时区的非标准的时间表示格式(如yyyy-mm-dd HH:MM:SS), 是有隐患的, 因为解析时会使用场景的默认设置, 如系统时区, 数据库默认时区可能引发事故. 确保服务器系统、数据库、应用程序使用统一的时区, 如果因为一些历史原因, 应用程序各自保持着不同时区, 那么编程时要小心检查代码, 知道时间数据在使用不同时区的程序之间交换时的行为.

代码示例:

 package main
 import (
     "fmt"
     "time"
 )
 func main() {
     now := time.Now()
     fmt.Println(now) // 2022-03-27 18:04:59.433297 +0800 CST m=+0.000087933
     t := time.Date(2022, 3, 27, 1, 25, 36, 0, time.UTC)
     t2 := time.Date(2022, 3, 27, 2, 30, 36, 0, time.UTC)
     fmt.Println(t)                                                  // 2022-03-27 01:25:36 +0000 UTC
     fmt.Println(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute()) // 2022 March 27 1 25
     fmt.Println(t.Format("2006-01-02 15:04:05"))                    // 2022-03-27 01:25:36
     diff := t2.Sub(t)
     fmt.Println(diff)                           // 1h5m0s
     fmt.Println(diff.Minutes(), diff.Seconds()) // 65 3900
     t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")
     if err != nil {
         panic(err)
     }
     fmt.Println(t3 == t)    // true
     fmt.Println(now.Unix()) // 1648738080
 }

17. 数字解析

代码示例:

 package main
 import (
     "fmt"
     "strconv"
 )
 func main() {
     f, _ := strconv.ParseFloat("1.234", 64)
     fmt.Println(f) // 1.234
     n, _ := strconv.ParseInt("111", 10, 64) //字符串、进制、精度
     fmt.Println(n) // 111
     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
 }

18. 进程信息

代码示例:

 package main
 import (
     "fmt"
     "os"
     "os/exec"
 )
 func main() {
     // go run example/20-env/main.go a b c d
     fmt.Println(os.Args)           // [/var/folders/8p/n34xxfnx38dg8bv_x8l62t_m0000gn/T/go-build3406981276/b001/exe/main a b c d]
     fmt.Println(os.Getenv("PATH")) // /usr/local/go/bin...
     fmt.Println(os.Setenv("AA", "BB"))
     buf, err := exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput()
     if err != nil {
         panic(err)
     }
     fmt.Println(string(buf)) // 127.0.0.1       localhost
 }