语法

Written by with ♥ on in

变量

var 关键字可以声明一个特定类型的变量,并设置变量的名字和初始值。如果没有设置初始值,默认为对应类型的零值

语法:var 变量名 类型 = 值

// 声明 int 类型变量 v,自动初始化值为零值(int 类型的零值为 0)
// 下面三条语句等价
var v int 
var v int = 0
var v = 0

// 将变量 v 初始化为 1
v = 1

// 声明变量 x,并设置初始值为 1,编译器自动推导其类型为 int
x := 1

上面两种方式完全等价,:= 的写法更简洁,也是官方推荐的写法,编译器可以自动从右值推导出声明的变量是哪种类型,x 必须是没被声明过的变量。

多重赋值

基本的赋值方法都是类似的,但 Go 提供了动态语言中才有的多重赋值功能

i, j = j, i 交换变量如此简单,不需要引入中间变量。

匿名变量

函数的返回值中可能只有一个感兴趣的,别的值都不需要,是否还需要定义变量去接收这几个返回值?使用匿名变量!

func GetName() (firstName, lastName, nickName string) {
    return "May", "Chan", "Chibi"
}

_, _, nickName := GetName()

常量

常量值必须是编译期可确定的数字、字符串、布尔值。即常说的字符字面量(literal),根据字面量可以推测出常量的类型。

// 多常量初始化
const x,y int = 1, 2 
const Pi float64 = 3.1415926
// 类型推断
const zero = 0.0
// 常量组
const (
    size int64 = 1024
    // 类型推断
    eof = -1
    // 在常量组中,如果不提供类型和初始化值,那么视作与上⼀常量相同
    Eof
)

内置常量

true false iota nil

iota

iota 比较特殊,可以认为是编译器内置的一个寄存器,在每一个 const 关键字出现时被重置为 0,然后在下一个 const 出现时,每出现一次 iota,其所代表的数字就会增 1

const (
    // 0
    Sunday = iota
    // 1
    Monday
    // 2
    Tuesday
)

枚举

Go 语言中不存在枚举类型,可以通过自定义类型的方式构造。

// 定义类型 Color,传递 Color 的地发不能传递 int
type Color int

const (
    // 定义常量 Black = 0
    Black Color = iota
    Red
    Blue
)

func test(c Color) {}

func main() {
    c := Black
    test(c)
    x := 1
    // Error: cannot use x (type int) as type Color in function argument
    test(x)
    // 常量会被编译器自动转换
    test(1)

1.9 版本后可以使用类型别名定义枚举 type Color = int

赋值

值语义和引用语义

Go 语言中,值语义很彻底,传递整个值的拷贝,不像 C 语言中的数组,在作为函数参数传递时基于引用语义,传递第一个元素的指针地址。

在结构体中定义数组变量是基于值语义(为结构体赋值时,该数组会被完整的复制)。Go 中的数组和基本类型没有区别,在哪都是纯粹的值语义,会被完全复制!

使用指针表达引用语义。

// 数组 a
a := [3]int{1, 2, 3}
// b 为数组 a 的地址
b := &a
// 通过 a 的地址取出 a 第二个元素增加 1
b[1]++
// a 第二个元素改变,b 依旧指向 a
fmt.Println(a, *b) // [1 3 3] [1 3 3]

Go 中只有 4 个类型 看起来像引用语义(其实是值语义)

  • slice
  • map
  • channel
  • interface

在这 4 个类型的内部,都存储着指向实际值的指针。

map 本质上存储一个字典指针,使用值传递没有额外开销,实际的底层哈希表也不会复制。

interface 类型,内部就 2 个指针,没有额外开销。

条件语句

if

  • 可以省略条件表达式括号
  • 支持初始化语句,可以定义代码块局部变量
  • 代码块左大括号必须在条件表达式尾部
x := 0

// 局部变量 n 只能在循环中使用
if n := "abc"; x > 0 {
    println(n[0])
} else if x < 0 {
    println(n[1])
} else {
    println(n[2])
}

不支持三元操作符 a > b ? a : b

for

支持三种循环方式。

s := "abc"

// 常见 For 循环
// 计算出长度 n,避免多次调用 len 函数
for i, n := 0, len(s); i < n; i++ {
    println(s[i])
}

// 替代 while (n > 0) {}
n := len(s)
for n > 0 {
    println(s[n])
    n--
}

// 替代 while (true) {}
for {
    println(s)
}

range

类似迭代器操作,返回 (索引, 值)或(键, 值)

range 可以作用在 array,string,slice,mapchannel 五种数据结构上。

Range 会复制对象,应该使用引用类型 slice、map。

for 循环的 range 格式可以对 slice 或者 map 进行迭代循环。

遍历 slice 时,返回当前的下标和该下标对应元素的一个拷贝

s := []int{1, 2, 3, 4, 5}

for i, v := range s { // 复制 struct slice { pointer, len, cap }
    if i == 0 {
        s = s[:3] // 对 slice 修改,不会影响 range
        s[2] = 100 // 对底层数据的修改
    }

    println(i, v)
}

Out:

0 1
1 2
2 100
3 4
4 5

switch

分支表达式可以是任意类型,不限于常量。可以省略 break,默认自动终止。

继续下一分支,可以使用 fallthrough,但不再判断条件。

x := []int{1, 2, 3}
i := 2

switch i {
    case x[1]:
        println("1")
        fallthrough
    case 1, 3:
        println("2")
    default:
        println("3")
}

Out:
1
2

省略条件表达式,可以当 if…else if…else 使用。

switch {
    case x[1] > 0: // if
        println("1")
    case x[1] < 0: // else if
        println("2")
    default: // else
        println("3")
}

// 带初始化语句
switch i := x[2]; {
    case x[1] > 0: // if
        println("1")
    case x[1] < 0: // else if
        println("2")
    default: // else
        println("3")
}

goto、break、continue

⽀持在函数内 goto 跳转。标签名区分⼤⼩写,未使⽤标签引发错误。

func main() {
    var i int
    for {
        println(i)
        i++
        if i > 2 { goto BREAK }
}
BREAK:
    println("break")
EXIT: // Error: label EXIT defined and not used
}

配合标签,breakcontinue 可在多级嵌套循环中跳出。

func main() {
L1:
    for x := 0; x < 3; x++ {
L2:
        for y := 0; y < 5; y++ {
            if y > 2 { continue L2 }
            if x > 1 { break L1 }

            print(x, ":", y, " ")
        }
        
        println()
    }
}

输出
0:0 0:1 0:2
1:0 1:1 1:2

break 可⽤于 for、switch、select,⽽ continue 仅能⽤于 for 循环。

位运算

https://homerl.github.io/2016/03/29/golang-bitwise-operators/

&      位运算 AND // 相同时为 1 保留
|      位运算 OR // 有一个为 1 保留
^      位运算 XOR // 一元运算时取反、二元运算时保留所有的 1
&^     位清空 (AND NOT) // 
<<     左移 // 整体向左移动 * 2
>>     右移 // 整体向右移动 / 2

x &^ y 如果 y 上的 bit 是 0 则取 x 对应位置的值,如果是 1 则取 0。

即 如果 y 是 0 则 x 不变, 如果 y 是 1 则清空为 0