GO学习笔记
本文最后更新于:2024年7月11日 下午
跟着《GO圣经》学习GO啦!
Go的优势
简单易学
- Go语言的作者都有C的基因,Go自然而然也有了C的基因,但是Go的语法比C还简单, 并且几乎支持大多数你在其他语言见过的特性:封装、继承、多态、反射等
丰富的标准库
- Go目前已经内置了大量的库,特别是网络库非常强大
- 前面说了作者是C的作者,所以Go里面也可以直接包含c代码,利用现有的丰富的C库
跨平台编译和部署
- Go代码可直接编译成机器码,不依赖其他库,部署就是扔一个文件上去就完事了. 并且Go代码还可以做到跨平台编译(例如: window系统编译linux的应用)
内置强大的工具
- Go语言里面内置了很多工具链,最好的应该是gofmt工具,自动化格式化代码,能够让团队review变得如此的简单,代码格式一模一样,想不一样都很困难
性能优势: Go 极其地快。其性能与 C 或 C++相似。在我们的使用中,Go 一般比 Python 要快 30 倍左右
- 语言层面支持并发,这个就是Go最大的特色,天生的支持并发,可以充分的利用多核,很容易的使用并发
- 内置runtime,支持垃圾回收
Go环境搭建
Goland下载安装
Go语言安装
go version
查看是否安装完成
开始GO!——Part1
笔者跟着《Go语言圣经》一起学习的~~😘
对应第一章
编写第一个GO程序
1 |
|
在命令行里执行go run .\Hello.go http://gopl.io
,得到返回包,说明你的Go语言环境已经完全搭建成功啦!
命令行参数
os
包提供一些与操作系统交互的函数和变量,程序的命令行参数可以从os
包的Args变量获取。
os.Args
变量是一个字符串的切片,区间索引时,同样是左闭右开
os.Args
的第一个元素,os.Args[0]
, 是命令本身的名字;其它的元素则是程序启动时传给它的 参数。s[m:n]形式的切片表达式,产生从第m个元素到第n-1个元素的切片。如果省略切片表达式的m或n,会默认传入0或 len(s),因此切片可以简写成os.Args[1:]。
1 |
|
对于上面的程序,我们可以有如下理解:
GO里面注释使用
//
GO初始定义的变量若是没有赋值,则隐式地被赋为零值,数值类型是0,字符串类型是空字符串””
对于string类型的变量,
+
号可以直接连接字符串循环变量
i
没有定义类型,是因为。符号:=
是短变量声明的一部分, 这是定义一个或多个变量并根据它们的初始值为这些变量赋予适当类型的语句GO中
i++
是语句,不是表达式,所以不能用于赋值,**j=i++
非法,而且++
和--
都只 能放在变量名后面,因此--i
也非法**GO中只有
for
一种循环语句,但有多种形式第一种:
1
2
3
4
5
6
7
8
9for initialization; condition; post{
}
/*
for 循环三个部分不需要括号包围,左大括号必须与post同一行
initalization如果存在,必须是一条简单语句。即,短变量声明、自增语句、赋值语句或函数调用。
condition 是一个布尔表达式(boolean expression),其值在每次循环迭代开始时计算。如果为 true 则执行循环体语句。
post语句在循环体执行结束后执行,之后再次对conditon求值。condition 值为 false 时,循环结束
*/省略:
1
2
3
4
5// a traditional "while" loop
for condition {
}
//上面就是省略了初始化和post,变成了熟悉的while循环使用
range
:1
2
3
4
5
6
7for _, arg := range os.Args[1:] {
s += sep + arg
sep = " "
}
/*for 循环的另一种形式, 在某种数据类型的区间(range)上遍历,如字符串或切片。
大多数程序员都这么写for循环
*/每次循环迭代,range产生一对值:索引以及索引对应的元素值
但是我们这个例子不需要索引,而range的语法要求:要处理元素,必须处理索引
而且GO不允许使用无用的局部变量,会编译错误
解决方法是使用空标识符_
,空标识符可用于任何语法需要变量名但程序逻辑不需要的时候
声明一个变量有好几种方式,下面这些都等价:
1
2
3
4s := ""
var s string
var s = ""
var s string = ""一个简洁的写法,使用strings包的Join函数
1
2
3func main(){
fmt.Println(strings.Join(os.Args[1:],""))
}
练习
练习 1.1
修改 echo 程序,使其能够打印 os.Args[0] ,即被执行命令本身的名字。
1 |
|
练习 1.2
修改 echo 程序,使其打印每个参数的索引和值,每个一行。
1 |
|
练习 1.3
做实验测量潜在低效的版本和使用了 strings.Join 的版本的运行时间差异。(1.6 节讲解了部分 time 包,11.4节展示了如何写标准测试程序,以得到系统性的性能评测。)
1 |
|
使用Join
函数确实可以大幅减少时间复杂度
查找重复的行
对文件做拷贝、打印、搜索、排序、统计或类似事情的程序都有一个差不多的程序结构:一 个处理输入的循环,在每个元素上执行计算处理,在处理的同时或最后产生输出。
1 |
|
对上面的代码可以有如下理解:
map
是存储了键值对的集合,键可以是任意类型,只要其值可以用==
运算符比较;值可以是任意类型。上面得的代码中,键是字符串,值是整数。内置函数make创建空函数打印结果使用
range
函数,在counts
上迭代,每次得到键和值。需要注意的是,**map
的遍历顺序不确定,该顺序随机,每次运行都会变化**。关于
bufio
包,这个包使得处理输入和输出方便高效。Scanner
类型是该包最有用的特性之 一,它读取输入并将其拆成行或单词;通常是处理行形式的输入最简单的方法。 程序使用短变量声明创建bufio.Scanner
类型的变量input
。读取内容由input.Text()
获取,在读到一行时返回true
。fmt.Printf
函数对一些表达式产生格式化输出。该函 数的首个参数是个格式字符串,指定后续参数被如何格式化。和C语言类似。后缀f
指fomart
,ln
指line
1
2
3
4
5
6
7
8
9
10%d 十进制整数
%x, %o, %b 十六进制,八进制,二进制整数。
%f, %g, %e 浮点数: 3.141593 3.141592653589793 3.141593e+00
%t 布尔:true或false
%c 字符(rune) (Unicode码点)
%s 字符串
%q 带双引号的字符串"abc"或带单引号的字符'c'
%v 变量的自然形式(natural format)
%T 变量的类型
%% 字面上的百分号标志(无操作数)fmt.Printf
相当于C的printf
函数,可以格式化参数;而fmt.Println
函数相当于C++的cout
函数,直接输出字符串1
2
3
4
5
6name := "Alice"
age := 30
fmt.Printf("Name: %s, Age: %d\n", name, age)
name := "Bob"
age := 25
fmt.Println("Name:", name, "Age:", age)
还有另一个方法,一口气把全部输入数据读到内存中,分割为多行,然后处理它们。这个例子引入 了ReadFile
函数(来自于io/ioutil
包),读取指定文件的全部内容,strings.Split
函数把字符串分割成子串的切片。
1 |
|
ReadFile
函数返回一个字节切片,必须把它转换为string
,才能 用strings.Split
分割。实现上,bufio.Scanner
、ioutil.ReadFile
和ioutil.WriteFile
都使 用*os.File
的Read
和Write
方法,但是,大多数程序员很少需要直接调用那些低级函数。
获取URL
Go语言在net
这 个强大package
的帮助下提供了一系列的package
来做这件事情,使用这些包可以更简单地用 网络收发信息,还可以建立更底层的网络连接,编写服务器程序。
下面是一个使用示例:
1 |
|
在文件资源目录下使用终端,命令:go run hello.go http://remixxyh.github.io
练习
练习1.7
1 |
|
这里使用io.Copy
函数,是题目所给的将内容直接拷贝到标准输出中,将 io.Copy
的返回值存储在一个匿名变量 _
中,因为我们主要关心错误处理,而不是复制的字节数。
练习1.8
1 |
|
练习1.9
1 |
|
并发获取多个URL
Go语言最有意思并且最新奇的特性就是对并发编程的支持
1 |
|
在terminal
里执行:go run hello.go https://www.baidu.com http://gopl.io https://remixxyh.github.io
将会得到:
我们可以发现整个程序执行的时间由获取请求最长的那个决定
这是一个并发请求的程序,分析上面的代码:
ch := make(chan string)
是一个用于在Goroutine
之间传递数据的管道,每当fetch
函数执行完毕后,它会将一个带有消息的字符串发送到通道中Goroutine
是一种函数的并发执行方式,main
函数本身也运行在一个goroutine
中,而go function
则表示创建一个新的goroutine,并在这个新的goroutine中执行这个函数- 这个程序里会异步执行
Get
方法,将Body
拷贝到ioutil.Discard
(可以把这个变量看成一个垃圾桶)输出流中,io.Copy
函数会返回两个值(复制的字节数和可能的错误),从而实现不处理Body
,得到响应体的字节数 - 在使用通道发送或者接收数据时,如果此时通道已满或者主函数
main
没有做好接收的准备(注意:main
函数也是一个Goroutine
),那么会在发送数据的Goroutine
那里阻塞,这个例子中就是fetch
函数;如果通道为空或者没有数据可以使用,那么会在接收数据的Goroutine
那里阻塞。所以这个例子在main
使用一个for
循环接收数据,是为了防止主函数执行完了但是fetch
还没有执行完。
Web服务
Go语言的内置库使得写一个类似fetch的web服务器变得异常地简单。在本节中,我们会展示 一个微型服务器,这个服务器的功能是返回当前用户正在访问的URL。比如用户访问的是 http://localhost:8000/hello ,那么响应是URL.Path = “hello”。
1 |
|
在浏览器里访问:
多个控制器:
1 |
|
在这些代码的背后,服务器每一 次接收请求处理时都会另起一个goroutine
,这样服务器就可以同一时间处理多个请求。在并发情况下,两个请求同一时刻去更新count
,那么这个值可能并不会被正确地 增加;这个程序可能会引发一个严重的bug
:竞态条件。
为了避免这个问题,我们必须保证每次修改变量的最多只能有一个goroutine
,这也就是代码里的mu.Lock()
和 mu.Unlock()
调用将修改count
的所有行为包在中间的目的。
第一章的学习就到这里,入门了GO👍👍👍