GO语言学习记录
GO
在Windows下测试注意换行是否为\r\n,否则最后一行是否有空行将会影响结果。
背景
Modula-2语言激发了包的概念。然后Oberon语言摒弃了模块接口文件和模块实现文件之间的区别。第二代的Oberon-2语言直接影响了包的导入和声明的语法,还有Oberon语言的面向对象特性所提供的方法的声明语法等。继承了C语言相似的表达式语法、控制流结构、基础数据类型、调用参数传值、指针等很多思想,还有C语言一直所看中的编译后机器码的运行效率以及和现有操作系统的无缝适配。iota语法是从APL语言借鉴,词法作用域与嵌套函数来自于Scheme语言。
新发明的defer语句;拥有自动垃圾回收、一个包系统、函数作为一等公民、词法作用域、系统调用接口、只读的UTF8字符串等;保证向后兼容。
但是没有隐式的数值转换,没有构造函数和析构函数,没有运算符重载,没有默认参数,也没有继承,没有泛型,没有异常,没有宏,没有函数修饰,更没有线程局部存储;相比传统的强类型语言更简洁且更多安全性;
软件的复杂性是乘法级相关的:通过增加一个部分的复杂性来修复问题通常将慢慢地增加其他部分的复杂性。通过增加功能、选项和配置是修复问题的最快的途径,但是这很容易让人忘记简洁的内涵。
简洁的设计需要在工作开始的时候舍弃不必要的想法,并且在软件的生命周期内严格区别好的改变和坏的改变。一个好的改变可以在不破坏原有完整概念的前提下保持自适应,而一个坏的改变则不能达到这个效果,它们仅仅是通过肤浅的和简单的妥协来破坏原有设计的一致性。只有通过简洁的设计,才能让一个系统保持稳定、安全和持续的进化。
配置环境
本地安装好Go语言环境,cmd输入go version验证。
感觉使用IDE太麻烦了,VSCode才是YYDS。
参考文章从零开始:VSCode 配置 Go 开发环境全指南(附必备工具清单)_vscode配置golang开发环境-CSDN博客
安装完VSCode扩展后,Ctrl+Shift+P输入go,选中Go:Install/Update Tools安装工具包。
编写测试代码后,执行以下命令:
//初始化模块,生成go.mod,记录我们依赖库以及版本号 |
基础知识
常识
- godoc识别的时源文件开头的注释,包注释很大就放到
doc.go文件。 - 名字的开头字母大小写决定了名字在包外的可见性:如果函数外部定义的包级名字是大写字母开头的,那么它是可以被外部的包访问,例如fmt包的Printf函数就是导出的,可以在fmt包外部访问。包本身的名字一般总是用小写字母。
- 驼峰式命名。
- 一个包的名字和包的导入路径的最后一个字段相同。
- 包的初始化首先是解决包级变量的依赖顺序,然后按照包级变量声明出现的顺序依次初始化。如果包中含有多个.go源文件,将按照发给编译器的顺序进行初始化(文件名排序)。
var a = b + c // a 第三个初始化, 为 3 |
%的符号和被取模数的符号总是一致的,/则依赖于操作数是否全为整数,比如5.0/4.0的结果是1.25,但是5/4的结果是1,因为整数除法会向着0方向截断余数。- Go语言源文件总是用UTF8编码,并且Go语言的文本字符串也以UTF8编码的方式处理,因此我们可以将Unicode码点也写到字符串面值中。
基础语法
声明
var:变量声明
var name type = expression
//短变量声明,定义变量并赋予适当类型,必须至少声明一个新的变量
name := expression
//new会创建一个T类型的匿名变量,然后返回变量地址,返回的指针类型为*T
p := new(int)
//元组赋值
x, y = y, x
//空标识符丢弃
for _, arg := range os.Args[1:]map查找、类型断言或通道接收出现在赋值语句的右边时,并不一定是产生两个结果,也可能只产生一个结果。
v = m[key] // map查找,失败返回零值
v = x.(T) // type断言,失败panic异常
v = <-ch // 管道接收,失败返回零值(阻塞不算是失败)nil可以赋值给任何指针或引用类型的变量。
*p++只是增加p指向的变量的值,并不改变p指针。变量类型:
%d 十进制整数
%x, %o, %08b 十六进制,八进制,二进制整数(8bit)。
%8.3f, %g, %e 浮点数: 3.141 3.141592653589793 3.141593e+00
%t 布尔:true或false
%c 字符(rune) (Unicode码点)
%s 字符串
%q 带双引号的字符串"abc"或带单引号的字符'c'
%v 变量的自然形式(natural format)
%T 变量的类型
%% 字面上的百分号标志(无操作数)
%[1]x [1]副词告诉Printf函数再次使用第一个操作数
%#[1]x #副词输出时生成0、0x或0X前缀const:常量声明
iota常量生成器,在第一个声明的常量所在的行,iota将会被置为0,然后在每一个有常量声明的行加一。
type Weekday int
const (
Sunday Weekday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)type:类型声明
type Celsius float64 // 摄氏温度
type Fahrenheit float64 // 华氏温度
//转换
func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) }func:函数声明
数据类型
运算符及其常见用法:
& 位运算 AND |
基础类型
整型
int/uint:8、16、32、64,有符号的采用2的补码形式表示,值域是$-2^{n-1}$到$2^{n-1}-1$,无符号的值域是$0$到$2^{n-1}$。无符号数只有在位运算或其它特殊的运算场景才会使用,就像bit集合、分析二进制文件格式或者是哈希和加密操作等。它们通常并不用于仅仅是表达非负数量的场合。
uintptr无符号类型。
rune等价于int32,表示一个Unicode码点。我们可以将一个符文序列表示为一个int32序列。这种编码方式叫UTF-32或UCS-4,每个Unicode码点都使用同样大小的32bit来表示。这种方式比较简单统一,但是它会浪费很多存储空间,因为大多数计算机可读的文本是ASCII字符,本来每个ASCII字符只需要8bit或1字节就能表示。而且即使是常用的字符也远少于65,536个,也就是说用16bit编码方式就能表达常用字符——>UTF-8(3.3.14内容)。
byte等价于uint8,byte(...) 将每个8位部分转换为 0~255 的整数。
浮点数到整数的转换将丢失任何小数部分,向数轴零方向截断。
任何大小的整数字面值都可以用八进制格式书写,例如0666;或用以0x或0X开头的十六进制格式书写,例如0xdeadbeef。
浮点数
float32:最大数值:math.MaxFloat32,有效bit位23个,当整数大于23bit能表达的范围时,float32的表示将出现误差。
float64:最大数值:math.MaxFloat64。
+Inf/-Inf:表示太大溢出的数字和除零的结果
NaN:表示无效的除法操作结果0/0或Sqrt(-1),math.IsNaN测试是否是NaN
复数
complex64和complex128:对应float32和float64的精度。
complex()构建,real()获取实部,imag()获取虚部。
一个浮点数或一个十进制整数跟着一个i,例如3.141592i或2i,将构成一个复数的虚部,复数的实部是0。
math/cmplx包提供复数处理函数,例如求复数的平方根函数cmplx.Sqrt(-1)。
布尔值&字符串
复制任何长度的字符串和字符串切片操作代价是低廉的,在这两种情况下都没有必要分配新的内存。
一个字符串是包含只读字节的数组,一旦创建,是不可变的。相比之下,一个字节
slice的元素则可以自由地修改。
s := "abc"
b := []byte(s) //分配了一个新的字节数组用于保存字符串数据的拷贝,然后引用这个底层的字节数组
s2 := string(b)
转义字符
\a 响铃 |
UTF-8
将Unicode码点编码为字节序列的变长编码,使用1到4个字节来表示每个Unicode码点,ASCII部分字符只使用1个字节,常用字符部分使用2或3个字节表示。每个符号编码后第一个字节的高端bit位用于表示编码总共有多少个字节。如果遇到一个错误的UTF8编码输入,将生成一个特别的Unicode字符\uFFFD,在印刷中这个符号通常是一个黑色六角或钻石形状,里面包含一个白色的问号”?”。
//如果第一个字节的高端bit为0,则表示对应7bit的ASCII字符,ASCII字符每个字符依然是一个字节,和传统的ASCII编码兼容。如果第一个字节的高端bit是110,则说明需要2个字节;后续的每个高端bit都以10开头。更大的Unicode码点也是采用类似的策略处理。 |
优点:
- 紧凑,完全兼容ASCII码,并且可以自动同步
- 是一个前缀编码,所以当从左向右解码时不会有任何歧义也并不需要向前查看(像GBK之类的编码,如果不知道起点位置则可能会出现歧义)。诸多字符串操作都不需要解码操作。我们可以不用解码直接测试一个字符串是否是另一个字符串的前缀。
- 搜索一个字符时只要搜索它的字节编码序列即可,不用担心前后的上下文会对搜索结果产生干扰。
- UTF8编码的顺序和Unicode码点的顺序一致,因此可以直接排序UTF8编码序列。
- 没有嵌入的NUL(0)字节,可以很好地兼容那些使用NUL作为字符串结尾的编程语言。
可以通过Unicode码点输入特殊的字符。有两种形式:\uhhhh对应16bit的码点值,\Uhhhhhhhh对应32bit的码点值,其中h是一个十六进制数字;一般很少需要使用32bit的形式。每一个对应码点的UTF8编码。unicode/utf8包解码器。
"世界" |
相关包
bytes:针对和字符串有着相同结构的[]byte类型,提供了Buffer类型用于字节slice的缓存。
- 字符串在 Go 中是不可变的,每次字符串拼接都会创建新字符串,效率较低。
bytes.Buffer是可变字节缓冲区,支持高效的追加操作,尤其适合多次拼接的场景。
func Contains(b, subslice []byte) bool
func Count(s, sep []byte) int
func Fields(s []byte) [][]byte
func HasPrefix(s, prefix []byte) bool
func Index(s, sep []byte) int
func Join(s [][]byte, sep []byte) []byte- 字符串在 Go 中是不可变的,每次字符串拼接都会创建新字符串,效率较低。
strings:字符串的查询、替换、比较、截断、拆分和合并等功能。
func Contains(s, substr string) bool
func Count(s, sep string) int
func Fields(s string) []string
func HasPrefix(s, prefix string) bool
func Index(s, sep string) int
func Join(a []string, sep string) stringstrconv:提供了布尔型、整型数、浮点数和对应字符串的相互转换,还提供了双引号转义相关的转换。
strconv.Itoa(“整数到ASCII”)
//FormatInt和FormatUint函数可以用不同的进制来格式化数字
x, err := strconv.Atoi("将一个字符串解析为整数")unicode:提供了IsDigit、IsLetter、IsUpper和IsLower等类似功能
复合类型
数组&slice
函数数组类型参数传递是低效的,并且对其任何修改都是发生在复制的数组上,并不能直接修改调用时原始的数组变量——->显式地传入一个数组指针/使用slice。
在Go语言中很少直接使用数组,和数组对应的类型是Slice,它是可以增长和收缩的动态序列,可以指定索引初始化。
[]byte("x")切片,[...]int{99: -1}数组
struct
数组
引用类型
slice/map/chan/指针/func
接口类型
控制流标号:break、continue或goto语句后面跟着的那种标号是函数级的作用域。
接口是一种抽象类型,这种类型可以让我们以同样的方式来处理不同的固有类型,不用关心它们的具体实现,而只需要关注它们提供的方法
库函数
flag库
var n = flag.Bool("n", false, "omit trailing newline") |
简单案例
echo
//声明该文件属于哪个包,main包定义了一个独立可执行的程序。 |
左闭右开,自增语句 i++ 是语句,而不像 C 系的其它语言那样是表达式, --i 也非法。不允许使用无用的局部变量。
for 循环的这三个部分每个都可以省略。
指针是可见的内存地址,&操作符可以返回一个变量的内存地址,并且*操作符可以获取指针指向的变量内容,但是在Go语言里没有指针运算,也就是不能像c语言里可以对指针进行加或减操作。
标准测试程序,time包性能评估
start := time.Now() |
dup
bufio 包处理输入和输出方便又高效。Scanner 类型是该包最有用的特性之一,它读取输入并将其拆成行或单词;通常是处理行形式的输入最简单的方法。
//标准输入传入,os.Stdin为输入流,标准错误流os.Stderr |
类型
//map 是一个由 make 函数创建的数据结构的引用 |
文件处理
//创建文件 |
GIF
image包:生成一系列的bit-mapped图,然后将这些图片编码为一个GIF动画。
gif.GIF是一个struct类型。
利萨如图形生成:
//复合声明:slice切片 |
获取URL
//strings.HasPrefix,检测字符串是否以指定的前缀开头,返回布尔值。 |
并发特性尤其好用。
fetchall的特别之处在于它会同时去获取所有的URL,所以这个程序的总执行时间不会超过执行时间最长的那一个任务。
goroutine是一种函数的并发执行方式,而channel是用来在goroutine之间进行参数传递。main函数本身也运行在一个goroutine中,而go function则表示创建一个新的goroutine。当一个goroutine尝试在一个channel上做send或者receive操作时,这个goroutine会阻塞在调用处,直到另一个goroutine从这个channel里接收或者写入值,这样两个goroutine才会继续执行channel操作之后的逻辑。
对每一个命令行参数,我们都用go这个关键字来创建一个goroutine。
io.Copy会把响应的Body内容拷贝到ioutil.Discard输出流中(译注:可以把这个变量看作一个垃圾桶,可以向里面写一些不需要的数据),因为我们需要这个方法返回的字节数,但是又不想要其内容。
每一个fetch函数在执行时都会往channel里发送一个值(ch <- expression),主函数负责接收这些值(<-ch)。这个程序中我们用main函数来完整地处理/接收所有fetch函数传回的字符串,可以避免因为有两个goroutine同时完成而使得其输出交错在一起的危险。
func main() { |
如果网站列表有一个完全无响应——http.Get 会一直阻塞,直到超时。
//加锁 |
Web服务
//处理器函数 |
服务器每一次接收请求处理时都会另起一个goroutine,这样服务器就可以同一时间处理多个请求。为了避免这个竞态问题,我们必须保证每次修改变量的最多只能有一个goroutine,因此修改共享变量时需要加锁。
问题
GIF
GIF动画 - Go语言圣经生成的gif是无法打开的
算法
每个数 i 的 1 的个数 = i/2 的 1 的个数 + i 的最低位是否为 1(i&1 结果为 1 则加 1,否则加 0
pc[i] = pc[i/2] + byte(i&1) |