
前言
一 简介
二 环境

export GOROOT=/usr/local/goexport GOPATH=$HOME/go
GOROOT:表示的是Go语言编译、工具、标准库等的安装路径,其实就相当于配置JAVA_HOME那样。
GOPATH:这个和Java有点不一样,Java里并不需要设置这个变量,这个表示Go的工作目录,是全局的,当执行Go命令的时候会依赖这个目录,相当于一个全局的workspace。一般还会把$GOPATH/bin设置到PATH目录,这样编译过的代码就可以直接执行了。
1 纯文本开发
package mainimport "fmt"func main() {fmt.Println("hello, world")}

2 GoLand


3 VSCODE
三 工程结构
├── bin│ ├── air│ ├── govendor│ ├── swag│ └── wire├── pkg│ ├── darwin_amd64│ ├── mod│ └── sumdb└── src├── calc├── gin-blog├── github.com├── golang.org├── google.golang.org├── gopkg.in└── simplemath
GOPATH=/Users/fuxing/develop/testgo/calc-outside:/Users/fuxing/develop/go #gosetup
1 Go Modules
// 创建mod项目,也是可以用IDE来new一个mod项目的:go mod init calc-mod// 一般开源在github上面的项目名字是这样的;和maven、gradle不一样的是,开发完成根本不需要发布到仓库!只要提交代码后打tag就可以了go mod init github.com/fuxing-repo/fuxing-module-name// 创建一个模块:执行这个命令主要是多了一个go.mod文件,里面就一行内容:module calc-mod// import以后,执行下载依赖命令,不需要编辑go.mod文件。依赖会下载到GOPATH/pkg/mod目录go list
go get -u github.com/fuxing-repo/fuxing-module-name


四 语法
1 包:Package 和 Import




Compiled binary cannot be executed.
2 变量
用 var 关键字修饰(类似于JS),有多个变量的时候用括号 () 包起来,默认是有初始化值的,和Java一样。
如果初始化的时候就赋值了那可以不需要 var 来修饰,和Java不同的是变量类型在变量后面而不是前面,不过需要 := 符号。
最大的变化就是类型在变量后面!
语句可以省略分号 ;
var v1 int = 10 // 方式一,常规的初始化操作var v2 = 10 // 方式二,此时变量类型会被编译器自动推导出来v3 := 10 // 方式三,可以省略 var,编译器可以自动推导出v3的类型//javaprivate HashMap<String, UGCUserDetail> mBlockInfo;
多重赋值
i, j = j, i
匿名变量

指针变量
变量类型
布尔类型:bool
整型:int8、byte、int16、int、uint、uintptr 等
浮点类型:float32、float64
复数类型:complex64、complex128
字符串:string
字符类型:rune,本质上是uint32
错误类型:error
指针(pointer)
数组(array)
切片(slice)
字典(map)
通道(chan)
结构体(struct)
接口(interface)
const (Sunday = iotaMondayTuesdayWednesdayThursdayFridaySaturdaynumberOfDays)
类型强转
v1 := 99.99v2 := int(v1) // v2 = 99v1 := []byte{'h', 'e', 'l', 'l', 'o'}v2 := string(v1) // v2 = hello//字符相关的转化一般用strconv包v1 := "100"v2, err := strconv.Atoi(v1) // 将字符串转化为整型,v2 = 100v3 := 100v4 := strconv.Itoa(v3) // 将整型转化为字符串, v4 = "100"//结构体类型转换//类型断言//x.(T) 其实就是判断 T 是否实现了 x 接口,如果实现了,就把 x 接口类型具体化为 T 类型;claims, ok := tokenClaims.Claims.(*jwt.StandardClaims)
数组与切片
//定义数组var a [8]byte // 长度为8的数组,每个元素为一个字节var b [3][3]int // 二维数组(9宫格)var c [3][3][3]float64 // 三维数组(立体的9宫格)var d = [3]int{1, 2, 3} // 声明时初始化var e = new([3]string) // 通过 new 初始化var f = make([]string, 3) // 通过 make初始化//初始化a := [5]int{1,2,3,4,5}b := [...]int{1, 2, 3}//切片b := []int{} //数组切片slice就是一个可变长数组c := a[1:3] // 有点类似于subString,或者js.sliced := make([]int, 5) //make相当于,new、alloc,用来分配内存//数组的长度length := len(a)//添加一个元素b = append(b, 4)
字典
var testMap map[string]inttestMap = map[string]int{"one": 1,"two": 2,"three": 3,}//还可以这样初始化:var testMap = make(map[string]int) //map[string]int{}testMap["one"] = 1testMap["two"] = 2testMap["three"] = 3make和new
// The make built-in function allocates and initializes an object of type// slice, map, or chan (only). Like new, the first argument is a type, not a// value. Unlike new, make's return type is the same as the type of its// argument, not a pointer to it. The specification of the result depends on// the type:// Slice: The size specifies the length. The capacity of the slice is// equal to its length. A second integer argument may be provided to// specify a different capacity; it must be no smaller than the// length. For example, make([]int, 0, 10) allocates an underlying array// of size 10 and returns a slice of length 0 and capacity 10 that is// backed by this underlying array.// Map: An empty map is allocated with enough space to hold the// specified number of elements. The size may be omitted, in which case// a small starting size is allocated.// Channel: The channel's buffer is initialized with the specified// buffer capacity. If zero, or the size is omitted, the channel is// unbuffered.func make(t Type, size ...IntegerType) Type// The new built-in function allocates memory. The first argument is a type,// not a value, and the value returned is a pointer to a newly// allocated zero value of that type.func new(Type) *Type
神奇的nil
func Foo() error {var err *os.PathError = nil// …return err //实际返回的是[nil, *os.PathError]//return nil //正确的方式是直接return nil 实际返回的是[nil, nil]}func main() {err := Foo()fmt.Println(err) // <nil>fmt.Println(err == nil) // falsefmt.Println(err == (*os.PathError)(nil)) //true}
根对象:Object

3 语句
控制流程

switch关键字后面可以不跟变量,这样case后面就必须跟条件表达式,其实本质上就是美化了if-else-if。
如果switch后面跟变量,case也变得强大了,可以出现多个结果选项,通过逗号分隔。
swtich后面还可以跟一个函数。
不需要用break来明确退出一个case,如果要穿透执行一层,可以用 fallthrough 关键字。
score := 100switch score {case 90, 100:fmt.Println("Grade: A")case 80:fmt.Println("Grade: B")case 70:fmt.Println("Grade: C")case 60:case 65:fmt.Println("Grade: D")default:fmt.Println("Grade: F")}s := "hello"switch {case s == "hello":fmt.Println("hello")fallthroughcase s == "xxxx":fmt.Println("xxxx")case s != "world":fmt.Println("world")}//output:hello xxxx
循环流程
//通用的用法for i := 1; i <= 5; i++ {fmt.Println(i)}//类似于while的用法a := 1for a <= 5 {fmt.Println(a)a ++}//死循环for {// do something}for ;; {// do something}//类似java for-each的用法listArray := [...]string{"xiaobi", "xiaoda", "xiaoji"}for index, item := range listArray {fmt.Printf("hello, %d, %s\n", index, item)}//javafor (String item : someList) {System.out.println(item);}
跳转流程
i := 1flag:for i <= 10 {if i%2 == 1 {i++goto flag}fmt.Println(i)i++}
func printName(name string) {fmt.Println(name)}func main() {name := "go"defer printName(name) // output: goname = "python"defer printName(name) // output: pythonname = "java"printName(name) // output: java}//output:javapythongo//defer后于return执行var name string = "go"func myfunc() string {defer func() {name = "python"}()fmt.Printf("myfunc 函数里的name:%s\n", name)return name}func main() {myname := myfunc()fmt.Printf("main 函数里的name: %s\n", name)fmt.Println("main 函数里的myname: ", myname)}//output:myfunc 函数里的name:gomain 函数里的name: pythonmain 函数里的myname: go
4 函数
关键字是 func,Java则完全没有 function 关键字,而是用 public、void 等等这样的关键字,JS也可以用箭头函数来去掉 function 关键字了。
函数的花括号强制要求在首行的末尾。
可以返回多个值!返回值的类型定义在参数后面了,而不是一开始定义函数就需要写上,跟定义变量一样,参数的类型定义也是一样在后面的,如果相同则保留最右边的类型,其他省略。
可以显式声明了返回值就可以了,必须每个返回值都显式,就可以省略 return 变量。
//一个返回值func GetEventHandleMsg(code int) string {msg, ok := EventHandleMsgMaps[code]if ok {return msg}return ""}//多个返回值func GetEventHandleMsg(code int) (string, error) {msg, ok := EventHandleMsgMaps[code]if ok {return msg, nil}return "", nil}//不显式return变量值func GetEventHandleMsg(code int) (msg string, e error) {var ok boolmsg, ok = EventHandleMsgMaps[code]if ok {//do somethingreturn}return}
匿名函数和闭包
//传递匿名函数func main() {i := 10add := func (a, b int) {fmt.Printf("Variable i from main func: %d\n", i)fmt.Printf("The sum of %d and %d is: %d\n", a, b, a+b)}callback(1, add);}func callback(x int, f func(int, int)) {f(x, 2)}//return 匿名函数func main() {f := addfunc(1)fmt.Println(f(2))}func addfunc(a int) func(b int) int {return func(b int) int {return a + b}}
不定参数
//定义func SkipHandler(c *gin.Context, skippers ...SkipperFunc) bool {for _, skipper := range skippers {if skipper(c) {return true}}return false}//调用middlewares.SkipHandler(c, skippers...)
五 面向对象
type Integer int
1 类
type Student struct {id uintname stringmale boolscore float64}//没有构造函数,但是可以用函数来创建实例对象,并且可以指定字段初始化,类似于Java里面的静态工厂方法func NewStudent(id uint, name string, male bool, score float64) *Student {return &Student{id, name, male, score}}func NewStudent2(id uint, name string, male bool, score float64) Student {return Student{id, name, male, score}}
2 成员方法
//这种声明方式和C++一样的,这个就是不是普通函数了,而是成员函数。//注意到的是,两个方法一个声明的是地址,一个声明的是结构体,两个都能直接通过点操作。func (s Student) GetName() string {return s.name}func (s *Student) SetName(name string) {s.name = name}//使用func main() {//a是指针类型a := NewStudent(1, "aa", false, 45)a.SetName("aaa")fmt.Printf("a name:%s\n", a.GetName())b := NewStudent2(2, "bb", false, 55)b.SetName("bbb")fmt.Printf("b name:%s\n", b.GetName())}//如果SetName方法和GetName方法归属于Student,而不是*Student的话,那么修改名字就会不成功//本质上,声明成员函数,就是在非函数参数的地方来传递对象、指针、或者说是引用,也就是变相传递this指针//所以才会出现修改名字不成功的case
type Animal struct {name string}func (a Animal) FavorFood() string {return "FavorFood..."}func (a Animal) Call() string {return "Voice..."}type Dog struct {Animal}func (d Dog) Call() string {return "汪汪汪"}//第二种方式,在初始化就需要指定地址,其他都没变化type Dog2 struct {*Animal}func test() {d1 := Dog{}d1.name = "mydog"d2 := Dog2{}d2.name = "mydog2"//结构体是值类型,如果传入值变量的话,实际上传入的是结构体值的副本,对内存耗费更大,//所以传入指针性能更好a := Animal{"ddog"}d3 := Dog{a}d4 := Dog2{&a}}
4 接口
//定义接口:type Phone interface {call()}//实现接口:type IPhone struct {name string}func (phone IPhone) call() {fmt.Println("Iphone calling.")}
六 并发编程

// 数据生产者func producer(header string, channel chan<- string) {// 无限循环, 不停地生产数据for {// 将随机数和字符串格式化为字符串发送给通道channel <- fmt.Sprintf("%s: %v", header, rand.Int31())// 等待1秒time.Sleep(time.Second)}}// 数据消费者func customer(channel <-chan string) {// 不停地获取数据for {// 从通道中取出数据, 此处会阻塞直到信道中返回数据message := <-channel// 打印数据fmt.Println(message)}}func main() {// 创建一个字符串类型的通道channel := make(chan string)// 创建producer()函数的并发goroutinego producer("cat", channel)go producer("dog", channel)// 数据消费函数customer(channel)}//output:dog: 1298498081cat: 2019727887cat: 1427131847dog: 939984059dog: 1474941318cat: 911902081cat: 140954425dog: 336122540
七 总结
招聘
技术公开课
《Go语言核心编程(1):基础语法、数组、切片、Map》
本课程共182课时,包含Go语言入门、变量、数据类型和指针、运算符、程序流程控制、函数、包和错误处理、数组、切片、排序和查找、Map等基础知识。
