GO 基础语法

96 阅读12分钟

你好,Go!

package main
​
import (
    "fmt"
)
​
func main() {
    fmt.Println("Hello, Go!")
}

和C语言相似,go语言的基本组成有:

  • 包声明,编写源文件时,必须在非注释的第一行指明这个文件属于哪个包,如package main
  • 引入包,其实就是告诉Go 编译器这个程序需要使用的包,如import "fmt"其实就是引入了fmt包。
  • 函数,和c语言相同,即是一个可以实现某一个功能的函数体,每一个可执行程序中必须拥有一个main函数。 变量,Go 语言变量名由字母、数字、下划线组成,其中首个字符不能为数字。
  • 语句/表达式,在 Go 程序中,一行代表一个语句结束。每个语句不需要像 C 家族中的其它语言一样以分号 ; 结尾,因为这些工作都将由 Go 编译器自动完成。
  • 注释,和c语言中的注释方式相同,可以在任何地方使用以 // 开头的单行注释。以 /* 开头,并以 */ 结尾来进行多行注释,且不可以嵌套使用,多行注释一般用于包的文档描述或注释成块的代码片段。

定义变量

变量类型

常见的类型主要包括:

  • bool :布尔类型,只有true or false两个值
  • int :整型
  • float32/float64 :浮点型
  • string :字符串类型

Go 语言数据类型 | 菜鸟教程 (runoob.com)

变量声明

Go语言使用var关键字声明变量(还有短声明,下面代码展示),同时Go语言中的变量需要声明后才能使用,同一作用域内不支持重复声明。并且Go语言的变量声明后必须使用

变量的初始化

Go语言在声明变量的时候,会自动对变量对应的内存区域进行初始化操作。每个变量会被初始化成其类型的默认值,例如: 整型和浮点型变量的默认值为0。 字符串变量的默认值为空字符串""。 布尔型变量默认为false。 切片、函数、指针变量的默认为nil

var 变量名 类型 = 表达式
​
var a = "CSA" // 类型推导,不指定类型会自动判断var b, c int = 1, 2 // 一次初始化多个变量var d = truevar e float64 // 普通声明未赋值
​
f := float32(e) // 短声明(只可在函数体内使用)
​
g := a + "golang"
fmt.Println(a, b, c, d, e, f) // CSA 1 2 true 0 0
fmt.Println(g)                // CSAgolang

在函数体外时只能使用var声明变量

变量作用域

作用域指的是已声明的标识符所表示的常量、类型、函数或者包在源代码中的作用范围,在此我们主要看一下go中变量的作用域,根据变量定义位置的不同,可以分为一下三个类型:

  • 函数内定义的变量为局部变量,这种局部变量的作用域只在函数体内,函数的参数和返回值变量都属于局部变量。这种变量在存在于函数被调用时,销毁于函数调用结束后。
  • 函数外定义的变量为全局变量,全局变量只需要在一个源文件中定义,就可以在所有源文件中使用,甚至可以使用import引入外部包来使用。全局变量声明必须以 var 关键字开头,如果想要在外部包中使用全局变量的首字母必须大写(下面代码展示)。
  • 函数定义中的变量成为形式参数,定义函数时函数名后面括号中的变量叫做形式参数(简称形参)。形式参数只在函数调用时才会生效,函数调用结束后就会被销毁,在函数未被调用时,函数的形参并不占用实际的存储单元,也没有实际值。形式参数会作为函数的局部变量来使用
匿名变量

匿名变量的特点是一个下画线_,这本身就是一个特殊的标识符,被称为空白标识符。它可以像其他标识符那样用于变量的声明或赋值(任何类型都可以赋值给它),但任何赋给这个标识符的值都将被抛弃,因此这些值不能在后续的代码中使用,也不可以使用这个标识符作为变量对其它变量进行赋值或运算。

使用匿名变量时,只需要在变量声明的地方使用下画线替换即可。

package main
​
func GetNum() (int, int) {
    return 1, 2
}
​
func main() {
    a, _ := GetNum()
    _, b := GetNum()
    fmt.Println(a,b)
}

常量定义

相对于变量,常量是恒定不变的值,多用于定义程序运行期间不会改变的那些值。 常量的声明和变量声明非常类似,只是把var换成了const,常量在定义的时候必须赋值。

const s string = "constant"
const h = 500000000
const i = 3e20 / h
fmt.Println(s, h, i, math.Sin(h), math.Sin(i))

需要注意的是:标识符是用来命名变量、类型等程序实体。一个标识符实际上就是一个或是多个字母和数字、下划线_组成的序列,但是第一个字符必须是字母或下划线而不能是数字。

当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出; 标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的。

例如在包hello中的hello.go中有两个变量

package hello
​
var (
    hello = "hello"     // var可使用括号多段声明,const同理
    Hello = "Hello"
)

此时我在包main中使用hello包中的变量

package main
​
import (
    "../hello"    //hello包的路径,导入hello包(..根据你的路径而定)
    "fmt"
)
​
func main() {
    fmt.Println(hello.hello)   // 程序报错
    fmt.Println(hello.Hello)   // Hello
}

下面介绍的函数原理类似

练习

www.acwing.com/problem/con…

循环语句

Go语言中的循环只有for循环

for init statement; condition expression; post statement {
    // 这里是中间循环体
}

statement是单次表达式,循环开始时会执行一次这里

expression是条件表达式,即循环条件,只要满足循环条件就会执行中间循环体。

statement是末尾循环体,每次执行完一遍中间循环体之后会执行一次末尾循环体

执行末尾循环体后将再次进行条件判断,若条件还成立,则继续重复上述循环,当条件不成立时则跳出当下for循环

package main
​
import "fmt"func main() {
    i := 1
    for {                           // 相当于c中的while循环
        fmt.Println("loop")
        break // 跳出循环
    }
    
    // 打印7、8
    for j := 7; j < 9; j++ {
        fmt.Println(j)
    }
​
    for n := 0; n < 5; n++ {
        if n%2 == 0 {
            continue
            // 当n模2为0时不打印,进到下一次的循环
        }
        fmt.Println(n)
    }
    // 直到i>3
    for i <= 3 {
        fmt.Println(i)
        i = i + 1
    }
  // for 循环嵌套
  for i := 0; i < 5; i++ {
        for j := 0; j < 5; j++ {
            fmt.Printf("i = %d, j = %d\n", i, j)
        }
    }
}

练习

www.acwing.com/problem/con…

条件选择语句

if

if 条件表达式 {
    //当条件表达式结果为true时,执行此处代码   
}
​
if 条件表达式 {
    //当条件表达式结果为true时,执行此处代码  
} else {
    //当条件表达式结果为false时,执行此处代码  
}
​
package main
​
import "fmt"
​
func main() {
    // 条件表达式为false,打印出"7 is odd"
    if 7%2 == 0 {
        fmt.Println("7 is even")
    } else {
        fmt.Println("7 is odd")
    }
​
    // 条件表达式为ture,打印出"8 is divisible by 4"
    if 8%4 == 0 {
        fmt.Println("8 is divisible by 4")
    }
​
    // 短声明,效果等效于
    //num := 9
    //if num < 0{
    //  ...
    //}
    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")
    }
}

练习

www.acwing.com/problem/con…

switch

当分支过多的时候,使用if-else语句会降低代码的可阅读性,这个时候,我们就可以考虑使用switch语句

  • switch 语句用于基于不同条件执行不同动作,每一个 case 分支都是唯一的,从上至下逐一测试,直到匹配为止。
  • switch 语句在默认情况下 case 相当于自带 break 语句,匹配一种情况成功之后就不会执行其它的case,这一点和 c/c++ 不同
  • 如果我们希望在匹配一条 case 之后,继续执行后面的 case ,可以使用 fallthrough
package main
​
import (
    "fmt"
    "time"
)
​
func main() {
​
    a := 2
    switch a {
    case 1:
        fmt.Println("one")
    case 2:
        // 在此打印"two"并跳出
        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")
    }
}

练习

www.acwing.com/problem/con…

array

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

package mainimport "fmt"
​
func main() {
    // 声明了长度为5的数组,数组中的每一个元素都是int类型
    var a [5]int
    // 给数组a的第4位元素赋值为100
    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
    for i := 0; i < 2; i++ {
        for j := 0; j < 3; j++ {
            twoD[i][j] = i + j
        }
    }
    fmt.Println("2d: ", twoD)
}

注意,go语言中的函数是值类型,所以在函数中改变并不会改变自身的值。

练习

www.acwing.com/problem/con…

www.acwing.com/problem/con…

slice

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

var s []int

类似与声明一个数组,只不过不用填写它的长度

值得一提的是,切片必须先初始化才能使用!

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// 使用append在尾部添加元素
    s = append(s, "d")
    s = append(s, "e", "f")
    fmt.Println(s) // [a b c d e f]
​
    c := make([]string, len(s))
    // 将s复制给c
    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]
}

练习

www.acwing.com/problem/con…

map

在 Go 语言中,map 是散列表的引用,map 的类型是 map[K]V ,其中 K 和 V 是字典的键和值对应的数据类型。map 中所有的键都拥有相同的数据类型,同时所有的值也都拥有相同的数据类型,但是键的类型和值的类型不一定相同。键的类型 K ,必须是可以通过操作符 == 来进行比较的数据类型,所以 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
    fmt.Println(m["unknown"]) // 0
​
    r, ok := m["unknown"]
    fmt.Println(r, ok) // 0 falsedelete(m, "one")
​
    m2 := map[string]int{"one": 1, "two": 2}
    var m3 = map[string]int{"one": 1, "two": 2}
    fmt.Println(m2, m3)
}

range

用于遍历,注意以下两点

  1. range在map中遍历顺序是随机的,多次遍历的结果可能不同
  2. range在数组中是从下标0开始递增遍历的,多次遍历的结果是相同的
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", "c": "C"}
    for k, v := range m {
        fmt.Println(k, v)
    }
    for k := range m {
        fmt.Println("key", k)
    }
}

练习

www.acwing.com/problem/con… (使用range)

函数 func

函数是指一段可以直接被另一段程序或代码引用的程序或代码,一个较大的程序一般应分为若干个程序块,每一个模块用来实现一个特定的功能。

package main
​
import "fmt"func add(a int, b int) int {
    // 返回a+b的和
    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
    v, ok = exists(map[string]string{"a": "A"}, "b")
    fmt.Println(v, ok) //   false
}

和变量一样,要在其他包使用当前此包函数需要首字母大写

练习

www.acwing.com/problem/con…

www.acwing.com/problem/con…

point

指针也是一个变量,但它是一种特殊的变量,因为它存储的数据不仅仅是一个普通的值,如简单的整数或字符串,而是另一个变量的内存地址。

package main
​
import "fmt"func add2(n int) {
    n += 2
}
​
func add2pt(n *int) {
    *n += 2
}
​
func swap(a int, b int) {
    a, b = b, a
}
​
//函数参数为指针类型
func swapWithPt(a *int, b *int) {
    *a, *b = *b, *a
}
​
func main() {
    n := 5
    add2(n)
    fmt.Println(n) // 5
    add2pt(&n)
    fmt.Println(n) // 7
​
    a, b := 2, 3
    swap(a, b)
    fmt.Println(a, b)
    swapWithPt(&a, &b)
    fmt.Println(a, b)
}

库函数

我这里介绍几个常用的,其他的可自行了解。

fmt

fmt 库函数

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
}

time

time 库函数

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
}

strconv

strconv 库函数

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
}

Go语言标准库文档中文版 | Go语言中文网 | Golang中文社区 | Golang中国 (studygolang.com)