golang笔记一
1.基本数据类型和数组都是值传递
变量
声明一个变量
第一种,指定变量类型,声明后若不赋值,使用默认值
var name type name = value
第二种,根据值自行判定变量类型(类型推断Type inference)
如果一个变量有一个初始值,Go将自动能够使用初始值来推断该变量的类型。因此,如果变量具有初始值,则可以省略变量声明中的类型。
var name = value
第三种,省略var, 注意 :=左侧的变量不应该是已经声明过的(多个变量同时声明时,至少保证一个是新变量),否则会导致编译错误(简短声明)
name := value // 例如 var a int = 10 var b = 10 c : = 10
这种方式它只能被用在函数体内,而不可以用于全局变量的声明与赋值
多变量声明
第一种,以逗号分隔,声明与赋值分开,若不赋值,存在默认值
var name1, name2, name3 type name1, name2, name3 = v1, v2, v3
第二种,直接赋值,下面的变量类型可以是不同的类型
var name1, name2, name3 = v1, v2, v3
第三种,集合类型
var ( name1 type1 name2 type2 )
1.1 布尔型bool
布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true 占用一个字节 不能 用0表示false 1表示true
1.2 数值型
1、整数型
-
int8 有符号 8 位整型 (-128 到 127) 长度:8bit 有符号 -2^7 ~ 2^7-1 一个字节8位,第一位表示符号正或负
-
int16 有符号 16 位整型 (-32768 到 32767) -2^15 ~ 2^15-1
-
int32 有符号 32 位整型 (-2147483648 到 2147483647) -2^31 ~ 2^31-1
-
int64 有符号 64 位整型 (-9223372036854775808 到 9223372036854775807) -2^63 ~ 2^63-1
-
uint8 无符号 8 位整型 (0 到 255) 8位都用于表示数值: 无符号 0 ~ 2^7-1 一个字节8位,第一位也表示数字
-
uint16 无符号 16 位整型 (0 到 65535) 0 ~ 2^16-1
-
uint32 无符号 32 位整型 (0 到 4294967295) 0 ~ 2^32-1
-
uint64 无符号 64 位整型 (0 到 18446744073709551615) 0 ~ 2^64-1
int和uint:根据底层平台,表示32或64位整数。除非需要使用特定大小的整数,否则通常应该使用int来表示整数。 大小:32位系统32位,64位系统64位。 范围:-2147483648到2147483647的32位系统和-9223372036854775808到9223372036854775807的64位系统。
数据类型转换
var i int32 = 100 var n1 float32 = float32(i)
上面只是把 i 的值转换为 float32 赋值给 n1 但 i 本身类型没有变化
int64很大的数 转换为 int8 会做溢出处理
2、浮点型
-
float32
IEEE-754 32位浮点型数
-
float64 (推荐使用)
IEEE-754 64位浮点型数
-
complex64
32 位实数和虚数
-
complex128
64 位实数和虚数
3、其他
-
byte
类似 uint8
-
rune
类似 int32
-
uint
32 或 64 位
-
int
与 uint 一样大小
-
uintptr
无符号整型,用于存放一个指针
1.3 字符串型
字符串就是一串固定长度的字符连接起来的字符序列。Go的字符串是由单个字节连接起来的。Go语言的字符串的字节使用UTF-8编码标识Unicode文本
var str string str = "Hello World"
字符串是由字节组成,单个字符可以用byte存储
单引号一个字符/汉字 是对应的 ASCII码值
1.字符串类型为 string,使用双引号或者反引号包起来
2.字符串拼接 "hello" + "world" 多个链接 + 后面换行
1.4 数据类型转换:Type Convert
语法格式:Type(Value)
常数:在有需要的时候,会自动转型
变量:需要手动转型 T(V)
注意点:兼容类型可以转换
二、 复合类型(派生类型)
1、指针类型(Pointer) 2、数组类型 3、结构化类型(struct) 4、Channel 类型 5、函数类型 6、切片类型 7、接口类型(interface) 8、Map 类型
package main import ( "fmt" "unsafe" ) func main() { var n2 int64 = 10 fmt.Printf("n2 的类型:%T,n2 的占用字节数%d",n2,unsafe.Sizeof(n2)) }
结果
n2 的类型:int64,n2 的占用字节数8
三、类型转换
其他类型转换为字符串类型
var num int = 99 var num1 float64 = 56.32 var b bool = true var str string str = fmt.Sprintf("%d",num) str = fmt.Sprint(num) str = strconv.Itoa(num) str = strconv.FormatInt(int64(num),10) str = strconv.FormatFloat(num1,'f',10,64) str = strconv.FormatBool(b) fmt.Printf("str type %T str=%q\n",str,str)
string类型转换为其他类型 用的 strconv包
var str string = "true" var str1 string = "12" var str2 string = "56.24" var b bool var n1 int64 var n2 int var f1 float64 b,_ = strconv.ParseBool(str) fmt.Printf("str type %T str=%v\n",b,b) n1,_= strconv.ParseInt(str1,10,64) n2 = int(n1) fmt.Printf("str type %T str=%v\n",n1,n1) fmt.Printf("str type %T str=%v\n",n2,n2) f1,_ = strconv.ParseFloat(str2,64) fmt.Printf("str type %T str=%v\n",f1,f1)
如果 一个 str ="hello" 转换为int 会转换为默认值 0 如果转换为 bool 也会是默认值 flase
值类型:
基本数据类型 int 系列, float 系列, bool, string 、数组和结构体 struct
引用类型:
指针、slice 切片、map、管道 chan、interface 等都是引用类型
四、算数运算
1. 除法整除整 取整 要得到小数部分 必须有浮点数据参与运算
fmt.Println(10 /4) //2 只会取整数部分 fmt.Println(10.0 /4) //2.5 有浮点数参加 才会有小数部分 fmt.Println(10%-3) //1 取余(取莫) 按照公式 a % b = a - a / b * b GO 只有 num++ num-- 没有 ++num --num 也不允许 a := b++ a:=b-- 只能当作一个独立的使用
不使用第三个变量来交换两个变量的值
//常规做法 var a int = 9 var b int = 5 a = a + b // 9 + 5 b = a - b // a + b - b a = a - b // a + b - a fmt.Println(a,b) //go的做法 a,b = b,a
运算符优先级
键盘输入语句:
fmt.包的 func Scan fmt.Scanln fmt.Scanf 例如 :
var x,y int fmt.Scanln(&x,&y)
其他进制转十进制
二进制 1101 = 1*2^0 + 0*2^1 + 1*2^2 + 1*2^3 = 1+0+4+8 = 13
八进制 1111 = 1*8^0 + 1*8^1+1*8^2 + 1*8^3 = 1+8+64+512 = 585
十六进制 0x111 = 1*16^0 + 1*16^1+1*16^2 = 1+16+256 = 273
十进制转其他进制
十进制转二进制 规则:将该数不断除以2,直到商为0为止,然后将每步得到的余数倒过来,就是对应的二进制
转二进制 56
十进制转八进制 规则:将该数不断除以8,直到商为0为止,然后将每步得到的余数倒过来,就是对应的二进制
十进制转十六进制 规则:将该数不断除以16,直到商为0为止,然后将每步得到的余数倒过来,就是对应的二进制
二进制转八进制 规则:将二进制数每三位一组(从低位开始组合),转换对应的八进制数即可 11 010 101 = 0325
二进制转十六进制 规则:将二进制数每四位一组(从低位开始组合),转换对应的十六进制数即可 1101 0101 = 0xD5
八进制转二进制 规则:将八进制数每一位,转换成对应的一个3位的二进制即可 0237 = 10011111
十六进制转二进制 规则:将十六进制数每一位,转换成对应的一个4位的二进制即可 0237 = 1000110111
原码、反码、补码
网上对源码,反码,补码的解释过于复杂,我这里精简6局话:
对于有符号的而言:
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)负数的补码 = 他的反码+1
5)0的反码 补码都是0
6)在计算机运算的时候,都是以补码的方式来运算的
1+1 1-1 = 1+(-1)
一.switch分支结构
switch 细节讨论
1) case后是一个表达式(即:常量值、变量、一个有返回值的函数等都可以)
2) case后的各个表达式的值的数据类型,必须和switch的表达式数据类型一致
3) case后面可以带多个表达式,使用逗号间隔.比如case 表达式1,表达式2 ...
4) case后面的表达式如果是常量值(字面量),则要求不能重复
5) case后面不需要带brear,程序匹配到一个case后就会执行对应的代码块,然后退出switch,如果一个都匹配不到,则执行default
6) default语句不是必须的
7) switch后也可以不带表达式,类似多个if--else分支来使用
8) switch后也可以直接声明/定义一个变量,分号结束,不推荐
9) switch 穿透-fallthrough,如果在case语句块后增加fallthrough,则会继续执行下一个case,也叫switch穿透
10) Type Switch: switch语句还可以被用于type-switch来判断某个 interface 变量中实际指向的变量类型
var score int = 30 switch { case score >90 : fmt.Println("成绩及格...") case score >70: fmt.Println("成绩优良...") case score >60 && score<70: fmt.Println("成绩及格...") default: fmt.Println("成绩不及格") }
//编写一个函数,可以判断输入的参数是什么类型 type Student struct { } //编写一个函数,可以判断输入的参数是什么类型 func TypeJudge(items... interface{}) { for index,v :=range items{ index++ switch v.(type) { case bool: fmt.Printf("第%v个参数是 bool 类型,值是%v\n",index,v) case int32: fmt.Printf("第%v个参数是 int32 类型,值是%v\n",index,v) case int64: fmt.Printf("第%v个参数是 int64 类型,值是%v\n",index,v) case float32: fmt.Printf("第%v个参数是 float32 类型,值是%v\n",index,v) case float64: fmt.Printf("第%v个参数是 float64 类型,值是%v\n",index,v) case string: fmt.Printf("第%v个参数是 string 类型,值是%v\n",index,v) case Student: fmt.Printf("第%v个参数是 Student 类型,值是%v\n",index,v) case *Student: fmt.Printf("第%v个参数是 *Student 类型,值是%v\n",index,v) default: fmt.Printf("第%v个参数是 不确定 类型,值是%v\n",index,v) } } } func main() { var n1 float32 = 1.1 var n2 float64 = 5.69 var n3 bool = true var n4 int32 = 5 var n5 int64 = 9 var n6 string = "hello" stu1 :=Student{} stu2 :=&Student{} TypeJudge(n1,n2,n3,n4,n5,n6,stu1,stu2) }
二.for循环
//1.第一种 for i:=1;i<=10;i++{ fmt.Println("输出",i) } //2.第二种 j:=1 for j<=10{ fmt.Println("shuchu",j) j++ } //3.第三种 i:=1 for ; ; { //也等价于 for{ fmt.Println(i) if i>10{ break } i++ }
//遍历字符串 var str string ="hello,world" 1.第一种 for i:=0;i{ fmt.Printf("%c\n",str[i]) } 2.第二种 for index,val := range str{ fmt.Printf("index=%d,val=%c\n",index,val) } //如果字符串里面有中文,遍历是取的每一个字节,汉字在utf-8里面占三个字节 var str string ="hello,world北京" str1 := []rune(str) for i:=0;i { fmt.Printf("%c\n",str1[i]) } //这种方式比较智能,按照字符的方式遍历 有中文也是没问题的 for i,v :=range str{ fmt.Printf("index=%d,var=%c\n",i,v) }
//产生一个0-100的随机数,当这个数等于99时 退出 rand.Seed(time.Now().Unix())//随机种子 也可以用 UnixNano() 纳秒 for{ see := rand.Intn(100)+1 // [1,100)+1 fmt.Println(see) if(see==99){ break } }
//break 跳出多层循环,指定标签 lable2: for i:=1;i<4;i++{ for j:=1;j<10;j++{ if j==2{ break lable2 } fmt.Println(j) } }
//continue跳过本次循环 lable2: for i:=1;i<4;i++{ for j:=1;j<10;j++{ if j==2{ continue lable2 //跳出本次循环 } fmt.Println("i=",i,"j=",j) } } //结果 i= 1 j= 1 i= 2 j= 1 i= 3 j= 1
go build -o bin/my.exe 目录
三.递归函数
package main import "fmt" func test(n int){ if n>2{ n-- test(n) } fmt.Println("n=",n) } func main() { test(4) } //结果 n = 2 n = 2 n = 3
//求出第n个斐波拉契数 func fbn(n int)int{ if n==1 || n==2{ return 1 }else { return fbn(n-1)+fbn(n-2) } } func main() { res :=fbn(10) fmt.Println(res) }
已知 f(1)=3 ;f(n) = 2*f(n-1)+1 func fbn(n int)int{ if n==1{ return 3 }else { return 2*fbn(n-1)+1 } } func main() { res :=fbn(10) fmt.Println(res) }
//一堆桃子,猴子第一天吃了其中的一半,并再多吃一个,以后每天都吃其中的一半再多吃一个,第十天想吃时,发现只一个了 问:最初共多少个桃子 1.第十天只有一个 2.第九天 (第十天的桃子+1)*2 //第九天吃完就只一个 第九天N个 n/2-1 = 第十天的桃子 那么 n= (第十天的桃子+1)*2 3.规律 第N天的桃子 peach(n) = (peach(n+1)+1)*2 func peach(n int)int{ if n>10 || n<1{ fmt.Println("输入的天数不对") return 0 } if n==10{ return 1 }else { return (peach(n+1)+1)*2 } } func main() { res :=peach(1) fmt.Println(res) //1534 }
四.函数
1.在go中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量了,通过该变量可以函数调用
2.函数既然是一种数据类型,因此在GO中,函数可以作为形参,并调用
3.为了简化数据类型定义,GO支持自定义数据类型
基本语法:type 自定义数据类型 数据类型 //理解:相当于一个别名
案例 type myInt int //这时myInt 就等价 int 来使用了 //但是 myInt 和 int 是两种不同的数据类型
案例 type mySum func(int,int)int //这时mySum就等价 一个函数类型 func(int,int)int
type myFunType func(int,int)int //定义函数类型 func getSum(n1,n2 int)int{ return n1+n2 } func myFun(funcvar myFunType,num1 int,num2 int)int { return funcvar(num1,num2) } func main() { a := getSum fmt.Printf("a的类型%T,getSum类型是%T\n",a,getSum) res :=a(10,100) fmt.Println("res= ",res) res1 := myFun(getSum,50,39) fmt.Println("res1=",res1) type myInt int var num1 myInt var num2 int num1 = 10 num2 = int(num1)//需要转换 fmt.Println(num1,num2) }
4.支持对函数返回值命名
func getSum(n1 int,n2 int)(sum int,sub int) { sum = n1+n2 sub = n1-n2 return } func main() { res,res1 := getSum(100,23) fmt.Println(res,res1) }
5.GO支持可变参数 可变参数必须放到形参的最后面
func getSum(args ...int)int { sum :=0 for i:=0;i{ sum+=args[i] } return sum } func main() { res := getSum(1,2,3,4,5,6) fmt.Println(res) }
6.每一个源文件都可以包含一个init函数 该函数会在mian函数执行前,被GO运行框架调用,也就是说 init会在main函数前被调用 通常完成初始化工作
如果一个文件同时包含全局变量,init函数 main函数 ,执行的顺序 全局变量定义 》init > mian
var age = test() func test()int { fmt.Println("test()...") return 90 } func init() { fmt.Println("init()...") } func main() { fmt.Println("mian()...age=",age) } 结果: test()... init()... main()...
对于init 函数
如果 package 存在依赖,调用顺序为最后被依赖的最先被初始化,例如:导入顺序 main –> A –> B –> C,则初始化顺序为 C –> B –> A –> main,一次执行对应的 init 方法。main 包总是被最后一个初始化,因为它总是依赖别的包
五.若冥函数(闭包)
匿名函数
var( fun1 = func(n1 int,n2 int)int {//全局匿名函数 return n1*n2 } ) func main() { res := func(n1 int,n2 int)int { //匿名函数 return n1+n2 }(10,20) fmt.Println(res) a := func(n1 int,n2 int)int { //匿名函数赋值给变量 return n1 - n2 } re1 := a(10,3) fmt.Println(re1) re2 :=fun1(2,3) fmt.Println(re2) }
闭包:就是一个函数和与其相关的引用环境组合的一个整体
func addUpper() func(int)int { var n int = 10 return func(x int) int { n = n+x return n } } func main() { f := addUpper() fmt.Println(f(1)) //11 fmt.Println(f(2)) //13 fmt.Println(f(3)) //16 } //上述 n 只初始化一次,多次调用 就在累加 返回的匿名函数和 n 构成了闭包
func makeSuffix(suffix string) func(string)string { return func(name string) string { if !strings.HasSuffix(name,suffix){ //是否存在指定后缀 return name + suffix } return name } } func main() { f := makeSuffix(".jpg") fmt.Println("文件名处理后:",f("winter")) }
六.defer的使用 (延时机制)
压入栈,先进后出的原则,如果涉及到相关的值,也会拷贝同时入栈
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) } // ok3 res= 32 ok2 n2= 20 ok1 n1= 10 32
全局变量 只能用 var a int 不能用 a : = 10 相当于 var a int 和 a = 10 . a = 10 赋值必须放在函数体里面
1.查找子串是否在指定的字符串中 :strings.Contains("seafood","food") //true
2.10进制转换 2 ,8 ,16 :str = strconv.FromatInt(123,2) // 2->8->16
3.统计一个字符串有几个指定的字串: strings.Count("ceheese","e") //4
4.不区分大小写字符串比较(==是区分字母大小写的):strings.EqualFold("abc","Abc") //true
5.返回子字符串第一次出现的位置 :strings.Index("NLT_abc","abc") //4
6.返回子串在字符串最后一次出现的位置 如果没用返回-1 :strings.LastIndex("go golang","go")
7.将指定的字符串替换成,另外一个字串:strings.Replace("go go hello ","go","go 语言",n) //n 可以指定你希望替换几个,如果n=-1 表示全部替换
8.按照制度的木个字符,为分割标识,将一个字符串拆分为字符串数组 : strings.Split("hello,world,ok",",")
9.j将字符串的字母进行大小写转换 :strings.ToLower("GO")// go strings.ToUpper("go") // GO
10.将字符串左右两边的空格去掉: strings.TrimSpace(" tn a lone gopher ntm ")
11.将字符串左右两边指定的字符去掉:strings.Trim("!hello!","!") 可以去掉多个 strings.Trim("!hello!"," !") //将左右两边的空格和! 去掉
12.将字符串左边指定的字符串去掉:strings.TrimLeft("!hello!","!")
13.将字符串右边指定的字符串去掉:strings.TrimReft("!hello!","!")
14.判断字符串是否以指定的字符串开头:strings.HasPrefix("ftp://192.168.10.1","ftp")//true
15.判断字符串是否以指定的字符串结束:strings.HasSuffix("NLT_abc.jpg","abc")// false
七.时间日期函数
1.获取当前时间:time.Now() //类型 time.Time
2.获取日期
now :=time.Now() 年:now.Year() 月:int(now.Month()) 日:now.Day() 时:now.Hour() 分:now.Minute() 秒:now.Second()
3.格式化日期
now :=time.Now() //第一种 fmt.Printf("当前日期: %d-%d-%d %d:%d:%d\n",now.Year(),now.Month(),now.Day(),now.Hour(),now.Minute(),now.Second()) //当前日期: 2022-2-9 16:30:58 dateStr := fmt.Sprintf("当前日期: %d-%d-%d %d:%d:%d\n",now.Year(),now.Month(),now.Day(),now.Hour(),now.Minute(),now.Second()) fmt.Println(dateStr) //当前日期: 2022-2-9 16:32:41 //第二种 //2006/01/02 15:04:05 日期数字是固定的 fmt.Printf(now.Format("2006/01/02 15:04:05")) //2022/02/09 16:34:15
1天=24小时 = 24*60 分钟 = 24*60*60秒 = 24*60*60*1000毫秒 = 24*60*60*1000*1000 微秒 = 24*60*60*1000*1000*1000 纳秒
second 秒 Millisecond 毫秒 Microsecond 微秒 Nanosecond 纳秒 unix时间戳 unixnano 纳秒时间戳 time.Sleep(time.Second) //睡眠一秒 time.Sleep(time.Millisecond*100) // 毫秒*100 每隔0.1秒 now.Unix() //时间戳 now.UnixNano() //纳秒时间戳
//测试程序执行时间 func test(){ str := "" for i:=0;i<100000;i++ { str +="hello"+strconv.Itoa(i) } } func main() { statr := time.Now().Unix() test() end := time.Now().Unix() fmt.Printf("执行时间为:%v",end-statr) }
八.内置函数 buildin
len() 用来求长度,比如 string,arry,slice,map,channel cap() 用来求容量 比如 slice new() 用来分配内存,主要用来分配值类型,比如int,float32,struce ...返回的是指针 make() 用来分配内存的,主要用来分配引用类型,比如 chan,map,slice. num2 := new(int) fmt.Printf("num2的类型%T,num2的值=%v,num2的地址%v,num2这个指针,指向的值=%v",num2,num2,&num2,*num2) //结果 num2的类型*int,num2的值=0xc00000a0a0,num2的地址0xc000006028,num2这个指针,指向的值=0
九.错误机制
1.在默认情况下,当发生错误后(panic),程序就会退出(崩溃)
2.GO中引入的处理方式为:defer,panic,recover
3.这几个异常的使用场景可以这么简述:GO中可以抛出一个panic 的异常,然后在defer中通过recover捕获这个异常,然后正常处理
func test() { defer func() { err := recover() //recover 内置函数,可以捕获到异常 if err !=nil{ //说明捕获到错误 fmt.Println("err=",err) } }() num1 := 10 num2 := 0 res := num1/num2 //此处发生错误 引发panic fmt.Println("res=",res) } func main() { test() fmt.Println("main()下面代码继续执行") }
4.自定义错误
1.GO中也支持自定义错误,使用errors.New 和 panic内置函数
1)errors.New(”错误说明“),会返回一个error类型的值,表示一个错误
2)panic内置函数,接收一个interface{} 类型的值(空接口可以接受任何值)作为参数,可以接受error类型的变量 输出错误信息,并退出程序
func readconf(name string) error { if name=="config.ini"{ //正确的 return nil }else { //返回一个自定义错误 return errors.New("读取文件错误") } } func test() { err :=readconf("config.ini") if err !=nil{ panic(err) } fmt.Println("test()继续执行") } func main() { test() fmt.Println("main()下面代码继续执行") }
十.数组
1.数组可以存放多个同一类型的数据,数组也是一种数据类型,在GO中数组是值类型
数组的定义 var 数组名 [数组的大小]数据类型 var a [5]int 赋值 a[0] =1 a[1]=2 数组的四种定义方式 var arr1 [3]int = [3]int{1,2,3} //[1 2 3] var arr2 = [3]int{3,4,5} //[3 4 5] var arr3 = [...]int{7,8,9} //[7 8 9] var arr4 = [...]int{1:200,2:400,4:500} //[0 200 400 0 500] 1.数组的地址,也就是数组的第一个元素的地址 (数组是连续分配内存的) var arr [3]int fmt.Printf("数组的地址%p,数组的第一个地址%p",&arr,&arr[0]) //结果 数组的地址0xc000010360,数组的第一个地址0xc000010360 2.数组地址大小间隔 由数组的类型决定 int 间隔8
数组循环
var arr = [5]string{"张三","李四","王五","张二麻子","田小雨"} for i:=0; i{ fmt.Println(arr[i]) } for index , value := range arr{ fmt.Printf("下标是:%v,值是:%v\n",index,value) }
1.数组是多个相同类型数据的组合,一个数组一旦声明/定义了,其长度是固定的,不能动态变化
2.var arr []int 这时arr是一个slice切片
3.数组中的元素可以是任何数据类型,包括值类型,和引用类型 但不能混用
4.数组创建后如果没用赋值,有默认值,数值类型为0 字符串类型为”“ bool类型为 false
5.使用数组的步骤:1声明数组并开辟孔明,2 给数组各个元素赋值 3 使用数组
6.数组下标必须在指定范围内使用,否则报panic 比如 var arr [5]int 有效的下标 0-4
7.数组默认情况值传递,会进行值拷贝
8.如果想在其他函数修改数组中的值 ,可以使用引用传递(指针方式)
//生成五个随机数,将其反转 rand.Seed(time.Now().UnixNano()) var intArr [5]int var arrlen int = len(intArr) for i:=0;i{ intArr[i] = rand.Intn(100) } fmt.Println(intArr) tem :=0 for i:=0;i 2;i++{ tem = intArr[arrlen-1-i] intArr[arrlen-1-i] = intArr[i] intArr[i] = tem } fmt.Println(intArr)
十一.切片
1.切片是数组的引用,是一个引用类型
a := [5]int{76, 77, 78, 79, 80} var b []int = a[1:4] //creates a slice from a[1] to a[3] fmt.Println(b) //[77 78 79] 长度len(b) //3 容量cap(b) //4
修改切片:slice没有自己的任何数据。它只是底层数组的一个表示。对slice所做的任何修改都将反映在底层数组中。
slice 从底层来说 其实就是一个数据结构(struct 结构体)
type slice struct{ ptr *[2]int len int cap int }
切片的使用
方式1.定义一个切片,然后让切片去引用一个已经创建好的数组,比如上面的案例
var arr = [5]int{1,2,3,4,5} var slice = arr[1:] //[2 3 4 5] var slice1 = arr[:3] //[1 2 3] var slice2 = arr[:] //[1,2,3,4,5] //切片可以继续切片 var newslice = slice2[1:4] //[2 3 4]
方式2.通过make来创建切片,var 切片名 []type = make([]int,len,cap) 参数说明:type 就是数据类型 len 大小 cap 容量
var slice []int = make([]int,4,10) fmt.Println(slice) //[0 0 0 0] fmt.Println("slice len=",len(slice),"slice cap=",cap(slice)) //slice len= 4 slice cap= 10 slice[1] = 10 slice[3] = 20 fmt.Println(slice) //[0 10 0 20]
make创建的切片 操作的数组对外部不可见
方式3.定义一个切片,直接就指定具体数组,使用原理类似make的方式
var slice []string = []string{"tom","jack","mary"} fmt.Println(slice)
append 动态增加切片
var slice []int = []int{100,200,300} slice = append(slice,400,500,600) fmt.Println(slice) //[100 200 300 400 500 600] slice = append(slice,slice...) //将后面的slice的元素被打散一个个append进strss fmt.Println(slice) // [100 200 300 400 500 600 100 200 300 400 500 600
切片拷贝 copy
1.拷贝数据是相互独立的
2.必须都是切片类型
3.如果拷贝容量不够,则只会拷贝相应容量大小的
var slice []int = []int{1,2,3,4,5} var slice1 = make([]int,10) //如果此处的容量不够 则只会拷贝当前容量的值 make([]int,2) //[1 2] 会被拷贝进来 copy(slice1,slice) fmt.Println(slice) // [1 2 3 4 5] fmt.Println(slice1) // [1 2 3 4 5 0 0 0 0 0]
string 和 slice
1.string底层是一个byte数组,因此string也可以进行切片处理
str :="hello@world" slice :=str[6:] fmt.Println(slice) //world
2.string 是不可变的,也就是说不能通过 str[0] = 'z' 方式来修改字符串
3.如果需要修改字符串,可以先将string ->[]byte 或者 []rune ->修改->重写转成 string.
str :="hello@world" str1 := []byte(str) str1[0] = 'z' str = string(str1) fmt.Println(str) //如果有中文则 转换成 []rune rune按字符处理的 兼容中文 str1 := []rune(str) str1[0] = '倍' str = string(str1) fmt.Println(str) //倍ello@world
将一个斐波拉契放到一个切片
func fbn(n int)[]uint64 { fbnslice := make([]uint64,n) fbnslice[0] = 1 fbnslice[1] = 1 for i := 2;i{ fbnslice[i] = fbnslice[i-1]+fbnslice[i-2] } return fbnslice } func main() { res :=fbn(10) fmt.Println(res) // [1 1 2 3 5 8 13 21 34 55] }
十二.排序查找
//冒泡排序 【24 69 80 57 13】 1.一共会经过arr.length-1次的轮数比较,每一轮将会确定一个数的位置 2.每一轮的比较次数在逐渐减少 3.当发现前面的一个数比后面的一个数大的时候,就进行交换 //冒泡排序 func bubbleSort(arr *[10]int) { fmt.Println("排序前的数组:",*arr) for i:=0;i1;i++{ for j:=0;j 1-i;j++{ if((*arr)[j]>(*arr)[j+1]){ (*arr)[j],(*arr)[j+1] = (*arr)[j+1],(*arr)[j] } } } fmt.Println("排序后的数组:",*arr) } func main() { arr :=[10]int{3,1,4,5,6,7,8,9,10,11} bubbleSort(&arr) }
//冒泡排序优化1 假设我们现在排序ar[]={1,2,3,4,5,6,7,8,10,9}这组数据,按照上面的排序方式,第一趟排序后将10和9交换已经有序,接下来的8趟排序就是多余的,什么也没做。所以我们可以在交换的地方加一个标记,如果那一趟排序没有交换元素,说明这组数据已经有序,不用再继续下去。 func bubbleSort1(arr *[10]int) { fmt.Println("排序前的数组:",*arr) for i:=0;i1;i++{ flag := true //定义标识 for j:=0;j 1-i;j++{ if((*arr)[j]>(*arr)[j+1]){ (*arr)[j],(*arr)[j+1] = (*arr)[j+1],(*arr)[j] flag = false //说明还在发生数据交换 } } if flag{ //当内循环没有发生了数据交换 说明已经排好了 break } } fmt.Println("排序后的数组:",*arr) } func main() { arr :=[10]int{1,2,3,4,5,6,7,8,10,9} bubbleSort1(&arr) }
//冒泡排序优化2 我们可以继续优化。既我们可以记下最后一次交换的位置,后边没有交换,必然是有序的,然后下一次排序从第一个比较到上次记录的位置结束即可。 func bubbleSort2(arr *[10]int) {//优化 fmt.Println("排序前的数组:",*arr) last_pos :=len(*arr)-1 //记录每一次外部循环过程中,最后进行数据交换的位置 next_pos :=len(*arr) //记录每一次数据交换的位置 for i:=0;i1;i++{ flag := true for j:=0;j { if((*arr)[j]>(*arr)[j+1]){ (*arr)[j],(*arr)[j+1] = (*arr)[j+1],(*arr)[j] flag = false next_pos = j //交换元素,记录最后一次交换的位置 } } if flag{ break } last_pos = next_pos } fmt.Println("排序后的数组:",*arr) } func main(){ arr1 :=[10]int{1,2,5,7,4,3,6,8,9,10} bubbleSort2(&arr1) }
//冒泡排序优化3
优化二的效率有很大的提升,还有一种优化方法可以继续提高效率。大致思想就是一次排序可以确定两个值,正向扫描找到最大值交换到最后,反向扫描找到最小值交换到最前面。例如:排序数据1,2,3,4,5,6,0
func bubbleSort3(arr *[10]int){ fmt.Println("排序前的数组:",*arr) last_pos :=len(*arr)-1 //记录每一次外部循环过程中,最后进行数据交换的位置 next_pos :=len(*arr) //记录每一次数据交换的位置 n := 0 //同时找最大值的最小需要两个下标遍历 for i:=0;i1;i++{ flag := true //正向寻找最大值 for j:=n;j { if((*arr)[j]>(*arr)[j+1]){ (*arr)[j],(*arr)[j+1] = (*arr)[j+1],(*arr)[j] flag = false //加入标记 next_pos = j //交换元素,记录最后一次交换的位置 } } if flag{ break } last_pos = next_pos for j:=last_pos;j>n;j--{ if (*arr)[j] < (*arr)[j - 1]{ (*arr)[j],(*arr)[j - 1] = (*arr)[j-1],(*arr)[j] flag = false //加入标记 } } n++ //n++是在之后的循环中从第n+1个数开始。比如你第一次循环找出了最大数和最小数,那么第二次循环的时候,你就只要从第2个数和第n-1个数之间去查找最大数和最小数了 if flag{ break } } fmt.Println("排序后的数组:",*arr) } func main() { arr1 :=[10]int{1,2,5,7,4,3,6,8,9,10} bubbleSort3(&arr1) }
查找
1.顺序查找
func main() { names :=[4]string{"张三","李四","王五","朱六"} var heroName = "" fmt.Println("请输入要查找的人名:") fmt.Scanln(&heroName) index :=-1 for i:=0;i{ if names[i] == heroName{ index = i break } } if index!=-1{ fmt.Printf("找到%v,下标是%v\n",heroName,index) } }
二分查找 (必须有序)
func BinaryFind(arr *[10]int,leftIndex int,rightIndex int,findVal int){ if leftIndex>rightIndex{ fmt.Println("找不到") return } //先找到中间的下标 middle := (leftIndex + rightIndex) / 2 if(*arr)[middle] >findVal{ BinaryFind(arr,leftIndex,middle-1,findVal) }else if (*arr)[middle]<findVal{ BinaryFind(arr,middle+1,rightIndex,findVal) }else { fmt.Println("找到了,下标为",middle) } } func main() { arr :=[10]int{1,2,3,4,5,6,7,8,9,10} BinaryFind(&arr,0,len(arr)-1,5) }
十三.二维数组
func main() { var arr [4][6]int //arr :=[4][6]int{{1,2,3,4,5,6},{7,8,9,10,11,12},{13,14,15,16,17,18},{19,20,21,22,23,24}} arr[1][2] = 1 arr[2][1] = 2 arr[2][3] = 3 for i:=0;i{ for j:=0;j { fmt.Print(arr[i][j]," ") } fmt.Println() } } for _,v :=range arr{ for _,v1 :=range v{ fmt.Print(v1," ") } fmt.Println() }
var arr [2][3]int // var arr [...][3]int fmt.Println(arr) fmt.Printf("第一个地址:%p\n",&arr[0]) //0xc0000b6060 fmt.Printf("第二个地址:%p\n",&arr[1]) //0xc0000b6078 //这个是16进制 比第一个相差24 因为每个里面都有3个元素int(8) 3*8=24 这个二维数组 是 两个指针分别指向一个3位的一维数组
//求出三个班,每个班的五个学生的,每个班级的平均分和所有班级的平均分 func main() { rand.Seed(time.Now().UnixNano()) var scores [3][5]float64 for i:=0;i{ for j:=0;j { scores[i][j] = float64(rand.Intn(100)+1) } } totalSum := 0.0 num := 0 for i:=0;i { sum :=0.0 for j:=0;j { sum += scores[i][j] num++ } totalSum +=sum fmt.Printf("第%v个班的总分为%v,平均分为:%v\n",i+1,sum,sum/float64(len(scores[i]))) } fmt.Printf("总分为%v,总平均分为:%.2f\n",totalSum,totalSum/float64(num)) }
十四.map
var 变量名 map[keytype][valtype] map是无序的
key 可以是 bool,数字,string ,指针,channel 还可以是只包含前面几个类型 接口,结构体,数组 通常的为 int ,string
注意:slice,map,还有function 不可以,因为这几个没办法用 == 来判断
value 通常为 整型 浮点型,string , map struct
声明不会分配内存,只有make才会分配内存
func main() { //方式一 var a map[string]string a =make(map[string]string,10) a["a"] = "张三" a["b"] = "李四" a["c"] = "王五" fmt.Println(a) //方式二 var b = make(map[string]string) b["no1"] = "天津" b["no2"] = "bejing" fmt.Println(b) //方式三 var heroes map[string]string = map[string]string{ "her01":"松江", "her02":"张飞", } fmt.Println(heroes) heroes1 := map[string]string{ "her01":"松江", "her02":"张飞", } heroes1["her03"] = "飞刀" fmt.Println(heroes) fmt.Println(heroes1) }
//存放三个学生的name和sex arr1 :=make(map[string]map[string]string) arr1["stu01"] = make(map[string]string) arr1["stu01"]["name"] = "tom" arr1["stu01"]["sex"] = "男" arr1["stu02"] = make(map[string]string) arr1["stu02"]["name"] = "tom1" arr1["stu02"]["sex"] = "男" arr1["stu03"] = make(map[string]string) arr1["stu03"]["name"] = "tom2" arr1["stu03"]["sex"] = "男" fmt.Println(len(arr1)) //获取map的长度 fmt.Println(arr1) //map的遍历一般使用for-range for key,val:=range arr1{ fmt.Println(key) for k,v :=range val{ fmt.Printf("\tk=:%v,v=:%v\n",k,v) } }
map的删除操作:delete(map,"key") delete 是一个内置函数,如果key存在,就删除该key-value, 如果不存在,不操作,也是不会报错
arr := make(map[string]string) arr["nan1"] = "张三" arr["nan2"] = "李四" //删除 delete(arr,"nan1") //直接删除 delete(arr,"nan4") //不会报错 fmt.Println(arr) //查找 val,ok :=arr["nan2"] if ok{ //true false fmt.Println("nan2存在,他的值为%v",val) } fmt.Println("没有找到nan2这个key") //遍历 for key,val:=range arr{ fmt.Printf("key=:%v,val=:%v\n",key,val) }
删除map所有key
1.遍历一下key,逐个删除 2.map=make(...),make一个新的,让原来的成为垃圾,被GC回收
map切片 :切片里面放的是map类型
切片的数据类型如果是map,则我们称为 slice of map ,map切片,这样使用则map个数就可以动态变化了
func main() { monsters :=make([]map[string]string,2) //这是一个切片,里面放的map 切片需要make if monsters[0] == nil{ monsters[0] = make(map[string]string,2) //map 也需要make monsters[0]["name"] ="小老鼠" monsters[0]["age"] = "400" } if monsters[1] == nil{ monsters[1] = make(map[string]string,2) //map 也需要make monsters[1]["name"] ="牛魔王" monsters[1]["age"] = "500" } //下面代码错误 切片长度为2 不能赋值第三个会报错 的用切片的append /*if monsters[2] == nil{ monsters[2] = make(map[string]string,2) //map 也需要make monsters[2]["name"] ="蜈蚣精" monsters[2]["age"] = "200" }*/ newMonsters := map[string]string{ //先创建一个map "name":"玉兔精", "age": "230", } monsters = append(monsters,newMonsters)//切片添加 fmt.Println(monsters) }
map是无序的,如果需要排序,可以进行key排序,再根据key输出 map排序
func main() { map1 :=make(map[int]string,10) map1[10] = "张三" map1[1] = "李四" map1[4] = "王五" map1[8] = "王二麻子" fmt.Println(map1) var keys []int for k,_ :=range map1{ keys = append(keys,k) } sort.Ints(keys) //字符串排序 sort.Strings(keys) fmt.Println(keys) for _,v :=range keys{ fmt.Printf("map[%v]=%v\n",v,map1[v]) } }
1.map是引用类型,遵守引用类型传递的机制,在一个函数接受map,修改后,会直接修改原来的map
func modify(map1 map[int]int) { map1[10] = 900 } func main() { map1 :=make(map[int]int) map1[0] = 3 map1[5] = 4 map1[10] = 10 modify(map1) fmt.Println(map1) }
2.map的容量达到后,再想map增加元素,会自动扩容,并不会发生panic,也就是说map 能动态的增长键值对
3.map的value 也经常使用struct类型,更适合管理复杂的数据(比value是一个map更好)比如value为student结构体
type stu struct { Name string Age int Address string } func main() { students :=make(map[string]stu,10) stu1 := stu{"张三",18,"北京"} stu2 := stu{"李四",30,"上海"} students["students1"] = stu1 students["students2"] = stu2 fmt.Println(students) for i,v:=range students{ fmt.Printf("学生的编号%v,学生的姓名:%v,学生的年龄:%v,学生的地址:%v\n",i,v.Name,v.Age,v.Address) } }
十五.结构体,struct
type Cat struct { Name string Age int Color string Hobby string } func main() { cat1 := Cat{"小白",18,"白色","吃鱼"} cat1.Name ="小白白" cat1.Age = 19 fmt.Printf("名称:%v,年龄:%v,颜色:%v",cat1.Name,cat1.Age,cat1.Color) }
type Person struct { Name string Age int Scores [5]float64 ptr *int slice []int map1 map[int]int } func main() { var p1 Person fmt.Println(p1) p1.slice = make([]int,10) p1.map1 = make(map[int]int) fmt.Println(p1) }
创建结构体的四种类型
type Person struct { Name string Age int } func main() { //第一种 var p1 Person p1.Name = "张三" p1.Age = 20 //第二种 p2 := Person{"李四",13} fmt.Print(p2) //第三中 var p3 *Person = new(Person) (*p3).Name = "王五" //等价与 p3.Name = "王五" (*p3).Age = 30 //等价与 p3.Age = 30 p3.Name = "和三" p3.Age = 50 //第四种 var p4 *Person = &Person{"mary",33} //等价于 var p4 = &Person{"mary",33} //类型推导 p4 := &Person{"mary",33} (*p4).Name = "李飞" //等价于 p4.Name = "李飞" p4.Age = 40 fmt.Println(p4) }
1.结构体在内存中的字段是连续的
type Point struct { x int y int } type Rect struct { leftUp,rightDown Point } type Rect2 struct { leftUp,rightDown *Point } func main() { r1 := Rect{Point{1,2},Point{3,4}} fmt.Printf("r1.leftUp.x 地址=%p r1.leftUp.y 地址=%p r1.rightDown.x 地址=%p r1.rightDown.y 地址=%p \n", &r1.leftUp.x,&r1.leftUp.y,&r1.rightDown.x,&r1.rightDown.y)//16进制每个都相差8 //r2 有两个 *Point类型,这两个*Poin类型的本身地址也是连续的, //但他们指向的地址不一定是连续的 r2 := Rect2{&Point{10,20},&Point{30,40}} fmt.Printf("r2.leftUp.x 地址=%p r2.leftUp.y 地址=%p r2.rightDown.x 地址=%p r2.rightDown.y 地址=%p \n", &r2.leftUp.x,&r2.leftUp.y,&r2.rightDown.x,&r2.rightDown.y)//16进制每个都相差8 fmt.Printf("r2.leftUp 指向地址=%p ,r2.rightDown 指向地址=%p \n",r2.leftUp,r2.rightDown) }
2.结构体是用户单独定义的类型,和其他类型进行转换时需要有完全相同的字段(名称,个数,类型)
3.结构体进行type重新定义(相当于取别名)golang认为是新的数据类型,但是相互间可以强转
4.struct 的每个字段上,可以写一个tag,该tag可以通过反射机制获取,常见的使用场景就是序列化和反序列化 //用户接口请求返回参数
返回结构体,结构体一般大写开头,(也不能改成小写,别的包 要用)很多不适应,我们就用tag 反射机制改成小写
type Monster struct { Name string `json:"name"` Age int `json:"age"` Skill string `json:"skill"` } func main() { moster :=Monster{"张三",46,"打人"} //将moster序列号 jsonMoster,err :=json.Marshal(moster) if err!=nil { fmt.Println("json 处理错误:",err) } fmt.Println(string(jsonMoster))//{"name":"张三","age":46,"skill":"打人"} }
十六.结构体,方法
1.方法是作用在指定的数据类型上的,和指定的数据类型绑定,因此自定义类型,都可以有方法,而不仅仅是struct
type Person struct { Name string } func (p Person) test() { p.Name = "jack" fmt.Println("test()...",a.Name) } func main() { var p Person p.Name = "tom" p.test() fmt.Println("main() p.name=",p.Name) //输出 tom 作用域不同,结构体是值类型 } //1.test方法和Person类型绑定 //2.test方法只能通过 Person类型的变量来调用,而不能直接调用,也不能通过其他类型的变量来调用 //3.func (p Person)test(){....} p表示哪个Person 变量调用
type integer int func (i integer) print() { fmt.Println("i = ",i) } func (i *integer) change(){ *i = *i + 1 } func main() { var i integer = 10 i.print() i.change() fmt.Println("i = ",i) //11 }
//求圆的面积
type Circle struct { Radius float64 } func (c Circle) area() float64 { return 3.14 * c.Radius * c.Radius } func (c *Circle) area2() float64 { c.Radius = 10 return 3.14 * c.Radius * c.Radius } func main() { var c Circle c.Radius = 4.0 res := c.area() //这里的C 传的是指针,结构体的指针 fmt.Println("面积为:",res) res2 :=c.area2() fmt.Println("面积=",res2) fmt.Println("c.radius = ",c.Radius) //10 因为是引用传递 }
如果一个类型实现了 String()这个方法,那么fmt.Println fmt.Print 默认会调用这个变量的 String()进行输出
type student struct { Name string Age int } func (stu *student) String() string { str := fmt.Sprintf("Name=[%v],Age=[%v]\n",stu.Name,stu.Age) return str } func main() { stu := student{ Name: "张三", Age: 90, } fmt.Println(&stu) }
//写一个加减乘除
type Calcuator struct { Num1 float64 Num2 float64 op byte } func (ca *Calcuator) Getnum()float64 { res :=0.0 switch ca.op { case '+': res = ca.Num1+ca.Num2 case '-': res = ca.Num1-ca.Num2 case '*': res = ca.Num1*ca.Num2 case '/': res = ca.Num1/ca.Num2 default: fmt.Println("输入的操作符有误") } return res } func main() { var mn Calcuator mn.Num1 = 3.45 mn.Num2 = 5.89 mn.op = '/' res := mn.Getnum() fmt.Printf("结果为:%.2f",res) }
//函数和方法的区别
1.调用方式不一样
函数的调用方式 :函数名(实参列表)
变量.方法名(实参列表)
2.对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然
3.对于方法(如struct 的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样可以 //因为方法底层做了处理
方法的绑定是值类型, 即使用地址调用 也是值拷贝 例如:(&p).test()
1.不管调用形式如何,真正决定是值拷贝还是地址拷贝,看这个方法是和那个类型绑定
2.如果是和值类型 比如 (p Person)则是值拷贝,如果是和指针类型,比如是(p *Person) 则是地址拷贝
type Person struct { Name string } func (p Person) test() { p.Name = "jack" fmt.Println("test() = ",p.Name) } func (p *Person) test1() { p.Name = "make" fmt.Println("test() = ",p.Name) } func main() { p := Person{Name:"tom"} p.test() //test() = jack (&p).test() //test()=jack //这里仍然是值拷贝 fmt.Println("man() p.name=",p.Name) //man() p.name= tom (p).test1()//test() = make 等价于 p.test() }
十七.工厂模式
golang的结构体没有构造函数,通常可以使用工厂模式来解决这个问题
//一个结构体的声明是这样的: package model type Student struct{ Name string } 因为这里的Student的 首字母S是大写的,如果我们想在其他包创建Student的实例(比如main包) 引入model包后,就可以直接创建Student结构体的变量(实例) 但问题来了,如果首字母是小写的,比如 type student struct{} 就不行了,怎么解决。。。。。。。工厂模式解决
正常大写情况引入包
//packgo/model/student.go package model type Student struct { Name string Score float64 } //packgo/main/mian.go package main import ( "fmt" "packago/model" ) func main() { var stu = model.Student{Name:"张三",Score:58.5} fmt.Println(stu) //{张三 58.5} }
工厂模式
//packgo/model/student.go package model type student struct { Name string score float64 } //因为student首字母s是小写,只能在model 包用 func NewStudent(n string,s float64) *student { return &student{ Name: n, score: s, } } //如果score的首字母小写 func (s *student) GetScore()float64 { return s.score } //packgo/main/mian.go package main package main import ( "fmt" "packago/model" ) func main() { var stu = model.NewStudent("tom",88.8) fmt.Println(*stu)//{tom 88.8} 因为score的首字母小写 外部包 不可访问 fmt.Println("name=",stu.Name,"score=",stu.GetScore()) //name = tom score= 88.8 }
十八.封装,继承,多重继承
1.将结构体,字段的首字母小写,其他包不能使用,类似private
2.给结构体所在包提供一个工厂模式的函数,首字母大写,类似一个构造函数
3.提供一个首字母大写的Set方法,类似其他语言的public 用于对属性判断并赋值
4.提供一个首字母大写的Get方法,类似其他语言的public 用于获取属性的值
//package\model\preson.go package model import "fmt" type person struct { Name string age int //其他包不能访问 sal float64 //其他包不能访问 } func NewPerson(name string) *person { return &person{ Name: name, } } func (p *person) SetAge(age int) { if age>0 && age <150{ p.age = age }else { fmt.Println("年龄范围不正确") } } func (p *person) GetAge()int { return p.age } func (p *person) SetSal(sal float64) { if sal>=3000 && sal<30000{ p.sal = sal }else { fmt.Println("薪水范围不正确") } } func (p *person) GetSal() float64 { return p.sal } //package\mian\mian.go package main import ( "fmt" "packago/model" ) func main() { p := model.NewPerson("smith") p.SetAge(18) p.SetSal(5000) fmt.Println(p) //&{smith 18 5000} fmt.Println(p.Name) //smith fmt.Println("age=",p.GetAge(),"sal=",p.GetSal()) //age= 18 sal= 5000 }
//继承 解决代码复用
1.当多个结构体存在相同的属性,和方法时,可以从这些结构体中抽象出结构体,在该结构体中定义这些相同的属性和方法
其他结构体不需要重新定义这些属性和方法,只需要嵌套一个匿名结构体即可,
golang中,如果一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承的特性
type Strudent struct { //同有特性 Name string //学生姓名 Age int //学生年龄 Score int //学生成绩 } func (stu *Strudent) ShowInfo() { //显示学生成绩 同有特性 fmt.Printf("学生姓名:%v,学生年龄:%v,学生成绩:%v\n",stu.Name,stu.Age,stu.Score) } func (stu *Strudent) SetScore(score int){//设置学生成绩 同有特性 stu.Score = score } func (stu *Strudent) GetSum(n1 int,n2 int)int { return n1+n2 } //小学生 type Pupil struct { Strudent //嵌入了匿名结构体 } func (p *Pupil) testing() { //特有的 fmt.Println("小学生正在考试.......") } //大学生 type Graduate struct { Strudent //嵌入了匿名结构体 } func (p *Graduate)testing() {//特有的 fmt.Println("大学生正在考试.......") } func main() { pupil := &Pupil{} pupil.Strudent.Name = "tom.." pupil.Strudent.Age = 8 pupil.testing() pupil.Strudent.SetScore(78) pupil.Strudent.ShowInfo() fmt.Println("小学生做加法:",pupil.Strudent.GetSum(8,5)) graduate := &Graduate{} graduate.Strudent.Name = "may.." graduate.Strudent.Age = 20 graduate.testing() graduate.Strudent.SetScore(99) graduate.Strudent.ShowInfo() fmt.Println("大学生做加法:",graduate.Strudent.GetSum(80,50)) }
1.结构体可以使用嵌套匿名结构体的所有字段和方法,即:首字母大写或者小写的字段和方法都可以使用
2.匿名结构体字段访问可以简化
3.当结构体和匿名结构体有相同的字段或者方法时,编辑器采用 就近访问原则,如希望访问匿名结构体的字段和方法,可以通过匿名结构体来区分
type A struct { Name string age int } func (a *A)SayOK() { fmt.Println("A SayOk",a.Name) } func (a *A)hello() { fmt.Println("A hello",a.Name) } type B struct { A Name string } func main() { var b B b.Name = "may" b.A.Name = "tome" b.A.age = 19 //等价于 b.age = 19 b.A.hello() //等价于 b.hello() b.A.SayOK() //等价于 b.SayOK() }
(1)当我们直接通过b 访问字段或者方法时,其执行流程如下,比如b.Name
(2)编辑器会先看b 对应的类型有没有Name,如果有,则直接调用B类型的Name字段
(3)如果没用就去看B中嵌套的结构A 有没有这个Name 字段 如果有就调用,如果没用 继续找A结构体里面的嵌套
4.结构体嵌入两个或者多个匿名结构体,如两个匿名结构体有相同的字段和方法(同事结构体本身没用同名的字段和方法)
在访问时,就必须明确指定匿名结构体名字,否则编辑报错
type A struct { Name string Age int } func (a *A)Show(){ fmt.Println("A 的Name=",a.Name,"A 的Age=",a.Age) } type B struct { Name string Age int } type C struct { A B } func main() { var c C c.A.Name = "tom" c.B.Name = "msy" c.A.Age = 59 c.B.Age = 10 c.Show() }
5.如果一个struct 嵌套了一个有名的结构体,这种模式就是组合,如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字
type D struct{ a A } //调用 var d D d.a.Name = "jack"
6.嵌套匿名结构体后,也可以在创建结构体变量时,直接指定各个匿名结构体的字段值。下面是多重继承,尽量不用使用多重继承
type Goods struct { Name string Price float64 } type Brand struct { Name string Address string } type Tv struct { Goods Brand } type Tv1 struct { *Goods *Brand } func main() { var tv =Tv{ Goods{Name:"电视机",Price:5999}, Brand{"华为","山东"}, } fmt.Println(tv) var tv1 =&Tv1{ &Goods{Name:"电视机",Price:5999}, &Brand{"华为","山东"}, } fmt.Println(*tv1.Goods,*tv1.Brand) }
7.结构体里面也可以匿名基本数据类型
type A struct { Name string Age int } type B struct { A int } func main() { var b B b.int = 9 b.Name = "tom" b.Age = 85 fmt.Println(b) }
十九.接口 interface
interface 类型可以定义一组方法,但是这些方法不需要实现,并且interface不能包含任何变量,到莫个自定义类型,比如结构体,要使用的时候,在根据具体情况把这些方法写出来
1.接口里面所有方法都没有方法体,即接口的方法都是没用实现的方法。接口体现了程序设计的多态和高内聚低耦合的思想
2.GOlang中接口,不需要显式的实现,只要一个变量,含有接口类型中的所有方法,那么这个变量九实现了这个接口,作为的鸭子类型,长的像鸭子,会下蛋,会鸭叫,会游泳的 就鸭子
type Usb interface { Start() Stop() } type Phone struct { } //让Phone 实现 Usb接口的方法 func (p Phone)Start() { fmt.Println("手机开始工作....") } func (p Phone)Stop() { fmt.Println("手机停止工作") } type Camera struct { } func (p Camera)Start() { fmt.Println("相机开始工作....") } func (p Camera)Stop() { fmt.Println("相机停止工作") } //计算机 type Computer struct { } //只要是USB接口 都可以可以用 需要实现USB接口的所有方法 func (c Computer)Working(usb Usb) { //这里体现了多态 usb变量会根据传入的实参,来判断到底是Phone 还是Camera usb.Start() usb.Stop() } func main() { computer :=Computer{}//电脑 phone :=Phone{}//手机 camera :=Camera{}//相机 computer.Working(phone) computer.Working(camera) }
1.接口本身不能创建实例,但可以指向一个实现了该接口的自定义类型的变量(实例)
type Ainterface interface { Say() } type Binterface interface { Hello() } type Stu struct { Name string } func (stu Stu)Say() { fmt.Println("stu Say()....") } type integer int //普通类型实现接口 func (i integer)Say() { fmt.Println("integer Say().......") } type Monster struct { } //Monster 实现 B接口 func (m Monster)Hello() { fmt.Println("Monster Hello()....") } //Monster 实现A接口 func (m Monster)Say() { fmt.Println("Monster Say()......") } func main() { var stu Stu var a Ainterface= stu a.Say() //普通类型实现接口 var i integer = 10 var b Ainterface = i b.Say() //Monster 实现了 A 和 B 两个接口 var monster Monster var a1 Ainterface = monster var a2 Binterface = monster a1.Say() a2.Hello() }
2.接口中所有方法都没用方法体,即都没有实现的方法
3.在golang中 一个自定义类型需要将木个接口所有的方法都实现,我们说这个自定义类型实现了该接口
4.一个自定义类型只有实现了木个接口,才能将该自定义类型的实例(变量)赋值给接口类型
5.只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型
6.一个自定义类型可以实现多个接口
7.一个接口(比如A),可以继承多个别的接口(比如B,C)这时如果要实现A接口,也必须将B,C接口的方法也全部实现,但B和C 不能有相同的方法名
type Binterface interface { test01() } type Cinterface interface { test02() } type Ainterface interface { Binterface Cinterface test03() } type Stu struct { } func (stu Stu)test01() { fmt.Println("Binterface test01()......") } func (stu Stu)test02() { fmt.Println("Cinterface test02()......") } func (stu Stu)test03() { fmt.Println("Ainterface test03()......") } func main() { var stu Stu var a Ainterface = stu a.test02() }
8. interface类型默认是一个指针,(引用类型),如果没用对interface初始化就使用,那么会输出nil
9.空接口interface{}没有任何方法,所以所有类型都实现了空接口,可以把任何类型的变量赋值给空接口
//实现对Hero结构体切片的排序:sort.Sort(data Interface) sort.Ints() 对一个int切片进行排序 (实用对学生成绩排序)
func main() { slice := []int{1,5,7,3,4,6,88,22,55} sort.Ints(slice)//对int类型的切片进行排序 fmt.Println(slice) }
package main import ( "fmt" "math/rand" "sort" ) type Hero struct { Name string Age int Score float64 } //声明一个Hero结构体切片类型 type HeroSlice []Hero //切片里面的val 是Hero结构体 //实现Interface接口 func (hs HeroSlice) Len() int{ return len(hs) } //less方法就是决定你使用什么标准进行排序 //1.按hero的年龄从小到大排序 func (hs HeroSlice) Less(i,j int) bool{ //return hs[i].Age > hs[j].Age //对Age排序 //return hs[i].Name > hs[j].Name //对Name排序 return hs[i].Score > hs[j].Score //对Name排序 } func (hs HeroSlice)Swap(i,j int) { hs[i],hs[j] = hs[j],hs[i] } func main() { var heroes HeroSlice for i:=0;i<=10;i++ { hero :=Hero{ Name: fmt.Sprintf("硬汉~%d",rand.Intn(100)), Age: rand.Intn(100), Score: float64(rand.Intn(100)), } heroes = append(heroes,hero) } fmt.Println("排序前:",heroes) //调用sort.Sort sort.Sort(heroes) //用这个排序必须实现接口,实现接口里面的三个方法,Len(),Less(),Swap(), fmt.Println("排序后:",heroes) }
//对学生成绩排序
package main import ( "fmt" "math/rand" "sort" ) type Student struct { Name string //学生姓名 Age int //学生年龄 Score float64 //学生分数 } type Stuslice []Student //结构体切片->切片里面放的结构体 func (stu Stuslice)Len()int { return len(stu) } func (stu Stuslice)Less(i,j int)bool { return stu[i].Score>stu[j].Score } func (stu Stuslice)Swap(i,j int) { stu[i],stu[j] = stu[j],stu[i] } func main() { //结构体切片排序 var stuslic Stuslice for i:=0;i<=10;i++ { hero :=Student{ Name: fmt.Sprintf("张三~%d",rand.Intn(100)), Age: rand.Intn(100), Score: float64(rand.Intn(100)), } stuslic = append(stuslic,hero) } fmt.Println("排序前",stuslic) sort.Sort(stuslic) fmt.Println("排序后",stuslic) //整型切片排序 var slic []int for i:=0;i<=10;i++{ s :=rand.Intn(100) slic = append(slic,s) } fmt.Println(slic) sort.Ints(slic) fmt.Println(slic) }
//一个小猴子,继承老猴子的爬树,又要有鸟的飞行能力,又要有鱼的游泳能力
package main import "fmt" //定义一个猴子的结构体 type Monkey struct { Name string //猴子名称 } func (mon *Monkey)Climbing() { fmt.Println(mon.Name,"生来会爬树....") } //小猴子结构体 type LittleMonkey struct { Monkey //小猴子继承了猴子的结构体 } //鸟的飞行接口 type BridAble interface { Flying() //飞行能力 } //小猴子实现飞行接口 func (mon *LittleMonkey)Flying() { fmt.Println(mon.Name,"通过学习会飞行....") } //鱼游泳的接口 type FishAble interface { Swimming() } //小猴子实现游泳接口 func (mon *LittleMonkey)Swimming() { fmt.Println(mon.Name,"通过学习会游泳....") } func main(){ mokey := LittleMonkey{ Monkey{ Name:"悟空", }, } fmt.Printf("mokey的类型是:%T\n",mokey) mokey.Climbing() mokey.Flying() mokey.Swimming() }
1)当A结构体继承了B结构体,那么A结构体就自动继承了B结构体的字段和方法,并且可以直接使用
2)当A结构体需要扩展功能,同时又不希望去破坏继承关系,则可以去实现木个接口即可,因此我们可以认为:实现接口是对继承机制的补充
1.接口和继承解决的问题不同
继承的价值在于:解决代码的复用性和可维护性
接口的价值在于:设计,设计好各种规范(方法),让其他自定义类型去实现这些方法
2.接口比继承更加灵活
接口比继承更加灵活,继承是满足is-a的关系,而接口是满足like-a的关系
3.接口在一定程度上实现代码解耦
二十.多态
1.变量(实例)具有多种形态,面向对象的第三大特征,在GO语言,多态的特征是通过接口实现的,可以按照统一的接口来调用不同的实现,这时接口变量就呈现不同的形态
2.前面的Usb接口案例,Usb usb既可以接受手机变量,又可以接受相机变量,就体现了Usb接口的多态特性
1)多态参数
在前面的Usb接口案例,Usb usb 既可以接受手机变量,又可以接受相机变量,就体现了Usb接口多态
2)多态数组
演示一个案例:给Usb数组中,存放Phone结构体,和 Camera结构体变量,Phone还有一个特有的方法call() 请遍历Usb数组,如果是Phone变量,除了调用Usb接口声明的方法外,还需要调用Phone 特有方法Call
type Usb interface { Start() Stop() } type Phone struct { Name string } //让Phone 实现 Usb接口的方法 func (p Phone)Start() { fmt.Println("手机开始工作....") } func (p Phone)Stop() { fmt.Println("手机停止工作") } type Camera struct { Name string } func (p Camera)Start() { fmt.Println("相机开始工作....") } func (p Camera)Stop() { fmt.Println("相机停止工作") } //计算机 type Computer struct { } //只要是USB接口 都可以可以用 需要实现USB接口的所有方法 func (c Computer)Working(usb Usb) { //这里体现了多态 usb变量会根据传入的实参,来判断到底是Phone 还是Camera usb.Start() usb.Stop() } func main() { //定义一个Usb接口数组,可以存放Phone 和 Camear 的结构体变量 //这里就体现出了多态数组 var usbArr [3]Usb usbArr[0] = Phone{Name:"华为"} usbArr[1] = Phone{Name:"小米"} usbArr[2] = Camera{Name:"佳能"} fmt.Println(usbArr) //[{华为} {小米} {佳能}] }
二十一.类型断言
type Point struct { x int y int } func main() { var a interface{} var point Point = Point{1,2} a = point // 空接口可以接受任何类型的数据 var b Point //b = a 错误的 空接口赋值给结构体 b = a.(Point) //类型断言 fmt.Println(b) //{1 2} }
类型断言,由于接口是一般类型,不知道具体类型,如果要转成具体类型,就需要使用类型断言。
func main() { var t float64 = 2.25 var x interface{} x = t //接口指向了 float64 y := x.(float64) fmt.Printf("y 的类型是 %T 值是=%v",y,y) }
对于上面代码
在进行类型断言时,如果类型不匹配,就会报panic 因此进行类型断言时,要确保原来的空接口指向的就是断言的类型
如果在断言时,带上了检测机制,如果成功就OK,否则也不要报panic
//第一个 func main() { var t float64 = 2.25 var x interface{} x = t //接口指向了 float64 y,ok := x.(float64) if ok{ fmt.Println("断言成功") }else { fmt.Println("断言类型不对") } fmt.Printf("y 的类型是 %T 值是=%v",y,y) }
//第二个 type Stu struct { Name string Age int } func main() { stu := Stu{Name:"tom",Age:59} var a interface{} a = stu var stu1 Stu stu1 = a.(Stu) fmt.Println(stu1) }
给Phone结构体增加一个特有的方法call() 当Usb接口接受的是Phone 变量时,还需要调用call方法
type Usb interface { Start() Stop() } type Phone struct { Name string } //让Phone 实现 Usb接口的方法 func (p Phone)Start() { fmt.Println(p.Name,"手机开始工作....") } func (p Phone)Stop() { fmt.Println(p.Name,"手机停止工作") } func (p Phone)Call() { fmt.Println(p.Name,"手机在打电话") } type Camera struct { Name string } func (p Camera)Start() { fmt.Println(p.Name,"相机开始工作....") } func (p Camera)Stop() { fmt.Println(p.Name,"相机停止工作") } //计算机 type Computer struct { } //只要是USB接口 都可以可以用 需要实现USB接口的所有方法 func (c Computer)Working(usb Usb) { //这里体现了多态 usb变量会根据传入的实参,来判断到底是Phone 还是Camera usb.Start() //类型断言 if phone,ok := usb.(Phone);ok{ phone.Call() } usb.Stop() } func main() { //定义一个Usb接口数组,可以存放Phone 和 Camear 的结构体变量 //这里就体现出了多态数组 var usbArr [3]Usb usbArr[0] = Phone{Name:"华为"} usbArr[1] = Phone{Name:"小米"} usbArr[2] = Camera{Name:"佳能"} //遍历数组 var computer Computer for _,v := range usbArr{ computer.Working(v) fmt.Println() } fmt.Println(usbArr) //[{华为} {小米} {佳能}] }
//编写一个函数,可以判断输入的参数是什么类型 type Student struct { } //编写一个函数,可以判断输入的参数是什么类型 func TypeJudge(items... interface{}) { for index,v :=range items{ index++ switch v.(type) { case bool: fmt.Printf("第%v个参数是 bool 类型,值是%v\n",index,v) case int32: fmt.Printf("第%v个参数是 int32 类型,值是%v\n",index,v) case int64: fmt.Printf("第%v个参数是 int64 类型,值是%v\n",index,v) case float32: fmt.Printf("第%v个参数是 float32 类型,值是%v\n",index,v) case float64: fmt.Printf("第%v个参数是 float64 类型,值是%v\n",index,v) case string: fmt.Printf("第%v个参数是 string 类型,值是%v\n",index,v) case Student: fmt.Printf("第%v个参数是 Student 类型,值是%v\n",index,v) case *Student: fmt.Printf("第%v个参数是 *Student 类型,值是%v\n",index,v) default: fmt.Printf("第%v个参数是 不确定 类型,值是%v\n",index,v) } } } func main() { var n1 float32 = 1.1 var n2 float64 = 5.69 var n3 bool = true var n4 int32 = 5 var n5 int64 = 9 var n6 string = "hello" stu1 :=Student{} stu2 :=&Student{} TypeJudge(n1,n2,n3,n4,n5,n6,stu1,stu2) }
二十二.文件操作
流:数据在数据源(文件)和程序(内存)之间经历的路径
输入流:数据从数据源(文件)到程序(内存)的路径
输出流:数据从程序(内存)到数据源(文件)的路径
os.File 封装了所有文件相关操作,File是一个结构体
func main() { //打开文件 file,err := os.Open("c:/ebay.txt") if err != nil{ fmt.Println("打开文件失败 err=",err) } fmt.Printf("file=%v",*file) //关闭文件 err = file.Close() if err != nil{ fmt.Println("关闭文件失败 err=",err) } }
1)读取文件的内容并显示在终端(带缓冲区的方式),使用 os.Open file.Close, bufio.NewReader(), reader.ReadString
func main() { //打开文件 file,err := os.Open("c:/go/golang.txt") if err != nil{ fmt.Println("打开文件失败 err=",err) } defer file.Close() ////关闭文件 否则会有内存泄漏 reader := bufio.NewReader(file) //缓冲读取 默认大小 4096 //循环读取文件内容 for{ str,err := reader.ReadString('\n') if err == io.EOF{ //io.EOF 表示文件的末尾 break } fmt.Print(str) } fmt.Println("文件读取结束") }
2)读取文件的内容并显示在终端,(使用ioutil一次将整个文件读入到内存中),这种方式适用于文件不大的情况,相关方法和函数(ioutil.ReadFile)
func main() { file :="c:/go/golang.txt" content,err :=ioutil.ReadFile(file)//返回的是一个byte切片 if err != nil{ fmt.Println("读取文件失败,err=",err) } fmt.Printf("%v",string(content)) }
打开文件的模式
const ( O_RDONLY int = syscall.O_RDONLY // 只读模式打开文件 O_WRONLY int = syscall.O_WRONLY // 只写模式打开文件 O_RDWR int = syscall.O_RDWR // 读写模式打开文件 O_APPEND int = syscall.O_APPEND // 写操作时将数据附加到文件尾部 O_CREATE int = syscall.O_CREAT // 如果不存在将创建一个新文件 O_EXCL int = syscall.O_EXCL // 和O_CREATE配合使用,文件必须不存在 O_SYNC int = syscall.O_SYNC // 打开文件用于同步I/O O_TRUNC int = syscall.O_TRUNC // 如果可能,打开时清空文件 )
func OpenFile(name string, flag int, perm FileMode) (file *File, err error)
OpenFile是一个更一般性的文件打开函数,大多数调用者都应用Open或Create代替本函数。它会使用指定的选项(如O_RDONLY等)、指定的模式(如0666等)打开指定名称的文件。如果操作成功,返回的文件对象可用于I/O。如果出错,错误底层类型是*PathError。
name:文件路径 fiag: 上述模式 perm:权限 FileMode 在WINDS下面无效,是用户Linux下面的
const ( // 单字符是被String方法用于格式化的属性缩写。 ModeDir FileMode = 1 << (32 - 1 - iota) // d: 目录 ModeAppend // a: 只能写入,且只能写入到末尾 ModeExclusive // l: 用于执行 ModeTemporary // T: 临时文件(非备份文件) ModeSymlink // L: 符号链接(不是快捷方式文件) ModeDevice // D: 设备 ModeNamedPipe // p: 命名管道(FIFO) ModeSocket // S: Unix域socket ModeSetuid // u: 表示文件具有其创建者用户id权限 ModeSetgid // g: 表示文件具有其创建者组id的权限 ModeCharDevice // c: 字符设备,需已设置ModeDevice ModeSticky // t: 只有root/创建者能删除/移动文件 // 覆盖所有类型位(用于通过&获取类型位),对普通文件,所有这些位都不应被设置 ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice ModePerm FileMode = 0777 // 覆盖所有Unix权限位(用于通过&获取类型位) )
1.创建一个新文件,写入内容 5句"hello,Gardon"
2.打开一个存在的文件中,将来源的内容覆盖成新的内容10句"你好,牛魔王"
3.打开一个存在文件,在原来的内容追加内容"ABC!ENGLIST!"
4.打开一个文件,读出里面的内容,并追加十句"你好,北京"
func main() { filepath :="c:/go/golang1.txt" //file,err :=os.OpenFile(filepath,os.O_WRONLY|os.O_CREATE,0666) //1.写 和 创建 的模式 //file,err :=os.OpenFile(filepath,os.O_WRONLY|os.O_TRUNC,0666) //2.写 和 清空 的模式 //file,err :=os.OpenFile(filepath,os.O_WRONLY|os.O_APPEND,0666) //3.写 和 追加 的模式 file,err :=os.OpenFile(filepath,os.O_RDWR|os.O_APPEND,0666) //.读写 和 追加 的模式 if err != nil{ fmt.Printf("打开文件错误,err=",err) } //及时关闭file句柄 defer file.Close() reader :=bufio.NewReader(file) for { str,err := reader.ReadString('\n') if err == io.EOF{ break } fmt.Print(str) } //str :="hello,Gardon\r\n" //str :="你好,牛魔王\r\n" //str :="ABC!ENGLIST!" str :="你好,北京\r\n" //写入时,使用带缓存的 *Writer writer := bufio.NewWriter(file) for i:=0;i<5;i++{ writer.WriteString(str) } //因为writer是带缓存,因此在调用writerString方法时,其实内容是写入了缓存 writer.Flush() //将缓存的内容写入到文件 }
将一个文件内容写入到另一个文件中(文件都已经存在)
func main() { filepath1 :="c:/go/golang1.txt" filepath2 :="c:/go/golang2.txt" data,err := ioutil.ReadFile(filepath1) //读出一个文件的所有内容,只适合小文件内容的 if err != nil{ fmt.Println("读取文件失败 err=",err) return } err = ioutil.WriteFile(filepath2,data,0666) if err != nil{ fmt.Println("写入错误 err=",err) } }
golang判断文件或文件夹是否存在的方法为:os.Stat() 函数返回的错误值进行判断:
1)如果返回的错误为nil说明文件或文件夹存在
2)如果返回的错误类型使用os.IsNoExist()判断为true 说明文件或文件夹不存在
3)如果返回的错误为其他类型,则不确定是否存在
func PathExists(paths string)(bool,error) { _,err := os.Stat(paths) if err == nil{ return true,nil } if os.IsNotExist(err){//判断错误是不是不存在的错误 return false,nil } return false,nil } func main() { //filepath1 :="c:/go/golang1.txt" filepath2 :="c:/go/golang3.txt" bos,err :=PathExists(filepath2) fmt.Println(bos,err) }
将一个图片/电影/视频 拷贝到另一个目录下
func Copy(dst Writer, src Reader) (written int64, err error)
package main import ( "bufio" "fmt" "io" "os" ) func CopyFile(dstFileName string,srcFileName string)(writtern int64,err error) { srcfile,err := os.Open(srcFileName) if err !=nil{ fmt.Println("打开文件错误,err=",err) } defer srcfile.Close() reader := bufio.NewReader(srcfile) dstFile,err :=os.OpenFile(dstFileName,os.O_WRONLY|os.O_CREATE,0666) if err != nil{ fmt.Println("打开文件失败err=",err) } defer dstFile.Close() writer :=bufio.NewWriter(dstFile) return io.Copy(writer,reader) } func main() { srcFile :="1.jpg" dstFile :="./model/2.jpg" _,err :=CopyFile(dstFile,srcFile) if err != nil{ fmt.Println("拷贝失败err=",err) } }
统计英文,数字,空格,和其他字符的数量
说明:统计一个文件中含有的英文,数字,空格及其它的字符数量
//定义一个结构体用户保存统计结构 type CharCount struct { ChCount int //记录英文个数 NumCount int //记录数字个数 SpaceCount int //记录空格个数 OtherCount int //记录其他字符个数 } func main() { filepath :="abc.txt" file,err :=os.Open(filepath) if err != nil{ fmt.Println("打开文件错误") return } defer file.Close() var count CharCount //记录结果的结构体 reader :=bufio.NewReader(file) for{ str,err := reader.ReadString('\n') if err == io.EOF{ break } //遍历str 进行统计 for _,v := range str{ switch { case v>'a' && v<'z': fallthrough //穿透 case v>'A' && v<'Z': count.ChCount++ case v == ' ' || v == '\t': count.SpaceCount++ case v >='0' && v<=9: count.NumCount++ default: count.OtherCount++ } } } fmt.Printf("数字的字符个数:%v,字符的个数为:%v,空格的字符为:%v,其他字符的个数:%v",count.NumCount,count.ChCount,count.SpaceCount,count.OtherCount) }
二十三.命令行参数
基本介绍
os.Args 是一个string的切片,用来存储所有的命令行参数
请编写一段代码,可以获取命令行各个参数
func main() { fmt.Println("命令行的参数有:",len(os.Args)) for i,v:=range os.Args{ fmt.Printf("args[%v]=%v\n",i,v) } }
flag包用来解析命令行参数
说明:前面的方式是比较原生的方式,对解析参数不是特别的方便,特别是带有指定参数形式的命令行。
比如:cmd>main.ext -f c:/aaa.txt -p 200 -u root 这样的形式命令行,go设计者给我们提供了 flag 包,可以方便的解析命令行参数,而且参数顺序可以随意
请编写一段代码获取各个输入参数
func main() { //定义几个变量,用于接受命令行参数 var user string var pwd string var host string var port int //&user 就是接受用户命令行中输入的 -u 后面的参数值 //"u",就是 -u 指定参数 //"",默认值 //"用户名,默认为空" 说明 flag.StringVar(&user,"u","","用户名,默认为空") flag.StringVar(&pwd,"p","","密码,默认为空") 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) }
二十四.json数据格式
结构体序列化
type Monster struct { Name string Age int Birthday string Sal float64 Skill string } func main() { monster :=Monster{ Name: "牛魔王", Age: 500, Birthday: "2022-01-05", Sal: 8000.0, Skill: "牛魔权", } data,err :=json.Marshal(&monster)//返回一个byte切片 if err != nil{ fmt.Println("序列化错误 err=",err) } fmt.Print("monster序列化的结果:%v",string(data)) }
结果:
monster序列化的结果:%v{"Name":"牛魔王","Age":500,"Birthday":"2022-01-05","Sal":8000,"Skill":"牛魔权"}