Go-learning

1.语言详解--类型

1.1 变量

变量(variable)表示没有固定值且可以改变的数(数学定义),也可以说是一段或者多段用来存储数据的内存。(计算机系统定义)

作为静态类型语言,go变量总是有固定的数据类型,类型决定了变量内存的长度和存储格式,可以改变变量值(类型转换或指针操作),无法改变类型。

定义

关键字var用于定义变量,类型放在变量名后。运行时内存分配操作会确保变量自动初始化为二进制零值(zero value),避免出现不可控行为。 如显式提供初始化值,可省略变量类型,有编译器推断。

var x int     //自动初始化为0
var y = false //自动推断为bool类型

可一次定义多个变量,包括用不同初始值定义不同类型

var x, y int  //相同类型的多个变量
var a, s = 100, "abc"   //不同类型的初始化值

按照编程习惯,建议以组的方式整理多行变量定义,即用大括号美观一点。

var {
  x, y int
  a, s =100, "abc"
}

简短模式(short variable declaration)

除var关键词外,还能使用如下模式

func main(){
  x := 100
  a, s :=1, "abc"
}

简短模式限制

  1. 定义变量,同时显式初始化
  2. 不能提供数据类型
  3. 只能用于函数内部

简短模式不一定总是重新定义变量,也可能是部分退化的赋值操作。 退化赋值的前提条件是 最少有一个新变量被定义且必须是同一作用域。

func main(){
  x := 100
  println(&x)

  x := 200                      //错误:no new variable on left side of :=
  println(&x, x)
}

输出: 1.png

func main(){
  x := 100
  println(&x,x)

  {x, y := 200, 300                      //不同作用域,全部hi新变量定义
  println(&x, x, y)
  }
}

在处理函数错误返回值时,退化赋值允许我们重复使用err变量

package main

import {
  "log"
  "os"
}

func main() {
  f, err := os.open("/dev/radom")
  ...

  buf := make([]byte, 1024)
  n, err := f.read(buf)               //err退化赋值,n新定义
}

多变量赋值

先计算出所有右值,再依次完成赋值操作。

func main(){
  x, y := 1,2
  x, y = y+3, x+2
  println(x, y)
}

输出: 1.png

5 3

tips:赋值操作,必须确保左右值类型相同

未使用错误

go语言的神奇之处,编译器讲未使用的局部变量当做错误,虽说知道它的初衷是好的,有助于培养良好的编程习惯。

1.2 命名

每个程序猿都会有自己变量命名习惯,但是还是建议标准化命名。

空标识符

和python类似,go也有一个名为 _ 的特殊成员(blank identifier)。通常作为忽略占位符使用,可做表达式,无法读取内容。

import "strconv"

func main() {
  x, _ := strconv.Atoi("12")    // 忽略atoi的err返回值
  println(x)
}

空标识符可用于临时规避编译器对未使用变量和导入包的错误检查。但他是预置成员,不能重新定义。

1.3 常量

常量就是指运行时不变的值,通常是一些字面量。使用常量就可用一个易于阅读的标识符号来代替“魔法数字”, 也使得在调整常量值时,无需修改所有引用代码。

  • 常量值必须是编译器可确定的字符,字符串,数字或者布尔型。可指定常量类型,或有编译器通过初始化值推断,和之前一样。
  • 可在函数代码块中定义常量,不曾使用的常量不会引发编译错误
  • 显式指定类型,确保常量左右值类型一致,可做显式转换。右值不能超出常量类型取值范围,否则会溢出。
  • 常量值也可以是某些编译器计算出结果的表达式,如 unsafesizeoflencap
  • 在常量组中如不能指定类型和初始化值,则与上一行非空常量右值(表达式文本)相同。

例子:

import "fmt"

func main() {
  const(
    x uint16 = 120            
    y                            //与上一行x类型,右值相同
    s = "abc"
    z                            //与s的烈性,右值相同
    )

    fmt.println("%T, %v\n", y, y)
    fmt.println("%T, %v\n", z, z)
}

输出:

uint16, 120<br> string, abc

枚举

go语言没有明确意义上的enum定义,但是可借助iota标识符实现一组自增常量值来实现枚举类型。

const(
  x = iota //0
  y        //1
  z        //2
  )
  • 自增作用范围是常量值,可在多个常量定义中使用多个iota,各自有单独技术,只需确保组中每行常量的列数量相同即可。
  • 如中断iota自增,必须显式恢复。且后续自增值按行序递增,而非c enum 那般按上一取值递增。
  • 自增默认数据类型为int, 可显式指定类型。
  • 在实际编码中,建议用自定义类型实现用途明确的枚举类型。

展开

不同于变量在运行期分配存储内存,常量通常会被编译器在预处理阶段直接展开,作为指令数据使用。

const y = 0x200

func main() {
  println(y)
}

数字常量不会分配存储空间,无需像变量那么通过内存寻址来取值,所以也不会获得地址。

1.4 基本类型

1.png

  • 支持八进制、十六进制以及科学计算书法。标准库math定义了各数字类型的取值范围。
  • 标准库strconv可在不同进制(字符串)间转换。
  • 使用浮点数,需注意小数位的有效精度

别名

在官方的规范中,专门提到有两个别名:

 byte alis for uint8
 tune alis for int32

别名类型无需转换,可直接赋值。

1.5 引用类型

  • 引用类型(reference type) 特指slice map channel 这三种预定义类型

  • 他们有浮渣的存储结构,除了分配内存外,还需要初始化相关属性。

  • 内置函数 new 计算类型⼤大⼩小,为其分配零值内存,返回指针。而 make会被编译器翻译 成具体的创建函数,由其分配内存和初始化成员结构,返回对象而非指针。

1.6 类型转换

除常量,别名类型以及未命名类型外,go强制要求使用显式类型转换,加上不支持操作符重载。

  • 同样不能将非bool类型结果当做true or false 使用。

语法歧义

如果转换的目标是指针,单向通道或没有返回值的函数类型,那么必须使用括号,以避免语法分解错误。

func main() {
  x := 100
  p := *int(&x)    //error : cannot convert &x (type *int) to type int32
                   // invalid indirect of int(&x) (type int)
}

正确的做法是用括号,让编译器 讲*int解析为指针类型

*Point(p) // 相当于 *(Point(p))
(*Point)(p)
<-chan int(c) // 相当于 <-(chan int(c))
(<-chan int)(c)

1.7 自定义类型

可将类型分为命名和未命名两⼤大类。命名类型包括 bool、int、string 等,而array、 slice、map 等和具体元素类型、长度等有关,属于未命名类型。

  • 具有相同声明的未命名类型被视为同一类型。
  • 具有相同基类型的指针。
  • 具有相同元素类型和⻓长度的 array。
  • 具有相同元素类型的 slice。
  • 具有相同键值类型的 map。
  • 具有相同元素类型和传送⽅方向的 channel。
  • 具有相同字段序列 (字段名、类型、标签、顺序) 的匿名 struct。
  • 签名相同 (参数和返回值,不包括参数名称) 的 function。
  • 方法集相同 (方法名、方法签名相同,和次序无关) 的 interface。

未命名类型转换规则

  • 所属类型相同
  • 基础类型相同,且其中一个是未命名类型
  • 数据类型相同,讲双向通道赋值给单向通道,且其中一个为未命名类型
  • 将默认值nil赋值给切片 字典 通道 指针 函数 接口
  • 对象实现了目标接口
x := 1234
var b bigint = bigint(x) // 必须显式转换,除⾮非是常量。
var b2 int64 = int64(b)
var s myslice = []int{1, 2, 3} // 未命名类型,隐式转换。
var s2 []int = s