Golang 基础知识

发布时间: 更新时间: 总字数:5219 阅读时间:11m 作者: 分享 复制网址

本文通过Golang helloworld示例开始,介绍Golang的标识符、关键字、操作符、常量、变量、指针等基础概念。

hello world

  • main.go
// package info
package main

import "fmt"

/*
  main func
*/
func main() {
  // print
	fmt.Println("hello world!")
}
  • 指定文件编译运行,必须包含 main 函数
$ go build main.go
$ ./main
hello world!

# 显示编译过程
go build -x ./main.go

# 指定编译包名称
go build -o helloworld main.go

# 编译并运行
go run ./main.go
go run -x ./main.go  # 指定临时目录编译
  • 编译目录,必须包含 main 函数,使用 go mod,并安装依赖,正常情况下编译包的名称为 go.mod 中 module 的名称
$ go mod init main
go: creating new go.mod: module main
go: to add module requirements and sums:
        go mod tidy
$ go mod tidy
$ go build
$ ls
go.mod  main    main.go
$ ./main
hello world!

基本元素

标识符

标识符:用于给包名、函数、变量、常量、类型、接口等命名,命名规则如下:

  • 由字母(unicode字符)、数字、下划线(_)组成,区分大小写
  • 以字母、下划线开头
  • 不能是Go语言的关键字、以及预定义的标识符
  • 方法名推荐使用驼峰式,尽量使用英文字符
  • 可以使用中文定义变量名,不推荐
  • 包名推荐全部小写,以 .go 结尾

关键字

关键字(共25个)

  • 声明:
    • import:导包
      • 一般顺序为:
        • Golang 内置包
        • 第三方包
        • 项目包
      • 点操作
        • import . "fmt"
        • Println("hello world")
      • 别名
        • import( f "fmt")
        • f.Println("hello world")
      • _ 操作
        • import (_ "github.com/go-sql-driver/mysql")
    • package
  • 实体声明和定义:
    • chan
    • 声明常量:const
    • 声明函数:func
    • 声明接口:interface
      • any 是 interface 的别名:type any = interface{} 参考
    • map
    • 声明结构体:struct
    • 声明类型:type
    • 声明变量:var
  • 流程控制:break case continue default defer if else for go goto fallthrough range return select switch

预定义的标识符

预定义的标识符:

  • 内置常量:true false nil iota(用于枚举类型)
  • 内置类型:
    • bool:
      • 布尔类型,表示真假;长度一个字节(8位)。零值为 false,字面量只能是 true、false
      • 逻辑运算:
        • 与 &&(aBool && bBool),两个均为 true 时,值为 true
        • 或 ||(aBool || bBool),一个值为 true 时,值为 true
        • 非 !(!aBool),aBool 的相反值
      • 关系运算:
        • 等于 == (true == true) 值为 true
        • 不等于 != (true != true) 值为 false
    • 有符号:int int8(0~2^8-1) int16 int32 int64
    • 无符号:uint uint8(-2^7~2^7-1) uint16 uint32 uint64
    • 1个Unicode码点:rune(将字符串转化成unicode 码点),使用单引号赋值。示例:var r rune = '中'参考
      • rune is an alias for int32 and is equivalent to int32 in all ways. It is used, by convention, to distinguish character values from integer values.
    • 1个字节(8位):byte,使用单引号赋值。示例:var a byte = 'A'
    • 存储指针的整型:uintptr,参考,说明
      • int uint uintptr 是32位操作系统为int32(4个字节),64位系统为int64(8个字节)
      • 有符号与无符号的区别:
        • 有符号最高位0表示整数、1表示负数
        • 无符号均为正数
#ifdef _64BIT
typedef	uint64		uintptr;
#else
typedef	uint32		uintptr;
#endif
  • 浮点数,零值为 0,浮点数存储是有精度损耗的(2.5==2.499999…)
    • float32
    • float64
    • 说明:
      • 字面量:
        • 十进制表示法
        • 科学技术法:MEN = M * 10 ^ N,示例:2.5e-1 == 0.25
      • 算术运算符:+,-,*,/,++,–
      • 关系运算:>,>=,<,<=, 由于存储精度存在损耗,相等运算可以认为差值<0.00005等方法>
      • 赋值运算:=,+=,-=,/=,*=
      • 类型转化可能出现溢出
  • 复数,如:默认为 complex128 i := 1+2i
    • complex64 complex128
  • 字符串:string,只能为 ascii 值
    • 说明:
      • var name string = “xianbin” # 可解释的字符串,包含特殊字符正常展示输出,可以转移输出,如 “\t”
      • var desc string = xianbin,中国人 # 原生字符串,可以包括特殊字符,如 \n,\f,\t,\b…,特殊字符原生输出
    • 算术运行符:
      • 连接:+
    • 关系运算:==,!=,>,>=,<,<=
    • 赋值运行:s := "abc"; s += "def"
    • 索引:0 ~ n-1(n 为字符串的长度,使用 len(s) 计算长度),仅对ascii有效,否则乱码。name = "xianbin"; fmt.Printf("%T, %c", name[0], name[0]) // "int8 x"
      • len/cap 是 Go 编译器的魔法,不是实际的函数调用,参考
    • 切片:s[start:end],字符串长度 start ~ end-1,不包括end,仅对ascii有效,否则乱码。name = "xianbin"; fmt.Printf("%T, %v", name[0:2], name[0:2]) // "string xi"
  • error
  • 进制:十进制、八进制(0644 = 6x8^2 + 4x8^1 + 4)、十六进制(0x开头,0-9A-F)
  • 内置函数:
    • append cap copy close complex delete make len real imag panic revocer
    • new:创建,返回值是指针类型,一般用于值类型中,不在引用类型中使用
  • 空白标识符:_

字面量

字面量:用于变量、常量的初始化

  • 基础数据类型,如:0,1.1,true
  • 构造自定义复合数据类型,如:type Name string
  • 复合数据类型值,用来构造array,slice,map,struct的值,如{“a”,“b”,“c”}

操作符

  • 算术运算符:+,-,*,/(5/2=2),%(5%2=1),++(仅支持放在变量后面:age++),–
  • 关系运算符:>,>=,<,<=,==,!=
  • 逻辑运算符:
    • 且:&&
    • 或:||
    • 非:!
  • 位运算(二进制的运算)符:
    • 与 & (5&2 = 2)
    • 或 | (5|2 = 7)
    • 异或 ^ (5^2 = 7)
    • 左移 « (2«1 = 4)
    • 右移 » (2»1 = 1)
    • 位清空(同为1的位清为0) &^ (5&^2 = 5)
  • 赋值运算符:=,+=,-=,*=,/=,%=,&=,|=,^=,«=,»=,&^=
  • 其他运算符:
    • 取地址:&
    • 取引用:*
    • 方法调用或属性值:.
    • 取反:-,a=-1 -> -a=1
    • 可变参数:…
    • chan 的读和写:<-

分隔符

  • 小括号:()
  • 中括号:[]
  • 大括号:{},用来声明作用域
  • 分号:;,每行后可选加;,一行多条语句使用;分隔
  • 逗号:,

变量

变量:对一块存储空间的名称定义,可通过该名称对存储空间的内容进行访问、修改等操作,格式如下:

  • var <变量名> <变量类型> = <变量值>
  • var <变量名> <变量类型>,var a string
  • var <变量名> = <变量值>
  • var <变量名1>, <变量名2>,.. <变量类型>
  • var <变量名1>, <变量名2> <变量类型> = <变量值1> <变量值2>
  • var <变量名1>, <变量名2> = <变量值1> <变量值2>
  • 短声明:<变量名> := <变量值>,变量类型自动推到。该声明方式只能在函数内部使用,如 b := true
  • 说明:
    • 函数内定义的变量必须使用,否则编译错误
    • 函数外定义的变量可以不使用
    • 变量只能声明一次
    • := 左侧至少有一个新变量
    • 类型后置的语言有:GoRust 等,Go 省略了 :,如 let x: i32;
    var (
        name string = "xianbin"
        age  int    = 18
    )

    // or

    var (
        name = "xianbin"
        age  = 18
    )

    // switch a, b
    var a, b = 1, 9
    a, b = b, a

常量

常量:不允许修改的量,变量名一般全大写,必须设置值

const NAME string = "xianbin"
const PI = 3.14
const NAME, AGE = "xianbin", 18
const (
  NAME string = "xianbin"
  age int = 18
)
const (
  NAME = "xianbin"
  age = 18
)

// 缺省赋值,同时省略类型和值,表示和前一个常量相同
const (
  C1 int = 1
  C2
  C3
)
fmt.Println(C1, C2, C3)  // 1 1 1

// 枚举赋值,同时省略类型和值,表示前一个常量增加1
const (
  I1 int = iota
  I2
  I3
)
const (
  I4 int = iota
  I5
  I6
)
fmt.Println(I1, I2, I3)  // 0 1 2
fmt.Println(I4, I5, I6)  // 0 1 2

作用域

作用域:用来定义标识符的使用范围,只能被声明一次且变量必须使用,Golang 使用 {} 定义作用域,大括号内的语句称为语句块

  • 子语句块可以使用父语句块的标识符
  • 父语句块不能使用子语句块的标识符
  • 子语句块中可以重复定义父语句块的标识符,并属于两个不同的作用域
  • 常见的语句块:
    • 包语句块;文件语句块;if、switch、case、for、select语句块

注释

  • 单号以 // 开头
  • 单号以 /* 开头,以 */ 结尾
  • 多行以 /* 开头,以 */ 结尾

类型转换

类型转换,包括 int/int*/uint/uint*/byte/rune 之间的相互转换,从大往小转化可能出现溢出截断,如 var A int = 0XFFFF 使用 uint8(A)会出现截断现象

var intA int = 10
var uintB uint = 2
fmt.Println(intA + int(uintB))
fmt.Println(uint(intA) + uintB)

指针

用于存储变量在内存中的存储位置,指针默认被初始化为 nil

package main

import "fmt"

func main() {
	var v1 = 2
	var v2 = 3
	v2 = v1
	fmt.Println(v1, v2) // 2 2
	v1 = 4
	fmt.Println(v1, v2) // 4 2

	// 指针:使用 & 取地址
	p1 := &v1
	var p2 *int = &v2
	fmt.Printf("%T %T\n", p1, p2) // *int *int

	// 取值:使用 * 取指针的值
	fmt.Println(*p1, *p2) // 4 2
	*p1 = 10
	*p2 = 15
	fmt.Println(v1, v2) // 10 15
}

输入&输出

fmt.Println("...")  // 输出一行
fmt.Print("...")  // 输出字符,不带换行

源码实现(参考):

// Print prints x to standard output, skipping nil fields.
// Print(fset, x) is the same as Fprint(os.Stdout, fset, x, NotNilFilter).
func Print(fset *token.FileSet, x any) error {
	return Fprint(os.Stdout, fset, x, NotNilFilter)
}

格式化输出

fmt.Printf("%s", "...")  // 格式化输出,不换行,使用 `\n` 换行
  • %s 字符串,若出现 %!s(xx=bool),表示使用 %s 输出的类型不匹配
  • %T 类型
  • %d 整数
  • %8d 占8位格式化整数,没有的空格填充
  • %08d 占8位格式化整数,没有的0填充
  • %-8d 占8位格式化整数,左对齐(没有-表示右对齐)
  • %f 浮点数
  • %6.2f 6位浮点数(包括小数点),保留2位小数
  • %b 二进制
  • %o 八进制
  • %x 十六进制
  • %t bool 类型
  • %c byte 类型
  • %p 指针地址
  • %q rune (码点)类型,原始值
  • %U rune (码点)类型,编码值
  • %v 调用每种类型自己的格式输出
  • %#v 打印结构体
  • %% 输出百分号
  • %w 格式化 error

输入

从命令行读入数据

package main

import "fmt"

func main() {
	var input string
	fmt.Print("please input something:")
	fmt.Scan(&input)
	fmt.Println("you input is", input)
}

注意:

  • 若输入的类型与期望的类型不匹配,将会赋予对应类型的零值

流程控制

条件 if-else

package main

import (
	"fmt"
	"math/rand"
	"time"
)

func main() {
	rand.Seed(time.Now().UnixNano())
	r := rand.Intn(10)
	if r >= 8 {
		fmt.Printf("%v >= 8", r)
	} else if r >= 5 {
		fmt.Printf("5 =< %v < 8", r)
	} else {
		fmt.Printf("%v < 5", r)
	}
}

选择 switch-case

// 值判断
switch var1 {
  case val1:
  case val2:
  default:
}

// 条件判断
switch {
  case conditionA:
  case conditionB:
  default:
}
  • 示例1
package main

import (
	"fmt"
	"math/rand"
	"time"
)

func main() {
	rand.Seed(time.Now().UnixNano())
	r := rand.Intn(10)

  // 值判断
	switch r {
	case 10:
		fmt.Printf("%v == 8\n", r)
	case 9, 8:
		fmt.Printf("%v == 8 or %v == 9\n", r, r)
	default:
		fmt.Printf("%v < 8\n", r)
	}

  // 条件判断
	switch {
	case r >= 8:
		fmt.Printf("%v == 8", r)
	case r >= 5:
		fmt.Printf("5 =< %v < 8", r)
	default:
		fmt.Printf("%v < 5", r)
	}
}
  • 示例2
package main

import "fmt"

func main() {
	isMatch := func(i int) bool {
		switch i {
		case 1:
		case 2:
			return true
		}
		return false
	}

	fmt.Println(isMatch(1))
	fmt.Println(isMatch(2))
}

// false, true
  • 示例三
package main

type AuthType int

const (
 A AuthType = iota
 B
 C
)

func (f AuthType) String() string {
 return [...]string{"A", "B", "C"}[f]
}

func main() {
 var f AuthType = A
 fmt.Println(f)
 switch f {
 case A:
  fmt.Println("A")
 case B:
  fmt.Println("B")
 default:
  fmt.Println("...")
 }
}

select

select 是 golang 实现的多路选择操作符,结构类似于 switch,一般用来监听和 channel 有关的 IO 操作,当 IO 操作发生时,触发相应的 case 动作

  • 多个 case 之间并非顺序的,同时满足时,随机选择一个执行
  • default 为可选的
select {
    case <-ch1:     // 检测有没有数据可读
        // 一旦成功读取到数据,则进行该case处理语句
    case value := <-ch1:     // 检测有没有数据可读
        // 一旦成功读取到数据,则进行该case处理语句
    case ch2 <- 1:  // 检测有没有数据可写
        // 一旦成功向ch2写入数据,则进行该case处理语句
    default:
        // 若以上都没有符合条件,则进入default处理流程
}
// Select case descriptor.
// Known to compiler.
// Changes here must also be made in src/cmd/compile/internal/walk/select.go's scasetype.
type scase struct {
	c    *hchan         // chan
	elem unsafe.Pointer // data element
}

以下代码造成死锁

package main

func main() {
  select {
  }
}

循环 for

for 初始化子语句; 条件子语句;后置子语句 {
    ...
}
for condition {

}
for {}
for range
  • 示例
package main

import "fmt"

func main() {
	sum := 0
	for i := 1; i <= 10; i++ {
		sum += i
	}
	fmt.Println(sum)

	sum = 0
	i := 1
	for i <= 10 {
		sum += i
		i++
	}
	fmt.Println(sum)

	sum = 0
	i = 0
	for {
		sum += i
		if i == 10 {
			break
		}
		i++
	}
	fmt.Println(sum)
}

for range

适用于字符串、数组、切片、映射、管道等 ascii 编码

package main

import "fmt"

func main() {
	message := "我是中文"
	for index, ch := range message {
		fmt.Printf("%d %T %q\n", index, ch, ch)
	}
}

输出:

0 int32 '我'
3 int32 '是'
6 int32 '中'
9 int32 '文'

说明:

  • rune 类型也是 int32
  • 字符使用单引号,字符串使用双引号

break & continue

  • break 跳出循环
  • continue 跳过本次循环
package main

import "fmt"

func main() {
	for i := 0; i < 10; i++ {
		if i == 4 {
			continue // 跳过 4
		} else if i == 6 {
			break // 跳出循环
		}
		fmt.Println(i)
	}
}
  • break + 标签用法
package main

import "fmt"

func main() {
END:
	for i := 0; i < 5; i++ {
		for j := 0; j < 5; j++ {
			if i*j == 9 {
				break END
			}
			fmt.Printf("%v * %v = %v\n", i, j, i*j)
		}
	}

	fmt.Println("end") // 总会执行
}

continue 同样有次用法,label 必须定义在循环的上面

goto

作用跳转到指定位置,可用于多层嵌套的for中

  • 标签定义:大写英文字符 + “:”

  • goto 标签:实现跳转到指定位置

  • goto 示例1

package main

import (
	"fmt"
	"math/rand"
	"time"
)

func main() {
	rand.Seed(time.Now().UnixNano())
	r := rand.Intn(10)
	if r > 5 {
		goto GT5
	} else {
		goto END
	}

GT5:
	fmt.Println("gt 5")
END:
}
  • goto 示例2
package main

import "fmt"

func main() {
    i := 1
    sum := 0

START:
    if i > 10 {
        goto END
    }
    sum += i
    i++
    goto START

END:
    fmt.Println(sum)
}

error

// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
    Error() string
}
wrapsErr := fmt.Errorf("... %w ...", ..., err, ...)
  • Unwrap errors 或 Is 判断
var (
    ErrInvalidParam = errors.New("invalid param")
)

if err != nil && err == ErrInvalidParam {
   //...
}

// 判断带参数的 error
if errors.Is(err, ErrInvalidParam) {
		//..
}

// `errors.Unwrap` 用来将错误中将原错误解析出来,`fmt.Errorf` 包装的 `error` 使用 `%w` 格式化
err := fmt.Errorf("origin error: %w", errxxx)
err = errors.Unwrap(err)
fmt.Printf("origin error: %+v\n", err)
  • panic
  • throw

内存分配

  • 栈(Stack) 由编译器进行管理,自动申请、分配、释放
    • 一般存放的对象不会太大,如函数参数、局部变量等放在栈上
  • 堆(Heap) 一般代码进行管理,显示申请、分配、释放
    • 一般存放较大的对象,分配相对慢,涉及到的指令动作也相对多,可采用
      • go build -gcflags '-m -l' x.go
      • go tool compile -S x.go 查看反编译命令

gc 垃圾回收

golang 垃圾回收有3种方式(参考):

  • gcTriggerHeap 当所分配的堆大小达到阈值时触发
  • gcTriggerTime 当距离上一个 GC 周期的时间超过一定时间时,将会触发
    • 时间周期以 runtime.forcegcperiod 变量为准,默认 2 分钟
  • gcTriggerCycle 未启动时,手动触发
// A gcTrigger is a predicate for starting a GC cycle. Specifically,
// it is an exit condition for the _GCoff phase.
type gcTrigger struct {
	kind gcTriggerKind
	now  int64  // gcTriggerTime: current time
	n    uint32 // gcTriggerCycle: cycle number to start
}

type gcTriggerKind int

const (
	// gcTriggerHeap indicates that a cycle should be started when
	// the heap size reaches the trigger heap size computed by the
	// controller.
	gcTriggerHeap gcTriggerKind = iota

	// gcTriggerTime indicates that a cycle should be started when
	// it's been more than forcegcperiod nanoseconds since the
	// previous GC cycle.
	gcTriggerTime

	// gcTriggerCycle indicates that a cycle should be started if
	// we have not yet started cycle number gcTrigger.n (relative
	// to work.cycles).
	gcTriggerCycle
)

入口函数

  • 编译命令:GOFLAGS="-ldflags=-compressdwarf=false" go build
    • Mac 的入口 src/runtime/rt0_darwin_amd64.s
    • Linux 的入口 src/runtime/rt0_linux_amd64.s
  • m0 主线程是 Go Runtime 创建的第一个系统线程
  • g0 每一个 m 只有且仅一个 g0,且每个 m 都只会绑定一个 g0
    • g0 的赋值是通过汇编赋值的
Home Archives Categories Tags Statistics
本文总阅读量 次 本站总访问量 次 本站总访客数