golang输出helloWorld
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
关于变量与常量
golang变量声明
a := "abcdefg"
fmt.Printf("a = %s, type of a = %T\n", a, a)
fmt.Println(a)
b := 1.23
fmt.Printf("type of b = %T\n", b)
fmt.Println(b)
golang常量
const pi float64 = 3.1415
fmt.Println(pi)
关于函数
golang函数初学
func main() {
s := fplus(2, 3)
fmt.Println(s)
}
func fplus(a int, b int)(c int){
c = a + b
return
}
glonag函数返回两个结果
func main() {
a, b := plusone(5, 6)
fmt.Println(a)
fmt.Println(b)
}
func plusone(a int, b int)(int, int){
return a + 1, b + 1
}
导包与init函数
图片说明 import导包与init函数

这也就是说如果文件里有导包,会先初始化导入的包里的 常量,变量, 执行init函数
注意:init函数的执行会早于main函数
关于go mod注意
如果开启并使用了go mod 在导入自己写的包的时候
import module名/包名 这个包名其实是是文件夹名 同时也是文件里声明的包名
package main
import (
"fmt"
//study是go mod init的时候输入的module名
//lib1 lib2 其实是文件夹名 也是文件里的package名
"study/lib1"
"study/lib2"
)
func main() {
fmt.Println("abc")
//函数名 首字母大写才能外部调用
lib1.Test()
lib2.Test()
}
import匿名导入 其实就是给包起了个别名 _
package main
import (
"fmt"
//如果只想调用包里的init方法 需要匿名导入
_ "study/lib1"
_ "study/lib2"
)
func main() {
fmt.Println("abc")
}
import导入的包也可以起个别名
package main
import (
"fmt"
//这里为导入的包起个别名,下面可以用别名来调用
abc "study/lib1"
def "study/lib2"
)
func main() {
fmt.Println("abc")
abc.Test()
def.Test()
}
关于指针
golang指针初学
func main() {
a := 10
//&a 代表 取地址 引用
changeValue(&a)
fmt.Println(a)
}
//*int 代表指针类型 引用类型
func changeValue(p *int){
//*p 代表取内存地址里的值 也叫解引用
*p = 100
}
关于defer
defer关健字 像个析构函数
注意:defer比return还要晚执行
func main() {
//defer关健字 最后执行的语句 像是PHP里的析构函数
defer fmt.Println("defer ..")
fmt.Println("a")
fmt.Println("b")
}
关于数组与slice
固定长度数组使用(不好用)
注意:不建议使用 建议用slice切片(动态数组)
由于长度固定,传给函数当参数都不好传
数组作为函数参数的话是值传递
func main() {
//定义一个固定长度的数组
var myArray1 [10]int
//定义并初始化一个固定长度的数组 后面5个元素由于没有给数据 是0
myArray2 := [10]int{1, 2, 3, 4, 5}
//遍历
for i := 0; i < len(myArray1); i++ {
fmt.Println(myArray1[i])
}
//另一种方法遍历
for index, value := range myArray2 {
fmt.Println("index=", index)
fmt.Println("value=", value)
}
}
slice 切片 动态数组
注意 切片作为参数传递给函数的话 是地址传递 也叫引用传递
用法跟数组是一样的,只是高级一点
func main() {
//定义一个切片
mySlice := []int{1,2,3}
//遍历
for _, value := range mySlice{
fmt.Println(value)
}
}
另一种定义切片的方式
//声明一个切片 有3个容量
mySlice1 := make([]int, 3)
fmt.Println(mySlice1)
切片的长度和容量
//切片的长度 和 容量
numbers := make([]int, 3, 10)
fmt.Printf("len = %d, cap = %d\n", len(numbers), cap(numbers))
向slice里用append添加元素
func main() {
//切片的长度 和 容量 当前容量为5
numbers := make([]int, 3, 5)
//向slice里添加元素
numbers = append(numbers, 4)
numbers = append(numbers, 6)
//当添加的元素超过容量了,会自动扩容
numbers = append(numbers, 7)
fmt.Printf("len = %d, cap = %d\n", len(numbers), cap(numbers))
}
切片的截取 用下标截取
注意:截取前与截取后的是同一个切片 改变任何一个 另一个也会变
如果不想这样 可以用copy函数 拷贝一份再截取
func main() {
//切片的截取
mySlice := make([]int, 2)
mySlice[0] = 2
mySlice[1] = 3
mySlice = append(mySlice, 4)
//截取 s := arr[startIndex:endIndex]
newSlice := mySlice[1:2]
fmt.Println(newSlice)
}
关于map
注意:map作为函数参数的话 是 引用传递
map简单使用
func main() {
//map第一种声明方式
//map声明
var myMap1 map[string]string
//make容量
myMap1 = make(map[string]string, 3)
myMap1["one"] = "php"
myMap1["two"] = "java"
myMap1["three"] = "python"
//容量用完了会自动扩容
myMap1["four"] = "rust"
fmt.Println(myMap1)
//map第二种声明
test2 := make(map[string]string)
test2["one"] = "php"
test2["two"] = "golang"
test2["three"] = "java"
fmt.Println(test2) //map[one:php two:golang three:java]
//map第三种声明
test3 := map[string]string{
"one" : "php",
"two" : "golang",
"three" : "java",
}
fmt.Println(test3) //map[one:php two:golang three:java]
//复杂一点的map value又是个map
language := make(map[string]map[string]string)
language["php"] = make(map[string]string, 2)
language["php"]["id"] = "1"
language["php"]["desc"] = "php是世界上最美的语言"
language["golang"] = make(map[string]string, 2)
language["golang"]["id"] = "2"
language["golang"]["desc"] = "golang抗并发非常good"
fmt.Println(language)
}
map的增删改查
func main() {
language := make(map[string]map[string]string)
language["php"] = make(map[string]string, 2)
language["php"]["id"] = "1"
language["php"]["desc"] = "php是世界上最美的语言"
language["golang"] = make(map[string]string, 2)
language["golang"]["id"] = "2"
language["golang"]["desc"] = "golang抗并发非常good"
//fmt.Println(language) //map[php:map[id:1 desc:php是世界上最美的语言] golang:map[id:2 desc:golang抗并发非常good]]
//增删改查
val, key := language["php"] //查找是否有php这个子元素
if key {
fmt.Printf("%v\n", val)
} else {
fmt.Printf("no\n");
}
//language["php"]["id"] = "3" //修改了php子元素的id值
//language["php"]["nickname"] = "啪啪啪" //增加php元素里的nickname值
//delete(language, "php") //删除了php子元素
fmt.Println(language)
}
map增删改查
func main() {
//声明一个map
city := make(map[string]string)
//增加元素
city["usa"] = "newyork"
city["japan"] = "tokay"
city["uk"] = "london"
//fmt.Println(city)
//删除 使用健来删除
delete(city, "uk")
//fmt.Println(city)
//修改
city["japan"] = "abc"
//fmt.Println(city)
//查询 遍历
for key, value := range city{
fmt.Println("key = ", key)
fmt.Println("value = ", value)
}
}
面向对象
struct 结构体
struct相当于面向对象语言里的类
注意:用struct构造出来的数据(new出来的对象) 作为函数参数的话是值传递
注意:类名首字母大写表示其它包也可以访问 属性名首字母大写表示可对外访问 方法名首字母大写表示其它包可访问
结构体初体验
// 定义一个结构体
type Book struct {
title string
auth string
}
func main() {
//用结构体构造一个对象
var book1 Book
book1.title = "好好学golang"
book1.auth = "张三"
fmt.Println(book1)
}
给结构体加方法
//结构体定义 相当于类
type Hero struct{
//两个属性
Name string
Age int
}
//类的方法定义
//这里最好加个*号 否则传进来的对象是拷贝
func (this *Hero) Show(){
fmt.Println("name = ", this.Name)
fmt.Println("age = ", this.Age)
}
//类的方法定义
//这里最好加个*号 否则传进来的对象是拷贝
func (this *Hero) SetName(name string){
this.Name = name
}
func main() {
//用结构体构造一个对象
hero := Hero{Name: "张三", Age: 20}
//改一下名字
hero.SetName("李四")
//查看一下改成功了
hero.Show()
}
golang里面的类继承(struct)
//父类
type Human struct{
Name string
}
//父类的方法
func (this *Human) Eat(){
fmt.Println("父类的eat方法")
}
//父类的方法
func (this *Human) Walk(){
fmt.Println("父类的Walk方法")
}
//====================================
//子类
type Superman struct{
//继承父类,直接把父类写在这里就行
Human
//再写个子类的属性
Age string
}
//子类的方法
func (this *Superman) Run(){
fmt.Println("子类的Run方法")
}
//也可以重写父类的方法
func (this *Superman) Walk(){
fmt.Println("子类重写了父类的Walk方法")
}
func main() {
//用子类来new一个对象
var superman1 Superman
superman1.Name = "张三"
superman1.Age = "100"
//调用一下父类的方法
superman1.Eat()
//调用一下子类的方法
superman1.Run()
//调用一下 子类重写的父类方法
superman1.Walk()
//再调用一下属性
fmt.Println(superman1.Name)
fmt.Println(superman1.Age)
}
interface 空指针 万能类型 类型断言
空指针可以接收任务类型的参数
类型断言能够判断传递过来数据的类型
func myfunc(arg interface{}){
//类型断言
_, ok := arg.(string)
if ok {
fmt.Println("是string类型")
}
_, ok = arg.(int)
if ok {
fmt.Println("是int类型")
}
}
func main() {
myfunc(11)
}
reflect反射
反射获取变量的类型和值
func myReflect(arg interface{}){
fmt.Println(reflect.TypeOf(arg))
fmt.Println(reflect.ValueOf(arg))
}
func main() {
myReflect(11)
}
利用reflect获取类的标签
// 带标签的结构体
type Book struct {
Name string `info:"书名"`
auth string `info:"作者"`
}
// 写个函数来查询对象的标签
func findTag(str interface{}) {
//获取对象的元素
t := reflect.TypeOf(str).Elem()
for i := 0; i < t.NumField(); i++ {
infoTag := t.Field(i).Tag.Get("info")
fmt.Println(infoTag)
}
}
func main() {
book1 := Book{}
findTag(&book1)
}
json
结构体与json互转 会用到结构体标签
import (
"encoding/json"
"fmt"
)
// 定义个结构体
type Movie struct {
Name string `json:"name"`
Year int `json:"year"`
Price int `json:"price"`
}
func main() {
//定义一个结构体
movie1 := Movie{"功夫", 2005, 15}
//结构体转json
jsonStr, err := json.Marshal(movie1)
if err != nil {
fmt.Println("转换json失败")
}
fmt.Printf("转换json成功 str=%s\n", jsonStr)
//再转回来
movie2 := Movie{}
err = json.Unmarshal(jsonStr, &movie2)
if err != nil {
fmt.Println("转换失败")
}
fmt.Println(movie2)
}
goroutine 协程 并发
协程初体验
import (
"fmt"
"time"
)
func task() {
i := 0
for {
fmt.Println("协程里执行的=", i)
i++
time.Sleep(1 * time.Second)
}
}
func main() {
//开一个go程
go task()
//主线程
i := 0
for{
fmt.Println("主线程里执行的=", i)
i ++
time.Sleep(1 * time.Second)
}
}
channel 管道 用于协程间通讯
注意:管理分为有缓存和没有缓存两种
没有缓存的管道会有阻塞效果,不管是对于接收还是发送
管道初体验
func main() {
//定义一个channel
c := make(chan int)
go func() {
fmt.Println("子协程开始运行...")
defer fmt.Println("子协程运行结束")
//子协程结束后发送个信号
c <- 1
}()
//主协程等待接收信号
num := <- c
fmt.Println(num)
fmt.Println("主协程结束....")
}
图解无缓存channel

图解有缓存channel

带缓存的channel简单应用
带缓存的channel当缓存满了 也会有阻塞效果
import (
"fmt"
"time"
)
func main() {
//带缓存的channel 容量cap为3 长度len为0
c := make(chan int, 3)
//fmt.Printf("cap = %d, len = %d", cap(c), len(c))
go func() {
defer fmt.Println("子go程结束")
for i := 0; i < 30; i++ {
//向管道中写入数据
c <- i
fmt.Println("子go程向管道中写入了数据", i)
}
}()
time.Sleep(1 * time.Second)
for i := 0; i < 30; i++ {
num := <- c
fmt.Println("主go程从管道中读取了数据", num)
}
fmt.Println("主go程结束")
}
用close关闭管道的简单应用
func main() {
//没有缓存的channel
c := make(chan int)
go func () {
for i := 0; i < 5; i++ {
//向管道写入数据
c <- i
}
//关闭管道
close(c)
}()
for{
//也可以这样从管道中读取数据
data, ok := <- c
if ok {
fmt.Println("从管道中读取的数据是", data)
} else {
fmt.Println("管道关闭")
break
}
}
fmt.Println("主go程结束")
}
也可以用range来 遍历 不断操作通道
func main() {
//没有缓存的channel
c := make(chan int)
go func () {
for i := 0; i < 5; i++ {
//向管道写入数据
c <- i
}
//关闭管道
close(c)
}()
//也可以用range来 不断操作channel
for data := range c{
fmt.Println(data)
}
fmt.Println("主go程结束")
}
select操作通道
demo
select {
case <- chan1:
// 如果chan1成功读到数据,则进行该case处理语句
case chan2 <- 1:
// 如果成功向chan2写入数据,则进行该case处理语句
default:
// 如果上面都没有成功,则进入default处理流程
}
select可以同时监控多路channel的状态
package main
import (
"fmt"
)
func fibonacci(c, quit chan int) {
x, y := 1, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 6; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}