Contents

golang-学习笔记-基本概念

go官方文档 go语言参考

在写过很多go代码之后,感觉自己并没有完全掌握go语言,还有很多知识盲区,所以有了这个go学习笔记系列,本系列是作者跟着电子书重新复习go语言相关内容的笔记

go程序基本结构和要素

包的概念、导入与可见性

go项目中每一个文件夹都是一个包包名为文件夹名。文件夹里面的.go文件都属于一个包。

标准库:go内置的库

go语言中文网

如果对一个包进行更改或重新编译,所有引用了这个包的客户端程序都必须全部重新编译。

每一段代码只会被编译一次

可见性规则:大写字幕开头的标识符可以被外部包使用

函数

1
func functionName()

注释

与C++一致//或者/* */

类型

类型可以是基本类型,如:int、float、bool、string;结构化的(复合的),如:struct、array、slice、map、channel;只描述类型的行为的,如:interface。

结构化的类型没有真正的值,它使用 nil 作为默认值。

函数也可以是一个确定的类型。

type 关键字可以定义你自己的类型,详见下面的类型定义

强制类型转换

1
2
var a int=10
var b float64=float64(a)

Go 程序的一般结构

Go 程序的执行(程序启动)顺序如下:

  1. 按顺序导入所有被 main 包引用的其它包,然后在每个包中执行如下流程:
  2. 如果该包又导入了其它的包,则从第一步开始递归执行,但是每个包只会被导入一次。
  3. 然后以相反的顺序在每个包中初始化常量和变量,如果该包含有 init 函数的话,则调用该函数。
  4. 在完成这一切之后,main 也执行同样的过程,最后调用 main 函数开始执行程序。

类型转换

在必要以及可行的情况下,一个类型的值可以被转换成另一种类型的值。所有的转换都必须显式说明,就像调用一个函数一样(类型在这里的作用可以看作是一种函数):

1
valueOfTypeB = typeB(valueOfTypeA)

具有相同底层类型的变量之间可以相互转换:

常量

const 定义:const Pi = 3.14159

常量的值必须是能够在编译时就能够确定的。

常量命名通常使用大驼峰命名

常量还可以用作枚举:

1
2
3
4
5
const (
    Unknown = 0
    Female = 1
    Male = 2
)

iota 可以被用作枚举值:

1
2
3
4
5
const (
    a = iota
    b = iota
    c = iota
)

go专家编程

根据go编译器源码,每个iota的值对应的就是当前iota对应const的行数,从0开始

变量

var identifier type或者var identifier=value定义变量,var主要用于函数外包级别的全局变量,函数体内更推荐使用:=让编译器自己去推导变量类型

多个var也可以用括号进行简写

同一类型的多个变量可以声明在同一行

变量的命名规则遵循骆驼命名法

当变量a和变量b之间类型相同时,才能进行如a = b的赋值

值类型和引用类型

引用类型并不直接表示值内存,而是一小块指向值内存地址的内存,值类型直接指向值内存

进行赋值时,值类型为深拷贝,引用类型为浅拷贝

值类型有:基本数据类型,数组,结构体

引用类型有:函数变量,指针,切片,map和channel

打印

fmt包

使用 := 赋值操作符

只能被用在函数体内,而不可以用于全局变量的声明与赋值,不可以多次对于相同名称的变量使用初始化声明

多变量可以在同一行进行赋值,这被称为 并行 或 同时 赋值。

空白标识符 _ 被用于抛弃值,如值 5 在:_, b = 5, 7 中被抛弃。_ 实际上是一个只写变量,你不能得到它的值。

init 函数

变量除了可以在全局声明中初始化,也可以在 init 函数中初始化。这是一类非常特殊的函数,它不能够被人为调用,而是在每个包完成初始化后自动执行,并且执行优先级比 main 函数高。

每一个源文件都可以包含一个或多个 init 函数。初始化总是以单线程执行,并且按照包的依赖关系顺序执行。

一个可能的用途是在开始执行程序之前对数据进行检验或修复,以保证程序状态的正确性。

init 函数也经常被用在当一个程序开始之前调用后台执行的 goroutine,如下面这个例子当中的 backend():

1
2
3
4
func init() {
   // setup preparations
   go backend()
}

基本类型和运算符

布尔类型 bool

1
2
var b bool = true
b:=true

可以使用相等 == 或者不等 != 运算符来进行比较并获得一个布尔型的值。

只有在两个的值的类型相同的情况下才可以使用。

如果值的类型是接口,它们也必须都实现了相同的接口。

布尔型的常量和变量也可以通过和逻辑运算符(非 !、和 &&、或 ||)结合来产生另外一个布尔值

数字类型

整型 int 和浮点型 float

格式化说明符

数字值转换:当进行类似 a32bitInt = int32(a32Float) 的转换时,小数点后的数字将被丢弃。

复数

Go 拥有以下复数类型:

  • complex64 (32 位实数和虚数)
  • complex128 (64 位实数和虚数)

复数使用 re+imI 来表示,其中 re 代表实数部分,im 代表虚数部分,I 代表根号负 1。

1
2
3
var c1 complex64 = 5 + 10i
fmt.Printf("The value is: %v", c1)
// 输出: 5 + 10i

如果 re 和 im 的类型均为 float32,那么类型为 complex64 的复数 c 可以通过以下方式来获得:

1
c = complex(re, im)

函数 real(c)imag(c) 可以分别获得相应的实数和虚数部分。

位运算

二元运算符:

  • 按位与 &:
  • 按位或 |:
  • 按位异或 ^:
  • 位清除 &^:a &^ b 的效果近似于 a & (^b)将b中为1的位置的位在a中置零

一元运算符:

  • 按位取反 ^:
  • 位左移 «:
  • 位右移 »:

循环左移和循环右移:x = (x << n) | (x >> (类型位数 - n))x = (x >> n) | (x << (类型位数 - n))

位左移常见实现存储单位的用例,使用位左移与 iota 计数配合可优雅地实现存储单位的常量枚举:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
type ByteSize float64
const (
    _ = iota // 通过赋值给空白标识符来忽略值
    KB ByteSize = 1<<(10*iota)
    MB
    GB
    TB
    PB
    EB
    ZB
    YB
)

逻辑运算符

Go 中拥有以下逻辑运算符:==、!=、<、<=、>、>=。

算术运算符

整数和浮点数的二元运算符有 +、-、* 和 /

Go 在进行字符串拼接时允许使用对运算符 + 的重载,但 Go 本身不允许开发者进行自定义的运算符重载

/ 对于整数运算而言,结果依旧为整数

取余运算符只能作用于整数

整数除以 0 可能导致程序崩溃,将会导致运行时的恐慌状态

浮点数除以 0.0 会返回一个无穷尽的结果,使用 +Inf 表示。

随机数

函数 rand.Float32() 和 rand.Float64() 返回介于 [0.0, 1.0) 之间的伪随机数,其中包括 0.0 但不包括 1.0。函数 rand.Intn(n int) 返回介于 [0, n) 之间的伪随机数。

使用 Seed(value) 函数来提供伪随机数的生成种子,一般情况下都会使用当前时间的纳秒级数字。

运算符与优先级

优先级 运算符 7 ^ ! 6 * / % « » & &^ 5 + - | ^ 4 == != < <= >= > 3 <- 2 && 1 ||

类型别名

type实现类型定义和类型别名

1
2
3
4
5
//类型定义
type B A

//类型别名
type B=A

定义的类型不具有原类型的方法,但具有相同的底层结构,所以可以相互转换,但是必须进行显式转换

1
2
b:=B{}
var a A=A(b)

类型别名则表示两个类型具有相同的方法和相同的底层结构,甚至你在B定义的新方法也是A的新方法,a的实例对象可以直接调用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12

type A struct{}
type B = A

func (receiver B) b() {
	fmt.Println("b被调用")
}

func main() {
	i := A{}
	i.b()
}

字符类型

'x'为字符字面量,字符字面量可以对byte和rune进行赋值

byte 类型是 uint8 的类型定义,表示只占用 1 字节的传统 ASCII 编码的字符。可以用'括起来的ascii字符或者数字赋值

Go 默认 Unicode(UTF-8)编码,字符为 Unicode 代码点或者 runes,并在内存中使用 int 来表示。rune 是 是 int32 的类型定义。

字符串

字符串是 UTF-8 字符的一个序列(当字符为 ASCII 码时则占用 1 个字节,其它字符根据需要占用 2-4 个字节)。

字符串是一种值类型,且值不可变。

Go 支持以下 2 种形式的字面值:

  • 解释字符串:使用双引号"括起来,其中的相关的转义字符将被替换,这些转义字符包括:\n:换行符,\r:回车符,\t:tab 键,\u\U:Unicode 字符,\\:反斜杠自身
  • 非解释字符串:该类字符串使用反引号`括起来,支持换行,例如: `This is a raw string \n` 中的 `\n\` 会被原样输出。

和 C/C++不一样,Go 中的字符串是根据长度限定,而非特殊字符\0。

string 类型的零值为长度为零的字符串,即空字符串 ""

一般的比较运算符(==、!=、<、<=、>=、>)通过在内存中按字节比较来实现字符串的对比。你可以通过函数 len() 来获取字符串所占的字节长度,例如:len(str)。

字符串的内容(纯字节)可以通过标准索引法来获取,在中括号 [] 内写入索引,索引从 0 开始计数:

  • 字符串 str 的第 1 个字节:str[0]
  • 第 i 个字节:str[i - 1]
  • 最后 1 个字节:str[len(str)-1]

这种转换方案只对纯 ASCII 码的字符串有效,对于具有非ascii字符(如中文)的字符串使用for range遍历,每次遍历一个rune。

获取字符串中某个字节的地址的行为是非法的,例如:&str[i]

字符串拼接符 +

可以通过以下方式来对代码中多行的字符串进行拼接:

1
2
str := "Beginning of the string " +
    "second part of the string"

由于编译器行尾自动补全分号的缘故,加号 + 必须放在第一行末尾

拼接的简写形式 += 也可以用于字符串:

在循环中使用加号 + 拼接字符串并不是最高效的做法,更好的办法是使用函数 strings.Join(),有没有更好地办法了?有!使用bytes.Buffer拼接更加给力

CSDN 机器铃砍菜刀 string和[]byte转换

https://img-blog.csdnimg.cn/20201031173756620.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NscGhhaGFoYQ==,size_16,color_FFFFFF,t_70#pic_center

字符串字面量可以转换成各种类型的切片比如:

1
2
[]byte("你好")
[]rune("你好")

[]rune, []bytes, string可以直接使用强制类型转换,这样的转换是副本

字符串转换成[]rune的好处是通过下标访问时返回rune字符,这样可以直接用unicode包的字符

如果你要多次对字符串中的字符取rune格式且不是按顺序取(顺序取可以用for range),那么你最好是把string直接转换为[]rune再访问:

1
2
s:="hello world"
rs:=[]rune(s)

给字符串追加一个字符

主函数编程网

使用string()强制转换字符为字符串然后直接+连接

指针

取地址符和反向引用符号与C++一致,但是不支持指针运算

一个指针变量可以指向任何一个值的内存地址 它指向那个值的内存地址,在 32 位机器上占用 4 个字节,在 64 位机器上占用 8 个字节,并且与它所指向的值的大小无关。

你不能得到一个文字或常量的地址

空指针的反向引用会使程序崩溃

获取的地址属于逻辑地址

PHP中文网 PHPz

  1. 指针不能随意转换,必须确保转换前后类型的大小和布局是相同的。
  2. 不要在指针和uintptr之间进行转换,因为uintptr是一个无类型指针,它不会进行类型检查,可能会造成灾难性后果。
  3. 不要在指针和基本类型之间进行转换,因为基本类型不是指针类型。

unsafe.Pointer可以实现无限制转换,有风险

1
2
3
4
5
var p *int
var q *float64

// 转换为不同的指针类型
p = (*int)(unsafe.Pointer(q))

查看构建程序的go版本

1
go version xxx
 |