GO 语言快速学习 | PHP转GO笔记
流行度排行
https://github.com/speedwheel/awesome-go-web-frameworks/blob/master/README.md#popularity
beego
https://beego.me/products
echo
https://echo.labstack.com/
gingo社区文档
https://learnku.com/docs/gin-gonic/2019
看云gin文档
https://www.kancloud.cn/shuangdeyu/gin_book/949418
gin快速使用
https://www.jianshu.com/p/98965b3ff638
gin-admin-vue
https://www.gin-vue-admin.com/docs/gorm
官网
https://golang.org/dl/
中文官网
https://go-zh.org/pkg/
go常用库
https://github.com/jobbole/awesome-go-cn
查看版本号
go version
查看env
go env
==拉不下来包==
export GOPATH=/usr/local/go/binexport GOPROXY=https://goproxy.ioexport PATH=$PATH:$GOPATHexport GO111MODULE=on
开启module
go env -w GO111MODULE=on
设置代理
微服务框架go env -w GOPROXY=https://goproxy.io,direct
go-kit
go micro
go-zero
常用网址go基础知识
go基础知识v1
go基础知识v2---micro
菜鸟GO
文档和技术论坛
gomod 详细使用
go官网库
==go官方pkg中文==
github对pgk的使用例子
go mod使用基本数据类型字符串操作本地gorm struct生成本地struct生成https://github.com/xxjwxc/gormt.git
https://github.com/hantmac/fuckdb.git
1 使用docker-composer方式启动2 遇到docker容器不能链接本地mysql的时候 在本地mysqlGRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'root' WITH GRANT OPTION;FLUSH PRIVILEGES
struct 生成go get -u github.com/gohouse/converter
err :=converter.NewTable2Struct(). SavePath("./model.go"). Dsn("root:root@tcp(127.0.0.1:3306)/foroo_beta_shopify?charset=utf8"). TagKey("db"). EnableJsonTag(true). Table("fr_store"). Run()fmt.Println(err)return
基础变量或者方法使用小写相当于protect 首字母大写 相当于public
使用+来拼接字符串
变量声明 var age int;
常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。
可以将 var f string="Runoob" 简写为 f :="Runoob":
函数外的每个语句都必须以关键字开始(var、const、func等)
:=不能使用在函数外。
多用于占位,表示忽略值。import _ "./hello" 用占位只会执行导入包内的init,其他方法都不能调用
iota是go语言的常量计数器,只能在常量的表达式中使用
strconv.Itoa(97) 将一个数字转成字符串类型 string(97)go自带的会把数字转换成对应的ascii码
const ( n1=iota //0 n2 //1 n3 //2 n4 //3 )
map、slice、chan、指针、interface默认以引用的方式传递。
互斥锁 var lock sync.Mutex
var x int64var wg sync.WaitGroupvar lock sync.Mutexfunc add() { for i :=0; i < 5000; i++ { lock.Lock() // 加锁 x=x + 1 lock.Unlock() // 解锁 } wg.Done()}func main() { wg.Add(2) go add() go add() wg.Wait() fmt.Println(x)}
读写互斥锁 需要注意的是读写锁非常适合读多写少的场景,如果读和写的操作差别不大,读写锁的优势就发挥不出来。
var ( x int64 wg sync.WaitGroup lock sync.Mutex rwlock sync.RWMutex)func write() { // lock.Lock() // 加互斥锁 rwlock.Lock() // 加写锁 x=x + 1 time.Sleep(10 * time.Millisecond) // 假设读操作耗时10毫秒 rwlock.Unlock() // 解写锁 // lock.Unlock() // 解互斥锁 wg.Done()}func read() { // lock.Lock() // 加互斥锁 rwlock.RLock() // 加读锁 time.Sleep(time.Millisecond) // 假设读操作耗时1毫秒 rwlock.RUnlock() // 解读锁 // lock.Unlock() // 解互斥锁 wg.Done()}func main() { start :=time.Now() for i :=0; i < 10; i++ { wg.Add(1) go write() } for i :=0; i < 1000; i++ { wg.Add(1) go read() } wg.Wait() end :=time.Now() fmt.Println(end.Sub(start))}
sync.Once其实内部包含一个互斥锁和一个布尔值,互斥锁保证布尔值和数据的安全,而布尔值用来记录初始化是否完成。这样设计就能保证初始化操作的时候是并发安全的并且初始化操作也不会被执行多次。
var icons map[string]image.Imagevar loadIconsOnce sync.Oncefunc loadIcons() { icons=map[string]image.Image{ "left": loadIcon("left.png"), "up": loadIcon("up.png"), "right": loadIcon("right.png"), "down": loadIcon("down.png"), }}// Icon 是并发安全的func Icon(name string) image.Image { loadIconsOnce.Do(loadIcons) return icons[name]}
==goroutine高并发下 操作map 要用 sync.Map 来操作==
//sync.Map内置了诸如Store、Load、LoadOrStore、Delete、Range等操作方法var m=sync.Map{}func main() { wg :=sync.WaitGroup{} for i :=0; i < 20; i++ { wg.Add(1) go func(n int) { key :=strconv.Itoa(n) m.Store(key, n) value, _ :=m.Load(key) fmt.Printf("k=:%v,v:=%v\n", key, value) wg.Done() }(i) } wg.Wait()}
==代码中的加锁操作因为涉及内核态的上下文切换会比较耗时、代价比较高。针对基本数据类型我们还可以使用原子操作来保证并发安全,因为原子操作是Go语言提供的方法它在用户态就可以完成,因此性能比加锁操作更好。Go语言中原子操作由内置的标准库sync/atomic提供。==
// 原子操作版加函数func atomicAdd() { atomic.AddInt64(&x, 1) wg.Done()}
make只用于slice、map以及channel的初始化,返回的还是这三个引用类型本身
变量之间使用引用地址直接可以改变
package mainfunc swap(a, b *int) { var temp int temp=*a *a=*b *b=temp}func main() { var a, b=1, 2 swap(&a, &b) fmt.Println(a) fmt.Println(b)}//指针小案例var a intfmt.Println(&a)var p *intp=&a*p=20fmt.Println(a)
在参数赋值时可以不用用一个一个的赋值,可以直接传递一个数组或者切片,特别注意的是在参数后加上“…”即可
package mainimport ( "fmt")func test(s string, n ...int) string { var x int for _, i :=range n { x +=i } return fmt.Sprintf(s, x)}func main() { s :=[]int{1, 2, 3} res :=test("sum: %d", s...) // slice... 展开slice println(res)}
"_"标识符,用来忽略函数的某个返回值
没有参数的 return 语句返回各个返回变量的当前值。这种用法被称作“裸”返回。
func add(a, b int) (c int) { c=a + b return}func calc(a, b int) (sum int, avg int) { sum=a + b avg=(a + b) / 2 return}func main() { var a, b int=1, 2 c :=add(a, b) sum, avg :=calc(a, b) fmt.Println(a, b, c, sum, avg)}
命名返回参数允许 defer 延迟调用通过闭包读取和修改。
defer 是先进后出
package mainfunc add(x, y int) (z int) { defer func() { z +=100 }() z=x + y return}func main() { println(add(1, 2)) }
定义一维数组
var a [5]int=[5]int{1,2,5,6,7}var a=[...]int{1,2,3,5,6} d :=[...]struct { name string age uint8 }{ {"user1", 10}, // 可省略元素类型。 {"user2", 20}, // 别忘了最后一行的逗号。 }
定义二维数组 第 2 纬度不能用 "..."
var b [3][2]int=[3][2]int{{1,2},{3,4},{5,6}}var arr1 [2][3]int=[...][3]int{{1, 2, 3}, {7, 8, 9}}
定义slice
全局:var arr=[...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}var slice0 []int=arr[start:end] var slice1 []int=arr[:end] var slice2 []int=arr[start:] var slice3 []int=arr[:] var slice4=arr[:len(arr)-1] //去掉切片的最后一个元素局部:arr2 :=[...]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}slice5 :=arr[start:end]slice6 :=arr[:end] slice7 :=arr[start:] slice8 :=arr[:] slice9 :=arr[:len(arr)-1] //去掉切片的最后一个元素//通过make来创建切片var slice []type=make([]type, len)slice :=make([]type, len)slice :=make([]type, len, cap)
定义map
scoreMap :=make(map[string]int) scoreMap["张三"]=90 scoreMap["小明"]=100 // 如果key存在ok为true,v为对应的值;不存在ok为false,v为值类型的零值 v, ok :=scoreMap["张三"] if ok { fmt.Println(v) } else { fmt.Println("查无此人") } //使用delete()函数删除键值对 delete(scoreMap, "小明")//将小明:100从map中删除
数组按照下标排序输出
tetcc :=make(map[int]string,5) tetcc[1]="smallsha" tetcc[5]="smallsha1" tetcc[3]="smallsha2" sli :=[]int{} for i :=range tetcc { sli=append(sli,i) } sort.Ints(sli) for i :=0; i < len(tetcc); i++ { fmt.Println("内容",tetcc[sli[i]]) } fmt.Printf("%#v\n", sli) return
if判断
if test2 :=19; test2> 20 { fmt.Println("test11212121212") }else if test2 < 20 { fmt.Println("test1111") }
switch使用
test3 :=80 switch test3 { case 80: fmt.Println("1") fallthrough //可以使用fallthrough强制执行后面的case代码。 case 90: fmt.Println("2") case 100: fmt.Println("3") default: fmt.Println("default") }
多个参数调用
func test(s string, n ...int) string { var x int for _, i :=range n { x +=i } return fmt.Sprintf(s, x)} s :=[]int{1, 2, 3} res :=test("sum: %d", s...) // slice... 展开slice println(res)
闭包使用
func a() func() int { i :=0 b :=func() int{ i++ fmt.Println(i) return i } return b} ccccc :=a() ccccc() ccccc() return
关键字 defer 用于注册延迟调用。
这些调用直到 return 前才被执。因此,可以用来做资源清理。
多个defer语句,按先进后出的方式执行
惯例是:导致关键流程出现不可修复性错误的使用 panic,其他使用 error。
func main() { test()}func test() { defer func() { if err :=recover(); err !=nil { println(err.(string)) // 将 interface{} 转型为具体类型。 } }() panic("panic error!")}
表达式
package mainimport "fmt"type User struct { id int name string}func (self *User) Test() { fmt.Printf("%p, %v\n", self, self)}func main() { u :=User{1, "Tom"} u.Test() mValue :=u.Test mValue() // 隐式传递 receiver mExpression :=(*User).Test mExpression(&u) // 显式传递 receiver}
定义errorhttp://www.topgoer.com/%E6%96%B9%E6%B3%95/%E8%87%AA%E5%AE%9A%E4%B9%89error.html
//返回异常package mainimport ( "errors" "fmt")func getCircleArea(radius float32) (area float32, err error) { if radius < 0 { // 构建个异常对象 err=errors.New("半径不能为负") return } area=3.14 * radius * radius return}func main() { area, err :=getCircleArea(-5) if err !=nil { fmt.Println(err) } else { fmt.Println(area) }}//系统抛异常package mainimport "fmt"func test01() { a :=[5]int{0, 1, 2, 3, 4} a[1]=123 fmt.Println(a) //a[10]=11 index :=10 a[index]=10 fmt.Println(a)}func getCircleArea(radius float32) (area float32) { if radius < 0 { // 自己抛 panic("半径不能为负") } return 3.14 * radius * radius}func test02() { getCircleArea(-5)}//func test03() { // 延时执行匿名函数 // 延时到何时?(1)程序正常结束 (2)发生异常时 defer func() { // recover() 复活 恢复 // 会返回程序为什么挂了 if err :=recover(); err !=nil { fmt.Println(err) } }() getCircleArea(-5) fmt.Println("这里有没有执行")}func test04() { test03() fmt.Println("test04")}func main() { test04()}//自定义errorpackage mainimport ( "fmt" "os" "time")type PathError struct { path string op string createTime string message string}func (p *PathError) Error() string { return fmt.Sprintf("path=%s \nop=%s \ncreateTime=%s \nmessage=%s", p.path, p.op, p.createTime, p.message)}func Open(filename string) error { file, err :=os.Open(filename) if err !=nil { return &PathError{ path: filename, op: "read", message: err.Error(), createTime: fmt.Sprintf("%v", time.Now()), } } defer file.Close() return nil}func main() { err :=Open("/Users/5lmh/Desktop/go/src/test.txt") switch v :=err.(type) { case *PathError: fmt.Println("get path error,", v) default: }}
interface 应用
// 空接口作为函数参数func show(a interface{}) { fmt.Printf("type:%T value:%v\n", a, a)}// 空接口作为map值 var studentInfo=make(map[string]interface{}) studentInfo["name"]="李白" studentInfo["age"]=18 studentInfo["married"]=false fmt.Println(studentInfo)
go调度 sync.WaitGroup
var wg sync.WaitGroupfunc hello(i int) { defer wg.Done() // goroutine结束就登记-1 fmt.Println("Hello Goroutine!", i)}func main() { for i :=0; i < 10; i++ { wg.Add(1) // 启动一个goroutine就登记+1 go hello(i) } wg.Wait() // 等待所有登记的goroutine都结束}
go runtime.Gosched()
//让出CPU时间片,重新等待安排任务(大概意思就是本来计划的好好的周末出去烧烤,但是你妈让你去相亲,两种情况第一就是你相亲速度非常快,见面就黄不耽误你继续烧烤,第二种情况就是你相亲速度特别慢,见面就是你侬我侬的,耽误了烧烤,但是还馋就是耽误了烧烤你还得去烧烤)package mainimport ( "fmt" "runtime")func main() { go func(s string) { for i :=0; i < 2; i++ { fmt.Println(s) } }("world") // 主协程 for i :=0; i < 2; i++ { // 切一下,再次分配任务 runtime.Gosched() fmt.Println("hello") }}
runtime.Goexit()
//退出当前协程(一边烧烤一边相亲,突然发现相亲对象太丑影响烧烤,果断让她滚蛋,然后也就没有然后了)package mainimport ( "fmt" "runtime")func main() { go func() { defer fmt.Println("A.defer") func() { defer fmt.Println("B.defer") // 结束协程 runtime.Goexit() defer fmt.Println("C.defer") fmt.Println("B") }() fmt.Println("A") }() for { }}
runtime.GOMAXPROCS
//Go1.5版本之后,默认使用全部的CPU逻辑核心数///两个任务只有一个逻辑核心,此时是做完一个任务再做另一个任务。 将逻辑核心数设为2,此时两个任务并行执行,代码如下。func a() { for i :=1; i < 10; i++ { fmt.Println("A:", i) }}func b() { for i :=1; i < 10; i++ { fmt.Println("B:", i) }}func main() { runtime.GOMAXPROCS(2) go a() go b() time.Sleep(time.Second)}
通道1.对一个关闭的通道再发送值就会导致panic。
2.对一个关闭的通道进行接收会一直获取值直到通道为空。
3.对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值。
4.关闭一个已经关闭的通道会导致panic。
5.chan<- int是一个只能发送的通道,可以发送但是不能接收;
6 <-chan int是一个只能接收的通道,可以接收但是不能发送。
func counter(out chan<- int) { for i :=0; i < 100; i++ { out <- i } close(out)}func squarer(out chan<- int, in <-chan int) { for i :=range in { out <- i * i } close(out)}func printer(in <-chan int) { for i :=range in { fmt.Println(i) }}func main() { ch1 :=make(chan int) ch4 :=make(chan int) go counter(ch1) go squarer(ch4, ch1) printer(ch4)}
通道使用案例type Job struct { Id int RandNum int}type Result struct { job *Job sum int}func createPool(num int, jobChan chan *Job,resultChan chan *Result) { for i :=0; i < num; i++ { go func(jobChan chan *Job,resultChan chan *Result) { for i2 :=range jobChan { r_num :=i2.RandNum var sum int for r_num !=0 { tmp :=r_num % 10 sum +=tmp r_num /=10 } r :=&Result{ job: i2, sum: sum, } resultChan <- r } }(jobChan,resultChan) }}func main() { flag.Parse() jobChan :=make(chan *Job, 128) resultChan :=make(chan *Result, 128) createPool(64, jobChan, resultChan) // 4.开个打印的协程 go func(resultChan chan *Result) { // 遍历结果管道打印 for result :=range resultChan { fmt.Printf("job id:%v randnum:%v result:%d\n", result.job.Id, result.job.RandNum, result.sum) } }(resultChan) var id int // 循环创建job,输入到管道 for { id++ // 生成随机数 r_num :=rand.Int() job :=&Job{ Id: id, RandNum: r_num, } jobChan <- job } return }
select 监听通道 select { case <-chan1: // 如果chan1成功读到数据,则进行该case处理语句 case chan2 <- 1: // 如果成功向chan2写入数据,则进行该case处理语句 default: // 如果上面都没有成功,则进入default处理流程 }
flag使用 和 os.args var name string var age int var married bool var delay time.Duration flag.StringVar(&name, "name", "张三", "姓名") flag.IntVar(&age, "age", 18, "年龄") flag.BoolVar(&married, "married", false, "婚否") flag.DurationVar(&delay, "d", 0, "延迟的时间间隔") 或者使用 name :=flag.String("name", "张三", "姓名") age :=flag.Int("age", 18, "年龄") married :=flag.Bool("married", false, "婚否") delay :=flag.Duration("d", 0, "时间间隔") //解析命令行参数 flag.Parse() fmt.Println(name, age, married, delay) //返回命令行参数后的其他参数 fmt.Println(flag.Args()) //返回命令行参数后的其他参数个数 fmt.Println(flag.NArg()) //返回使用的命令行参数个数 fmt.Println(flag.NFlag()) //os.args if len(os.Args) > 0 { for index, arg :=range os.Args { fmt.Printf("args[%d]=%v\n", index, arg) } }
Log文件 logFile, err :=os.OpenFile("./smallsha.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err !=nil { fmt.Println("open log file failed, err:", err) return } log.SetOutput(logFile) //设置日志文件写入 log.SetFlags(log.Llongfile | log.Lmicroseconds | log.Ldate) log.SetPrefix("[smallsha]") log.Printf("%s","smallsha") log.Println("test") //log.Panic("test1") //log.Fatalln("test2") logger :=log.New(os.Stdout, "<New>", log.Lshortfile|log.Ldate|log.Ltime) logger.Println("这是自定义的logger记录的日志。")
循环操作arr :=[5]int{1,2,4,5,6,8}for i,num :=range arr { fmt.Println(i,num)}//while for 循环配合通道 var ( ch11=make(chan int) ch12=make(chan int) ) go func() { for i :=0; i < 10; i++ { ch11 <- i } close(ch11) }() go func() { for { i, ok :=<-ch11 if !ok { break } ch12 <- i * i } close(ch12) }() for i :=range ch12 { fmt.Println(i) }
指针const MAX int=3func main(){ a :=[]int{1,3,5} var ptr [MAX]*int; for i=0; i < MAX; i++ { ptr[i]=&a[i] /* 整数地址赋值给指针数组 */ } for i=0; i < MAX; i++ { fmt.Printf("a[%d]=%d\n", i,*ptr[i] ) }}
strconv 使用 s3, _ :=strconv.ParseBool("1") fmt.Printf("%# v\n", pretty.Formatter(s3)) return //int 转 string s2 :=100 i2 :=strconv.Itoa(s2) fmt.Printf("%# v\n", pretty.Formatter(i2)) return //string 转 int s1 :="100" i1, err :=strconv.Atoi(s1) if err !=nil { fmt.Println(err) return } fmt.Printf("%# v\n", pretty.Formatter(i1)) return
template使用可以注册httppackage logicimport ( "fmt" "html/template" "net/http")func init() { http.HandleFunc("/",sayHello) err :=http.ListenAndServe(":9090", nil) if err !=nil { fmt.Println(err) return }}type UserInfo struct { Name string Gender string Age int}func sayHello(w http.ResponseWriter,r *http.Request) { // 解析指定文件生成模板对象 tmpl, err :=template.ParseFiles("./hello.html") if err !=nil { fmt.Println("create template failed, err:", err) return } // 利用给定数据渲染模板,并将结果写入w user :=UserInfo{ Name: "枯藤", Gender: "男", Age: 18, } // 利用给定数据渲染模板,并将结果写入w tmpl.Execute(w, user)}
定义结构体package mainimport "fmt"type Books struct { title string author string subject string book_id int}func main() { var Book1 Books /* 声明 Book1 为 Books 类型 */ var Book2 Books /* 声明 Book2 为 Books 类型 */ /* book 1 描述 */ Book1.title="Go 语言" Book1.author="www.runoob.com" Book1.subject="Go 语言教程" Book1.book_id=6495407 /* book 2 描述 */ Book2.title="Python 教程" Book2.author="www.runoob.com" Book2.subject="Python 语言教程" Book2.book_id=6495700 /* 打印 Book1 信息 */ printBook(&Book1) /* 打印 Book2 信息 */ printBook(&Book2)}func printBook( book *Books ) { fmt.Printf( "Book title : %s\n", book.title) fmt.Printf( "Book author : %s\n", book.author) fmt.Printf( "Book subject : %s\n", book.subject) fmt.Printf( "Book book_id : %d\n", book.book_id)}//结构体new 和赋值 type Person struct { Username string `json:"username"` Password string `json:"password"`}//声明构造方法func newPerson(username string, password string) *Person { return &Person{ Username: username, Password: password, }}//定义修改结构体的方法func (p *Person) setUsername(username string) { p.Username=username}
map操作package mainimport "fmt"func main() { var countryCapitalMap map[string]string /*创建集合 */ countryCapitalMap=make(map[string]string) /* map插入key - value对,各个国家对应的首都 */ countryCapitalMap [ "France" ]="巴黎" countryCapitalMap [ "Italy" ]="罗马" countryCapitalMap [ "Japan" ]="东京" countryCapitalMap [ "India " ]="新德里" /*使用键输出地图值 */ for country :=range countryCapitalMap { fmt.Println(country, "首都是", countryCapitalMap [country]) } /*查看元素在集合中是否存在 */ capital, ok :=countryCapitalMap [ "American" ] /*删除元素*/ delete(countryCapitalMap, "France") /*如果确定是真实的,则存在,否则不存在 */ /*fmt.Println(capital) */ /*fmt.Println(ok) */ if (ok) { fmt.Println("American 的首都是", capital) } else { fmt.Println("American 的首都不存在") }}
声明变量 package main import "fmt" func main() { var a string="Runoob" fmt.Println(a) var b, c int=1, 2 fmt.Println(b, c) } 声明方法 package main import "fmt" func swap(x, y string) (string, string) { return y, x } func main() { a, b :=swap("Google", "Runoob") fmt.Println(a, b) }定义数组 var balance=[5]float32{1000.0, 2.0, 3.4, 7.0, 50.0} var balance=[...]float32{1000.0, 2.0, 3.4, 7.0, 50.0} package main import "fmt" func main() { var n [10]int /* n 是一个长度为 10 的数组 */ var i,j int /* 为数组 n 初始化元素 */ for i=0; i < 10; i++ { n[i]=i + 100 /* 设置元素为 i + 100 */ } /* 输出每个数组元素的值 */ for j=0; j < 10; j++ { fmt.Printf("Element[%d]=%d\n", j, n[j] ) } }
Go 语言结构体package mainimport "fmt"type Books struct { title string author string subject string book_id int}func main() { var Book1 Books /* 声明 Book1 为 Books 类型 */ var Book2 Books /* 声明 Book2 为 Books 类型 */ /* book 1 描述 */ Book1.title="Go 语言" Book1.author="www.runoob.com" Book1.subject="Go 语言教程" Book1.book_id=6495407 /* book 2 描述 */ Book2.title="Python 教程" Book2.author="www.runoob.com" Book2.subject="Python 语言教程" Book2.book_id=6495700 /* 打印 Book1 信息 */ fmt.Printf( "Book 1 title : %s\n", Book1.title) fmt.Printf( "Book 1 author : %s\n", Book1.author) fmt.Printf( "Book 1 subject : %s\n", Book1.subject) fmt.Printf( "Book 1 book_id : %d\n", Book1.book_id) /* 打印 Book2 信息 */ fmt.Printf( "Book 2 title : %s\n", Book2.title) fmt.Printf( "Book 2 author : %s\n", Book2.author) fmt.Printf( "Book 2 subject : %s\n", Book2.subject) fmt.Printf( "Book 2 book_id : %d\n", Book2.book_id)}结构体作为函数参数 func printBook( book Books ) { fmt.Printf( "Book title : %s\n", book.title) fmt.Printf( "Book author : %s\n", book.author) fmt.Printf( "Book subject : %s\n", book.subject) fmt.Printf( "Book book_id : %d\n", book.book_id) }
声明函数和方法区别#函数func main() { sum :=add(1, 2) fmt.Println(sum)} func add(a, b int) int { return a + b}#方法type person struct { name string} func (p person) String() string{ return "the person name is "+p.name}func main() { p:=person{name:"张三"} fmt.Println(p.String())}#可变参数func main() { print("1","2","3")}func print (a ...interface{}){ for _,v:=range a{ fmt.Print(v) } fmt.Println()}
修改字符串 需要先转换成 byte 或者 runefunc changeString() { s1 :="hello" // 强制类型转换 byteS1 :=[]byte(s1) byteS1[0]='H' fmt.Println(string(byteS1)) s2 :="博客" runeS2 :=[]rune(s2) runeS2[0]='狗' fmt.Println(string(runeS2))}
2007 年 9 月 21 日,Robert Griesemer,Rob Pike 和 Ken Thompson 在一块白板上勾勒了一个新语言,并在几天内定下了计划和目标,作为一个自由时间的项目进行;2008 年年中,这个项目得到 Google 的全力支持,成为了一个全职项目;2009 年 11 月,Go 语言正式开源 [1],并于三年后发布 1.0 版本。发布 12 年至今,已有上百万开发者成为“gopher”,已经远超设计者最初的预期。
# 源起 # Go 语言的开发团队可以说是高人云集:Ken Thompson 创造了 B 语言(C 语言前身),是 Unix 和 Plan9 操作系统的创立者之一,和 Dennis Ritchie 一起获得了 1983 年的图灵奖;Rob Pike 是 Unix 小组成员,也是 Limbo 语言和 UTF-8 编码的主要设计者;Robert Griesemer 曾参与制作 Google 的 JavaScript 引擎 V8、Java 的 HotSpot 虚拟机和领域特定语言 Sawzall [2](Sawzall 最早被 Google 用于处理大批量日志,后来基本被 Go 取代[3])。 Go 语言的三位创始人在 Google I/O 2012 - Meet the Go Team 随着 Go 的发展,出现了一些声音称 Go 语言没什么技术优势,而 Rob Pike 对此的态度是“你们是对的”,Go 语言本身 “并不有趣”,其设计初衷不是为了推动编程语言设计的进步,是为改善软件工程的工作环境而生 [4]。 当时 Google 面临着软件大规模增长的问题,开发方式变得缓慢而笨拙。规模化在各种维度上均存在,比如多核并行、生产需求和依赖关系等,导致硬件和软件都非常大,很多软件的代码多达百万行(大部分使用 C++、Java 或 Python)。Go 语言的诞生解决了大规模的软件开发中一些核心痛点,比如开发缓慢、不受控制的依赖关系、代码理解困难和重复造轮子等。 [5] 官网描述 Go 语言可用于构建“简单、可靠、高效的大规模软件”,JetBrains 2021 年的最新调研报告显示,程序员们在工作中使用 Go 语言的比例达到了 61%(Rust 语言用于工作的比例仅 16%) [6]。 使用 Go 的场景 可见 Go 确实不是一门“有趣”的编程语言,而更看重工程实践中的实用性,Go 语言设计中的方方面面都在体现这一点。 # 演进 # 版本更新历史 [7][8]: 2012 年 3 月,语言第一个正式版本 Go 1.0 发布,同时还有一份兼容性说明文档说明未来版本会确保向后兼容性。 2013 年 5 月,Go 1.1 发布,这个版本的编译器、垃圾回收机制、goroutine 调度器性能均有增强。 2014 年 12 月,Go 1.4 发布,堆栈大小减少了 10~30%;Android 官方支持包随版本一起发布;同时 Go 项目从 Google Code 迁移到了 Github。 2015 年 8 月,Go 编译器实现自举,完全移除了 C 语言;重新设计了垃圾回收器,支持并发,垃圾回收延迟显著降低,此后版本对垃圾回收器有更进一步的优化。 2018 年 8 月,Go 1.11 版本引入了 Go 模块功能,以响应在当年语言调研中反馈的模块管理问题;增加了实验性的 WebAssembly 支持。 2019 年 9 月,Go 1.13 版本中,改进了 sync 包 Pool 组件的资源被垃圾回收器清除的机制;重写了逃逸分析逻辑。 2020 年 8 月,Go 链接器得到了实质性的提高,减少了30% 的内存资源占用,提高了代码的可维护性。 2021 年 2 月,Go 1.16 支持了 macOS ARM64;同时默认使用 Go 模块功能(同样来自调研报告反馈的情况)。 2021 年 8 月,Go 1.17 发布,增加了对 Windows ARM64 的支持;语言有三个小的增强(unsage 包的两个新函数和语言类转换规则的扩展)。 Go 语言并没有特别激进的新特性,而是从早期的编程语言中继承了很多内容,并做了有效的组合 [9]。 影响 Go 语言设计的早期编程语言 Go 语言里一些广受开发者喜爱的特性 [10]: 并发性:这是 Go 语言最大的特色,从语言层面支持并发,goroutine 和 channel 可以非常容易的实现安全高并发。 高性能 Http Server:通过简单的代码调用就可以快速实现基于协程的 Web 服务[11],处理效率远高于 PHP。 易部署:直接编译成机器码,除了 glibc 没有其他外部依赖。 易学习:语言设计精简,25 个关键词表达能力强大;因为有 C 语言的基因,所以有 C 语言基础的开发者学习 Go 语言会非常轻松。 工具链 & 库:完善的工具链(代码分析、依赖管理等)和出色的标准库大大优化了开发者学习和应用的体验。 Go 发布至今还未正式支持泛型,甚至曾有很多人认为 Go 语言或许永远不会加泛型,但其实关于泛型的推进工作一直在进行,从 2020 年的调研报告来看,泛型是使用 Go 语言的开发者最需要的特性 [12]。 2020 年 Go 开发者调研报告:最需要但还缺少的特性 2021 年初 Ian Lance Taylor 正式发布了添加泛型的提案,不出意外会在下一个版本 Go 1.18 中实现。 # 社区 # # 语言项目社区 Go 语言项目在 2009 年开源,项目的 Star 数已达到 9 万。根据 GitHub 提供的数据 [13],截止 2021 年 10 月 20 日共有 166 人提交了超过 5 万次代码,其中 Go 语言项目成员占比 28.9%,贡献占比 64.9%,Google 员工(非语言项目成员)占比 24.7%,贡献占比 17.83%,贡献趋势如下图: Go 语言项目代码提交情况 从代码提交情况来看,大部分贡献来自 Google 内部,近三年来每年的提交人数稳定在 70~80 人。 参与社区提交 Issue 的用户数量也在持续增长,不过目前开启中的 Issue 仍有超过 7000 个,有近一半为“待调查分类”,可能为无效的 Issue。 Go 语言项目 Issue 创建情况 除了语言本身项目,语言的社区其实是更加广义的,使用 Go 语言的开发者有一个可爱的昵称 “Gopher”。 # “Gophers” 社区 在 GolangUK 2015 会议上,Damian Gryski 提到了 “Gophers” 的文化,这是开发者被 Go 吸引进而成为 “Gopher” 的原因 [14]。“Gophers” 的社区文化: SlashData 在 2021 年初发布的开发者调研显示 Go 开发者人数已超过 200 万,社区人数排行第十 [15]。 编程语言社区人数 在官方调研报告中,约半数开发者清楚了解如何给社区贡献,而感到自己的贡献是受欢迎的开发者比例约为 60%,这两个比例都在逐年提升 [12]。接受调研的开发者其实已经是相对活跃的,主观感受会导致调研结果有误差,但整体来看,仍有越来越多开发者愿意参与到社区贡献中。 Go 开发者调研报告 2020:社区满意度 活跃于社区的开发者通过邮件、媒体论坛、固定会议等交流 [16],有约 30% 开发者会频繁参与到开源项目中[12],但更多开发者并不会发出声音,他们在实践中使用 Go,也切实的从社区资源中获益。 邮件列表:golang-nuts、golang-dev、golang-announce 用户群和会议:GoBridge、GoDiscourse、GopherCon 媒体论坛:The Go Forum、Gophers Slack Channel、Golang News、Twitter、Stack Overflow、Reddit 等 # 应用 # GitHub 上 Go 为主要语言的开源项目已超过 10 万个,约 24 万开发者在这些开源项目中提交 PR [17]。 GitHub 上每月新创建的 Go 语言项目数 官方调研报告显示,Go 应用最多的领域是 Web 开发,此外也被广泛应用于数据库、DevOps、网络编程和系统编程;在科学计算、手机等方面的应用较少 [12]。 Go 开发者调研报告 2020:应用领域 这一点从开源项目中也可以了解到,2021 年截止 9 月最活跃的仓库(根据 PR 数量排序)基本上都与数据库、微服务平台和容器编排相关 [17]。Go 已成为云基础架构的语言,这绝不是偶然,最初 Go 的设计目的就是让 Google 工程师能更高效的编写“云端”软件。 2021 年 GitHub 活跃仓库(根据 PR 数量排序)Top 10 2013 年 Matt Stine 首次提出云原生(CloudNative)概念,2015 年云原生计算基金会(CNCF,Cloud Native Computing Foundation)成立,致力于发展推广云原生技术。CNCF 给云原生的定义中提到了目前的代表技术包括容器、服务网格、微服务、不可变基础架构和声明式 API 等 [18]。 图片可上下滑动 云原生基础设施的很多代表性项目均为 Go 语言实现,这些技术的普及又带动了 Go 语言的推广,可以说 Go 语言和“云”是相互成就的。 容器应用:Docker、Kubernetes 数据库应用:etcd、TiDB 微服务框架:Go Kit、Go Micro、Gizmo Web 框架:Gin、Echo、Beego 国内如 PingCAP、腾讯、百度和京东等大公司均有 Go 语言的应用,2020 年 JetBrains 的开发者生态系统调研中,中国有 16% 的开发者在使用 Go 语言 [19]。 Go 语言开发者地域分布 # 总结 # 回顾 Go 语言的发展情况,我们可以清楚地看到: 1. 针对开发痛点的语言特性:Go 从语言层面支持并行,部署简单,很好的解决了软件开发各维度规模化中出现的痛点。 2. 低学习曲线:可以快速上手并投入实际开发工作。 3. 开发者体验不断改善:开源社区运营,有完整的工具链、标准库和一些不错的开发框架。 4. “杀手级”应用:Docker、Kubernetes 出现,云计算技术不断完善普及,云原生概念兴起。 5. 巨头公司支持:大量国内外公司在使用 Go 语言开发,也会作为 Go 语言发展的支持后盾。 技术发展过程中,会顺应需求出现不同的开发工具或者说材料,Go 语言所有的特点都是为了实现更加高效的大规模开发而生,至少在云原生领域,Go 语言的地位已很难动摇。 扩展阅读 《Go语言设计与实现》会在今年年底正式出版,感兴趣的小伙伴可以看这里:《Go语言设计与实现》纸质书诚邀读者评论 (作者:Draveness) 参考 [1] What is the history of the project? - golang.org https://golang.org/doc/faq#history [2] Go (programming language) - wikipedia https://en.wikipedia.org/wiki/Go_(programming_language) [3] https://www.unofficialgoogledatascience.com/2015/12/replacing-sawzall-case-study-in-domain.html [4] The Go Programming Language and Environment - UNSW Computing https://www.youtube.com/watch?v=YXV7sa4oM4I&t=1136s [5] Go at Google: Language Design in the Service of Software Engineering https://talks.golang.org/2012/splash.article [6] The State of Developer Ecosystem 2021: Go - JetBrains https://www.jetbrains.com/lp/devecosystem-2021/go/ [7] Go: Retrospective https://medium.com/a-journey-with-go/go-retrospective-b9723352e9b0 [8] Release History - golang.org https://golang.org/doc/devel/release [9] Go语言起源 - Go语言圣经(中文版) https://books.studygolang.com/gopl-zh/ch0/ch0-01.html [10] 为什么要使用 Go 语言?Go 语言的优势在哪里?- 知乎 https://www.zhihu.com/question/21409296 [11] 大道至简—GO语言最佳实践 https://cloud.tencent.com/developer/article/1145176?fromSource=waitui [12] Go Developer Survey 2020 Results - golang.org https://go.dev/blog/survey2020-results [13] list commits - GitHub REST API https://docs.github.com/cn/rest/reference/repos#commits [14] The Go Community https://dgryski.medium.com/the-go-community-f0d00e3a19e [15] State of the Developer Nation 20th edition - SlashData [16] Wiki: The Go Community - GitHub https://github.com/golang/go/wiki#the-go-community [17] GH Archive https://www.gharchive.org/ [18] cncf https://www.cncf.io/ [19] Go 语言现状调查报告 - JetBrains https://blog.jetbrains.com/zh-hans/go/2021/02/19/the-state-of-go/ 从C++转向Rust需要注意哪些问题? 高并发场景下JVM调优实践之路 Apache APISIX 扩展指南 Pulsar与Rocketmq、Kafka、Inlong-TubeMQ,谁才是消息中间件的王者? Go 语言网络库 getty 的那些事 技术原创及架构实践文章,欢迎通过公众号菜单「联系我们」进行投稿。 高可用架构 改变互联网的构建方式- Code- gofmt- if err !=nil { ... }- go vet- go build -race- golint (CodeReviewComments)- godoc.org/github.com/user/package- go get- limited use of interface{}- Social- Simple is better than complex- Performance matters- Costs are visible
发表评论