概述:Go
[TOC]
1.Go语言 Go语言也称 Golang,兼具效率、性能、安全、健壮等特性,部署简单,Go编译生成静态可执行文件,除glibc外无其他外部依赖,可直接编译成机器码,语言层面支持并发执行,内置runtime,支持垃圾回收,含丰富的标准库,可跨平台编译。
Go语言适合用于服务器编程,处理日志、数据打包、虚拟机处理、文件系统,网络编程,云平台开发等,另外可基于Go实现Web开发、内网穿透、高性能爬虫等等。
2.Go语法示范网站 (1)Example:https://gobyexample.com/
(2)Video-尚硅谷韩顺平Go语言核心编程:https://www.bilibili.com/video/av35928275
3.Hello World 1 2 3 4 5 6 7 8 package mainimport "fmt" func main () { fmt.Println("hello world" ) }
4.变量和静态变量、变量类型 4.1 使用var关键字声明变量,可不设置变量类型,标准格式var 变量名 变量类型 = 变量值 ,可同时声明多个变量,无须分号结尾,且变量必须使用/输出,在函数外声明变量为全局变量,另外Golang严格区分大小写,“_”为特殊标识符,成为空标识符,它对应值通常会被忽略,通常被用于占位符使用,特例:变量名可以用int、float32,开发不建议使用,程序可编译通过
Tip:全局变量不能在函数外赋值(不等同于初始化)
1 2 3 4 var a = 100 var b string b = "111" name := "jun"
4.2 静态变量可以不使用/输出,使用关键字const声明静态变量
4.3 变量类型含基本数据类型:
数值型:(int,int{x},unint,uint{x},byte,float32,float64)、字符型、布尔型、字符串型;
类型
有无符号
占用存储空间
数范围
int{x}
有
{x}*1字节|或理解为x位
-2^{x}~(2^{x}-1)
uint{x}
无
{x}*1字节
0~(2^{x+1}-1)
单精度float32
有
4字节
-3.403E38~3.403E38
双精度float64
有
8字节
-1.798E308~1.798E308
Go语言字符使用UTF-8编码,英文字母为1个字节,中文为3个字节
派送/复杂数据类型:指针、数组、结构体、管道、函数、切片、接口、集合
另外一种划分方式:值类型和引用类型
值类型:基本数据类型int系列、float系列、bool、string、数组、结构体。|| 变量直接存储值,内存通常在栈中分配;
引用类型:指针、slice切片、map集合、管道、interface。|| 变量存储地址,该地址对应空间为真正存储数据值,内存通常在堆分配,当没有任何变量引用该地址,则地址对应的数据空间为垃圾地址,需要进行垃圾回收
通常约定:变量名、函数名、常量名的首字母大写则可以被其他包访问,若首字母小写则在本包使用,即只有首字母大写的变量才可以被其他包使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 var a = "example" var b,c int = 123 ,222 o := 1 var d,e,f = 100 ,"eee" ,888 g,h,j := -11 ,true ,"aaa" var (n1 = 100 n2 = 200 ) const s = "ssssss" var sss = "hello" sss[0 ] = "a" aaa :=` func zeroptr(iptr *int) { *iptr = 0 }` fmt.Println(aaa) fmt.Printf("n1类型%T\n" ,n1) fmt.Printf("n1占用字节数:%d" ,unsafe.Sizeof(n1)) var c1 byte = "a" var c2 byte = "0" fmt.Println("c1=" ,c1)#直接输出byte 即输出ASCII值 a=>97 fmt.Println("c2=" ,c2)#0 =>48 fmt.Printf("c1=%c,c2=%c" ,c1,c2)#格式化输出 var c3 byte = "好" fmt.Println("c3=" ,c3)#ERROR:overflow byte var c4 int = "好" fmt.Println("c4=" ,c4)#输出ASCII值 var a int var b float32 var c float64 var d bool var name string fmt.Println("a=%d,b=%f,c=%f,d=%v,name=%v" ) (util.go )var HeroName string = "aaaaaa" (hello.go )import ("awesomeProject/model" ) (hello.go )fmt.Println(model.HeroName)
5.批量导入函数 1 2 3 4 5 6 7 8 import ( "fmt" "math" ) func main () { const n = 6.28 fmt.Println(math.Cos(n)) }
6.强制转换类型 任何类型间转换均需要强制转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 var a = 6.66 fmt.Println(int64 (a)) var num1 int = 99 var num2 float32 = 2.45 var b bool = true var str string str = fmt.Sprintf("%d" ,num1) fmt.Println(str) str = fmt.Sprintf("%f" ,num2) fmt.Println(str) fmt.Printf("%T\n" ,str) str = fmt.Sprintf("%t" ,b) fmt.Println(str) var num3 int = 99 var str string str = strconv.FormatInt(int64 (num3),10 ) fmt.Printf("str's type is %T, str's value is %s\n" ,str,str) var num4 float64 = 9.22 str = strconv.FormatFloat(num4,10 ,64 ) var num5 int = 4567 str = strconv.Itoa(num5) var str string = "true" var b bool b,_ = strconv.ParseBool(str) fmt.Printf("b's type is':%T,b's value is'%t" ,b,b) var str1 string = "11" var n1 int64 n1,_ = strconv.ParseInt(str1,10 ,64 ) fmt.Printf("n1's type is':%T,n1's value is'%d" ,n1,n1) var str2 string = "22.22" var n2 float64 n2,_ = strconv.ParseFloat(str2,64 ) fmt.Printf("n2's type is':%T,n2's value is'%f" ,n2,n2)
7.For循环 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 i := 1 for i <= 3 { fmt.Println(i) i = i+1 } for j:=0 ;j<2 ;j++{ fmt.Println(j) } var k = 1 for { if k >5 { fmt.Println("k>5" ) break } k++ fmt.Println(k) } var str string = "helloWorld" for i:=0 ;i<len (str);i++{ fmt.Printf("%c\t" ,str[i]) } var str2 string = "helloWorld哈喽" str3 := []rune (str2) for i:=0 ;i<len (str3);i++{ fmt.Printf("%c\t" ,str3[i]) } for _,value := range str{ fmt.Printf("%c\t" ,value) }
Tip1:可使用break永久跳出循环
Tip2:可使用continue跳跃该次循环
8.判断IF/ELSE 1 2 3 4 5 6 var a,b = 100 ,10 if a > b{ fmt.Println("a > b" ) } else { fmt.Println("a <= b" ) }
9.Switch Golang中可在一个case后可有多个表达式,使用”,”间隔
Tip1:表达式可以是常量值、变量、一个有返回值的函数之一均可
Tip2:case后的表达式和switch后接表达式的数据类型必须一致
Tip3:case后的表达式不能重复
Tip4:default不是必填项
Tip5:switch后可定义/声明变量,但需要后接“;”(不推荐)
Tip6:switch穿透-fallthrough,如果在case语句后增加fallthrough则会继续执行下一个case,称为switch穿透(默认穿透1层)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 a := 10 switch a {case 10 ,40 : fmt.Println("i = 10 or 40" ) case 20 : fmt.Println("i = 20" ) case 30 : fmt.Println("i = 30" ) default : fmt.Println("i not in (10,20,30,40)" ) } func test (key int ) int { return key } switch test(10 ) { case 10 ,40 : fmt.Println("i = 10 or 40" ) default : fmt.Println("i not in (10,40)" ) } var n1 int32 = 3 var n2 int32 = 5 switch n1 { case n2: fmt.Println("匹配n2" ) default : fmt.Println("不匹配n2" ) } switch n1 := 100 ; { case n1>90 : fmt.Println(">90" ) default : fmt.Println("<=90" ) } var n1 int = 10 switch n1{ case 10 : fmt.Println("10" ) fallthrough case 20 : fmt.Println("20" ) default : fmt.Println(">20 or <10" ) }
10.输出今日星期/小时 1 2 3 4 5 6 7 8 9 a := time.Now().Weekday()//星期 fmt.Println(a) if a == time.Saturday{ fmt.Print("今天是周六" ) }else { fmt.Print("今天不是周六" ) } b := time.Now()//小时 fmt.Println(b.Hour())
11.函数 Go语言中函数分为自定义函数,系统函数
在Go使用interface()可用于向函数传递任意类型的变量,在函数内部,该变量仍为interface{}类型,注意函数的输入参数通常需要判断类型后再强制转换,不然易报错
函数格式:func 函数名(形参名 形参类型) 函数返回类型{…}
规范:通常不会将项目所需要所有函数写入mian文件,一般写入utils.go,该go文件专门用于定义函数,由main或者其他文件调用,其中会被其他文件引用的函数名必须首字母 大写,或理解为该函数可导出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 // 使用interface不定义形参类型 myfunc := func(i interface{}) { switch i.(type ) { case string: fmt.Println("string" ,i.(string)) case int: fmt.Println("int" ,i.(int)) default: fmt.Printf("others" ) } } myfunc(100) myfunc("abc" ) // 明确函数的形参类型 myfunc2 := func(a int,b int){ fmt.Println(a+b) } myfunc2(1,2) // 定义多个形参类型 myfunc3 := func(a, b, c int) int { return a + b + c } // 在外部定义函数(必须在main函数外定义) func plus1(a, b, c int) int { return a + b + c } // 在外部定义函数,多个返回值(必须在main函数外定义) func plus2(a, b, c int) (int,string) { return a + b + c,"sum" }
关于上述中调用utils文件函数截图举例:
Tip1:注意通常导入时文件包名和文件夹名一致,即utils.go所在的文件夹名为utils,则文件中第一行规范书写为packge utils,一般为小写,package后的包名不是必须与包名一致,不过建议一致,不然影响开发效率
Tip2:导入时语法为包名.函数名
Tip3:给包名其别名,将utils更改为util,其原名则失效,使用方法则为别名.函数名
1 import util "awesomeProject/utils"
Tip3:将go文件编译成可执行文件,需要将包声明为main,即package main,在$GOPATH执行
举例:个人GOPATH在C:\Users\Junming\go下,则在该目录下启动CMD,而目标文件在C:\Users\Junming\go\src\awesomeProject\main\helloworld.go,在执行命令时直接以src下定位,命令如下
1 C:\Users\Junming\go>go build awesomeProject\main
编译后生成一个有默认名的可执行文件,在$GOPATH目录下,也可以指定名字和目录,以上命令执行后会在GOPATH下生成一个main.exe,同时可自定义设置存储名字与路径:
1 C:\Users\Junming\go>go build -o bin/my.exe awesomeProject\main
Tip:以上执行后其实会在pkg文件夹中...\下生成utils.a,该文件为库文件(二进制文件)
函数调用机制底层解析:
栈区:基本数据类型
堆区:引用数据类型
(1)调用函数时会分配一个新的空间,编译器会将该函数对应的栈与其他栈进行区分
(2)当一个函数被调用完后,程序会自动销毁掉该函数对应的栈空间
函数形参调用
(1)值传递(n1 int)
(2)引用传递(n1 *int)(地址拷贝,效率更高,其值传递数据越大,消耗越大)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func test1(n1 int){ n1 = n1 + 10 fmt.Println(n1) } func test2(n1 *int){ *n1 = *n1 + 10 } func main (){ num := 20 test1(num) fmt.Println(num) test2(&num) fmt.Println(num) }
(3)函数作为形参
以下abc函数作为myFun函数的形参传入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func getSum(n1 int,n2 int)int{ return n1+n2 } // 当多个函数形参类型一致时可以修改(函数功能同上) //func getSum(n1,n2 int)int{ // return n1+n2 //} func myFun(abc func(int,int) int,num1 int, num2 int)int{ return abc(num1,num2) } func main (){ res := myFun(getSum,50,60) fmt.Println(res) }
可变函数:形参数量不预先预定,需要注意的是可变形参可以和固定形参一起使用,不过可变参数必须为形参列表的最后一项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 func sum(nums ...int) int{ res := 0 for _,v :=range nums{ res+=v } return res } func sum2(nums ...int)int{ sum := 0 for i:=0;i<len(nums);i++{ sum+=nums[i] } return sum } func main (){ res := sum(1,2,3,4) fmt.Println(res) res2 := sum2(1,2,3,4) fmt.Println(res2) }
12.数组Array 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 // 数组的地址可以通过数组名获取,数组的地址也是第一个元素的地址,第二个元素的地址是第一个元素的地址+数组类型所占字节数(int占8字节) var a [3] int fmt.Printf("a的地址=%p,a[0]的地址=%p" ,&a,&a[0]) //%p取变量地址 var a [5]int fmt.Println(a) //声明int类型数组初始化为全0,Output:[0 0 0 0 0] // 赋值方法一 b := [5]int{1,2,3,4,5} // 赋值方法二(不定义具体数量,固定三个点...) b := [...]int{1,2,3,4,5} // 赋值方法三(下标1值为800,下标0值为900) b := [...]int{1:800,0:900} // 赋值方法四 a[0] = 10 a[1] = 20 // 定义/声明二维数组 var tD [2][3]int for i:=0;i<2;i++{ for j:=0;j<3;j++{ tD[i][j] = i+j } fmt.Println(tD) } //二维数组直接初始化 var tD [2][3]int = [2][3]int{{1,2,3},{2,3,4}} // 或者 var tD [2][3]int = [...][3]int{{1,2,3},{2,3,4}} // 或者 var tD = [2][3]int{{1,2,3},{2,3,4}} // 或者 var tD = [...][3]int{{1,2,3},{2,3,4}} fmt.Println(tD) // 方式1 二维数组的遍历 for i:=0;i<len(tD);i++{ for j:=0;j<len(tD[i]);j++{ fmt.Print(tD[i][j],"\t" ) } fmt.Println() } // 方式2 二维数组的遍历 for _,v1:=range tD{ for _,v2:=range v1{ fmt.Print(v2,"\t" ) } fmt.Println() } a := [4]int{1,2,3,4} // 数组遍历一(注意格式化输出使用Printf) for _,value := range a{ fmt.Printf("%d\t" ,value) } // 数组遍历二 for i:=0;i<len(a);i++{ fmt.Printf("%d\t" ,a[i]) } // 引用传递,修改数组的值 func change(a *[4]int){//注意数组长度必须一致,也是数据类型的一部分 (*a)[0] = 88 } func main (){ a := [4]int{1,2,3,4} change(&a) fmt.Printf("%v" ,a) }
13.分片Slices 关键字make() 只用于映射、切片和程道,不返回指针,即可以不初始化具体元素值
1 2 3 4 5 6 a := make([]string, 3) b := make([]string,len(a)) copy(b,a)//复制数组 // 数组分片 c := [3]int{1,2,3} fmt.Println(c[1:])
14.集合Map 关键字map实现键值对
1 2 3 4 5 6 7 8 9 m := make(map[string]int) m["k1" ] = 7 m["k2" ] = 11 fmt.Println(m) delete(m,"k1" )//删除某键值对 _, prs := m["k2" ]//第二个返回值返回该键值是否存在,而第一个返回值不是关注内容,可用_代替 fmt.Println("prs:" , prs) n := map[string]int{"foo" : 1, "bar" : 2} //定义map格式:map[key-type]value-type{key-real:value-real}
15.范围Range 1 2 3 4 5 6 7 8 9 10 //int数组 nums := []int{2,3,4} for index,num := range nums{ fmt.Println(index,num) } //map集合(键值对) kvs := map[string]string{"a" : "apple" , "b" : "banana" } for k, v := range kvs { fmt.Printf("%s -> %s\n" , k, v) }
16.生成随机数 1 2 3 4 //表示生成0-99之间的数,但是每次生成为伪随机数,即多次重复运行均为同一值,因此需要提前设置一个种子,即Seed方法,返回1970.01.01 /00:00:00到此时的秒数/纳秒 rand.Seed(time.Now().Unix())//秒数 rand.Seed(time.Now().UnixNano())//秒数 rand.Intn(100)
17.匿名函数 包含匿名函数的函数返回类型为func(),格式:func 函数名() func() 匿名函数返回类型{…}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 // 方式1:在main中定义匿名函数 func main (){ res1 := func(n1 int,n2 int)int{ return n1+n2 }(10,20) fmt.Println(res1) } // 方式2:把匿名函数赋给一个变量(函数变量),再通过变量调用匿名函数 func main (){ a:= func (n1 int,n2 int)int{ return n1 - n2 } res1 := a(10,30) fmt.Println("res1=" ,res1) } // 方式3:全局匿名函数:将匿名函数 var ( fun1 = func(n1 int,n2 int)int{ return n1 + n2 } ) func main (){ res := fun1(1,2) fmt.Println("res=" ,res) }
18.指针 指针类型:指针变量存的是一个地址,该地址指向的空间才是值,指针本身也有地址
int类型:int变量存的是一个int值(与指针类型作对比)
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var num int = 10 fmt.Println("num的地址为:" ,&num) var ptr *int = &num fmt.Println("ptr为:" ,ptr) fmt.Println("ptr指向的值:" ,*ptr) //解释var ptr *int = &num //解释1:ptr是一个指针类型的变量 //解释2:ptr类型是*int //解释3:ptr本身值维&num //num = 10 //ptr = num的存储地址 //num的地址为: 0xc04203c1d0 //ptr为: 0xc04203c1d0 //ptr指向的值: 10
*变量名表示该变量的指针,&变量名读取变量的内存地址(即地址),例如示范代码中int类型变量i值为1,&i表示变量i的内存地址,在zeroptr函数中形参为指针类型,
1 2 3 4 5 6 7 func zeroptr(iptr *int) { *iptr = 0 } func main (){ i := 1 zeroptr(&i) }
练习1:获取一个int变量num的地址并显示
练习2:将num地址赋给ptr并通过ptr修改num值
1 2 3 4 5 6 var num int = 20 var ptr1 *int = &num fmt.Println(ptr1) *ptr1 = 30 fmt.Println(num)
19.运算符% a % b = a - a / b * b
1 2 3 4 10 % 3 = 1 -10 % 3 = -10 - (-10) / 3 * 3 = -10 +9 = -1 10 % -3 = 1 -10 % -3 = -1
20.逻辑与/或 不仅可以针对变量值判断之外,还可以对函数返回值作为逻辑与/或的判断
1 2 3 4 5 6 7 8 func test () bool{ return true } func main (){ if test () { fmt.Println("true" ) } }
21.结构体及方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type person struct { name string age int sex string } func newPerson(name string,age int) person{ p:= person{name:name,age:age} p.sex = "male" return p } func main () { fmt.Println(newPerson("John" ,18)) }
结构体方法格式:func (结构体名 结构体类型) 方法名() 返回类型{…}
Tips:结构体方法中对结构体类型前是否添加*对结果不影响
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 type rect struct { width, height int } func (r rect) area() int { return r.width * r.height } // 使用*结构体类型效果一致 //func (r *rect) area() int { // return r.width * r.height //} func (r rect) perim() int { return 2*r.width + 2*r.height } func main () { r := rect{width: 10, height: 5} fmt.Println("area: " , r.area()) fmt.Println("perim:" , r.perim()) }
22.错误Error 在Go中,通过显式的、单独的返回值来传递错误是常见的现象,首先需要导入”error”,在函数返回类型添加error,
补充:nil在Go中表示空值
1 2 3 4 5 6 7 8 9 10 11 12 func f1(arg int) (int, error) { if arg > 100 { return -1, errors.New("more than 100" ) } return arg, nil } func main () { fmt.Println(f1(120)) fmt.Println(f1(90)) } //Output: -1 more than 100 //Output: 90 <nil>
23.获取用户终端输入 调用fmt包中的fmt.Scanf和fmt.Scanln,其中fmt.Scanln表示读取一行数据
1 2 3 4 5 6 7 8 9 10 11 12 13 // 方式一:使用Scanln var name string var age int fmt.Println("input your name:" ) fmt.Scanln(&name)//传入变量的地址(引用传递),则函数内的值会影响函数外的变量值,使用name无法执行该语句 fmt.Println("input your age:" ) fmt.Scanln(&age)//传入变量的地址(引用传递),则函数内的值会影响函数外的变量值 fmt.Println(name,age) // 方式二:使用Scanf fmt.Println("Input your name,age by spacing off" ) fmt.Scanf("%s %d" ,&name,&age) fmt.Println(name,age)
24.进制输出 八进制:以数字0开头表示;
十六进制:以数字0x或者0X开头表示
1 2 3 4 5 6 7 8 9 // 二进制(使用格式化输出用Printf) var i int = 5 fmt.Printf("%b\n" ,i) // 八进制 var j int = 011 fmt.Println("j=" ,j) // 十六进制 var m int = 0x11 fmt.Println("m=" ,m)
25.原码、反码、补码 1)二进制最高位是符号位:0表示正数,1表示负数
例如:1=>[0000 0001] -1=>[1000 0001]
2)正数的原码、反码、补码相同
3)负数的反码 = 原码符号位不变,其他位数取反
例如:1=>原码、反码、补码 [0000 0001]
-1=>原码[1000 0001],反码[1111 1110],补码[1111 1111](下一性质)
4)负数的补码 = 反码 + 1
5)0的反码、补码 = 0
6)计算机运算时以补码方式运算
26.移位运算 右移运算符>>:低位溢出,符号位不变,使用符号位补溢的高位
左移运算符<<:符号位不变,低位补0
例如:1>>2 // 0000 0001 => 0000 0000 = 0
例如:1<<2 // 0000 0001 => 0000 0100 = 4(左移2位)
27.自定义数据类型 基本语法:type 自定义数据类型 数据类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 type myInt int //myInt等价于int// 自定义函数类型 // 1.定义函数 func getSum(n1 int,n2 int)int{ return n1+n2 } // 2.自定义函数类型 type myFunType func(int,int)int// 3.将定义函数作为另一个函数的形参 func myFun(abc myFunType,num1 int, num2 int)int{ return abc(num1,num2) } // 4.主函数:调用函数 func main (){ res := myFun(getSum,50,60) fmt.Println(res) }
28.init函数 每一个源文件都有一个init函数,当声明init函数和main函数时,init优先执行,通常在init完成初始化工作,如果文件中包含全局变量定义、init函数、main函数时,执行流程:变量定义->init函数->main函数
29.闭包 闭包:是一个函数与其相关的引用环境组合的一个整体(实体)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import "fmt" func myfunc() func(int) int { var n int = 10 return func(i int) int { n = n + i return n } } func main (){ nextInt := myfunc() fmt.Println(nextInt(1))// 10 + 1 fmt.Println(nextInt(2))// 11 + 2(主要原因在于myfunc中的变量n并不是每一次均被初始化,而是原基础变化) fmt.Println(nextInt(3))// 13 + 3 } //Output: //11 //13 //16 其中myfunc返回的是fun (int) int,返回的是一个匿名函数,匿名函数引用到函数外的变量n,共同构成了整体
练习:
(1)编写一个函数makeSuffix(suffix string)可接收一个文件后缀名(比如.jpg),并返回一个闭包
(2)调用闭包,可传入一个文件名,如果该文件名没有指定的后缀(比如.jpg),则返回文件名.jpg,如果有.jpg后缀,则返回原文件名
(3)要求使用闭包方式完成
(4)strings.HasSuffix可以判断某个字符串是否有指定的后缀
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func makeSuffix(suffix string) func(string) string{ return func (name string) string{ // 如果name没有指定后缀,则加上,否则返回原来的名字 if strings.HasSuffix(name,suffix){ return name } return name + suffix } } func main (){ f := makeSuffix(".jpg" ) fmt.Println("文件名处理后=" ,f("winter" )) fmt.Println("文件名处理后=" ,f("bird.jpg" )) } //返回函数与变量suffix组合成一个闭包,返回函数引用变量suffix //闭包优势在于仅需要输入一次suffix(后缀)即可
30.函数关键字-defer 通常需要创建资源(例如:数据库连接、文件句柄、锁等),为了函数执行后及时释放资源,Go中提供了defer(延时机制)
(1)当执行defer时,暂时不执行,且会将defer后语句压入独立的栈(defer栈),当函数执行后才按照先入后出的方式执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func sum(n1 int,n2 int)int{ defer fmt.Println("n1=" ,n1) defer fmt.Println("n2=" ,n2) n1++ n2++ res := n1 + n2 fmt.Println("res=" ,res) return res } func main () { sum(10,20) } // 返回结果: res=32、n2=20、n1=10 // 原因在于在n1++、n2++前已经压入defer栈
(2)及时释放函数创建的资源(模拟代码)
1 2 3 4 5 6 7 8 9 10 11 func test (){ file = openfile("文件名" ) defer file.close() //其他代码 } func test (){ connect = openDatabase() defer connect.close() //其他代码 }
31.系统常用函数——字符串
序号
函数
说明
1
len(str)
统计字符串长度
2
r :=[]rune(str)
转切片,字符串遍历,同时解决中文问题,需要重新定义变量
3
n,err := strconv.Atoi[“12”]
字符串转整数
4
str := strconv.Itoa(1234)
整数转字符串
5
var bytes = []byte(“hello go”)
字符串转[]byte
6
str := string([]byte{1,2,3})
[]byte转字符串
7
str := strconv.FormatInt(123,2)
10进制转2,8,16进制,返回对应的字符串
8
strings.Contains(“sea”,”food”)
查找子串是否在指定的字符串
9
strings.Count(“cheese”,”e”)
统计一个字符串有几个指定的不重复的子串
10
fmt.Println(strings.EqualFold(“abc”,”ABC”))
不区分大小写的字符串比较(==区分大小写)
11
strings.Index(“NLT_ABC”,”abc”)
返回子串在字符串第一次出现的index值
12
strings.LastIndex(“go golang”,”go”)
返回子串在字符串最后一次index
13
strings.Replace(“go go hello”,”go”,”go语言”,n)
n可以指定你希望替换几个,如果n=-1全部替换,注意不会替换原字符串,而是返回一个新的字符串
14
strings.Split(“hello,world,ok”,”,”)
将一个字符串拆分成字符串数组,返回新数组
15
strings.ToLower(“Go”)
将字符串的字母进行大小写转换
16
strings.TrimSpace(“ abc aa “)
将字符串左右两边的空格去掉
17
strings.TrimLeft(“! hello!”,”!”)
将字符串左边指定的字符去掉//左边!和“ ”去掉
18
strings.TrimRight(“! hello!”,”!”)
将字符串右边指定的字符去掉//右边!和“ ”去掉
19
strings.HasPrefix(“ftp://“,”ftp”)
判断字符串是否以指定字符串开头
20
strings.HasSuffix(“ABac”,”AB”)
判断字符串是否以指定字符串结尾
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 // 3. 字符串转整数 n,err := strconv.Atoi("123" ) if err!= nil{ fmt.Println("转换错误" ,err) }else { fmt.Println(n) } //如果非整数则返回:转换错误 strconv.Atoi: parsing "z123" : invalid syntax // 4. 整数转字符串 str := strconv.Itoa(12345) fmt.Printf("str=%v,type=%T" ,str,str) // 补充格式化输出 %v 表示按照相应的默认格式 %T 相应值的类型 %t 布尔值 true 或 false %b 二进制表示 %c 相应 Unicode 码点所表示的字符 %d 十进制表示 %o 八进制表示 %x 十六进制表示,字母形式为小写 a-f %X 十六进制表示,字母形式为大写 A-F %e 科学计数法,例如 -1234.456e+78 %E 科学计数法,例如 -1234.456E+78 %f 有小数点而无指数,例如 123.456 %s 字符串或切片的无解译字节 // 5.字符串转[]byte(byte的%v输出是对应的ASCII码) var bytes = []byte("hello go" ) // 13.n可以指定你希望替换几个,如果n=-1全部替换 strings.Replace("go go hello" ,"go" ,"go语言" ,n)
32.时间和日期相关函数 需要提前导入time包
时间的常量:
时间常量
定义
Nanosecond
纳秒
Microsecond
微秒
Millisecond
毫秒
Second
秒
Minute
分钟
Hour
小时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 //获取当前时间,分别获得年月日时分秒:now.Year()/Month()/Day()/Hour()/Minute()/Second() now := time.Now() fmt.Println(now)//2019-11-12 18:38:40.4767544 +0800 CST fmt.Println(now.Year())//2019 fmt.Println(now.Month())//Novemeber fmt.Println(int(now.Month()))//11 // 每隔0.1秒打印一个数字 i := 0 for { i++ fmt.Println(i) time.Sleep(time.Millisecond*100) if i == 100{//10秒后退出 break } } //Unix和UnixNano的使用 fmt.Printf("unix时间戳=%v,unixnano时间戳=%v" ,now.Unix(),now.UnixNano()) // 统计函数执行时间 func test (){ str := "" for i:=0;i<100000;i++ { str += "hello" + strconv.Itoa(i) //备注:strconv.Itoa()表示将整数转为字符串 } } func main (){ start := time.Now().Unix() test () end := time.Now().Unix() fmt.Printf("执行test()耗费时间%v" ,(end-start)) }
33.内置函数
函数名
功能
new
分配内存,主要分配值类型,int,float32,struct,返回指针
make
分配内存,主要分配引用类型,char,map,slice
1 2 3 4 5 6 7 func main (){ num1 := 100 fmt.Printf("num1类型:%T,num1值:%v,num1的地址:%v\n" ,num1,num1,&num1) num2 := new(int) fmt.Printf("num2类型:%T,num2值:%v,num2的地址:%v,num2指向的值:%v" ,num2,num2,&num2,*num2) } //因此new有2个子过程:其一分配指针内存空间,其二再指向一个默认值
34.错误/异常的捕获和处理 Go中错误(panic)程序会退出(奔溃) Go引用的处理方式:defer,panic,recover Go可以抛出一个panic的异常,在defer中通过recover捕获这个异常,再正常处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func test (){ // 使用defer + recover捕获和处理异常 defer func (){//匿名函数 err := recover() // recover()内置函数,可以捕获异常 if err != nil{ fmt.Println("err=" ,err) // 通常该函数的负责员(保证了主函数的执行) } }() num1 := 10 num2 := 0 res := num1 / num2 fmt.Println(res) } func main (){ test () fmt.Println("执行test()" )//使用了defer+recover后仍可以执行该句 }
Go也支持自定义错误,使用errors.New和panic内置函数 1)errors.New(“错误说明”),会返回一个error类型的值,表示一个错误 2)panic内置函数,接收一个interface{}类型的值作为参数,可以接收error类型的变量,输出错误信息,并退出程序
1 2 3 4 5 6 7 8 func test2 (){ err := readConf("config.ini" ) if err !=nil{ //发生错误并输出错误,终止程序! panic(err) } fmt.Println("执行完test2()" ) }
35.四舍五入 1 2 3 4 h1 := 27.0 h2 := 5.0 h3 := fmt.Sprintf("%.2f" ,h1/h2)//保留2位小数 fmt.Printf("h3 = %v" ,h3)
36.切片Slice (1)切片是数组的引用,切片是引用类型,在进行传递时,遵守引用传递的机制
(2)切片的使用和数组类似,遍历切片和访问切片的元素、求切片长度都是一样
(3)切片长度可以变换,即动态变换数组
(4)定义基本语法:var 变量名 [] 类型
(5)切片结构:
指向数组的地址
长度
容量
address(eg.0x0011)
len
cap
声明/定义切片三个方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 // 方式一:定义一个切片,让切片引用已经创建好的数组 var myarr [5] int = [...]int{1,2,3,4,5} myslice := myarr[1:3] // 引用myarr数组的第2个元素到第3个元素(左闭右开),即myarr[1]和myarr[2] fmt.Println(myslice) fmt.Println("切片容量:" ,cap (myslice))//切片容量是动态变化,cap 全称为capability,cap 容量取决于具体类型,其中切片容量一般是底层数组长度的两倍 // 切片由3部分组成:(1)指向数组的指针(2)切片长度(3)切片容量 // 方式二:通过make创建切片 // 基本语法:var 切片名 [] type = make([]type ,len,[cap ]) // 其中type 数据类型 len大小 cap 指定切片容量 func main (){ var myslice [] int = make([]int,4)// cap >len fmt.Println(myslice)// 默认值为[0 0 0 0] fmt.Println("slice len=" ,len(myslice),"slice cap=" ,cap (myslice))// slice len= 4 slice cap = 4 myslice[0] = 100 myslice[2] = 200 fmt.Println(myslice)// [100 0 200 0] } // 方式三:定义一个切片,直接指定具体数组,使用原理类似make func main (){ var myslice [] int = []int {1,3,5} fmt.Println(myslice) }
区分:
方式1:是直接引用数组,该数组是预先存在的,程序员可见,数组和切片均可访问 方式2:通过make创建切片,make会创建一个数组,由切片在底层进行维护,程序员不可见,切片存放的指针指向内部创建的数组空间的第一个元素,只能通过切片访问元素
切片的遍历
1 2 3 4 5 6 7 8 9 10 11 var myarr [5]int = [...]int{1,2,3,4,5} var myslice = myarr[1:3] // 方式1 for 循环 for i:=0;i<len(myslice);i++{ fmt.Println(myslice[i]) } //方式2 for-range for _,value:=range myslice{ fmt.Println(value) }
切片简写:
1 2 3 var myslice = myarr[0:end] => var myslice = myarr[:end] var myslice = myarr[start:len(arr)] => var myslice = myarr[start:?] var myslice = myarr[0:len(arr)] => var myslice = myarr[:]
(1)cap是一个内置函数,用于统计切片容量,即最大可存放多少个元素 (2)切片定义完后不能使用,因为本身为空的,需要让其引用一个数组,或者make一个空间供切片使用 (3)切片可继续切片 (4)用append内置函数可以对切片动态追加,若切片有足够的容量,其目标会重新切片以容纳新的元素,否则会分配一个新的基本数组,append返回更新后的切片,因此必须存储追加后的结果,注意需要重新赋给切片
在切片上追加元素/切片:
1 2 3 4 5 var myslice [] int = []int{1,2,3}//定义切片 fmt.Println(myslice) myslice = append(myslice,4,5,6) // 追加元素 myslice = append(myslice,myslice...) // 追加切片(注意要加...,另外仅能添加切片,不能添加数组) fmt.Println(myslice)
切片append操作的底层原理分析: (1)切片append操作的本质是对数组扩容 (2)go底层会创建新的数组newArr (3)将slice原来包含的元素拷贝到新的数组newArr (4)slice重新引用到newArr (5)newArr是在底层维护,程序员不可见
切片的拷贝操作:
1 2 3 4 5 6 var myslice1 []int = []int{1,2,3,4,5} var myslice2 = make([]int,10)//定义一个长度为10的切片 copy(myslice2,myslice1)//把myslice1拷贝给myslice2 fmt.Println(myslice1)//[1 2 3 4 5] fmt.Println(myslice2)//[1 2 3 4 5 0 0 0 0 0] // 注意myslice1和myslice2的空间是独立的,即修改myslice1的元素不会影响myslice2
在拷贝(copy)过程中,如果slice的长度不够时,会依据slice的真实长度拷贝对应长度,不会因为长度不够而报错
例如:
1 2 3 4 5 var a[]int = []int{1,2,3,4,5} var myslice = make([]int,1) fmt.Println(a)//[1 2 3 4 5] copy(myslice,a) fmt.Println(myslice)//[1]
切片是引用类型,因此在传递过程中,遵守引用机制
1 2 3 4 5 6 7 8 9 func test (myslice []int){ myslice[0] = 100 } func main (){ var myslice[]int = []int{1,2,3,4,5} fmt.Println(myslice)//[1 2 3 4 5] test (myslice) fmt.Println(myslice)//[100 2 3 4 5] }
string底层是byte数组,因此string可进行切片处理
1 2 3 mystr := "helloworld" myslice := mystr[5:] fmt.Println(myslice)//world
string是不可变数据结构,不能赋值具体元素
1 mystr[0] = 'a' // × 编译失败 error 无法赋值
需要修改string可以通过先将string -> [] byte 或者 []rune -> 修改 -> 重转成string
1 2 3 4 5 mystr := "helloworld" arr := []byte(mystr) arr[0] = 'a' mystr = string(arr) fmt.Println(mystr)//aelloworld
但是以上方法仅能处理英语和数字,不能处理中文,1个byte按照1个字节处理,但一个汉字占3个字节,解决办法将string转成[]rune,按照字符处理,兼容汉字
1 2 3 4 5 mystr := "helloworld" arr := []rune(mystr) arr[0] = '汉' mystr = string(arr) fmt.Println(mystr)
斐波拉契数列(每个元素为前两个元素之和,第一个和第二个元素均为1)
1 2 3 4 5 6 7 8 9 10 11 12 func fbn(n int)([]int){ myslice := make([]int,n) myslice[0],myslice[1] = 1,1 for i:=2;i<n;i++{ myslice[i] = myslice[i-1] + myslice[i-2] } return myslice } func main (){ myfbn := fbn(10) fmt.Println(myfbn) }
37.排序 排序分类:
(1)内部排序:将需要处理的所有数据加载在内存存储器进行排序(交换式排序、选择式排序、插入式排序)
交换式排序:冒泡排序、快速排序
(2)外部排序:数据量过大时无法全部加载到内存,需要借助外部存储进行排序(合并排序、直接合并排序)
冒泡排序实战:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func BubbleSort(arr *[5]int){ for i:=0;i<len(arr)-1;i++{ for j:=i+1;j<len(arr);j++{ if (*arr)[i]>(*arr)[j]{ arr[i],arr[j] = arr[j],arr[i] } } } } func main (){ arr := [5]int{2,3,1,5,4}// 定义数组 BubbleSort(&arr) fmt.Println(arr)//[1 2 3 4 5] }
二分查找实战(必须是有序数组):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func binaryfind(arr *[7]int,leftindex int, rightindex int,target int){ // 判断leftindex是否大于rightindx if leftindex > rightindex{ fmt.Println("找不到该元素" ) return } mid := (leftindex+rightindex)/2 if (*arr)[mid] > target{ binaryfind(arr,leftindex,mid-1,target) }else if (*arr)[mid] < target{ binaryfind(arr,mid+1,rightindex,target) }else if (*arr)[mid] == target{ fmt.Printf("找到该元素,下标为%v" ,mid) } } func main (){ arr := [7]int{1,2,3,4,5,6,7} binaryfind(&arr,0,6,14) }
38.map字段 map是key-value数据结构,又称为字段或者关联数组,类似于其他编程语言的集合/字典,
基本语法:var map 变量名 map[keytype] valuetype
key类型:int 、bool、string、指针、channel、接口、结构体、数组
key不支持的类型:切片、map、函数(无法使用==判断)
声明map不会分配内存,初始化需要value,分配内存后才可以赋值和使用
Tip1:key值不可以重复
Tip2:value值可重复
Tip3:make-value是无序的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 //方式一:声明 + 分配 var a map[string]string// 声明 a = make(map[string]string,10)// 分配数据空间(其中第2个参数表示该map可以存放10个key-value) //方式二:声明分配 a := make(map[string]string) a["num1" ] = "张三" //初始化 a["num2" ] = "李四" fmt.Println(a)// map[num1:张三 num2:李四] //方式三:声明分配初始化 var cities map[string]string = map[string]string{ "num1" :"北京" , "num2" :"深圳" ,//注意最后一项也要添加"," } fmt.Println(cities)
练习题:建立一个学生信息Map,存放3个学生信息,每个学生信息有name和sex信息,即嵌套map
1 2 3 4 5 6 7 8 9 10 11 12 studentMap := make(map[string]map[string]string)//key类型为string,value类型为map studentMap["stu01" ] = make(map[string]string,2) studentMap["stu01" ]["name" ] = "Jack" studentMap["stu01" ]["sex" ] = "Male" studentMap["stu02" ] = make(map[string]string,2) studentMap["stu02" ]["name" ] = "Mary" studentMap["stu02" ]["sex" ] = "Female" studentMap["stu03" ] = make(map[string]string,2) studentMap["stu03" ]["name" ] = "Lawrence" studentMap["stu03" ]["sex" ] = "Male"
map删除操作:
1 delete(map,"key" )//key不存在不会报错
删除所有的key:
1 cities = make(map[string]string)//重新分配一个新空间即全部删除key
map查找(判断map中是否存在指定的key):
1 2 3 4 5 6 7 8 //方式一 val,findRes = cities["num1" ]// 如果cities存在"num1" ,则findRes返回true ,否则返回false //方式二 if cities["num1" ] != nil{ //存在该用户 }else { //不存在该用户 }
map遍历(for-range):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var cities map[string]string = map[string]string{ "num1" :"北京" , "num2" :"深圳" , } for k,v := range cities{ fmt.Println(k,v) } // 嵌套map遍历(复杂的双重for-range) studentMap := make(map[string]map[string]string)//key类型为string,value类型为map studentMap["stu01" ] = make(map[string]string,2) studentMap["stu01" ]["name" ] = "Jack" studentMap["stu01" ]["sex" ] = "Male" for k1,v1 := range studentMap{ fmt.Println(k1) for k2,v2 := range v1{ fmt.Println(k2,":" ,v2,"\t" ) } }
获取map长度:
1 fmt.Println(len(studentMap))
map切片:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 var monsters []map[string]string // 声明一个切片 monsters = make([]map[string]string,2)//初始化切片 if monsters[0] == nil{ monsters[0] = make(map[string]string,2) monsters[0]["name" ] = "PersonA" monsters[0]["age" ] = "18" } // 定义一个monster信息 newMonster := map[string]string{ "name" : "新英雄" , "age" : "22" , } monsters = append(monsters,newMonster) fmt.Println(monsters)
map排序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 map1 := make(map[int]int,3) map1[10] = 100 map1[1] = 11 map1[2] = 999 fmt.Println(map1) var keys []int for k,_ := range map1{ keys = append(keys,k) } // 排序 sort.Ints(keys) fmt.Println(keys) // 根据keys遍历切片 for _,k := range keys{ fmt.Print(k,":" ,map1[k],"\t" ) }
map也是引用类型,遵守引用类型的传递机制,在函数接受map后会直接修改原map
复习:切片也是引用类型,切片和map的使用均需要先make分配空间,其中切片的make需要指定长度,map不需要
1 2 3 4 5 6 7 8 9 10 11 func modify(mymap map[int]int){ mymap[10] = 100 } func main (){ mymap := make(map[int]int) mymap[10] = 1 mymap[20] = 2 fmt.Println(mymap) modify(mymap) fmt.Println(mymap) }
map容量达到后会自动扩容,而不会发生panic,即动态增长键值对
map的value也经常用struct类型,更适合于管理复杂的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type Stu struct{ Name string Age int Address string } func main (){ students := make(map[string]Stu,10) Stu1 := Stu{Name:"Tom" ,Age:18,Address:"Beijing" } Stu2 := Stu{Name:"Mary" ,Age:18,Address:"Hongkong" } Stu3 := Stu{Name:"Jack" ,Age:18,Address:"Shenzhen" } students["No1" ] =Stu1 students["No2" ] =Stu2 students["No3" ] =Stu3 fmt.Println(students) for k,v := range students{ fmt.Println("学生编号:" ,k) fmt.Println("学生姓名:" ,v.Name) fmt.Println("学生年龄:" ,v.Age) fmt.Println("学生地址:" ,v.Address) } }
39.结构体 面向对象编程(OOP, Object Oriented Programming)特性——结构体
(1)Golang和传统的面向对象编程有区别,并不是纯粹的面向对象语言,即通常说Golang支持面向对象编程特性
(2)在Golang中没有类,而结构体与其他编程语言的类有同等地位,基于struct实现OOP,无OOP的继承,方法重载,构造函数,析构函数,隐藏的this指针等
(3)Golang仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式与其他OOP语言不一样,例如继承:Golang没有extend关键字,而是通过匿名字段 实现
结构体:声明后,字段/属性拥有默认初始值,注意当字段为:指针/切片/map均为nil,即还没有分配空间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type Stu struct{ Name string Age int Address string ptr *int//指针 slice []int//切片 mymap map[string]string//map } p1 := Stu{"Yoyo" ,15,"shenzhen" } // 使用slice和map均提前make p1.slice = make([]int,10) p1.slice[0] = 100 p1.mymap = make(map[string]string) p1.mymap["keya" ] = "valuea" fmt.Println(p1)
不同结构体变量的字段是独立的,互不影响,即一个结构体变量字段更改不影响另外一个
1 2 3 4 5 6 7 8 9 10 //声明1 var p2 Stu //声明2 //var p2 Stu = Stu{} p2.Name = "KK" p2.Age = 23 p3 := p2 // 默认值拷贝 p3.Age = 24 fmt.Println(p2)//{KK 23 <nil> [] map[]} fmt.Println(p3)//{KK 24 <nil> [] map[]}
创建结构体变量:以下两种方式返回结构体指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 type Person struct{ Name string Age int } // 声明3 var p3 *Person = new (Person) (*p3).Name = "Jack~" //注意:"." 的运算符优先级高于"*" ,因此必须加上() (*p3).Age = 28 // 以上可等价于(设计目的:方便使用,在go编译器底层对其转换,默认添加*) p3.Name = "Pearson" fmt.Println(p3) // 声明4 var p4 *Person = &Person{} (*p4).Name = "Alex" //等价于 p4.Name = "Fisher" fmt.Println(p4)
结构体的所有字段在内存中是连续分布的(可熟练通过地址获取内容),但是其指向的地址不一定连续(可能连续)
1 2 // 打印结构体字段的地址 fmt.Printf("Name的地址:%p,Age的地址:%p" ,&p3.Name,&p3.Age)
结构体在与其他类型转换时需要有相同的字段(名字、个数、类型)
1 2 3 4 5 6 7 8 9 10 11 12 type A struct{ Num int } type B struct{ Num int } func main (){ var a A var b B a = A(b) fmt.Println(a)//强制转换可通过(A和B结构体的字段名字、个数、类型一致) }
结构体进行type重新定义(重命名),Golang认为该新名为新的数据类型,不可以直接赋值,但是可以互相强制转换
1 2 3 4 5 6 7 8 9 10 11 12 type Student struct{ Name string Age int } type Stu Student//重命名func main (){ var stu1 Student var stu2 Stu //stu2 = stu1//ERROR,不可以直接赋值,数据类型不匹配 stu2 = Stu(stu1)//需要使用强制转换 fmt.Println(stu2) }
struct的每个字段上,可以写上一个tag,该tag可以通过反射机制(后续介绍) 获取,常见的使用场景是序列化和反序列化
序列化:变量转成字符串(Json)
问题:在结构体中字段名通常首字母为大写,不过在返回给其他程序使用时,例如jquery.php不习惯首字母大写的方式,由于其他包访问结构体字段时如果遇到小写则无法共享,因此必须使用大写
解决:使用tag标签解决(import “encoding/json”)
tag格式:json:"别名"
json.Marshal函数使用反射(后续介绍)
1 2 3 4 5 6 7 8 9 10 11 type Person struct{ Name string `json:"name" `//使用反引号,`json:"别名" ` Age int `json:"age" ` } func main (){ p1 := Person{"TT" ,25}//创建一个Person变量 jsonPerson,error := json.Marshal(p1)//返回2个值([]byte , error),注意如果结构体字段首字母为小写,该返回为空(即不显示首字母小写的字段信息),由于json在另一个包调用该包 if error == nil{ fmt.Println(string(jsonPerson))//使用反引号后首字母小写 } }
总结创建结构体变量:
1 2 3 4 5 6 //方式1(可指定字段值名,也可忽略) var stu1 Student = Student("Tom" ,18) //方式2 stu2 := Student{"JoJo" ,19} //方式3 var stu3 *Student = &Student{"Smith" ,30}
40.方法 Golang的方法是作用在指定的数据类型,自定义类型都可以有方法,不仅仅struct
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 type A struct{ Num int } func (a A)test (n int)int{//表示A结构体有test ()方法 fmt.Println(a.Num) fmt.Println("received n: " ,n) return n+10 } func main (){ var a A res := a.test(11) fmt.Println(res) } type integer intfunc (i interger) print (){//值传递 fmt.Println("i=" ,i) } func main (){ var i integer = 10 i.print() } type integer intfunc (i *integer ) print (){//引用传递 *i = *i + 1 fmt.Printf("i=%v\n" ,*i) } func main (){ var i integer = 10 (&i).print () fmt.Printf("i=%v" ,i) }
如果一个类型实现了String()方法,则fmt.Println默认调用String()进行输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type Student struct { Name string Age int } func (stu *Student)String()string{ //该出输入参数为指针类型,是为了main()中fmt.Println(stu)正常输出stu,仅(&stu)才使用改写的String() str := fmt.Sprintf("Name=[%v],Age=[%v]" ,stu.Name,stu.Age)//底层优化可省略"*" return str } func main (){ stu := Student{"Chen" ,25} fmt.Println(stu)//{Chen 25} // 实现了*Student类型的String方法,则在执行fmt.Println()自动调用 fmt.Println(&stu)//Name=[Chen],Age=[25] }
方法和函数的区别 - 1:
函数调用方式:函数名(实参列表)
方法调用方式:变量.方法名(实参列表)
方法和函数的区别 - 2:
普通函数:接受者为值类型时,不能将指针类型的数据直接传递,即形参类型必须与定义的的类型一致
方法:接收者为值类型时,可以直接使用指针类型,但仍然为方法的传递机制(基于方法,默认底层优化的机制)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 type Person struct{ Name string Age int } func(p Person) print () {//值传递(不管是结构体变量还是其地址) p.Name = "Jack" //fmt.Println("print() Name = " ,p.Name) } func (p *Person) print2 (){//引用传递(不管是结构体变量还是其地址) p.Name = "Jack" //fmt.Println("print2() Name = " ,p.Name) } func main (){ p := Person{"ABC" ,22} p.print()//结构体变量 (&p).print ()//结构体变量地址(本质仍然是值传递) fmt.Println("print()->main() Name=" ,p.Name)//print ()->main() Name= ABC(没有变化) p.print2()//结构体变量 fmt.Println("print2()->main() Name=" ,p.Name)//print2()->main() Name= Jack(变化了) }
41.工厂模式 Golang的结构体没有构造函数,通常使用工厂模式来解决问题
需要:结构体名首字母小写且可以被其他包所调用
不使用工厂模式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // 文件1 awesomeProject/utils/utils.go package utils type Student struct {//必须大写才可以被其他包调用 Name string Age int } // 文件2 awesomeProject/main/helloworld.go package main import ( "awesomeProject/utils" "fmt" ) func main (){ stu := utils.Student{"Chen" ,25} fmt.Println(stu) }
使用工厂模式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 // 文件1 awesomeProject/utils/utils.go package utils type student struct { Name string Age int } // 由于student结构体首字母小写,因此只能在utils.go中使用,可以通过工厂模式解决 func NewStudent(n string, a int) *student{ return &student{n,a} } // 文件2 awesomeProject/main/helloworld.go package main import ( "awesomeProject/utils" "fmt" ) func main (){ stu := utils.NewStudent("Chen" ,25) fmt.Println(stu) }
遇到结构体字段的首字母小写时,可使用Get..()方法解决
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 // 文件1 awesomeProject/utils/utils.go package utils type student struct { Name string age int } // 由于student结构体首字母小写,因此只能在utils.go中使用,可以通过工厂模式解决 func NewStudent(n string, a int) *student{ return &student{n,a} } func (s *student) GetAge() int { return s.age } // 文件2 awesomeProject/main/helloworld.go package main import ( "awesomeProject/utils" "fmt" ) func main (){ stu := utils.NewStudent("Chen" ,25) fmt.Println(stu.GetAge()) }
42.继承 使用嵌套匿名结构体实现继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 type Goods struct{ Name string Price int } type Book struct{ Goods//嵌套匿名结构体Goods Writer string } func main (){ book1 := utils.Book{utils.Goods{Name:"书籍1" ,Price:18},"Tomcat" } fmt.Println(book1) }
结构体可用嵌套匿名结构体所有字段和方法,即首字母大小写的字段、方法均可使用,调用方法时会先寻找本身结构体的方法,若找不到则向上寻找方法
1 2 3 4 5 6 7 8 9 10 func (good Goods) Print (){ fmt.Println(good) } func (book Book) Print (){ fmt.Println("abc" ) } book1.Goods.Name = "bbb" //修改Goods的字段值 book1.Print() //调用Book/Goods的方法,当找不到Book类型的方法则自动向上寻找
如果struct嵌套了一个有名结构体,该模式为组合,如果是组合关系,那么在访问组合的结构体字段/方法时,必须带上结构体名字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type A struct{ Name string Age int } type B struct{ nickname A //有名结构体,格式:字段名 字段类型 } type C struct{ *A // 嵌套结构体指针其效率更高,注意声明结构体变量的操作【遇到指针就给地址,也返回地址】 } func main (){ var b B b.nickname.Name = "aaa" b.nickname.Age = 11 fmt.Println(b) c := C{&A{"aaa" ,11}} //需要添加& fmt.Println(*c.A)//返回地址,使用*取值 }
结构体还可以定义基本数据类型直接为字段
1 2 3 4 5 6 7 8 9 type Person struct{ Name string int//匿名字段:基本数据类型 n int//有相同类型则需重命名 } func main (){ p := Person{"aa" ,11} fmt.Println(p)//使用方法相同 }
43.接口Interface 接口(interface)类型可以定义一组方法,不需要实现 ,interface不能包含任何变量,在某个自定义类型需要使用时才根据具体情况实现所有方法
例子1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // 定义2个方法的接口 type geometry interface { //声明2个没有实现的方法 area() float64 perim() float64 } // 定义结构体 type rect struct { width, height float64 } // 让rect结构体实现geometry接口的定义方法area() func (r rect) area() float64 { return r.width * r.height } // 让rect结构体实现geometry接口的定义方法perim() func (r rect) perim() float64 { return 2*r.width + 2*r.height }
例子2:(接口——“松耦合高类聚”的思想)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 type Usb interface{ Start() Stop() } // 定义Phone结构体 type Phone struct{ } // 让Phone结构体实现Usb的接口方法(不需要显式定义,即未知是Usb接口所声明的方法,与java中的implement关键字的继承截然不同) func (phone Phone)Start (){ fmt.Println("phone is started" ) } func (phone Phone)Stop (){ fmt.Println("phone is stoped" ) } // 定义Camera结构体 type Camera struct{ } // 让Camera结构体实现Usb的接口方法 func (camera Camera)Start (){ fmt.Println("camera is started" ) } func (camera Camera)Stop (){ fmt.Println("camera is stoped" ) } // 定义Computer结构体 type Computer struct{ } //接收一个Usb接口类型变量,实现了Usb接口,即实现Usb接口所声明的所有方法 func (c Computer) Working(usb Usb){ usb.Start()//通过usb接口变量调用start()方法 usb.Stop()//通过usb接口变量调用stop()方法 } func main (){ computer := Computer{} phone := Phone{} camera := Camera{} computer.Working(phone)//传入的Phone类型变量,即调用Phone.start()和Phone.stop() computer.Working(camera)//调用Camera.start()和Camera.stop() // 以上传入Phone和Camera类似于多态,呈现不同的状态 }
接口相关知识:
(1)接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量(实例)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 type A interface{ Say() } //定义一个结构体并实现A接口的方法 type Stu struct{ Name string } func (student Stu)Say (){ fmt.Println("student is saying" ) } func main (){ var student Stu var a A = student//若不实现Say()方法,即Student必须实现A接口声明的方法,则尽可以声明a,但无法实例化a,即无法将一个其他类型赋给接口类型变量a a.Say()// }
(2)接口中的所有方法都没有方法体,即没有实现的方法
(3)需要将接口作为形参传入时,即实现该接口,即需要实现该接口所声明的所有方法
(4)一个自定义类型只有实现了某接口,才能将该自定义类型的实例赋给接口类型
(5)所有自定义数据类型均可以实现接口
(6)一个自定义类型可以实现多个接口
(7)接口中不允许有变量
1 2 3 type A interface{ Name string//×不允许定义 }
(8)一个接口可以继承多个接口,需实现继承的全部接口所声明的方法
(9)接口类型默认是指针(引用类型),没有对其初始化时会输出
1 2 3 4 5 6 7 type A interface{ start() } func main (){ var a A fmt.Println(a) }
(10)空接口没有任何方法,即所有类型均实现了空接口
1 2 3 4 5 6 7 8 type T interface{} func main (){ var student Stu var t interface{} = student //空接口 var t T = student //空接口 }
(11)接口继承多接口时,不允许有重复方法名
1 2 3 4 5 6 7 8 9 10 11 12 type A interface{ Test1() } type B interface{ Test1() } type C interface{//继承多接口出现了重复方法名 A B } func main (){ }
(12)变量指针类型和变量类型是有区别的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type A interface{ Say() } type Stu struct{} func (stu *Stu) Say (){ fmt.Println("Stu Say()" ) } func main (){ var stu Stu = Stu{} var a A = stu//编译不通过,由于实现的是指针类型 var a A = &stu//编译通过 a.Say() }
(13)实现结构体切片的排序:sort.Sort(data Interface)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 import ( "fmt" "math/rand" "sort" ) //1.声明Person结构体 type Person struct{ Name string Age int } //2.声明一个Person结构体切片类型 type PersonSlice []Person//3.实现接口方法(实现Sort需要实现Len()int,Less(i,j int)bool,Swap(i,j int)方法) func (p PersonSlice) Len() int{ return len(p) } func (p PersonSlice) Less(i,j int) bool{//决定使用什么标准排序(按照age从小到大排序) return p[i].Age < p[j].Age } func (p PersonSlice) Swap(i,j int) { temp := p[i] p[i] = p[j] p[j] = temp } //4.测试 结构体切片排序 func main (){ // 定义一个数组/切片 var intSlice = []int{0,-1,10,7,90} sort.Ints(intSlice) fmt.Println(intSlice) // 定义结构体切片 var ps PersonSlice for i:=0;i<10;i++{ p := Person{ Name:fmt.Sprintf("人物%d" ,rand.Intn(100)), Age:rand.Intn(100), } ps = append(ps,p) } for _,v := range ps{ fmt.Println(v) } //5.调用sort.Sort sort.Sort(ps) fmt.Println("=======Sorted=======" ) //6.检查结果 for _,v := range ps{ fmt.Println(v) } }
继承:解决代码的复用性和可维护性
接口:设计和规范方法,让自定义类型实现方法
多态参数:接口既可以允许A结构体实现方法,也可以允许B结构体实现方法,即接口会依据具体实现方法的类型变量而定
多态数组:接口类型切片变量既可以存放A结构体也可以存放B结构体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 type Usb interface { start() stop() } type Phone struct { Name string } type Camera struct { Name string } func (p Phone)start (){//多态参数 1 fmt.Println("phone start" ) } func (p Phone)stop (){ fmt.Println("phone stop" ) } func (p Camera)start (){//多态参数 2 fmt.Println("camera start" ) } func (p Camera)stop (){ fmt.Println("camera stop" ) } func main (){ var usbs [3]Usb//多态数组 usbs[0] = Phone{"vivo" } usbs[1] = Phone{"oppo" } usbs[2] = Camera{"索尼" } fmt.Println(usbs) }
44.类型断言assert 类型断言:由于接口是一般类型,不知道具体类型,如果要转成具体类型,需要使用类型断言
例子 1 :
1 2 3 4 var t float32 var x interface{} x = t y := x
例子 2 :
1 2 3 4 5 6 7 8 9 10 11 12 type Point struct{ x,y int } func main (){ var a interface{} var point Point = Point{1,2} a = point // 空接口可接收任何一个变量 var b Point //b = a//ERROR,不允许将空接口(类型不明确)赋给Point类型 b = a.(Point) //类型断言,即尝试将a变量转换为Point类型变量,成功即通过,不成功即报错 fmt.Println(b) //{1 2} }
例子 3:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 package main import "fmt" type Usb interface { start() stop() } type Phone struct { Name string } type Camera struct { Name string } type Computer struct{//不同之处} func (c Computer)Working(usb Usb){//不同之处 usb.start() //如果为Phone类型(类型断言)则调用Call()方法 if phone,ok := usb.(Phone);ok{ phone.Call() } usb.stop() fmt.Println("phone start" ) } func (p Phone)start (){//多态参数 1 fmt.Println("phone start" ) } func (p Phone)Call (){//多态参数 1 fmt.Println("phone is calling" ) } func (p Phone)stop (){ fmt.Println("phone stop" ) } func (p Camera)start (){//多态参数 2 fmt.Println("camera start" ) } func (p Camera)stop (){ fmt.Println("camera stop" ) } func main (){ var usbs [3]Usb//多态数组 usbs[0] = Phone{"vivo" } usbs[1] = Phone{"oppo" } usbs[2] = Camera{"索尼" } //fmt.Println(usbs) var computer Computer for _,v := range(usbs){ computer.Working(v)//基于v的类型调用usb.start()和usb.stop()因此,前2组为手机,第3组为相机 } }
例子 4:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 func Typejudge(items ...interface{}){ for i,x := range items{ switch x.(type ) { case bool: fmt.Printf("第%v个参数为布尔类型\n" ,i) case int,int32,int64: fmt.Printf("第%v个参数为整数类型\n" ,i) case float32,float64: fmt.Printf("第%v个参数为浮点数类型\n" ,i) case string: fmt.Printf("第%v个参数为字符串类型\n" ,i) case *Student: fmt.Printf("第%v个参数为*Student类型\n" ,i) case Student: fmt.Printf("第%v个参数为Student类型\n" ,i) default: fmt.Printf("第%v个参数为其他类型\n" ,i) } } } func main (){ a := 1 b := "ss" c := 5.5 d := Student{"Jomin" } Typejudge(a,b,c,d,&d) }
TODO 尚硅谷韩顺平Go语言核心编程 P229 226家庭收支记账 / P391 387结束语