Golang 快速入门
Golang 语言特性
Golang 的优势
极简单的部署方式:可直接编译成机器码、不依赖其他库、直接运行即可部署。
静态类型语言,编译的时候可以检查出大多数问题。
语言层面的并发:天生的基因支持、充分的利用多核
// Go 语言实现并发的代码
func goFunc(i int) {
fmt.Println("goroutine ", i, " ...")
}
func main() {
for i := 0; i < 1000; i++ {
go goFunc(i) // 开启一个并法协程
}
time.Sleep(time.Second)
}
强大的标准库:runtime 系统调度机制、高效的 CG 垃圾回收、丰富的标准库
“大厂” 领军:Google、facebook、Tencent、Baidu、七牛、字节 …
Golang 的应用场景
1、云计算基础设施领域:
代表项目:docker、kubernetes、etcd、consul、cloud flare CDN、七牛云存储 等。
2、基础后端软件:
代表项目:tidb、influxdb、 cockroach 等。
3、微服务
代表项目:go-kit、 micro、 monzo bank 的 typhon、bilibili 等。
4、互联网基础设施
代表项目:以太坊、hyperledger 等。
Golang 明星作品:Docker、Kubernetes
Golang 的不足
1、包管理,大部分包都托管在 Github 上。
2、无泛化类型。
3、所有 Exception 都用 Error 来处理(有争议)。
4、对 C 的降级处理,并非无缝,没有 C 降级到 asm 那么完美。(序列化问题)
基础语法
main
package main
// 导多个包
import (
"fmt"
"time"
)
// Golang 有无 ; 都可,建议不加
func main() { // 函数的 { 必须和函数名同行, 否则编译报错
fmt.Println("Hello Go!")
time.Sleep(1 * time.Second)
}
变量
局部变量的声明:
// 方法一:声明一个变量 默认的值是0
var a int
// 方法二:声明一个变量,初始化一个值
var b int = 100
// 方法三:初始化的时候, 省去数据类型,通过值自动匹配当前的变量的数据类型
var c = 100
// 方法四:(常用) 省去var关键字,直接自动匹配
d := 100
全局变量的声明:以上只有方法四不支持(编译会报错)
多变量的声明:
// 单行写法
var xx, yy int = 100, 200
var kk, ll = 100, "Aceld"
// 多行写法
var (
vv int = 100
jj bool = true
)
常量与 iota
使用 const 定义常量,常量是只读的,不允许修改。
const a int = 10
const (
a = 10
b = 20
)
const 可以用来定义枚举:
const {
BEIJING = 0
SHANGHAI = 1
SHENZHEN = 3
}
const 可以和 iota 一起使用来定义有规则的枚举:
const (
// 可以在const() 添加一个关键字 iota, 每行的iota都会累加1, 第一行的iota的默认值是0
BEIJING = iota // iota = 0
SHANGHAI // iota = 1
SHENZHEN // iota = 2
)
const (
a, b = iota+1, iota+2 // iota = 0, a = iota + 1, b = iota + 2, a = 1, b = 2
c, d // iota = 1, c = iota + 1, d = iota + 2, c = 2, d = 3
e, f // iota = 2, e = iota + 1, f = iota + 2, e = 3, f = 4
g, h = iota * 2, iota *3 // iota = 3, g = iota * 2, h = iota * 3, g = 6, h = 9
i, k // iota = 4, i = iota * 2, k = iota * 3 , i = 8, k = 12
)
函数
多返回值
单返回值的函数:
func foo1(a string, b int) int {
return 100
}
多返回值的函数:
// 返回多个返回值,匿名的
func foo2(a string, b int) (int, int) {
return 666, 777
}
// 返回多个返回值,有形参名称的
func foo3(a string, b int) (r1 int, r2 int) {
// r1 r2 属于foo3的形参,初始化默认的值是0
// r1 r2 作用域空间 是foo3 整个函数体的{}空间
fmt.Println("r1 = ", r1) // 0
fmt.Println("r2 = ", r2) // 0
// 给有名称的返回值变量赋值
r1 = 1000
r2 = 2000
return
}
func foo4(a string, b int) (r1, r2 int) {
// 给有名称的返回值变量赋值
r1 = 1000
r2 = 2000
return
}
init 函数
每个 go 程序都会在一开始执行 init() 函数,可以用来做一些初始化操作:
package main
import "fmt"
func init() {
fmt.Println("init...")
}
func main() {
fmt.Println("hello world!")
}
init...
hello world!
如果一个程序依赖了多个包,它的执行流程如下图:
制作包的时候,项目路径如下:
$GOPATH/GolangStudy/5-init/
├── lib1/
│ └── lib1.go
├── lib2/
│ └── lib2.go
└── main.go
lib1 .init() ...
lib2 .init() ...
lib1Test()
lib2Test()
import 导包
-
import _ "fmt"
给 fmt 包一个匿名, ⽆法使用当前包的⽅法,但是会执行当前的包内部的 init() 方法
-
import aa "fmt"
给 fmt 包起一个别名 aa,可以用别名直接调用:
aa.Println()
-
import . "fmt"
将 fmt 包中的全部方法,导入到当前包的作用域中,全部方法可以直接调用,无需
fmt.API
的形式
指针
经典:在函数中交换两数的值
func swap(pa *int, pb *int) {
var temp int
temp = *pa
*pa = *pb
*pb = temp
}
func main() {
var a, b int = 10, 20
swap(&a, &b) // 传地址
fmt.Println("a = ", a, " b = ", b)
}
defer
defer 声明的语句会在当前函数执行完之后调用:
func main() {
defer fmt.Println("main end")
fmt.Println("main::hello go ")
}
main::hello go main end
如果有多个 defer,依次入栈,函数返回后依次出栈执行:
上图执行顺序:func3() -> func2() -> func1()
关于 defer 和 return 谁先谁后:
func deferFunc() int { fmt.Println("defer func called...") return 0}func returnFunc() int { fmt.Println("return func called...") return 0}func returnAndDefer() int { defer deferFunc() return returnFunc()}func main() { returnAndDefer()}
return func called...defer func called...
结论:return 之后的语句先执⾏,defer 后的语句后执⾏。
切片 slice
数组
声明数组的方式:(固定长度的数组)
var array1 [10]intarray2 := [10]int{1,2,3,4}array3 := [4]int{1,2,3,4}
数组的长度是固定的,并且在传参的时候,严格匹配数组类型
// 传入参数的数组长度为4,则只能传递长度为4的数组func printArray(myArray [4]int) { fmt.Println(myArray) // [1 2 3 4] myArray[0] = 666 // 数组是值传递}func main() { myArray := [4]int{1, 2, 3, 4} printArray(myArray) fmt.Println(myArray) // [1 2 3 4]}
声明动态数组和声明数组一样,只是不用写长度。
// 不指定长度则是动态数组func printArray(myArray []int) { fmt.Println(myArray) // [1 2 3 4] myArray[0] = 10 // 动态数组是引用传递}func main() { myArray := []int{1, 2, 3, 4} printArray(myArray) fmt.Println(myArray) // [1 2 3 4]}
slice
slice 的声明方式:通过 make 关键字
// 1 声明一个切片,并且初始化,默认值是1,2,3,长度是3slice1 := []int{1, 2, 3} // [1 2 3]// 2 声明一个切片,但是没有给它分配空间var slice2 []int // slice2 == nil// 开辟3个空间,默认值是0slice2 = make([]int, 3) // [0 0 0]// 3 声明一个切片,同时给slice分配3个空间,默认值是0var slice3 []int = make([]int, 3) // [0 0 0]// 4 声明一个切片,同时给slice分配3个空间,默认值是0,通过:=推导出slice是一个切片slice4 := make([]int, 3) // [0 0 0]
len() 和 cap() 函数:
- len:长度,表示左指针⾄右指针之间的距离。
- cap:容量,表示指针至底层数组末尾的距离。
切⽚的扩容机制,append 的时候,如果长度增加后超过容量,则将容量增加 2 倍。
var numbers = make([]int, 3, 5)fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)// 向numbers切片追加一个元素1, len = 4, [0,0,0,1], cap = 5numbers = append(numbers, 1)fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)// 向numbers切片追加一个元素2, len = 5, [0,0,0,1,2], cap = 5numbers = append(numbers, 2)fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)// 向一个容量cap已经满的slice 追加元素, len = 6, cap = 10numbers = append(numbers, 3)fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)
len = 3, cap = 5, slice = [0 0 0]len = 4, cap = 5, slice = [0 0 0 1]len = 5, cap = 5, slice = [0 0 0 1 2]len = 6, cap = 10, slice = [0 0 0 1 2 3]
slice 操作
可以通过设置下限以及上限设置截取切片 [lower-bound: upper-bound]
,实例:
func main() { /* 创建切片 */ numbers := []int{0, 1, 2, 3, 4, 5, 6, 7, 8} fmt.Println(numbers) /* 打印原始切片 */ fmt.Println("number ==", numbers) /* 打印子切片从索引1(包含)到索引4(不包含) */ fmt.Println("numbers[1:4] ==", numbers[1:4]) /* 默认下限为 0 */ fmt.Println("numbers[:3] ==", numbers[:3]) /* 默认上限为 len(s) */ fmt.Println("numbers[4:] ==", numbers[4:]) numbers1 := make([]int, 0, 5) fmt.Println(numbers1) /* 打印子切片从索引 0(包含) 到索引 2(不包含) */ numbers2 := numbers[:2] fmt.Println(numbers2) /* 打印子切片从索引 2(包含) 到索引 5(不包含) */ numbers3 := numbers[2:5] fmt.Println(numbers3)}
[0 1 2 3 4 5 6 7 8]number == [0 1 2 3 4 5 6 7 8]numbers[1:4] == [1 2 3]numbers[:3] == [0 1 2]numbers[4:] == [4 5 6 7 8][][0 1][2 3 4]
利用 copy 函数拷贝切片,是深拷贝。
slice1 := []int{1, 2, 3}slice2 := make([]int, 3)copy(slice2, slice1)slice2[0] = 10fmt.Println(slice1) // [1 2 3]
直接赋值是切片,是浅拷贝。
slice1 := []int{1, 2, 3}slice2 := slice1slice2[0] = 10fmt.Println(slice1) // [10 2 3]
map
map 的声明
map 的第一种声明方式:
// 声明myMap1是一种map类型 key是string,value是stringvar myMap1 map[string]stringfmt.Println(myMap1 == nil) // true// 使用map前,需要先用make给map分配数据空间myMap1 = make(map[string]string, 10)myMap1["one"] = "java"myMap1["two"] = "c++"myMap1["three"] = "python"fmt.Println(myMap1)// map[one:java three:python two:c++]
map 的第二种声明方式:
myMap2 := make(map[int]string)myMap2[1] = "java"myMap2[2] = "c++"myMap2[3] = "python"fmt.Println(myMap2)// map[1:java 2:c++ 3:python]
map 的第三种声明方式:
myMap3 := map[string]string { "one": "php", "two": "c++", "three": "python",}fmt.Println(myMap3)// map[one:java three:python two:c++]
map 的使用
func printMap(cityMap map[string]string) {
for key, value := range cityMap {
fmt.Println("key = ", key+", value = ", value)
}
}
func AddValue(cityMap map[string]string) {
// map 是引用传递
cityMap["England"] = "London"
}
func main() {
cityMap := make(map[string]string)
// 添加
cityMap["China"] = "Beijing"
cityMap["Japan"] = "Tokyo"
cityMap["USA"] = "NewYork"
// 删除
delete(cityMap, "China")
// 遍历
printMap(cityMap)
fmt.Println("-------")
// 修改
cityMap["USA"] = "DC"
// 利用函数添加 - map 是引用传递
AddValue(cityMap)
// 遍历
printMap(cityMap)
}
key = Japan, value = Tokyo
key = USA, value = NewYork
-------
key = England, value = London
key = Japan, value = Tokyo
key = USA, value = DC
面向对象编程
利用 type 可以声明某个类型的别名(理解为声明一种新的数据类型)
type myint int
func main() {
var a myint = 10
fmt.Println("a = ", a)
fmt.Printf("type of a = %T\n", a)
}
a = 10
type of a = main.myint
struct
func changeBook(book Book) {
// 传递一个book的副本
book.price = "666"
}
func changeBook2(book *Book) {
// 指针传递
book.price = "777"
}
func main() {
var book Book
book.title = "Golang"
book.price = "111"
fmt.Printf("%v\n", book) // {Golang 111}
changeBook(book)
fmt.Printf("%v\n", book) // {Golang 111}
changeBook2(&book)
fmt.Printf("%v\n", book) // {Golang 777}
}
封装
Golang 中,类名、属性名、⽅法名 首字⺟大写 表示对外(其他包)可以访问,否则只能够在本包内访问。
// 如果类名首字母大写,表示其他包也能够访问
type Hero struct {
// 如果类的属性首字母大写, 表示该属性是对外能够访问的,否则的话只能够类的内部访问
Name string
Ad int
level int // 只能本包访问
}
func (h *Hero) Show() {
fmt.Println("Name = ", h.Name)
fmt.Println("Ad = ", h.Ad)
fmt.Println("Level = ", h.level)
fmt.Println("---------")
}
func (h *Hero) GetName() string {
return h.Name
}
// 不用指针则传递的是副本,无法赋值
func (h *Hero) SetName(newName string) {
h.Name = newName
}
func main() {
hero := Hero{Name: "zhang3", Ad: 100}
hero.Show()
hero.SetName("li4")
hero.Show()
}
Name = zhang3
Ad = 100
Level = 0
---------
Name = li4
Ad = 100
Level = 0
---------
继承
父类:
type Human struct {
name string
sex string
}
func (h *Human) Eat() {
fmt.Println("Human.Eat()...")
}
func (h *Human) Walk() {
fmt.Println("Human.Walk()...")
}
子类:
type SuperMan struct {
Human // SuperMan类继承了Human类的方法
level int
}
// 重定义父类的方法Eat()
func (s *SuperMan) Eat() {
fmt.Println("SuperMan.Eat()...")
}
// 子类的新方法
func (s *SuperMan) Fly() {
fmt.Println("SuperMan.Fly()...")
}
定义子类:
// 定义一个子类对象
// s := SuperMan{Human{"li4", "female"}, 88}
var s SuperMan
s.name = "li4"
s.sex = "male"
s.level = 88
s.Walk() // 父类的方法
s.Eat() // 子类的方法
s.Fly() // 子类的方法
s.Print()
Human.Walk()...
SuperMan.Eat()...
SuperMan.Fly()...
name = li4
sex = male
level = 88
多态
Golang 中多态的基本要素:
- 有一个父类(有接口)
// 本质是一个指针
type AnimalIF interface {
Sleep()
GetColor() string // 获取动物的颜色
GetType() string // 获取动物的种类
}
- 有子类(实现了父类的全部接口)
// 具体的类
type Cat struct {
color string // 猫的颜色
}
func (c *Cat) Sleep() {
fmt.Println("Cat is Sleep")
}
func (c *Cat) GetColor() string {
return this.color
}
func (c *Cat) GetType() string {
return "Cat"
}
- 父类类型的变量(指针)指向(引用)子类的具体数据变量
// 接口的数据类型,父类指针
var animal AnimalIF
animal = &Cat{"Green"}
animal.Sleep() // 调用的就是Cat的Sleep()方法, 多态
通用万能类型
interface{}
表示空接口,可以用它引用任意类型的数据类型。
// interface{}是万能数据类型
func myFunc(arg interface{}) {
fmt.Println(arg)
}
type Book struct {
auth string
}
func main() {
book := Book{"Golang"}
myFunc(book)
myFunc(100)
myFunc("abc")
myFunc(3.14)
}
Golang 给 interface{}
提供类型断言机制,用来区分此时引用的类型:
func myFunc(arg interface{}) {
// 类型断言
value, ok := arg.(string)
if !ok {
fmt.Println("arg is not string type")
} else {
fmt.Println("arg is string type, value = ", value)
fmt.Printf("value type is %T\n", value)
}
}