golang-学习笔记-基本概念
在写过很多go代码之后,感觉自己并没有完全掌握go语言,还有很多知识盲区,所以有了这个go学习笔记系列,本系列是作者跟着电子书重新复习go语言相关内容的笔记
go程序基本结构和要素
包的概念、导入与可见性
go项目中每一个文件夹都是一个包包名为文件夹名。文件夹里面的.go
文件都属于一个包。
标准库:go内置的库
如果对一个包进行更改或重新编译,所有引用了这个包的客户端程序都必须全部重新编译。
每一段代码只会被编译一次
可见性规则:大写字幕开头的标识符可以被外部包使用
函数
|
|
注释
与C++一致//
或者/* */
类型
类型可以是基本类型,如:int、float、bool、string;结构化的(复合的),如:struct、array、slice、map、channel;只描述类型的行为的,如:interface。
结构化的类型没有真正的值,它使用 nil 作为默认值。
函数也可以是一个确定的类型。
type 关键字可以定义你自己的类型,详见下面的类型定义
强制类型转换
|
|
Go 程序的一般结构
Go 程序的执行(程序启动)顺序如下:
- 按顺序导入所有被 main 包引用的其它包,然后在每个包中执行如下流程:
- 如果该包又导入了其它的包,则从第一步开始递归执行,但是每个包只会被导入一次。
- 然后以相反的顺序在每个包中初始化常量和变量,如果该包含有 init 函数的话,则调用该函数。
- 在完成这一切之后,main 也执行同样的过程,最后调用 main 函数开始执行程序。
类型转换
在必要以及可行的情况下,一个类型的值可以被转换成另一种类型的值。所有的转换都必须显式说明,就像调用一个函数一样(类型在这里的作用可以看作是一种函数):
|
|
具有相同底层类型的变量之间可以相互转换:
常量
const 定义:const Pi = 3.14159
常量的值必须是能够在编译时就能够确定的。
常量命名通常使用大驼峰命名
常量还可以用作枚举:
|
|
iota 可以被用作枚举值:
|
|
根据go编译器源码,每个iota的值对应的就是当前iota对应const的行数,从0开始
变量
var identifier type
或者var identifier=value
定义变量,var
主要用于函数外包级别的全局变量,函数体内更推荐使用:=
让编译器自己去推导变量类型
多个var也可以用括号进行简写
同一类型的多个变量可以声明在同一行
变量的命名规则遵循骆驼命名法
当变量a和变量b之间类型相同时,才能进行如a = b的赋值
值类型和引用类型
引用类型并不直接表示值内存,而是一小块指向值内存地址的内存,值类型直接指向值内存
进行赋值时,值类型为深拷贝,引用类型为浅拷贝
值类型有:基本数据类型,数组,结构体
引用类型有:函数变量,指针,切片,map和channel
打印
使用 := 赋值操作符
只能被用在函数体内,而不可以用于全局变量的声明与赋值,不可以多次对于相同名称的变量使用初始化声明
多变量可以在同一行进行赋值,这被称为 并行 或 同时 赋值。
空白标识符 _
被用于抛弃值,如值 5 在:_, b = 5, 7
中被抛弃。_
实际上是一个只写变量,你不能得到它的值。
init 函数
变量除了可以在全局声明中初始化,也可以在 init 函数中初始化。这是一类非常特殊的函数,它不能够被人为调用,而是在每个包完成初始化后自动执行,并且执行优先级比 main 函数高。
每一个源文件都可以包含一个或多个 init 函数。初始化总是以单线程执行,并且按照包的依赖关系顺序执行。
一个可能的用途是在开始执行程序之前对数据进行检验或修复,以保证程序状态的正确性。
init 函数也经常被用在当一个程序开始之前调用后台执行的 goroutine,如下面这个例子当中的 backend():
|
|
基本类型和运算符
布尔类型 bool
|
|
可以使用相等 == 或者不等 != 运算符来进行比较并获得一个布尔型的值。
只有在两个的值的类型相同的情况下才可以使用。
如果值的类型是接口,它们也必须都实现了相同的接口。
布尔型的常量和变量也可以通过和逻辑运算符(非 !
、和 &&
、或 ||
)结合来产生另外一个布尔值
数字类型
整型 int 和浮点型 float
数字值转换:当进行类似 a32bitInt = int32(a32Float) 的转换时,小数点后的数字将被丢弃。
复数
Go 拥有以下复数类型:
- complex64 (32 位实数和虚数)
- complex128 (64 位实数和虚数)
复数使用 re+imI 来表示,其中 re 代表实数部分,im 代表虚数部分,I 代表根号负 1。
|
|
如果 re 和 im 的类型均为 float32,那么类型为 complex64 的复数 c 可以通过以下方式来获得:
|
|
函数 real(c)
和 imag(c)
可以分别获得相应的实数和虚数部分。
位运算
二元运算符:
- 按位与 &:
- 按位或 |:
- 按位异或 ^:
- 位清除 &^:a &^ b 的效果近似于 a & (^b)将b中为1的位置的位在a中置零
一元运算符:
- 按位取反 ^:
- 位左移 «:
- 位右移 »:
循环左移和循环右移:x = (x << n) | (x >> (类型位数 - n))
和x = (x >> n) | (x << (类型位数 - n))
位左移常见实现存储单位的用例,使用位左移与 iota 计数配合可优雅地实现存储单位的常量枚举:
|
|
逻辑运算符
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实现类型定义和类型别名
|
|
定义的类型不具有原类型的方法,但具有相同的底层结构,所以可以相互转换,但是必须进行显式转换
|
|
类型别名则表示两个类型具有相同的方法和相同的底层结构,甚至你在B定义的新方法也是A的新方法,a的实例对象可以直接调用
|
|
字符类型
'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]
字符串拼接符 +
可以通过以下方式来对代码中多行的字符串进行拼接:
|
|
由于编译器行尾自动补全分号的缘故,加号 + 必须放在第一行末尾
拼接的简写形式 += 也可以用于字符串:
在循环中使用加号 + 拼接字符串并不是最高效的做法,更好的办法是使用函数 strings.Join(),有没有更好地办法了?有!使用bytes.Buffer拼接更加给力
字符串字面量可以转换成各种类型的切片比如:
|
|
[]rune, []bytes, string可以直接使用强制类型转换,这样的转换是副本
字符串转换成[]rune的好处是通过下标访问时返回rune字符,这样可以直接用unicode包的字符
如果你要多次对字符串中的字符取rune格式且不是按顺序取(顺序取可以用for range),那么你最好是把string直接转换为[]rune再访问:
|
|
给字符串追加一个字符
使用string()强制转换字符为字符串然后直接+连接
指针
取地址符和反向引用符号与C++一致,但是不支持指针运算
一个指针变量可以指向任何一个值的内存地址 它指向那个值的内存地址,在 32 位机器上占用 4 个字节,在 64 位机器上占用 8 个字节,并且与它所指向的值的大小无关。
你不能得到一个文字或常量的地址
空指针的反向引用会使程序崩溃
获取的地址属于逻辑地址
- 指针不能随意转换,必须确保转换前后类型的大小和布局是相同的。
- 不要在指针和uintptr之间进行转换,因为uintptr是一个无类型指针,它不会进行类型检查,可能会造成灾难性后果。
- 不要在指针和基本类型之间进行转换,因为基本类型不是指针类型。
unsafe.Pointer可以实现无限制转换,有风险
|
|
查看构建程序的go版本
|
|