Go!

文章目录

  • Go!
    • 变量定义
      • 变量
      • 常量
    • 基本数据类型
      • int
      • float
      • bool
      • string
      • byte
      • rune
      • 修改字符串
      • strconv
    • 流程控制
      • for
      • for range
      • switch
      • goto
    • 数组
    • 切片
      • 基于数组定义切片
      • 基于切片再切片
      • 关于切片的长度和容量
      • 本质
      • make()创建一个切片
      • append()
      • copy()
      • 从切片中删除元素
    • sort包
    • 复合数据类型
      • map
    • 函数
      • 函数定义
      • 函数的可变参数
      • 函数类型与变量
      • 匿名函数和闭包
      • defer
      • panic + recover
    • time包及日期函数
    • 指针(童年阴影)
    • 结构体(害怕)
      • 结构体的定义
      • 结构体实例化
      • 结构体方法和接收者
      • 结构体嵌套和继承
      • 结构体序列化与反序列化Json
      • 嵌套结构体的序列化与反序列化
    • go mod以及go包
      • go包管理工具go mod
      • init()函数
      • go中使用第三方包
        • 安装这个包
        • 引入这个包
    • 接口
      • 接口的定义
      • 空接口
      • 类型断言
      • 结构体值接收者和指针接收者实现接口的区别
      • 接口方法有返回值的情况
      • 一个结构体实现多个接口,接口嵌套
      • 空接口和类型断言的使用细节
    • goroutine协程
      • 为什么要使用goroutine
      • Golang中的协程(goroutine) 以及主线程
      • Goroutine的使用以及sync.WaitGroup
        • 使用sync.WaitGroup
      • 设置golang并行运行时候占用的cpu数量
      • 一个需求
    • channel管道
      • channel类型
      • 创建channel
      • channel操作
      • goroutine结合channel
      • gorutine结合channel实现统计素数
      • 单向管道
      • select多路复用
      • goruntine recover解决协程中出现的panic
    • go并发安全和锁
      • 互斥锁
      • 读写锁
    • 反射
      • 反射的基本介绍
      • go可以实现的功能
      • reflect.TypeOf()获取任意值的类型对象
      • reflect.ValueOf()
      • 结构体反射
      • 不要乱用反射!
    • 文件 目录操作
      • 读取文件
      • 写入文件
      • 目录操作

变量定义

变量

var

str := "aaa"  //自动推断字符串类型
num := 10	//int类型var(n1 = 10n2 = 20n3 = 30
)

常量

const

const NAME := "kk"
const(N1 = 0N2 = 1N3 = 3
)//iota 计数器
const (a = iota	//0b			//1c			//2_			//跳过d			//4
)const (a = iota	//0b = 100		//100c = iota	//2d			//3
)const (a, b = iota + 1, iota + 2 //1,2c, d                      //2,3e, f                      //3,4
)

基本数据类型

int

image-20200604151413054

强制转换

var a1 int32 = 10
var a2 int64 = 20
fmt.Println(int64(a1) + a2)    //30
num := 12
fmt.Printf("num=%v\n", num) //%v 原样输出
fmt.Printf("num=%d\n", num) //%d 表示10进制输出
fmt.Printf("num=%b\n", num) //%b表示二进制输出
fmt.Printf("num=%o\n", num) //%o八进制输出
fmt.Printf("num=%x\n", num) //%x表示16进制输出

float

image-20200604153344376

使用科学计数法表示浮点数据

var f2 float32 = 3.14e2 //表示f2等于3.14*10的2次方
fmt.Printf ("%v--%T",f2,f2)
//314--float32

bool

var flag = true

string

var str = "string"

常用方法

引入strings包

方法 介绍
len(str) 长度
+或fmt.Sprintf 拼接字符串
strings.Split 分割
strings.contains 判断是否包含
strings.HasPrefix,strings.HasSuffix 前缀/后缀判断
strings.Index(),strings.LastIndex() 子串出现的位置
strings.join(a[] string, sep string) join操作

byte

字节嘛

rune

这个和char差不多

用增强型for循环遍历就不会把中文搞开了

s := "你好golang"
for _,v := range s{fmt.Printf("%v(%c)",v,v)
}//20320(你) 22909(好) 32( ) 103(g) 111(0) 108(1) 97(a) 110(n) 103(g)

修改字符串

image-20200604162559514

strconv

把其他类型转换成string类型,也可以反向转换

i := 20
str1 := strconv.FormatInt(int64(i),10)		//两个参数,要转换的变量(要求int64),进制

流程控制

for

for i := 0;i<10;i++{fmt.Print("*")
}

for range

增强型for循环

for k,v := range str{fmt.Printf("%v---%c",k,v)	
}
//str是个字符串数组,k为字符的下标,v为字符
//不想打印下标也可以用_省略for _,v := range str{fmt.Printf("%c",v)
}

switch

var n = 8
switch n {case 1,3,5,7,9 :fmt.Println("奇数")breakcase 2,4,6,8,10:fmt.Println("偶数")break
}

注意:

  • go中的switch case分支语句可以有多个值
  • 每一个case可以不用写break,不会穿透,不过还是写上比较好
  • 使用fallthrough可以进行手动穿透,只能穿透一层

goto

var n = 30
if n > 24{fmt.Println("成年人")goto label
}
fmt.Println("aaa")
fmt.Println("bbb")label:
fmt.Println("ccc")
fmt.Println("ddd")

说明,当走到goto之后,程序会直接跳到对应的label,label中间的就不会被执行了

数组

//1 初始化0值
var nums = [3]int
var strs = [4]string//2 初始化
var arr = [4]int{0,1,2,3}//3 自行推断数组长度
var arr1 = [...]string{"php","java","golang"}fmt.Println(arr)  //[0,1,2,3]

**值类型:**改变变量副本的值,不会改变变量本身的值

**引用类型:**改变变量副本的值,会改变变量本身的值

切片

就是把声明数组时把长度去掉

var name []int	//声明了切片以后,切片的默认值就是nil

基于数组定义切片

a := [5]int{1,2,3,4,5}	//定义了一个长度为5的数组
b := a[:]	//获取数组里面的所有值
c := a[1:4]	//指定获取数组中的内容组成切片,左闭右开	1,2,3
d := a[2:]	//3,4,5
e :=a[:3]	//1,2,3

基于切片再切片

a := []string{"北京","上海","广州","深圳","成都","重庆"}
b := a[1:]	//差不多

关于切片的长度和容量

切片拥有自己的长度和容量,我们可以通过使用内置的len()函数求长度,使用内置的cap()函数求切片的容量。

  • 长度:切片的长度就是它所包含的元素个数
  • 容量:切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。
s := []int{1,2,3,4,5,6}
fmt.Printf("长度%d 容量%d\n", len(s)cap(s)) //长度6容量6a := s[2:]
fmt.Printf("长度%d 容量%d\n", len(a)cap(a)) //长度4容量4b := s[1:3]
fmt.Printf("长度%d 容量%d\n", len(b)cap(b)) //长度2容量5	底层数组末尾,所以是5

本质

本质就是对底层数组的封装,包含三个信息,底层数组的指针,切片长度len,和切片容量cap

make()创建一个切片

var sliceA = make([]int,4,8)	//make(切片类型,len,cap)	有默认值,打印一下是[0 0 0 0]

append()

扩容

//golang中没法通过下标的方式给切片扩容
//golang中给切片扩容的话要用到append()方法
var sliceA []int
sliceA = append( sliceA,12)
sliceA = append(sliceA,24)//一次传入多个值
sliceA = append(sliceA,1,2,3,4)

合并

sliceA := []string{"php", "java"}
sliceB := []string{"nodejs", "go"}
sliceA = append(sliceA, sliceB...)	//切片合并	[php java nodejs go]

切片的扩容策略

有三种扩容策略,上源码

newcap := old.capdoublecap := newcap + newcapif cap > doublecap {newcap = cap} else {if old.len < 1024 {newcap = doublecap} else {// Check 0 < newcap to detect overflow// and prevent an infinite loop.for 0 < newcap && newcap < cap {newcap += newcap / 4}// Set newcap to the requested cap when// the newcap calculation overflowed.if newcap <= 0 {newcap = cap}}}

copy()

sliceA := []int{1, 2, 3, 45}
sliceB := make([]int, 4, 4)
copy(sliceB, sliceA)		//直接进行赋值是浅拷贝,引用传递,使用copy就是深拷贝,值传递了sliceB[0] = 111				//此时对切片进行改变并不会影响原来切片的值
fmt.Print1n(sliceA)
fmt.Println(sliceB )

从切片中删除元素

Go语言中并没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除元素

a := []int{30,31 ,32 ,33 ,34,35,36,37}	//想要删除索引为2的元素
a = append(a[1:2],a[3:]...)			//使用append进行操作,appen合并的时候最后一个元素要加...

sort包

升序

sort.Ints()		//整型排序
sort.Float64s()	//浮点型排序
sort.Strings()	//字符串排序

降序

sort.Sort(sort.Reverse(sort.IntSlice(intList)))		//整型降序
sort.Sort(sort.Reverse(sort.Float64Slice(floatList)))	//浮点型降序
sort.Sort(sort.Reverse(sort.StringSlice(floatList)))	//字符串降序

复合数据类型

map

k-v

使用make创建一个map

make(map[KeyType]ValueType,[cap])

cap表示map的初始容量,该参数不是必须的

var userinfo = make(map[string]string)
userinfo["name"] = "张三"

在声明map的时候直接填充数据

var userinfo = map[string]string{"username":	"张三","age": "20","gender":"男",
}

循环遍历map

for k,v := range userinfo{fmt.Printf("%v:%v",k,v)
}

map的crud

//创建,修改map数据
userinfo := make(map[string]string)
userinfo["name"] = "张三"
userinfo["age"]	= "20"
fmt.Println(userinfo)	//map[name:张三]//获取map的数据
username := userinfo["name"]
fmt.Println(username)	//张三//查看map中是否包含key
v,ok := userinfo["name"]
fmt.Println(v,ok)		//张三 true//v,ok := userinfo["xxx"]
//fmt.Println(v,ok)		//(空) false//删除map中的kv
delete(userinfo,"name")
fmt.Println(userinfo)	//map[age:20]

创建元素为map类型的切片

//我们想在切片里面放一系列用户的信息,这时候我们就可以定义一个元素为map类型的切片
userinfo := make([]map[string]string,3,3)
fmt.Println(userinfo)	//map[]		map不初始化的默认值nil
if userinfo[0] == nil {userinfo[0] = make(map[string]string)userinfo[0]["name"] = "张三"userinfo[0]["age"] = "20"userinfo[0]["height"] = "180cm"userinfo[0]["gender"] = "男"
}
if userinfo[1] == nil {userinfo[1] = make(map[string]string)userinfo[1]["name"] = "李四"userinfo[1]["age"] = "21"userinfo[1]["height"] = "179cm"userinfo[1]["gender"] = "女"
}
for _,v := range userinfo{fmt.Println(v)
}
/**[map[] map[] map[]]map[age:20 gender:男 height:180cm name:张三]map[age:21 gender:女 height:179cm name:李四]map[]
*/

将切片作为map的value

//如果我们想在map对象中存放一系列的属性的时候,我们就可以把map类型的值定义成切片
userinfo := make(map[string][]string)
userinfo["hobby"] = []string{"吃饭","睡觉","rip",
}
userinfo["work"] = []string{"需求","设计","实现",
}
fmt.Println(userinfo)/**map[hobby:[吃饭 睡觉 rip] work:[需求 设计 实现]]
*/

map也是引用类型

小练习

//写一个程序,统计一个字符串中每个单词出现的次数。比如: "how do you do"中how=1 do=2 you=1
str := "how do you do"
strs := strings.Split(str," ")
count := make(map[string]int)
for _,v := range strs{count[v]++
}
fmt.Println(count)

函数

函数定义

func 函数名(参数)(返回值){函数体
}

两数相加

func twoSum(x int, y int) int {return x + y
}
func main() {fmt.Println(twoSum(11,2))
}

如果入参的类型是一样的,可以省略,直接写最后

func twoSum(x, y int) int {return x + y
}

函数的可变参数

理解为一个切片

func change(x ...int) {fmt.Printf("%v----%T",x,x)
}func main(){change(1,2,3,4,5,9,2)	//[1 2 3 4 5 9 2]----[]int		
}
//代表参数中,第一个参数赋给x,剩下的都赋给y
func change(x int, y ...int) {fmt.Printf(x,y)sum := Xfor v := range y{sum += v}return sum
}

多个返回值

func calc(x, y int) (int,int) {sum := x+ysub := x-yreturn sum, sub
}
//给返回值命名,这样就不用在函数体中声明返回值了
func calc(x, y int) (sum, sub int) {sum = x+ysub = x-yreturn sum, sub
}

封装一个降序排序函数

func sortIntDesc(since []int)  {sort.Sort(sort.Reverse(sort.IntSlice(since)))
}

函数类型与变量

定义函数类型

type calc func(int, int) int //定义一个为calc的函数类型func add(x, y int) int {return x + y
}
func sub(x, y int) int {return x - y
}func calculation(x, y int,op calc) int {return op(x,y)
}func main() {var c calcc = addfmt.Println(calculation(2,3,c))	//5
}
/**
也可以传一个匿名函数
fmt.Println(calculation(2,3, func(x int, y int) int {return x * y}))
*/

让函数返回函数

type calc func(int, int) int //定义一个为calc的函数类型func add(x, y int) int {return x + y
}
func sub(x, y int) int {return x - y
}
func mul(x, y int) int {return x * y
}
func div(x, y int) int {return x / y
}func myfun(o string) calc {switch o {case "+":return addcase "-":return subcase "*":return mulcase "/":return divdefault:return nil}
}func main() {c := myfun("*")fmt.Println(c(2,3))	//6
}

匿名函数和闭包

匿名函数没有名字

func(参数)(返回值){函数体
}
func main(){//匿名函数func() {fmt.Println("test..")}()	//在这里我们加一个() 表示执行这个匿名函数本身	匿名自执行函数//匿名自执行函数接收参数func(x, y int) {fmt.Println(x +y)}(10, 20)}

闭包

全局变量特点:

  • 常驻内存
  • 可能污染全局

局部变量的特点:

  • 不常驻内存
  • 不污染全局

闭包

  • 可以让一个变量常驻内存
  • 可以让一个变量不污染全局

闭包是指有权访问另一个函数作用域中的变量的函数

创建闭包的常见的方式就是在一个函数内部创建另一个函数,通过另一个函数访问这个函数的局部变量

注意:由于闭包里作用域返回的局部变量资源不会被立刻销毁回收,所以可能会占用更多的内存。过度使用闭包会导致性能下降,建议在非常有必要的时候才使用闭包。

func adder() func() int {var i = 10return func() int {return i + 1}
}func adder1() func(y int) int {var i = 10return func(y int) int {i += yreturn i}
}func main() {var fn = adder()	//执行方法fmt.Println(fn())	//11fmt.Println(fn())	//11fmt.Println(fn())	//11var fn1 = adder1()	//执行方法fmt.Println(fn1(10))	//20fmt.Println(fn1(10))	//30fmt.Println(fn1(10))	//40
}

defer

Go语言中的defer语句会将其后面跟随的语句进行延迟处理。在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行,也就是说,先被defer的语句最后被执行,最后被defer的语句,最先被执行。

defer

把语句像栈一样执行,先进后出

func main() {fmt.Println("开始")fmt.Println(1)fmt.Println(2)fmt.Println(3)fmt.Println("结束")
}
/**开始	1 2 3 结束
*/
func main() {fmt.Println("开始")defer fmt.Println(1)defer fmt.Println(2)defer fmt.Println(3)fmt.Println("结束")
}
/**开始	3 2 1 结束
*/

defer在命名返回值和匿名返回函数中表现不一样

func f1() {fmt.Println("开始")defer func() {fmt.Println("aaaa")}()		//注意:此处必须是匿名自执行方法fmt.Println("结束")
}
func main() {f1()
}
/**开始结束aaaa
*/
//匿名返回值		执行结果是0
func f2() int {var a intdefer func() {a++}()return a
}
func main() {fmt.Println(f2())		//0
}
//命名返回值		执行结果是1
func f2() (a int) {defer func() {a++}()return a
}
func main() {fmt.Println(f3())		//1
}

defer执行时机

在Go语言的函数中return语句在底层并不是原子操作,它分为给返回值赋值和RET指令两步。而defer语句执行的时机就在返回值赋值操作后,RET 指令执行前。具体如下图所示:

image-20200606122456224

panic + recover

Go语言中目前是没有异常机制,但是使用panic/recover 模式来处理错误。

panic可以在任何地方引发,但recover只有在defer调用的函数中有效。

func fn1()  {fmt.Println("fn1")
}
func fn2()  {panic("抛出一个异常")
}func main() {fn1()fn2()fmt.Println("结束")
}
/**程序遇到panic会直接结束运行并抛出异常
*/

使用recover来接收异常

go里面没有try catch,所以使用panic和recover来进行异常处理

func fn1()  {fmt.Println("fn1")
}
func fn2()  {defer func() {err := recover()if err != nil {fmt.Println("err:",err)}}()panic("抛出一个异常")
}func main() {fn1()fn2()fmt.Println("结束")
}

处理异常的例子

func fn3(a,b int) int {defer func() {err := recover()if err != nil {fmt.Println("error:",err)}}()return a/b
}func main() {fmt.Println(fn3(3, 0))fmt.Println("结束")
}
/**error: runtime error: integer divide by zero0结束
*/

说白了就跟try catch一样,只不过需要在defer中用recover捕获异常,注意defer的一定是一个自执行函数

使用recover捕获异常之后,后续的代码是可以继续执行的

例子:模拟读取文件失败

func readFile(fileName string) error {if fileName == "main.go" {return nil}else {return errors.New("读取文件失败")}
}func myFun() {defer func() {err := recover()if err != nil {fmt.Println("error:",err,"\n给管理员发邮件")}}()err := readFile("xxx.go")if err != nil {panic(err)}
}func main() {myFun()
}/**error: 读取文件失败 给管理员发邮件
*/

time包及日期函数

最拉夸的方式

func main() {now := time.Now()fmt.Println(now)	//2020-06-06 14:29:14.8076067 +0800 CST m=+0.011968301year := now.Year()month := now.Month()day := now.Day()hour := now.Hour()minute := now.Minute()second := now.Second()//year, month, day := now.Date()fmt.Printf("%d-%02d-%02d %02d:%02d:%02d",year, month, day,hour,minute,second)		//2020-06-06 14:38:23
}

格式化

需要注意的是Go语言中格式化时间模板不是常见的Y-m-d H:M:S
而是使用Go的诞生时间2006年1月2号15点04分(记忆口诀为2006 1 2 3 4)

  • 2006 年
  • 01 月
  • 02 日
  • 03 时 12小时制 15 24小时制
  • 04 分
  • 05 秒
func main() {now := time.Now()fmt.Println(now)	//2020-06-06 14:44:39.9233244 +0800 CST m=+0.007976001nowFormat1 := now.Format("2006-01-02 03:04:05")fmt.Println(nowFormat1)						//2020-06-06 02:42:50nowFormat2 := now.Format("2006-01-02 15:04:05")fmt.Println(nowFormat2)						//2020-06-06 14:44:39}

获取当前时间戳

时间戳是自1970年1月1日(08:00:00GMT)至当前时间的总毫秒数。它也被称为Unix时间戳(UnixTimestamp) 。

func main() {now := time.Now()timeStamp := now.Unix()fmt.Println(timeStamp)		//1591426031
}

将时间戳转化为日期格式

func main() {now := time.Now()timeStamp := now.Unix()fmt.Println(timeStamp)		//1591426031//将时间戳转化为日期格式fmt.Println(time.Unix(timeStamp, 0).Format("2006-01-02 15:04:05"))	//2020-06-06 14:51:14
}

将日期转换为时间戳

顺便复习一下recover和panic

func main() {str := "2020-06-06 14:51:14"	tmp := "2006-01-02 15:04:05"defer func() {err := recover()if err != nil {fmt.Println("时间转换错误", err)//str := "2020/06-06 14:51:14"	//时间转换错误 parsing time "2020/06-06 14:51:14" as "2006-01-02 15:04:05": cannot parse "/06-06 14:51:14" as "-"}}()location, err := time.ParseInLocation(tmp, str, time.Local)if err != nil {panic(err)} else {unix := location.Unix()fmt.Println("时间戳是", unix)		//时间戳是 1591426274}
}

时间间隔类型常量

1、time包中定义的时间间隔类型的常量如下:
const (
Nanosecond Duration = 1
Microsecond 		= 1000 * Nanosecond
Millisecond			= 1000 * Microsecond
Second				= 1000 * Millisecond
Minute 				= 60 * Second
Hour				= 60 * Minute
)func main(){// fmt.Println(time.Millisecond) //1ms// fmt.Println(time.Second)	//1s
}

时间操作函数

Add

时间+时间间隔

func (t Time) Add(d Duration) Time

Sub 时间差

Equal 比较时间,会考虑时区

定时器

使用time.NewTicker(时间间隔)来设置定时器

func main(){ticker := time.NewTicker(time.Second)n := 0for t := range ticker.C{n++if n > 5 {			//一共执行5次任务ticker.Stop()	//停止计时器return}fmt.Println(t)fmt.Println("offer")}
}

使用time.Sleep()休眠

func main(){for i := 5 ;i > 0 ;i-- {fmt.Println("offer")time.Sleep(time.Second)		//休眠1秒,相当于TimeUnit.SECOND.sleep(1)}
}

指针(童年阴影)

指针也是一个变量,但它是一种特殊的变量,它存储的数据不是一个普通的值,而是另一个变量的内存地址。

func main() {a := 10p := &afmt.Printf("a: %v,%T,%p\n",a,a,&a)fmt.Printf("p: %v,%T,%p\n",p,p,&p)fmt.Printf("%v",*p)					//*p : 取出这个地址对应的值,即a的值
}
/**a: 10,int,0xc00000a0a8p: 0xc00000a0a8,*int,0xc00000602810
*/

指针地址和指针类型

每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。Go语言中使用&字符放在变量前面对变量进行取地址操作。Go 语言中的值类型(int、 float、 bool、 string、array、struct) 都有对应的指针类型,如: *int、 *int64、 *string 等。

简单例子

func fun1(x int) {x = 20		//值传递
}func fun2(x *int) {*x = 40		//引用传递
}func main() {a := 10fun1(a)fmt.Println(a)		//10fun2(&a)fmt.Println(a)		//40
}

指针也是引用类型

这样写会报错

var a *int
*a = 100
fmt.Println(*a)

使用new关键字创建初始化指针变量

new在实际开发中基本用不到**(shit! , java程序员直呼外行)**

func main() {a := new(int)	//a是一个指针变量 类型是*int的指针类型 指针变量对应的值是0fmt.Printf("%v--%T--%v",a,a,*a)		//0xc00000a0a8--*int--0
}

make函数分配内存

主要用于切片,map,channel的内存创建

make和new的区别

  • 二者都是用来做内存分配的。
  • make只用于slice、map以及channel的初始化,返回的还是这三个引用类型本身
  • 而new用于类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针。

结构体(害怕)

type自定义类型与类型别名的区别

func main() {var a myInt = 10fmt.Printf("%v,%T\n",a,a)	//10,main.myIntvar b myFloat = 12.3fmt.Printf("%v,%T\n",b,b)	//12.3,float64
}

结构体的定义

使用type和struct关键字来定义结构体,具体代码格式如下

type 类型名 struct {字段名 字段类型字段名 字段类型
}

其中:

  • ==类型名:==表示自定义结构体的名称,在同一个包内不能重复。
  • ==字段名:==表示结构体字段名。结构体中的字段名必须唯一。
  • ==字段类型:==表示结构体字段的具体类型。

我们定义一个Person结构体

注意,结构体名字首字母可以大写也可以小写,小写代表他是私有的,字段也是一样的

type Person struct {name stringage intgender string
}

结构体实例化

第一种方法 var

(小声BB:相当于java里面的setter)

结构体类型

var 结构体实例 结构体类型
func main(){var p1 Personp1.name = "张三"p1.age = 20p1.gender = "男"fmt.Printf("%v--%T\n",p1,p1)	//{张三 20 男}--main.Personfmt.Printf("%#v--%T",p1,p1)		//main.Person{name:"张三", age:20, gender:"男"}--main.Person
}

%#v代表详细信息(相当于toString)

第二种方式 new

(java菜逼直呼内行)

指针类型

func main(){var p2  = new(Person)p2.name = "李四"p2.age = 20p2.gender = "男"fmt.Printf("%#v--%T",p2,p2)		//&main.Person{name:"李四", age:20, gender:"男"}--*main.Person
}

从打印的结果中我们可以看出p2是一个结构体指针。
注意:在Golang中支持对结构体指针直接使用.来访问结构体的成员。p2.pname = "张三”其实在底层是(*p2).name = "张三”

第三种方式 直接结构体地址赋值

(无参构造器?)

指针类型

func main(){p3 := &Person{}p3.name = "花玲"p3.age = 18p3.gender = "女"fmt.Printf("%#v--%T",p3,p3)		//&main.Person{name:"花玲", age:18, gender:"女"}--*main.Person
}

第四种方式 键值对赋值

(有参构造器?)

结构体类型

func main(){p4 := Person{name:   "花玲",age:    18,gender: "女",}fmt.Printf("%#v--%T",p4,p4)		//main.Person{name:"花玲", age:18, gender:"女"}--main.Person
}

第五种方式 指针加赋值

指针类型

func main(){p5 := &Person{name:   "花玲",age:    18,gender: "女",}fmt.Printf("%#v--%T", p5, p5) //&main.Person{name:"花玲", age:18, gender:"女"}--*main.Person
}

注意,初始化的时候可以只给部分字段赋值,剩下的就是默认值,0值或空值

第六种方式 不加字段名,但是要一一对应

指针类型

func main(){p6 := &Person{"花玲",18,"女",}fmt.Printf("%#v--%T", p6, p6)	//&main.Person{name:"花玲", age:18, gender:"女"}--*main.Person
}

结构体方法和接收者

在go语言中,没有类的概念但是可以给类型(结构体,自定义类型)定义方法。

所谓方法就是定义了接收者的函数。

接收者的概念就类似于其他语言中的this或者self。

方法定义的格式如下

func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {函数体
}

其中:

  • **接收者变量:**接收者中的参数变量名在命名时,官方建议使用接收者类型名的第一个小写字母,而不是self、 this 之类的命名。例如,Person 类型的接收者变量应该命名为p,Connector类型的接收者变量应该命名为c等。
  • **接收者类型:**接收者类型和参数类似,可以是指针类型和非指针类型。

定义一个Person结构体,定义一个方法打印person的信息

type Person2 struct {name   stringage    intgender stringheight int
}func (p Person2) PrintInfo() {fmt.Printf("姓名:%v 年龄:%v", p.name, p.age)
}func main() {p1 := Person2{name:   "花玲",age:    18,gender: "女",height: 165,}p1.PrintInfo()	//姓名:花玲 年龄:18
}

定义一个方法,修改对象信息

注意,想要修改属性,接收者类型必须指定为结构体指针

当然,不修改结构体中的属性的话,不用指定为结构体指针

func (p Person2) PrintInfo() {fmt.Printf("姓名:%v 年龄:%v\n", p.name, p.age)
}
//这里
func (p *Person2) SetName(name string)  {p.name = name
}func main() {p1 := Person2{name:   "花玲",age:    18,gender: "女",height: 165,}p1.PrintInfo()			//姓名:花玲 年龄:18p1.SetName("老婆")p1.PrintInfo()			//姓名:老婆 年龄:18
}

注意:结构体实例是独立的,不会相互影响

结构体嵌套和继承

结构体的匿名字段

就是不给字段取名字,直接定义类型

type person struct {stringint
}

结构体嵌套

听起来好高端,就是DI

type User struct {username stringpassword stringaddress Address	//表示User结构体里面嵌套了Address结构体
}type Address struct {name stringphone stringcity string
}func main() {var u Useru.username = "小明"u.password = "123456"u.address = Address{name:  "小明",phone: "13996459090",city:  "重庆",}/*** 也可以这样* u.address.name = "小明"* u.address.phone = "13996459090"* u.address.city = "重庆"*/	fmt.Println(u)	//{小明 123456 {小明 13996459090 重庆}}
}

当然,你可以写匿名的嵌套结构体,比如u.city = "重庆",由于User结构体本身并没有city这个字段,所以赋值的时候他会去user里面的嵌套结构体里找这个字段并给他赋值

关于嵌套结构体的字段名冲突

  • 如果结构体中有和嵌套结构体中相同的某一字段,那么赋值的时候会给结构体本身的字段赋值
  • 如果有两个嵌套结构体,他们有相同的字段,那么赋值的时候会报错,指定是哪一个嵌套结构体就行了u.Address.Addtime = "2006-01-01"

结构体的继承

通过匿名嵌套结构体实现继承

就这??虽然拉胯,不过确实是实现了继承

type Animal struct {name string
}
func (a Animal) run() {fmt.Printf("%v在运动\n",a.name)
}type Dog struct {age intAnimal
}
func (d Dog) shut()  {fmt.Printf("%v在叫\n",d.name)
}func main() {var dog Dogdog.age = 2dog.name = "旺财"dog.run()			//旺财在运动dog.shut()			//旺财在叫
}

结构体序列化与反序列化Json

注意,想要将字段转换成json的话,字段必须是公有的(即首字母大写)

GolangJSON序列化是指把结构体数据转化成JSON格式的字符串,Golang JSON的反序列化是指把JSON数据转化成Golang中的结构体对象

序列化

通过json.Marshal()序列化

type Student struct {Id intGender stringName stringSno string
}func main() {s1 := Student{Id:     12,Gender: "男",Name:   "张三",Sno:    "s001",}fmt.Println(s1)					//{12 男 张三 s001}marshal, _ := json.Marshal(s1)jsonStr := string(marshal)fmt.Println(jsonStr)			//{"Id":12,"Gender":"男","Name":"张三","Sno":"s001"}
}

反序列化

type Student struct {Id intGender stringName stringSno string
}func main() {var str = `{"Id":12,"Gender":"男","Name":"张三","Sno":"s001"}`		//注意此处是`` 而不是''var s1 Studenterr := json.Unmarshal([]byte(str),&s1)if err != nil {fmt.Println(err)}fmt.Printf("%#v",s1)		//main.Student{Id:12, Gender:"男", Name:"张三", Sno:"s001"}}

结构体标签Tag

我想在json序列化之后将json中的字段首字母改为小写

type Student struct {Id int	`json:"id"`Gender string	`json:"gender"`Name string	`json:"name"`Sno string	`json:"xxx"`
}func main() {s1 := Student{Id:     12,Gender: "男",Name:   "张三",Sno:    "s001",}fmt.Println(s1)					//{12 男 张三 s001}marshal, _ := json.Marshal(s1)jsonStr := string(marshal)fmt.Println(jsonStr)			//{"Id":12,"Gender":"男","Name":"张三","Sno":"s001"}	原来的//{"id":12,"gender":"男","name":"张三","xxx":"s001"}	加了Tag
}

嵌套结构体的序列化与反序列化

序列化

type Student struct {Id intGender stringName string}
type Class struct {Title stringStudents []Student
}
func main() {class := Class{Title:   "6班",Students: make([]Student, 0,200),}for i := 0; i < 10; i++ {s := Student{Id:     i,Gender: "女",Name:   fmt.Sprintf("学生_%v",i),}class.Students = append(class.Students,s)}fmt.Println(class)marshal, _ := json.Marshal(class)jsonStr := string(marshal)fmt.Println(jsonStr)
}
/**
{6班 [{0 女 学生_0} {1 女 学生_1} {2 女 学生_2} {3 女 学生_3} {4 女 学生_4} {5 女 学生_5} {6 女 学生_6} {7 女 学生_7} {8 女 学生_8} {9 女 学生_9}]}
{"Title":"6班",
"Students":[{"Id":0,"Gender":"女","Name":"学生_0"},{"Id":1,"Gender":"女","Name":"学生_1"},{"Id":2,"Gender":"女","Name":"学生_2"},{"Id":3,"Gender":"女","Name":"学生_3"},{"Id":4,"Gender":"女","Name":"学生_4"},{"Id":5,"Gender":"女","Name":"学生_5"},{"Id":6,"Gender":"女","Name":"学生_6"},{"Id":7,"Gender":"女","Name":"学生_7"},{"Id":8,"Gender":"女","Name":"学生_8"},{"Id":9,"Gender":"女","Name":"学生_9"}]}
*/

使用json解析工具可以看到,我们是解析成功的

image-20200606231811206

反序列化

type Student struct {Id intGender stringName string}
type Class struct {Title stringStudents []Student
}func main(){jsonStr := `{"Title":"6班","Students":[{"Id":0,"Gender":"女","Name":"学生_0"},{"Id":1,"Gender":"女","Name":"学生_1"},{"Id":2,"Gender":"女","Name":"学生_2"},{"Id":3,"Gender":"女","Name":"学生_3"},{"Id":4,"Gender":"女","Name":"学生_4"},{"Id":5,"Gender":"女","Name":"学生_5"},{"Id":6,"Gender":"女","Name":"学生_6"},{"Id":7,"Gender":"女","Name":"学生_7"},{"Id":8,"Gender":"女","Name":"学生_8"},{"Id":9,"Gender":"女","Name":"学生_9"}]}`var class Classerr := json.Unmarshal([]byte(jsonStr), &class)if err != nil {fmt.Println(err)}else {fmt.Println(class)}
}
//{6班 [{0 女 学生_0} {1 女 学生_1} {2 女 学生_2} {3 女 学生_3} {4 女 学生_4} {5 女 学生_5} {6 女 学生_6} {7 女 学生_7} {8 女 学生_8} {9 女 学生_9}]}

go mod以及go包

  • 包( package)是多个Go源码的集合,是一种高级的代码复用方案,Go 语言为我们提供了很多内置包,如fmt、strconv、 strings、 sort、 errors、 time、 encoding/json、 OS、io 等。
  • Golang中的包可以分为三种: 1.系统内置包 2.自定义包 3.第三方包
  • 系统内置包: Golang语言给我们提供的内置包,引入后可以直接使用,如fmt、strconv、strings、sort、errors、 time、encoding/json、 OS、io 等。
  • 自定义包:开发者自己写的包
  • 第三方包:属于自定义包的一种,需要下载安装到本地后才可以使用,如前面给大家介绍的"github.com/shopspring/decimal"包解决float精度丢失问题。

go包管理工具go mod

在Golang1.11版本之前如果我们要自定义包的话必须把项目放在GOPATH目录。Go1.11版本之后无需手动配置环境变量,使用go mod管理项目,也不需要非得把项目放到GOPATH指定目录下,你可以在你磁盘的任何位置新建一个项目, Go1.13以后可以彻底不要GOPATH了。

go mod init 初始化项目

实际项目开发中我们首先要在我们项目目录中用go mod命令生成一个go.mod文件管理我们项目的依赖。
比如我们的golang项目文件要放在了某个文件夹,这个时候我们需要在这个文件夹里面使用go mod命令生成一个go.mod文件

go mod简介及参数

image-20200607103933132

image-20200607104831631

某些同学用golan的时候import会爆红,但是又能正常使用包里面的方法

看看你的go moudles有没有开启

image-20200607002312969

另外,通过命令修改go的module开启,以及国内代理设置

go env # 查看go的环境设置

设置命令

go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct

image-20200607002553615

包名很长,给包取一个别名

import ("fmt"T "gomod/calc"
)func main() {sum := T.Add(10,2)fmt.Println(sum)
}

init()函数

在Go语言程序执行时导入包语句会自动触发包内部init()函数的调用。需要注意的是:init()函数没有参数也没有返回值。init()函 数在程序运行时自动被调用执行,不能在代码中主动调用它。

image-20200607003450200

在运行时,被最后导入的包会最先初始化并调用其init()函数,如下图示:

image-20200607003520616

import ("fmt"T "gomod/calc"
)func init() {							//系统自动执行,不需要手动调用fmt.Println("init........")
}func main() {sum := T.Add(10,2)fmt.Println(sum)	
}
/**
init........
12
*/

go中使用第三方包

我们可以在https://pkg.go.dev/ 查找看常见的golang第三方包

找到我们需要下载安装的第三方包的地址

比如前面给大家演示的解决float精度损失的包decimal https://github.com1/shopspring/decimal

安装这个包

第一种方法

go get 包名称 (全局)

go get github.com/shopspring/decimal

第二种方法

go mod download (全局)

第三种方式

go mod vender 将依赖复制到当前项目的vender下(本项目)

引入这个包

可以在第三方包的文档里面看到该怎么引入

一般是import go get后的地址

哎反正你import包就完事儿了,golan会帮你下载的

接口

接口是什么在这里不做阐述,java开发的同学应该很清楚,接口就是一系列动作的规范

接口的定义

  • 在Golang中接口(interface) 是一种类型,一种抽象的类型。接口(interface) 是一-组函数method的集合,Golang 中的接口不能包含任何变量。
  • 在Golang中接口中的所有方法都没有方法体,接口定义了一个对象的行为规范,只定义规范不实现。接口体现了程序设计的多态和高内聚低耦合的思想
  • Golang中的接口也是一种数据类型,不需要显示实现。只需要-一个变量含有接口类型中的所有方法,那么这个变量就实现了这个接口。
type 接口名 interface{方法名1( 参数列表1 )	返回值列表1方法名2( 参数列表2 )	返回值列表2...
}
  • **接口名:**使用type将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加er,如有写操作的接口叫Writer,字符串功能的接口叫Stringer等。接口名最好要能突出该接口的类型含义。
  • **方法名:**当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package) 之外的代码访问。
  • 参数列表,返回值列表: 参数列表和返回值列表中的参数变量名可以省略

如果接口里面有方法,必须通过结构体或者自定义类型实现接口

哎,就是实现类 implements,懂?

哎,乏味

type Usber interface {start()stop()
}type Phone struct {Name string}func (p Phone) start()  {fmt.Println(p.Name,"启动")
}
func (p Phone) stop()  {fmt.Println(p.Name,"关机")
}func main()  {var p1 Usber = Phone{Name: "华为"}	//就和java一样,接口接收实现类p1.start()							 //华为 启动
}

当然,众所周知,接口里面没有定义的方法,是不能调用的

比如你的实现类中有自己的方法,通过接口是不能调用到的,但是为什么要这样做呢?接口都是行为规范了.

比如java中的List接口中没有定义LinkedList的相关方法,所以如果你用List去接,是调用不到的,你要用LinkedList去接.

这就变成了直接使用实现类(结构体).

再来看一个例子

java开发的同学都知道,其实应该把各个接口和实现类分开放到不同的go中,但是这里方便展示就放一起了

这里打的电脑的work方法接收的参数是一个接口类型,实际上是体现了多态,这里不多赘述

type Usber interface {start()work()stop()
}
//电脑
type Computer struct {}
func (c Computer) work(usb Usber)  {usb.start()usb.work()usb.stop()
}
//手机
type Phone struct {
}
func (p Phone) start()  {fmt.Println("手机启动")
}
func (p Phone) stop()  {fmt.Println("手机关机")
}
func (p Phone) work(){fmt.Println("手机工作")
}
//相机
type Camera struct {}
func (c Camera) start()  {fmt.Println("相机启动")
}
func (c Camera) stop()  {fmt.Println("相机关机")
}
func (c Camera) work(){fmt.Println("相机工作")
}func main()  {var computer = Computer{}var phone Usber = Phone{}var camera Usber = Camera{}computer.work(phone)computer.work(camera)
}
/**
手机启动
手机工作
手机关机
相机启动
相机工作
相机关机
*/

空接口

Serializable?其实有差别

空接口代表没有任何约束,可以接收任意类型

空接口作为函数的参数进行

//空接口测试
func show(a interface{})  {fmt.Printf("值:%v,类型:%T",a,a)
}func main()  {str := "这是一个测试"show(str)				//值:这是一个测试,类型:string
}

map的值实现空接口

这样就可以往这个map中传任意类型了,就好比Map<String,Object>

func main(){map1 := make(map[string]interface{})map1["1"] = "string类型"map1["2"] = 2map1["3"] = []string{"切片类型1","切片类型2",}show(map1)
}
/**
值:map[1:string类型 2:2 3:[切片类型1 切片类型2]],类型:map[string]interface {}
*/

切片值实现空接口

同理,Object类型的list

func main(){arr := make([]interface{},3,3)arr[0] = "string"arr[1] = 1arr[2] = 2.84show(arr)
}
/**
值:[string 1 2.84],类型:[]interface {}
*/

类型断言

相当于instanceof

一个接口的值(简称接口值)是由一个具体类型和具体类型的值两部分组成的。这两部分分别称为接口的动态类型和动态值。
如果我们想要判断空接口中值的类型,那么这个时候就可以使用类型断言,其语法格式:

x.(T)
  • x:表示类型为interface{}的变量
  • T:表示断言x可能是的类型。

该语法返回两个参数,第- 个参数是x转化为T类型后的变量,第二个值是一个布尔值,若为true则断言成功,false则失败

func main(){var a interface{} = "你好"v,ok := a.(string)if ok {fmt.Println("a是一个string类型",v)}else {fmt.Println("断言失败")}
}
/**
a是一个string类型 你好
*/

注意:类型.(type)只能结合switch语句使用

定义一个方法,可以传入任意数据类型,然后根据不同的类型实现不同的功能

func justifyType(x interface{}) {switch v := x.(type) {case string:fmt.Printf("x is a string, value is %v\n", v)case int:fmt.Printf("x is a int is %v\n", v)case bool:fmt.Printf("x is a bool is %v\n", v)default:fmt.Println("unsupport type! ")}
}

再比如

func (c Computer) work(usb Usber)  {//根据传入接口的不同类型执行不同的逻辑switch usb.(type) {case Phone:usb.start()case Camera:usb.stop()}
}
func main()  {var computer = Computer{}var phone Usber = Phone{}var camera Usber = Camera{}computer.work(phone)computer.work(camera)
}
/**
手机启动
相机关机
*/

结构体值接收者和指针接收者实现接口的区别

值接收者

如果结构体中的方法是值接收者,那么实例化后的结构体值类型和结构体指针类型都可以赋值给接口变量

type Usber interface {start()work()stop()
}//手机
type Phone struct {
}
func (p Phone) start()  {	//值接收者fmt.Println("手机启动")
}
func (p Phone) stop()  {fmt.Println("手机关机")
}
func (p Phone) work(){fmt.Println("手机工作")
}func main() {//结构体值接收者,实例化后的结构体值类型和结构体指针类型都可以赋值给接口变量var p1 Usber = Phone{}var p2 Usber = &Phone{}p1.start()p2.stop()
}

指针接收者

结构体指针接收者,实例化后只有结构体指针类型才可以赋值给接口变量

type Usber interface {start()work()stop()
}//手机
type Phone struct {
}
func (p *Phone) start()  {	//值接收者fmt.Println("手机启动")
}
func (p *Phone) stop()  {fmt.Println("手机关机")
}
func (p *Phone) work(){fmt.Println("手机工作")
}func main() {//结构体指针接收者,实例化后只有结构体指针类型才可以赋值给接口变量var p1 Usber = Phone{}		//报错	Phone does not implement Usber (start method has pointer receiver)var p2 Usber = &Phone{}p1.start()p2.stop()
}

接口方法有返回值的情况

哎其实差不多,不过因为要修改结构体属性值,所以注意方法要使用指针类型

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() (name string) {return d.Name
}type Cat struct{Name string
}func (c *Cat) SetName(name string) {c.Name = name
}func (c Cat) GetName() string {return c.Name
}func main() {var dog Animal = &Dog{Name: "wangcai"}var cat Animal = &Cat{Name: "xiaohei"}fmt.Println(dog.GetName())dog.SetName("旺财")fmt.Println(dog.GetName())fmt.Println(cat.GetName())cat.SetName("小黑")fmt.Println(cat.GetName())
}
/**
wangcai
旺财
xiaohei
小黑	
*/

一个结构体实现多个接口,接口嵌套

多态?

使用接口嵌套达到结构体实现多个接口的目的

当然,尽量遵守单一职责设计原则,一个接口尽量不耦合其它接口,另外定义一个接口来组合已有的接口

type Animal interface {SetName(string)GetName() string
}
type Action interface {run()sleep()
}
//定义一个接口,要求实现该接口的结构体实现包含接口的所有方法
type MovingAnimal interface {AnimalAction
}type Dog struct {Name string
}
func (d *Dog) SetName(name string)  {d.Name = name
}
func (d Dog) GetName() (name string) {return d.Name
}func (d Dog) run() {fmt.Println(d.Name,"在奔跑")
}func (d Dog) sleep() {fmt.Println(d.Name,"在睡觉")
}func main() {var dog MovingAnimal = &Dog{Name: "wangcai"}fmt.Println(dog.GetName())dog.SetName("旺财")fmt.Println(dog.GetName())dog.run()dog.sleep()
}
/**
wangcai
旺财
旺财 在奔跑
旺财 在睡觉
*/

空接口和类型断言的使用细节

空接口类型是不能进行索引或者说拿到结构体中的值的,通过类型断言就可以做到,具体看下面的示例

type Address struct {Name  stringPhone int
}//Golang中空接口和类型断言使用细节
func main() {var userinfo = make(map[string]interface{})userinfo["username"] = "花玲"userinfo["age"] = 18userinfo["hobby"] = []string{"吃饭", "睡觉", "我"}fmt.Println(userinfo["username"])	//花玲fmt.Println(userinfo["age"])		//18//fmt.Println(userinfo["hobby"][1])	//type interface {} does not support indexingaddress := Address{Name:  "ky",Phone: 123456789,}userinfo["address"] = address//fmt.Println(userinfo["age"].Name)	//type interface {} is interface with no methods//解决方案,类型断言hobby,_ := userinfo["hobby"].([]string)fmt.Println(hobby[2])	//我addr,_ := userinfo["address"].(Address)fmt.Println(addr.Name,addr.Phone)	//ky 123456789
}

goroutine协程

为什么要使用goroutine

**需求: **要统计1-10000006的数字中那些是素数,并打印这些素数?
素数: 就是除了1和它本身不能被其他数整除的数

实现方法:

  1. 传统方法,通过一个for循环判断各个数是不是素数
  2. 使用并发或者并行的方式,将统计素数的任务分配给多个goroutine去完成,这个时候就用到了goroutine
  3. goroutine 结合channel

Golang中的协程(goroutine) 以及主线程

golang中的主线程: ( 可以理解为线程/也可以理解为进程),在一个Golang程序的主线程上可以起多个协程Golang 中多协程可以实现并行或者并发。
协程: 可以理解为用户级线程,这是对内核透明的,也就是系统并不知道有协程的存在,是完全由用户自己的程序进行调度的。Golang的一大特色就是从语言层面原生支持协程,在函数或者方法前面加go 关键字就可创建一个协程。可以说Golang中的协程就是goroutine。

image-20200607161831651

Golang中的多协程有点类似其他语言中的多线程。

多协程和多线程: Golang中每个goroutine (协程)默认占用内存远比Java、C的线程少。
OS线程(操作系统线程)一般都有固定的栈内存(通常为2MB左右) ,一个goroutine (协程)占用内存非常小,只有2KB左右,多协程goroutine切换调度开销方面远比线程要少。
这也是为什么越来越多的大公司使用Golang的原因之一。

Goroutine的使用以及sync.WaitGroup

并行执行需求:

  • 在主线程(可以理解成进程)中,开启一个goroutine,该协程每隔50毫秒秒输出"你好golang"
  • 在主线程中也每隔50毫秒输出"你好golang",输出10次后,退出程序,要求主线程和goroutine同时执行。
func test() {for i := 0; i < 10; i++ {fmt.Println("test() hello golang")time.Sleep(time.Millisecond * 50)}
}func main() {go test()	//开启一个协程for i := 0; i < 10; i++ {fmt.Println("main() hello golang")time.Sleep(time.Millisecond * 50)}
}

问题?

如果主线程中任务的执行速度比协程中的任务执行速度快,会出现什么问题?

当主线程执行结束,协程无论是否执行完毕,都会停止执行.

image-20200607162950856

使用sync.WaitGroup

这是个什么东西?

java开发的同学应该知道,其实这就是个CountdownLatch,等我娓娓道来

var wg sync.WaitGroupfunc test() {for i := 0; i < 10; i++ {fmt.Println("test() hello golang--",i)time.Sleep(time.Millisecond * 100)}wg.Done()	//计数器减一
}func main() {wg.Add(1)	//计数器加一go test()	//开启一个协程for i := 0; i < 10; i++ {fmt.Println("main() hello golang--",i)time.Sleep(time.Millisecond * 50)}wg.Wait()fmt.Println("主线程执行结束")
}

其中

wg.Add(1) 相当于CowndownLatch的初始化为1

wg.Done() 相当于CowndownLatch的countDown()方法

wg.wait() 相当于CowndownLatch的await()

这样说好理解了吗?

当然,可以开启多个协程任务

这里我们开启了5个协程去执行test方法,每个test方法里打印10条语句

var wg sync.WaitGroupfunc test(x int) {defer wg.Done()for i := 1; i <= 10; i++ {fmt.Println("协程--",x,"--",i)}
}
func main() {for i := 1; i <= 5 ; i++ {go test(i)wg.Add(1)}wg.Wait()
}

设置golang并行运行时候占用的cpu数量

Go运行时的调度器使用GOMAXPROCS参数来确定需要使用多少个OS线程来同时执行Go代码。默认值是机器上的CPU核心数。例如在一个8核心的机器上,调度器会把Go代码同时调度到8个OS线程上。
Go语言中可以通过runtime.GOMAXPROCS()函数设置当前程序并发时占用的CPU逻辑核心数。
Go1.5版本之前,默认使用的是单核心执行。Go1.5 版本之后,默认使用全部的CPU逻辑核心数。

一个需求

统计1-120000内的素数,for循环实现

func main() {start := time.Now().Unix()for i := 2; i <= 120000; i++ {flag:=truefor j := 2; j <= i/2 ; j++ {if i%j == 0 {flag = falsebreak}}if flag {//fmt.Print(i," ")}}end := time.Now().Unix()fmt.Println(end-start)	// j<i/2 耗时3毫秒 	j<i	耗时5毫秒
}

开启多个协程

func cal(n int) {start := (n-1)*30000 + 1end := n * 30000defer wg.Done()for i := start; i <= end; i++ {if i == 1 {continue}flag := truefor j := 2; j <= i/2; j++ {if i%j == 0 {flag = falsebreak}}if flag {fmt.Print(i,"是素数\n")}}
}func main() {//统计1-120000内的素数,协程实现start := time.Now().Unix()for i := 1; i <= 4; i++ {wg.Add(1)go cal(i)}wg.Wait()end := time.Now().Unix()fmt.Println(end - start)	//2毫秒
}

channel管道

  • 管道是Go语言在语言级别上提供的goroutine间的通讯方式,我们可以使用channel在多个goroutine之间传递消息。如果说goroutine是Go程序并发的执行体,channel就是它们之间的连接。channel是可以让一- 个goroutine发送特定值到另-个 goroutine 的通信机制。

  • Go语言的并发模型是CSP ( Communicating Sequential Processes),提倡通过通信共享内存而不是通过共享内存而实现通信。

  • Go语言中的管道(channel) 是一种特殊的类型。管道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序。每一个管道都是一个具体类型的导管,也就是声明channel的时候需要为其指定元素类型。

channel类型

channel是一种类型, 一种引用类型。声明管道类型的格式如下:

var 变量 chan 元素类型举几个例子:var ch1 chan int 	//声明一个传递整型的管道
var ch2 chan bool	//声明一个传递布尔型的管道
var ch3 chan []int  //声明一个传递int切片的管道

创建channel

声明的管道后需要使用make函数初始化之后才能使用。

make(chan 元素类型,容量)
//创建一个能存储10个int 类型数据的管道
ch1 := make(chan int, 10)
//创建一一个能存储4个bool类上数据的管道
ch2 := make(chan bool, 4)
//创建一个 能存储3个[]int 切片类型数据的管道
ch3 := make(chan []int, 3)

channel操作

管道有``发送(send) 、接收(receive) 和关闭(close) 三种操作。 发送和接收都使用<-`符号的
现在我们先使用以下语句定义一个管道:

ch := make(chan int, 3)

1.发送(将数据放在管道内)

将一个值放进管道

ch <- 10	//把10发送到ch中

2.接收(从管道内取值)

x := <- ch	//从ch中取值并赋值给变量x

3.关闭管道

close(ch)

管道是引用数据类型

func main() {//1.创建channelch := make(chan int, 3)//2.给管道里面发送数据ch <- 3ch <- 4ch <- 5//3.获取管道内容a := <-chfmt.Println(a) //3<-ch //从管道里取值	//4c := <-chfmt.Println(c)	//5ch <- 6ch <- 7//4.管道的容量,和长度fmt.Printf("值:%v,容量:%v,长度:%v",ch,cap(ch),len(ch))		//值:0xc00007e000,容量:3,长度2//6.管道阻塞ch1:=make(chan int,1)ch1 <- 2ch1 <- 3	//all goroutines are asleep - deadlock!}

关于管道阻塞

  • 管道满了,添加(deadlock)
  • 管道空了,取值(deadlock)

在没有使用协程的情况下,如果我们的管道数据已经全部取出,再取就会报告deadlock

管道的循环遍历

循环遍历管道数据
使用for range遍历通道,当通道被关闭的时候就会退出for range, 如果没有关闭管道就会报个错误

func main() {var ch1 = make(chan int, 10)for i := 0; i < 10; i++ {ch1 <- i}close(ch1)//for range循环遍历管道的值	管道没有keyfor v := range ch1 {fmt.Println(v) //如果不关闭管道会报错fatal error: all goroutines are asleep - deadlock!}
}

但是使用for循环不关闭管道不会有这个问题

我觉得细细想一下,也很科学,range的时候会去取"下一个值",但是for就不会,所以不会deadlock

func main(){ch2 := make(chan int,10)for i := 0; i < 10; i++ {ch2 <- i}for i := 0; i < 10; i++ {fmt.Println(<-ch2)}
}

总结

  • 使用forr时,记得关闭管道.
  • 如果不知道什么时候关闭管道就使用fori

goroutine结合channel

**需求1:**定义两个方法,一个方法给管道里面写数据,一个给管道里面读取数据。要求同步进行。

  1. 开启一个fn1的的协程给向管道inChan中写入100条数据
  2. 开启一个fn2的协程读取inChan中写入的数据
  3. 注意: fn1和fn2同时操作一一个管道
  4. 主线程必须等待操作完成后才可以退出
var wg sync.WaitGroup//写数据
func fn1(ch chan int) {for i := 1; i <= 10; i++ {ch <- ifmt.Printf("[写入]数据%v成功\n", i)time.Sleep(time.Millisecond * 50)}close(ch)wg.Done()
}//读数据
func fn2(ch chan int) {for v := range ch {fmt.Printf("[读取]数据%v成功\n", v)time.Sleep(time.Millisecond * 50)}wg.Done()
}func main() {var ch = make(chan int, 10)wg.Add(1)go fn1(ch)wg.Add(1)go fn2(ch)wg.Wait()fmt.Println("退出........")
}
/**
[读取]数据1成功
[写入]数据1成功
[写入]数据2成功
[读取]数据2成功
[写入]数据3成功
[读取]数据3成功
[写入]数据4成功
[读取]数据4成功
[写入]数据5成功
[读取]数据5成功
[写入]数据6成功
[读取]数据6成功
[写入]数据7成功
[读取]数据7成功
[写入]数据8成功
[读取]数据8成功
[写入]数据9成功
[读取]数据9成功
[写入]数据10成功
[读取]数据10成功
退出........
*/

说明

管道是安全的,也许你写入数据很慢,读取数据很快,他读不到数据会阻塞,管道是安全的

gorutine结合channel实现统计素数

3个channel,太秀了,上代码

image-20200607235313134

var wg sync.WaitGroup//存放初始数据
func putNum(inChannel chan int) {for i := 2; i < 120000; i++ {inChannel <- i}close(inChannel)wg.Done()
}//统计素数
func primeNum(inChannel, primeChannel chan int, exitChan chan bool) {for num := range inChannel {flag := truefor j := 2; j <= num/2; j++ {if num%j == 0 {flag = falsebreak}}if flag {primeChannel <- num //num是素数}}//给exitChan里面放入一条数据exitChan <- true//close(primeChannel)		//如果一个channel关闭了就没法给这个channel发送数据了,所以我们不在这里closewg.Done()}//打印素数
func printPrime(primeChannel chan int) {for prime := range primeChannel {fmt.Println("得到一个素数", prime)}wg.Done()
}func main() {start := time.Now().Unix()inChannel := make(chan int, 1000)primeChannel := make(chan int, 1000)exitChan := make(chan bool, 16) //标识primeChan close//存放数字的协程wg.Add(1)go putNum(inChannel)//统计素数的协程for i := 0; i < 16; i++ {wg.Add(1)go primeNum(inChannel, primeChannel, exitChan)}//打印素数的协程wg.Add(1)go printPrime(primeChannel)//判断exitChan是否存满值wg.Add(1)go func() {for i := 0; i < 16; i++ {<-exitChan //如果执行的速度比其他的协程快,这里会阻塞}close(primeChannel)wg.Done()}()wg.Wait()end := time.Now().Unix()fmt.Println("执行完毕........", end-start,"ms")
}

运行结果

image-20200607235213946

单向管道

有的时候我们会将管道作为参数在多个任务函数间传递,很多时候我们在不同的任务函数中使用管道都会对其进行限制,比如限制管道在函数中只能发送或只能接收。

默认情况下,管道是双向的

func main(){ch := make(chan int,5)ch<-1ch<-2<-ch<-ch
}

声明为只读管道

func main() {ch := make(chan <- int,5)	//声明为只写ch<-1ch<-2<-ch	//receive from send-only type chan<- int
}

声明为只读管道

func main() {ch := make(<- chan int,5)	//声明为只读ch<-1	//send to receive-only type <-chan intch<-2<-ch	
}

应用:一个管道两个协程,一个只读,一个只写

var wg sync.WaitGroupfunc fn1(ch chan<- int) {for i := 0; i < 10; i++ {ch<-ifmt.Printf("[写入]数据%v成功\n",i)time.Sleep(time.Millisecond*50)}close(ch)wg.Done()
}
func fn2(ch <-chan int)  {for v := range ch {fmt.Printf("[读取]数据%v成功\n",v)time.Sleep(time.Microsecond*50)}wg.Done()
}func main() {var ch = make(chan int,10)wg.Add(1)go fn1(ch)wg.Add(1)go fn2(ch)wg.Wait()fmt.Println("执行结束......")
}
/**
....
[写入]数据7成功
[读取]数据7成功
[写入]数据8成功
[读取]数据8成功
[写入]数据9成功
[读取]数据9成功
执行结束......
*/

select多路复用

以上这种方式虽然可以实现从多个管道接收值的需求,但是运行性能会差很多。

为了应对这种场景,Go内置了select关键字,可以同时响应多个管道的操作。

select的使用类似于switch语句,它有一系列case分支一个默认的分支。每个case会对应一个管道的通信(接收或发送)过程。select 会一直等待,直到某个case的通信操作完成时,就会执行case分支对应的语句。具体格式如下:

select{case <-ch1:...case data := <-ch2:...case ch3<-data:...default:默认操作
}

select多路复用是结合for循环实现的

使用select多路复用的时候不需要关闭channel

func main(){//select多路复用for {select {case v:=<-inChan:fmt.Printf("从inChan中读取数据-%v\n",v)time.Sleep(time.Millisecond*50)case v:=<-stringChan:fmt.Printf("从stringChan中读取数据-%v\n",v)time.Sleep(time.Millisecond*50)default:fmt.Printf("数据获取完毕")return		//注意退出}}
}

goruntine recover解决协程中出现的panic

和之前我们处理异常的方式一样

func sayHello() {for i := 0; i < 10; i++ {time.Sleep(time.Millisecond * 50)fmt.Println("hello world")}}func test() {//处理异常defer func() {err := recover()if err!=nil {fmt.Println("错误:",err)}}()//一个mapvar myMap map[int]string	//这里没有分配内存,所以必定抛异常,如果不处理程序就终止myMap[0] = "golan"}func main() {go sayHello()go test()time.Sleep(time.Second)fmt.Println("执行完毕....")
}

go并发安全和锁

互斥锁

互斥锁是传统并发编程中对共享资源进行访问控制的主要手段,它由标准库sync中的Mutex结构体类型表示。sync.Mutex 类型只有两个公开的指针方法,Lock 和Unlock。Lock 锁定当前的共享资源,Unlock 进行解锁

同一时间只能有一个协程对资源进行访问

并行改串行

var count = 0
var wg sync.WaitGroupvar mutex sync.Mutexfunc test() {defer wg.Done()mutex.Lock()		//加锁count++fmt.Println("count : ",count)time.Sleep(time.Millisecond)mutex.Unlock()		//解锁
}func main() {for i := 0; i < 20; i++ {wg.Add(1)go test()}wg.Wait()fmt.Println("执行结束....")
}

读写锁

互斥锁的本质是当一个goroutine访问的时候,其他goroutine都不能访问。这样在资源同步,避免竞争的同时也降低了程序的并发性能。程序由原来的并行执行变成了串行执行。
其实,当我们对一个不会变化的数据只做“读"操作的话,是不存在资源竞争的问题的。因为数据是不变的,不管怎么读取,多少goroutine 同时读取,都是可以的。
所以问题不是出在“读”上,主要是修改,也就是“写”。修改的数据要同步,这样其他goroutine才可以感知到。所以真正的互斥应该是读取和修改、修改和修改之间,读和读是没有互斥操作的必要的。

因此,衍生出另外一-种锁,叫做读写锁
读写锁可以让多个读操作并发,同时读取,但是对于写操作是完全互斥的。也就是说,当一个goroutine进行写操作的时候,其他goroutine既不能进行读操作,也不能进行写操作。

GO中的读写锁由结构体类型sync.RWMutex表示。此类型的方法集合中包含两对方法:

一组是对写操作的锁定和解锁,简称“写锁定”和“写解锁:

func (*RWMutex)Lock()
func (*RWMutex)Unlock()

另一组表示对读操作的锁定和解锁,简称为“读锁定”与“读解锁" :

func (*RWMutex)RLock()
func (*RWMutex)RUnlock()

读写锁示例

var wg sync.WaitGroup
var rwMutex sync.RWMutex//写方法
func write() {defer wg.Done()rwMutex.Lock()fmt.Println("执行写操作")time.Sleep(time.Second * 2)rwMutex.Unlock()
}//读方法
func read() {defer wg.Done()rwMutex.RLock()fmt.Println("----执行读操作")time.Sleep(time.Second * 2)rwMutex.RUnlock()
}func main() {//开启10个协程执行读操作for i := 0; i < 10; i++ {wg.Add(1)go read()}for i := 0; i < 10; i++ {wg.Add(1)go write()}wg.Wait()fmt.Println("执行结束....")
}

反射

有时我们需要写一个函数, 这个函数有能力统一处理 各种值类型,而这些类型可能无法共享同一个接口,也可能布局未知,也有可能这个类型在我们设计函数时还不存在,这个时候我们就可以用到反射。

  1. 空接口可以存储任意类型的变量,那我们如何知道这个空接口保存数据的类型是什么?值是什么呢?
    1. 可以使用类型断言
    2. 可以使用反射实现,也就是在程序运行时动态的获取一个变量的类型信息和值信息。
  2. 把结构体序列化成json字符串,自定义结构体Tab标签的时候就用到了反射
  3. ORM框架就用到了反射技术

反射是框架的灵魂, java开发的同学就应该知道,spring等框架底层大量用到了反射

**ORM:**对象关系映射( Object Relational Mapping,简称ORM)是通过使用描述对象和数据库之间映射的元数据,将面向对象语言程序中的对象自动持久化到关系数据库中。

反射的基本介绍

反射是指在程序运行期间对程序本身进行访问和修改的能力。正常情况程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。

go可以实现的功能

  • 反射可以在程序运行期间动态的获取变量的各种信息,比如变量的类型类别
  • 如果是结构体,通过反射还可以获取结构体本身的信息,比如结构体的字段、结构体的方法。
  • 通过反射,可以修改变量的值,可以调用关联的方法

go语言中的变量是分为两部分的

**类型信息:**预先定义好的元信息

**值信息:**程序运行过程中可动态变化

在Go语言的反射机制中,任何接口值都由是一个具体类型具体类型的值两部分组成的。

在Go语言中反射的相关功能由内置的reflect包提供,任意接口值在反射中都可以理解为由reflect.Typereflect.Value两部分组成,并且reflect包提供了reflect.TypeOfreflect.ValueOf两个重要函数来获取任意对象的Value和Type

reflect.TypeOf()获取任意值的类型对象

在Go语言中,使用reflect.TypeOf()函数可以接受任意interface{}参数,可以获得任意值的类型对象(reflect.Type) ,程序通过类型对象可以访问任意值的类型信息。

type myInt int
type Person struct {Name stringAge int
}//反射获取任意变量的类型
func reflectFn(x interface{}) {typeOf := reflect.TypeOf(x)fmt.Println(typeOf)
}
func main() {a := 10b := 12.3c := trued := "你好"reflectFn(a)	//intreflectFn(b)	//float64reflectFn(c)	//boolreflectFn(d)	//stringvar num myInt = 2var person = Person{Name: "花玲",Age:  18,}reflectFn(num)	//main.myIntreflectFn(person)	//main.Personvar h = 24reflectFn(&h)	//*int
}

type Name和type Kind

在反射中关于类型还划分为两种:类型(Type)种类(Kind) 。因为在Go语言中我们可以使用type关键字构造很多自定义类型,而种类(Kind) 就是指底层的类型,但在反射中,当需要区分指针、结构体等大品种的类型时,就会用到种类(Kind) 。

举 个例子,我们定义了两个指针类型和两个结构体类型,通过反射查看它们的类型和种类。

Go语言的反射中像数组、切片、Map、指针等类型的变量,它们的.Name()都是返回空。

type myInt int
type Person struct {Name stringAge int
}//反射获取任意变量的类型
func reflectFn(x interface{}) {typeOf := reflect.TypeOf(x)fmt.Printf("类型:%v,Name:%v,Kind:%v\n",typeOf,typeOf.Name(),typeOf.Kind())
}
func main() {a := 10b := 12.3c := trued := "你好"reflectFn(a)	//类型:int,Name:int,Kind:intreflectFn(b)	//类型:float64,Name:float64,Kind:float64reflectFn(c)	//类型:bool,Name:bool,Kind:boolreflectFn(d)	//类型:string,Name:string,Kind:stringvar num myInt = 2var person = Person{Name: "花玲",Age:  18,}reflectFn(num)	//类型:main.myInt,Name:myInt,Kind:intreflectFn(person)	//类型:main.Person,Name:Person,Kind:structvar h = 24reflectFn(&h)	//类型:*int,Name:,Kind:ptr
}

Name:类型名称 Kind():底层类型

reflect.ValueOf()

reflect.valueOf()返回的是reflect.Value类型,其中包含了原始值的值信息。reflect.Value 与原始值之间可以互相转换

reflect.Value类型提供的获取原始值的方法如下:

image-20200608215611839

//反射获取任意变量的值
func reflectValue(x interface{}) {//fmt.Println(x)//num := x.(int)//sum := num + 10//fmt.Println(sum)	//23//反射获取变量的原始值v:=reflect.ValueOf(x)sum := v.Int() + 10fmt.Println(sum)	//23
}
func main() {var a = 13reflectValue(a)
}
//反射获取任意变量的值
func reflectValue(x interface{}) {v := reflect.ValueOf(x)kind := v.Kind()switch kind {case reflect.Int:fmt.Printf("int类型的原始值%v\n",v.Int())case reflect.Float32:fmt.Printf("float32类型的原始值:%v\n",v.Float())case reflect.String:fmt.Printf("string类型的原始值:%v\n",v.String())default:fmt.Printf("还没有判断该类型\n")}
}
func main() {var a int64 = 13var b float32 = 12.3var c string = "你好golang"reflectValue(a)		//还没有判断该类型reflectValue(b)		//float32类型的原始值:12.300000190734863	精度损失reflectValue(c)		//string类型的原始值:你好golang
}

利用reflect.ValueOf()修改原始值

func reflectSetValue(x interface{}) {//*x = 120	//错误写法//v := x.(*int64)	//类型断言可以//*v = 120v:=reflect.ValueOf(x)//kind := v.Kind()     //ptr//k := v.Elem().Kind() //int64switch v.Elem().Kind() {case reflect.Int64:v.Elem().SetInt(123)case reflect.String:v.Elem().SetString("go你好")default:fmt.Println("还未识别该类型")}}
func main() {var a int64 = 100reflectSetValue(&a)fmt.Println(a)var b string = "你好go"reflectSetValue(&b)fmt.Println(b)
}

结构体反射

与结构体相关的方法

任意值通过reflect.TypeOf()获得反射对象信息后,如果它的类型是结构体,可以通过反射值对象(reflect.Type)NumField()Field()方法获得结构体成员的详细信息。

reflect.Type中与获取结构体成员相关的的方法如下表所示。

image-20200608223237195

StructField类型

type StructField struct {//参见http://golang.org/ref/spec#Uniqueness_of_identifiersName string // Name是字段的名字PkgPath string	//PkgPath是非导出字段的包路径,对导出字段该字段为”Type	Type	//字段的类型Tag	StructTag //字段的标签Offset	uintptr	//字段在结构体中的字节偏移量Index	[]int	//用于Type.FieldByIndex时的索引切片Anonymous bool	//是否匿名字段
}

如何获取属性

//定义结构体
//Student结构体
type Student struct {Name  string `json:"name1" form:"username"`Age   int    `json:"age"`Score int    `json:"score"`
}func (s Student) GetInfo() string {str := fmt.Sprintf("姓名:%v,年龄:%v,成绩:%v", s.Name, s.Age, s.Score)return str
}
func (s *Student) SetInfo(name string, age, score int) {s.Name = names.Age = ages.Score = score
}
func (s Student) print() {fmt.Println("这就是个打印方法....")
}
//打印字段
func PrintStructField(s interface{}) {//判断参数是不是结构体类型t := reflect.TypeOf(s)v := reflect.ValueOf(s)if t.Kind() != reflect.Struct && t.Elem().Kind() != reflect.Struct {fmt.Println("传入的参数不是一个结构体")return}//通过类型变量里面的Field可以获取结构体的字段field0 := t.Field(0)                           fmt.Printf("%#v \n", field0)                   fmt.Println("字段名称: ", field0.Name)             //字段名称:  Namefmt.Println("字段类型: ", field0.Type)             //字段类型:  stringfmt.Println("字段tag: ", field0.Tag.Get("json")) //字段tag:  name1fmt.Println("字段tag: ", field0.Tag.Get("form")) //字段tag:  usernamefmt.Println("=============")//通过类型变量里面的FieldByName可以获取结构体的字段field1, ok := t.FieldByName("Age")if ok {fmt.Println("字段名称: ", field1.Name)fmt.Println("字段类型: ", field1.Type)fmt.Println("字段tag: ", field1.Tag.Get("json"))}fmt.Println("=============")//通过类型变量里面的NumField获取到该结构体有几个字段var fieldCount = t.NumField()fmt.Println("结构体有", fieldCount, "个属性")	//结构体有 3 个属性fmt.Println("=============")//通过值变量获取结构体属性对应的值fmt.Println(v.FieldByName("Name"))fmt.Println(v.FieldByName("Age"))fmt.Println("=============")//通过for循环获取所有的属性for i := 0; i < t.NumField(); i++ {field := t.Field(i)value := v.Field(i)fmt.Printf("属性名称:%v,属性值:%v,属性类型:%v,属性Tag:%v\n",field.Name,value,field.Type,field.Tag.Get("json"))}
}
func main() {s1 := Student{Name:  "花玲",Age:   18,Score: 100,}PrintStructField(s1)
}

字段

//打印字段
func PrintStructField(s interface{}) {//判断参数是不是结构体类型t := reflect.TypeOf(s)v := reflect.ValueOf(s)if t.Kind() != reflect.Struct && t.Elem().Kind() != reflect.Struct {fmt.Println("传入的参数不是一个结构体")return}//通过类型变量里面的Field可以获取结构体的字段field0 := t.Field(0)                           //获取第0个属性,此时是Namefmt.Printf("%#v \n", field0)                   //reflect.StructField{Name:"Name", PkgPath:"", Type:(*reflect.rtype)(0x4aef40), Tag:"json:\"name\"", Offset:0x0, Index:[]int{0}, Anonymous:false}fmt.Println("字段名称: ", field0.Name)             //字段名称:  Namefmt.Println("字段类型: ", field0.Type)             //字段类型:  stringfmt.Println("字段tag: ", field0.Tag.Get("json")) //字段tag:  name1fmt.Println("字段tag: ", field0.Tag.Get("form")) //字段tag:  usernamefmt.Println("=============")//通过类型变量里面的FieldByName可以获取结构体的字段field1, ok := t.FieldByName("Age")if ok {fmt.Println("字段名称: ", field1.Name)fmt.Println("字段类型: ", field1.Type)fmt.Println("字段tag: ", field1.Tag.Get("json"))}fmt.Println("=============")//通过类型变量里面的NumField获取到该结构体有几个字段var fieldCount = t.NumField()fmt.Println("结构体有", fieldCount, "个属性") //结构体有 3 个属性fmt.Println("=============")//通过值变量获取结构体属性对应的值fmt.Println(v.FieldByName("Name"))fmt.Println(v.FieldByName("Age"))fmt.Println("=============")//通过for循环获取所有的属性for i := 0; i < t.NumField(); i++ {field := t.Field(i)value := v.Field(i)fmt.Printf("属性名称:%v,属性值:%v,属性类型:%v,属性Tag:%v\n", field.Name, value, field.Type, field.Tag.Get("json"))}
}

执行方法

func PrintStructFn(s interface{}) {//判断参数是不是结构体类型t := reflect.TypeOf(s)v := reflect.ValueOf(s)if t.Kind() != reflect.Struct && t.Elem().Kind() != reflect.Struct {fmt.Println("传入的参数不是一个结构体")return}//通过类型变量里面的Method以获取结构体的方法method0 := t.Method(0)    //这个0和方法的顺序无关,是根据ASCII码有关fmt.Println(method0.Name) //GetInfofmt.Println(method0.Type) //func(main.Student) stringfmt.Println("-------------------")//通过类型变量获取这个结构体有多少个方法fmt.Println("方法个数:", t.NumMethod())method1, ok := t.MethodByName("Print")if ok {fmt.Println(method1.Name) //Printfmt.Println(method1.Type) //func(main.Student)}fmt.Println("-------------------")//通过<值变量>执行方法(注意 需要使用值变量,并且要注意参数) v.Method(0).Call(nil)或者v.MethodByv.Method(1).Call(nil) //这就是个打印方法....//执行方法传入参数(注意需要使用<值变量>,并且要注意参数,接收的参数是[]reflect.Value的切片)fmt.Println(v.MethodByName("GetInfo").Call(nil)) //[姓名:花玲,年龄:18,成绩:100]var params = []reflect.Value{reflect.ValueOf("kk"),reflect.ValueOf(18),reflect.ValueOf(100),}v.MethodByName("SetInfo").Call(params)fmt.Println(v.MethodByName("GetInfo").Call(nil)) //[姓名:kk,年龄:18,成绩:100]}

通过反射修改结构体属性

func reflectChangeStruct(s interface{}) {t := reflect.TypeOf(s)v := reflect.ValueOf(s)if t.Kind() != reflect.Ptr {fmt.Println("传入的不是指针类型")return} else if t.Elem().Kind() != reflect.Struct {fmt.Println("传入的不是结构体指针类型")return}//修改结构体属性的值name :=v.Elem().FieldByName("Name")name.SetString("kk")age := v.Elem().FieldByName("Age")age.SetInt(21)
}

主函数

func main() {s1 := Student{Name:  "花玲",Age:   18,Score: 100,}//PrintStructField(s1)//PrintStructFn(&s1)reflectChangeStruct(&s1)fmt.Printf("%#v\n",s1)	//main.Student{Name:"kk", Age:21, Score:100}
}

不要乱用反射!

反射是一个强大并富有表现力的工具,能让我们写出更灵活的代码。但是反射不应该被滥用,原因有以下三个。

  1. 基于反射的代码是极其脆弱的,反射中的类型错误会在真正运行的时候才会引发panic,那很可能是在代码写完的很长时间之后。
  2. 大量使用反射的代码通常难以理解。

文件 目录操作

读取文件

只读方式打开文件file,err := os .Open()

func main() {//只读方式打开文件file, err := os.Open("p:/a.txt")defer file.Close()	//必须关闭if err != nil {fmt.Println(err)return}//读取文件内容fileSlice := make([]byte,128)	//每次读取128字节var strSlice []bytefor  {n, err := file.Read(fileSlice)if err == io.EOF {	//err==io.EOF表示读取完毕fmt.Println("读取完毕")break}if err != nil {fmt.Println("读取失败")return}fmt.Printf("读取到了%v个字节\n",n)strSlice = append(strSlice, fileSlice[:n]...)	//最后可能读取到的块没有128字节了,为了避免切片填充,限制切片范围 }fmt.Println(string(strSlice))	//读出来的是byte切片,我们需要转换成string再来打印}

读取文件(方法2) bufio读取文件

java开发的同学应该不难看出这个很像是字节流

func main() {file, err := os.Open("p:/a.txt")defer file.Close()if err != nil {fmt.Println(err)return}//读取文件reader := bufio.NewReader(file)var out stringfor  {str, err := reader.ReadString('\n')if err == io.EOF {fmt.Println("读取结束")out+=str	//这里也需要拼接,不然会读不全break}if err != nil {fmt.Println(err)return}out+=str}fmt.Println(out)
}

读取文件(方法3) ioutil读取文件

最简单方式,如果文件比较小,可以用这种方式来读取

func main() {fileByte, err := ioutil.ReadFile("p:/a.txt")if err != nil {fmt.Println(err)return}fmt.Println(string(fileByte))
}

写入文件

方法1 os.OpenFile()

模式 含义
os.O_ WRONLY 只写
os.O_ CREATE 创建文件
os.O_ RDONLY 只读
os.O_ RDWR 读写
os.O_ TRUNC 清空
os.O_ APPEND 追加

perm:文件权限,一个八进制数。r(读) 04,w (写) 02,x (执行) 01。一般来说传0666就行

os.OpenFile()需要传入三个参数,文件路径,模式,文件权限

func main() {file, err := os.OpenFile("p:/a.txt", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)defer file.Close()if err != nil {fmt.Println(err)return}//写入文件for i := 0; i < 10; i++ {file.WriteString(strconv.Itoa(i)+"花玲花玲花玲花玲花玲花玲\r\n")}var str = "直接写入的字符串数据byte"file.Write([]byte(str))	//这样子也可以
}

写入文件(方法2) bufio 写入文件

记得flush,java开发的同学应该都知道

func main() {file, err := os.OpenFile("p:/a.txt", os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0666)defer file.Close()if err != nil {fmt.Println(err)return}writer := bufio.NewWriter(file)for i := 0; i < 10; i++ {writer.WriteString("你好\r\n") //将数据先写入缓存}writer.Flush() //将缓存中的内容写入文件
}

写入文件(方法3)ioutil写入文件

但是这个,没有append模式,测试写东西的话可以用一下,写日志还是用上面的吧

func main() {str := "你好offer"err := ioutil.WriteFile("p:/a.txt",[]byte(str),0666)if err != nil {fmt.Println(err)return}
}

目录操作

复制文件 ioutil

func copyFile(srcFileName, dstFileName string) (err error) {bytestr, err := ioutil.ReadFile(srcFileName)if err != nil {return err}err1 := ioutil.WriteFile(dstFileName, bytestr, 006)if err1 != nil {return err1}return nil
}func main() {src := "p:/a.txt"dst := "p:/b.txt"err := copyFile(src, dst)if err != nil {fmt.Println(err)return}fmt.Println("复制成功")
}

复制文件 文件流

func copyFile(srcFileName, dstFileName string) (err error) {sFile, err1 := os.Open(srcFileName)defer sFile.Close()	//不关闭可能造成内存泄漏dFile, err2 := os.OpenFile(dstFileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)defer dFile.Close()if err1 != nil {return err1}if err2 != nil {return err2}var fileSlice = make([]byte, 1024*10)for true {//读取数据n1, err := sFile.Read(fileSlice)if err == io.EOF {break}if err != nil {return err}//写入数据if _, err4 := dFile.Write(fileSlice[:n1]); err4 != nil {return err4}}return nil
}func main() {src := "p:/a.txt"dst := "p:/b.txt"err := copyFile(src, dst)if err != nil {fmt.Println(err)return}fmt.Println("复制成功")
}

创建目录

func main() {os.Mkdir("p:/a", 0666)	//创建单个目录os.MkdirAll("p:/a/b/c",0666)	//创建多级目录
}

删除文件

func main() {err := os.Remove("p:/aaa.txt") //可以删除一个文件也可以删除文件夹if err != nil {fmt.Println(err)}err1 := os.RemoveAll("p:/a.txt") //级联删除if err1!=nil {fmt.Println(err1)}
}

重命名

func main() {err := os.Rename("p:/a.txt", "p:/b.txt")if err != nil {fmt.Println(err)}fmt.Println("重命名成功")
}
查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 公众号营销借势热点要学会找到角度新奇的切入点

    互联网的高速发展,越来越多的企业开始选择利用网络宣传自己的产品。而在网络营销中软文营销一直深受众多广告主的青睐。软文营销的第一步就是写好一篇好的软文。而写好软文的第一步,不是拟一个漂亮的标题,而是想一个好的创意。那么什么是软文的创意呢?说白了就是软文的写作…...

    2024/5/9 17:20:15
  2. 网络

    网络1、什么是同源2、url是什么3、域名解析4、dns5、当你在浏览器里输入一个url发生什么6、TCP/UDP(传输层协议)7、三次握手:建立TCP连接8、四次挥手:断开TCP连接9、应用层协议:http https等10、请求方法GET和POST的区别11、状态码12、常用状态码13、解决跨域问题的几种办…...

    2024/5/9 19:38:27
  3. 日常问题处理

    日常问题处理: 1、问题:终端显示-bash-4.2 解决方法:这是由于用户环境变量有问题,cp /etc/skel/.bash* . 复制环境变量到自身家目录下 退出重新登录;问题:终端显示-bash-4.1,并且无法敲ls等命令 解决方法:看用户家目录得权限是不是被改动了, ll /home 会发现权限变成了…...

    2024/5/10 1:18:26
  4. 机器学习“调音师”:如何及何时重新调校ML

    全文共1838字,预计学习时长6分钟图源:unsplash古希腊哲学家赫拉克利特说:“唯一不变的就是变化”。时间拥有着我们无法撼动的强大力量,无论是观念、社会还是人类本身,都会随着时间的推移而发生巨大的变化。往日最先进的事物会被当今所淘汰,如今新颖的想法也可能在接下来几…...

    2024/4/15 4:43:56
  5. 软件测试面试都会问什么?你真的懂这些面试问题背后的意图吗?

    导读 面试是我们进入一个公司的门槛,通过了面试才能进入公司工作,你的面试结果和你的薪资、享有的福利待遇是息息相关的。那如何能顺利的通过面试,得到HR的认可呢? 面试软件测试要注意哪些问题?这些问题的目的是什么呢?下面和成都IT培训优就业笔者一起来看看吧一,谈一下…...

    2024/4/15 4:43:55
  6. 关于vue项目中监听滚动条事件的一些坑

    在vue项目中监听滚动条事件 最近在进行一个vue项目时,需要监听滚动条高度,控制一个导航Dom元素的显示或者隐藏,但是总是无法实现onscoll事件的监听。 在网上看了一下基本都是这样的: methods:{handleScroll(){console.log(1);}}, mounted(){ window.addEventListene…...

    2024/4/15 4:43:54
  7. 截取AVI格式的视频C语言代码

    首先在阅读本代码之前百度一下avi,虽然经过我验证上面有部分错误,但是不影响阅读。因为有些变量的注释我没有写,所以请读者自行搜索吧。下面是c语言文件,编译之后能够直接运行,用来截取开始时间(单位s)后指定长度(单位s)的视频流。最后附上一部分视频文件的二进制,方…...

    2024/4/24 12:09:21
  8. Spring Framework(4) Spring IoC应用

    文章目录Spring IOC 应用Spring IOC 基础BeanFactory 与 ApplicationContext 的区别启动IOC容器的方式纯xml模式Bean标签的属性DI 依赖注入的xml配置xml与注解相结合模式xml中标签与注解的对应DI 依赖注入的注解实现方式纯注解模式Bean 的作用域和生命周期 Spring IOC 应用 Spr…...

    2024/4/24 12:09:27
  9. Qt+FFmpeg播放RTSP H264视频流(2)- FFmpeg自定义类封装

    Qt+FFmpeg播放RTSP H264视频流(2)- FFmpeg操作类封装FFmpeg操作类封装FFmpeg播放RTSP流程分析MyFFmpegSetUrl(QString playUrl) 设置RTSP播放路径MyFFmpegInit() 初始化MyFFmpegDestroy() 逆初始化MyFFmpegReadFrame() 读取视频帧MyFFmpegSigGetOneFrame(QImage img) 发送单帧…...

    2024/4/24 12:09:25
  10. 微信小程序集成第三方视频直播和视频聊天的限制记录

    微信小程序做视频聊天,一般都是直接适用第三方的平台SDK集成进来的,这里以网易云信视频通话为例 网易云信官方demo里边包含有视频聊天 demo 地址 :https://github.com/netease-im/NIM_Web_Weapp_Demo 当我将demo下载下来,换成了自己的 appid ,请求白名单设置完毕,然后直接…...

    2024/4/24 12:09:24
  11. node.js的安装和基本命令行

    ...

    2024/4/24 12:09:15
  12. hadoop-13 mapTask、reduceTask工作机制

    mapTask工作机制Read阶段:MapTask通过用户编写的RecordReader,从输入InputSplit中解析出一个个key/value。 Map阶段:该节点主要是将解析出的key/value交给用户编写map()函数处理,并产生一系列新的key/value。 Collect收集阶段:在用户编写map()函数中,当数据处理完成后,一…...

    2024/4/24 12:09:14
  13. 写一个函数,计算阶乘例如4!=4*3*2*1

    <!DOCTYPE html> <html><head><meta charset="utf-8" /><title></title><script type="text/javascript">//1.写一个函数,计算阶乘例如4!=4*3*2*1//方式一function factorial_01(num){var result1 = 1;for(var …...

    2024/4/24 12:09:20
  14. 并查集——力扣990.等式方程的可满足性

    并查集——力扣990.等式方程的可满足性 并查集 ​ 首先先从一个题目来看看背景。990.等式方程的可满足性 ​ 题目中讲到给出 “==” 和 “!=” 来表示两个变量之间的关系,有等于和不等于两种关系。一看到题目呢,大家应该都有一个共同的解题思路,将所有相等的变量放在一起,组…...

    2024/4/24 12:09:19
  15. PTA指针练习 判断回文字符串 (20分)

    6-5 判断回文字符串 (20分)本题要求编写函数,判断给定的一串字符是否为“回文”。所谓“回文”是指顺读和倒读都一样的字符串。如“XYZYX”和“xyzzyx”都是回文。函数接口定义:bool palindrome( char *s );函数palindrome判断输入字符串char *s是否为回文。若是则返回true,…...

    2024/4/24 12:09:11
  16. Kotlin基础 - 第十一章高阶函数

    Kotlin基础 - 第十一章高阶函数#### [kotlin官方文档 https://www.kotlincn.net/docs/reference/](https://www.kotlincn.net/docs/reference/) ####高阶函数 以另一个函数作为参数或者返回值的函数被称为高阶函数。 高阶函数可以把函数作为参数传递或者返回值返回的函数。既然…...

    2024/5/2 17:47:59
  17. Spring Boot整合JPA

    3.2 Spring Boot整合JPA (1)添加Spring Data JPA依赖启动器。在项目的pom.xml文件中添加Spring Data JPA依赖启动器,示例代码如下<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactI…...

    2024/4/24 12:09:09
  18. keep-alive不能缓存多层级路由(vue-router)菜单问题解决

    需求 如图需要缓存“风控报告”及“风控规则查询”页面,并且每次删除后才能重新加载(如图二)"风控管理"是一个blank.vue文件(所有嵌套路由其父都是一个仅放的空白文件——占位让其子视图渲染而已),代码如下 <template><router-view /> </templa…...

    2024/4/24 12:09:08
  19. 重启Nginx服务

    重启Nginx服务 方法一:进入nginx可执行目录sbin下,输入命令./nginx -s reload 即可 方法二:查找当前nginx进程号,然后输入命令:kill -HUP 进程号 实现重启nginx服务...

    2024/5/2 17:59:44
  20. 37 岁接触 Python,3 年搭建 Python 金融“金字塔”

    导语: 在从事金融行业的20余年里,斯文博士拥有一系列闪亮的名片——经济学博士、中国注册会计师(CPA),特许金融分析师(CFA)、金融风险管理师(FRM)。 当金融科技(Fintech)被引入国内,他敏锐地意识到,Python将是未来金融职场的必备技能。遗憾的是,Python并没有在中…...

    2024/4/24 12:09:14

最新文章

  1. expected an expression报错

    “expected an expression” 是一种编程错误&#xff0c;通常发生在程序中某个地方需要一个表达式&#xff08;expression&#xff09;的位置&#xff0c;但实际上没有提供一个有效的表达式。 据此&#xff0c;我在main.h—define宏定义中发现了问题&#xff0c;即&#xff1a;…...

    2024/5/10 2:42:02
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/5/9 21:23:04
  3. audio_video_img图片音视频异步可视化加载

    最近在做即时消息&#xff0c;消息类型除了文字还有音频、视频、图片展示&#xff0c;如果消息很多&#xff0c;在切换聊天框时&#xff0c;会有明显卡顿&#xff0c;后续做了懒加载&#xff0c;方案是只加载用户能看到的资源&#xff0c;看不到的先不加载&#xff1b; LazyAud…...

    2024/5/8 2:36:28
  4. Databend 开源周报第 138 期

    Databend 是一款现代云数仓。专为弹性和高效设计&#xff0c;为您的大规模分析需求保驾护航。自由且开源。即刻体验云服务&#xff1a;https://app.databend.cn 。 Whats On In Databend 探索 Databend 本周新进展&#xff0c;遇到更贴近你心意的 Databend 。 支持多表插入 …...

    2024/5/8 2:36:25
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/8 6:01:22
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/5/9 15:10:32
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/5/4 23:54:56
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/5/9 4:20:59
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/5/4 23:54:56
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/5/4 23:55:05
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/5/4 23:54:56
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/5/7 11:36:39
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/5/4 23:54:56
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/5/6 1:40:42
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/5/4 23:54:56
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/5/8 20:48:49
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/5/7 9:26:26
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/5/4 23:54:56
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/8 19:33:07
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/5/5 8:13:33
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/5/8 20:38:49
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/5/4 23:54:58
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/5/9 7:32:17
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/5/9 17:11:10
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57