一.Go 语言介绍
Go 即 Golang,是 Google 公司 2009 年 11 月正式对外公开的一门编程语言。根据 Go 语言开发者自述,近 10 多年,从单机时代的 C 语言到现在互联网时代的Java,都没有令人满意的开发语言,而 C++往往给人的感觉是,花了 100%的经历,却只有60%的开发效率,产出比太低,Java 和 C#的哲学又来源于 C++。并且,随着硬件的不断升级,这些语言不能充分的利用硬件及 CPU。因此,一门高效、简洁、开源的语言诞生了。
Go 语言不仅拥有静态编译语言的安全和高性能,而且又达到了动态语言开发速度和易维护性。有人形容 Go 语言:Go = C + Python , 说明 Go 语言既有 C 语言程序的运行速度,又能达到 Python 语言的快速开发。
Go 语言是非常有潜力的语言,是因为它的应用场景是目前互联网非常热门的几个领域,比如 WEB 开发、区块链开发、大型游戏服务端开发、分布式/云计算开发。国内比较知名的B 站就是用 Go 语言开发的,像 Goggle、阿里、京东、百度、腾讯、小米、360 的很多应用也是使用 Go 语言开发的。
二、基础语法部分
1.定义变量
// var 变量名 = 表达式
var a = "123"
fmt.Println(a)
// 变量名 := 表达式
b := 20
fmt.Printf("%v %T\n",b,b)
2.一次性声明多个变量
// var ( 变量名 = 表达式 ... )
var (
name = "ww"
age = 33
sex = "男"
)
fmt.Println(name, age, sex)
// 变量名,变量名 := 表达式,表达式
name, age, sex := "zz", 30, "女"
fmt.Println(name, age, sex)
3.定义常量
const pi = 3.1415
fmt.Println(pi)
const (
n1 = 100 + iota // 100 + 0
_
n3
)
fmt.Println(n1,n3) // 100 102
4.基本类型 整型、浮点型、布尔型、字符串
a.整型
var a uint8 = 128 // 1字节
var b int8 = -2 // 1字节
fmt.Println(int8(a)+b) // 126
c: = 1 // 64位,8字节 32位,4字节
b.浮点型和布尔型
var a float32 = 3.12
fmt.Println(unsafe.Sizeof(a)) // 4字节
var b float64 = 3.124323544
fmt.Println(unsafe.Sizeof(b)) // 8字节
c := 3.12
fmt.Println(unsafe.Sizeof(c)) // 64位,8字节 32位,4字节
var flag = true
fmt.Printf("%v %T\n", flag, flag)
if flag {
fmt.Println("ok")
} else {
fmt.Println("no")
}
c.字符串 声明
str := "\"Hello World!\""
str2 := "C:\\Go\\bin\n"
str3 := `
Hello
Word
Yeah
`
fmt.Println(str,str2, str3)
长度
str1 := "中" // 3个字节
str2 := "国"
fmt.Println(str1, len(str1),str1 + str2)
str3 := fmt.Sprintf("%v %v",str1,str2) // 拼接
fmt.Println(str3)
Split和Join
arr :=[]string{"ts","java","go"}
strs := strings.Join(arr, "-")
fmt.Println(strs)
fmt.Println(strings.Split(strs, "-"))
Contains、HasPrefix、HasSuffix
// 包含,前缀,后缀
strs := "Hello,World!"
flag := strings.Contains(strs, "Wo")
flag2 := strings.HasPrefix(strs, "He")
flag3 := strings.HasSuffix(strs, "!")
if flag && flag2 && flag3 {
fmt.Println("Have")
}
Index和LastIndex
strs := "Hello,World!"
fmt.Println(strings.Index(strs, "o")) // 4
fmt.Println(strings.LastIndex(strs, "o")) // 7
fmt.Println(strings.LastIndex(strs, "-")) // -1
d.字符
// 字符 byte字母ASCII编码 rune汉字UTF-8编码
a := '中'
fmt.Printf("%v %c %T\n",a,a,a) // 20013 中 int32
b :="中国"
fmt.Printf("%v %T\n",b,b) // 中国 string
fmt.Println(unsafe.Sizeof(b),len(b)) // 16 6字节
c :="你好, GO"
for _, v := range c { // rune
fmt.Printf(("%c\n"), v)
}
类型转换 a.int/float 转 string
// 方式一:fmt.Sprintf
i := fmt.Sprintf("%v", 123)
f := fmt.Sprintf("%v", 1.23566)
fmt.Printf("%s %T\n",i,i) // int to string
fmt.Printf("%s %T\n",f,f) // float to string
// 方式二:strcov
i2 := strconv.Itoa(123)
f2 := strconv.FormatFloat(1.23566, 'f', -1, 64)
fmt.Printf("%s %T\n",i2,i2) // int to string
fmt.Printf("%s %T\n",f2,f2) // float to string
b.string 转 int/float
s := "123"
i, _ := strconv.Atoi(s)
i2, _ := strconv.ParseInt(s, 10, 0)
fmt.Printf("%v %T\n",i,i) // string to int
fmt.Printf("%v %T\n",i2,i2) // string to int64
s2 := "1.23566"
f, _ := strconv.ParseFloat(s2, 64)
fmt.Printf("%v %T\n",f,f) // string to float64
三.内置运算符
算术运算符、关系运算符、逻辑运算符、赋值运算符、位运算符
// 算术运算符 + - * / %
fmt.Println(3+2)
fmt.Println(3-2)
fmt.Println(3*2)
fmt.Println(3.0/2)
fmt.Println(3%2)
// 关系运算符 ==、!=、>、>=、<、<=
var (
a1 = 9
a2 = 8
a3 = true
)
fmt.Println(a1 >= a2) // true
// 逻辑运算符 && || !
if a1 > 9 && a2 < 9 {
println("&&")
}
if a1 > 9 || a2 < 9 {
println("||")
}
if(!a3) {
println(('!'))
}
// 赋值运算符 =、+=、-=、*=、/=、%=
var a = 1
a += 3
println(a) // 4
a *= 4
println(a) // 16
a /= 2
println(a) // 8
a %= 4
println(a) // 0
// 位运算符 &、|、^、<<、>>
var a = 5 // 二进制 101
var b = 2 // 二进制 010
fmt.Println("a&b=", a&b) // 000 值0
fmt.Println("a|b=", a|b) // 111 值7
fmt.Println("a^b=", a^b) // 111 值7
fmt.Println("a<<b=", a<<b) // 10100 值20
fmt.Println("a>>b=", a>>b) // 5/2的2次方 值1
四.流程控制
1.if else
// 全局变量
flag := true
if flag {
fmt.Println("flag")
}
// 局部变量
if age := 20; age > 40 {
fmt.Println("> 40")
} else if age > 20 {
fmt.Println("> 20")
} else {
fmt.Println("<= 20")
}
2.for
/**
for 初始语句;条件表达式;结束语句{
循环体语句
}
**/
// 基本
for i := 0; i < 10; i++ {
fmt.Println(i) // 0 ~ 9
}
// 条件
i := 0
for i < 10 {
fmt.Println(i) // 0 ~ 9
i++
}
// 类似 while 循环
for {
if i < 10 {
fmt.Println(i)
} else {
break
}
i++
}
// 九九乘法表
for i := 1; i <= 9; i++ {
for j := 1; j <= i; j++ {
fmt.Printf("%v*%v=%v \t",j,i,i*j)
}
fmt.Println("")
}
3.for range
可以使用 for range 遍历数组、切片、字符串、map 及通道(channel)。
// 字符串
str := "你好,世界!"
for _,v := range str {
fmt.Printf("%c\n",v)
}
// 切片
arr :=[]string{"ts","java","go"}
for _,v := range arr {
fmt.Printf("%s\n",v)
}
4.switch case
// break可以不写
switch n := 1;n {
case 1, 3, 5, 7, 9:
fmt.Println("奇数")
case 2, 4, 6, 8:
fmt.Println("偶数")
default:
fmt.Println(n)
}
5.其他
// fallthrough 穿透,默认只穿透一层(用于switch)
// break 跳出循环(用于for 或者 switch)
// continue 跳过当前循环,继续下次循环(用于for)
// goto 跳转到指定标签
var n = 30
if n > 24 {
fmt.Println("成年人")
goto x
}
fmt.Println("aaa")
fmt.Println("bbb")
x:
fmt.Println("ccc")
// 成年人、ccc
五.数组Array和切片Slice
数组(Array)是指一系列同一类型数据的集合,值类型,长度固定。
切片 (Slice)是指一个拥有相同类型元素的可变长度的序列,引用类型,长度不固定。它非常灵活,支持自动扩容。
// 声明一维数组
var arr1 = [3]int{22, 24, 5}
var arr2 = [3]string{"ts", "java", "go"}
// 值类型:数组
var arr3 = [...]int{1,4,6,6}
// 引用类型:切片
var arr4 = []int{1,4,6,6}
var a = arr3
arr3[0] = 2
var b = arr4
arr4[0] = 2
fmt.Println(arr3, a) // [2 4 6 6] [1 4 6 6]
fmt.Println(arr4, b) // [2 4 6 6] [2 4 6 6]
fmt.Println(arr2)
fmt.Println(arr3, len(arr3))
// 遍历
for _, v := range arr1 {
fmt.Println(v)
}
// 声明二维数组
var arr5 = [3][2]string {
{"A1","B1"},
{"B2","B2"},
{"C3","C2"},
}
// 遍历
for _, v := range arr5 {
for _,j := range v {
fmt.Println(j)
}
}
切片的本质就是对底层数组的封装, 它包含了三个信息: 底层数组的指针、 切片的长度 (len) 和切片的容量(cap)。
// 长度:包含的元素个数
// 容量:从它第一个元素开始数,到其底层元素末尾的个数
var arr1 []int
var arr2 = []int{1,2,3}
var arr3 = arr2[1:2]
fmt.Println(arr1 == nil, arr2 == nil) // true false
fmt.Println(arr1, len(arr1), cap(arr1)) // [] 0 0
fmt.Println(arr2, len(arr2), cap(arr2)) // [1 2 3] 3 3
fmt.Println(arr3, len(arr3), cap(arr3)) // [2] 1 2
// make() 函数创建一个切片 make([]T, size, cap)
sliceA := make([]int, 4, 4)
fmt.Println(sliceA,len(sliceA),cap(sliceA))
// 切片修改
sliceA[0] = -1
// 切片新增
sliceA = append(sliceA, 1,2,3,4,5)
// 切片删除
sliceD := append(sliceA[:1],sliceA[4:]...)
fmt.Println(sliceA,len(sliceA),cap(sliceA))
fmt.Println(sliceD,len(sliceD),cap(sliceD))
// 切片合并
arr1 := []string{"a","b"}
arr2 := []string{"c","d"}
arr3 := append(arr1, arr2...)
fmt.Println(arr3)
// copy引用类型
arr4 := []int{1,2}
arr5 := make([]int,2,2)
copy(arr5, arr4)
arr5[1] = 3
fmt.Println(arr4, arr5)
六.字典Map
Map 是一种无序的基于 key-value 的数据结构,Go 语言中的 Map 是引用类型,必须初始化 才能使用。
// 声明
user := map[string]string{
"name": "ww",
"age": "33",
"sex": "male",
}
fmt.Println(user) // map[age:33 name:ww sex:male]
// 遍历
for k,v := range user {
fmt.Println(k,v)
}
// 新增
user["hobby"] = "play"
// 获取
v, ok := user["name"]
fmt.Println(ok, v)
// 修改
user["name"] = "zz"
fmt.Println(user) // map[age:33 hobby:play name:zz sex:male]
// 删除
delete(user, "hobby")
fmt.Println(user) // map[age:33 name:zz sex:male]
// 声明一个赋值string[]
ww := make(map[string][]string)
ww["work"] = []string{
"eat",
"coding",
"sleep",
}
fmt.Println(ww) // map[work:[eat coding sleep]]
七.函数Func
Go 语言中定义函数使用 func 关键字,具体格式如下:
func 函数名(参数)(返回值){
函数体
}
可变参数是指函数的参数数量不固定。Go 语言中的可变参数通过在参数名后加...来标识。
var global = "全局变量"
// 求和
func sum(x int, y int) int {
return x + y
}
// 求差
func sub(x int, y int) int {
return x - y
}
// 合并
func clac1(x int, y int) (int, int) {
return x + y, x - y
}
func clac2(x, y int) (sum int, sub int) {
fmt.Println(global)
sum = x + y
sub = x - y
return
}
func main() {
/*
全局变量:函数外部
局部变量:函数内部
*/
a, b := clac1(2,3)
c, d := clac2(2,3)
fmt.Println(sum(2,3), sub(2, 3), a,b,c,d) // 5 -1 5 -1 5 -1
}
2.定义函数类型 我们可以使用 type 关键字来定义一个函数类型
type clacType func (int, int) int //表示定义了一个calc的类型
// 求差
func sub(x int, y int) int {
return x - y
}
var e clacType = sub // 自定义类型: main.clac
f := sub // 推倒类型:func(int, int) int
fmt.Printf("%T %T\n", e, f) // main.clac func(int, int) int
3.高阶函数 高阶函数分为函数作为参数和函数作为返回值两部分。
func clac(x, y int, cb clacType) int {
return cb(x, y)
}
fmt.Println(clac(2, 3, sum), clac(2, 3, sub)) // 5 -1
4.匿名函数和闭包
匿名函数就是没有函数名的函数,闭包可以理解成 “定义在一个函数内部的函数“。
本质上, 闭包是将函数内部和函数外部连接起来的桥梁。
type clacType func (int, int) any
func do(t string) clacType {
switch t {
case "+":
return func(x, y int) any {
return x + y
}
case "-":
return func(x, y int) any {
return x - y
}
case "*":
return func(x, y int) any {
return x * y
}
case "/":
return func(x, y int) any {
return float64(x) / float64(y)
}
default:
return nil
}
}
fmt.Println(do("+")(2,3), do("-")(2,3), do("*")(2,3), do("/")(2,3))
// 5 -1 6 0.6666666666666666
5.柯里化函数
柯里化(Currying)是一种函数式编程技术:
接受多个参数的函数转换为一系列接受单个参数的函数,通过闭包,每个函数都可以访问之前传入的参数。
柯里化函数优势:
- 函数复用:可以创建预配置的函数
- 代码组织:将复杂函数拆分为多个步骤
- 灵活性:可以按需组合不同的函数
- 可读性:代码更清晰,意图更明确"
// 普通加法函数
func add(a, b int) int {
return a + b
}
// 柯里化版本:先传入第一个参数,返回一个函数
func curryAdd(x int) func(int) int {
// 闭包捕获了 x
return func(y int) int {
return x + y
}
}
add(2,3) // 5
curryAdd(2)(3) // 5
八.报错和时间
1.defer/panic/recover defer 语句会将其后面跟随的语句进行延迟处理。 使用 panic/recover 模式来处理错误。
defer fmt.Println("World")
fmt.Println("Hello")
// Hello World
safeOperation()
func safeOperation() {
defer func() {
if r := recover(); r != nil {
fmt.Printf(" 捕获panic: %v\n", r)
fmt.Println(" 程序继续执行")
}
}()
fmt.Println(" 即将触发panic")
panic("发生错误")
fmt.Println(" 这行不会执行")
}
2.time
now := time.Now()
fmt.Println("当前时间:", now)
fmt.Println("当前时间戳:", now.Unix())
// 格式化的模板为 Go 的出生时间 2006 年 1 月 2 号 15 点 04 分,2006 1,2,3,4,5
var tmp = "2006-01-02 15:04:05"
fmt.Println("当前日期字符串:", now.Format(tmp))
// 时间转换
fmt.Println("时间戳转日期字符串",time.Unix(1789034269, 0).Format(tmp))
t, _ :=time.ParseInLocation(tmp,"2026-09-10 17:57:49",time.Local)
fmt.Println("日期字符串转时间戳",t.Unix())
// 时间函数
fmt.Println("当前时间加1小时", now.Add(time.Hour).Format("15:04"))
// 设置定时器,每隔1s
ticker := time.NewTicker(time.Second) // 定义1s的定时器
n := 1
for i := range ticker.C {
fmt.Println("定时执行任务:"+i.Format("04:05"))
n++
if n > 5 {
ticker.Stop()
return
}
}
for {
time.Sleep(time.Second)
fmt.Println("定时执行任务")
}
九.指针
指针也是一个变量, 但它是一种特殊的变量, 它存储的数据不是一个普通的值, 而是另一个变量的内存地址。
Go 语言中的指针操作非常简单,我们只需要记住两个符号:&(取内存地址)和 *(根据内存地址取值)。
a := 10
b := &a // 获取a的内存地址
fmt.Printf("a:%v type:%T ptr:%p\n", a, a, &a) // a:10 type:int ptr:0x4230641ea0c0
fmt.Printf("b:%v type:%T ptr:%p\n", *b, b, b) // b:0x4230641ea0c0 type:*int ptr:0x4230641ea0c0
// make、new 分配内存空间
var user = make(map[string]string)
user["name"] = "ww"
fmt.Println(user)
var arr = make([]int, 4, 4)
arr[0] = 1
fmt.Println(arr)
// 了解即可
var c = new(int)
*c = 100
fmt.Println(*c)
十.结构体
1.Golang 提供了一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体,英文名称 struct。
/*
值类型:数组,基本数据结构,结构体
引用类型:切片,map
定义结构体:
type 类型名 struct {
字段名 字段类型
字段名 字段类型
…
}
*/
type Person struct {
name string
age int
sex string
}
/*
定义结构体方法:
func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
函数体
}
*/
func (p Person) PrintInfo() {
fmt.Printf("姓名:%v 年龄:%v\n",p.name, p.age)
}
/* 必须靠指针修改值 */
func (p *Person) SetInfo(name string, age int) {
p.name = name
p.age = age
}
// 实例化,常用 p4
var p1 Person
p2 := new(Person)
p3 := &Person{
name: "default",
}
p4 := Person {
name: "ww",
age: 33,
}
p3.SetInfo("zz", 20)
p3.PrintInfo()
p4.PrintInfo()
fmt.Println(p1, *p2, *p3, p4)
2.结构体的嵌套
type Person struct {
name string
age int
hobby []string
record map[string]string
address Address
}
type Address struct {
city string
phone string
}
func (p Person) PrintInfo() {
fmt.Printf("姓名:%v 年龄:%v\n",p.name, p.age)
}
func (p *Person) SetInfo(name string, age int) {
// 必须靠指针修改
p.name = name
p.age = age
}
func (p *Person) SetHobby(hobby []string) {
p.hobby = hobby
}
func (p *Person) SetRecord(record map[string]string) {
p.record = record
}
func (p *Person) SetAddress(address Address) {
p.address = address
}
p := Person {}
p.SetInfo("zz", 20)
p.SetHobby([]string{"eat","coding","sleep"})
p.SetRecord(map[string]string{
"1": "200",
"2": "300",
})
p.SetAddress(Address{city:"shanghai", phone: "136xxxxx666"})
fmt.Println(p) // {zz 20 [eat coding sleep] map[1:200 2:300] {shanghai 136xxxxx666}}
3.结构体的继承
type Animal struct {
name string
}
func (a Animal) eat (){
fmt.Printf("%v 在吃饭\n", a.name)
}
type Person struct {
age int
Animal // 继承Animal
}
p := Person {
name: "zz",
age: 20
}
p.eat()
fmt.Println(p) // {20 {zz}}
4.结构体对象和Json字符串之间的转行
// Marshal 和 Unmarshal
// 通过`json:"name"`指定tag实现json序列化的key
type People struct {
Age int `json:"age"`
Animal
}
type Animal struct {
Name string `json:"name"`
}
p := People {
Name: "zz",
Age: 20,
}
// 结构体 to json string
jsonByte, _ := json.Marshal(p)
fmt.Println(string(jsonByte)) // {"age":20,"name":"zz"}
// json string to 结构体
str := `{"Age":33,"Name":"ww"}`
p2 := People {}
err := json.Unmarshal([]byte(str), &p2)
if err != nil {
fmt.Println("error")
} else {
fmt.Printf("%v\n",p2) // {33 {ww}}
}
结构体嵌套结构体
type Student struct {
ID int
Gender string
Name string
}
type Class struct {
Title string
Students []Student
}
c := &Class{
Title: "001",
Students: make([]Student, 0, 200),
}
for i := 0; i < 2; i++ {
// 添加学生
stu := Student{
Name: fmt.Sprintf("stu%02d", i),
Gender: "男",
ID: i,
}
c.Students = append(c.Students, stu)
}
fmt.Println(*c) // {001 [{0 男 stu00} {1 男 stu01}]}
str =
`{
"Title":"001",
"Students":[{
"ID":0,
"Gender":" 男",
"Name":"stu00"
},{
"ID":1,
"Gender":"男",
"Name":"stu01"
}]
}`
c1 := &Class{}
json.Unmarshal([]byte(str), c1)
fmt.Printf("%#v\n", *c1)
// main.Class{Title:"001", Students:[]main.Student{main.Student{ID:0, Gender:" 男", Name:"stu00"}, main.Student{ID:1, Gender:"男", Name:"stu01"}}}
十一.包管理工具 go mod
// 初始化一个新的 module
god mod init 项目名
// 定义一个包
package 包名
// 1.go内置方法和包
https://pkg.go.dev/std
// 2.初始化第三方包
go get github.com/shopspring/decimal
// 3.下载依赖第三方包
go mod tidy
目录结构如下:
demo
|- clac
|- clac.go
|- tools
|- a.go
|- b.go
|- c.go
|- go.mod
|- main.go
go.mod
module demo
go 1.27.0
main.go
package main
import (
"fmt"
"demo/calc"
"demo/tools"
)
func init() {
fmt.Println("main init")
}
func main() {
fmt.Println("main")
fmt.Println(calc.Add(2, 3), calc.Sum(2, 3), calc.Global)
tools.A()
tools.B()
tools.C()
/* 依次输出
calc init
A init
B init
C init
main init
main
5 -1 共有变量
A
B
C
*/
}
clac.go
package calc
import "fmt"
var global = "私有变量"
var Global = "共有变量"
func init() {
fmt.Println("calc init")
}
func Add(x, y int) int { // Add 共有办法 add 私有方法
return x + y
}
func Sum(x, y int) int {
return x - y
}
a.go
package tools
import "fmt"
func init() {
fmt.Println("A init")
}
func A() {
fmt.Println("A")
}
b.go
package tools
import "fmt"
func init() {
fmt.Println("B init")
}
func B() {
fmt.Println("B")
}
c.go
package tools
import "fmt"
func init() {
fmt.Println("C init")
}
func C() {
fmt.Println("C")
}
使用第三方包
import (
"fmt"
"github.com/shopspring/decimal"
"github.com/tidwall/gjson"
)
func main() {
price, err := decimal.NewFromString("136.11")
if err != nil {
panic(err)
} else {
fmt.Printf("%v %T\n", price, price) // 136.11 decimal.Decimal
}
const json = `{"name":"ww","age":33,"hobby":["eat","coding","sleep"]}`
println(gjson.Get(json, "hobby").String()) // ["eat","coding","sleep"]
}
十二.接口
// 接口是一个规范,统一设计的标准
type USB interface {
start()
stop()
}
// 手机
type Phone struct {
Name string
}
// 接口有方法,通过结构体实现这个接口
func (p Phone) start() {
fmt.Printf("%v开机\n", p.Name)
}
func (p Phone) stop() {
fmt.Printf("%v关机\n", p.Name)
}
// 照相机
type Camera struct{}
func (p Camera) start() {
fmt.Println("照相机开机")
}
func (p Camera) stop() {
fmt.Println("照相机关机")
}
func (p Camera) run() {
fmt.Println("照相机运行")
}
// 电脑
type Computer struct{}
func (c Computer) work(u USB) {
u.stop()
fmt.Println("连接电脑")
u.start()
}
func main() {
p := Phone{
Name: "华为",
}
c := Camera{}
c.run() // 照相机运行
t := Computer{}
t.work(p) // 华为关机 -> 连接电脑 -> 华为开机
t.work(c) // 照相机开机 -> 连接电脑 -> 照相机关机
}
2.空接口和类型断言
// 空接口 表示没有任何约束,任意类型都可实现
type TempInterface interface{}
func main() {
var a TempInterface = "123"
var b TempInterface = true
fmt.Println(a, b)
// Go 1.18及以上,用any,1.18以下,用interface{}
var c = []interface{}{"123", true, 12.04}
var d = []any{"123", true, 12.04}
fmt.Println(c, d)
// 推荐
var e = map[string]any{
"name": "ww",
"age": 33,
"male": true,
}
fmt.Println(e)
// 类型断言 判断空接口中值的类型 x.(T)
v, ok := a.(string)
if ok {
fmt.Println(v) // 123
} else {
fmt.Println("error")
}
}
func (c Computer) work(u USB) {
// 断言,判断u类型
if _, ok := u.(Phone); ok {
fmt.Println("手机")
} else {
fmt.Println("相机")
}
}
十三.结构体实现多接口
结构体接受者:接受值和接受指针的区别 接受指针,不能
type Animal interface {
SetName(string)
GetName() string
}
type Dog struct {
Name string
}
func (d *Dog) SetName(name string) {
d.Name = name
}
func (d Dog) GetName() string {
return d.Name
}
var d Animal = &Dog{
Name: "wangcai",
}
d.SetName("ww")
fmt.Println(d.GetName()) // 不能通过d.Name获取
结构体实现多个接口
type AInterface interface {
GetName() string
}
type BInterface interface {
SetName(string)
}
type Animal interface {
AInterface
BInterface
}
接口嵌套
type SayInterface interface {
say()
}
type MoveInterface interface {
move()
}
// 接口嵌套
type Animal interface {
SayInterface
MoveInterface
}
十四.协程
import (
"fmt"
"runtime"
"sync"
"time"
)
func test2() {
for i := 0; i < 10; i++ {
fmt.Println("主线程,golang-", i)
time.Sleep(time.Millisecond * 50)
}
}
func test1() {
defer wg.Done() // 协程执行完毕
// 开一个goroutine,该协程,每隔100毫秒,输出"你好,golang"
for i := 0; i < 10; i++ {
fmt.Println("协程,golang-", i)
time.Sleep(time.Millisecond * 100)
}
}
// 协程计数器
var wg sync.WaitGroup
func main() {
/*
进程:正在执行的程序
线程:进程分配的多个任务
并发:多个线程同时竞争一个位置,只有一个线程执行
并行:多个线程可以同时执行
单核CPU运行就是并发,多线程在多核CPU运行就是并行
主线程(进程),可以起多个协程,线程(Java,C)2M,协程(Go)2KB
*/
fmt.Println("主线程---当前电脑CPU个数:", runtime.NumCPU())
wg.Add(1) // 协程计数器+1
go test1() // 开启一个协程
test2() // 主线程
wg.Wait() // 等待协程执行完毕
}
十五.管道
// 管道创建
ch := make(chan int, 3)
// 管道存数据
ch <- 10
// 管道关闭
close(ch)
// 管道获取
fmt.Println(<-ch)
// 管道长度和容量
fmt.Printf("值:%v 容量:%v 长度:%v\n", ch, cap(ch), len(ch))
默认双向管道,声明只读<-chin,声明只写chin<-。
// produce 函数只能向 ch 写入数据(只写通道)
func produce(ch chan<- int) {
ch <- 10
ch <- 12
close(ch) // 写完记得关闭
}
// consume 函数只能从 ch 读取数据(只读通道)
func consume(ch <-chan int) {
for num := range ch {
fmt.Println(num)
}
}
// 1. 在 main 中声明一个正常的“双向通道”
ch := make(chan int, 2)
// 2. 传递给函数时,会自动隐式转换为单向通道
go produce(ch)
consume(ch)
select多路复用,如果通道缓冲区满了,或者没有协程在接收,正常写入会阻塞。
// 创建一个容量为 1 的有缓冲通道
ch := make(chan int, 1)
ch <- 100 // 此时通道满了
// 再次尝试非阻塞写入
select {
case ch <- 200:
fmt.Println("成功写入 200")
default:
// 如果通道满了,立刻走到这里,不会阻塞
fmt.Println("通道已满,写入失败(避免了死锁/阻塞)")
}
十六.并发安全和锁
互斥锁是传统并发编程中对共享资源进行访问控制的主要手段, 它由标准库 sync 中的 Mutex
结构体类型表示。sync.Mutex 类型只有两个公开的指针方法,Lock 和 Unlock。Lock 锁定当
前的共享资源,Unlock 进行解锁。1.互斥锁(Mutex)
// 两个协程同时去加同一个数字。如果不加锁,数字会错乱;加了锁,结果就绝对正确。
var count = 0
var mu sync.Mutex // 1. 声明互斥锁
// 协程 A:加 1000 次
go func() {
for i := 0; i < 1000; i++ {
mu.Lock() // 2. 加锁
count++ // 3. 修改公共变量
mu.Unlock() // 4. 解锁
}
}()
// 协程 B:加 1000 次
go func() {
for i := 0; i < 1000; i++ {
mu.Lock() // 2. 加锁
count++ // 3. 修改公共变量
mu.Unlock() // 4. 解锁
}
}()
time.Sleep(time.Second) // 等待两个协程执行完毕
fmt.Println("最终结果:", count) // 必定输出 2000
2.读写锁(RWMutex)
var score = 100
var rwMu sync.RWMutex // 1. 声明读写锁
// 协程 1:读取数据(用 RLock)
go func() {
rwMu.RLock() // 加读锁
fmt.Println("读协程 1 看到分数:", score)
rwMu.RUnlock()
}()
// 协程 2:读取数据(用 RLock,可以和协程 1 同时读)
go func() {
rwMu.RLock() // 加读锁
fmt.Println("读协程 2 看到分数:", score)
rwMu.RUnlock()
}()
// 协程 3:修改数据(用 Lock,排斥所有人)
go func() {
rwMu.Lock() // 加写锁
score = 200
fmt.Println("写协程 修改分数完毕")
rwMu.Unlock()
}()
time.Sleep(time.Second) // 等待所有协程执行完
十七.反射
ORM框架,用的就是反射机制。 ORM:对象关系映射(Object Relational Mapping,简称 ORM)是通过使用描述对象和数据库 之间映射的元数据,将面向对象语言程序中的对象自动持久化到关系数据库中。
num := 100
// 1. 获取类型 (TypeOf)
t := reflect.TypeOf(num)
fmt.Println("类型是:", t) // 输出: int
// 2. 获取值 (ValueOf)
v := reflect.ValueOf(num)
fmt.Println("值是:", v) // 输出: 100
进阶,类型名字t.Name(),底层种类t.Kind()
u := User{Name: "ww"}
// ======= 1. reflect.TypeOf 进阶 =======
t := reflect.TypeOf(u)
// t.Name() 是你定义的类型名字 -> "User"
// t.Kind() 是 Go 底层认定的种类 -> "struct"
fmt.Println("类型名字(Name):", t.Name())
fmt.Println("底层种类(Kind):", t.Kind())
// ======= 2. reflect.ValueOf 进阶 =======
v := reflect.ValueOf(u)
// 拿到值对象 v 之后,可以通过它的接口把值“还原”或“提取”出来
fmt.Println("直接打印值(ValueOf):", v) // 输出: {ww}
// 提取出真正的底层类型
realString := v.Field(0).String() // 获取结构体第一个字段并转为 string
fmt.Println("提取出字段的值:", realString) // 输出: ww
// 还原回普通变量 (Interface)
originalData := v.Interface().(User) // 还原并断言回 User 类型
fmt.Println("还原后的结构体:", originalData.Name) // 输出: ww
// 不要乱用反射,大量使用反射的代码通常难以理解。