go语言学习
go语言常见包
声明变量方式
// 声明变量格式:var 变量名 变量类型
// 批量声明
var (
a int
b string
c []float32
d func() bool
e struct{
x int
}
)
多重复制
// 常见的交换两个值。在排序算法很常见
func main() {
var a = 100
var b = 20
a,b=b,a
print(a,b)
}
匿名变量
// 匿名变量,匿名变量不占用命名空间,不会分配内存。匿名变量与匿名变量之间也不会因为多次声明而无法使用。
func GetData() (int,int) {
return 100,2000
}
func main() {
var a = 1
_,a=GetData()
print(a
package main
import "fmt"
// 注意点1: package与 func 要一致
// 注意点2:func main 后面的括号要在同一行
func main() {
var a int
//1- 变量声明标准方式
var name string ="ddd"
//2- 编译器推导方式
var age=190
// 3- 更简洁方式。注意 海象符号不能够声明全局变量。否则抛出异常,并且编译也编译不过去
weight := 13.3
fmt.Println("age =",age)
fmt.Println("weight =",weight)
fmt.Println("hello go",name)
fmt.Println(a)
// 声明多个变量(同一种类型)
var x,y =100,22
print("x=",x,",y=", y)
// 声明多个变量(不同种类型)
var (
bol =false
num =1222
)
print("bol=",bol,",num=", num)
}
变量的生命周期
编译器觉得变量应该分配在堆和栈上的原则是:
● 变量是否被取地址。
● 变量是否发生逃逸。
// 声明空结构体测试结构体逃逸情况
type Data struct {
}
func dummy() *Data {
// 通过 go run -gcflags "-m -l" HeapAnalysis.go 分析,可以看到 编译器将c变量的地址,放到了堆中。这句话表示,Go编译器已经确认如果将c变量分配在栈上是无法保证程序最终结果的。如果坚持这样做,dummy()的返回值将是Data结构的一个不可预知的内存地址。这种情况一般是C/C++语言中容易犯错的地方:引用了一个函数局部变量的地址。
var c Data
// 返回函数局部变量地址
return &c
}
func main() {
fmt.Println(dummy())
}
常量与枚举:const与iota
//const 与iota可以花里胡哨
// 注意:iota必须和const一起
const (
a,b = iota+1,iota+2
c,d // iota =1.依次递增
e,f
g,h=iota*2,iota*3
m,n
)
func main() {
// const 关键字作用一:定义常量
const legth = 110
print("length=",legth)
print("Spring=",SPTING)
}
字符串
获取字符串长度
●ASCII字符串长度使用len()函数。
● Unicode字符串长度使用utf8.RuneCountInString函数。go默认是使用UTF-8编码,一个中文字符占3个
// 1-获取字符串长度
func main() {
// len函数表示字符串的ASCII编码字符个数或者字节长度
str :="dddddddd"
fmt.Println(len(str))
strC :="中国"
// 或者使用
fmt.Printf("个数:%d\n",utf8.RuneCountInString("中国"))
// 下面打印是6,这是由于go使用的是UTF-8 格式保存,每个中文占用3个字节
fmt.Println(len(strC))
字符串遍历
总结:
● ASCII字符串遍历直接使用下标。(若有中文出现乱码,fori格式)
● Unicode字符串遍历用for range。
strC :="中国"
for _, v := range strC {
fmt.Printf("%c",v)
}
字符串搜索和截取
//● strings.Index:正向搜索子字符串。start := strings.Index(str,"d")
//● strings.LastIndex:反向搜索子字符串。lastIndex := strings.LastIndex(str,"d")
//● 搜索的起始位置可以通过切片偏移制作。str[3:] 类似java的 subString()
函数与多返回值
// 关键字 函数名 形参 返回值{函数体}
func funName(param1 type,param2 type) [(int,int)]{
// 函数体
}
// 函数与多返回值
func fun(a int, name string) {
println("a=",a,",name=",name)
}
func fun2(a int, name string) (int,int) {
println("-----fun2------")
println("a=",a,",name=",name)
return 100,200
}
func fun3(a int, name string) (re1 int ,ret2 int) {
println("-----fun2------")
println("a=",a,",name=",name)
re1=100
ret2=2000
return
}
func fun4(a int, name string) (re1 ,ret2 int) {
println("-----fun2------")
println("a=",a,",name=",name)
re1=100
ret2=2000
return
}
import与init
这个有点类似,java的多重继承。也是先初始化父类的常量,代码块,然后最后才是当前类。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iaehCK0E-1630501774968)(D:\root\mdPic\image-20210831204942388.png)]
演示一下
目录结构
---src
main.go
---lib1
lib1.go
---lib2
lib2.go
package main
import "5-init/lib1"
import "5-init/lib2"
func main() {
lib1.Lib1Test()
lib2.Lib2Test()
}
package lib1
// todo 若 方法首字母小写,代表此方法是私有方法
func Lib1Test() {
println("lib1.Lib2Test()......")
}
func init() {
println("lib1.init()......")
}
package lib2
// todo 若 方法首字母小写,代表此方法是私有方法
func Lib2Test() {
println("lib2.Lib2Test()......")
}
func init() {
println("lib2.init()......")
}
输出
lib1.init()......
lib2.init()......
lib1.Lib2Test()......
lib2.Lib2Test()......
注意点:
1-导入包,但是不使用,会报错。但是golang IDE,会自动删除
2-支持匿名:
import (
_ "5-init/lib1"
"5-init/lib2"
// 你不使用,这个包的init方法会被执行
)
3-支持给包起别名
import (
_ "5-init/lib1"
myLib2 "5-init/lib2"
)
指针
● 类型指针,允许对这个指针类型的数据进行修改。传递数据使用指针,而无须拷贝数据。类型指针不能进行偏移和运算。
● 切片,由指向起始元素的原始指针、元素数量和容量组成。
func swap(a int,b int) {
var temp int
temp=a
a=b
b=temp
}
func swap2(a *int, b *int) {
var temp int
temp =*a
*a=*b
*b=temp
}
func main() {
var a ,b= 10,20
// 值传递
//swap(a,b)
// 指针(引用传递),c好像还分二级指针(本质上就是套娃)
swap2(&a,&b)
println("a=",a,",b=",b)
// 二级指针(也可以多级指针)
var p = &a
var pp **int= &p
println("p的地址=",p)
println("pp地址=",pp)
}
defer关键字
作用:让方法结束之后,调用之,类似于finally以及aop的后置通知
func A() {
println("A")
}
func B() {
println("B")
}
func C() {
println("C")
}
func main() {
// 这里涉及到 函数出入栈的问题
defer A()
defer B()
defer C()
println("main() ....end...")
}
结果
main() ....end...
C
B
A
defer 与return一起使用。
func deferCall() {
println("deferCall-----")
}
func returnCall() int {
println("returnCall........")
return 0
}
func test() int {
defer deferCall()
return returnCall()
}
func main() {
test()
}
输出
returnCall…
deferCall-----
数组
数组定义与遍历
// 注意点1:传参类型必须是:[10]int。
func testArray(test [10]int) {
// 此种for循环,出参是 index key。但是若定义了index就必须使用,可通过匿名变量的方式。定义了,但是又没有完全定义。tmd
for _,value := range test {
println("value=",value)
}
// 这里还是值传递。和java一样。java里数组属于引用类型了。
// 这行并不会真正生效
test[7]=10000
}
func main() {
var ints [10]int
// 数组定义方式二
intArr :=[10]int{1111,2222,777}
//还可以这样定义
// names :=[]string{"ssss","eeeeeeee"}
testArray(intArr)
// 数组遍历方式一
for i := 0; i < len(ints); i++ {
println(ints[i])
}
// 数组遍历方式二
for index,value := range ints {
println("index=",index,",value=",value)
}
}
切片(动态数组)
func testArray(test []int) {
// 引用传递
for _,value := range test {
println("value=",value)
}
// 修改生效
test[2]=10000
}
func main() {
// 动态数组,切片
intArr :=[]int{1111,2222,777,444,5555,666,777}
testArray(intArr)
println("修改后的值=",intArr[2])
}
输出
value= 1111
value= 2222
value= 777
value= 444
value= 5555
value= 666
value= 777
修改后的值= 10000
声明方式
func main() {
//方式一:声明并初始化
intArr := []int {1,2,3,56,33}
fmt.Printf("len=%d,slice=%v\n",len(intArr),intArr)
// 方式二:注意:此时只是声明并没有分配内存,此时访问,将抛出越界。
var ints [] int
// 此时直接访问,ide会高亮,提示。
//ints[1]=11
fmt.Printf("len=%d,slice=%v\n\n", len(ints), ints)
var ageArr [] int
// 分配空间
ageArr=make([]int,3)
ageArr[2]=10
fmt.Printf("len=%d,slice=%v\n",len(ageArr),ageArr)
// 第三种方式
nameArr :=make([]string,3);
nameArr[0]="dddd"
fmt.Printf("len=%d,slice=%v\n",len(nameArr),nameArr)
}
切片操作
动态扩容
func main() {
// 演示动态数组。len和cap的概念。len指的是数组有效数据的长度,cap是给你数组分配了多少位置
nameArr :=make([]string,3,6);
nameArr[0]="dddd"
nameArr[1]="第二"
// 追加,也就是下标为3的元素。java数组出来就是容量就不会变了。但是go,动态数组,容量会自动扩容。
nameArr = append(nameArr, "lalaaa")
fmt.Printf(nameArr[2])
fmt.Printf("len=%d,cap=%d,slice=%v\n",len(nameArr),cap(nameArr),nameArr)
for index, va := range nameArr {
fmt.Printf("index=%d,value=%s\n",index,va)
}
}
截取
有很多方式进行截取,以及深拷贝
func main() {
nameArr :=make([]string,4,6);
nameArr[0]="dddd"
nameArr[1]="第二"
nameArr[2]="第3"
nameArr[3]="第4"
// 这里slice是个浅拷贝,slice与nameArr 指向一个地方
//slice := nameArr[:];
// 同样是浅copy,对slice2修改,影响到nameArr。
slice2 := nameArr[0:2];
slice2[0]="修改"
// 有点像字符串的截取,不同方式
//slice3 :=nameArr[:4]
//slice3 :=nameArr[2:]
// todo 深copy
deepSlice :=make([]string,len(nameArr),cap(nameArr)*2)
copy(deepSlice,nameArr)
fmt.Printf("len=%d,cap=%d,slice=%v\n",len(deepSlice),cap(deepSlice),deepSlice)
//for i, value := range nameArr {
// println("index=",i,",value=",value)
//}
}
map类型
声明方式
func main() {
// 方式一:只是声明,并没有初始化
var hashMap map[int] string
if hashMap==nil {
println("没有初始化")
}
hashMap =make(map[int]string,3)
hashMap[1]="java"
hashMap[2]="c++"
// 方式二
hashMap2 :=make(map[string]string,3)
hashMap2["one"]="java"
hashMap2["two"]="c"
// 方式三
hashMap3 :=map[string]string{
"1":"java",
"2":"c",
"3":"c++",
}
fmt.Printf("map=%v\n",hashMap3)
}
基本操作
func printMap(mat map[string]string ) {
for k,v:= range mat {
println("key=",k,",value=",v)
}
}
func main() {
hashMap:=map[string]string{
"1":"java",
"2":"c",
"3":"c++",
}
// 遍历
printMap(hashMap)
// 删除
delete(hashMap,"1")
// 修改
hashMap["2"]="java"
println("----------------")
printMap(hashMap)
}
struct定义与使用
type Person struct {
name string
age int
}
func changeValue(per Person) {
per.name="修改值"
}
func pChangeValue(p *Person) {
p.name="入参是指针修改值"
}
func main() {
var person Person
person.name="szh"
person.age=111
fmt.Printf("person=%v\n",person)
// 修改值(竟然是值传递)
changeValue(person)
println("---------修改值后打印-------")
fmt.Printf("person=%v\n",person)
// 需要传指针
pChangeValue(&person);
println("---------指针修改值后打印-------")
fmt.Printf("person=%v\n",person)
}
面向对象
封装
// 类名若是大写,表其他模块在导入的时候,可以使用,否则表私有
type Hero struct {
// 若属性名首字母大写,表public,小写表私有
Name string
Ad int
level int
}
func (th *Hero) Show() {
fmt.Println("name=", th.Name)
fmt.Println("Ad=", th.Ad)
fmt.Println("level=", th.level)
}
func (th *Hero) GetName() string {
return th.Name
}
func (th *Hero)SetName(newName string) {
// th 是调用者该方法的对象的一个副本
th.Name = newName
}
func main() {
// 创建对象
hero :=Hero{"111",12,1}
hero.Show()
hero.SetName("szh")
hero.Show()
}
继承
type Human struct {
Name string
Sex int
}
func (th *Human) eat() {
println("Human eat----")
}
func (th *Human) walk() {
println("Human walk----")
}
type SuperMan struct {
// 表 Super继承 Human
Human
level int
}
func (th *SuperMan) eat() {
println("SuperMan eat。。。。")
}
func (th *SuperMan)walk() {
println("SuperMan walk.....")
}
func (th *SuperMan) fly() {
println("SuperMan fly.....")
}
func main() {
human :=Human{"huamn",22}
human.eat()
// 创建对象方式
super :=SuperMan{Human{"lu",1},22}
super.eat()
// 第二种创建
println("---------------")
var superMan SuperMan;
superMan.Name="szh"
superMan.Sex=1
superMan.level=22
superMan.eat()
superMan.fly()
}
多态
// 定义一个接口
// 注意:子类若是没有完全实现接口,则表示,这个子类不是该接口的子类
type AnimalI interface {
Sleep()
GetColor() string
GetType() string
}
// 子类去实现
type Cat struct {
Color string
}
func (th *Cat) Eat() {
panic("Cat eat")
}
func (th *Cat) GetColor() string{
return th.Color
}
func (th *Cat) GetType() string{
return "cat"
}
func (th *Cat) Sleep() {
println("cat is sleep")
}
// 子类去实现
type Dog struct {
Color string
}
func (th *Dog) GetColor() string{
return th.Color
}
func (th *Dog) GetType() string{
return "Dog"
}
func (th *Dog) Sleep() {
println("Dog is sleep")
}
func showAnimal(i AnimalI) {
println(i.GetColor())
println(i.GetType())
}
func main() {
//感觉离谱
var animal AnimalI;
animal=&Cat{"yellow"}
animal.GetColor()
animal.Sleep()
// 多态演示
dog :=Dog{"yellow"}
showAnimal(&dog)
}
万能类型
类似于java的Object,入参可接任意类型
// interface{} 万能类型,类似于Object
func test(arg interface{}) {
println("test is.....")
println(arg)
// 判断入参实际类型
value,flag:= arg.(string)
if flag {
fmt.Println("agr是个string类型")
}else {
println("arg不是string,arg=",arg,",value=",value)
}
}
func main() {
i :=1
test(i)
test(2222)
}
Pair结构
任何变量,都有一个这样的结构,类似于java的头信息
type Reader interface {
ReadBook()
}
type Writer interface {
WriteBook()
}
type Book struct {
}
func (book *Book)ReadBook() {
println("ReadBook-------")
}
func (book *Book)WriteBook() {
println("WriteBook-------")
}
func main() {
// b pair<type:book,value:Book{}地址}
b :=&Book{}
b.ReadBook()
var r Reader
r=b
r.ReadBook()
var w Writer
w=r.(Writer)
w.WriteBook()
}
反射
import (
"fmt"
"reflect"
)
type User struct {
Id int
Name string
Age int
}
// 简单类型
func reflectNum(arg interface{}) {
fmt.Println("type=", reflect.TypeOf(arg))
fmt.Println("value=", reflect.ValueOf(arg))
}
func DeFiledAndMethod(input interface{}) {
// 获取input Type
inputType :=reflect.TypeOf(input)
fmt.Println("type=", inputType.Name())
// 获取input value
inputValue :=reflect.ValueOf(input)
fmt.Println("type=", inputValue)
//得到该类所有属性
inputType.NumField()
// 得到该类所有方法。
//todo 和java类似
inputType.NumMethod()
}
func main() {
var num float64 = 1.2
reflectNum(num)
println("---------------")
user := User{1, "sz", 222}
}
获取tag标签
import (
"reflect"
)
type User struct {
Id int `info:"Id" doc:"主键"`
Name string `info:"Name" doc:"名称"`
}
func findTag(str interface{}) {
t := reflect.TypeOf(str).Elem()
for i := 0; i < t.NumField(); i++ {
info := t.Field(i).Tag.Get("info")
doc := t.Field(i).Tag.Get("doc")
println("info=", info, ",doc=", doc)
}
}
func main() {
var use User
findTag(&use)
}
结构体标签在json中作用
import (
"encoding/json"
"fmt"
)
type Movie struct {
Title string `json:"title"`
Year int `json:"Year"`
Price int `json:"price"`
Actors []string `json:"actors"`
}
func main() {
movie := Movie{"习题集", 2, 3, []string{"1111", "111"}}
// 编码的过程,结构体
jsonStr, err := json.Marshal(movie)
if err != nil {
println(err)
return
}
fmt.Printf("jsonStr =%s\n", jsonStr)
println("jsonStr --->object")
myMovie :=Movie{}
err=json.Unmarshal(jsonStr,&myMovie)
if err != nil {
println(err)
return
}
fmt.Printf("jsonStr =%v\n", myMovie)
}
goroutine
匿名方式
import "time"
// 主go,此进程不能退出,若退出则子go也会退出
func main() {
//定义并调用
go func() {
defer println("A defer")
// 也可以传递参数
func() {
defer println("b defer")
// 退出当前 goroutine
//runtime.Goexit()
println("B")
}()
println("A")
}()
time.Sleep(1 * time.Second)
}
线程通信
无缓冲channel
func main() {
// 定义一个无缓冲channel
c :=make(chan int)
// 子 goroutine
go func() {
defer println("子 goroutine end")
println("子 goroutine .....")
// 将666 发送给c
c <-666
}()
// 在主goroutine 中使用num接受,子goroutine的传递的变量
num := <-c
println("num = ",num)
println("main goroutine 结束")
}
无缓冲的channel存在,同步问题。类似,阻塞队列一样。我没有接,你需要等着(阻塞)
有缓存channel
func main() {
// 定义一个缓冲channel
c :=make(chan int,3)
// 子 goroutine
go func() {
defer println("子 goroutine end")
for i := 0; i < 3; i++ {
c<-i
println("子 goroutine在运行中,发送元素是:",i,"此时长度len=",len(c))
}
}()
// 主goroutine 休息一会
time.Sleep(1*time.Second)
for i := 0; i < 3; i++ {
// 从channel去元素
num :=<-c
println(num)
}
println("main goroutine 结束")
}
注意:1-channl已经满,再向里面写数据,将会阻塞。2-当channel为空,从里面取数据也会阻塞。
类似于生产者消费者模式
channel关闭特性
func main() {
c := make(chan int)
// 子 goroutine
go func() {
defer println("子 goroutine end")
for i := 0; i < 3; i++ {
c <- i
println("子 goroutine在运行中,发送元素是:", i, "此时长度len=", len(c))
}
// todo 若是使用的是下面的for循环语句,没有关闭的话,将抛出异常
// close 可以关闭channel
close(c)
}()
// for循环还可这样写
for {
// ok为true表channel没有关闭,
if data, ok := <-c; ok {
println(data)
}else{
break
}
}
// todo 也可以这样去写。channel只要不关闭就读取.比上面更加简洁
for data := range c {
println(data)
}
println("main goroutine 结束")
}
channel与select
若是有同时监视多个channel的需要,可以同时channel与select
func fibonaciii(c, quit chan int) {
x, y := 1, 1
for {
select {
case c <- x:
// 若c可写,则case进来
x, y = y, x+y
case<-quit:
println("quit")
return
}
}
}
func main() {
c:=make(chan int)
quit :=make(chan int)
go func() {
for i := 0; i < 6; i++ {
println(<-c)
}
quit<-0
}()
fibonaciii(c,quit)
println("main goroutine 结束")
}
go modules 模式
一些环境变量的配置
go env -w GO111MODULE=on // 默认是auto
go env -w GOPROXY=https://goproxy.cn,direct