1)go build 生成编译文件
D:\go_work>go build demo
2)go install 在go工作代码环境的bin目录下生成编译文件
D:\go_work>go install demo
println()回车换行
print()普通输出,不进行换行输出
go语言中不允许隐式转换,所有类型转换必须显示声明,而且转换只能发生在两种相互兼容的类型之间
func main() {
var ch byte
ch = 'a'
var t int
//字节类型可以转换成int类型
t = int(ch)
fmt.Println("t=",t)
var flag bool
flag = true
//bool类型不能转换成int类型
//fmt.Printf("flag=%d\n",int(flag))
}
func main() {
type bigint int64
var a bigint //bigint == int64
fmt.Printf("a type is %T\n",a)
}
加加++,减减–只能放到运算符后边
func main() {
A := 20
A++ // ++只能放到后边
//++A //syntax error: unexpected ++
fmt.Println("A++ 的结果",A)
}
go语言支持最基本的三种程序运行结构:顺序结构、选择结构、循环结构
顺序结构:程序按顺序执行,不发生跳转
选择结构:依据是否满足条件,有选择的执行相应功能
循环结构:依据条件是否满足,循环多次执行某段代码
func main() {
var a int = 3
var b int = 10
if a == 3 { //条件表达式没有括号
fmt.Println("a==3")
}
//支持一个初始化表达式,初始化字句和条件表达式直接需要用分号分隔,b的赋值只是if局部范围内的变量
if b := 3; b == 3 {
fmt.Println("b==3")
}
//获取main函数内的全局变量b=10
fmt.Println("b==",b)//10
}
go里面switch默认相当于每个case最后带有break,匹配成功后不会自动向下执行其他case,而是跳出整个switch,但是可以使用fallthrough强制执行后面的case代码
func main() {
var b int = 3
switch b {
case 1:
fmt.Println("this is number:1")
case 2:
fmt.Println("this is number:2")
case 3:
fmt.Println("this is number:3")
fallthrough //只要你不跳出switch语句,后面无条件执行
default:
fmt.Println("this is number:other" )
}
}
switch内初始化变量并使用
func main() {
switch b := 2; b {
case 1:
fmt.Println("this is number:1")
case 2:
fmt.Println("this is number:2")
//fallthrough //只要你不跳出switch语句,后面无条件执行
case 3:
fmt.Println("this is number:3")
default:
fmt.Println("this is number:other")
}
}
合并相同case语句
func main() {
switch b := 3; b {
case 1:
fmt.Println("this is number:1")
case 2:
fmt.Println("this is number:2")
case 3:
fmt.Println("this is number:3")
case 4, 5, 6, 7: //合并相同语句
fmt.Println("this is number:4,5,6,7")
default:
fmt.Println("this is number:other")
}
}
实现1加到100的和
func main() {
sum := 0
//初始化条件;判断条件;条件变化
for i := 1;i<=100;i++{
sum = sum + i //循环体
}
fmt.Println("sum = ",sum)
}
关键字range会返回两个值,第一个返回值是元素的数组下标,第二个返回值是元素的值
func main() {
str := "abc"
//通过for打印每个字符
for i := 0;i<len(str);i++{
fmt.Printf("str[%d]=%c\n",i,str[i])
}
fmt.Println("------------------------")
//range 迭代打每个元素,默认返回2个值
for i,data := range str{
fmt.Printf("str[%d]=%c\n",i,data)
}
fmt.Println("------------------------")
//第二个返回值默认丢弃,默认返回元素的位置(下标)
for i := range str{
fmt.Printf("str[%d]=%c\n",i,str[i])
}
}
func main() {
i := 0
for{
i++
time.Sleep(time.Second)//睡眠1秒
if i == 5{
//break //跳出循环,如果嵌套多个循环,则跳出最近的那个循环,本层循环
continue //跳出本次循环,继续下一次循环
}
fmt.Println("i=",i)
}
}
goto跳转到必须在当前函数内定义的标签
func main() {
fmt.Println("11111111")
goto END//goto是关键字,END是用户起的名字,他叫标签,goto不能跨函数调用
fmt.Println("22222222")
END:
fmt.Println("3333333")
}
函数构成代码执行逻辑结构。在go语言中,函数的基本组成为:关键字func、函数名、参数列表、返回值、函数体和返回语句。go语言函数定义格式如下
func FuncName(/*参数列表*/) (ol type1,o2 type2/*返回类型*/) {
//函数体
return v1,v2//返回多值
}
函数定义说明:
1、func:函数由关键字 func 开始声明
2、FuncName:函数名称,根据约定,函数名首字母小写即为private,大写即为public
3、参数列表:函数可以有0个或多个参数,参数格式为:变量名 类型,如有多个参数通过逗号分隔,不支持默认参数
4、返回类型:
1)上面返回值声明了两个变量名o1和o2(命名返回参数),这个不是必须,可以只有类型没有变量名
2)如果只有一个返回值且不声明返回值变量,那么你可以省略,包括返回值的括号
3)如果没有返回值,那么就直接省略最后的返回信息
4)如果有返回值,那么必须在函数的内部添加return 语句
1)无参无返回值函数调用
func main() {
MyFuncion()
}
func MyFuncion() {
b := 77
fmt.Println(b)
}
2) 普通函数调用
func main() {
MyFunc("A","send message")
}
func MyFunc(a,b string) {
fmt.Println(a,b)
}
3)不定数量参数类型函数
不定参数是指函数传入的参数个数为不定数量,为了做到这点,首先需要将函数定义为接受不定参数类型
//例如:...type格式的类型只能作为函数的参数类型存在,并且必须是最后一个参数
func Test(args ...int){
for _,n := range args{ //遍历参数列表
fmt.Println(n)
}
}
func main(){
//函数调用,可传0到多个参数
Test()
Test(1,2,3)
}
4)两个函数不定参数传递写法
func MyFunc(tem ...int){
for _,n := range tem{ //遍历参数列表
fmt.Println(n)
}
}
func Test(args ...int){
MyFunc(args...) //实参一定是args...
fmt.Println("------")
MyFunc(args[0:2]...)//通过切片传递指定范围参数
}
func main(){
//函数调用,可传0到多个参数
//Test()
Test(1,2,3)
}
5)参数返回值
func MyFunc() (a int, b, c int) {
a, b, c = 11, 22, 33
return
}
func main() {
a, b, c := MyFunc()
fmt.Printf("a = %d,b = %d,c = %d", a, b, c)
}
6)有参数返回值
交换两个变量
func MyFunc(a,b int) (max,min int) {
if a > b {
max = a
min = b
}else{
max = b
min = a
}
return
}
func main() {
max,min := MyFunc(10,15)
fmt.Printf("max = %d,min = %d,", max,min)
}
1)普通函数递归调用
会先从funccc函数开始打印最后到main函数
func funccc(c int) {
fmt.Println("c=", c)
}
func funccb(b int) {
funccc(b - 1)
fmt.Println("b=", b)
}
func funcca(a int) {
funccb(a - 1)
fmt.Println("a=", a)
}
func main() {
funcca(3)
fmt.Println("main")
}
2)递归函数调用
实现1加到100的和
func MyFnc(num int) int{
if num == 1{
return 1
}
return num + MyFnc(num-1)
}
func main() {
var sum int
sum = MyFnc(100)
fmt.Println("sum = ",sum)
}
在go语言中,函数类型也是一种数据类型,我们可以通过type来定义它,它的类型就是所有拥有相同的参数,相同的返回值的一种类型
func Add(a,b int) int {
return a+b
}
func Minus(a,b int) int {
return a-b
}
//函数也是一种数据类型,通过type给一个函数类型起名
//FuncType 它是一种函数类型
type FuncType func(int,int) int //没有函数名,没有{}
func main() {
//声明为FuncType函数类型
var fTest FuncType
fTest = Add
result := fTest(3,5) //等价于Add(3,5)
fmt.Println(result)
fTest = Minus
result = fTest(3,1) //等价于Minus(3,1)
fmt.Println(result)
}
回调函数,函数有一个参数是函数类型,这个函数就是回调函数。多态,多种形态,调用一个接口,不同表现
type FuncType func(int, int) int
//声明fTest 函数类型为FuncType
func Calc(a, b int, fTest FuncType) (result int) {
result = fTest(a, b) //Add(a,b)
return
}
func Add(a,b int) int {
return a+b
}
func Minus(a,b int) int {
return a-b
}
func main() {
a := Calc(3,4,Add)
fmt.Println("a=",a)
a = Calc(7,5,Minus)
fmt.Println("a=",a)
}
所谓闭包就是一个函数“捕获”了和它在同一作用域的其它常量和变量。这就意味着当闭包被调用的时候,不管在什么程序什么地方调用,闭包能够使用这些常量或者变量
匿名函数:就是没有名字的函数,函数在定义时还没被调用
func main() {
f1 := func() {
fmt.Println("匿名函数")
}
//给函数类型起个别名
type FuncType03 func()
var f2 FuncType03
f2 = f1
f2()
//匿名函数直接调用
func(a, b int) {
fmt.Println("匿名函数直接调用", a, b)
}(2, 3)
}
闭包以引用方式捕获外部变量
func main() {
a := 20
str := "aaa"
func(){//闭包以引用方式捕获外部变量
a = 40
str = "bbb"
fmt.Printf("a=%d,str=%s\n",a,str)//a=40,str=bbb
}()
fmt.Printf("a=%d,str=%s\n",a,str)//a=40,str=bbb
}
func test() func() int {
var x int //x 没有初始化,值为0
return func() int {
x++
return x * x
}
}
func main() {
//返回值是一个匿名函数,返回一个函数类型,通过f来调用返回的匿名函数,f来调用闭包函数
//f不关心这些捕获的变量和常量是否超出范围,只要你的f存在,还在使用x,这个变量就会一直存在
f := test()
fmt.Println(f())
fmt.Println(f())
fmt.Println(f())
fmt.Println(f())
f = test()
fmt.Println(f())
}
定义在{}里面的变量就是局部变量,只能在{}里面有效,执行到定义变量那句话,才开始分配空间,离开作用域自动释放
func main() {
a := 11
test1()
fmt.Println("a=",a)
}
func test1() {
a := 100
fmt.Println("a=",a)
}
定义在{}外部的变量就是全局变量,全局变量在任何地方都能使用,伴生程序
不同作用域允许定义同名变量。使用变量的原则,就近原则
var a int = 10 //全局变量
func main() {
test1()
fmt.Println("a=",a)
}
func test1() {
fmt.Println("a=",a)
}
当一个包导入时,如果该包还导入了其他的包,那么先将其他包导入进来,然后再对这些包中的包及常量和变量进行初始化,接着执行init函数(如果存在init的话),依次类推。等所有被导入的包都加载完毕了,就会开始对main包中的包及常量和变量进行初始化,然后执行main包中的init函数(如果存在的话),最后只执行main函数。
下图解释了整个执行过程:
关键字defer用于延迟一个函数或方法(或当前所创建的匿名函数)的执行。注意,defer语句只能出现在函数或方法的内部
多个defer执行结果是先进后出
func main() {
defer fmt.Println("aaaaaa")
defer fmt.Println("ccccc")
defer fmt.Println("ddddd")
fmt.Println("bbbbbb")
}
结果:
bbbbbb
ddddd
ccccc
aaaaaa
使用:go run main.go a b
func main() {
list := os.Args //接收用户传递参数,都是以字符串方式传递
n := len(list)
fmt.Println("n=", n)
for i:=0;i<n;i++{
fmt.Printf("list[%d]=%s\n",i,list[i])
}
fmt.Println("-----------------------")
for i,data := range list{
fmt.Printf("list[%d]=%s\n",i,data)
}
}
保存某个变量的地址,需要指针类型 int 保存int类型的地址
声明(定义),定义只是特殊的声明,定义一个变量p类型为int( var p *int)
func main() {
var a int = 10
fmt.Printf("a=%d \n", a) //变量的值
fmt.Printf("&a=%v \n", &a) //变量的地址
//保存地址必须要用指针
var p *int //定义一个变量p,类型为*int类型
p = &a //指针变量&a指向谁,就把谁的地址赋值给指针变量p
fmt.Printf("p=%v,a=%v,*p=%v \n",p,&a,*p)
*p = 1000
fmt.Printf("p=%v,a=%v,*p=%v \n",p,&a,*p)
}
func main() {
//a := 10 //整型变量
var p *int //指针变量
// *p = 1000 // 相当于a = 1000
p = new(int)
fmt.Printf("p=%v,*p=%d\n", p, *p)
*p = 6000
fmt.Printf("p=%v,*p=%d\n", p, *p)
q := new(int)
*q = 888
fmt.Println("*q=", *q)
}
数组是指一系列同一类型的数据集合。数组中包含的每个数据被称为数组元素(element),一个数组包含的元素个数被称为数组长度
1)基本使用
func main() {
var id [50]int //数组,同一类型的集合
for i :=0;i<len(id);i++{
id[i]=i+1
fmt.Printf("id[%d]=%d\n",i,id[i])
}
}
2)数组基本使用02
func main() {
//定义一个数组 [10]int和[5]int 是不同的类型
//[数字],这个数字代表的是数组的元素个数
var a [10]int
var b [5]int
fmt.Printf("len(a)=%d,len(b)=%d\n", len(a), len(b))
//注意点 定义数组,指定的数组的个数必须是常量,变量会报错
//n :=10 //non-constant array bound n
//var c [n]int
//fmt.Printf("len(a)=%d\n",len(c))
//操作数组元素,从0开始,到len()-1,不对称元素,这个数字叫下标,下标是可以使用变量的
a[0] = 1
i := 1
a[i] = 10
//赋值,每个元素
for i := 0; i < len(a); i++ {
a[i] = i + 1
}
//打印,第一个返回下标,第二个返回元素对应值
for i, data := range a {
fmt.Printf("a[%d]=%d\n", i, data)
}
}
数组初始化
func main() {
//声明定义同时初始化
var a [5]int = [5]int{1,2,3,4,5}
fmt.Println("a",a)
b := [5]int{1,2,3,4,5} //:= 自动推导b变量类型
fmt.Println("b",b)
//部分初始化,没有初始化的部分自动不全0
c := [5]int{1,2,3}
fmt.Println("c",c)
//指定某个元素的初始化
d := [5]int{2:10,4:20}
fmt.Println("d=",d)
}
数组比较和赋值
func main() {
//只支持 ==、!=,比较要求2个数组类型和长度要一致,[3]int和[4]int无法比较,因为长度不一致
a := [5]int{1,2,3,4,5}
b := [5]int{1,2,3,4,5}
c := [4]int{1,2,3}
fmt.Println("a==b",a==b)
//ac数组变量无法比较,因为长度不一致
//fmt.Println("a==c",a==c)
var d [4]int
d = c
fmt.Println("d=",d)
}
随机数使用
func main() {
//如果种子参数一致,每次运行的程序产生的结果是一致的
//每次运行让种子参数不一致
rand.Seed(time.Now().UnixNano())//以当前系统时间作为种子参数
for i:=0;i<6;i++{
//fmt.Println("rand=",rand.Int())//随机数很大
fmt.Println("rand=",rand.Intn(100))//限制在100以内的数字
}
}
func main() {
a := [5]int{24,69,80,57,13}
n := len(a)
for i:=0;i< n-1;i++{
for j :=0;j<n-1-i;j++{
if a[j]>a[j+1]{
a[j],a[j+1] = a[j+1],a[j]
}
}
}
fmt.Println("a=",a)
}
数组做函数参数,它是值传递,实参数组的每个元素给形参数组拷贝一份,形参的数组是实参数组的复制品
func modify(a [5]int) {
a[1] =666
fmt.Println("modify a=",a)
}
func main() {
a := [5]int{1,2,3,4,5}
modify(a)
fmt.Println("main a=",a)
}
数组 & == p
p指向数组a,它是指向数组,它是数组指针p代表指针所指向的内存就是实参
//*p代表指针所指向的内存就是实参
func modify(p *[5]int) {
(*p)[1] =666
fmt.Println("modify p=",p)
}
func main() {
a := [5]int{1,2,3,4,5}
modify(&a)
fmt.Println("main a=",a)
}
数组的长度在定义之后无法再次修改,数组是值类型,每次传递都将产生一份副本。显然这种数据结构无法完全满足开发者的真实需求。go语言提供了数组切片(slice)来弥补数组的不足。
切片并不是数组或指针,它通过内部指针和相关属性引用数组片段,以实现边长方案。slice并不是真正意义上的动态数组,而是一个引用类型。slice总是指向一个底层array,slice的声明也可以像array一样,只是不需要长度。
func main() {
a := [5]int{1,2,3,4,5}//左包含右不包含
s := a[1:3:5]//1获取元素下标起始位置,3获取元素下标终止位置,5容量取值范围
fmt.Println("s=",s)
fmt.Println("len(s)=",len(s)) //切片长度:3-1=2, {2,3}
fmt.Println("cap(s)=",cap(s)) //容量:5-1=4
}
slice扩容后,地址在扩容后不再指向原有数组,未扩容前地址是指向原数组
func main() {
a := [5]int{1,2,3,4,5}//左包含右不包含
s := a[0:3:5]//1获取元素下标起始位置,3获取元素下标终止位置,5容量取值范围
fmt.Println("s=",s)
fmt.Println("len(s)=",len(s)) //切片长度:3-1=2, {2,3}
fmt.Println("cap(s)=",cap(s)) //容量:5-1=4
fmt.Printf("%p\n",&s)
s = append(s,1)
s = append(s,1)
fmt.Println("s=",s)
fmt.Println("len(s)=",len(s))
fmt.Println("cap(s)=",cap(s))
fmt.Printf("%p\n",&s)
s = append(s,1)
fmt.Println("s=",s)
fmt.Println("len(s)=",len(s))
fmt.Println("cap(s)=",cap(s))
fmt.Printf("%p\n",&s)
//地址在扩容后不再指向原有数组
s[0]=99
fmt.Println("a=",a) // [1 2 3 1 1]
fmt.Println("s=",s) //[99 2 3 1 1 1]
}
1、切片是不定长的,不需要指定固定长度,可增加
2、数组的是长度固定的常量,数组不能修改长度,len和cap固定
func main() {
//自动推导类型,同时进行初始化
s1 := []int{1,2,3,4}
fmt.Printf("len(s1)=%d,cap(s1)=%d \n",len(s1),cap(s1))
//借助make的方式创建切片(类型、长度、容量),没有指定容量则和长度一致
s2 :=make([]int,5,10)
fmt.Println("s2=",s2)
fmt.Printf("len(s2)=%d,cap(s2)=%d",len(s2),cap(s2))
}
func main() {
array := [10]int{0,1,2,3,4,5,6,7,8,9}
//[low:hight:max] s[1:2:4]取下标从low开始 len=hight-low cap=max-low
s1 := array[:]//[0:len(array):len(array)] 长度和容量一样
fmt.Println("s1=",s1)
fmt.Printf("len=%d,cap=%d \n",len(s1),cap(s1))
//操作某个元素和数组操作一样
data := array[1]
fmt.Println("data=",data)
s2 := array[3:6:7] //array[3] array[4] array[5] len=6-3 cap=7-3
fmt.Println("s2=",s2)
fmt.Printf("len=%d,cap=%d \n",len(s2),cap(s2))
s3 := array[:6] //从0开始6个元素 容量10
fmt.Println("s3=",s3)
fmt.Printf("len=%d,cap=%d \n",len(s3),cap(s3))
s4 := array[3:] //从3开始7个元素 容量7
fmt.Println("s4=",s4)
fmt.Printf("len=%d,cap=%d \n",len(s4),cap(s4))
}
func main() {
a := [10]int{0,1,2,3,4,5,6,7,8,9}
s1 := a[2:5] // len=5-2 cap=10-2
s1[1] = 666
fmt.Println("s1=",s1) //[2 666 4]
fmt.Printf("len=%d,cap=%d \n",len(s1),cap(s1))
s2 := s1[2:7] // len=7-2 cap=8-2
s2[1] = 777
fmt.Println("a=",a) //[0 1 2 666 4 777 6 7 8 9]
fmt.Println("s2=",s2) //[4 777 6 7 8]
fmt.Printf("len=%d,cap=%d \n",len(s2),cap(s2))
}
切片在函数传参就地址引用
func InitDate(s []int) {
//设置种子
rand.Seed(time.Now().UnixNano())
for i:=0;i<len(s);i++{
s[i] = rand.Intn(100)
}
}
func BubbleSort(s []int) {
n := len(s)
for i:=0;i<n-1;i++{
for j :=0;j<n-1;j++{
if s[j]>s[j+1]{
s[j],s[j+1] = s[j+1],s[j]
}
}
}
}
func main() {
n := 10
//创建切片
s := make([]int,n)
InitDate(s)
fmt.Println("排序前",s)
BubbleSort(s)
fmt.Println("排序后",s)
}
go语言中的map(映射、字段)是一种内置的数据结构,它是一个无序的key-value对的集合,比如以身份证号作为唯一键来标识一个人的信息
map格式为:
map[keyType]valueType
在一个map里所有的键都是唯一的,而且必须是支持==和!=操作符的类型,切片、函数以及包含切片的结构类型,这些结构类型由于具有引用语义,不能作为映射的键,使用这些类型会造成编译错误。
map值可以是任意类型,没有限制。map里所有键的数据类型必须是相同的,值也必须相同,但键和值数据类型可以不相同。
注意:map是无序的,我们无法决定它的返回顺序,所以,每次打印结果的顺序有可能不同
1)map初始化操作
func main() {
//定义一个map变量,类型为map[int]string,如果使用map一定初始化,make分配内存空间
var m1 map[int]string = make(map[int]string)
fmt.Println("m1=", m1)
m1[1] = "jake"
fmt.Println("m1=", m1)
m2 := make(map[int]string) //make方式创建并自动推导类型
fmt.Println("len=", len(m2))
m2[1] = "mike"
fmt.Println("m2=", m2)
fmt.Println("len=", len(m2))
//map 先给map指定一个可以容纳长度,一旦超过这个长度,从新分配底层空间
m3 := make(map[int]string, 2)
m3[1] = "mile"
m3[2] = "jack"
m3[3] = "go"
fmt.Println("m3=",m3)
fmt.Println("len=",len(m3))
//map另一种初始化并且赋值
m4 := map[int]string{1:"mike",2:"jack",3:"go"}
fmt.Println("m4=",m4)
}
2)map赋值
func main() {
m1 := map[int]string{1:"mike",2:"jack"}
fmt.Println("m1=",m1)
m1[1]="marry" //如果key存在,修改值内容
m1[3]="tom" //如果key不存在,追加内容,类似切片的append()方法
fmt.Println("m1=",m1)
}
3)遍历map值
func main() {
m1 := map[int]string{1: "mike", 2: "jack", 3: "tom"}
for key, value := range m1 {
fmt.Printf("%d=>%s \n", key, value)
}
//第一个返回值为key的所对应的value,第二个返回值为key是否存在的条件,如果存在ok为true
if value, ok := m1[1]; ok == true {
fmt.Println("m1[1]=", value)
} else {
fmt.Println("key不存在")
}
}
4)删除map值
func main() {
m1 := map[int]string{1: "mike", 2: "jack", 3: "tom"}
delete(m1,2)//删除key=2值
fmt.Println(m1)
}
5)map作为函数参数
map和切片一样都是引用类型
func test(m1 map[int]string) { //map和切片一样都是引用类型
delete(m1,2)//删除key=2值
}
func main() {
m1 := map[int]string{1: "mike", 2: "jack", 3: "tom"}
test(m1)
fmt.Println(m1)
}
结构体是一种聚合的数据类型,它是由一系列具有相同类型或不同类型的数据构成的数据集合。每个数据称为结构体的成员。
有时我们需要将不同类型的数据组合成一个有机的整体,如:一个学生有学号/姓名/性别/年龄/地址等属性。显然单独定义以上变量比较繁琐,数据不便于管理。
1)初始化结构体
//定义一个结构体
type Student struct {
id int
name string
sex byte //字符类型
age int
addr string
}
func main() {
//顺序初始化
var s1 Student = Student{1, "mike", 'M', 18, "bj"}
fmt.Println("s1=", s1)
//指定成员初始化,没有初始化的成员,自动赋值为0
s2 := Student{name: "mike", addr: "bj"}
fmt.Println("s2=",s2)
}
2)结构体指针类型初始化
//定义一个结构体
type Person struct {
id int
name string
sex byte //字符类型
age int
addr string
}
func main() {
//p1 存放是地址Person
var s1 *Person = &Person{1, "mike", 'M', 18, "bj"}
fmt.Printf("s1=%v \n", s1)
s2 := &Person{name: "mike", addr: "bj"}
fmt.Printf("s2 type is %T\n",s2)
fmt.Println("s2=",s2)
}
3)结构体通过点操作成员
//定义一个结构体
type Person struct {
id int
name string
sex byte //字符类型
age int
addr string
}
func main() {
var student Person
student.id = 1
fmt.Println(student)
}
4)结构体指针变量操作
//定义一个结构体
type Person struct {
id int
name string
sex byte //字符类型
age int
addr string
}
func main() {
//定义一个普通结构体变量
var s Person
//定义一个指针变量
var p1 *Person
p1 = &s
p1.id = 1
(*p1).name="jack"
p1.sex='M'
fmt.Println("s=",s)
p2 := new(Person)//new 地址
p2.id =1
p2.name="mike"
fmt.Println("p2=",p2)
}
5)结构体作为函数参数(默认为值传递)
//定义一个结构体
type Person struct {
id int
name string
sex byte //字符类型
age int
addr string
}
func test(p Person){
p.id = 666
fmt.Println("test:",p)
}
func test02(p *Person){
(*p).id = 777
fmt.Println("test:",p)
}
func main() {
s := Person{1,"mike",'m',18,"bj"}
test(s) //值传递
fmt.Println("main:",s)
test02(&s)//引用传递
fmt.Println("main:",s)
}
go语言没有沿袭传统的面向对象编程概念,比如继承(不支持继承,尽管匿名字段的内存分布和行为类似继承)
封装:通过方法实现
继承:通过匿名字段实现
多态:通过接口方式实现
go语言对关键字的增加非常吝啬,其中没有private、protect、public这样的关键字。
要使某个符号对其他包(package)可见(即可以访问),需要将该符号定义为以大写字母开头。
一般情况下,定义结构体时候字段名和类型是一一对应的,实际中,go支持只提供类型,而不写字段的方式,这就是匿名字段,也称嵌入式字段。
当匿名字段也是一个结构体的时候,那么这个结构体所有的全部字段都被隐式的方式引入到当前定义的这个结构体中。
type Person struct {
name string
sex byte //字符类型
age int
}
type Student struct {
Person //匿名字段,Student默认继承Person所有的字段
id int
addr string
}
1)匿名字段初始化
注意:
在同一个包下不同文件,定义的结构体或者常量,在任何文件下面都可以使用
type Person struct {
name string
sex byte //字符类型
age int
}
type Student struct {
Person //匿名字段,Student默认继承Person所有的字段
id int
addr string
}
func main() {
//匿名字段初始化
var s1 Student = Student{Person{"mike", 'm', 18}, 1, "bj"}
fmt.Println("s1=", s1)
//自动推导
s2 := Student{Person{"mike", 'm', 18}, 1, "bj"}
//%+v 显示更详细信息
fmt.Printf("s2=%+v\n", s2)
//指定类型初始化,没有初始化的常用类型按照默认值赋值
s3 :=Student{id:1}
s4 :=Student{Person:Person{name:"zhangsan"},id:1}
fmt.Printf("s3=%+v\n", s3)
fmt.Printf("s4=%+v\n", s4)
}
2)匿名对象成员的操作
type Person struct {
name string
sex byte //字符类型
age int
}
type Student struct {
Person //匿名字段,Student默认继承Person所有的字段
id int
addr string
}
func main() {
s1 := Student{Person{"mike",'m',18},1,"bj"}
//对象成员操作
s1.name = "yoyo"
s1.sex = 'f'
s1.age = 22
//对象操作匿名字段方式二
s1.Person = Person{"tom",'m',19}
fmt.Println(s1)
}
3)非结构体的匿名字段
type mystr string //自定义类型,给每一个类型改名
type Person struct {
name string
sex byte //字符类型
age int
}
type Student struct {
Person //匿名字段,Student默认继承Person所有的字段
int
mystr
}
func main() {
s := Student{Person{"mike", 'm', 18}, 1,"test"}
fmt.Printf("s=%+v\n", s)
fmt.Println(s.Person, s.int,s.mystr)
}
4)结构体指针类型匿名字段
type Person struct {
name string
sex byte //字符类型
age int
}
type Student struct {
*Person //匿名字段,Student默认继承Person所有的字段
id int
addr string
}
func main() {
s := Student{&Person{"mike", 'm', 18}, 1,"test"}
fmt.Printf("s=%+v\n", s)
fmt.Println(s.Person, s.id,s.addr)
//先定义变量
var s2 Student
s2.Person = new(Person) //分配内存空间地址
s2.name = "tom"
fmt.Println("s2",s2)
}
1)面向对象与面向过程比对
//面向过程
func Add(a, b int) int {
return a + b
}
//面向对象 方法:给一个类型绑定函数,这个函数就称为方法
type long int
func (a long) add01(b long) long {
return a + b
}
func main() {
//面向过程与面向对象比对
//面向过程:考驾照-》买车-》自己开车-》亲自操作
var result int
result = Add(1, 2)
fmt.Println("result=", result)
//面向对象:出租车.开车方法(目的地)
var a long = 2
result1 := a.add01(3)
fmt.Println(result1)
}
2)为结构体添加方法
在面向对象编程中,一个对象其实就是一个简单的值或者变量,在这个对象中包含一些函数,这种带有接收者的函数我们称为方法,本质上,一个方法则是一个和特殊类型关联的函数。
type Person struct {
name string
sex byte
age int
}
func (p Person) SetInfo(name string, sex byte, age int) {
p.name = name
p.sex = sex
p.age = age
fmt.Println("SetInfo=",p)
}
func main() {
//定义一个结构体变量
var p Person
p.SetInfo("tom",'s',18)
}
3)指针结构体作为参数传递
type Person struct {
name string
sex byte
age int
}
func (p Person) PrintInfo() {
fmt.Println("p=",p)
}
func (p *Person) SetInfo(name string, sex byte, age int) {
p.name = name
p.sex = sex
p.age = age
fmt.Println("SetInfo=",p)
}
func main() {
//定义一个结构体变量
var p Person
p.SetInfo("tom",'s',18)
p.PrintInfo()
}
4)值语义和引用语义
type Person struct {
name string
sex byte
age int
}
func (p Person) SetPersonInfo(n string,s byte,a int) {
p.name = n
p.sex = s
p.age = a
fmt.Println("p=",p)
fmt.Printf("SetPersonInfo &p=%p \n",&p)
}
//接收者为普通变量,非指针,值语义,一份拷贝
func main() {
p := Person{"lili",',',20}
fmt.Printf("main &p=%p \n",&p)
p.SetPersonInfo("tom",'s',18)
fmt.Printf("main &p=%p \n",&p)
}
type Person struct {
name string
sex byte
age int
}
func (p Person) SetPersonInfo(n string,s byte,a int) {
p.name = n
p.sex = s
p.age = a
fmt.Println("p=",p)
fmt.Printf("SetPersonInfo &p=%p \n",&p)
}
func (p *Person) SetPerson2(n string,s byte,a int) {
p.name = n
p.sex = s
p.age = a
fmt.Println("p=",p)
fmt.Printf("SetPerson2 &p=%p \n",&p)
}
func main() {
p := Person{"lili",',',20}
fmt.Printf("main &p=%p \n",&p)
(&p).SetPerson2("tom",'s',18)
fmt.Println("p = \n",&p)
fmt.Printf("main &p=%p \n",&p)
}
5)指针变量方法集
type Person struct {
name string
sex byte
age int
}
func (p Person) SetPersonInfo() {
fmt.Println("p=",p)
fmt.Printf("SetPersonInfo &p=%p \n",&p)
}
func (p *Person) SetPerson2() {
fmt.Println("p=",p)
fmt.Printf("SetPerson2 &p=%p \n",&p)
}
func main() {
//结构体变量是一个指针类型的变量,它能够调用方法,这些可以调用的方法简称方法集
p := &Person{"lili",',',20}
p.SetPersonInfo()
//先把指针p隐式转成*p再调用
//(*p).SetPerson2()
p.SetPerson2()
}
6)方法继承
type Person struct {
name string
sex byte
age int
}
//Student继承Person字段,成员和方法都继承了
type Student struct {
Person
id int
addr string
}
func (tmp Person) PrintInfo() {
fmt.Printf("name=%s,sex=%c,age=%d\n",tmp.name,tmp.sex,tmp.age)
}
func main() {
s := Student{Person{"mike",'m',18},666,"bj"}
//继承了Person方法
s.PrintInfo()
}
7)方法重写
把方法复制给变量叫做值传递
type Person struct {
name string
sex byte
age int
}
//Student继承Person字段,成员和方法都继承了
type Student struct {
Person
id int
addr string
}
func (tmp Person) PrintInfo() {
fmt.Printf("name=%s,sex=%c,age=%d\n",tmp.name,tmp.sex,tmp.age)
}
func (s Student) PrintInfo() {
fmt.Println("Student PrintInfo",s)
}
func main() {
s := Student{Person{"mike",'m',18},666,"bj"}
//就近原则,先找本作用域的方法,找不到再找继承的方法
s.PrintInfo()
}
比如跳高的猫:正常猫(name、color、age)eat sleep
jump()
在go语言中,接口(interface)是一个自定义的类型,接口类型具体描述了一些列的方法集合
接口类型是一种抽象的类型,它不会暴露出所有的对象内部值的机构和这个对象支持的基础操作集合,他们只在乎自己的方法。因此接口类型不能实例化,必须通过子类来实例化。
go通过接口实现了鸭子类型(duck-typing):”当看到一只鸟走起来像鸭子,游泳起来像鸭子,叫起来像鸭子,那么这只鸟就可以被称为鸭子“,我们不关心对象是什么类型,到底是不是鸭子,我们只关心行为。
1)接口定义
type Humaner interface{
SayHai()
}
接口命名习惯以er结尾
接口只有方法声明,没有方法实现,没有数据字段
接口可以匿名嵌入到其他接口,或嵌入到结构体中
注意:接口用来定义行为类型,这些被定义的行为不由接口直接实现,而是通过方法由用户定义的类型实现
//定义一个接口类型
type Humaner interface {
//方法,只有声明,没有实现,由别的类型(自定义类型)实现
sayHi()
}
type Student struct {
id int
name string
}
func (temp *Student)sayHi() {
fmt.Printf("Student[%d,%s] sayHi\n",temp.id,temp.name)
}
func main() {
//定义一个接口的类型
var i Humaner
s := &Student{1,"alan"}
//实现Humaner接口方法sayHi()
i = s
i.sayHi()
}
2)接口赋值与转换
//定义一个接口类型
type Humaner interface {
//方法,只有声明,没有实现,由别的类型(自定义类型)实现
sayHi()
}
type Personer interface {
Humaner //匿名字段,继承sayHi()
sing(lrc string)
}
type Student struct {
id int
name string
}
func (temp *Student) sayHi() {
fmt.Printf("Student[%d,%s] sing\n", temp.id, temp.name)
}
func (temp *Student) sing(lrc string) {
fmt.Println("Student sing", lrc)
}
func main() {
//定义一个接口的类型
var p Personer
s := &Student{1, "alan"}
p = s
p.sayHi()
p.sing("义勇军进行曲")
var h Humaner
h = s
h.sayHi()
h = p//子接口可以赋值给父接口,但父接口不能赋值给
}
3)空接口使用
func xxx(arg ...interface{}) { //interface{}空接口
for i, k := range arg {
fmt.Println(i, k)
}
}
func main() {
//空接口万能类型,保存任意类型的值
var i interface{} = 1
fmt.Println("i=",i)
i="abc"
fmt.Println("i=",i)
xxx(i)
}
type Student struct {
id int
name string
}
func main() {
i := make([]interface{},3)
i[0] = 1
i[1] = "hello go"
i[2] = Student{1,"alan"}
//类型查询,即(类型断言)
for index,data := range i{
if value,ok := data.(int);ok == true{
fmt.Printf("i[%d] 类型为int,内容为%d\n",index,value)
}else if value,ok := data.(string);ok == true{
fmt.Printf("i[%d] 类型为string,内容为%s\n",index,value)
}else if value,ok := data.(Student);ok == true {
fmt.Printf("i[%d] 类型为Student,内容name=%s\n",index,value.name)
}
}
}
错误接口使用
func main() {
var err1 error = fmt.Errorf("%s", "this is normal err")
fmt.Println("err1=", err1)
err2 := errors.New("this is normal err2")
fmt.Println(err2)
}
func MyDiv(a, b int) (result int, err error) {
err = nil
if b == 0 {
err = errors.New("分母不为零")
} else {
result = a / b
}
return
}
func main() {
result, err := MyDiv(10, 0)
if err != nil {
fmt.Println("err=", err)
} else {
fmt.Println("result=", result)
}
}
显示调用panic函数,导致程序的中断
func testa() {
fmt.Println("aaaaaaa")
}
func testb() {
fmt.Println("bbbbbb")
//程序会执行到这开始中断
panic("panic exists...")
}
func testc() {
fmt.Println("cccccc")
}
func main() {
testa()
testb()
testc()
}
recover恢复错误中断导致的程序
func testa() {
fmt.Println("aaaaaaa")
}
func testb(x int) {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
var a [10]int
a[x] = 666
}
func testc() {
fmt.Println("cccccc")
}
func main() {
testa()
testb(20)
testc()
}
字符串在开发中经常用到,包括用户的输入,数据库读取的数据等,我们经常需要到字符串进行分割、连接、转换等操作,我们可以通过go标准库中的strings和strconv两个包中的函数进行相应的操作
func Contains(s,substr,string) bool
功能:字符串s中是否包含substr,返回bool值
//"hellogo" 中是否包含hello,包含返回true
fmt.Println(strings.Contains("hellogo","hello"))
fmt.Println(strings.Contains("hellogo","abc"))
func Join(a []string,sep string) string
功能:字符串连接,把slice a通过sep连接起来
s := []string{"abc", "hello", "mike", "go"}
buf := strings.Join(s, "_")
fmt.Println("buf=", buf) //buf= abc_hello_mike_go
func Index(s,sep string) int
功能:在字符串s中查找sep所在的位置,返回位置值,找不到返回-1
fmt.Println(strings.Index("abcdhello","hello")) //4
fmt.Println(strings.Index("abcdhello","test")) //-1
func Repeat(s string,count int) string
功能:重复s字符串count次,最后返回重复的字符串
buf := strings.Repeat("go", 5)
fmt.Println("buf=", buf)
func Split(s,sep string) []string
功能:把s字符串按照sep分割,返回slice
buf := "abc_hello_mike_go"
s2 := strings.Split(buf,"_")
fmt.Println("s2=",s2)
去除两头的空格字符
buf := strings.Trim(" are you ok? ", " ")
fmt.Printf("buf=#%s#\n", buf)
s3 := strings.Fields(" are you ok? ")
fmt.Println("s3", s3) //s3 [are you ok?]
func main() {
//转换成字符串后追加到字节数组中
slice := make([]byte, 0, 1024)
slice = strconv.AppendBool(slice, true)
//第二个数为要追加的数,第三个指规定追加方式
slice = strconv.AppendInt(slice, 1234, 10)
slice = strconv.AppendQuote(slice, "adfadf")
fmt.Println("slice=", string(slice))
//把其他类型转换成字符串
var str string
str = strconv.FormatBool(true)
strconv.FormatInt(10, 8)
fmt.Println("str=", str)
str = strconv.FormatFloat(3.14, 'f', -1, 64)
fmt.Println("str=", str)
//常用,把整形转换成字符串
str = strconv.Itoa(666)
fmt.Println("str=", str)
}
把字符串转换成其他类型
func main() {
//把字符串转换成其他类型
var flag bool
var err error
flag,err = strconv.ParseBool("true")
if err == nil{
fmt.Println("flag=",flag)
}else{
fmt.Println("err=",err)
}
a,err := strconv.Atoi("123")
if err == nil{
fmt.Println("a=",a)
}else{
fmt.Println("err=",err)
}
}
正则表达式是一种进行模式匹配和文本操纵的复杂而又强大的工具。虽然正则表达式比纯粹的文本匹配效率低,但是它却更灵活。按照它的语法规则,随需构造出匹配模式就能够从原始文本中筛选出几乎任何你想要得到的字符组合。
其实字符串处理我们可以使用strings包来进行搜索(Contains、Index)、替换(Replace)和解析(Split、Join)等操作,但是这些都是简单的字符操作,他们的搜索都是大小写敏感,而且固定的字符串,如果我们需要匹配可变的那种就没办法了,当然如果strings包能解决你的问题,那么就尽量使用它来解决。因为他们足够简单、而且性能和可读性都会比正则好。
规则
. 匹配除换行符以外的任意字符
\w 匹配字母或数字或下划线或汉字
\s 匹配任意的空白符
\d 匹配数字
\b 匹配单词的开始或结束
^ 匹配字符串的开始
& 匹配字符串的结束
* 重复零次或更多次
+ 重复一次或更多次
? 重复零次或一次
{n}重复n次
{n,}重n次或更多
{n,m}重复n到m次
()标记一个子表达式的开始和结束的位置, 子表达式可以获取供以后使用
正则表达式规范
1)写一个规则进行编译
//匹配^z开头,&l结尾,.任意字符 *重复零次或者多次的字符串
reg := regexp.MustCompile(`^z.*l$`)
2)拿规则reg去匹配查询的字符串
//匹配-1表示所有的字符
result := reg.FindAllString("zhangshanl zhangshanl",-1)
fmt.Printf("%v\n",result)
二位数组格式返回结果
func main() {
buf := "abc asszc a7c aac 888 ass9c tac"
//匹配axc字符串,x为匹配除换行符以外的任意字符
//reg := regexp.MustCompile(`a.c`)
//匹配a[0-9]c字符串,0-9的单个数字
//reg := regexp.MustCompile(`a[0-9]*c`)
reg := regexp.MustCompile(`a\dc`)
//reg := regexp.MustCompile(`a\wc`)
//result := reg.FindAllString(buf, -1)
//切片方式返回二位数组
result := reg.FindAllStringSubmatch(buf, -1)
fmt.Printf("%v\n", result)
}
正则表达式查找邮箱
func main() {
buf := `[email protected]
email is [email protected]
email is [email protected]
[email protected]`
reg := regexp.MustCompile(`([A-Za-z0-9]+)@([A-Za-z0-9]+)\.([A-Za-z0-9]+)+`)
result := reg.FindAllStringSubmatch(buf, -1)
fmt.Printf("%v\n", result)
for _,m := range result{
fmt.Println(m[0])
}
}
Json(javaScript Object Notation)是一种比XML更轻便的数据交换格式,易于读和编写,也易于程序的解析和生成。json表现形式一般表现为键/值对应的集合,这种json传输为较为理想的跨平台跨语言的数据交换格式
go标准库encoding/json,开发者可以轻松使用go语言程序生成json格式的数据
1)结构体生成json
type IT struct {
Company string
Subjects []string
IsOk bool
Price float64
}
func main() {
s := IT{"zhan", []string{"go", "docker", "fabric", "test"}, true, 3.14}
buf, err := json.Marshal(s)
if err != nil {
fmt.Println("err", err)
return
}
fmt.Println("buf=", string(buf))
}
2)json结构体的二次编码
将大写字段在输出时候转小写
type IT struct {
Company string `json:"company"` //二次编码输出
Subjects []string `json:"-"` //-此字段不会输出到屏幕上(隐藏字段)
IsOk bool `json:"is_ok,string"`
Price float64 `json:",string"`
}
func main() {
s := IT{"zhan", []string{"go", "docker", "fabric", "test"}, true, 3.14}
buf, err := json.Marshal(s)
if err != nil {
fmt.Println("err", err)
return
}
fmt.Println("buf=", string(buf))
}
3)json格式化输出
type IT struct {
Company string `json:"company"` //二次编码输出
Subjects []string `json:"-"` //-此字段不会输出到屏幕上(隐藏字段)
IsOk bool `json:"is_ok,string"`
Price float64 `json:",string"`
}
func main() {
s := IT{"zhan", []string{"go", "docker", "fabric", "test"}, true, 3.14}
//buf, err := json.Marshal(s)
buf, err := json.MarshalIndent(s,"","") //格式化输出
if err != nil {
fmt.Println("err", err)
return
}
fmt.Println("buf=", string(buf))
}
4)map生成json
func main() {
//interface{}表示万能类型,保存任意类型的值
m := make(map[string]interface{}, 4)
m["company"] = "zhan"
m["subjects"] = []string{"go", "fabric", "python"}
m["isok"] = false
m["price"] = 99
result, err := json.Marshal(m)
if err != nil {
fmt.Println("err=", err)
return
}
fmt.Println("result=", string(result))
}
5)Json解析到结构体
type IT struct {
Company string `json:"company"`
Subjects []string `json:"subjects"`
IsOk bool `json:"isok"`
Price float64 `json:"price"`
}
func main() {
jsonbuf := `{"company":"zhan","isok":false,"price":99,"subjects":["go","fabric","python"]}`
//定义一个结构体变量
var temp IT
err := json.Unmarshal([]byte(jsonbuf), &temp) //一定要传入地址
if err != nil {
fmt.Println("err=", err)
return
}
fmt.Println("temp=", temp)
}
6)json解析到map,并断言方式输出结果
func main() {
jsonbuf := `{"company":"zhan","isok":false,"price":99,"subjects":["go","fabric","python"]}`
//定义一个map变量
m := make(map[string]interface{}, 4)
err := json.Unmarshal([]byte(jsonbuf), &m) //一定要传入地址
if err != nil {
fmt.Println("err=", err)
return
}
fmt.Println("m=", m)
//输出map值,类型断言判断
var str string
for key,value := range m{
switch data := value.(type) {
case string:
str = data
fmt.Printf("map[%s]的值类型为string,value=%s\n",key,str)
case bool:
fmt.Printf("map[%s]的值类型为bool,value=%v\n",key,data)
case float64:
fmt.Printf("map[%s]的值类型为float64,value=%f\n",key,data)
case []string:
fmt.Printf("map[%s]的值类型为[]string,value=%v\n",key,data)
case []interface{}:
fmt.Printf("map[%s]的值类型为interface,value=%v\n",key,data)
}
}
}
1)标准设备写入与读取内容
func main() {
//往标准输入设备(屏幕)写内容
fmt.Println("write start")
os.Stdout.WriteString("are you ok?\n")
var a int
fmt.Println("请输入一个int类型")
fmt.Scan(&a)
fmt.Println("a=",a)
}
2)往文件写入数据
func WriteFile(path string) {
//打开文件,没有则新建文件
f, err := os.Create(path)
if err != nil {
fmt.Println("err = ", err)
return
}
//使用完毕,关闭文件
defer f.Close()
var buf string
for i := 0; i < 10; i++ {
buf = fmt.Sprintf("i=%d\n", i)
n, err := f.WriteString(buf)
if err != nil {
fmt.Println("err=", err)
return
}
fmt.Println("n=", n)
}
}
func main() {
path := "./demo.txt"
WriteFile(path)
}
3)读取文件数据
func ReadFile(path string){
f,err := os.Open(path)
if err != nil {
fmt.Println("err=", err)
return
}
defer f.Close()
buf := make([]byte,1024*2) //每次读取2k大小
n,err := f.Read(buf)
if err != nil && err != io.EOF { //文件出错同时文件没有到结尾
fmt.Println("err=", err)
return
}
fmt.Println("buf=",string(buf[:n]))
}
func main() {
path := "./demo.txt"
ReadFile(path)
}
4)每次读取一行数据
func ReadFileLine(path string) {
f, err := os.Open(path)
if err != nil {
fmt.Println("err=", err)
return
}
defer f.Close()
//新建缓冲区,把内容放入到缓冲区里
r := bufio.NewReader(f)
for {
//遇到\n结束读取,但是\n也进入缓冲区
buf, err := r.ReadBytes('\n')
if err != nil {
if err == io.EOF { //文件读取结束
break
}
fmt.Println("err=", err)
}
fmt.Printf("buf=#%s#\n", string(buf))
}
}
func main() {
path := "./demo.txt"
ReadFileLine(path)
}
并发和并行
并行:同一时间两台以上机器同时运行
并发:指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。
go在并发上的优势
有人把go语言比作21世纪的C语言,第一因为go语言设计简单,第二,21世纪最重要的就是并发程序设计,而go语言底层就是支持了并发和并行。同时并发程序内存管理有时候非常复杂,而go语言提供了自动垃圾回收机制。
go语言并发编程内置了上层API基于顺序通信模型。避免锁的麻烦。因为go语言通过相当安全的通道发送和接收数据以实现同步,这就大大简化了并发程序的编写。
goroutine是什么?
goroutine是go并发设计的核心。goroutine说到底就是协程。但它比线程更小,十几个goroutine可能体现在底层就是56个线程。go语言内部帮我们实现这些goroutine的内存共享内存。执行goroutine只需要极少的栈内存(大概45KB),当然会根据相应的数据进行伸缩,也正是如此,可以运行上万个并发任务。goroutine比thread更容易用,更高效,更轻便。
创建goroutine
只需要在函数调用语句前添加go关键字,就可以创建并发执行单位。开发人员不用了解任何机制,调度器就会安排到合适的系统上执行。
在并发编程里面。我们通常想要将一个过程切分成几块,然后让每个goroutine各自执行一块工作。当一个程序启动时,其主函数在一个单独的goroutine里面运行,这个主函数我们叫main goroutine。新的goroutine会用go语句来实现。
func newTask() {
for{
fmt.Println("this is newTask goroutine")
time.Sleep(time.Second)
}
}
func main() {
go newTask() //go 关键字就新建一个协程,新建一个业务
for{
fmt.Println("this is main goroutine")
time.Sleep(time.Second)
}
}
由于主协程先退出导致子协程没来得及调用
func main() {
//由于主协程先退出导致子协程没来得及调用
go func() { //子协程代码,由于程序并发执行,会先从主协程代码开始,但是主协程并未有处理代码从了结束了程序
i := 0
for{
i++
fmt.Println("子协程 i=",i)
time.Sleep(time.Second)
}
}()
//这里没有主协程代码
}
让出时间片,先让别的协程执行,它执行完,再来执行此程序
func main() {
go func() {
i := 0
for {
i++
fmt.Println("子协程 i=", i)
time.Sleep(time.Second)
}
}()
for i := 0; i < 2; i++ {
//方法1:让出时间片,先让别的协程执行,它执行完,再来执行此程序
runtime.Gosched()
fmt.Println("hello")
//方法2:让出时间片,先让别的协程执行,它执行完,再来执行此程序
//time.Sleep(time.Second)
}
}
终止协程
func test(){
defer fmt.Println("ccccc")
//return //终止函数
runtime.Goexit() //终止所有协程
fmt.Println("dddddd")
}
func main() {
go func() {
fmt.Println("aaaaaa")
test()
fmt.Println("bbbbbb")
}()
for {
}
}
goroutine运行在相同的地址空间,因此访问共享内存必须做好同步。goroutine奉行通过通信来共享内存,而不是共享内存来通信。
引用类型channel是CSP模式的具体实现,用于多个goroutine通信。其内部实现了同步,确保并发安全。
和map类似,channel也通过make创建的底层数据结构的引用。
当我们复制一个channel或用于函数参数传递时,我们只是拷贝了一个channel引用,因此多个调用者之间引用的是channel对象。和其它的引用类型一样,channel的零值也是nil。
1)channel创建
当capacity=0时,channel是无缓冲阻塞读写的,当capacity>0时,channel有缓冲、是非阻塞的,直到写满capacity个元素才阻塞写入。
make(chan Type)//等价于make(chan Type,0)
make(chan Type,capacity)
2)channel定义
channel通过操作符<-来接收和发送数据,发送和接收数据语法
channel<-value //发送value到channel
<-channel //接收并将其丢弃
x := <-channel //从channel中接收数据,并赋值给x
x,ok := <-channel //功能同上,同时检查通道是否关闭或者是否为空
3)通过Channel实现同步和数据交互
func main() {
//创建channel
ch := make(chan string)
//defer为先进后出
defer fmt.Println("主协程结束")
go func() {
defer fmt.Println("子协程也结束")
for i := 0; i < 2; i++ {
fmt.Println("子协程i=", i)
time.Sleep(time.Second)
}
ch <- "子协程,工作完毕"
}()
str := <-ch //没有数据则处于阻塞状态
fmt.Println("str=", str)
}
无缓冲的通道是指在接收前没有能力保存任何值的通道。
这种类型的通道要求发送goroutine和接收goroutine同时准备好,才能完成发送和接收操作。如果两个goroutine没有同时准备好,通道会导致先执行发送或接收操作的goroutine阻塞等待。
这种对通道进行发送和接收的交互行为本身就是同步的,其中任意一个一个操作都无法离开另一个操作单独存在。
1)使用channel解决打印资源抢占问题
//定义全局变量,创建以一个channel
var ch = make(chan string)
//定义一个打印机,参数字符串,按照每个字符打印
func Printer(str string) {
for _, data := range str {
fmt.Printf("%c", data)
time.Sleep(time.Second)
}
fmt.Printf("\n")
}
func Person01() {
Printer("hello")
//阻塞,先打印hello后打印world
ch<-"aaa"
}
func Person02() {
<-ch //从管道读取数据,接收,如果没有数据它会阻塞
Printer("world")
}
func main() {
//2个协程公用一个资源
go Person01()
go Person02()
for {
}
}
案例二:
func main() {
//创建一个无缓冲区的channel
ch := make(chan int, 0)
//len(ch)缓冲区使用数据个数,cap(ch)缓冲区大小
fmt.Printf("len(ch)=%d,cap(ch)=%d", len(ch), cap(ch))
go func() {
for i := 0; i < 3; i++ {
fmt.Printf("子协程:i=%d\n", i)
ch <- i //往ch写内容
}
}()
time.Sleep(time.Second*2)
for i := 0; i < 3; i++ {
num := <-ch //读取管道内容,没有内容则阻塞
fmt.Println("num=", num)
}
}
有缓冲通道是一种在被接收前能存储一个或者多个值的通道。
这种类型的通道并不强制要求goroutine之间必须同时完成发送和接收。通道会阻塞发送和接收动作的条件也会不同。只有在通道中没有要接收的值时,接收动作才会阻塞。只有在通道没有可用缓冲区容纳被发送的值时,发送动作才会被阻塞。
这导致有缓冲的通道和无缓冲的通道之间的一个很大的不同;无缓冲的通道保证进行发送和接收goroutine会在同一时间进行数据交换;有缓冲的通道没有这种保证。
func main() {
//创建一个有缓冲区的管道
ch := make(chan int, 3)
//len(ch) 缓冲区里有多个数据,cap(ch)缓冲区大小
fmt.Printf("len(ch)=%d,cap(ch)=%d\n", len(ch), cap(ch))
go func() {
for i := 0; i < 10; i++ {
ch <- i //往chan写内容
fmt.Printf("子协程[%d]:len(ch)=%d,cap(ch)=%d\n", i, len(ch), cap(ch))
}
}()
time.Sleep(time.Second*2)
for i := 0; i < 3; i++ {
num := <-ch //管道只有3个的容量,取完就会进行阻塞等待放入才能消费
fmt.Println("num=", num)
}
}
如果发送者知道,没有更多的值需要发送到channel的话,那么让接收者也能及时知道没有多余的值可以接收将是有用的,因为接收者可以停止不必要的接收等待。这可以通过内置的close函数来关闭channel实现。
注意:
channel不像文件一样需要经常去关闭,只有当你确实没有任何数据发送时,或者你想显示的结束range循环之类的,才去关闭channel;
关闭channel后,无法再向channel发送数据
关闭channel后,可以继续向channel接收数据
对于nil channel,无论收发都会被阻塞
1)关闭管道
func main() {
//创建一个无缓冲区的管道
ch := make(chan int)
//len(ch) 缓冲区里有多个数据,cap(ch)缓冲区大小
fmt.Printf("len(ch)=%d,cap(ch)=%d\n", len(ch), cap(ch))
go func() {
for i := 0; i < 5; i++ {
ch <- i //往chan写内容
fmt.Printf("子协程[%d]:len(ch)=%d,cap(ch)=%d\n", i, len(ch), cap(ch))
}
//关闭ch后就不发再写入数据 ch <- 666
close(ch)
}()
for {
// 如果ok == true,说明管道没有关闭
if num, ok := <-ch; ok == true {
fmt.Println("num=", num)
} else { //管道关闭
break
}
}
}
2)通过range读取对应的值
func main() {
//创建一个无缓冲区的管道
ch := make(chan int)
go func() {
for i := 0; i < 5; i++ {
ch <- i //往chan写内容
fmt.Printf("子协程[%d]:len(ch)=%d,cap(ch)=%d\n", i, len(ch), cap(ch))
}
//关闭ch后就不发再写入数据 ch <- 666
close(ch)
}()
for num := range ch {
fmt.Println("num=", num)
}
}
默认情况下,通道是双向的,也就是,即可以往里面发送数据也可以往里面接收数据。我们经常见一个通道作为参数进行传递值希望对方是单向使用的,要么只让它发送数据,要么只让它接收数据,这时候我们可以指定通道的方向
func main() {
//创建管道
ch := make(chan int)
//双向channel隐式转换为单向的channel
var writech chan <- int = ch //只能写不能读
writech <- 666
//error
//<- writech //invalid operation: <-writech (receive from send-only type chan<- int)
var readch <- chan int = ch //只能读不能写
<- readch
}
单向管道应用
//创建管道只能写,不能读
func producter(out chan<- int) {
for i := 0; i < 10; i++ {
out <- i
}
}
func consumer(in <-chan int) {
for num := range in {
fmt.Println("num=", num)
}
}
func main() {
//创建双向管道
ch := make(chan int)
//生产者,开启一个协程
go producter(ch)
//消费者,从channel读取内容
consumer(ch)
}
Timer是一个定时器,代表未来的一个单一事件,你可以告诉timer你要等待多长时间,它提供一个channel,在将来的那个时间那个channel提供了一个时间值。
func main() {
//创建一个定时器,设置时间为2s,2s后往time通道写数据
timer := time.NewTimer(2 * time.Second)
fmt.Println("当前时间:", time.Now())
t := <-timer.C //channel没有数据前后阻塞
fmt.Println("t=", t)
}
验证time.NewTimer时间到只打印一次
func main() {
//验证time.NewTimer时间到只打印一次
timer := time.NewTimer(2 * time.Second)
for {
<-timer.C
fmt.Println("时间到")
}
}
延时的三种方式
func main() {
//第一种
//time.Sleep(2 * time.Second)
//fmt.Println("时间到")
//第二种
/*timer := time.NewTimer(2 * time.Second)
for {
<-timer.C
fmt.Println("时间到")
}*/
//第三种
<- time.After(2 * time.Second)
fmt.Println("时间到")
}
定时器停止操作
func main() {
timer := time.NewTimer(3 * time.Second)
go func() {
<-timer.C
fmt.Println("定时器时间到,打印子协程")
}()
//定时3秒未到,这里就停止了时间,导致<-timer.C接收不到数据从而阻塞
timer.Stop()
for {
}
}
定时器时间重置
func main() {
timer := time.NewTimer(3 * time.Second)
ok := timer.Reset(1 * time.Second) //把以前3s重置为1s
fmt.Println("ok=", ok)
<-timer.C
fmt.Println("时间到")
}
ticker是一个定时触发的计时器,它会以一个间隔往channel发送一个事件(当前使劲),而channel的接收者可以固定的时间间隔从channel中读取事件(周期性)
func main() {
ticker := time.NewTicker(1 * time.Second)
i := 0
for {
<-ticker.C //1s时间间隔
i++
fmt.Println("i=", i)
if i == 5 {
ticker.Stop()
break
}
}
}
获取D:/demo.txt的文件属性
go run main.go D:/demo.txt
func main() {
list := os.Args
fmt.Println(len(list))
if len(list) != 2 {
fmt.Println("useage:xxx file")
return
}
fileName := list[1]
info, err := os.Stat(fileName)
if err != nil {
fmt.Println("err=", err)
return
}
fmt.Println("name=", info.Name())
fmt.Println("size=", info.Size()) //字节
}
文件操作(客户端发送文件,服务端接收并保存文件)
1)服务端接收并保存文件
//文件接收
func RecvFile(filename string, conn net.Conn) {
//新建文件
f, err := os.Create(filename)
if err != nil {
fmt.Println("os.Create err=", err)
return
}
buf := make([]byte, 1024)
for {
n, err := conn.Read(buf) //接收对方传过来的文件内容
if err != nil {
if err == io.EOF {
fmt.Println("文件接收完毕")
} else {
fmt.Println("conn.Read err=", err)
}
return
}
if n == 0 {
fmt.Println("n==0 文件接收完毕")
break
}
f.Write(buf[:n])
}
}
func main() {
//监听
listenner, err := net.Listen("tcp", "127.0.0.1:8000")
if err != nil {
fmt.Println("net.Listen err=", err)
return
}
defer listenner.Close()
//阻塞等待用户连接
conn, err := listenner.Accept()
if err != nil {
fmt.Println("listenner.Accept err=", err)
return
}
defer conn.Close()
//缓冲
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil {
fmt.Println("conn.Read err=", err)
return
}
filename := string(buf[:n])
//回复ok
conn.Write([]byte("ok"))
//接收文件内容
RecvFile(filename, conn)
}
2)客户端发送文件
func SendFile(path string, conn net.Conn) {
//以只读的方式打开文件
f, err := os.Open(path)
if err != nil {
fmt.Println("os.Open err=", err)
return
}
defer f.Close()
//缓冲每次读取文件1024*4,内读完的继续轮询读取文件内容
buf := make([]byte,1024*4)
for{
n,err := f.Read(buf)
if err != nil{
if err == io.EOF{
fmt.Println("文件传输完毕")
}else{
fmt.Println("f.Read err=",err)
}
return
}
//发送文件内容
conn.Write(buf[:n])
}
}
func main() {
//提示输入文件
fmt.Println("请输入需要传输的文件")
var path string
fmt.Scan(&path)
//获取文件名,info.name()
info, err := os.Stat(path)
if err != nil {
fmt.Println("os.Stat err=", err)
return
}
conn, err := net.Dial("tcp", "127.0.0.1:8000")
if err != nil {
fmt.Println("net.dial err=", err)
return
}
//defer conn.Close()
//给接收方发送文件名
_,err = conn.Write([]byte(info.Name()))
if err != nil {
fmt.Println("conn.Write err=", err)
return
}
var n int
buf := make([]byte, 1024)
n, err = conn.Read(buf)
if err != nil {
fmt.Println("conn.Read err=", err)
return
}
if "ok" == string(buf[:n]) {
//发送文件内容
SendFile(path, conn)
}
}
go里面提供了一个关键字select,通过select可以监听channel上的数据流动。
select的用法与switch语言非常相似,由select开始一个新的选择块,每个选择条件由case语句来描述。
与switch语句可以选择任何可使用相等比较的条件相比,select有较多的限制,其中最大的一条限制就是每个case语句里必须是一个IO操作。
1)select实现斐波那契数列相加
func fibonacci(ch chan<- int, quit <-chan bool) {
x, y := 1, 1
for {
//监听channel数据流动
select {
case ch <- x:
x, y = y, x+y
case flag := <-quit:
fmt.Println("flag=", flag)
return
}
}
}
func main() {
ch := make(chan int) //数字通信
quit := make(chan bool) //程序是否退出
go func() {
for i := 0; i < 8; i++ {
num := <-ch
fmt.Println("num=", num)
}
quit <- true
}()
fibonacci(ch, quit)
}
2)通过Select实现超时功能
有时候会出现goroutine阻塞的情况,那么我们如何避免整个程序进入阻塞的情况呢?我们可以利用select来设置超时
func main() {
ch := make(chan int)
quit := make(chan bool)
go func() {
for {
select {
case num := <-ch:
fmt.Println("num=", num)
case <-time.After(3 * time.Second):
quit <- true
}
}
}()
for i := 0; i < 5; i++ {
ch <- i
time.Sleep(time.Second)
}
qt := <-quit
fmt.Println("程序结束 qt=", qt)
}
socket起源Unix而Unix基本哲学是一切皆文件,可以用”打开open-》读写write/read-》关闭close”模式来操作。socket就是该模式一个实现。网络的socket数据传输其实就是一种特殊的I/O。socket也是一种文件描述符,socket也是具有一个类似文件打开,函数读写调用,文件关闭操作。socket()该函数返回一个整体的socket描述符,随后建立连接,数据传输等操作都是通过socket实现。
socket分类:流式socket和数据报式socket,流式socket是一种面向连接的socket,标准tcp服务应用,数据报式socket是一种无连接的socket,对应udp
socket中TCP模型
1)Socket TCP服务端代码
func main() {
//监听
listener, err := net.Listen("tcp", "127.0.0.1:8000")
if err != nil {
fmt.Println("err=", err)
return
}
//关闭连接
defer listener.Close()
//阻塞等待客户端用户的连接
conn, err := listener.Accept()
if err != nil {
fmt.Println("err=", err)
return
}
//接收用户的请求
buf := make([]byte, 1024) //1024大小进行缓存
//阻塞等待用户的连接,并读取客户端写入的数据
n, err := conn.Read(buf)
if err != nil {
fmt.Println("err=", err)
return
}
fmt.Println("buf=", string(buf[:n]))
defer conn.Close()
}
2)Socket TCP客户端代码
func main() {
conn, err := net.Dial("tcp", "127.0.0.1:8000")
if err != nil {
fmt.Println("err=", err)
return
}
defer conn.Close()
//发送数据
conn.Write([]byte("hello world,learn go!"))
}
Socket多用户多客户端返回数据
1)Socket 服务端代码
func HandleConn(conn net.Conn) {
//函数调用完毕,自动关闭conn
defer conn.Close()
//获取客户端的网络地址信息
addr := conn.RemoteAddr().String()
fmt.Println(addr, "connect sucessful")
//分批缓冲数据,每次2048
buf := make([]byte, 2048)
for {
//读取缓冲的数据
n, err := conn.Read(buf)
if err != nil {
fmt.Println("err=", err)
return
}
fmt.Printf("[%s]:%s\n", addr, string(buf[:n]))
fmt.Println("len=", len(string(buf[:n])))
if "exit" == string(buf[:n-2]) {
fmt.Println(addr, "exit")
return
}
//把用户小写转换成大写,再发给用户
conn.Write([]byte(strings.ToUpper(string(buf[:n]))))
}
}
func main() {
//监听
listener, err := net.Listen("tcp", "127.0.0.1:8000")
if err != nil {
fmt.Println("err=", err)
return
}
//关闭连接
defer listener.Close()
//接收多个用户连接
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("err=", err)
return
}
//处理用户请求,新建立一个协程
go HandleConn(conn)
}
}
2)Socket客户端代码
func main() {
conn, err := net.Dial("tcp", "127.0.0.1:8000")
if err != nil {
fmt.Println("net.dial err=", err)
return
}
defer conn.Close()
//从键盘输入,开启一个协程
go func() {
//从键盘输入,发送给服务器
str := make([]byte, 1024)
for {
n, err := os.Stdin.Read(str) //从键盘输入,放入到str里面
if err != nil {
fmt.Println("os.stdin.err=", err)
return
}
//把内容发给服务器
conn.Write(str[:n])
}
}()
//接收服务器回复的数据
buf := make([]byte, 1024)
for {
n, err := conn.Read(buf) //接收服务器请求
if err != nil {
fmt.Println("conn.Read err=", err)
return
}
fmt.Println(string(buf[:n]))
}
}
服务器端代码的编写案例
//如果匹配成功进入业务逻辑处理
func myHandler(w http.ResponseWriter,r *http.Request){
//fmt.Fprintln往浏览器中写内容
fmt.Fprintln(w,"hello world")
}
func main() {
http.HandleFunc("/hello",myHandler)
http.ListenAndServe(":8000",nil)
}
1)HTTP服务器端代码
//如果匹配成功进入业务逻辑处理
func myHandler(w http.ResponseWriter,r *http.Request){
fmt.Println("r.Method",r.Method)
fmt.Println("r.URL",r.URL)
fmt.Println("r.Header",r.Header)
fmt.Println("r.Body",r.Body)
w.Write([]byte("hello world
"))
}
func main() {
http.HandleFunc("/hello",myHandler)
http.ListenAndServe(":8000",nil)
}
2)HTTP客户端代码
func main() {
//resp, err := http.Get("http://www.baidu.com")
resp,err := http.Get("http://127.0.0.1:8000/hello")
if err != nil {
fmt.Println("http.Get err=", err)
return
}
defer resp.Body.Close()
fmt.Println("status=", resp.Status)
fmt.Println("statuscode=", resp.StatusCode)
fmt.Println("header=", resp.Header)
fmt.Println("Body=", resp.Body)
buf := make([]byte, 1024*4)
var temp string
for {
n, err := resp.Body.Read(buf)
if err != nil || n == 0 {
fmt.Println("read err=", err)
break
}
temp += string(buf[:n])
}
fmt.Println("temp=", temp)
}
方法二:这个可以一次性获取所有页面数据,而不是分批次读取内容
func main() {
resp, err := http.Get("http://www.zhenai.com/zhenghun")
if err != nil {
//pani会中断之后的代码执行
panic(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK{
fmt.Println("err:",resp.StatusCode)
return
}
all,err := ioutil.ReadAll(resp.Body)
if err !=nil{
panic(err)
}
fmt.Printf("%s\n",all)
}