命名规范 变量名、函数名、常量名采用驼峰法
如果变量名、函数名、常量名首字母大写,则可以被其他的包访问,如果首字母小写,则只能在本包中使用,可以理解为首字母大写是公开,小写是私有的,方法名也遵守这个规则
包名和文件名一般为小写字母
如果一个类型实现了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) } 
 
参考资料