自学go语言的笔记干货

news/2024/11/15 20:40:13/

在经过3个月的Go的学习中 整理出来的比较  感觉还拿得出手。

文档资料:http://yuancao.meicx.com/d/34
书签网:https://www.bookstack.cn/
格式化:gofmt -w hello.go
编译*指令:go build -o 123.exe main.gogo build xxx.go输入输出fmt:var name string fmt.Println("请输入姓名")	//输出fmt.Scanln(&name)			//输入Sprintf:	接受打印后的字符串赋给一个变量存储dataStr := fmt.Sprintf("当前年月日 %d-%d-%d %d:%d:%d \n",time.Now().Year(),.....)fmt.Println("dataStr=%v",dataStr)
进制*:(1) 其他进制转十进制:a. 二进制转十进制::: 从最低位开始(右边),将每个位上的数据提取出来,乘以2的(位数-1)次方,然后求和1011 => 1*2零次方 + 1*2一次方 + 0*2二次方 + 1*2三次方 => 1+2+0+8 = 11b. 八进制转十进制::: 从最低位开始(右边),将每个位上的数据提取出来,乘以8的(位数-1)次方,然后求和0123 => 3*8零次方 + 2*8一次方 + 1*8二次方 + 0*8三次方 => 3+16+64+0 = 83c. 十六进制转十进制::: 从最低位开始(右边),将每个位上的数据提取出来,乘以16的(位数-1)次方,然后求和0x34A => 10*16零次方 + 4*16一次方 + 3*16二	次方 => 10 + 4*16 + 3*16*16 = 842(2) 十进制转其他进制:a. 十进制转二进制::: 将该数不断除以2,直到商为0为止,然后将每部得到的余数倒过来,就是对应的二进制56 => 56/2余0 28/2余0 14/2余0 7/2余1 3/2余1 余1 => 111000b. 十进制转八进制 (八进制以0开头):: 将该数不断除以8,直到商为0为止,然后将每部得到的余数倒过来,就是对应的八进制156 => 156/8余c. 十进制转十六进制: (十六进制以0x开头):: 将该数不断除以16,直到商为0为止,然后将每部得到的余数倒过来,就是对应的十六进制356 => 356/16余4 22/16余6 余1 => 0x164(3) 二进制转八进制和十六进制:a. 二进制转八进制::: 将二进制数每三位一组(从低位开始组合),转换成对应的八进制数即可 0开头倒转11010101 => 101转8#5 010转8#2 11转8#3 =>0325b. 二进制转十六进制::: 将二进制数每四位一组(从低位开始组合),转换成对应的十六进制数即可 0x开头倒转11010101 => 0101转16#5 1101转16#13(D) => 0xD5(4) 八进制和十六进制转二进制:a. 八进制转二进制::: 将八进制的每1位,转为对应的一个3位的二进制即可 倒转0237 => 7转2#111 3转2#011 2转2#10 => 10011111b. 十六进制转二进制::: 将十六进制的每1位,转为对应的一个4位的二进制即可 倒转0x237 => 7转2#0111 3转2#	0011 2转2#10 => 1000110111
位运算*:(1) 二进制的三个重要概念:原码,反码,补码a. 对于有符号的而言:1) 二进制的最高位是符号位: 0 表示正数, 1 表示负数 1 ==> [0000 0001]	-1 ==> [1000 0001]2) 正数的原码,反码,补码都一样3) 负数的反码 = 它的源码符号位不变,其他位取反(0->1,1->0)1 ==> 原码[0000 0001] 反码[0000 0001] 补码[0000 0001]-1 ==> 原码[1000 0001] 反码[1111 1110] 补码[1111 1111] 4) 负数的补码 = 它的反码+15) 0的反码,补码都是06) *在计算机运算的时候,都是以补码的方式来运算  1-1 => 1 + (-1)(2) go中有三个位运算 按位与(&),按位或(|),按位异或(^)a. 按位与& 	 :	 两位全部为1,结果为1,否则为02&3 ==> 2的补码[0000 0010] 3的补码[0000 0011]		2&3 => [0000 0010] ->2b. 按位或|	 :	 两位只要有一个为1,结果为1,否则为02|3 ==> 2的补码[0000 0010] 3的补码[0000 0011] 	2|3 => [0000 0011] ->3c. 按位异或^  :   两位相同取0,不相同取12^3 ==>	2的补码[0000 0010] 3的补码[0000 0011]		2^3 => [0000 0001] ->1-2^2 ==> -2的补码->原码[1000 0010]->反码[1111 1101]->补码[1111 1110]2的补码[0000 0010]		-2^2 => [1111 1100](补码)->反码[1111 1011](补码-1)->原码[1000 0100](符号位不变其他取反) ->-4(3) go中的2个移位运算符a. >> 右移运算符		:: 低位溢出,符号位不变,并用符号位补溢出的高位1>>2 ==> 1的补码[0000 0001] =>[0000 0000] = 0b. << 左移运算符		:: 符号位不变,低位补01<<2 ==> 1补码[0000 0001] => [0000 0100] = 4变量*的使用方法:(1) 变量作用域:a. 函数内申明/定义的变量交局部变量,作用域仅限于函数内部b. 函数外部申明/定义的变量交全局变量,作用域在整个包都使用,如果其首字母大写,则作用域在整个程序都有效c. 如果变量是在一个代码块,比如for/if中,那么该变量的作用域就是在该代码块中(2) 变量的使用方法:var name = "tom"Name := "jack"	//会报错,因为函数外部不能做赋值操作func main(){//golang 变量使用//1. 指定变量类型 声明后不赋值 使用默认值 var i intfmt.Println("i=",i)//2. 根据数值类型判断(类型推导)var num = 10.22fmt.Println("num=",num)//3. 省略var 使用 :=  左侧的变量不应该是已经申明过的 否则编译会导致错误// 等价于  var name string     name := "tom"name := "tom"fmt.Println("name=",name)}(3) 多变量申明:func main(){//方式1var n1, n2, n3 intfmt.Println("n1=",n1,"n2=",n2,"n3=",n3)//方式2var n1, name ,ne = 100,"tom",888fmt.Println("n1=",n1,"name=",name,"ne=",ne)}(4) 判断类型:func main(){var num = 10//fmt.Printf()可以做格式化输出fmt.Printf("num 的类型 T%", num)// 如何在程序查看某个变量的占用字节大小和数据类型(使用较多)var n1 int64 = 10// unsafe.Sizeof(n1) 是unsafe包的一个函数 可以返回n1变量占用的字节数fmt.Printf("n1 的类型 T%  n1占用的字节数是 d%",n1, unsafe.Sizeof(n1))}(5) 基本数据类型转换:func main(){// 将i => floatvar i int32 = 100var n1 float32 = float32(i)// %T 查看类型fmt.Printf("n1 type is %T\n",n1)//被转换的是变量存储的数值 变量本身的数据类型是不会改变的fmt.Printf("i type is %T\n",i)//在转换中 如将 int64 转成int8【-128---127】编译时不会报错//只是转换的结果是按溢出处理, 和我们希望的结果不一样 var num1 int64 = 999999var num2 int8 = int8(num1)fmt.Println("num2 =",num2)}
常量*:常量的介绍:a. 常量必须在申明时候初始化b. 常量不能修改c. 常量只能修饰bool,数值类型(int,float系列),string类型d. 常量没有硬性规定常量必须大写字母e. 依旧通过首字母大小写来控制常量的访问范围常量的注意事项:a. 比较简洁的写法:const (a = 1b = 2)fmt.Println(a,b)b. 还有一种专业的写法const (a = iota 	// 0b 			// 1c 			// 2)
字符串*:(1) 改变字符串:func modifystring(){s := "我hello word"s1 := []rune(s)		//字符串强转为数组s1[0] = 'a'str := string(s1)	//数组转为字符串fmt.Println(str)s := "hello";c := []byte(s)		c[0] = 'c's2 := string(c)fmt.Printf("%s\n",s2)a := "hello"ss := "c" + a[1:]	 字符串虽不能更改,但可进行切片操作fmt.Printf("%s\n",ss)}(2) 字符串常用的系统函数1. len()	=>	func len(v type) intexample:len("aaa北")		//返回6 一个汉字占3个字节2. []rune(str):字符串遍历,同时处理有中文的问题 res := []rune(str)	//吧字符串转rune的切片、example:str := "hello北京"r := []rune(str)	for i=0; i<len(r);i++{ fmt.Printf("字符=%c\n",r[i]) }3. strconv.Atoi:字符串转整数:		n,err := strconv.Atoi("12")example:n,err := strconv.Atoi("11")if err != nil {fmt.Println("转换错误:",err)}else{fmt.Println("转换结果是:",n)}4. strconv.Itoa: 整数转字符串:		str := strconv.Itoa(1234)fmt.Printf("str=%v, 类型是 %T",str,str)5. 字符串转[]byte :	 var bytes = []byte("abc")//fmt.Printf("bytes=%v\n",bytes)		//打印出 bytes=[97 98 99]6. []byte 转字符串:	str := string([]byte{97,98,99})fmt.Printf("str=%v\n",str)			//打印出 str=abc7. strconv.FormatInt:1O进制转 2,8,16进制: str := strconv.FormatInt(132,2)	//第一个参数是传入的整数, 第二个参数是要转的进制(2,8,16) 返回字符串8. strings.Contains:查找子串是否在指定字符串中:strings.Contains("abc","b")	//查找在第一个参数中是否存在第二个参数的子串 返回bool型9. strings.Count:统计一个字符串有几个指定的子串:res := strings.Count("aabbbcc","b")	//310. strings.EqualFold:不区分大小写的字符串比较(==是区分字母大小写):strings.EqualFold("abc","ABC")	//true11. strings.Index:返回子串在字符串第一次出现的index值,如果没有返回-1:strings.Index("NLT_abc","abc")	//412.	strings.LastIndex: 返回子串在字符串中最后一次出现的index,没有返回-1strings.LastIndex:("ttt_abc","abc") //13. strings.Replace("go go llo","go","go语言",n) //n可以指定你希望替换几个,如果n=-1表示全部替换14. strings.Split("hello,word,ok",",")		//按照指定的某个字符,为分割标识,将一个字符串分割返回一个切片15. 字符串大小写转换:strings.ToLower("Go")	//gostrings.ToUpper("Go") 	//GO16.	a.将字符串左右两边的空格去掉:	strinngs.TrimSpace("yyy hee ")res := strings.TrimSpace("yyy hhh   ")  fmt.Printf("res=%q\n",res)	//%q 字符串格式 双引号格式b.将字符串左右两边的指定字符去掉 : strings.Trim("!hello!"," !")	//[hello] 去掉了左右两边的!和" "c.将字符串左边指定字符去掉: strings.TrimLeft("!hello!","!")17 a.判断字符串以指定字符串开头:  strings.HasPrefix("ftp://192.128.1.1","ftp")	//trueb.判断字符串以指定	字符串结束: strings.HasSuffix("abc.jpg","abc")	//false指针*:(1)普通类型,变量存的就是值,也叫值类型(2) 获取变量地址 用&, 比如: var a int , 获取a的地址:&a(3) 指针类型 (*int), 变量存的是一个地址 这个地址存的才是值(4) 获取指针类型所指向的值,使用: *, 比如: var *p int, 使用*p 获取P指向的值func main(){var a int = 5var p *int =&afmt.Println(p)	//地址:0x12454078fmt.Println(*p)	//指针所指向的值: 5}
时间*日期*函数:(1) 时间和日期函数,需要导入time包 	  import "time"a. 格式化的第一种方式:now := time.Now() 		//返回当前时间戳fmt.Printf("当前年月日时分秒:%d-%d-%d %d:%d:%d\n",now.Year(),now.Month(),now.Day(),now.Hour(),now.Minute(),now.Sencond())b. 格式化的第二种方式fmt.Printf(now.Format("2006/01/02 15:04:05"))fmt.Printf(now.Format("2006-01-02"))	//返回当前的年月日fmt.Printf(now.Format("15:04:05"))		//返回当前的时分秒fmt.Printf(now.Format("15"))			//返回当前的小时数值* "2006/01/02 15:04:05" 这个字符串的各个数字是固定的,必须是这样写(2) 时间常量:const(Nanosecond  Duration = 1					//纳秒Microsecond          = 1000 * Nanosecond	//微妙Millisecond          = 1000 * Microsecond	//毫秒Second               = 1000 * Millisecond	//秒Minute               = 60 * Sencond			//分钟Hour                 = 60 * Minute			//小时)常量的作用:在程序中可用于获取指定时间单位的时间,比如像得到100毫秒 100*time.Microsecond
日志*: 	import "log"log.SetFlags(log.Lshortfile| log.Ltime | log.Ldate)var arr [3]int = [3]int{1:100}log.Println(arr)
流程控制*:(1) 从终端出入字符串 转为整形 否则输出错误信息func main(){var str string fmt.Scanf("%s",&str)number,err := strconv.Atoi(str)if err != nil {fmt.Println("convert faild,err:",err)return}fmt.Println(number)}	(2)Switch:		//没有breakfunc main(){a := 10switch a {case 0,1,2,3,4,5:	//满足其中一个都走此分支fmt.Println("a is equal 0")fallthrough		//继续走下一个分支case 10:fmt.Println("a is equal 10")default:fmt.Println("a is equal default")		}}//猜字游戏:import("fmt" "math/rand")func main(){n := rand.Intn(100)for{var input intfmt.Scanf("%d\n",&input)	///Scanf 可变需传入地址flag := falseswitch  {case input == n:fmt.Println("you are ok")flag = truecase input > n:fmt.Println("you are bigger")case input < n:fmt.Println("you are less")}if flag { break }}}	(3) For 循环:a. 常规用法:func Printa(n int){for i := 0; i <=n; i++ {for j := 0; j < i; j++ {fmt.Printf("A")		//Printf 格式化打印A (横排)}	fmt.Println()	//换行}}b. For range 语句:str := "hello world 中国"	for i, v := range str {	//i=>key   v=>valuefmt.Printf("index[%d] val[%c] len[%d]\n",i,v,len([]byte(v)))	}c. For true语句(死循环):for true { fmt.Println(i) }	for { fmt.Println(i) }	// 条件true 可省略(等效于上面)d. goto 和 label语句func main(){LABEL:HERE:for i := 0; i < 6; i++ {if i ==2 {continue LABEL 		// 当满足条件后 跳到循环外部的LABEL 位置 作用域是一个函数内	goto HERE			// 当满足条件后 跳到循环外部的HERE 位置 作用域是一个函数内}}}
包*:go的每一个文件都属于一个包,也就是说Go是以包的形式来管理文件和项目目录结构的(1) 包的三大作用:a. 区分相同名字的函数,变量等标识符b. 当程序文件过多时,可以很好的管理项目c. 控制函数 变量等访问范围,即作用域打包基本语法:	package 包名引入包语法:		import "包名"(2) 包的注意事项:a. 在给一个文件打包时 该包对应一个文件夹.比如utils文件夹对应的包名就是utils,文件的包名通常和文件所在的文件夹名一致,一般为小写字母b. 当一个文件要使用其他包的函数或变量时,需要引入对应的包c. package在文件第一行,然后是import指令d. 在inport包时,路径从$GOPATH的src下开始,不用带src,编译器会自动从src下开始引入e. 为了让其他包的文件,可以访问到本包的函数,则改函数的首字母必须大写,类似其他语言的Public,这样才能跨包访问f. 在访问其他包函数时,其语法是: 包名.函数名. 比如:Utils.Cal()g. 如果包名过长,Go支持给包取别名. 注意细节:取别名后,原来的包名就不能用了h. *如果需要编译成一个可执行的文件,就需要将这个包声明为main,即package main. 这就是语法规范.如果写一个库文件,包名可以自定义i. *项目目录结构在$GOPATH的src下. 编译需进入在main包的目录下,编译*指令:比如:E:\code\Go> go build -o bin/my.exe go_code/day2/code/demo/main //在GOPATH目录下生成一个存编译文件的目录(3) 调用外部包和函数package mainimport("fmt""go_code/chaptes/function1/utils"		//引入包路径 (GOPATG的src下路径开始不包含src)//ut "go_code/chaptes/function1/utils"	//取别名)return := utils.Cal(n1,n2,operator)			//调用外部函数 需导入包名和函数名(函数名需首字母大写->该函数可导出)
函数*:(1) 函数注意事项和细节:a. 函数的形参列表可以是多个,返回值列表页可以是多个b. 形参列表和返回值列表的数据类型可以是值类型也可以是引用类型c. 函数的命名遵循标识符命名规范,首字母不能是数字,首字母是大写的函数可以被本包文件和其它包文件使用,类似Public,首字母小写只能被本包文件使用,类型privated. 函数中的变量是局部的,函数外部生效e. 基本数据类型和数组默认是值传递的,即进行值拷贝. 在函数内修改,不会影响到原来的值f. 如何希望函数内的变量能改变函数外的变量,可以传递变量的地址&,函数内以指针的方式操作变量g. Go函数不支持重载(2) 函数在内存分布说明:a. 在调用一个函数时,会给函数分配一个新的空间(栈区),编译器会自身处理让这个新的空间和其它的栈的空间区分b. 在每个函数对应的栈中,数据空间是独立的,不会混淆c. 当一个函数调用完毕(执行完毕)后,程序会销毁这个函数的栈空间1) 函数也是一种数据类型 可以赋值给一个变量,改变量就是一个函数类型的变量,通过该变量可以对函数调用func add(a,b int) int { return a+b }	func main(){c := add			// add函数赋值变量csum := c(100,200)fmt.Println(sum)}2) 函数既是一种数据类型,因此在go中函数可以做为形参,并且调用:基本语法: type 自定义数据类型 数据类型	//理解:相当于一个别名案列: type myint int 				//这时myint等价于int来使用案列: type mySum func(int,int)int 	//这时mySum等价于一个函数类型type op_func func(int,int) int		//自定义类型 2个参数 1个返回值[函数也是一种类型] 相当于一个别名func add(a,b int) int {	return a+b }func operator(op op_func, a, b int) int {	//op 变量名,类型是上面自定义的op_func 参数是上面定义的两个int型return op(a,b)							//上面 op_func 类型 接受的2个参数}func main(){c := add								//add 函数赋值给变量csum := operator(c,100,200)				// c函数当参数引用fmt.Println(sum)}3)*函数传参的方法: 值传递和引用传递注意:a. 无论是值传递还是引用传递 传递给函数的都是变量的副本 不过,值传递是值得拷贝。引用传递是地址的拷贝。一般来说,地址拷贝更为高效,因为数据量小,而值拷贝取决于拷贝的对象大小,对象越大,则性能越低。b. *值类型: 基本数据类型 int系列,float系列,bool,string,数组和结构体struct b. *引用类型:map,slice切片,管道chan,指针,interface,默认都是引用的方式传递 4) 函数返回值命名:func cale(a,b int) (sum int, avg int) {sum = a+bavg = (a+b)/2return}5) _标识符 , 用来忽略返回值func main(){ sum,_ := cale(100,200) }6) 支持可变参数:(可变参数要放在形参列表最后)func add(a int, arg... int) int {}	//1个或多个参数//注意: 其中 arg是一个slice 我们可以通过arg[index]依次访问所有参数,通过len(arg)来判断传递参数的个数		7) defer* :(1) 在函数中,程序员经常需要创建资源(比如数据库连接,文件句柄,锁等),为了在函数执行完毕后及时释放资源,Go的设计者提供了defer(延时机制)(2) 当函数执行defer时,暂时不会执行,会将defer后面的语句压入到独立的栈(defer栈),当函数执行完毕后,再从栈按先入后出的方式出栈 a. 当函数返回时 执行defer语句. 因此,可以用来做资源清理b. 多个defer语句 按先进后出的方式执行c. defer语句中的变量,在defer声明时就决定了example1:func a(){i := 0defer fmt.Println(i)	//当函数返回结果之后才执行(定义一个压入一个栈里[先进后出])i= 10fmt.Println(i)}(3) 8) 匿名函数*:a. 如果某个函数只是希望使用一次,可以考虑使用匿名函数,匿名函数也可以实现多次调用func test(a,b int) int {result := func(a1,b1 int) int {	return a1+b1 }(a,b)		//(a,b)体现了调用return result}b. 将匿名函数赋给一个变量(函数变量),再通过该变量来调用匿名函数a := func(n1 int, n2 int) int { return n1-n2 }res := a(10,20)c. 全局匿名函数的使用var (	//申明Fun1 = func (n1 int,n2 int) int { return n1*n2 })res := Fun1(2,5)	//调用全局匿名函数9) init 函数 a. 如果一个文件同时包含全局变量定义,init函数和main函数,则执行流程顺序是: 变量定义->init函数->main函数b. init函数主要作用是完成一些初始化的工作内置函数*:(1) close:用来关闭channel(2) len:用来求长度, 比如string,array,slice,map,channel(3) new*: 用来分配内存,主要用来分配值类型,比如int,struct,返回的是指针j := new(int) 	fmt.Printf("j的类型%T, j的值=%v,j的地址%v,j这个指针指向的值=%v\n",j,j,&j,*j)	//j的类型*int, j的值=0xc000062090,j的地址0xc00008c018,j这个指针指向的值=0*j = 100	fmt.Println(*j)	//地址内的值 100(4) make*: 用来分配内存,主要用来分配引用类型,比如chan,map,slice,返回slice本身s2 := make([]int,5)fmt.Println(s2)			// [0 0 0 0 0](5) append: 用来追加元素到数组,slice中var a []int	// [5] --> 数组  [] -->切片slicea = append(a,10,20,33)a = append(a,a...)	//a... 相当于 展开上面的数组[10,20,33]fmt.Println(a)(6) panic和recover: 用来做错误处理func test(){defer func(){							//defer return 结束后调用err := recover(); 					//recover() 内置函数,可以捕获异常if err != nil {						//nil 表示没有错误fmt.Println("err=",err)		fmt.Println("发送邮箱给admin@163.com")}}()										//申明匿名函数并调用b := 0a := 100/bfmt.Println(a)return}func main(){for {test()time.Sleep(time.Second)}}
递归函数*: =一个函数在体内调用了本身,则称为递归调用(1) 递归函数的重要原则:a. 执行一个函数时,就会创建一个新的受保护的独立空间b. 函数的局部变量是独立的,不会相互影响c. 递归必须向退出递归的条件逼近,否则就是无限递归func test(n int){fmt.Println("hello word")time.Sleep(time.Second)if n>10 {	//出口条件 跳出循环return }test(n+1)	}
闭包*: 一个函数和与其相关的引用环境组合而成的实体//累加器//a.返回的是一个匿名函数,但是这个匿名函数引用到函数外的x,因此这个匿名函数和x形成一个整体,构成闭包//b.可以这样理解:闭包是类,匿名函数是操作(方法),x是字段(属性)//c.我们反复调用f函数时,因为a是初始化一次,故每次调用一次就进行累计//d.我们要搞清楚闭包的关键,就是分析出返回函数它使用(引用)到哪些变量,因为函数和它引用到的变量共同构成闭包func Adder() func(int) int {	//返回值是一个函数类型var x int 					//定义局部变量xreturn func(d int) int {	//返回的闭包函数 和上面的Adder函数返回函数一一对应x += d 					//外部引用变量x会保留上次的值return x }}func main(){var f := Adder()fmt.Print(f(1),"--")fmt.Print(f(20),"--")}//输入一个文件名,有后缀名就返回原文件名,没有后缀则返回带后缀名的文件名func makeSuffix(suffix string ) func (string) string {return func (name string) string {if !strings.HasSuffix(name,suffix) {return name+suffix}return name}}f := makeSuffix()fmt.Print(f("aa.jpg"),"--")
数组*:(1) 数组内存中分配:a. 数组的地址可以通过数组名来获取&Arr b. 数组的第一个元素的地址就是数组的首地址  c. 数组的各个元素的地址间隔是依据数组类型所占字节数递增而列 比如int 8字节 int32 4字节(2) 数组使用注意事项和细节(重要):1. 使用数组步骤:1 申明数组并开辟空间 2 给数组各个元素赋值 3 使用数组1. 数组是多个相同类型数据的组合,一个数组一旦申明/定义了 其长度是固定的,不能动态变化2. var arr[]int 这时arr 是一个slice切片 3. 数组中的元素可以是任意数据类型,包括值类型和引用类型,但是不能混用4. 创建数组后 如果没有赋值 则有默认值 数组类型默认值0,字符串数组默认值"", bool数组默认值false5. 数组属于值类型,默认是值传递,因此会进行值拷贝.(数组间不会相互影响)(3) 数组定义:   数组是值类型a. var a[len] int  比如:var a[5] int   * 一旦定义,长度不能变(默认值是0)b. 长度是数组类型的一部分,因此 var a[6] int 和 var a[10] int 是不同的类型c. 数组可以通过下标进行访问,下标从0开始,最后一个元素下标是: len-1for i := 0; i < len(a); i++ {}for index,value := range a {}1. index是数组的下标,value是该下标位置的值2. 它们都仅是在for循环内部可见的局部变量3. 遍历数组元素的时候,如果不想使用下标index,可以直接把下标index标为下划线_d. 访问越界,如果下标在数组合法范围外,则会触发访问越界,会panice. 数组是值的类型,因此改变副本的值,不会改变本身的值var a [5] intb := a 				//b 是 a 的副本b[0] = 100			//改变数组b的值fmt.Println(a)		//a的值不变 [0 0 0 0 0]fmt.Println(b)		//b的值会变 [100 0 0 0 0](4) 初始化:a. var arr1 [5]int = [5]int{1,2,3}b. var arr2 = [5]int{1,2,3,4,5}c. var arr3 = [...]int{1,2,3,4,5,6}			//[...] 让系统自己判断数组的列个数d. var str = [5]string{3:"hello",4:"tom"}	// 可以指定元素数组的下表来初始化数组的值 第4个元素值是hello(5) 多维数组:a. var age [5][3]intb. var f [2][3]int = [...][3]int{{1,2,3},{7,8,9}}	//2个子数组,每个子数组有3个值
切片*:   (1) 切片是数组的一个引用,因此切片是引用类型(2) 切片的长度可以改变,因此切片是一个可变的数组(3) 切片遍历方式和数组一样,长度用len()求长度(4) cap可以求出slice的最大容量, 0<=len(slice)<=cap(array) 其中array是slice引用的数组(4.1) 从slice底层来说,其实切片就是一个数据结构(struct结构体)a. 切边内存包含3个空间 引用到数据的那个下标的指针,len , cap(5) 切片的定义: var 变量名 []类型, 比如:  var str []string  var arr []int*切片使用的三种方式:1. 定义一个切片,然后让切片去引用一个创建好的数组var arr[5]int = [...]int{1,2,3,4,5}	var slice []intslice = arr[2:3]		//slice是切片名  arr[2:3] 表示slice引用到arr这个数组 从下标2到3结束但不包含3fmt.Printf("切片的地址%p\n",slice)			//输出:切片的地址0x120100bcfmt.Printf("数组第三个元素地址%p\n",&a[2])	//输出:数组第三个元素地址0x120100bc		2. 通过make来创建切片var slice []int = make([]int,4,10)			//make来创建一个切片	(slice := make([]type,len,cap)).fmt.Println(slice)				//默认值是0 fmt.Printf("len=%v cap=%v 地址=%p",len(slice),cap(slice),&slice) //len=4 cap=10 地址=0xc00005a2c0slice[2] = 200总结:	a. 通过make方式创建切片可以指定切片的大小和容量b. 如果没有给切片赋值,那么就胡使用默认值[int float =>0 string=>"" bool=>false]c. 通过make方式创建的切片对应的数组是由make底层维护,对外不可见,即只能通过slice去访问各个元素3. 定义一个切片,直接就指定具体数组,使用原理类型make的方式var slice []int = []int {1,3,5}fmt.Println(slice)		(6) 切片扩容(追加)用append内置函数,可以对切片进行动态追加1. 通过append 直接给slice追加具体元素var slice []int = []int{100,200}slice = append(slice,300,400)fmt.Println(slice)	//[100,200,300,400]2. 通过append将切片slice追加给sliceslice = append(slice,slice...)	//固定写法切片append操作的底层原理分析: 	1. 切片append操作本质就是对数组扩容2. go底层会创建一个新的数组newArr(扩容后的大小)3. 将slice原来包含的元素拷贝到新的数组newArr4. slice重新引用到newArr5. 注意newArr 是在底层维护的,程序员不可见(7) 切片拷贝var a[]int  = []int{1,2,3,4,5}	b := make([]int, 10)		//make 一个切片copy(b,a)fmt.Println(a)				//[1 2 3 4 5]fmt.Println(b)				//[1 2 3 4 5 0 0 0 0 0]说明: copy(para1,para2)	:para1和para2都是切片类型(8)string与切片str := "hello go"s1 := str[0:5]s2 := str[5:]排序*:(1) 引入包 import "sort"(2) 数组排序:a.func arrSort(){var arr = [...]int{1,33,5,99,2}sort.Ints(a[:])			//因为数组是值类型 所以要传入切片 fmt.Println(a)}b.  var s = [...]string{"as","eee","rrt"}	sort.Strings(s[:])		//对字符串进行排序c.  var f = [...]float64{0.8,0.2,99,8.2}sort.Float64s(f[:])		//对浮点数进行排序(3) search 检索s := [...]int{3,1,6,5}sort.Ints(s[:])						//检索key前先要排序 不然search会不准index := sort.SearchInts(s[:],3)	//返回值3的key
map*: key-value的数据结构,又叫字典或关联数组(1) 注意: slice,map,还有function不可以用做key, 因为这几个没法用==判断 (1) 声明:	(申明是不会分配内存的 初始化需要make)var a map[string]string 					//key是字符串value是字符串var a map[string]int 						//key是字符串value是intvar a map[string]map[string]string 			//key是字符串value是map注意:map申明是不会分配内存的,初始化需要make,分配内存后才能赋值和使用总结:	a. map在使用前一定要makeb. map的key是不能重复的.map的value可以重复c. map的key-value是无序的(2) map的使用:1. 方式1: var a map[string]stringa = make(map[string]string,10)a["one"] = "张三"a["two"] = "李四"2. 方式2:m := make(map[string]string,10)m["no1"] = "北京"m["no2"] = "上海"3. 方式3:a := map[string]string{	//申明初始化赋值"a":"qq","b":"ww",}(3) map的增删改查a. map的增加和更新map["key"] = value 	//如果key不存在,就是新增,如果key不存在就是修改b. map的删除m := make(map[string]string,10)m["no1"] = "北京"m["no2"] = "上海"delete(m,"no1")	//删除key是no2的值 delete是内置函数 当指定key不存在,删除操作也不会报错1) 如果希望一次性删除所有的keya. 遍历所有的key,逐一删除*map遍历只能用for-range的结构遍历for k,v := range m {fmt.Printf("k =%v v=%v\n",k,v)}b. 直接make一个新的空间citis = make(map[string]string)fmt.Println(citis)c. map的查找val, ok := m["no1"]		//val 为map对应key的value , ok 为查找返回结果bool类型if ok { fmt.Printf("有no1的key 值为%v\n",val) }(4) map切片		[]map基本介绍: 切片数据类型如果是map,则我们称为slice of map切片,这样使用则map个数就可以动态变化了案例:var mons []map[string]string 			//申明一个map切片mons = make([]map[string]string,2)		//切片也要make分配内存// 给map切片赋值2个if mons[0] == nil {mons[0] = make(map[string]string,2)	//map赋值钱需要makemons[0]["name"] = "张三"mons[0]["age"] = "20"}if mons[1] == nil {mons[1] = make(map[string]string,2)mons[1]["name"] = "李四"mons[1]["age"] = "24"}// 定义一个新的mapnewMons := map[string]string{ "name" = "王武","age" = "30",}mons = append(mons,newMons)fmt.Println(mons)(5) map排序:a. golang中的map默认是无序的,每次遍历得到的输出可能不一样b. golang中map的排序,是现将key进行排序,然后根据key值遍历输出即可1. 现将map的key放入到切片中,再对切片排序,遍历切片,然后再按key输出mapmap1 := map[int]int{10:100,3:44,7:77,}var keys []intfor k,_  := range map1{keys = append(keys,k)}sort.Ints(keys)for _,k := range keys{fmt.Sprintf("map1[%v]=%v\n",k,map1[k])}(6)	map的使用细节:a. map是引用类型,遵循引用类型的机制,在一个函数接收map,修改后会直接修改原来的mapb. map的容量达到后,想要map增加元素,会自动扩容,并不会发送panic,也就是说map能自动增长键值对(key-value)c. map的value也经常使用struct类型,更适用于管理复杂的数据
锁*sync*:https://blog.zxysilent.com/post/goweb-03-6.html(1)线程同步 (sync 同步包)a. 互斥锁, var mu sync.Mutexb. 读写锁, var mu sync.RWMuteximport ("fmt";"sync";"time";)var wg sync.WaitGroup	//WaitGroup 可以用来等待协程执行完成func fn1(){ time.Sleep(time.Second*1);fmt.Println("sleep 1 s");wg.Done()	 // 执行完成就关闭一个等待}func fn2(){ time.Sleep(time.Second*1);fmt.Println("sleep 2 s");wg.Done()	 // 执行完成就关闭一个等待}func main(){begin := time.Now()	//开始时间for i:=0;i<5 ;i++  {go fn1()wg.Add(1)	// 每次启动一个协程就添加一个进入同步组go fn2()wg.Add(1)}wg.Wait()	//等待所有子协程执行完成后才继续执行下面代码end := time.Now()fmt.Println("总共用时:",end.Sub(begin))}	(2)go get 安装第三方包go get github://。。。。
结构体*:(和 java/PHP 语言的对象class 类似)(1) 结构体特点:a. 去掉了传统oop语言的继承,方法重载,构造函数和析构函数,隐藏的this指针等等a. 用来定义复杂数据结构b. struct里面可以包含多个字段c. struct类型可以定义方法,注意和函数的区分d. struct类型是值类型,也可以嵌套e. Go语言没有class类型,只有struct类型(2) 结构体的内存分配机制:a. 变量总数分配在内存中的(3) 结构体定义:a.  结构体申明:type 结构体名称(自定义) struct {field1 typefield2 type}例如: type Student struct{		// 结构体名称如果首字母大写则在其他包和本包使用Name string 		// 属性首字母大写也一样可以在其他包使用 (public)Age int 			// 属性一般是基本数据类型,数组,也可以是引用类型}b. 创建结构体变量和访问结构体字段1) 	var stu Student2) 	var stu1 Student = Student{"张三",20}stu2 := Student{"张三",20}2) 	var stu3 *Student = new (Student)	//申明//因为stu3是一个指针,因此标准的字段赋值的方式是:(*stu3).Name = "小王"		//赋值   [标准的写法](*stu3).Age = 30			//(*stu3).Age = 30 等价于 stu3.Age = 30	原因: go的设计者为了使用方法在底层对stu3.Age = 30				// stu3.Age = 30进行处理 会对stu3加上取值运算(*stu3).Age = 30 5) 	var stu5 *Student = &Student{"mary",45}	//申明并给属性赋值4) 	var stu4 *Student = &Student{}// stu4是一个指针(*stu4).Age = 11		//标准写法stu4.Name = "tom"		//简单写法*stu4.Age = 11 			//会报错! 原因是.的运算优先级会高于*c. 结构体初始化:1) 		var stu Studentstu.Name = "jack"stu.Age = 202)		var stu2 = Student{Age:20,Name:"jack",}fmt.Printf("Name:%p\n",&stu2.Name)			//打印Name的地址d. 结构体的使用注意事项和细节:1. 结构体type重新定义(相当于取别名),golang认为是新的数据类型,但是相互间可以强转type integer initfunc main(){var i integer =10var j int =20//j=i //会报错j = integer(i)}2. 在结构体的每个字段上,可以写一个tag,该tag可以通过反射机制获取,常见的使用场景是序列化和反序列化// 序列化 是返回一个字符串json到客户端 type Monster struct{			//申明一个结构体 Name string `json:"name"`	//`json:"name"` 就是结构体标签 struct tag 作用在反射结果json后可以使Name小写成nameAge int	`json:"age"`Skill string `json:"skill"`}func main(){// 1 创建一个monster变量monster := Monster{"牛魔王",200,"芭蕉扇"}//2 将moster变量序列化为一个josn字符串jsonStr,err := json.Marshal(monster)		//json.Marshal 函数中用到反射if err != nil{fmt.Println("json 错误处理,",err)}fmt.Println("jsonStr:",string(jsonStr))}
方法*:(1) 基本介绍:Golang 中的方法是作用在指定的数据结构上的(即:和指定的数据类型绑定),因此自定义类型都可以有方法,而不仅仅是struct(2) 方法的申明和调用type Person struct{ Name string }//给Person这个结构体绑定一个test方法func (p Person) test(){		//表示Person这个结构体有一个方法,方法名是test (类型php里的class类里有一个方法)fmt.Println(a.Num)}func main(){var p Person 		//申明一个结构体变量p.Name = "tom"		//给这个结构体变量赋值p.test()			//调用这个结构体里绑定的一个方法}说明:	a. 	func (p Person) test(){	//表示A这个结构体有一方法,方法名是testb.	(p Person) 				//体现 test方法是A类型绑定的	,表示哪个Person变量调用,这个p就是它的副本(类型函数传参)c.  p.test()				//不能直接test()调用和用其他类型结构体调用,只能本所绑定的结构体来调用d. p是可以自定义的(3) *方法的调用和传参机制原理:a. 通过一个变量取调用方法时,其调用机制和函数一样b. 不一样的是 变量调用方法时, 该变量本身也会作为一个参数传递到方法(如果变量是值类型 则是值拷贝,如果变量是引用类型 则进行地址拷贝)(4) 方法注意事项和细节:a. 结构体类型是值类型,在方法调用中,遵循值类型的传递机制,是只拷贝传递方式b. 如果想在方法中修改结构体变量的值,可以通过结构体指针的方式来处理  type Circle struct{ radius float64 }func (c *Circle) area() float64 {	// 为了提高效率 通常结构体方法定义是指针类型 c.radius = 10.0					//c.radius 等价于 (*c).radiusreturn 3.14 * c.radius * c.radius}func main(){var c Circlec.radius = 7.0res := c.area()			fmt.Println(c.radius)	//10fmt.Println(res)		}c. Golang中的方法作用在指定的数据类型上(即:和指定的数据类型绑定),因此自定义类型都可以有方法,而不仅仅是struct,比如int,float64等都可以有type integer intfunc (i *integer) printt() {*i = *i + 1}func main(){var i integer = 55i.printt()fmt.Println(i)		//56	}d. 方法的访问范围控制规则和函数一样,方法名首字母小写,只能在本包访问,方法名首字母大写,可以在本包和其他包访问e. 如果一个类型实现了String()这个方法,那么fmt.Println默认会调用这个变量的String()进行输出type Student struct {Name stringAge int}func (stu *Student) string() string {str := fmt.Sprintf("Name=[%v] Age=[%v]",stu.Name,stu.Age)return str}func main(){stu := Student{Name : "TOM",Age : 22,}fmt.Println(&stu)}
面向对象*:(1) 面向对象编程实例步骤:	申明定义结构体 编写结构体字段 编写结构体方法type Student struct{name string gender stringage intid intscore float64}func (s *Student) say() string {inforStr := fmt.Sprintf("student的信息:name=[%v],gender=[%v],age=[%v],id=[%v],score=[%v]",s.name,s.gender,s.age,s.id,s.score)return inforStr}func main(){//创建一个实例var stu = Student{name : "tom",gender : "male",age : 18,id : 1000,score : 99.0,}str := stu.say()fmt.Println(str)}(2) 工厂模式:说明:Golang的结构体没有构造函数,通常可以使用工厂模式解决这个问题看一个需求:package modeltype Student struct{Name string ...}详解:	因为这里的Student的首字母S是大写的,如果我们想在其他包创建Student的实例(比如main包),引入model包后,就可以直接创建Student结构体的实例。但是如果首字母是小写,比如是type student struct{...}就不行了 怎么办--->工厂模式来解决。例子:student.go:package model//定义一个结构titype student struct{	//student首字母小写 外部包不能直接访问Name string 		//首字母大写 外部包可以直接访问score float64 		//首字母小写 外部包不能直接访问}//定义一个工厂可外部访问的函数func NewStudent(n string, s float64) *student {return &student{Name : n,score : s,}}//如果score字段首字母小写,则在其他包不可以直接访问,我们可以提供一个方法在其他包访问小写字母的字段func (s *student) GetScore() float64 {return s.score}main.go :package mainimport "go_code/day6/code/factory/model"func main(){//定义的student结构体是首字母小写,我们可以通过工厂模式解决var stu = model.NewStudent("tom~~",88.8)fmt.Println("Name=",stu.Name , "score=",stu.GetScore())}(3) 面向对象的三大特性:说明:Golang语言也又面向对象的继承,封装,多态的特性,只是实现方式和其他oop语言不一样如何理解抽象:定义一个结构体的时候,实际上是把一类事物的共有的属性和方法提出出来,形成一个屋里模型(结构体),这种研究问题的方法称为抽象a.封装:就是吧抽象出来的字段和对字段的操作封装在一起,数据被保护在内部,程序的其他包只能通过被授权的方法才能对字段进行操作封装的实现步骤:1. 将结构体,字段的首字母小写(其他包不能访问,类似private)2. 将结构体所在包提供一个工厂模式的函数,首字母大写,类似一个构造函数3. 提供一个首字母大写的Set方法(类似其他语言的Public),用于对属性赋值func (var 结构体类型名) SetXxx(参数列表) (返回值列表) {//数据验证逻辑var 字段 = 参数}4. 提供一个首字母大写的GetXxx方法(类似public) 用于获取属性的值func (s *student) GetScore() float64 {return s.score}b. 继承:在Golang中,如果一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性基本语法:type Goods struct{Name string Price int}type Book struct{	Goods			//这里嵌套匿名结构体Goods 相当于继承了Goods结构体的属性和方法Writer string }继承的深入讨论:1) 结构体可以使用嵌套结构体所有的字段和方法. 即:首字母大写或者小写的字段,方法,都可以使用type A struct{Name stringage int}func (a *A) SayOk(){	//A 结构体的方法	SayOKfmt.Println("A SayOk",a.Name)}func (a *A) hello(){	//A 结构体的方法	hello	fmt.Println("A hello",a.Name)}type B struct{			//相当于B 是 A 的子类A 			}func main(){var b B b.A.Name = "tom"b.A.age = 22b.A.SayOk()		//A SayOk tom 	// 简化: b.SayOk()b.A.hello()		//A hello tom   //小写的方法也可以调用}2) 匿名结构体访问可以简化:var b B b.a.name = "tom"	===>	b.name= "tom"b.a.say()			===>	b.say()3) 当结构体和嵌套的匿名结构体有相同的字段或方法时,编译器采用就近访问原则,如果希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分 即:b.A.hello() 明确是A结构体的方法 b.hello() 则优先访问B结构体的hello方法4) 结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段或方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错	type A struct{Name string Age int}type B struct{Name stringscore int}type C struct{AB// Name string}func main(){var c C c.Name = "tom" 		//报错 必须 c.A.Name  或 c.B.Name }5) 如果一个结构体嵌套了一个有名结构体,这种模式就是组合。如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体名字type A struct{Name string}type C struct{a A 	//相当于给嵌套的A 结构体取了一个别名}func main(){var c Cc.a.Name = "jack"	//访问有名结构体字段时 必须带上有名结构体的名字}6)嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值type Good struct{Name string Price float64}type Brand struct{Name stringAddress string}type TV struct{GoodsBrand}type TV2 struct{*Goods 				//也可以嵌套指针类型*Brand}func main(){//嵌套匿名结构体后,也可在创建结构体实例时,直接指定各个匿名结构体字段的值tv := TV{ Goods{"电视机01",4999.00},Brand{"海尔","山东"}, }tv2 := TV{ Goods{Name : "电视机02",Price : 5999.00,},Brand{Name : "夏普",Address : "北京",}, }tv3 := TV2{ &Goods{"电视机03",6999.00}, &Brand{"创维","湖北"}, }	//指针类型用&fmt.Println(tv)fmt.Println(tv2)fmt.Println("tv3",*tv3.Goods, *tv3.Brand)}
接口*(interface):  多态性主要通过接口来体现基本介绍:	interface类型可以定义一组方法,但这些方法不需要实现.且interface不能包含任何变量。到某个自定义类型(比如结构体Phone)要使用的时候,根据具体情况把这些方法实现基本语法:type 接口名 interface{method1(参数列表) 返回值列表method2(参数列表) 返回值列表}小结说明:1)	接口里的所有方法都没有方法体,即接口的方法都是没有实现的方法。接口体现了程序设计的多态和高内聚低耦合的思想2)	Golang中的接口,不需要显式的实现。只要一个变量,含有接口类型的所有方法,那么这个变量就实现了这个接口。因此Golang中没有implement这样的关键字注意事项和细节:1)	接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的实例type Usb interface{ Start();Stop(); }type Phone struct{}type Camera struct{}func (p Phone) Start(){fmt.Println("手机开始工作..")}func (p Phone) Stop(){fmt.Println("手机停止工作..")}func (p Camera) Start(){fmt.Println("相机开始工作..")}func (p Camera) Stop(){fmt.Println("相机停止工作..")}type Computer struct{}//编写一个work方法,接受一个Usb接口类型变量 实现了Usb接口func (c Computer) Working(usb Usb){//通过usb这个接口变量来调用Start和Stop方法usb.Start()usb.Stop()}func main(){//先创建结构体实例computer := Computer{}phone := Phone{}camera := Camera{}computer.Working(phone)	computer.Working(camera)}2)	接口中所有的方法都没有方法体,即都是没有实现的方法 3)	在Golang中,一个自定义类型需要将某个接口的所有方法都实现,我们说这个自定义类型实现了该接口4)	只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型5)	一个自定义类型可以实现多个接口type AInterface interface{ Say() }type BInterface interface{ Hello() }type Monster struct{}		//Monster 结构体实现了AInterface和BInterfacefunc (m Monster) Say(){ fmt.Println("Monster Hello()") }func (m Monster) Hello(){ fmt.Println("Monster Hello()") }func main(){var monster Monstervar a2 AInterface = monstervar b2 BInterface = monstera2.Say()b2.Hello()}6)	Golang接口中不能有任何变量7)	一个接口(比如A接口)可以继承多个别的接口(比如B,C接口),这时如果要实现A接口,也必须将B,C接口的方法也全部实现type BInterface interface{ test01() }type CInterface interface{ test02() }type AInterface interface{	//AInterface 继承了 BInterface 和 CInterfaceBInterfaceCInterfacetest03()}//如果需要实现AInterface,就需要将BInterface和CInterface的方法都实现type Stu struct{ }//必须要实现所有方法func (stu Stu) test01(){}func (stu Stu) test02(){}func (stu Stu) test03(){}func main(){var stu Stuvar a AInterface = stuvar b BInterface = stuvar c CInterface = stua.test01()	//a可以调 test01 test02 test03 三个方法b.test01()	//b只能调 本身的test01方法c.test02()	//c只能调 本身的test02方法}8)	interface类型默认是一个指针(引用类型),如果没有interface初始化就使用,那么会输出nil9)	空接口interface{} 没有任何方法,所以所有类型都实现了空接口,即我们可以把任何一个变量赋给空接口接口编程的最佳实践:import ("fmt""sort""math/rand")//1申明Hero结构体type Hero struct{Name stringAge int}//2申明Hero结构体切片类型type HeroSlice []Hero//3 实现Interface 接口func (hs HeroSlice) Len() int { return len(hs) }// Less 方法决定使用什么标准进行排序func (hs HeroSlice) Less(i,j int) bool {return hs[i].Age < hs[j].Age//也可以改为对Name排序//return hs[i].Name < hs[j].Name}func (hs HeroSlice) Swap(i,j int)  { hs[i], hs[j] = hs[j], hs[i] } 	//交换func main(){var intSlice = []int{0,-1,22,7,99,66}sort.Ints(intSlice)		//对切片进行排序fmt.Println(intSlice)//对结构体切片排序var heroes HeroSlicefor i:=0;i<10;i++{hero := Hero{Name : fmt.Sprintf("英雄|%d", rand.Intn(100)),Age : rand.Intn(100),}//将hero append 到heroes切片heroes = append(heroes,hero)}//看看排序前的顺序for _,v := range heroes {fmt.Println(v)}//调用sort.Sortsort.Sort(heroes)fmt.Println("排序后~~~~~~~")//看看排序后的顺序for _,v := range heroes {fmt.Println(v)}}实现接口VS 继承:继承的价值主要在于:解决代码的复用性和可维护性接口的价值主要在于:设计好各种规范,让其他自定义类型取实现这些方法接口比继承更灵活接口比继承更加灵活,继承是满足 is - a 的关系, 而接口只需满足like - a 的关系接口在一定程度上实现代码解耦多态:基本介绍:实例具有多种形态,面向对象的第三大特征,在Go语言,多态特征是通过接口实现的。可以按统一的接口来调用不同的实现。这时接口实例就呈现不同的形态类型断言: 由于接口是一般类型,不知道具体类型,如果要转成具体类型,就需要使用类型断言。类型断言判断案例: 如何在进行断言时,带上检测机制,如果成功ok,否则也不要抱panicvar x interface{}var b float32 = 1.1x = b //空接口可以接收任意类型y,ok := x.(float64)	//x ==>float32 [使用类型断言]if ok {fmt.Println("convert success...")fmt.Printf("y 的类型是%T 值是%v\n",y,y)}else{fmt.Println("convert fail...")}fmt.Println("go........")类型断言最佳案例: [!!!注意体会]type Usb interface{	Start(); Stop();}type Phone struct{name string}//让Phone 实现 Usb接口的方法 并额外实现一个Call方法func (p Phone) Start(){fmt.Println("手机开始工作..")}func (p Phone) Stop(){fmt.Println("手机停止工作..")}func (p Phone) Call(){fmt.Println("手机开始打电话..")}type Camera struct{name string}	func (p Camera) Start(){fmt.Println("相机开始工作..")}func (p Camera) Stop(){fmt.Println("相机停止工作..")}type Computer struct{}			//编写一个Working方法,接受一个Usb接口类型变量 通过usb这个接口变量来调用Start和Stop方法func (c Computer) Working(usb Usb){usb.Start()//如果usb是指向Phone的结构体变量,则还需要调用Call方法if phone,ok := usb.(Phone); ok {	//类型断言 [!!!注意体会]phone.Call()}usb.Stop()}func main(){var usbArr [3]UsbusbArr[0] = Phone{"vivo"}usbArr[1] = Phone{"小米"}usbArr[2] = Camera{"尼康"}var computer Computerfor _,v := range usbArr {computer.Working(v)fmt.Println()}}
文件* : 文件是数据源(保存数据的地方)	----->  os.File基本介绍: 文件在程序中是以流的形式来操作的流:		数据在数据源(文件)和程序(内存)之间经历的路径输入流:	数据从数据源(文件)到程序(内存)的路径	[读文件]输出流:	数据从程序(内存)到数据源(文件)的路径	[写文件]a.读文件操作:(1) 打开文件和关闭文件:func Open(name string) (file *File, err error)func (f *File) Close() errorexample:import ( "fmt";"os";)func main(){file , err := os.Open("d:/go.go")	//open fileif err != nil { fmt.Println("open file err=",err) }fmt.Printf("file=%v",file)err = file.Close()	//close fileif err != nil { fmt.Println("close file err=",err)}}(2) 读取文件内容并显示在终端(带缓冲的方式 [适合大文件] ): 使用os.Open , file.Close ,bufio.NewReader() , reader.ReadString  函数和方法import ( "fmt";"os";"bufio";"io"; )func main(){file , err := os.Open("d:/go.go")			//open fileif err != nil { fmt.Println("open file err=",err) }defer file.Close()							//timeliness close filereader := bufio.NewReader(file)				//创建一个Reader 带缓冲的 适合大文件for {str,err := reader.ReadString('\n')		//每读到一个换行符就结束 if err == io.EOF { break }				//io.EOF 代表文件的末尾  //读取到文件末尾不再继续读文件fmt.Print(str)}fmt.Println("文件读取结束...") }(3) 读取文件内容并显示在终端(使用ioutil一次性将整个文件读取到内存中) [适合文件不大的情况].相关方法和函数(ioutil.ReadFile)import ( "fmt";"io/ioutil";)func main(){//使用ioutil.ReadFile 一次性将文件读取到位 不需要显示Open 和 Close 文件file := "d:/go.go"content,err := ioutil.ReadFile(file)if err != nil { fmt.Printf("read file err=%v",err)}fmt.Printf("%v",string(content))	//[]byte}b.写文件操作:基本介绍:func OpenFile(name string,flag int,perm FileModel)(file *file ,err error) //name打开文件路径 flag文件打开模式 perm权限控制(1) 创建一个新文件 写入5句 "hello go" import ( "fmt";"bufio";"os";)func main(){filePath := "d:/abc.txt"file,err := os.OpenFile(filePath, os.O_WRONLY | os.O_CREATE,0666)if err != nil { fmt.Printf("open file err=%v\n",err);return;}defer file.Close()			//及时关闭句柄str := "hello go\r\n"//写入到缓冲 使用带缓冲的*writerwriter := bufio.NewWriter(file)for i := 0;i<5;i++{ writer.WriteString(str) }//因为writer是带缓冲的,因此在调用WriteString方法时 其实内容是线写入到缓冲的 所以需要调用Flush方法//将缓冲的数据真正写入到文件中,否则文件中会没有数据writer.Flush()}(2)	打开一个存在的文件 将原来的内容覆盖成新的内容 10 句 "你好 祖国!"file,err := os.OpenFile(filePath, os.O_WRONLY | os.O_CREATE,0666) 替换如下,file,err := os.OpenFile(filePath, os.O_WRONLY | os.O_TRUNC ,0666)(3) 打开一个存在的文件,将原来的内容追加内容 "追加内容"file,err := os.OpenFile(filePath, os.O_WRONLY | os.O_TRUNC ,0666) 替换如下,file,err := os.OpenFile(filePath, os.O_WRONLY | os.O_APPEND ,0666)(4) 将一个文件的内存写入到另一个文件。注:两个文件都存在		使用 ioutil.ReadFile / ioutil.WriteFileimport ( "fmt";"io/ioutil";)func main(){	//file1Path 数据写到file2Pathfile1Path := "d:/abc.txt"	file2Path := "d:/kkk.txt"data,err := ioutil.ReadFile(file1Path)if err != nil { fmt.Printf("read file err=%v\n",err) return }err = ioutil.WriteFile(file2Path,data,0666)if err != nil {	fmt.Printf("Write file err=%v",err) }}(5) 统计一个文件中含有的英文,数字,空格以及其他字符数量import ( "fmt";"os";"io";"bufio";)type CharCount struct{ChCount int 	//英文个数NumCount int 	//数字个数SpaceCount int 	//空格个数OtherCcunt int 	//其他字符个数}func main(){fileName := "d:/abc.txt"file,err := os.Open(fileName)if err != nil {fmt.Printf("open file err=%v\n",err);return ;}defer file.Close()var count CharCountreader := bufio.NewReader(file)for{		//循环读取fileName 内容str, err := reader.ReadString('\n')if err == io.EOF{ break }for _,v := range str{	// 遍历 str 进行统计switch {case v >='a' && v <= 'z':fallthroughcase v >='A' && v <= 'Z':count.ChCount++case v ==' ' || v== '\t':count.SpaceCount++case v =='0' || v== '9':count.NumCount++default:count.OtherCcunt++}}}fmt.Printf("英文个数=%v 数字个数=%v 空格个数=%v 其他字符个数=%v",count.ChCount,count.NumCount,count.SpaceCount,count.OtherCcunt)}(6) 编写一段程序,可以获得命令行各个参数  os.Argsfunc main(){fmt.Println("命令行的参数",len(os.Args))for i,v := range os.Args {fmt.Printf("Args[%v]=%v\n",i,v)}}go build -o test.exetest.exe tom d:/aaa/bbb/init.log 999		//参数以空格分隔放在切片里// Args[0]=test.exe// Args[1]=tom// Args[2]=d:/aaa/bbb/init.log// Args[3]=999(7) flag* 包来解析命令行参数:func main(){var user string;var pwd string;var host string;var port int;flag.StringVar(&user,"u","","用户名默认为空")flag.StringVar(&pwd,"pwd","","密码默认为空")flag.StringVar(&host,"h","localhost","主机名默认为localhost")flag.IntVar(&port,"port",3306,"端口号默认为3306")flag.Parse()	//转换解析fmt.Printf("user=%v pwd=%v host=%v port=%v",user,pwd,host,port)}go build -o test.exe main.go 					//编译文件test.exe -pwd 123456 -h 127.0.0.1 -u jack		//user=jack pwd=123456 host=127.0.0.1 port=3306c.判断文件是否存在:基本介绍:os.Stat() (1)返回错误为nil,说明文件或文件夹存在 (2)返回错误类型使用os.isNotExist()判断为true,说明文件或文件夹不存在(3)返回错误为其他类型,则不确定是否存在// 基于以上三个错误类型  自己定义了一个函数func PathExists(path string)(bool,error){_,err := os.Stat(path)if err == nil {	//文件或目录存在return true,nil}if os.isNotExist(err){return false,nil}return false,err}
json*序列化:(1) 将结构体,map,切片 进行序列化 	 ---> import "encoding/json"a.//结构体序列化type Monster struct { Name string `json:"name"`;Age int;Birthday string;Sal float64;skill string; }func testStruct(){monster := Monster{Name : "牛魔王",Age : 500,Birthday : "1880-11-22",Sal : 8000.0,skill : "风沙走石",}data,err := json.Marshal(&monster)	// monster 序列化if err != nil { fmt.Println("序列化失败",err) }fmt.Printf("序列化后的结构%v\n",string(data))}b. //Map 序列化func testMap(){var a map[string]interface{}a = make(map[string]interface{})a["name"]="红孩儿";a["age"]=15;a["address"]="火云洞";data,err := json.Marshal(a)if err != nil { fmt.Println("序列化错误",err) }fmt.Printf("序列化后的结构%v\n",string(data))}c. //切片序列化func testSlice(){var slice []map[string]interface{}var m1 map[string]interface{}m1 = make(map[string]interface{})m1["name"] = "jack"; m1["age"] = 10; m1["address"] = "上海";slice = append(slice,m1)var m2 map[string]interface{}m2 = make(map[string]interface{})m2["name"] = "tom"; m2["age"] = 19; m2["address"] = [2]string{"长沙","武汉"}slice = append(slice,m2)data,err := json.Marshal(slice)if err != nil { fmt.Println("序列化错误",err) }fmt.Printf("序列化后的结构%v\n",string(data))}	(2) 反序列化:	---->  json.Unmarshal([]byte(str),&monster)a. //反序列化为结构体	type Monster struct { Name string `json:"name"`;Age int;Birthday string;Sal float64;skill string; }func unmarshalStruct() {str := "{\"name\":\"牛魔王\",\"Age\":500,\"Birthday\":\"1880-11-22\",\"Sal\":8000}"var monster Monstererr := json.Unmarshal([]byte(str),&monster)if err != nil { fmt.Println("反序列化错误",err) }fmt.Printf("反序列化后 monster=%v monster.Name=%v\n",monster,monster.Name)}b. //反序列化为mapfunc unmarshalMap() {str := "{\"address\":\"火云洞\",\"age\":15,\"name\":\"红孩儿\"}"var map1 map[string]interface{}//反序列化map的时候不需要make,因为make操作封装到Unmarshal函数err := json.Unmarshal([]byte(str),&map1)if err != nil { fmt.Println("反序列化错误",err) }fmt.Printf("反序列化后 monster=%v \n",map1)}c. //反序列化为Slicefunc unmarshalSlice() {str := "[{\"address\":\"上海\",\"age\":10,\"name\":\"jack\"},"+"{\"address\":[\"长沙\",\"武汉\"],\"age\":19,\"name\":\"tom\"}]"var slice []map[string]interface{}//反序列化Slice的时候不需要make,因为make操作封装到Unmarshal函数err := json.Unmarshal([]byte(str),&slice)if err != nil { fmt.Println("反序列化错误",err) fmt.Printf("反序列化后 monster=%v \n",slice)}
单元测试*基本介绍: 	go语言自带一个轻量级的测试框架testing和自带的go test命令来实现单元测试和性能测试。testing框架和其他语言中的测试框架类似,可以基于这个框架写针对相应的测试应用,也可以基于框架写相应的压力测试用例,通过单元可是可以解决如下问题:1)	确保每个函数都是可运行的,并且结果是正确的2)  确保写出来的代码性能是好的3)	单元测试能及时的发现程序设计或实现的逻辑错误,是问题早暴露,便于问题的定位解决。而性能测试的重点在于程序设计上的一些问题,让程序在高并发的情况下还能稳定单元测试细节:1 测试用例文件名必须以 _test.go结尾.比如cal_test.go ,2 测试用例函数必须以Test开头,一般来说就是Test+被测试的函数名。比如TestAddUpper3 TestAddUpper(t *testing.T)的形参类型必须是 *testing.T4 一个测试用例文件中,可以有多个测试用例函数.比如 TestAddUpper TestSub5 运行测试用例指令a. cmd>go test [如果运行正确,无日志,错误时,会输出日志]b. cmd>go test -v [运行正确或是错误,都输出日志]6 当出现错误时,可以使用t.Fatalf来格式化输出错误信息,并退出程序7 t.Logf 可以输出相应的日志8 测试用例函数,并没有放在main函数中,但也执行了,这就是测试用例的方便之处9 PASS表示测试用例运行成功,FALL表示测试用例失败10 测试单个文件,一定要带上被测试的源文件   go test -v cal_test.go cal.go 		//cal被测试的源文件11 测试单个方法							go test -v -test.run TetsAdduppergoroutine*(1) goroutine基本介绍:a. 进程和线程说明:1) 进程是程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位2) 线程是进程的一个执行实例,是程序执行的最小单元,它是比进程更小的能独立运行的基本单元3) 一个进程可以创建和销毁多个线程,同一个进程中的多个线程可以并发执行4) 一个程序至少有一个进程 一个进程至少有一个线程b. 并发和并行:1) 多线程程序在单核上运行,就是并发	---> 多个任务作用在一个cpu,在微观角度,在同一个时间点上其实只有一个任务在执行2) 多线程程序在多核上运行,就是并行 ---> 多个任务作用在多个cpu同时进行(2) go协程和go的主线程:1) go主线程(有程序员称为线程/也可以理解位就是进程):一个Go线程上,可以起多个协程,可以理解,协程就是轻量级线程。2) Go协程的特点*:有独立的栈空间,共享程序堆程序,调度由用户控制,协程(goroutine)是轻量级线程 (3) goroutine案例:1) 主线程是一个物理线程,直接作用在cpu上.是重量级的 非常耗费cpu资源2) 协程从主线程开启的,是轻量级的线程,是逻辑态。对资源消耗相对小3 Golang的协程机制是重要的特点,开启过多的线程 资源消耗大,这里就凸显Golang在并发上的优势(4)	goroutine的调度模型:	MPG模式	MPG模式基本介绍:	 M:操作系统主线程(是物理线程)  P: 协程执行需要上下文环境(需要的资源,内存空间等)  G:协程(5) 设置Golang 运行的cpu数import ( "fmt"; "runtime"; )func main(){cpuNum := runtime.NumCPU()			// 返回本地机器的逻辑CPU个数fmt.Println("cpuNum=",cpuNum)runtime.GOMAXPROCS(cpuNum-1)		// 设置可同时执行的最大CPU数 1.8版本以后不需要设置}
chanel*(1) channel(管道)-基本介绍:1)	channel本质上就是一个数据结构-队列2)	数据是先进先出 【FIFO】3)	线程安全,多goroutine访问时 不需要加锁,就是说channel本身是线程安全的[多个协程操作同一个管道时,不会发生资源竞争问题]4)	channel时有类型的,一个string的channel只能存放string类型数据(2) 定义/声明 channel1)var 变量名 chan 数据类型 var intChan chan int (intChan 存放int数据的管道变量名)2) 说明: a. channel是引用类型b. channel必须初始化才能写入数据,即make后才能使用c. 管道是有类型的,intChan 只能写入int(3) 管道的初始化 读 写 func main() {var intChan chan int 			//申明intChan = make(chan int, 3)		//初始化 容量是3 写入不能超过3fmt.Printf("值=%v 地址=%p\n",intChan,&intChan) //管道是引用类型 intChan <- 10				//向管道写入数据num := 10					intChan <- numintChan <- 11fmt.Printf("管道长度=%v 容量=%v\n",len(intChan),cap(intChan))//从管道取取数据num2 := <-intChan 	// 给管道的第一个数据取出赋值给变量num2intChan <- 14		// 先进先出 只能取出一个后再才能像写入一个fmt.Printf("管道长度=%v 容量=%v num2=%v\n",len(intChan),cap(intChan),num2)}注意事项和细节:	a. channel只能存放指定类型的数据类型	b. channel数据放满后就不能再放入了,如果再从channel取出数据后,可以继续放入c. 再没有使用协程的情况下,如果channel数据取完了,再取,就会报deadlock(4) 存放任意数据类型的管道func main() {//定义一个存放任意数据类型的管道 3个数据//var allChan chan interface{}allChan := make(chan interface{},3)allChan <- 10allChan <- "tom"cat := Cat{"小白猫",4}allChan <- cat //我们希望获得管道中的第三个元素 则先将前2个推出<-allChan<-allChannewCat := <-allChanfmt.Printf("newCat=%T , newCat=%v\n",newCat,newCat)a := newCat.(Cat)							//类型断言// fmt.Printf("newCat.Name=%v",newCat.Name)	//错误写法 需要类型断言fmt.Printf("newCat.Name=%v",a.Name)			}(5) channel的遍历和关闭:关闭:使用内置函数close可以关闭channel,当channel关闭后,就不能再向channel写数据了,但仍然可以从该channel读数据遍历:	a. 使用for-range 方式进行遍历 b. 在遍历时,如果channel没有关闭 则会出现deadlock错误c. 在遍历时,如果channel已经关闭 则会正常遍历数据,遍历完后就会推出遍历func main() {intChan := make(chan int,100)for i := 0; i < 100; i++ {	//遍历管道intChan <- i*2	//放入100个数据到管道}close(intChan)	//遍历管道前关闭管道 避免出现deadlockfor v := range intChan{	//取的时候 变量管道只能for-range fmt.Println("v=",v)}}(6) 控制协程在主进程结束之后全部读写完成、func writeData(intChan chan int) {	//write Datafor i := 0; i < 50; i++ {intChan <- ifmt.Println("write v=",i)}close(intChan)}func readData(intChan chan int ,exitChan chan bool) {	//read datafor { v,ok := <- intChanif !ok { break } fmt.Printf("readData 读到数据=%v\n",v)}exitChan <- true //读完管道数据close(exitChan)}func main() {intChan := make(chan int,50)exitChan := make(chan bool,1)go writeData(intChan)go readData(intChan,exitChan)for { _,ok := <- exitChan 	//读出是true则说明协程已经读取完毕 if !ok { break } }}(7) channel 可以申明为只读或者只写 性质	func main() {var chan1 chan<- int 		//申明为只写chan1 = make(chan int,3)chan1 <- 100//num := <- chan1	// errorvar chan2 <-chan int 		//申明为只读num2 := <-chan2//chan2 <- 20	//errorfmt.Println(num2)}(8) 使用*select 解决从管道取数据的阻塞问题func main() {intChan := make(chan int,10)	//定义一个管道 10个数据 intfor i := 0; i < 10; i++ { intChan <- i }//定义一个管道 5个数据stringstringChan := make(chan string,5)for i := 0; i < 5; i++ { stringChan <- "hello" + fmt.Sprintf("%d",i) }//传统的方法在遍历管道时,如果不关闭会阻塞而导致deadlock//问题是 实际开发中,我们不好确定在哪关闭该管道 可以使用select 方式解决for {select {//注意:这里,如果intChan一直没有关闭,不会一直阻塞而deadlock 会自动到下一个case匹配case v := <- intChan :fmt.Printf("从intChan读取数据%d\n",v)case v := <- stringChan :fmt.Printf("从stringChan读取数据%s\n",v)default:fmt.Println("都找不到了! 开发者可以加入自己的逻辑")return  }}}	(9) 使用 *recover 捕获管道错误func sayHello() {for i := 0; i < 10; i++ { fmt.Println("hello word") }}func test() {defer func(){//捕获test抛出的panicif err := recover(); err != nil { fmt.Println("test() 发生错误\n",err) }}()var myMap map[int]stringmyMap[0] = "golang"		//没有make 会panic}func main() {go sayHello()go test()for i := 0; i < 10; i++ {fmt.Println("main() ok=",i)time.Sleep(time.Second)}}	
反射*(1)基本介绍:1)	反射可以运行时动态获取变量的各种信息 比如变量的类型(type) 类别(kind)2)	如果是结构体变量,还可以获取到结构体本身的信息(包括结构体的字段和方法)3)	通过反射,可以改变变量的额值,可以调用相关的方法4)	使用放射,需要import("reflect")(2)反射重要的函数和概念:1) 	变量,interface{} 和 reflect.Value 是可以相互转换的.这在实际开发中是经常用到的(3)基本数据类型,interface{},reflect.Value 进行反射的基本操作func reflectTest01 (b interface{}){//获取reflect.Type rTyp := reflect.TypeOf(b)//获取 reflect.ValuerVal := reflect.ValueOf(b)//将 reflect.Value 转 int类型 rVal.Int()nu := 2 + rVal.Int()//将 reflect.Value 转成 interface{}iv := rVal.Interface()//将 interface{} 通过断言转换成需要的类型num := iv.(int)}(4) 结构体类型,interface{},reflect.Value, 进行反射的基本类型操作func reflectTest01 (b interface{}){rVal := reflect.ValueOf(b)					//获取 reflect.ValueiV := rVal.Interface()fmt.Printf("iv=%v type=%T \n",iV,iV)		//iv={tom 12} type=main.Studentstu,ok := iV.(Student) 						//需要类型断言if ok {fmt.Printf("stu.Name=%v\n",stu.Name)	//stu.Name=tom}}type Student struct { Name string; Age int; }func main() {stu := Student{Name : "tom",Age : 12,}reflectTest01(stu)}(5) reflect.Value.Kind,获取变量的类别,返回的是一个常量(6) 通过反射修改变量的值 func reflect1(b interface{}) {rVal := reflect.ValueOf(b)fmt.Printf("rVal kind=%v\n",rVal.Kind())//Elem返回v持有的接口保管的值得Value封装,或有v持有的指针的值的value封装rVal.Elem().SetInt(20) 	//反射修改变量的值}func main() {var num int =10reflect1(&num)fmt.Println("num=",num)	// 输出20}
TCP编程:网络编程有两种:1 TCP socket编程,是网络编程的主流。之所有叫Tcp socket编程,是因为底层是基于Tcp/ip 协议的.比如QQ2 b/s 结构的http编程,我们使用浏览器取访问服务器时,使用的就http协议,而http协议底层也是tcp socket 实现端口:a. 0是保留端口b. 1-1024是固定端口,又叫做有名端口,即被某些程序固定使用,一般程序员不能使用22:SSH 远程登录协议	23: telent 使用 21:ftp使用 25:smtp服务使用 80:iis使用 7:echo服务c. 1025-65535 是动态端口,这些端口 程序员可以使用d. 可以使用netstat -an 查看本机有哪些端口在监听
Ridis*: (官方文档:http://redisdoc.com/)一. Golang 操作Redis:1. 使用第三方开源库 github.com/garyburd/redigo/redis2. 在使用Redis前 先安装第三方Redis库 在GOPATH路径下执行安装指令:D:\goproject>go get github.com/garyburd/redigo/redis注意: 在安装前 确保已经安装并配置了Git.(1) 添加key-value import "github.com/garyburd/redigo/redis"func main() {c,err := redis.Dial("tcp","localhost:6379")		//连接redisif err != nil { fmt.Println("conn redis faild,",err);return; }defer c.Close()_,err = c.Do("Set","key1",888) 					//添加key-valueif err != nil { fmt.Println(err); return; }r,err := redis.Int(c.Do("Get","key1")) 			//获取get keyif err != nil { fmt.Println("get key1 faild,",err); return; }fmt.Println(r)}(2) 操作Hash_,err = c.Do("HSet","user01","name","汤姆")r,err := redis.String(c.Do("HGet","user01","name")) 	//如果存放的是int则使用reids.Int()(3) 批量Set/Get数据_,err = c.Do("MSet","name","小明","address","武汉")r,err := redis.Strings(c.Do("MGet","name","address"))二. Ridis的五大数据类型:String,Hash,List,Set,zSet(有序集合) (1) redis 的基本使用: 默认有16个数据库 初始默认值0号库 编号是0-15a. 添加key-val [set]b. 查看当前的redis的所有key [keys *]c. 获取key对应的值 [get key]d. 切换redis数据库 [select index]e. 查看当前数据库key-value的数量 [dbsize]f. 清空当前数据库的key-val和清空所有数据库的key-val [flushdb  flushall](2) String:二进制安全,除了普通字符串外,也可以存放图片等数据。redis字符串value最大值是512Ma. CURD:set [存在则修改,不出存则添加] get delsetex key1 10 helloword				设置key1 值是helloword 10秒mset key value [key valye...] 		一次性设置多个key valuemget key1 key2 						同时获取多个key对应的value(3) Hash: 是一个键值对集合 是一个string类型的field和value的映射表CURD:	hset user1 name jack				添加user1字段 的 name jackhget user1 name 					获取user1字段下的name的值hgetall user1 						获取user1字段下的所有属性的值hmset user2 name mary age 21		设置user2字段下的name 和 age 属性的值hmget user2 name age 				获取user2 下的name和age的值hlen user2 							统计一个哈希有几个元素hexists user2 name 					查看user2哈希中是否有name这个属性(4) List: List本质是个链表,List元素是有序的,元素的值可以重复a. lindex  a按照索引下标获得元素(从0到右 编号从0开始)b. llen key 返回列表key的长度 如果可以不存在,则key被解释为一个空列表 返回0c.CURD:lpush city beijing shanghai 		向city列表中从左边插入beijing shanghai lrange city 0 -1 					取所有的city列表中的所有值rpush herolist aa bb cc 			从链表的左边插入三个值lpop herolist 						从链表左边弹出一个 返回弹出的值rpop herolist						从链表右边弹出一个 返回弹出的值del  herolist						删除链表(5) Set: Ridis的Set是String类型的无序集合,且元素值不能重复CURD:sadd emails xx xxx 					向集合emails 集合添加2个值smembers emails						遍历该集合中的所值sismember 							判断是否是成员srem 								删除指定的值三. Ridis连接池 : 事先初始化一定数量的连接,放入连接池.当Go需要操作Redis时 直接从redis连接池取出连接即可(1) 核心代码:var pool *redis.Poolpool = &redis.Pool{MaxIdle:8, 						//最大空闲链接数MaxActive:0,					//表示和数据库的最大链接数,0表示没有限制IdleTimeout:100,				//最大空闲时间Dial:func()(redis.Conn,error){ 	//初始化return redis.Dial("tcp","localhost:6379")},}c := pool.Get()  					//从链接池中取出一个链接pool.Close()						//关闭连接池 就再不能从连接池中取链接了(2) 案例:import "github.com/garyburd/redigo/redis"var pool *redis.Poolfunc init() {pool = &redis.Pool{MaxIdle:8, 						MaxActive:0,					IdleTimeout:100,				Dial:func()(redis.Conn,error){ 	return redis.Dial("tcp","localhost:6379")},}}func main() {conn := pool.Get()defer conn.Close()_,err := conn.Do("Set","name","jack")if err != nil{ fmt.Println(err); return; }r,err := redis.String(conn.Do("Get","name"))if err != nil{ fmt.Println(err); return; }fmt.Println("r=",r)}
数据结构*:a. 数据结构是一门研究算法的学科.要学数据结构就要多多思考如何将生活中遇到的问题,用程序去实现解决稀疏数组: 当一个数组中大部分元素是0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组稀疏数组的处理方法:记录数组一共有几行几列,有多少个不同的值,吧具有不同值得元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模队列:队列是一个有序列表,可以用数组和链表实现 遵循先入先出的原则

 


http://www.ppmy.cn/news/724949.html

相关文章

【云原生丶Kubernetes】Kubernetes初体验

人生若只如初见&#xff0c;何事秋风悲画扇。 前言 Kubernetes 是目前最流行的容器编排工具之一&#xff0c;由Google开发并维护。它提供了完整的容器编排解决方案&#xff0c;包括自动化部署、资源管理和调度、服务发现和负载均衡等功能。 然而&#xff0c;对于初学者来说&a…

SpringBoot处理全局异常详解(全面详细+Gitee源码)

前言&#xff1a;在日常的开发工作中&#xff0c;项目在运行过程中多多少少是避免不了报错的&#xff0c;对于报错信息肯定不可以把全部信息都抛给客户端去显示&#xff0c;这里就需要我们对常见的七种异常情况统一进行处理&#xff0c;让整个项目更加优雅。 目录 一、基本介绍…

中国移动发狠,给携转用户巨额优惠,反击中国电信

近日据业内人士指出中国移动针对携号转网用户提供了巨额优惠&#xff0c;只要21元就可获得30GB全国通用流量移动套餐&#xff0c;再另送500M宽带&#xff0c;两年免费、安装费全免&#xff0c;对于我们老用户来说这可是139元套餐才能享受到&#xff0c;这显示出中国移动为了争夺…

Java实现PDF转Word【收集整理】

首先感谢 Mgg9702 博主提供的转换依赖包处理&#xff0c;关于如何获得一个破解的pdf转word我这里就不追述了&#xff0c;有需要去看&#xff1a; https://blog.csdn.net/Mgg9702/article/details/124987483?spm1001.2014.3001.5506 我这里主要涉及到整理一个pdf转word的jar工…

以太网 (数据链路层协议)

以太网 认识以太网以太网数据帧 认识以太网 “以太网” 不是一种具体的网络&#xff0c;而是一种技术标准&#xff1b;既包含了数据链路层的内容&#xff0c;也包含了一些物理层的内容。 例如&#xff1a;规定了网络拓扑结构&#xff0c;访问控制方式&#xff0c;传输速率等&a…

重装系统会影响到电脑的正常使用吗

​重装系统对电脑有影响吗?重装系统是指对计算机的操作系统进行重新的安装。当用户错误操作或遭受病毒的破坏时&#xff0c;系统中的重要文件就会受损导致错误&#xff0c;严重甚至可能出现崩溃&#xff0c;电脑无法启动操作&#xff0c;因此不得不进行重新安装。一些喜欢操作…

xp系统安装c语言教程,神舟战神K670C-G4A1笔记本u盘重装系统xp教程

神舟战神K670C-G4A1笔记本预装Windows 10系统&#xff0c;搭载了Intel Pentium G5420处理器&#xff0c;主频为3.8GHz&#xff0c;而三级缓存为L3 4M。另外&#xff0c;电脑还配置了8GB运行内存&#xff0c;而最大支持内存则是32GB&#xff0c;整体性价比较高。但是&#xff0c…

关于神舟电脑U盘重装windows10系统

关于神舟电脑U盘重装windows10系统 神舟电脑重装新安装系统遇到的坑&#xff0c;之前电脑安装使用的是Linux系统&#xff0c;但是由于电脑版本原因导致安装Linux失败&#xff0c;于是便想装回windows10系统&#xff0c;本人从售后获取windows10专业版镜像以后使用ultralso&…