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

66 阅读9分钟
基础组成
  • 包声明 package main
  • 引入包 import "fmt"
  • 函数
  • 变量
  • 语句 & 表达式
  • 注释
标记

一行代表一个语句结束。不像 C 以分号 ; 结尾

第一个字符必须是字母或下划线而不能是数字

//单行注释;/**/多行注释

字符串连接

字符串连接可以通过 + 实现:

package main
import "fmt"
func main() {
    fmt.Println("hello" + "world")
}
格式化字符串

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

  • Sprintf 根据格式化参数生成格式化的字符串并返回该字符串。
  • Printf 根据格式化参数生成格式化的字符串并写入标准输出。
package main
import "fmt"
func main() {
   // %d 表示整型数字,%s 表示字符串
    var stockcode=123
    var enddate="2020-12-31"
    var url="Code=%d&endDate=%s"//不需要&a,&会输出
    var target_url=fmt.Sprintf(url,stockcode,enddate)
    fmt.Println(target_url)
}
//输出:Code=123&endDate=2020-12-31
package main
import "fmt"
func main() {
   // %d 表示整型数字,%s 表示字符串
    var stockcode=123
    var enddate="2020-12-31"
    var url="Code=%d&endDate=%s"
    fmt.Printf(url,stockcode,enddate)
}
//输出:Code=123&endDate=2020-12-31
类型
var b bool=true//==bool b=true;
appendboolbytecapclosecomplexcomplex64complex128uint16
copyfalsefloat32float64imagintint8int16uint32
int32int64iotalenmakenewnilpanicuint64
printprintlnrealrecoverstringtrueuintuint8uintptr
变量

声明变量 var identifier1, identifier2 type = value1, value2 如果没有赋类型,会根据value自行判断类型 如果没有初始化,则变量默认为零值 如果变量已经使用 var 声明过了,再使用 := 声明变量,就产生编译错误 intVal := 1 相等于:var intVal int =1

  • 数值类型(包括complex64/128)为 0
  • 布尔类型为 false
  • 字符串为 ""(空字符串)
  • 以下几种类型为 nil: var a *int var a []int var a map[string] int var a chan int var a func(string) int var a error // error 是接口

声明的值必须使用,但是全局变量允许声明但不使用 多变量可以在同一行进行赋值,如:a, b, c := 5, 7, "abc" 空白标识符 _ 也被用于抛弃值,如值 5 在:_, b = 5, 7 中被抛弃。

常量

const identifier [type] = value

const (
    Unknown = 0
    Female = 1
    Male = 2
)
iota

iota,特殊常量,可以认为是一个可以被编译器修改的常量。 iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。

iota 可以被用作枚举值: const ( a = iota b = iota c = iota ) 第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1;

const (
    a = iota   //0
    b          //1
    c          //2
    d = "ha"   //独立值,iota += 1
    e          //"ha"   iota += 1
    f = 100    //iota +=1
    g          //100  iota +=1
    h = iota   //7,恢复计数
    i          //8
)

<<n==*(2^n)

运算符
  • 同C语言

由上至下代表优先级由高到低:

优先级运算符
5* / % << >> & &^
4+ - | ^
3== != < <= > >=
2&&
1|
条件语句

if switch select 不支持 ?: 形式的条件判断

select

Go 语言 select 语句

select 是 Go 中的一个控制结构,类似于 switch 语句。 select 语句只能用于通道操作,每个 case 必须是一个通道操作,要么是发送要么是接收。 select 语句会监听所有指定的通道上的操作,一旦其中一个通道准备好就会执行相应的代码块。 如果多个通道都准备好,那么 select 语句会随机选择一个通道执行。如果所有通道都没有准备好,那么执行 default 块中的代码。

Go 编程语言中 select 语句的语法如下:

select {
case <- channel1:
// 执行的代码
case value := <- channel2:
// 执行的代码
case channel3 <- value:
// 执行的代码

    // 你可以定义任意数量的 case

  default:
// 所有通道都没有准备好,执行的代码
}

以下描述了 select 语句的语法:

  • 每个 case 都必须是一个通道
  • 所有 channel 表达式都会被求值
  • 所有被发送的表达式都会被求值
  • 如果任意某个通道可以进行,它就执行,其他被忽略。
  • 如果有多个 case 都可以运行,select 会随机公平地选出一个执行,其他不会执行。
    否则:
    1. 如果有 default 子句,则执行该语句。
    2. 如果没有 default 子句,select 将阻塞,直到某个通道可以运行;Go 不会重新对 channel 或值进行求值。
循环语句(For)

和 C 语言的 for 一样: for init; condition; post { }

和 C 的 while 一样: for condition { }

和 C 的 for(;;) 一样: for { }

  • init: 一般为赋值表达式,给控制变量赋初值;
  • condition: 关系表达式或逻辑表达式,循环控制条件;
  • post: 一般为赋值表达式,给控制变量增量或减量。
sum := 0
  for i := 0; i <= 10; i++ {
	 sum += i
  }

Go 语言 for 循环 | 菜鸟教程 (runoob.com) //没看完

函数

Go 语言函数定义格式如下:

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

Go语言中的数组是值类型,当将一个数组赋值给另一个数组时,会将整个数组内容复制一份。这可能导致在处理大型数组时出现性能问题。 为了避免这种问题,可以使用切片(slice),切片是对数组的引用,它更灵活、更高效。

切片是对底层数组的一个视图,它包含了指向底层数组的指针、切片的长度和容量信息。由于切片是对数组的引用,因此修改切片中的元素会影响原始数组,反之亦然。切片支持自动扩容,当切片的容量不够时,Go会自动为其分配更大的底层数组,并将原有的数据复制到新的数组中。

创建切片有多种方式,其中最常见的是通过make函数:

slice := make([]int, 5) // 创建一个初始长度为5的整数切片

通过make函数,我们可以方便地创建指定类型和长度的切片,初始值都是该类型的零值。

另外,我们还可以通过切片字面量创建切片:

slice := []int{1, 2, 3, 4, 5} // 创建一个包含5个整数的切片

  • slice := []int{1, 2, 3, 4, 5}:这是一个切片的创建方式。在初始化时没有指定长度,只有一个空的方括号[],表示创建一个切片。切片的长度是根据初始值的个数来确定的,所以这里的切片slice长度是5,包含5个整数元素。

  • intArray := [5]int{10, 20, 30, 40, 50}:这是一个数组的创建方式。在初始化时,使用了固定长度的方括号[5],表示创建一个长度为5的整数数组。数组的长度在创建时就确定,无法更改。

切片可以通过索引来访问和修改元素,例如:

slice[0] = 10 // 修改第一个元素为10
fmt.Println(slice[3]) // 访问第四个元素的值

使用append追加元素:

slice := []int{1, 2, 3} slice = append(slice, 4)

通过append函数,我们可以向切片中追加新的元素。如果切片的容量不足,会进行扩容,返回一个新的切片,因此需要将append的结果赋值给原切片

切片的切割操作:

slice := []int{1, 2, 3, 4, 5} subSlice := slice[1:4] // 取出第二个到第四个位置的元素,得到[2, 3, 4]

切片支持Python风格的切片操作,可以方便地截取出子切片。

==切片的优势:==

  • 动态长度:切片的长度可以根据需要动态增加或缩减,相比于数组的静态长度,切片在处理不确定长度的数据时更为方便。
  • 函数传参:切片在函数参数传递时,不会像数组一样复制整个数据,而是复制指向底层数组的指针、长度和容量信息,这样可以避免大量数据的复制,节省内存和提高性能。
  • 灵活的使用方式:切片支持切割、追加、复制和连接等操作,使得切片在各种场景下都有广泛的应用。

另外:

  1. 切片的长度和容量: 切片的长度表示当前包含的元素个数,而容量表示切片扩容前的底层数组可以容纳的元素个数。切片的容量是自动扩容的,具体扩容策略是在长度不断增加时,当容量不够时,Go会自动为其分配更大的底层数组,并将原有的数据复制到新的数组中。
  2. 切片的底层原理: 切片是对底层数组的一个引用,它包含了指向底层数组的指针、切片的长度和容量信息。因此,当多个切片引用同一个底层数组时,它们会共享相同的数据。
map

也叫哈希或者字典

当我们创建一个 map 时,需要有两个类型,第一个是 key 的类型,第二个是 value 的类型。我们可以从里面去存取键值对,也可以用 delete 来删除键值对。Go语言中的 map 是完全无序的,因此遍历的时候不会按照顺序来遍历,而是随机的。

下面是 map 的使用记录。

  • map的创建和初始化:

在Go语言中,可以使用内置的make函数来创建和初始化map

make函数接受两个参数:map的类型和初始容量(可选)。

例如:

// 创建一个键类型为string,值类型为int的空map 
m := make(map[string]int) 
// 创建一个键类型为string,值类型为string的map,并指定初始容量为10 
m := make(map[string]string, 10)
  • 存取键值对:

可以使用索引操作符[]来存取map中的键值对。例如:

m := make(map[string]int) 
m["apple"] = 1 // 存储键值对 
m["orange"] = 2 

fmt.Println(m["apple"])  
// 获取键值对中键为"apple"的值
fmt.Println(m["orange"]) 
// 获取键值对中键为"orange"的值
  • 删除键值对:

使用内置的delete函数可以删除map中的键值对。例如:

m := make(map[string]int) 
m["apple"] = 1 
m["orange"] = 2 
delete(m, "apple") // 删除键为"apple"的键值对
  • 遍历 map

map是无序的,因此遍历时无法保证顺序,用range来遍历map

m := map[string]int{
    "apple":  1,
    "orange": 2,
}

// 使用 range 关键字来迭代 Map,从而在每次循环迭代中获取键和对应的值。
for key, value := range m {
    // 每次迭代会获取 Map 中的一个键值对,此时打印键值对的值
    fmt.Println(key, value)
}

函数传指针才能改原来的值
func add2ptr(n *int) {
    *n += 2
}
错误处理

在Go语言中,错误处理是一种常见的编程习惯,并且Go语言提供了一种简洁且明确的错误处理机制。

不同于 Java 使用的异常。go 语言的处理方式,能够很清晰地知道哪个函数返回了错误,并且能用简单的 if else 来处理错误

来看下面这个 findUser 函数:

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")
}

这个函数findUser接受一个users切片和name字符串作为参数,并返回一个*user类型的指针和一个error类型的错误。函数首先遍历users切片,查找是否有与name匹配的用户。如果找到了,就返回指向该用户的指针,同时error为nil表示没有错误。如果没有找到,就返回nil指针和一个自定义的not found错误。

错误处理示例:

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)
}

这里展示了两种错误处理的情况:

第一种情况,调用findUser函数查找name为"wang"的用户。因为存在该用户,所以函数返回了指向该用户的指针,并且err为nil表示没有错误。所以输出结果为wang。 第二种情况,调用findUser函数查找name为"li"的用户。由于不存在该用户,所以函数返回了nil指针和一个自定义的错误not found。因此,在if err != nil条件下,我们输出了错误信息not found。


参考文章: Go语言上手 - 基础语法 | 青训营 - 掘金 (juejin.cn)