零、各种安装
1、go的安装
mac下go的安装很简单, 官网下载对应安装包直接默认点击安装即可。
唯一值得注意的就是安装后要配置一下几个环境变量,这个到处都是。
2、安装IDE goland
值得注意的是安装后好要做些简单的配置,否则会发现并没有自动补齐等功能。
具体来说就是配置一下 Goland → Preference → Go → GOROOT/GOPATH。
在配置GOROOT的时候可能在自己尝试选择 默认的路径(如usr/local/go)的时候会出现诸如"The selected directory is not a valid home for Go SDK"的报错,按照网上的说法在zversion.go中添加自己对应的版本并重启Goland即可。
一、go学习
(1)查看go相关环境变量
go env
(2)其中的GOROOT 与 GOPATH 非常重要
默认情况下我们import的包都是在GOROOT 和 GOPATH路径下进行搜索的。我们引入的fmt、math这些都不例外。
我们先看看下这些库的样子,如下。
举个例子对于fmt文件这个路径下其实是若干go文件。
我们把这些go文件打开可以看到开头都有个"package fmt"。
上面就是模块的引用规则。
总结一下就是首先要在GOROOT 或 GOPATH路径下有一个模块名文件夹mymodule;然后在这个文件夹下有一个或多个.go文件;其中每个文件都有一个"package mymodule";
其他业务在引用的时候只需要在自己文件的开头加一句"import mymodule"就好了。
(3)举个例子:
1)首先有如下test.go文件。注意这里引用了一个自己定义的mymath模块。
bash
package main
import "fmt"
import "errors"
import "mymath"
func main() {
fmt.Println("(%d,%d)=%d ",1,1)
fmt.Printf("(%d,%d)=%d \n",1,1,2)
errors.New("xxxxxx")
sum,_:=mymath.Add(11,33)
fmt.Println("sum=",sum)
}
2)然后在 /mystudy/golang/go/src/(GOROOT) 或者 /root/go/src/mymath(GOPATH)下面新建一个mymath文件夹;并创建一个内容如下的.go文件。
bash
package mymath
import "errors"
func Add(a int, b int)(ret int, err error) {
if a < 0 || b < 0 {
err=errors.New("should be no-negative numbers!")
return
}
return a + b,nil
}
注意:这个文件的文件名无所谓,重要的"package mymath"
3)之后我们就可以执行了如下:
go run test.go
或
go build test.go
(4)我们可以修改这两个环境变量,例如把GOPATH修改成"/mystudy/golang/workspace";这时我们在workspace下新建一个"src";模块在放到这个src目录下就好了。
1)vim ~/.bashrc
2)加上如下一行:
export GOPATH=/mystudy/golang/workspace
3)然后执行如下语句即可:
source ~/.bashrc
4)echo $GOPATH
二、go的两种开发模式
1.GOPATH( GO111MODULE设为off/auto**)**
GOPATH是早期的设置方式,就是将工作目录通过GOPATH设置为全局环境变量。**也就是说所有下载下来的包都放在GOPATH/src这个目录下才可以引用。**显然这样是不太方便的,因为不同项目引用的package都放到了一起,这用git管理起来也是相当的麻烦(无法对依赖进行版本控制)。
2、Go mod( GO111MODULE设为on**)** go mod就是为了解决上述依赖问题(写程序会用到很多第三方库,但是第三方库会发生版本变化,go mod可以管理依赖的版本)。go mod有自己的一套引用机制,无论go文件在哪里都可以引用包。因为go mod是从1.13之后开始推行的,时间并不长;所以现在网上两种教程都有,很容易混淆。go mod完全可以替代GOPATH设置,只需要go env -w GO111MODULE=on
开启go mod。
(1)go文件import第三方库的时候导的是git的链接。如import "git.code.oa.com/trpc-go/trpc-go/log"库对应的就是"https://git.code.oa.com/trpc-go/trpc-config-rainbow"这个链接。
(2)我们完全可以在自己测试的go文件中引用trpc-go的库,例如其中的log。代码如下:
Go
package main
import (
"fmt"
"git.code.oa.com/trpc-go/trpc-go/log"
)
func main() {
log.Errorf("this is shuozhuo test")
log.Debugf("shuozhuo log test")
log.Errorf("this is shuozhuo test")
log.Debugf("shuozhuo,shuozhuo log test")
k := 5
fmt.Printf("shuozhuo,nihao,%d\n", k)
}
1)go mod init hello
2)go run hello.go #效果如下
3)go build -o hello hello.go #编译生成可执行文件hello
go mod init git.code.oa.com/shuozhuo/timeline_proxy
trpc create --protofile=timeline.proto
trpc-cli -func /trpc.track.timeline.timeline_proxy/SayHello -target ip://127.0.0.1:8000 -body '{"msg":"hello"}'
trpc-cli -func /trpc.track.timeline.timeline_proxy/SayHello -target ip://9.134.50.94:8000 -body '{"msg":"hello"}'
#查看接口信息
trpc-cli -protofile=./timeline.proto -interfacelist
#生成用例
trpc-cli -protofile=./timeline.proto -interfacename=SayHi -outputjson=testData.json
#发起测试
trpc-cli -datafiles=./testData.json -target=ip://127.0.0.1:8000 -out=testResult.out
#可以通过go get指令判断某个git远程模块是否可以被正常引用:
go get git.code.oa.com/trpc-go/trpc-go/log
go get git.woa.com/baicaoyuan/moss/plugins/codec/inner
包名不必和目录名一致,但为了更好的维护和更高的可读性,普遍的做法是报名和目录名一致,如若不一致,import的时候要写目录名,引用的时候要写包名。
有关GO的包引用:包名和目录名的关系 - adwin's blog,一个不知所措的青春,adwin
三、go语言快速入门
GOPATH是
1、可以快速的过一遍这个 Go 语言教程 | 菜鸟教程
2、另外B站上找些系统是视频也是不错的选择。
(1) := 等效于先声明在赋值 注:本质是个声名语句
a := 1 等价于
var a int
a =1
#如果再次给a赋值直接等号就好
a=2
(2)go中声名一个局部变量就必须要使用,否则报错。
如下声名了变量a,但是却并没有使用所以会报 " a declared but not used"。
Go
package main
import "fmt"
func main() {
var a string = "abc"
fmt.Println("hello, world")
}
注:全局变量是允许只声名不使用的。
(3)空白标志符"_"
go中空白标识符""被用于抛弃值,如值 5 在:, b = 5, 7 中被抛弃。
(4)go语言的数据类型
按照类型来分:
基本类型: int, float, string, bool
复合类型: array, slice, map, struct, pointer, function, channel
按照特点来分: 前者传递的是数据副本,后者传递的是地址。
值类型: int, float, string, bool, array
引用类型: slice, map,
(5)指针与引用
首先要明确:go中的引用并不是C++中别名的概念。他是在已经有了 直接值 和 指针 的前提下,针对特定类型的优化:为了兼顾易用性和性能,针对具体类型,在 值 和 指针 之前折中。一般是由一个结构体负责管理元数据,结构体里有一个指针,指向真正要使用的目标数据。
这种东西,如果在 C++ 或者 Java 里,就是一个官方提供的类(如 Java 的 String
类),可以看到它的内部机制。而 Go 引用的实现逻辑却内置在 runtime 里,不仅无法直接访问元数据,还表现得像在直接操作目标数据。你会以为它是个普通的值,直到某些行为跟想象中不一样,才想起了解它的底层结构。如果不去看 runtime 的源码,这些元数据结构体仿佛不存在。
Go 的引用类型有。具体参见如下实例。
1.字符串 string:底层的数据结构为 stringStruct ,里面有一个指针指向实际存放数据的字节数组,另外还记录着字符串的长度。不过由于 string 是只读类型(所有看起来对 string 变量的修改,实际上都是生成了新的实例),在使用上常常把它当做值类型看待。由于做了特殊处理,它甚至可以作为常量。string 也是唯一零值不为 nil 的引用类型。
2.切片(slice):底层数据结构为 slice 结构体 ,整体结构跟 stringStruct 接近,只是多了一个容量(capacity)字段。数据存放在指针指向的底层数组里。
3.映射(map):底层数据结构为 hmap ,数据存放在数据桶(buckets)中,桶对应的数据结构为 bmap 。
4.函数(func):底层数据结构为 funcval ,有一个指向真正函数的指针,指向另外的 _func 或者 funcinl 结构体(funcinl 代表被行内优化之后的函数)。
5.接口(interface):底层数据结构为 iface 或 eface (专门为空接口优化的结构体),里面持有动态值和值对应的真实类型。
6.通道(chan):底层数据结构为 hchan,分别持有一个数据缓冲区,一个发送者队列和一个接收者队列。
Go语言教程第六集 指针、引用和值类型_go 指针和引用_办公模板库 素材蛙的博客-CSDN博客
#举例1:对于struct采用值传递尝试修改参数
Go
package main
import "fmt"
func main() {
p:=person{name: "张三",age: 18}
modifyPerson(p)
fmt.Println("person name:",p.name,",age:",p.age)
}
func modifyPerson(p person) {
p.name = "李四"
p.age = 20
}
type person struct {
name string
age int
}
#此时执行结果为
person name: 张三 ,age: 18
显然此处因为是值拷贝,所以不能按预期那样修改。
#举例2:对于struct采用指针传递尝试修改参数
Go
package main
import "fmt"
func main() {
p:=person{name: "张三",age: 18}
modifyPerson(&p)
fmt.Println("person name:",p.name,",age:",p.age)
}
modifyPerson(&p)
func modifyPerson(p *person) {
p.name = "李四"
p.age = 20
}
type person struct {
name string
age int
}
此时执行结果为:
person name: 李四 ,age: 20
#举例3:对于map采用值传递尝试修改参数(至少直观看起来是值传递)
Go
package main
import "fmt"
func main() {
m:=make(map[string]int)
m["飞雪无情"] = 18
fmt.Println("飞雪无情的年龄为",m["飞雪无情"])
modifyMap(m)
fmt.Println("飞雪无情的年龄为",m["飞雪无情"])
}
func modifyMap(p map[string]int) {
p["飞雪无情"] =20
}
执行结果为:
飞雪无情的年龄为 18
飞雪无情的年龄为 20
根据结果可见这个时候我们肉眼看起来的值传递居然成功的修改了目标数据。
没有使用指针,只是用了 map 类型的参数,按照 Go 语言值传递的原则,modifyMap 函数中的 map 是一个副本,怎么会修改成功呢?要想解答这个问题,就要从 make 这个 Go 语言内建的函数说起。在 Go 语言中,任何创建 map 的代码(不管是字面量还是 make 函数)最终调用的都是 runtime.makemap 函数。
小提示:用字面量或者 make 函数的方式创建 map,并转换成 makemap 函数的调用,这个转换是 Go 语言编译器自动帮我们做的。
从下面的代码可以看到,makemap 函数返回的是一个 *hmap 类型,也就是说返回的是一个指针,所以我们创建的 map 其实就是一个 *hmap。
src/runtime/map.go
// makemap implements Go map creation for make(map[k]v, hint).
func makemap(t *maptype, hint int, h *hmap) *hmap{
//省略无关代码
}
因为 Go 语言的 map 类型本质上就是 *hmap,所以根据替换的原则,我刚刚定义的 modifyMap(p map) 函数其实就是 modifyMap(p *hmap)。这是不是和上一小节讲的指针类型的参数调用一样了?这也是通过 map 类型的参数可以修改原始数据的原因,因为它本质上就是个指针。
Go
package main
import "fmt"
func main() {
m:=make(map[string]int)
m["飞雪无情"] = 18
fmt.Printf("main函数:m的内存地址为%p\n",m)
fmt.Println("飞雪无情的年龄为",m["飞雪无情"])
modifyMap(m)
fmt.Println("飞雪无情的年龄为",m["飞雪无情"])
}
func modifyMap(p map[string]int) {
fmt.Printf("modifyMap函数:p的内存地址为%p\n",p)
p["飞雪无情"] =20
}
执行结果为:
main函数:m的内存地址为0x1400010e0c0
飞雪无情的年龄为 18
modifyMap函数:p的内存地址为0x1400010e0c0
飞雪无情的年龄为 20
从输出结果可以看到,它们的内存地址一模一样,所以才可以修改原始数据,得到年龄是 20 的结果。而且我在打印指针的时候,直接使用的是变量 m 和 p,并没有用到取地址符 &,这是因为它们本来就是指针,所以就没有必要再使用 & 取地址了。
所以在这里,Go 语言通过 make 函数或字面量的包装为我们省去了指针的操作,让我们可以更容易地使用 map。其实就是语法糖,这是编程界的老传统了。
严格来说go语言没有引用类型,但是我们可以把上述map、chan、interface、func、slice、string称为引用类型,这样便于理解。但是你要知道它本质上就是个指针,只是可以叫做引用类型而已。在参数传递的时候实际上还是值传递并不是诸如C++中的引用传递。
https://www.cnblogs.com/lidabo/p/16393500.html 具体参见这篇文章。
因为指针和引用本质上也是值,字面意义上Go 里面所有传递都是值传递。
对于Go语言,严格意义上来讲,只有一种传递,也就是按值传递(by value)。
当一个变量当作参数传递的时候,会创建一个变量的副本,然后传递给函数或者方法,你可以看到这个副本的地址和变量的地址是不一样的。
当变量当做指针被传递的时候,一个新的指针被创建,这个新的指针和老的指针一样指向同样的内存地址,所以你可以将这个指针看成原始变量指针的副本。当这样理解的时候,我们就可以理解成Go总是创建一个副本按值转递,只不过这个副本有时候是变量的副本,有时候是变量指针的副本。
在 Go 语言中,函数的参数传递只有值传递,而且传递的实参都是原始数据的一份拷贝。如果拷贝的内容是值类型的,那么在函数中就无法修改原始数据;如果拷贝的内容是指针(或者可以理解为引用类型 map、chan 等),那么就可以在函数中修改原始数据。
但在使用中,大部分情况下,指针只是改善性能(避免拷贝)、提高代码灵活性(共享对象)、实现复杂数据结构的工具。我们并不关心指针的值本身,而是关心指针指向的值 。为了方便讨论,指针变量跟它指向的值,常常会被等同看待。就像送礼或者颁奖时,不会有人举着汽车交给对方,而是会递交车钥匙;我们会将拿到车钥匙等同于拿到了车。(特别是 Go 取消了箭头操作符
->
,值和指针都用同样的方式访问成员,更是弱化了这个区分。)关于这一点可以看下面程序实例。无论是操作结构体本身还是操作的结构体指针,都用同样的方式来访问成员。
下面两个程序,可以看到无论传值 还是传指针printBook函数在访问成员的时候都是一样的。
Go
#结构体作为函数入参
package main
import "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)
}
Go
#结构体指针作为函数入参。
package main
import "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)
}
(6)go语言的"接口"
go语言的接口作用类似于C++中的虚函数机制,用于实现多态。即提供一个统一的调用方式,但是一种接口可以有多种实现。
简单的说多态的意思就是"一个接口,多种实现",通俗的说就是指同一个操作作用与不同的对象就会产生不同的响应。再具体一点的描述就是:在基类的函数前加上virtual关键字,在派生类中重写该函数,定义基类的指针来调用方法,运行时将会根据传给指针的对象的实际类型来调用相应的函数。如果对象类型是派生类的对象,就调用派生类的函数;如果对象类型是基类,就调用基类的函数。多态实际上可以分为静态多态 和动态多态。
注:感觉virtual还是蛮形象的,具体用的时候才知道调用那个函数(这个函数是"虚"的)。
静态多态:
函数的重载(运算符重载)和模板就是静态的多态,程序到底执行什么、怎么执行是在程序编译的时候就确定了的即静态联编。
动态多态:
C++依靠虚函数(virtual函数)实现动态多态,在程序运行的时候才确定具体的方法,此时称为"动态联编",C++就是通过虚函数实现动态联编的。
用virtual关键字声明的函数叫做虚函数,虚函数肯定是类的成员函数。
存在虚函数的类都有一个一维的虚函数表叫做虚表。类的对象有一个指向虚表开始的虚指针。虚表是和类对应的,虚表指针是和对象对应的。
多态性是一个接口多种实现,是面向对象的核心。分为类的多态性和函数的多态性。
多态用虚函数来实现,结合动态绑定。
纯虚函数是虚函数再加上"=0"。
抽象类是指包括至少一个纯虚函数的类。
参见一下实例。
以上代码实例中。先是定义了一个Shape接口,里面包含了一个area()方法,该方法返回了一个float64类型的面积值。然后我们定义了两个结构体Rectangle和Circle,他们分片实现了Shape接口的area()方法。在main函数中我们首先定义了一个Shape类型的变量s,然后分别将Rectangle和Circle类型的实例赋值给它,并通过area()方法计算面积并打印。
Go
package main
import "fmt"
type Shape interface{
area() float64
}
type Rectangle struct{
width float64
height float64
}
//就是给 Rectangle 结构体定义了一个方法。参见 https://m.runoob.com/go/go-method.html
func (r Rectangle) area() float64{
return r.width * r.height
}
type Circle struct{
radius float64
}
//就是给 Circle 结构体定义了一个方法。参见 https://m.runoob.com/go/go-method.html
func(c Circle) area() float64{
return 3.14 * c.radius * c.radius
}
func main(){
var s Shape
s = Rectangle{10, 5}
fmt.Printf("矩形面积:%f\n", s.area())
s = Circle{3}
fmt.Printf("原型面积:%f\n", s.area())
}
接口类型变量可以存储任何实现了该接口的类型的值。在示例中,我们将 Rectangle 和 Circle 类型的实例都赋值给了 Shape 类型的变量 s,并通过 area() 方法调用它们的面积计算方法。
四、加载不了远程git模块:
前言:需要配置的主要是两块。1)代理(GOPRIVATE等go相关环境变量好像也不是特别需要) 2)~/.gitconfig
1、DevCloud linux应该去掉全部http、https的代理!!!!
(1)~/.bashrc 添加如下
cpp
export http_proxy= #一定需要
export https_proxy= #一定需要
export GOPRIVATE="git.code.oa.com,git.woa.com" #不要好像也行
#GOPROXY等这些好像不要也都问题不大!!!
(2)~/.gitconfig
bash
#这个没有好像也行
[http]
proxy = http://devnet-proxy.oa.com:8080
#下面这一坨转换是一定需要的!!!!
[url "git@git.code.oa.com:"]
insteadOf = https://git.code.oa.com/
[url "git@git.code.oa.com:"]
insteadOf = http://git.code.oa.com/
[url "git@git.woa.com:"]
insteadOf = https://git.woa.com/
[url "git@git.woa.com:"]
insteadOf = http://git.woa.com/
五、其他点
1、GO111MODULE 引发的包引用异常
大致上来讲就是按照教科书上讲的那种引用模块的方式总是报错。正常应该这样,但是死活不行(Go语言包的基本概念)。具体而言只有把引用库放到GOROOT/src路径下才能被第三方程序引用到,放到GOPATH/src、绝对路径、相对路径都是引用不到的。一般报错如下,不过我的引用应该是没有问题的,那为什么会这样呢??
经过多方查阅资料才知道还有GO111MODULE这个一个东西。echo 之后发现其为on,如下:
cpp
[root@shuozhuo1605259746495-0 /data/go/gostudy]# echo $GO111MODULE
on
按指引,这个地方设为off就好了。
设置方法如下
(1)首先尝试直接执行如下指令,然后"echo $GO111MODULE"看下是不是为off了
go env -w GO111MODULE=off
设置的时候可能会发现如下异常。原因是在设置的时候系统的环境变量GO111MODULE已经有值,而go env是不支持覆盖写入的。既然如此我们直接修改系统文件得了。
[root@shuozhuo1605259746495-0 /data/go/gostudy]# go env -w GO111MODULE=off
warning: go env -w GO111MODULE=... does not override conflicting OS environment variable
(2)修改系统文件
例如说这里直接修改 ~/.bashrc系统文件。其实就是在这个系统文件里面添加如下一行;然后 "source ~/.bashrc"一下就好了。
export GO111MODULE=off
这个时候再echo一下就发现该变量被设为off了。如下:
果然设置完后整个世界都安静了!!!
注:上面已经介绍了GOPAHT和go mod两种模式,接下来我们还是推荐使用go mod而不是上述设为off的方式。
2、项目目录结构不合理引发的问题。
具体来说就是引用重复了。例如GOROOT 与 GOPATH都引用了某个包、某个包已经在两个配置路径下了第三方代码又按照绝对或相对路径的方式引用等。
解决方法:删除一个或者更改名称等都行。
3、开启go mod后 "$GOPATH/go.mod exists but should not"
开启模块支持后,并不能与GOPATH共存,所以把项目从GOPATH中移出即可
4、import语句中的下划线"_"
导入包加下划线"_"表示只执行该库的init函数,而不对其他部分进行真正的导入。
还有很多基础语法的问题:
(1)go语言中的方法
方法是特殊的函数,定义在某一特定的类型上,通过类型的实例来进行调用,这个实例被叫接收者(receiver)。
函数将变量作为参数:Function1(recv)
方法在变量上被调用:recv.Method1()
接口的类型和价值是动态的。例如Shape接口与Rectangle 和 Circle的关系 https://www.jianshu.com/p/82436645927b
(2)结构体:
#这样初始化就需要两个逗号
s=&Rect{
4.0,
5.0,
}
#这样就只需要一个逗号
s=&Rect{4.0, 5.0}