Golang简介
Why Golang?
我发现我花了四年时间锤炼自己用 C 语言构建系统的能力,试图找到一个规范,可以更好的编写软件。结果发现只是对 Go 的模仿。缺乏语言层面的支持,只能是一个拙劣的模仿。 — 吴云洋(云风的 BLOG)
特点
- 并行
- 快速
- UTF-8
- 跨平台
配置运行环境
下载安装
官网下载地址 https://golang.org/dl/
下载文件并执行安装,Linux系统只需要解压即可!
tips 命令行
godoc -http=:8081
可以查看离线文档
配置
指定GOPATH为将要工作的目录,然后将bin添加到PATH中,输入命令go env
查看
GOARCH="amd64" ---架构
GOBIN="" ---编译好的文件
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin" --系统
GOPATH="/Users/gaozhimin/work" --工作目录
GORACE=""
GOROOT="/usr/local/go" --go的安装目录
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64"
CC="clang"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/qy/y1gwdk9x425f4nn60278ncp00000gn/T/go-build689949734=/tmp/go-build -gno-record-gcc-switches -fno-common"
CXX="clang++"
CGO_ENABLED="1"
编辑器
推荐使用
基本语法
HELLO_WORLD
// 一个文件夹内只能有一个包名
package main
import "fmt"
func main() {
// P在⬇️此处大写,注意
fmt.Println("Hello world!")
// 普通字符使用双引号
}
Golang的双引号和反引号都可用于表示一个常量字符串,不同在于:
- 双引号用来创建可解析的字符串字面量(支持转义,但不能用来引用多行)
- 反引号用来创建原生的字符串字面量,这些字符串可能由多行组成(不支持任何转义序列),原生的字符串字面量多用于书写多行消息、HTML以及正则表达式
变量声明
类型在变量的后面
例子
var a int
var b bool
// := 等价于 var 后赋值
c := 5
var (
e int
f bool
)
g := `Linkin
Park`
const x = 42
const (
y = 1
b = ""
)
基本类型
- int,Runes(注:Rune 是int 的别名)
- int8 ,int16 ,int32 ,int64
- byte ,uint8 ,uint16 ,uint32 ,uint64 (注:byte是uint8 的别名)
- float32 ,float64 (没有float 类型)
- bool
- string
- complex128,complex64
保留关键字
break case chan const continue default func defer go else goto fallthrough if for import interface map package range return select struct switch type var
对比PHP
控制结构
// if
if err := Chmod(0664); err != nil {
// 注意err的作用于
fmt.Printf(err)
return err
}
// for
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
//循环中的break和continue
for{
// 如果不加break,就是一个完美的死循环
break
}
for pos, char := range "abc" {
fmt.Printf("character '%c' starts at byte position %d\n", char, pos)
}
// switch
var i int
switch i {
case 0:
case 1:
f()
default:
g() //当i不等于0或1时调用
}
复合类型
array、slices 和 map
array
// 定长
var arr [10]int
// 长度不同类型不同
var a [10]int
var b [11]int
a = b //报错
// 传值不传址
slice
// 长度可以改变
sl := make([]int, 10)
// 传地址
s1 := []int{1, 2, 3}
s2 := s1
fmt.Println(s2)
s1[0] = 0
fmt.Println(s2)
// [1 2 3]
// [0 2 3]
s3 := append(s3, 4)
fmt.Println(s3)
map
≈ python.map
newMap := map[int]string{
0: "Sun", 1: "Mon",
2: "Tue", 3: "Wed",
4: "Thu", 5: "Fri",
6: "Sat",
}
// 查找键值是否存在
if v, ok := newMap[7]; ok {
fmt.Println(v)
} else {
fmt.Println("Key Not Found")
}
// Key Not Found
delete(newMap, 5)
fmt.Println(newMap)
函数
作用域
package main
var a = 6
func main() {
p()
q()
p()
}
func p() {
println(a)
}
func q() {
a := 5
// a = 5
println(a)
}
返回格式
// 函数参数 ip,类型为string
// 函数返回,conn和error
func connect(ip string) (conn *conn, err error){
...
...
return
}
func connect(ip string) (*conn, error){
conn := xxxx
err := xxx
return conn, err
}
// 接参数
con, err = connect("127.0.0.1")
if err != nil{
panic("error")
}
// 忽略参数
_, err = connect("127.0.0.1")
if err != nil{
panic("error")
}
Defer
func downloadFile(filepath string, url string) (err error) {
// Create the file
out, err := os.Create(filepath)
if err != nil {
return err
}
defer out.Close()
// Get the data
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
// Writer the body to file
_, err = io.Copy(out, resp.Body)
if err != nil {
return err
}
return
}
匿名函数
func main() {
a := func() {
fmt.Println("你好")
}
a()
}
回调
func main() {
callback("callback", print)
}
func print(s string) {
fmt.Println(s)
}
func callback(s string, f func(string)) {
f(s)
}
恐慌和恢复
func main() {
if throwsPanic(p) {
fmt.Println("Panic Happened")
}
}
//可能出现恐慌
func p() {
panic("failed")
}
func throwsPanic(f func()) (b bool) {
// defer在发生恐慌后能够执行,使用recover能捕捉到panic
defer func() {
if x := recover(); x != nil {
b = true
}
}()
f()
return
}
包
包是函数和数据的集合。用 package 关键字定义一个包。每个文件都必须以package开头!
包名
简洁明了,无下划线,无大小写混合,分隔符表示目录层级关系。
import的起点为
import (
"io"
"net/http"
"os"
"github.com/laogao/tools/lib"
)
net/http net/http 实现了 HTTP 请求、响应和 URL 的解析,并且提供了可扩展的 HTTP 服 务和基本的 HTTP 客户端。
flag flag 包实现了命令行解析。
os os 包提供了与平台无关的操作系统功能接口。其设计是 Unix 形式的。
io 这个包提供了原始的 I/O 操作界面。它主要的任务是对 os 包这样的原始的 I/O 进 行封装,增加一些其他相关,使其具有抽象功能用在公共的接口上。
fmt 包 fmt 实现了格式化的 I/O 函数,这与 C 的 printf 和 scanf 类似。格式化短语 派生于 C 。
首字母大小写
名称以大写字母起始的是可导出(exported)的。
func Add(a, b int) int {
return a + b
}
func hide(){
xxxx
}
工程目录
goWorkSpace // goWorkSpace为GOPATH目录
-- bin
-- myApp1 // 编译生成
-- myApp2 // 编译生成
-- myApp3 // 编译生成
-- pkg
-- src
-- common 1
-- common 2
-- common utils ...
-- myApp1 // project1
-- models
-- controllers
-- others
-- main.go
-- myApp2 // project2
-- models
-- controllers
-- others
-- main.go
-- myApp3 // project3
-- models
-- controllers
-- others
-- main.go
获取第三方Go包
以https://github.com/go-sql-driver/mysql为例
go get github.com/go-sql-driver/mysql
指针和结构体
指针
func main() {
var i int
i = 1
var p *int
p = &i
fmt.Printf("i=%d;p=%d;*p=%d\n", i, p, *p)
*p = 2
fmt.Printf("i=%d;p=%d;*p=%d\n", i, p, *p)
i = 3
fmt.Printf("i=%d;p=%d;*p=%d\n", i, p, *p)
}
结构体
type foo int
func main() {
var a foo
a = 1
fmt.Println(a)
t1 := T{"t1"}
fmt.Println("M1调用前:", t1.Name)
t1.M1()
fmt.Println("M1调用后:", t1.Name)
fmt.Println("M2调用前:", t1.Name)
t1.M2()
fmt.Println("M2调用后:", t1.Name)
}
//想要使用结构体,可以在其类型上添加函数,也可以直接做参数传入函数中
type T struct {
Name string
}
func (t T) M1() {
t.Name = "name1"
}
func (t *T) M2() {
t.Name = "name2"
}
传值与传指针
当我们传一个参数值到被调用函数里面时,实际上是传了这个值的一份copy,当在被调用函数中修改参数值的时候,调用函数中相应实参不会发生任何变化,因为数值变化只作用在copy上。
传指针比较轻量级 (8bytes),只是传内存地址,我们可以用指针传递体积大的结构体。如果用参数值传递的话, 在每次copy上面就会花费相对较多的系统开销(内存和时间)。所以当你要传递大的结构体的时候,用指针是一个明智的选择。
Go语言中string,slice,map这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传递指针。(注:若函数需改变slice的长度,则仍需要取地址传递指针)
要访问指针 p 指向的结构体中某个元素 x,不需要显式地使用 * 运算,可以直接 p.x
接口
package main
import (
"fmt"
)
type apple struct {
}
type pineapple struct {
}
func (a apple) eat() {
fmt.Println("I'm an apple,I can be eaten")
}
func (p *pineapple) eat() {
fmt.Println("I'm a pineapple,I can be eaten")
}
type eatable interface {
eat()
}
func main() {
apple := apple{}
pineapple := new(pineapple)
eat(apple)
eat(pineapple)
}
func eat(sth eatable) {
sth.eat()
switch sth.(type) {
case *pineapple:
fmt.Println("Got a pineapple")
case apple:
fmt.Println("Got a apple")
}
}
GC
设想你正在编写一个长期运行的后台服务,就让它是一个 web 应用服务或者某些更复杂的东西。通常来说,在整个运行周期都会需要分配内存。了解如何处理这些内存是必要的。
通常,每 2 分钟会执行一次垃圾收集。如果某个片段持续 5 分钟都没有被使用,回收器会将其释放。
因此,如果你认为内存使用会降低,那么 7 分钟之后再去确认吧。
更多关于GC优化,可以浏览Go’s march to low-latency GC
并发
goroutine
goroutine不是线程、协程、进程等等,传递了不准确的含义。goroutine 有简单的模型:它是与其他 goroutine 并行执行的, 有着相同地址空间的函数。它是轻量的,仅比分配栈空间多一点点。 而初始时栈是很小的,所以它们也是廉价的,并且随着需要在堆空间上分 配(和释放)。
使用go关键字让一个函数以goroutine方式执行
func echo(s string) {
fmt.Println(s)
}
func main() {
echo("hi")
go echo("hi")
// time.Sleep(5 * time.Second)
}
//猜测一下回打印出什么?为什么?
channel
如果不考虑go出去的函数,那么程序就会执行完毕并退出,goroutine也会随之停止,需要引入一个通讯机制,他就是channel。
channel ≈ pipe
channel必须使用make关键字创建
c1 := make(chan int)
// channel可以有长度
c2 := make(chan string, 100)
//channel的使用方法
c1 <- 1 //把int 1 放入 c1
<-c1 //尝试从c1取值
a := <-c1 //取值并赋变量a
结合
在goroutine中我们把一个值写入chan,然后在主进程中尝试从chan中读取数据,如果此时chan中没有数据,程序会被阻塞,只到有值取出。
var c chan int
func ready(w string, sec int) {
time.Sleep(time.Duration(sec) * time.Second)
fmt.Println(w, "is ready!")
c <- 1
}
func main() {
c = make(chan int)
go ready("Tea", 2)
go ready("Coffee", 1)
fmt.Println("I'm waiting, but not too long")
<-c
<-c
}
上面的例子是已经提前知道go出去的goroutine的个数才会这样写,当我们不知道个数的时候呢?
func main() {
c = make(chan int)
go ready("Tea", 2)
go ready("Coffee", 1)
i := 0
//跳转标记L
L:
for {
select {
case <-c:
i++
if i > 1 {
break L
}
}
}
}
超时机制
func main() {
c = make(chan string)
timeout = make(chan bool)
go ready("Tea", 6)
go ready("Coffee", 1)
go func() {
time.Sleep(time.Second * 3) // 5 秒超时
timeout <- true
}()
fmt.Println("I'm waiting, but not too long")
L:
for {
select {
case done := <-c:
fmt.Println(done, "is ready!")
case <-timeout:
fmt.Println("timeout")
break L
}
}
}
读取文件
方法一
func main() {
buf := make([]byte, 1024)
f, _ := os.Open("/etc/passwd")
defer f.Close()
for {
n, _ := f.Read(buf)
if n == 0 {
break
}
os.Stdout.Write(buf[:n])
}
}
方法二
func main() {
fi, err := os.Open("/etc/passwd")
if err != nil {
panic(err)
}
defer fi.Close()
fd, err := ioutil.ReadAll(fi)
fmt.Println(string(fd))
}