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

38 阅读8分钟

笔记:

声明变量

新手可看的声明变量方式,可能有不对的地方

var i int     // 基本类型 声明不赋值,默认0值(0)
i := 1        // 基本类型 声明并赋值
var i int = 1 // 基本类型 声明并赋值
var i = 1     // 基本类型 声明并赋值

var ANode Node             // 结构体 非指针,不赋值(内部的基本类型都为0值,引用类型和指针类型为nil)
var ANode Node = Node{}    // 结构体 非指针,赋0值(内部的基本类型都为0值,引用类型和指针类型为nil)
ANode := Node{}            // 结构体 非指针,赋0值(内部的基本类型都为0值,引用类型和指针类型为nil)
var ANodePoint = &Node{}   // 结构体 指针,分配内存地址,并且该指针指向该地址,其中为0值
var ANodePoint = new(Node) // 结构体 指针,分配指针地址,并且该指针指向该地址,其中为0值
ANodePoint := &Node{}      // 结构体 指针,分配指针地址,并且该指针指向该地址,其中为0值

var AMap = make(map[string]int, 5) // map 引用类型 分配内存,并提前分配长度为5的内存空间
AMap := make(map[string]int, 5)    // map 引用类型 分配内存,并提前分配长度为5的内存空间
AMap := map[string]int{}           // map 引用类型 赋了0值
var AMap = map[string]int{}        // map 引用类型 赋了0值

var channel chan int         // channel 声明不分配内存
var channel = make(chan int) // channel 分配内存
channel := make(chan int)    // channel 分配内存

var slice []string                      // 切片 声明不赋值,为nil,只能通过append追加,或者从其他切片或数组切出
var slice []string = make([]string, 21) // 切片 声明分配内存
var slice = make([]string, 21)          // 切片 声明分配内存
slice := make([]string, 21)             // 切片 声明分配内存
slice := []string{"123", "123"}         // 切片 声明分配内存

slice := new([]string)    // 切片指针 声明分配指针内存,但切片本身为nil
var slice = new([]string) // 切片指针 声明分配指针内存,但切片本身为nil

数据类型

  • 有符号整型:int、int8、int16、int32、int64
  • 无符号整型:uint、uint8、uint64、uint32、uint64、uintptr 实际开发中由于编译器和计算机硬件的不同,int 和 uint 所能表示的整数大小会在 32bit 或 64bit 之间变化。

  • float32 : 范围 约1.4e-45 到 约3.4e38

  • float64 :范围约4.9e-324 到 约1.8e308 通常应该优先使用 float64 类型,因为 float32 类型的累计计算误差很容易扩散,并且 float32 能精确表示的正整数并不是很大。
  • 布尔型:bool,默认 false
  • 字符/字符串:byte,string

用来表示 Unicode 字符的 rune 类型int32 类型是等价的,通常用于表示一个 Unicode 码点。这两个名称可以互换使用。同样,byteuint8 也是等价类型,byte 类型一般用于强调数值是一个原始的数据而不是一个小的整数。

切片

切片(Slice)与数组一样,也是可以容纳若干类型相同的元素的容器。

与数组不同的是,无法通过切片类型来确定其值的长度。

每个切片值都会将数组作为其底层数据结构。

我们也把这样的数组称为切片的底层数组

切片(slice)是对数组的一个连续片段的引用,所以切片是一个引用类型。

这个片段可以是整个数组,也可以是由起始和终止索引标识的一些项的子集,需要注意的是,终止索引标识的项不包括在切片内(左闭右开的区间)。

Go语言中切片的内部结构包含地址大小容量,切片一般用于快速地操作一块数据集合。

从连续内存区域生成切片是常见的操作,格式如下:

slice [开始位置 : 结束位置]

语法说明如下:

  • slice:表示目标切片对象;
  • 开始位置:对应目标切片对象的索引;
  • 结束位置:对应目标切片的结束索引。

从数组生成切片,代码如下:

四种方式

slice1 := []int{1, 2, 3}

var slice2 []int

var slice3 = make([]int, 3)

slice4 := make([]int, 3)

从数组或切片生成新的切片拥有如下特性:

  • 取出的元素数量为:结束位置 - 开始位置;
  • 取出元素不包含结束位置对应的索引,切片最后一个元素使用 slice[len(slice)] 获取;
  • 当缺省开始位置时,表示从连续区域开头到结束位置(a[:2])
  • 当缺省结束位置时,表示从开始位置到整个连续区域末尾(a[0:])
  • 两者同时缺省时,与切片本身等效(a[:])
  • 两者同时为 0 时,等效于空切片,一般用于切片复位(a[0:0])

函数

Go 语言支持普通函数、匿名函数和闭包,从设计上对函数进行了优化和改进,让函数使用起来更加方便。

Go 语言的函数属于“一等公民”(first-class),也就是说:

  • 函数本身可以作为值进行传递。
  • 支持匿名函数和闭包(closure)。
  • 函数可以满足接口。

函数定义:

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

defer

Go语言的 defer 语句会将其后面跟随的语句进行延迟处理

defer特性:

  1. 关键字 defer 用于注册延迟调用。
  1. 可以用来做资源清理。延迟函数执行时间(位置),方法return之后,返回参数到调用方法之前
  1. 多个defer语句,按先进后出的方式执行。
  1. defer语句中的变量,在defer声明时就决定了。
  2. 在方法结束(正常返回,异常结束)都会去调用defer声明的延迟函数,可以有效避免因异常导致的资源无法释放的问题
  3. defer 与recover配合可以实现异常捕获与处理逻辑

defer的用途:

  1. 关闭文件句柄
  1. 锁资源释放
  1. 数据库连接释放
package main

import "fmt"

func main() {
	var whatever = [5]int{1,2,3,4,5}

	for i := range whatever {
		defer fmt.Println(i)
	}
}

结构体

Go语言可以通过自定义的方式形成新的类型,结构体就是这些类型中的一种复合类型,结构体是由零个或多个任意类型的值聚合成的实体,每个值都可以称为结构体的成员。

结构体成员也可以称为“字段”,这些字段有以下特性:

  • 字段拥有自己的类型和值;
  • 字段名必须唯一;
  • 字段的类型也可以是结构体,甚至是字段所在结构体的类型。

使用关键字 type 可以将各种基本类型定义为自定义类型,基本类型包括整型、字符串、布尔等。结构体是一种复合的基本类型,通过 type 定义为自定义类型后,使结构体更便于使用。

结构体的定义格式如下:

type 类型名 struct {
    字段1 字段1类型
    字段2 字段2类型
    …
}
  • 类型名:标识自定义结构体的名称,在同一个包内不能重复。
  • struct{}:表示结构体类型,type 类型名 struct{}可以理解为将 struct{} 结构体定义为类型名的类型。
  • 字段1、字段2……:表示结构体字段名,结构体中的字段名必须唯一。
  • 字段1类型、字段2类型……:表示结构体各个字段的类型。

示例:

type Point struct {
    X int
    Y int
}

接收器

接收器的格式如下:

func (接收器变量 接收器类型) 方法名(参数列表) (返回参数) {
    函数体
}
  • 接收器变量:接收器中的参数变量名在命名时,官方建议使用接收器类型名的第一个小写字母,而不是 self、this 之类的命名。例如,Socket 类型的接收器变量应该命名为 s,Connector 类型的接收器变量应该命名为 c 等。
  • 接收器类型:接收器类型和参数类似,可以是指针类型和非指针类型。
  • 方法名、参数列表、返回参数:格式与函数定义一致。

接收器根据接收器的类型可以分为指针接收器非指针接收器,两种接收器在使用时会产生不同的效果,根据效果的不同,两种接收器会被用于不同性能和功能要求的代码中。

指针类型的接收器:

指针类型的接收器由一个结构体的指针组成,更接近于面向对象中的 this 或者 self。

由于指针的特性,调用方法时,修改接收器指针的任意成员变量,在方法结束后,修改都是有效的

示例:

使用结构体定义一个属性(Property),为属性添加 SetValue() 方法以封装设置属性的过程,通过属性的 Value() 方法可以重新获得属性的数值,使用属性时,通过 SetValue() 方法的调用,可以达成修改属性值的效果:

package main
import "fmt"
// 定义属性结构
type Property struct {
    value int  // 属性值
}
// 设置属性值
func (p *Property) SetValue(v int) {
    // 修改p的成员变量
    p.value = v
}
// 取属性值
func (p *Property) Value() int {
    return p.value
}
func main() {
    // 实例化属性
    p := new(Property)
    // 设置值
    p.SetValue(100)
    // 打印值
    fmt.Println(p.Value())
}