@Author: Solomanxbr | WeChat_ID: Solomanxbr | 微信公众号:
1. 环境与命令
在编译目标程序时,可以设置GOOS和GOARCH环境变量,来配置程序的目标运行平台,实现跨平台编译。
GOOS:设定程序目标运行平台
- Mac:GOOS=darwin
- Linux:GOOS=linux
- Windows:GOOS=windows
GOARCH:设定程序目标运行平台的处理器架构
- 386:GOARCH=386
- AMD64:GOARCH=amd64
- ARM:GOARCH=arm
Go 常用命令
// 查看当前Go环境变量
go env
// 直接运行源代码程序
go run xx.go
go run *.go
// 编译程序,-o 编译结果文件名称
go build -o xx.exe xx.go
go build *.go
// 安装编译好的程序到GOBIN目录下(GOPATH下的bin目录,需要在环境变量中设置GOBIN)
go install
2. 数组与切片
Golang 普通数组为固定长度,而切片就是非定长的数组,其长度和容量会随着元素增多而增大。
package main
import "fmt"
func main() {
cities := [8]string {"重庆", "成都", "北京", "上海", "广州", "深圳", "杭州", "南京"}
city1 := cities[2:6]
fmt.Println("city1: ", city1, "cities: ", cities)
// 切片引用原数组,可看作原数组部分元素的别名,修改切片会导致修改原数组的元素
city1[2] = "三亚"
fmt.Println("city1: ", city1, "cities: ", cities)
// copy()函数直接复制一份数据,修改新值不会影响原数组
copyCities := make([]string, len(cities))
copy(copyCities, cities[:])
fmt.Println("copySites: ", copyCities, "cities: ", cities)
copyCities[0] = "山城雾都"
fmt.Println("copySites: ", copyCities, "cities: ", cities)
cities[4] = "广州again"
city2 := cities[:2]
city3 := cities[6:]
city4 := cities[2:5]
fmt.Println("city2: ", city2, "city3: ", city3, "city4: ", city4)
for index, value := range cities {
fmt.Println(index+1, ".", value)
}
// 第二个参数为切片长度,第三个参数为切片容量
sites := make([]string, 2, 2)
fmt.Println("sites: ", sites)
sites[0] = "www.soloman.vip"
sites[1] = "www.sundaydesign.top"
fmt.Println("sites: ", sites, "len: ", len(sites), "cap: ", cap(sites))
sites = append(sites, "www.golang.org")
fmt.Println("sites: ", sites, "len: ", len(sites), "cap: ", cap(sites))
sites = append(sites, "www.python.org")
sites = append(sites, "www.google.com")
// 增加元素,会动态改变切片长度和容量大小。len()获取切片长度,cap()获取切片容量
fmt.Println("sites: ", sites, "len: ", len(sites), "cap: ", cap(sites))
for _, site := range sites {
fmt.Println(site)
}
}
3. 字典
使用make()函数分配空间时,建议指定具体长度,这样性能会更好。集合不存在访问越界,对于不存在的键会返回值对应数据类型的零值(布尔:false、数字:0、字符串:空字符串)
package main
import "fmt"
func main() {
siteMap := make(map[string]string, 5)
siteMap["name"] = "Soloman"
siteMap["domain"] = "soloman.vip"
siteMap["category"] = "personal site"
siteMap["developer"] = "Solomanxbr"
siteMap["technology"] = "Python/Golang/MySQL/Redis/Nginx/Linux"
printMap(siteMap)
siteMap["host"] = "www.soloman.vip"
delete(siteMap, "technology")
delete(siteMap, "category")
printMap(siteMap)
// 通过ok为true/false来判断集合中是否存在该键
value, ok := siteMap["ip_address"]
if ok {
fmt.Println("ip address:", value)
} else {
fmt.Println("Doesn't has ip address info:", ok)
}
}
// 遍历打印集合键值对
func printMap(mapData map[string]string) {
fmt.Println("------------------")
for key, value := range mapData {
fmt.Println(key, ": ", value)
}
fmt.Println("------------------")
}
4. 函数
函数名首字母大写,为 public,可在其他包中导入使用;首字母小写的函数为 private, 只能在包内使用。内存逃逸:Golang 编译器制动管理内存,在编译时会判断并将需要的变量从栈移动到堆上。
package main
import "fmt"
func main() {
score, greeting, flags := funcExample(10, "Soloman", false)
fmt.Printf("%s! your score is %d. The final exam passed:%t", greeting, score, flags)
}
func funcExample(num int, name string, ok bool) (ret int, msg string, flg bool) {
ret = num * 10
msg = "Nice to meet you, " + name
flg = !ok
return
}
5. switch case
./switch_case.exe site hello["/Code/switch_case.exe", "site", "hello"]
package main
import (
"fmt"
"os"
)
func main() {
cmds := os.Args
if len(cmds)<2 {
fmt.Println("Please input more parameters...", cmds)
} else {
for index, cmd := range cmds {
fmt.Printf("%d %s\n", index, cmd)
}
switch cmds[1] {
case "hi":
fmt.Println("Hello")
case "site":
fmt.Println("www.soloman.vip")
case "bye":
fmt.Println("Bye Bye")
default:
fmt.Println("I don't know")
}
}
}
6. init 和 defer
在导入一个包时,会自动执行该包中的所有 init() 函数。
- init() 函数没有参数,也没有返回值
- 一个包可以包含多个 init() 函数,调用顺序不确定
func init() {
fmt.Println("Do some init operations when this package been imported.")
}
defer 用于定义延迟操作的关键字,确保程序退出当前栈时会执行相关操作。
- 一般用于定义清理资源的操作,如打开文件后关闭文件、替代锁的作用等
- 在同一函数中有多个 defer 时,执行顺序类似栈:先进后出
func deferExample() {
f1, err := os.Open("temp.log")
// 定义并调用匿名函数
defer func() {
fmt.Println("Ready to close file.")
_ = f1.Close()
}()
if err != nil {
fmt.Println("An error occurred when open file:", err)
} else {
buf := make([]byte, 256)
cnt, _ := f1.Read(buf)
fmt.Printf("Read content: %d\n%s", cnt, string(buf))
}
defer fmt.Println("I am executed before file closed.")
defer fmt.Println("I am executed on the second.")
defer fmt.Println("\nI am executed on the first after file content.")
}
/*
Read content: 172
contents in temp.log
I am executed on the first after file content.
I am executed on the second.
I am executed before file closed.
Ready to close file.
*/
7. 类的封装、继承与多态
7.1 封装
Golang 没有 class 关键字,通过结构体来定义类属性并绑定类方法。封装一个 Site 类,以指针和值副本的方式分别绑定 Introduce1() 和 Introduce2() 方法。
package main
import "fmt"
func main() {
site1 := Site{
"Soloman",
"www.soloman.vip",
"Python/Golang/MySQL/Redis",
3,
8888.88,
}
site2 := site1
fmt.Println("使用指针绑定方法")
fmt.Println("修改之前:", site1)
site1.Introduce1()
fmt.Println("修改之后:", site1)
fmt.Println("使用值副本绑定方法")
fmt.Println("修改之前:", site2)
site2.Introduce2()
fmt.Println("修改之前:", site2)
}
// Site 定义类属性
type Site struct {
name string
host string
technology string
age int
price float64
}
// Introduce1 指针形式绑定方法,会修改site1本身
func (this *Site) Introduce1() {
fmt.Printf("My name is %s, my host is %s, I'm %d years old, built by %s, worth %f dollars.\n",
this.name, this.host, this.age, this.technology, this.price)
this.host = "www.sundaydesign,top"
this.age = 6
this.price = 9999.66
fmt.Printf("My name is %s, my host is %s, I'm %d years old, built by %s, worth %f dollars.\n",
this.name, this.host, this.age, this.technology, this.price)
}
// Introduce2 值副本形式绑定方法,不会修改site2本身
func (this Site) Introduce2() {
fmt.Printf("My name is %s, my host is %s, I'm %d years old, built by %s, worth %f dollars.\n",
this.name, this.host, this.age, this.technology, this.price)
this.host = "www.sundaydesign,top"
this.age = 8
this.price = 9999.88
fmt.Printf("My name is %s, my host is %s, I'm %d years old, built by %s, worth %f dollars.\n",
this.name, this.host, this.age, this.technology, this.price)
}
7.2 继承
注意区分结构体嵌套与类继承的区别。Golang 中的 public 和 private 都是通过首字母大小写来区分,在 import 导包和类中都是这样。在不同包中,想导入使用其他包中的类属性和类方法,则相关属性和方法首字母必须大写。
package main
import "fmt"
func main() {
soloman := Student{
person: Person{
"Soloman",
17,
"男",
false,
},
score: 98,
class: "高二(1)班",
school: "清华附中",
}
teacherMa := Teacher{
grade: "教授",
subject: "量子力学",
school: "清华大学",
}
teacherMa.name = "马老师"
teacherMa.age = 50
teacherMa.gender = "男"
teacherMa.adult = true
fmt.Println(soloman)
fmt.Println(teacherMa)
soloman.Saying()
teacherMa.Speaking()
}
// Person 定义父类
type Person struct {
name string
age int
gender string
adult bool
}
// Student 嵌套 Person
type Student struct {
person Person
score int
class string
school string
}
// Teacher 继承 Person
type Teacher struct {
Person
grade string
subject string
school string
}
func (stu *Student) Saying() {
fmt.Printf("My name is %s, I'm a student come from %s, %d years old.\n",
stu.person.name, stu.school, stu.person.age)
}
func (tch *Teacher) Speaking() {
fmt.Printf("My name is %s, I'm a %s come from %s, good at %s",
tch.name, tch.grade, tch.school, tch.subject)
}
7.3 多态
接口可以接受任何数据类型,一般用于根据不同数据类型做不同的程序处理。接口基本使用:
package main
import "fmt"
func main() {
arr := make([]interface{}, 5)
arr[0] = "www.soloman.vip"
arr[1] = 999
arr[2] = 3.1415926
arr[3] = false
for _, value := range arr {
switch v := value.(type) {
case int:
fmt.Printf("Value %d is a number.\n", v)
case float64:
fmt.Printf("Value %f is a float,\n", v)
case string:
fmt.Printf("Value %s is a string.\n", v)
case bool:
fmt.Printf("Value %v is an bool.\n", v)
default:
fmt.Println("Not a legal value:", v)
}
}
}
接口实现多态:
package main
import "fmt"
func main() {
//var creature Creature
soloman := Human{
name: "Soloman",
gender: "boy",
age: 17,
}
goldFish := Fish{
name: "Fairy of Water",
category: "gold fish",
}
eagle := Bird{
name: "King of the Sky",
category: "bird",
}
//creature = &soloman
//creature.Move()
//creature.Speak()
Introduce(&soloman)
Introduce(&goldFish)
Introduce(&eagle)
}
// Creature 接口及其方法
type Creature interface {
Move()
Speak()
}
type Human struct {
name string
gender string
age int
}
type Fish struct {
name string
category string
}
type Bird struct {
name string
category string
}
// 绑定 Human 类到接口
func (man *Human) Move() {
fmt.Printf("My name is %s, %d years old %s, I can walk run and swimming.\n",
man.name, man.age, man.gender)
}
func (man *Human) Speak() {
fmt.Printf("My name is %s, %d years old %s, I can speak Chinese and English.\n",
man.name, man.age, man.gender)
}
// 绑定 Fish 类到接口
func (fish *Fish) Move() {
fmt.Printf("My name is %s, I'm a %s, I can swimming fast under water.\n",
fish.name, fish.category)
}
func (fish *Fish) Speak() {
fmt.Printf("My name is %s, I'm a %s, I can speak Fishingish: gu gu gu\n",
fish.name, fish.category)
}
// 绑定 Bird 类到接口
func (bird *Bird) Move() {
fmt.Printf("My name is %s, I'm a %s, I can fly in the sky.\n",
bird.name, bird.category)
}
func (bird *Bird) Speak() {
fmt.Printf("My name is %s, I'm a %s, I can speak Birdish: ji jo ji jo\n",
bird.name, bird.category)
}
// 通用接口
func Introduce(living Creature) {
living.Move()
living.Speak()
}
8. 并发
三种退出方式对比:
- return 退出当前函数
- Exit 退出当前进程
- Goexit 退出当前 routine
package main
import (
"os"
"runtime"
)
func main() {
go func() {
runtime.Goexit()
}()
os.Exit(-1)
return
}
多 routine 间通信使用管道:
- 当缓冲区写满时,写阻塞,当被读取后,恢复写入
- 当读取完数据后,读阻塞,当再次有数据写入管道时,恢复读取
- 如果管道没有使用 make 分配空间,那默认为 nil,读写都会阻塞,即无法进行读写
- 对于一个管道,读写必须次数对等,否则若阻塞在主程序,则程序会崩溃;若阻塞在子 routine,那么会出现内存泄漏
package main
import (
"fmt"
"time"
)
func main() {
intChan := make(chan int, 5)
go func() {
for i:=0; i<10; i++ {
fmt.Println("routine 1 写入数据:", i)
intChan <- i * 5
}
}()
go func() {
for i:=10; i<20; i++ {
fmt.Println("routine 2 写入数据:", i)
intChan <- i * 5
}
}()
for i:=0; i<20; i++ {
data := <-intChan
fmt.Println("main 读取数据:", data)
time.Sleep(50 * time.Millisecond)
}
fmt.Println("main 结束!")
}
为了使对一个管道的读写次数对等,一般使用 for range 遍历管道数据:
- 使用 close 重复关闭同一个管道程序会崩溃
- 向已关闭的管道继续写入数据,程序会崩溃
- 从已关闭的管道读取数据,会返回零值,程序不会崩溃
- 关闭管道的操作应该在写入数据端,因为读取数据端不知道何时(即写入端还会写入数据吗)关闭管道
package main
import (
"fmt"
)
func main() {
intChan := make(chan int, 5)
go func() {
for i:=0; i<20; i++ {
fmt.Println("routine 写入数据:", i)
intChan <- i
}
close(intChan)
}()
// for range 遍历未关闭的管道不知道何时停止,会一直等待读取新写入的数据
// 在写入端 使用 close 关闭管道,for range 遍历完关闭的管道后即退出
for num := range intChan {
fmt.Println("main 读取数据:", num)
}
fmt.Println("main 结束!")
}
ok-idomnum, ok := <-intChan
package main
import (
"fmt"
)
func main() {
intChan := make(chan int, 5)
go func() {
for i:=0; i<20; i++ {
fmt.Println("routine 写入数据:", i)
intChan <- i
}
close(intChan)
}()
for {
num, ok := <-intChan
if !ok {
fmt.Println("已取完数据,管道已关闭,main 准备退出...")
break
}
fmt.Println("读取数据:", num)
}
fmt.Println("main 退出")
}
单向管道
之前一直定义的都是双向通道,既可以写入,又可以读取。Golang 还支持定义单向通道:
intChan := make(chan int, 5)intChanReadOnly := make(<-chan int, 5)intChanWriteOnly := make(chan<- int, 5)
单向管道一般用于函数参数,这样语义更明确:
package main
import (
"fmt"
"time"
)
func main(){
intChan := make(chan int, 5)
// 参数为单向只写通道
go producer(intChan)
// 参数为单向只读通道
go consumer(intChan)
time.Sleep(time.Second)
fmt.Println("main over!")
}
// 生产者向管道写入数据
func producer(in chan<- int) {
for i:=0; i<20;i++ {
fmt.Println("生产者写入:", i)
in <- i
}
}
// 消费者从管道取数据
func consumer(out <-chan int) {
for num := range out {
fmt.Println("消费者读取:", num)
}
}
监听通道
select case
package main
import (
"fmt"
"time"
)
func main() {
intChan1 := make(chan int, 5)
intChan2 := make(chan int, 5)
// 使用 select case 监视两个 channel
go func() {
for {
fmt.Println("监听通道...")
select {
case num1 := <-intChan1:
fmt.Println("读取到 intChan1 的数据:", num1)
case num2 := <-intChan2:
fmt.Println("读取到 intChan2 的数据:", num2)
default:
fmt.Println("两个管道都未读取到数据...:")
time.Sleep(2 * time.Second)
}
}
}()
// 向 intChan1 写数据
go func() {
for i:=0; i<10;i++ {
fmt.Println("intChan1 写入:", i)
intChan1 <- i
time.Sleep(500 * time.Millisecond)
}
}()
// 向 intChan2 写数据
go func() {
for i:=10; i<20;i++ {
fmt.Println("intChan2 写入:", i)
intChan2 <- i
time.Sleep(time.Second)
}
}()
for {
fmt.Println("main over!")
time.Sleep(5 * time.Second)
}
}