命名规范 变量名、函数名、常量名采用驼峰法
如果变量名、函数名、常量名首字母大写,则可以被其他的包访问,如果首字母小写,则只能在本包中使用,可以理解为首字母大写是公开,小写是私有的,方法名也遵守这个规则
包名和文件名一般为小写字母
如果一个类型实现了String()方法,fmt.Println默认会调用该方法进行输出
原码、反码、补码
最高位表示符号位,0正数,1负数
正数的原码、反码、补码都一样
负数的反码=它的原码符号位不变,其他位取反
负数的补码=它的反码+1
计算机运算时,都是以补码的方式来运算
数据类型 整形:
int8:有符号整型,占1个字节存储空间,范围:-128到127
int16:占2个字节
int32: 占4个字节
int64:占8个字节
uint8:无符号整型,范围:0-255
uint16:
uint32:
uint64:
int:有符号,32位系统占4个字节,64位系统占8个字节,默认推导类型
uint:无符号,32位系统占4个字节,64位系统占8个字节
rune:有符号,和int32一样,表示一个Unicode码
byte:无符号,和uint8一样,当要存储字符时选用byte
uintprt: 可以保存任意指针的位模式的整数类型
浮点数都是有符号的
float32:IEEE-754 32位浮点型数, 4个字节
float64:IEEE-754 64位浮点型数 ,8个字节,默认推导类型
complex64:32 位实数和虚数
complex128:64 位实数和虚数
布尔类型:bool 占用1个字节
字符类型:在Go中,字符的本质是一个整数,直接输出是该字符对应的UTF-8码值
string类型:Go的string底层是用byte数组实现的,一但赋值就不能再修改了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 // 默认值 整型:0 浮点:0 字符串:“” 布尔:false var x float64 = 3.4 var y string = "abc" var c int = 100 var b = false var age byte = 90 var c1 byte = 'a' // 字符a,输出时则是其ASCII值 var c2 int = 22269 // 该值对应utf-8的‘国’ var n1 = 10 + 'a' // 10+97=107 var num = 5.12e2 // 5.12 * 10 的2次方 var num = 5.12E2 // 5.12 * 10 的2次方 var num = 5.12E-2 // 5.12 * 10 的-2次方 // 类型转换 var i int32 = 100 var n1 float32 = float32(i) //将int转换成float
rune用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package main import ( "fmt" "unicode/utf8" ) func main() { var str = "hello 中国" // golang的string底层是用byte数组实现的 // 直接求len实际就是求字节数,utf8下一个汉子占3个字节,所以是12 fmt.Printf("len: %v \n", len(str)) // 12 // rune等同于int32,但是它用来区分字符值,如下把两个汉子算成2个长度 fmt.Printf("len: %v \n", len([]rune(str))) // 8 // 此处是用utf8包下的函数求字符串长度 fmt.Printf("len: %v \n", utf8.RuneCountInString(str)) // 8 }
值类型和引用类型 1 2 值类型:int系列、float系列、bool、string、数组、结构体 引用类型:指针、slice切片、map、管道chan、interface
new/make new: 用来分配值类型内存
make:用来分配引用类型内存
1 2 var num *int = new(int) //返回指针*int,是一个内存地址 *num = 100 // 赋值100
变量 1 2 3 4 5 6 7 8 // 1. 指定变量类型,声明后若不赋值,使用默认值0 var a int // 2. 根据值自行判定变量类型 var b = 10 // 3. 省略var, 注意 :=左侧的变量不应该是已经声明过的,否则会导致编译错误 c : = 10
多变量声明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 无初始值 var x, y int // 有初始值 var c, d int = 1, 2 var e, f = 123, "liudanbing" // 这种分解的写法,一般用于声明全局变量 var ( a int b bool ) //如下这种不带声明格式的只能在函数体内声明 //g, h := 123, "需要在func函数体内实现"
常量 常量的数据类型只可以是布尔、数字型(整数、浮点和复数)、字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // 显式类型定义 const b string = "abc" // 隐式类型定义 const b = "abc" const ( Unknown = 0 Female = 1 Male = 2 ) // 函数必须是内置函数,否则编译不过 const ( a = "abc" b = len(a) c = unsafe.Sizeof(a) )
自增长iota 简化了常量用于增长数字的定义
1 2 3 4 5 const ( CategoryBooks = iota // 0 CategoryHealth // 1 CategoryClothing // 2 )
自增长常量经常包含一个自定义类型
1 2 3 4 5 6 7 type Stereotype int //这是一个自定义类型 Stereotype 实际上就是 int类型 const ( TypicalNoob Stereotype = iota // 0 TypicalHipster // 1 TypicalUnixWizard // 2 TypicalStartupFounder // 3 )
另外,我们可以使用下划线跳过不想要的值
1 2 3 4 5 6 7 8 9 type AudioOutput int const ( OutMute AudioOutput = iota // 0 OutMono // 1 OutStereo // 2 _ _ OutSurround // 5 )
用于表达式,在 Go 语言的spec中, 这就是所谓的隐性重复最后一个非空的表达式列表.
1 2 3 4 5 6 7 8 type Allergen int const ( IgEggs Allergen = 1 << iota // 1 << 0 which is 00000001 IgChocolate // 1 << 1 which is 00000010 IgNuts // 1 << 2 which is 00000100 IgStrawberries // 1 << 3 which is 00001000 IgShellfish // 1 << 4 which is 00010000 )
省略第一个值
1 2 3 4 5 6 7 8 9 10 11 12 type ByteSize float64 const ( _ = iota // ignore first value KB ByteSize = 1 << (10 * iota) // 1 << (10*1) MB // 1 << (10*2) GB // 1 << (10*3) TB // 1 << (10*4) PB // 1 << (10*5) EB // 1 << (10*6) ZB // 1 << (10*7) YB // 1 << (10*8) )
当你在把两个常量定义在一行的时候会发生什么?
Banana 的值是什么? 2 还是 3? Durian 的值又是?
1 2 3 4 5 6 7 8 9 10 11 12 const ( Apple, Banana = iota + 1, iota + 2 Cherimoya, Durian Elderberry, Fig ) // Apple: 1 // Banana: 2 // Cherimoya: 2 // Durian: 3 // Elderberry: 3 // Fig: 4
运算符 1 2 3 4 5 6 7 8 9 A = 0011 1100 B = 0000 1101 ----------------- A&B = 0000 1100 // 按位与:对应位上都是1时则是1 A|B = 0011 1101 // 或:对应位上有一个是1时则是1 A^B = 0011 0001 // 异或:对应位上相同时为0,对应的二进位相异时为1 ~A = 1100 0011 // 非:每个位取相反 A<<2 = 1111 0000 // 左移:十进制为240,左移n位就是乘以2的n次方,高位丢弃,低位补0 A>>2 = 0000 1111 // 右移:十进制为15,右移n位就是除以2的n次方
if语句 支持在if中,直接定义一个变量,然后再写一个判断语句
1 2 3 if age := 20; age > 18 { .... }
Swtich语句
swtich后可以直接定义一个变量,分号结束
不需要加break,程序执行完case语句后,会直接退出switch,如果需要依次执行下一个case,则可以在case语句块后增加fallthrough,称为switch穿透
Type switch:来判断某个interface变量中实际指向的变量类型
1 2 3 4 5 6 7 8 9 10 11 var x interface{} var y = 10.0 x = y switch i := x.(type) { case nil: ... case int: ... case float64: ... }
for语句 Go语言的For循环有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 28 29 30 31 32 package main import ( "fmt" ) func main() { // 第一种 for a := 0; a < 10; a++ { fmt.Printf("a 的值为: %d\n", a) } // 第二种 // for 循环的 range 格式可以对 slice、map、数组、字符串等进行迭代循环 b := [6]int{1, 2, 3, 5} for i, x := range b { fmt.Printf("第%d 位x的值 = %d\n", i, x) } // 第三种 var c int var d int = 15 for c < d { c++ fmt.Printf("c 的值为: %d\n", c) } // 第四种,无限循环,需配合break使用 for { } }
break 语句 经常用于中断当前 for 循环或跳出 switch 语句 ,如果在多层嵌套语句中,可以通过标签指明要终止哪一层语句块,默认是跳出最近的for循环
1 2 3 4 5 6 7 8 lable2: for i := 0; i<4; i++ { for j := 0; j<10; j++ { if j==2 { break lable2 } } }
continue 语句 跳过当前循环的剩余语句,然后继续进行下一轮循环。 也可以使用标签指明要跳过的是哪一层循环,规则和break一样
goto 语句无条件将控制转移到被标记的语句
1 2 3 4 5 6 7 8 9 10 11 var a int = 10 LOOP: for a < 20 { if a == 15 { a = a + 1 goto LOOP } fmt.Printf("a的值为:%d\n", a) a++ }
输出如下,跳过了a=15时的迭代:
1 2 3 4 5 6 7 8 9 a的值为:10 a的值为:11 a的值为:12 a的值为:13 a的值为:14 a的值为:16 a的值为:17 a的值为:18 a的值为:19
golang没有while语句
错误机制 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 20 21 22 23 24 25 26 27 28 29 package main import ( "fmt" ) /** 使用defer+recover 处理异常 */ func test() { defer func() { fmt.Println("enter defer") err := recover() // 内置函数,可以捕获到异常 if err != nil { fmt.Println("err= ", err) fmt.Println("发送邮件给admin@sohu.com") } }() n1 := 10 n2 := 0 res := n1 / n2 fmt.Println("res= ", res) } func main() { test() }
自定义错误
errors.New(“错误说明”),会返回一个error类型,表示一个错误
panic内置函数,接受一个interface{}类型的值,即任何类型,可以接受error类型,输出错误信息,并退出程序
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 package main import ( "errors" "fmt" ) func readConf(name string) error { if name == "init.conf"{ // 读取 return nil } else { // 返回一个自定义错误 return errors.New("读取文件错误") } } func test1() { err := readConf("ini.conf") if err != nil { // 抛出错误,终止程序 panic(err) } fmt.Println("test1 end ...") } func main() { test1() fmt.Println("main() ...") }
函数
1 2 3 4 // args是slice切片,通过args[index]可以访问到各个值 func sum(args... int) int { .... }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package main import ( "fmt" // "reflect" ) func main() { a, b := swap("MA", "Ku") fmt.Println(a, b) } func swap(x, y string) (string, string) { return y, x }
引用传递 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 package main import "fmt" func main() { var a int = 100 var b int = 200 fmt.Printf("交换前,a 的值 : %d\n", a) fmt.Printf("交换前,b 的值 : %d\n", b) swap(&a, &b) fmt.Printf("交换后,a 的值 : %d\n", a) fmt.Printf("交换后,b 的值 : %d\n", b) } func swap(x *int, y *int) { var temp int temp = *x // 保存x地址上的值 *x = *y // 将y值赋给x *y = temp // 将temp值给y } 输出: 交换前,a 的值 : 100 交换前,b 的值 : 200 交换后,a 的值 : 200 交换后,b 的值 : 100
函数作为值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package main import ( "fmt" "math" ) func main() { getSquareRoot := func(x float64) float64 { return math.Sqrt(x) } fmt.Println(getSquareRoot(9)) } 输出:3
闭包 Go 语言支持匿名函数,可作为闭包。
闭包就是一个匿名函数和其相关的引用环境组合的一个整体。
以下实例中,我们创建了函数 getSequence() ,返回另外一个匿名函数,该匿名函数引用到函数外的i变量,因此这个匿名函数就和i变量形成了一个整体,构成闭包。当我们反复调用nextNumber函数时,因为n是初始化一次,所以每调用一次就进行累计
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 package main import ( "fmt" ) // func() int是指返回类型 func getSequence() func() int { i := 0 return func() int { i += 1 return i } } func main() { // nextNumber 为一个函数,函数 i 为 0 nextNumber := getSequence() // 调用 nextNumber 函数,i 变量自增 1 并返回 fmt.Println(nextNumber()) fmt.Println(nextNumber()) fmt.Println(nextNumber()) //创建新的函数 nextNumber1,并查看结果 nextNumber1 := getSequence() fmt.Println(nextNumber1()) fmt.Println(nextNumber1()) } 输出: 1 2 3 1 2
main函数和init函数 Go里面有两个保留的函数:init函数(能够应用于所有的package)和main函数 (只能应用于package main)。这两个函数在定义时不能有任何的参数和返回值。
虽然一个package里面可以写任意多个init函数,但这无论是对于可读性还是以后的 可维护性来说,我们都强烈建议用户在一个package中每个文件只写一个init函数。
Go程序会自动调用init()和main(),所以你不需要在任何地方调用这两个函数。每个 package中的init函数都是可选的,但package main就必须包含一个main函数。
程序的初始化和执行都起始于main包。如果main包还导入了其它的包,那么就会在 编译时将它们依次导入。有时一个包会被多个包同时导入,那么它只会被导入一次 (例如很多包可能都会用到fmt包,但它只会被导入一次,因为没有必要导入多 次)。当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进 来,然后再对这些包中的包级常量和变量进行初始化,接着执行init函数(如果有的 话),依次类推。等所有被导入的包都加载完毕了,就会开始对main包中的包级常 量和变量进行初始化,然后执行main包中的init函数(如果存在的话),最后执行main函数。
如果一个文件同时包含全局变量定义,init() main()则执行顺序是:
全局变量定义 –> init函数 –>main函数
defer函数 当执行到defer时,暂时不执行,会将defer后面语句压入到独立的栈,defer栈
当函数执行完毕后,再从defer栈,按照先入后出的方式出栈执行
在defer将语句放入到栈时,也会将相关的值拷贝同时入栈
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 package main import "fmt" func sum(n1 int, n2 int) int { defer fmt.Println("ok1 n1 = ", n1) defer fmt.Println("ok2 n2 = ", n2) n1++ n2++ res := n1 + n2 fmt.Println("ok3 res=", res) return res } func main() { res := sum(10, 20) fmt.Println("res ", res) } 输出: ok3 res= 32 ok2 n2 = 20 ok1 n1 = 10 res 32
指针 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package main import ( "fmt" ) func main() { var a int = 20 var ip *int // 声明指针变量 ip = &a // 指针存储变量a值的地址 fmt.Printf("a 变量的地址是: %x\n", &a) fmt.Printf("ip 变量储存的指针地址: %x\n", ip) fmt.Printf("*ip 变量的值: %d\n", *ip) } 输出: a 变量的地址是: c00000e1c8 ip 变量储存的指针地址: c00000e1c8 *ip 变量的值: 20
空指针 当一个指针被定义后没有分配到任何变量时,它的值为 nil。nil 指针也称为空指针。
nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。
一个指针变量通常缩写为 ptr。
1 2 3 4 5 var ptr *int fmt.Printf("ptr 的值为 : %x\n", ptr ) if(ptr != nil) /* ptr 不是空指针 */ if(ptr == nil) /* ptr 是空指针 */
指针数组 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 package main import ( "fmt" ) const MAX int = 3 func main() { a := []int{10, 20, 30} var ptr [MAX]*int for i := 0; i < MAX; i++ { ptr[i] = &a[i] } for i := 0; i < MAX; i++ { fmt.Printf("a[%d] = %d\n", i, *ptr[i]) } } 输出: a[0] = 10 a[1] = 20 a[2] = 30
数组和切片 数组
一个数组一但定义,长度固定,不能动态改变
数组创建后,如果没有赋值,有默认值0
注意数组是值传递,如果想在其他函数中修改原数组数据,需要传地址
数组作为函数形参时要写上长度,实参和形参的长度也要一样
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 package main import "fmt" func main() { // 声明即开辟了内存空间 var arr [3]int fmt.Println("arr: ", arr) // 初始化 var arr1 [3]int = [3]int{1,2,3} fmt.Println("arr: ", arr1) var arr2 = [3]int{5,6,7} fmt.Println("arr: ", arr2) var arr3 = [...]int{8,9,10} fmt.Println("arr: ", arr3) var arr4 = [...]int{1: 800, 0: 900, 2:999} fmt.Println("arr: ", arr4) arr5 := [...]string{1: "tom", 0: "jack"} fmt.Println("arr: ", arr5) // 遍历 for i:=0; i<len(arr1); i++ { fmt.Printf("arr1: %v=%v\n", i, arr1[i]) } for index, val := range arr2 { fmt.Printf("arr2: %v=%v\n", index, val) } }
切片Slice 切片是一个动态数组,属于引用传递
1 2 3 4 5 6 7 8 9 10 11 // 空切片,定义完不能使用,要让其引用一个数组,或者make一个内存空间来使用 var numbers []int var slice1 []type = make([]type, len) 也可以简写为 slice1 := make([]type, len) 也可以指定容量,其中capacity为可选参数 make([]type, length, capacity) 这里 len 是数组的长度并且也是切片的初始长度
切片初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // 直接初始化切片,[]表示是切片类型,{1,2,3}初始化值依次是1,2,3.其cap=len=3 // s := []int{1, 2, 3} // 初始化切片s,是数组arr的引用 // s := arr[:] // 将arr中从下标startIndex到endIndex-1 下的元素创建为一个新的切片 // s := arr[startIndex:endIndex] // 缺省endIndex时将表示一直到arr的最后一个元素 // s := arr[startIndex:] // 缺省startIndex时将表示从arr的第一个元素开始 // s := arr[:endIndex] // 通过切片s初始化切片s1 // s1 := s[startIndex:endIndex]
切片是可索引的,并且可以由 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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 package main import ( "fmt" ) func main() { /* 创建切片 */ numbers := []int{0, 1, 2, 3, 4, 5, 6, 7, 8} printSlice(numbers) /* 打印原始切片 */ fmt.Println("numbers ==", numbers) /* 打印子切片从索引1(包含) 到索引4(不包含)*/ fmt.Println("numbers[1:4] ==", numbers[1:4]) /* 默认下限为 0*/ fmt.Println("numbers[:3] ==", numbers[:3]) /* 默认上限为 len(s)*/ fmt.Println("numbers[4:] ==", numbers[4:]) numbers1 := make([]int, 0, 5) printSlice(numbers1) /* 打印子切片从索引 0(包含) 到索引 2(不包含) */ number2 := numbers[:2] printSlice(number2) /* 打印子切片从索引 2(包含) 到索引 5(不包含) */ number3 := numbers[2:5] printSlice(number3) } func printSlice(x []int) { fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x) } 输出: len=9 cap=9 slice=[0 1 2 3 4 5 6 7 8] numbers == [0 1 2 3 4 5 6 7 8] numbers[1:4] == [1 2 3] numbers[:3] == [0 1 2] numbers[4:] == [4 5 6 7 8] len=0 cap=5 slice=[] len=2 cap=9 slice=[0 1] len=3 cap=7 slice=[2 3 4]
append() 函数append操作的本质就是对数组扩容,底层会新建一个数组,将原来元素拷贝过去,再继续加新元素,最后把新数组地址重新引用到
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 package main import ( "fmt" "reflect" ) func main() { var arr [5]int = [...]int{1,2,3,4,5} slice1 := arr[:] slice2 := slice1[1: 2] fmt.Printf("slice1 = %v \nslice2 = %v\n", slice1[1], slice2[0]) slice2[0] = 30 fmt.Printf("slice1 = %v \nslice2 = %v\n", slice1[1], slice2[0]) slice2 = append(slice2, 7, 8, 9,8,7,6) fmt.Println(slice2) //[30 7 8 9 8 7 6] slice1 = append(slice1, 7, 8, 9,8,7,6) fmt.Println(reflect.TypeOf(slice1)) fmt.Println(slice1)// [1 30 3 4 5 7 8 9 8 7 6] }
string和slice
string底层是一个byte数组,因此也可以进行切片处理-
string是不可变的,也就是说不能用str[0]=’z’来修改
map
声明不会分配内存,初始化需要make分配内存,之后才能赋值和使用
map是引用传递
map容量到达后,会自动扩容
map的value可用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 34 35 36 37 38 39 40 41 42 package main import "fmt" func main() { // 创建 var map1 map[string]string map1 = make(map[string]string, 10) map1["one"] = "111" fmt.Println(map1) map2 := map[int]string{ 1: "one", 2: "tow", } fmt.Println(map2) // 删除 delete(map2, 1) fmt.Println(map2) // 获取 val, ok := map2[2] if ok { fmt.Printf("ok: %v\n", val) } else { fmt.Println("ok no") } /** 一个map存放学生信息,每个学生有name和sex */ stuMap := make(map[string]map[string]string) stuMap["stu01"] = make(map[string]string, 2) stuMap["stu01"]["name"] = "tom" stuMap["stu01"]["sex"] = "man" fmt.Println(stuMap) fmt.Println(stuMap["stu01"]) fmt.Println(stuMap["stu01"]["name"]) }
map切片 1 2 3 4 5 6 7 8 9 10 11 12 13 14 // map切片 ms := make([]map[string]string, 1) ms[0] = make(map[string]string) ms[0]["name"] = "jack" ms[0]["age"] = "88" fmt.Println(ms) m1 := make(map[string]string) m1["name"] = "jane" m1["age"] = "77" ms = append(ms, m1) fmt.Println(ms)
map排序
go中没有方法对map进行排序
map默认是无序的,也不是按照添加的顺序存放,每次遍历输出可能都不一样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // map排序 mu := make(map[int]string, 3) mu[9] = "nice" mu[5] = "five" mu[3] = "three" var keys = make([]int, len(mu)) for k,_ := range mu{ keys = append(keys, k) } sort.Ints(keys) fmt.Println(keys) for _, v := range keys { fmt.Printf("map[%v] = %v\n", v, mu[v]) } }
结构体
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 package main import "fmt" type Student struct { Name string Age int } func main() { // 结构体值传递 stu1 := Student{ Name: "Tom", Age: 5, } stu2 := stu1 stu2.Name = "Jack" stu2.Age = 77 fmt.Printf("stu1: %v\nstu2: %v\n", stu1, stu2) // 创建方式1 var s1 Student s1.Name = "xxx" fmt.Println(s1) // 创建方式2 s2 := Student{ Name: "xxx", Age: 2, } fmt.Println(s2) // 创建方式3 var stu3 *Student = new(Student) stu3.Name = "john" // go底层对stu3做了取值运算(*stu3).Name (*stu3).Age = 88 // ok fmt.Printf("stu3: %v\n", *stu3) // 方式4 var s4 *Student = &Student{} s4.Name = "xx" // go底层为了方便默认进行取值运算s4 fmt.Println(*s4) }
结构体可以和其他类型转换,需要有完全相同的字段(名字,个数,类型)
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, b) }
struct的每个字段上,可以写一个tag,可以通过反射机制获取,常见场景就是序列化
Go的结构体没有构造函数,通常用工厂模式来解决,如果结构体首字母是大写,则可以引包创建,如果是小写字母,要创建只能用工厂模式来解决
1 2 3 4 5 6 7 8 9 10 11 12 // 工厂模式实现跨包创建结构体实例 type student struct { Name string Score float64 } func NewStudent(n string, s float64) *student { return &student{ Name: n, Score: s, } }
方法
会将调用方法的变量,当做参数也传递给方法
方法名首字母小写,只能在包内访问,大写可以在其他包访问
不管调用方式如何,真正决定是值传递还是地址传递,是看方法的接收者是和哪种类型绑定,如p Person是值拷贝不会影响原值,p *Person是地址传递会影响
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package main import ( "fmt" "math" ) type Point struct{ X, Y float64 } // 这是给struct Point类型定义一个方法 // 前个附加的参数p,叫做方法的接收器(receiver) func (p Point) Distance(q Point) float64 { return math.Hypot(q.X-p.X, q.Y-p.Y) } func main() { p := Point{1, 2} q := Point{4, 6} fmt.Println(p.Distance(q)) //通过p调用方法 }
基于指针接受类型的方法 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 package main import ( "fmt" "math" ) type T struct { name string } func (t T) method1() { t.name = "new name1" } func (t *T) method2() { t.name = "new name2" } func main() { t := T{"old name"} fmt.Println("method1 调用前 ", t.name) t.method1() fmt.Println("method1 调用后 ", t.name) fmt.Println("method2 调用前 ", t.name) t.method2() fmt.Println("method2 调用后 ", t.name) } 输出: method1 调用前 old name method1 调用后 old name method2 调用前 old name method2 调用后 new name2
方法值 在一个包的API需要一个函数值、且调用方希望操作的是某一个绑定了对象的方法 的话,方法”值”会非常实用.
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 package main import ( "fmt" "math" ) type Point struct{ X, Y float64 } func (p Point) Distance(q Point) float64 { return math.Hypot(q.X-p.X, q.Y-p.Y) } func main() { p := Point{1, 2} q := Point{4, 6} // 方法值,类似C语言的函数指针 // 实际上distanceFormP 就绑定了 p接收器的方法Distance distanceFormP := p.Distance fmt.Println(distanceFormP(q)) // 5 fmt.Println(p.Distance(q)) // 5 // 实际上distanceFormQ 就绑定了 q接收器的方法Distance distanceFormQ := q.Distance fmt.Println(distanceFormQ(p)) // 5 fmt.Println(q.Distance(p)) // 5 }
方法表达式 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 package main import ( "fmt" "math" ) type Point struct{ X, Y float64 } func (p Point) Distance(q Point) float64 { return math.Hypot(q.X-p.X, q.Y-p.Y) } func main() { p := Point{1, 2} q := Point{4, 6} // 方法表达式, 是一个函数值(相当于C语 言的函数指针) // var op func(p, q Point) Point 定义一个 op变量,类型是方法表达式 distance1 := Point.Distance fmt.Println(distance1(p, q)) // 5 // %T表示打出数据类型 ,这个必须放在P rintf使用 fmt.Printf("%T\n", distance1) distance2 := (*Point).Distance fmt.Println(distance2(&p, q)) // 5 fmt.Printf("%T\n", distance2) } 输出: 5 func(main.Point, main.Point) float64 5 func(*main.Point, main.Point) float64
封装 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 package main import ( "fmt" ) type data struct { val int } func (p_data *data) set(num int) { p_data.val = num } func (p_data *data) show() { fmt.Println(p_data.val) } func main() { p_data := &data{4} p_data.set(5) p_data.show() }
继承
子结构体可以使用父结构体的所有的字段和方法,包括首字母大小写的字段和方法。
当父子结构体有相同的字段或方法时,编译器采用就近原则访问,如果希望访问则可以通过结构体名来区分
当嵌入多个匿名结构体时,且该多个匿名结构体中有相同的字段和方法,且被嵌入的结构体本身没有同名的,在访问时,就必须明确指定匿名结构体的名字,否则编译错误
如果嵌套了一个有名结构体,这种模式就是组合,此时在访问组合结构体的字段和方法时,必须带上结构体名字
如果一个结构体有一个int类型的匿名字段,就不能有第二个,否则必须指定名字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package main import ( "fmt" ) type parent struct { val int } type child struct { parent num int } func main() { var c child c = child{parent{1}, 2} fmt.Println(c.num) fmt.Println(c.val) }
接口
A结构体继承B结构体,A就可以直接使用B的方法和字段,当A需要扩展功能时,同时不希望去破坏继承关系,就可以实现接口
1 2 3 4 type Usb interface { Start() Stop() }
任何其他类型只要实现了这些方法就是实现了这个接口
接口中不能包含任何变量
空接口interface{} 没有任何方法,所以所有类型都实现了空接口,即可以把任何一个变量赋值给空接口
接口不能创建实例,但可以指向一个实现了该接口的自定义类型的实例
只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型
1 2 3 4 5 type integer int // 相当于创建了一个新类型,同int func (i integer) Say() { ... }
一个接口A可以继承多个别的接口B和C,如果要实现接口A,则要把B和C的方法都实现
1 2 3 4 5 6 7 8 9 10 11 12 type B interface { f2() } type C interface { f3() } type A interface { B C f1() }
interface类型默认是一个指针(引用类型),如果没有对interface初始化,则是nil
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 type Usb interface { Say() } type Stu struct { } func (s *Stu) Say() { ... } func main() { var stu Stu = Stu{} // var u Usb = stu 报错,会报Stu类型没有实现Usb接口 var u Usb = &stu // ok u.Say() }
多态 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 package main import ( "fmt" ) type act interface { write() } type xiaoming struct { } type xiaofeng struct { } func (xm *xiaoming) write() { fmt.Println("xiaoming write") } func (xf *xiaofeng) write() { fmt.Println("xiaofeng write") } func main() { var a act xm := xiaoming{} xf := xiaofeng{} a = &xm a.write() a = &xf a.write() }
类型断言
由于接口是一般类型,不知道具体类型,如果要转,就需要使用类型断言
在进行类型断言时,如果不匹配,就会报panic
直接断言
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 package main import ( "fmt" ) func test1(a interface{}) string { value, ok := a.(string) if !ok { fmt.Println("It's not ok for type string") return "" } fmt.Printf("This is %s\n", value) return value } func main() { // a := "aaa" a := 10 test1(a) }
参考资料