这篇笔记记录本人学习Go语言基础入门的一些收获和体会,我之前主要是写Java比较多,所以对于Go语言这个内嵌C类型的语言,需要认真过一遍基础,好好看书学一遍。
为何是Go?
优势
- 部署简单:直接编译为机器码,不依赖于其他库,直接运行即可部署
- 语言层面的并发:充分利用CPU多核
- 丰富标准库
- runtime调度机制
- 高效GC机制
- 跨平台
- 完善的工具链
- 有编译、代码格式化、包管理、代码补充提示、单元测试框架等工具。
- 大厂领军
Go强项:
- 云计算基础设施:docker、k8s、etcd、七牛云
- 基础后端软件:tidb、influxdb、cockroachdb
- 微服务:go-kit、micro、bilibili、monzo bank
- 互联网基础设施:以太坊、hyperledger
不足
- 包管理:大部分第三库托管在GitHub上,私人维护,代码稳定性有风险。
- 不支持泛型emmm
- 所有的异常都用Error来处理。无try-cache语法,写Java的不太习惯。
- 对C的降级处理并非无缝。没有C降级到asm那么完美(序列化问题)
Win11实战搭建Go开发环境
- 去官网下载Go的安装包,直接安装。
- 下载VsCode,在扩展那边输入go,安装第一个插件。
- 切换代理,否则Vscode安装工具会一直失败,当时在这步就卡住了很久。 命令行输入:
go env -w GOPROXY=https://goproxy.cn,direct,将GOPROXY换成七牛云的站点。(附:这里direct指的是如果找不到包,就到回源到GitHub下载) - 使用VsCode创建main.go,这个时候会提示要下载一堆工具,确定即可。下好的文件会在
C:\\User\\用户名\\go\\bin下。 - 下载Code Runner插件,可以直接运行Go程序。
编译运行命令:
go build -o main.exe main.go 编译,生成可执行程序
go run main.go 编译+运行
Go基础语法
Hello World
package main
import (
"fmt"
"time"
)
func main() { //语法:
fmt.Println("hello Go!")
time.Sleep(1*time.Second)
}
package:表示程序的包名,如果两个.go文件都是main包,那么他们就无需互相导包,编译的时候会在一起编译。
值得注意的是,Go语法要求左花括号和main同行,我直接狂喜!
即func abc(){ 正确,,但是func abc() 换行{ 错误。
变量声明
- 局部变量支持四种方法。
var a intvar b int = 100- 自动推断:
var c = 10 - 省略var:
e:= 100
- 全局变量不支持
:=。 - 多变量声明:
var xx,yy = 100,"张三"
const那些事儿
- const()可以定义枚举,枚举中每行会有一个叫iota的值, 每行的iota都会累加1,第一行的iota默认是0
- iota只能够配合const符号一起使用。平时是不能使用的。
- 奇怪的语法增加了:下面的每一行会遵循上面行的公式
const(
BEIJING = 10*iota // 0
SHANGHAI // iota = 1 SHANGHAI=10
SHANTOU // iota = 2 SHANTOU=20
)
const(
a,b = iota+1,iota+2 // iota = 0 a = 1 b= 2
c,d // iota = 1 c = 2 d = 3
e,f // iota = 2 e = 3 f = 4
g,h = iota * 2, iota * 3 // iota = 3 g = 6 h=9
i,k // iota=4 i = 8 k = 12
)
函数
- 函数定义形式:func 函数名(参数)(返回值参数){}
- 返回值:
- 支持多个返回值
- 返回值可以匿名
- 可以给有名字的返回值变量赋值
- 返回值参数是一个局部变量!
//值返回
func fool(a string, b int) int {
c:= 100
return c
}
//返回值匿名
func fool2(a string, b int)(int,int){
return 666,777
}
//返回值具有参数名称
func fool3(a string, b int)(r1 int,r2 int){
//给有名称的返回值变量赋值
r1 = 1024
r2 = 2048
return
}
//返回值具有参数名称
func fool4(a string, b int)(r1,r2 int){
//形参是局部变量,返回值的形参也是局部变量。作用域是整个函数内。
fmt.Println(r1) // 0
fmt.Println(r2) // 0
//给有名称的返回值变量赋值
r1 = 4096
r2 = 10086
return
}
init函数和import导包
init函数优先调用于main,我的理解是类似于Java的静态代码块。
导包:
- 导了包必须使用,否则会报错。
import _ "lib1"表示这个包导入但是不使用,匿名别名。import . "lib2"表示可以不通过.的方式调用,而是直接调用。import mylib2 "lib2"别名,包长可以用
defer语句
- defer 会在函数执行结束后执行。
- 多个def的执行顺序:以堆栈的形式,后进先出。
- 先调用return,然后调用defer。
切片——动态数组
- 普通数组根据不同的长度,打印类型是不同的。这就引出了函数传参的问题。
- 奇怪的语法:Golang的普通数组给函数传递,是值传递!
切片的使用:
- 初始化:
slice1 := []int{1,2,3}- 声明但是不分配空间,
var slice2 []int slice2 = make([]int, 3)var nums = make([]int,3,5) //长度3,容量5
- 长度:
len(c),容量:cap(c)- slice有尾指针,指向长度的最后一个。即使后面还有容量,但是访问是不合法的!
- 追加元素:
nums = append(nums, 1)- cap满了会自动扩容。
- 截取元素:左闭右开。
fmt.Println(nums[0:2])fmt.Println(nums[4:])
- 拷贝切片,底层指向的数组是一样的。改变一个会修改另外一个。
s1:=nums[0:2] - copy可以讲底层的slice数组一起拷贝.
copy(s2,nums)
Map
- 声明:
var myMap1 map[string]string表示key为string,value也是stringmyMap1 = make(map[string]string,10)开辟空间myMap2 :=make(map[int]string)myMap3 :=map[string]string{ "first": "java", "second" : "c++", "third" : "python", }
- 增加值:
myMap1["one"] = "java"
- 删除:
delete(cityMap,"USA")
- 遍历:
for key,value :=range cityMap{
fmt.Println("key = " ,key)
fmt.Println("value = " ,value)
}
OOP
结构体:
type Human struct{
name string
sex string
}
func (this *Human) Eat(){
fmt.Println("Human.eat() 被执行")
}
//main中
hero := Hero{
Name: "张三",
Ad: 100,
Level: 1}
hero.Show()
- 封装:
- 成员函数的编写方式,是在函数名前加大括号写类型。有指针和不是指针两种。
- 类名首字母大写,表示其他包能访问。
- 属性首字母大写,表示属性可以对外访问。
type Human struct{
name string
sex string
}
func (this *Human) Eat(){
fmt.Println("Human.eat() 被执行")
}
func (this *Human) Walk(){
fmt.Println("Human.Walk() 被执行")
}
type SuperMan struct{
Human // SuperMan继承了Human
level int
}
//重定义父类的方法Eat
func (this *SuperMan) Eat(){
fmt.Println("SuperMan.Eat() 被执行")
}
//子类定义的新方法
func (this *SuperMan) Fly(){
fmt.Println("SuperMan.Fly() 被执行")
}
func main(){
h:= Human{"Ajax","male"}
h.Eat()
h.Walk()
//定义子对象
s:=SuperMan{Human{"Sonia","female"},18}
s.Walk() // 调用父类的方法
s.Eat() //子类重写的方法
s.Fly() // 子类的独有方法
}
- 继承:使用方式是在结构体声明第一行加上父类类名。
type AnimalIF interface {
Sleep()
GetColor() string
GetType() string
}
//具体的类
type Cat struct{
color string
}
func (this *Cat) Sleep(){
fmt.Println("Cat is Sleeping.")
}
func (this *Cat)GetType()string{
return "Cat"
}
func (this *Cat)GetColor()string{
return this.color
}
//多态
func showAnimal(animal AnimalIF){
animal.Sleep()
fmt.Println("color = ",animal.GetColor())
fmt.Println("type = ",animal.GetType())
fmt.Println("===== ===== ===== =====")
}
func main(){
var animal AnimalIF //接口数据类型,父类指针
animal = &Cat{"Green"} // 绑定子类
animal.Sleep() // 调用Cat的方法
cat:=Cat{"Green"}
showAnimal(&cat)
}
- 多态:
- 定义接口:
type AnimalIF interface {- 接口本质是个指针
- 类必须实现全部接口方法,否则指针无法正确指向。
- 多态基本要素:有一个父类接口,由子类实现父类全部接口。
- 父类类型的变量指向子类的具体方法。
- 定义接口:
interface{}是万能类型
- interface{}是通用万能类型。
- int string float32 float64 struct ...都实现了interface
- 可以用interface{}类型引用任意的数据类型。
- 区分引用的底层数据类型——类型断言机制
value,ok := arg.(string) //判断arg是否是string类型
pair
- go语言中变量的结构分为type和value。其中type由分为static type和concrete type。
- 无论怎么赋值,pair的底层不变
package main
import "fmt"
type Reader interface{
ReadBook()
}
type Writer interface{
WriteBook()
}
type Book struct{
}
func (this *Book) ReadBook(){
fmt.Println("Read a Book")
}
func (this *Book) WriteBook(){
fmt.Println("Write a Book")
}
func main(){
//b : pair<type:Book, value:book{}地址>
b := &Book{}
// r: pair<type, value:>
var r Reader
//r : pair<type: Book , value:book{}地址>
r = b
r.ReadBook()
var w Writer
//w : pair<type :Book, value: book{}地址 >
w = r.(Writer) //此处断言成功:w和r具体的type是一致的。
w.WriteBook()
}
反射reflect
反射允许我们看出一个变量的数据类型。
- 接口1:ValueOf - 获取输入参数接口中数据的值,如果接口为空返回0
- 接口2:TypeOf - 动态获取输入参数接口中值的类型,如果接口为空返回nil
这里给出使用反射的例子,具体的要看官方文档:
- 反射获取type:
inputType := reflect.TypeOf(arg) - 反射获取value
inputValue := reflect.ValueOf(arg) - 反射获取字段:
- 获取reflect.Type,得到NumField,进行遍历.
field := inputType.Field(i) - 得到每个field,数据类型 。field.Type field.Name
- 通过field有一个Interface()方法得到对应的value
value := inputValue.Field(i).Interface()
- 获取reflect.Type,得到NumField,进行遍历.
- 反射获取方法:
method := inputType.Method(i)method.Name\method.Type
Golang反射解析结构体标签Tag
- 标签的格式:key:value
- 作用:属性在不同包中的用法可以不同。常见用JSON、ORM。
type resume struct{
Name string `info:"name" doc:"我的名字"`
Sex string `info:"sex" doc:"性别"`
}
func findTag(str interface{}){
tag := reflect.TypeOf(str).Elem()
for i:=0;i<tag.NumField();i++{
taginfo := tag.Field(i).Tag.Get("info")
tagdoc := tag.Field(i).Tag.Get("doc")
fmt.Println("info : ",taginfo)
fmt.Println("doc : ",tagdoc)
}
}
json案例:
- 导包:encoding/json
type Movie struct{
Title string `json:"title"`
Year int `json:"year"`
Price int `json:"rmb"`
Actors []string `json:"actors"`
}
func main(){
movie :=Movie{"我是电影",2004,8,[]string{"Ajax","Cagur"}}
//encoing: 结构体-->json
jsonStr,err := json.Marshal(movie)
if err!=nil{
fmt.Println("json marshal error",err)
return
}
//Json格式的字符串
fmt.Printf("jsonstr = %s\n",jsonStr)
//decoding : 将json --> 结构体
my_movie :=Movie{}
err = json.Unmarshal(jsonStr,&my_movie)
if err!=nil{
fmt.Println("json unmarshal error",err)
}
fmt.Println(my_movie)
}