go语言
1.go基础
1.数据类型转换
a:=3//int b:=5.0//float64 c:=float64(a) //将a强转为float d:=int(b)//将b强转为int
2.输出格式
fmt.Println()//打印换行 fmt.Printf()//格式化输出 fmt.Print()//打印输出 fmt.Scanln(&x,&y) //变量取地址 &变量 指针/地址来修改和操作变量 fmt.Scanln()//接收输入 Scan | fmt.Scanf()//接收输入 格式化输入 作业 fmt.Scan()//接收输入 作业
赋值运算符面试题
两个变量a,b,不使用中间变量,让他们两的值交换
var a int=10 var b int=20 a=a+b b=a-b a=a-b
3.switch语句
switch score { case 90: fmt.Println("A") case 80: fmt.Println("B") default: fmt.Println("D") }
fallthrough 贯穿;直通
switch默认情况下匹配成功后就不会执行其他case,如果我们需要执行后面的case,可以使用fallthrough穿透case使用fallthrough会强制后面的case语句,fallthrough不会判断下一条case的表达式结果是否为true。break可以终止穿透。
go程序设计中一般不主张使用goto语句,以免造成程序流程的混乱,使理解和调试程序都产生困难。
var n int =30 fmt.Println("ok1") if n>20{ goto label1 } fmt.Println("ok2") fmt.Println("ok3") fmt.Println("ok4") label1: fmt.Println("ok5") fmt.Println("ok6") fmt.Println("ok7")
4.string
获取string的长度 : len(str)
5.for
for range循环,遍历数组、切片。。。
6.函数
func 函数名(参数,参数....) 函数调用后的返回值{ 函数体 } //可变参数函数 func 函数名(参数 ...参数类型) 返回值{ 函数体 }//一个函数列表中只能有一个可变参数 基本数据类型和数组默认都是值传递的,即进行值拷贝。在函数内修改,不会影响到原来的值。 如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量。 在Go中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用 函数既然是一种数据类型,因此在Go中,函数可以作为形参,并且调用 在Go中myInt和int虽然都是int类型,但是go认为myInt和int是两个类型 type myInt int var num1 myInt var num2 int num1=40 num2=num1//会报错,无法将myInt的类型的数据交给int
为了让其它包的文件使用Cal函数,需要将c大写类似其他语言的public
(1)在给一个文件打包时,该包对应一个文件夹,比如这里的utils文件夹对应的包名就是utils,文件的包名通常和文件所在的文件夹名一致,一般为小写字母。
(2)当一个文件要使用其它包涵数或变量时,需要先引入对应的包
(3)如果包名较长,Go支持给包取别名,注意细节:取别名后,原来的报名就不能使用了
(4)在同一包下,不能有相同的函数名(也不能有相同的全局变量名),否则报重复定义。
(5)如果你要编译成一个可执行程序文件,就需要将这个包声明为main,即package main。这个就是一个语法规范,如果你是写一个库,包名可以自定义。
GO不支持传统的函数重载
2.init函数
init函数,通常可以在init函数中完成初始工作
func init(){ fmt.Println("init()...") } func main(){ fmt.Println("main()...") } /* 输出结果: init()... main()... */ (1)如果一个文件同时包含全局变量定义,init函数和main函数,则执行的流程:全局变量定义-》init函数-》main函数 (2)init函数最主要的作用,就是完成一些初始化的工作
3.匿名函数
在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次
res1 :=func(n1 int,n2 int)int{ return n1+n2 }(10,20) //将匿名函数func(n1 int,n2 int)int赋给a变量,则a的数据类型就是函数类型,此时,我们可以通过a完成调用。
将匿名函数赋给一个变量(函数变量),再通过该变量来调用匿名函数
如果将匿名函数赋给一个全局变量,那么这个匿名函数就成为一个全局匿名函数,可以在程序有效。
7.值传递
值传递,传递的是数据副本,修改数据,对原始的数据没有影响。如基础数据类型、array、struct
8.引用类型传递
切片是可以扩容的数组,切片是引用类型数据
s1 := []int{1,2,3,4} 引用类型传递传递的是地址如切片、map、chan、。。。。 不管是值传递还是引用传递,传递给函数的都是变量的副本,不同的是,值传递的是值的拷贝,引用传递的是地址的拷贝,一般来说,地址拷贝效率高,因为数据量小,而值拷贝决定拷贝的数据大小,数据越大,效率越低。
9.变量的作用域
局部变量:函数内部定义的变量,叫做局部变量
全局变量:函数外部定义的变量,叫做全局变量
10.指针
var ptr *int = &i /* 1.ptr是一个指针变量 2.ptr的类型*int 3.ptr本身的值&i */ //获取指针类型所指向的值,使用:*,如var ptr *int,使用*ptr获取ptr指向的值。 var num int=9 var ptr *int ptr=&num *ptr=10 //这里修改时,会导致num的值变化
11.闭包
闭包就是一个函数和与其相关的引用环境组合的一个整体(实体)
(1)AddUpper是一个函数,返回的数据类型是fun(int)int
(2)闭包的说明
返回的是一个匿名函数,但是这个匿名函数引用到函数外的n,因此这个匿名函数就和n形成一个整体,构成闭包。
(3)闭包是类,函数是操作,n是字段。函数和它使用到n构成闭包。
(4)当我们反复调用f函数时,因为n是初始化一次,因此每调用一次就进行累计。
strings.HasSuffix:判断s是否有后缀字符串suffix
func HasSffix(s,suffix string) bool
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("text"))//text.jpg } //返回的匿名函数和makeSuffix(suffix string)的suffix变量形成了闭包 //如果使用闭包完成好处是只需传入一次后缀即可
12.defer
1.为什么需要defer
在函数中,程序员经常需要创建资源(比如:数据库连接、文件句柄、锁等),为了在函数执行完毕后及时的释放资源,Go的设计者提供defer(延时机制)
func sum(n1 int,n2 int) int { //当执行到defer时,暂时不执行,会将defer后面的语句压入到独立的栈(defer栈中) //当函数执行完毕后,再从defer栈按照先入后出的方式出栈,执行 defer fmt.Println("ok1 n1=",n1)//3.ok1 n1=10 defer fmt.Println("ok2 n2=",n2)//2.ok2 n2=20 res :=n1+n2 defer fmt.Println("ok3 n3=",res)//1.ok3 res= 30 return res } func main(){ res:=sum(10,20) fmt.Println("res=",res)//4.res=30 }
在defer将语句放入到栈时,也会将相关的值拷贝同时入栈
func sum(n1 int,n2 int) int { //当执行到defer时,暂时不执行,会将defer后面的语句压入到独立的栈(defer栈中) //当函数执行完毕后,再从defer栈按照先入后出的方式出栈,执行 defer fmt.Println("ok1 n1=",n1)//3.ok1 n1=10 defer fmt.Println("ok2 n2=",n2)//2.ok2 n2=20 n1++//n1=11 n2++//n2=21 res :=n1+n2 defer fmt.Println("ok3 n3=",res)//1.ok3 res= 32 return res } func main(){ res:=sum(10,20) fmt.Println("res=",res)//4.res=32 }
defer最主要的价值是在当函数执行完毕后可以及时的释放函数创建的资源
1)在golang编程中通常的做法是,创建资源后,比如(打开了文件,获取了数据库的连接,或者是锁资源),可以执行defer file.Close() defer connect.Close()
2)在defer后,可以继续使用创建资源
3)当函数完毕后,系统会依次从defer栈中,取出语句,关闭资源
13.字符串常用的函数
rand.Intn()随机生成数字
1)统计字符串的长度,按字节len(str)
str:="hello北" fmt.Println("str len=",len(str)) //golang的编码统一为utf-8(ascii的字符(字母和数字)占一个字节,汉字占用三个字节) //字符串遍历,同时处理有中文的问题 r:=[]rune(str) r:=[]rune(str) for i:=0;i<len(r);i++{ fmt.Printf("字符=%c\n",r[i]) }
2)字符串转整数
n,err:=strconv.Atoi("123") if err != nil{ fmt.Println("转换错误",err) }else{ fmt.Println("转换成功,转换结果为:",n) }
3)整数转字符串
str=strconv.Itoa(12345) fmt.Println("str=%v,str=%T",str,str)
4)字符串转[]byte
//字符串转[]byte var bytes=[]byte("hello go") //[]byte转字符串 str=string([]byte{97,98,99})
5)10进制转2,8,16进制
str=strconv.FormatInt(123,2)
6)查找子串是否在指定的字符串中:string.Contains("seafood","foo")
7)统计一个字符串有几个指定的子串:strings.Count("ceheese","e")//4
9)不区分大小写的字符串比较(==是区分大小写的):fmt.Println("abc","Abc")//true
10)返回子串在字符串第一次出现的index值,如果没有返回-1:strings.Index("NLT_abc","abc")//4
11)返回子串在字符串最后一次出现的index,如没有返回-1:string.LastIndex("go golang","go")
12)将指定的子串替换成另外一个子串:string.Reolace("go go hello","go","go语言",n) n可以指定你希望替换几个,如果n=-1表示全部替换。
13)按照指定的某个字符,分割标识,将一个字符串拆分成字符串数组:strings.Split("hello,world,ok",",")
14)将字符串的字母进行大小写的转换:strings.ToLower("Go") strings.ToUpper("Go")
15)将字符串左右两边的空格去掉:strings.TrimSpace(" tn a lone gopher ntm ")
16)将字符串左右两边指定的字符去掉:strings.Trim("!hello!","!")
17)将字符串左边指定的字符去掉:strings.TrimLeft("!hello!","!")
18)将字符串右边指定的字符去掉:strings.TrimRight("!hello!","!")
14.日期和时间函数
1)time.Time()用于表示时间
//获取当前时间 now:=time.Now() //2.通过now可以获取到年月日,时分秒 now.Year()//年 now.Month()//月 now.Day()//日 now.Hour()//时 now.Minute()//分 now.Second()//秒 //格式化日期时间 fmt.Printf("当前年月日 %d-%d-%d %d:%d:%d",now.Year(),now.Month(),now.Day(),now.Hour(),now.Minute(),now.Second()) fmt.Printf(now.Format("2006/01/02 15:04:05")) time.Sleep()//休眠
2)获取当前unix时间戳和unixnano时间戳。(作用是可以获取随机数字)
15.内置函数
可以直接使用的函数就是内置函数
1)len,用来求长度。比如string,array,slice,map,channel
2)new,用来分配内存,主要用来分配值类型,比如int,float32,struct..返回的是指针
num1:=100 fmt.Printf("num1的类型%T,num1的值=%v,num1的地址%v\n",num1,num1,&num1) num2:=new(int)//*int //num2的类型%T=》*int //num2的值=地址? //num2的地址%v=地址? fmt.Printf("num2的类型%T,num2的值=%v,num2的地址%v\n",num2,num2,&num2)
3)make,用来分配内存,主要用来分配引用类型,比如chan,map,slice
16.错误处理机制
1)Go语言不支持传统的try...catch...finally这种处理
2)Go中引入的处理方式为:defer,panic,recover
3)Go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理
defer func(){ err:=recover()//recover()内置函数,可以捕获异常 if err!=nil{//说明捕获到错误 fmt.Println("err=",err) } }() num1:=10 num2:=0 res:=num1/num2 fmt.Println("res=",res)
自定义错误
Go程序中,也支持自定义错误,使用errors.New和panic内置函数。
1)errors.New("错误说明"),会返回一个error类型的值,表示一个错误
2)panic内置函数,接收一个interface()类型的值(任何值)作为参数。可以接收error类型的变量,输出错误信息,并退出程序。
func readConf(name string) (err error){ if name=="config.nin"{ //读取... return nil }else{ //返回一个自定义错误 return errors.New("读取文件错误...") } }
17.数组与切片
数组是一种数据类型,在Go中数组是值类型,在默认情况下是值传递,因此会进行值拷贝,数组间不会相互影响
//四种初始化数组的方式 var numArr01 [3]int=[3]int{1,2,3} var numArr02 =[3]int{5,6,7} var numArr03 =[...]int{8,9,10} var numArr04=[...]int{1:800,0:900,2:9999} //删除某个元素 package main import "fmt" func main() { slice := []int{1, 2, 3, 4, 5} indexToDelete := 2 // 假设我们要删除索引为2的元素 var newSlice []int for i, value := range slice { if i != indexToDelete { newSlice = append(newSlice, value) } } fmt.Println(newSlice) // 输出: [1 2 4 5] }
数组的遍历
基本语法
for index,value :=range array01{ ... } 1.第一个返回值index是数组的下标 2.第二个value是在该下标位置的值 3.都是仅在for循环内部可见的局部变量 4.遍历数组元素的时候,如果不想使用下标index,可以直接把下标index标为下划线_ 5.index和value的名称不是固定的,即程序员可以自行制定,一般命名为index和value
var arr []int 这时arr就是一个slice切片。
长度是数组类型的一部分,在传递函数参数时,需要考虑数组的长度
2)切片(slice)
1)切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制。
2)切片的使用和数组类似,遍历切片、访问切片的元素和求切片长度len(slice)都一样。
3)切片的长度是可以变化的,因此切片是一个可以动态变化的数组。
4)切片定义的基本语法:
var 变量名 []类型
方式1 var intArr [5]int=[...]int{1,22,33,66,99} //1.slice就是切片名 //2.intArr[1:3]表示slice引用到intArr这个数组(左包含右不包含) slice:=intArr[1:3] //slice容量一般是元素个数的两倍 /* 1.slice的确是一个引用类型 2.slice从底层来说,其实就是一个数据结构(struct结构体) type slice struct{ ptr *[2]int len int cap int//容量 } */ 方式2 //通过make来创建切片 var 切片名 []type=make([]T,len,[cap]) //type:就是数据类型 len:大小 cap:指定切片容量 T:数据类型 //对于切片,必须使用make /* 1.通过make方式创建切片可以指定切片的大小和容量 2.如果没有给切片的各个元素赋值,那么就会使用默认值[int,float=>0 string=>"" bool=>false] 3.通过make方式创建的切片对应的数组是由make底层维护,对外不可见,即只能通过slice去访问各个元素 */ 方式3 定义一个切片,直接就指定具体数组,使用原理类似make方式 var strSlice []string=[]string{"tom","jack","mary"}
切片的遍历和数组的遍历一样
string和slice
1)string底层是一个byte数组,因此string也可以进行切片处理
str:='hello@atguigu' slice:=str[6:]
2)string和切片在内存的形式
3)string是不可变的,也就是说不能通过str[0]='z'方式来修改字符串
4)如果需要修改字符串,可以先将string->[]byte/或者[]rune->修改->重写转成string
18.数组排序
//冒泡排序 func maoPao(arr *[]int) (arr []int){ num:=0 for j:=0;j<len(*arr)-1;j++{ for i:=0;i<len(arr)-1-j;i++{ if (*arr)[i]>(*arr)[i+1]{ num=(*arr)[i] (*arr)[i]=(*arr)[i+1] (*arr)[i+1]=num } } } return arr } //顺序查找 //(1) names:=[4]string{"aa","bb","cc","dd"} var hName="" fmt.Scanln(&hName) for i:=0;i<len(name);i++{ if hName==name[i]{ fmt.Printf("找到%v,下标%v\n",hName,i) break }else if i==(len(name)-1){ fmt.Peintf("数组中没有\n") } } //(2) index:=-1 for i:=0;i<len(name);i++{ if hName==name[i]{ index=i } } if index==-1{ fmt.Printf("没有找到\n") }else{ fmt.Printf("找到%v,下标%v\n",hName,i) } //二分查找 前提必须是有序数组 func findArr(leftIndex int,rightIndex int,num int,arr *[]int){ if leftIndex>rightIndex{ fmt.Printf("num不在数组中") break } //leftIndex=0 //rightIndex=len(*arr)-1 mindle:=(leftIndex+rightIndex)/2 if *arr[mindle]>num{ rightIndex=mindle-1 findArr(leftIndex,rightIndex,num,&arr) }else if *arr[mindle]<num{ leftIndex=mindle+1 findArr(leftIndex,rightIndex,num,&arr) }else if *arr[mindle]=num{ fmt.Printf("num在数组中,下表为%v\n",mindle) break } }
19.二维数组
声明方式:
var 数组名 [大小][大小]类型
二维数组的遍历
1)双层for循环完成遍历
2)for-range方式完成遍历
20.map(无序的)
声明方式:
var map变量名 map[keytype]valuetype //如 var a map[string]string //go中的map的key可以是很多中类型,比如bool,数字,string,指针,channel,还可以是只包含前面几个类型的接口,结构体,数组 通常key为int,string 注意:slice,map还有function不可以,因为这几个没法用==来判断 //第一种 var a map[string]string //在使用map前,需要先make,make的作用就是给map分配数据空间 a=make(map[string]string,10) a["num1"]="松江" //第二种 cities:=make(map[string]string) //第三种 heros:=map[string][string]{ "hero1":"aa", "hero2":"bb" }
//增加和直接赋值一样 //删除delete,也可以重新make一下生成一个新的空间 delete(heros,"hero1") //查找 val,ok:=heros["hero1"] if ok{ fmt.Printf(val) }else{ fmt.Printf("没有") }
遍历
map的遍历使用的是for-range结构
for k,v:=range heros{ fmt.Printf(k,v) }
map切片
切片的数据类型如果是map,则我们称为slice of map,map切片,这样使用map个数就可以动态变化了。
//1.声明一个map切片 monsters:=[]map[string]string monsters=make([]map[string]string,2)//最多只能增加两个,再增加只能使用append函数动态增加 //2.增加第一个信息 if monster[0]==nil{ monster[0]=make(map[string]string,2)//因为monster[0]也是一个map,因此也需要make monster[0]["name"]="牛魔王" monster[0]["age"]="12" } newMonster:=map[string]sting{ "name":"新妖怪", "age":200, } newMonster=append(monster,newMonster)
map排序
/* 如果按照map的key的顺序进行排序输出 1.先将map的key放入到切片中 2.对切片排序 3.遍历切片,然后按照key来输出map的值 */ var keys []int for k,v:=range map1{ keys=append(keys,k) } //排序 sort.Ints(keys)
21.面向对象(不是对象是结构体
结构体
type 结构体名字 struct{ 变量1 变量2 } var cat1 Cat
结构体是值类型默认为值拷贝
创建结构体的方式:
1)直接声明
如:var person Person
2)
var person Person=Person{}
3)
var person *Person=new(Person)
因为person是一个指针,因此标准的给字段赋值方式
(*person).Name="smith" 也可以这样写p3.Name="smith"
(*person).Age=30
4)
var person *Person=&Person{}
因为person是一个指针,因此标准的访问字段的方式
(*person).Name="scott"
也可以使用person.Name="scott"
结构体内存情况
结构体的使用注意事项和细节
1)结构体的所有字段在内存中是连续的
2)结构体是用户单独定义的类型,和其他类型进行转换时需要有完全相同的字段(名字,个数和类型)
3)结构体进行type重定义(相当于取别名),Golang认为是新的数据类型,但是相互间可以强转
4)struct的每个字段上,可以写上一个tag,该tag可以通过反射机制获取,常见的使用场景就是序列化与反序列化
方法的参数传递
1)通过一个变量去调用方法时,其调用机制和函数一样
2)不一样的地方是变量调用方法时,该变量本身也会作为一个参数传递到方法(如果变量是值类型,则进行值拷贝,如果变量是引用类型,则进行地址拷贝)
为了提高效率,通常我们方法和结构体的指针类型绑定 type Circle struct{ radius float64 } func (c *Circle) arr() float64{ return 3.14*(*c).radius*(*c).radius } func main(){ var c Circle c.radius=5.0 (&c).arr() }
方法的注意事项和细节
1)结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式。
2)如程序员希望在方法中修改结构体变量的值,可以通过结构体指针的方式来处理
3)Golang中的方法作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型,都可以有方法而不仅仅是struct,比如int,float32等都可以有方法
4)方法的访问范围控制的规则,和函数一样,方法名首字母小写,只能在本包访问,方法首字母大写,可以在本包和其它包访问。
5)如果一个类型实现了String()这个方法,那么fmt.Println默认会调用这个变量的String()进行输出
方法和函数区别
1)调用的方式不一样
函数的调用方式: 函数名(实参列表)
方法的调用方式: 变量.方法名(实参列表)
2)对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之毅然
3)对于方法(如struct的方法),接收者为值类型时,可以直接使用指针类型的变量调用方法,反过来同样也可以
总结:
1)不管调用形式如何,真正决定是值拷贝还是地址拷贝,看这个方法是和那个类型绑定
2)如果是和值类型,比如(p Person),则是值拷贝,如果和指针类型,比如是(p *Person)则是地址拷贝
工厂模式来解决问题
如果使用其他包的结构体变量首字母大写,引入后,直接使用,没有问题。
当结构体是首字母小写我们可以在结构体的包下面定义一个方法,在mian的包下面去调用这个方法(工厂模式)
如果某字段首字母小写,则,在其他包不可以直接访问,我们可以再定义一个方法,通过调用这个方法去访问
面向对象编程思想-抽象
我们前面去定义一个结构体的时候,实际上就是把一类事物的公有的属性(字段)和行为(方法)提取出来,形成一个物理模型(模版)。这中研究问题的方法成为抽象
面向对象编程思想-封装
就是把抽象出的字段和对字段的操作封装到一起,数据被保护在内部,程序的其它包只有通过被授权的操作(方法),才能对字段进行操作。
面向对象编程思想-继承
基本语法
type Goods struct{ Name string Price int } type Book struct{ Goods//这里就是嵌套匿名结构体Goods Writer string }
type A struct{ Name string age int } type B struct{ Name string Score float64 } type C struct{ A B } func main(){ var c C c.Name="hhh"//当C中没有这个字段时会报错,必须指定是谁的字段如:c.A.Name }
如果一个struct嵌套了一个有名的结构体,这种模式就是组合,如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字。
type A struct{ Name string age int int } type C struct{ a A } func main(){ var c C c.a.Name="hhhh"//正确 c.Name="hhhhh"//报错 c.a.int=10//也可以正确输出 }
多重继承
如果一个struct嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现了多重继承。
type A struct{ Name string age int } type B struct{ Name string Score float64 } type C struct{ A B } func main(){ var c C c.A.Name="hhh"//当C中没有这个字段时会报错,必须指定是谁的字段如:c.A.Name }
为了保证简洁性,尽量不要使用多继承
22.接口
type Usb interface{ Start() Stop() } type Phone struct{ } type Computer struct{ } func (p Phone) Start(){ fmt.Println("手机开始工作....") } func (p Phone) Stop(){ fmt.Println("手机停止工作...") } func (c Computer) Working(usb Usb){ usb.Start() usb.Stop() } func main(){ //先创建结构体变量 computer:=Computer() phone:=Phone() camera:=Camera() computer.Working(phone) }
注意事项和细节
1)接口本身不能创建实例,但是但可以指向一个实现了该接口的自定义类型的变量(实例)
type AInterface interface{ Say() } type Stu struct{ Name string } func (stu Stu) Say(){ fmt.Println("Stu Say()") } func main(){ var stu Stu//结构体变量,实现了Say()实现了AInterface Var a AInterface =stu a.Say() }
2)接口中所有的方法都没有方法体,即都是没有实现的方法
3)在Golang中,一个自定义类型需要将某个接口的所有方法都实现,我们说这个自定义类型实现了该接口
4)一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型
5)只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型
6)一个自定义类型可以实现多个接口
7)Golang接口中不能有任何变量
8)一个接口(比如A接口)可以继承多个别的接口(比如B,C接口),这时如果要实现A接口,也必须将B,C接口的方法也全部实现。
9)interface类型默认是一个指针(引用类型),如果没有对interface初始化就使用,那么会输出nil
10)空接口interface{}没有任何方法,所以所有类型都实现了空接口
接口的经典案例
对切片进行排序1.冒泡排序2.使用sort.ints()方法进行排序
对结构体切片进行排序
//声明Hero结构体 type Hero struct{ Name string Age int } type Student struct{ Name string Age int Score float64 }//将Student的切片,按Score从大到小排序 //声明一个Hero结构体切片类型 type HeroSlice []Hero //实现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//降序排列 } func (hs HeroSlice) Swap(i,j int){ temp:=hs[i] hs[i]=hs[j] hs[j]=temp } func main(){ var heroes HeroSlice for i:=0;i<10;i++{ hero:=Hero{ Name:fmt.Sprintf("英雄~%d",rand.Intn(100)), Age:rand.Intn(100) } //将hero append到heroes切片 heroes=append(heros,hero) } //调用sort.Sort() sort.Sort(heroes) }
接口和继承的区别
1)当A结构体继承了B结构体,那么A结构体就自动的继承了B结构体的字段和方法,并且可以直接使用。
2)当A结构体需要扩展功能,同时不希望去破坏继承关系,则可以去实现某个接口即可,因此我们可以认为:实现接口是对继承机制的补充。
继承的价值主要在于:解决代码的复用性和可维护性
接口的价值主要在于:设计,设计好各种规范(方法),让其它自定义类型去实现这些方法。
接口比继承更加灵活
接口比继承更加灵活,继承是满足is-a的关系,而接口只需满足like-a的关系。
接口在一定程度上实现代码解耦
23.多态
接口体现多态特征:
1)多态参数
在前面的Usb接口案例,Usb usb,既可以接收手机变量,又可以接收相机变量,就体现了Usb接口多态。
2)多态数组
演示一个案例:给Usb数组中,存放Phone结构体和Camera结构体变量,Phone还有一个特有的方法call(),请便利Usb数组,如果是Phone变量,除了调用Usb接口声明的方法外,还需要调用Phone特有方法call。
类型断言
type Point struct{ x int y int } func main(){ var a interface{} var point Point=Point{1,2} a=point //ok var b Point b=a//不可以 b=a.(Point)//可以 }
在进行类型断言时,如果类型不匹配,就会报panic,因此进行类型断言时,要确保原来的空接口指向的就是断言的类型。
type Point struct{ x int y int } func main(){ var a interface{} var point Point=Point{1,2} a=point //ok var b Point b=a//不可以 b,ok=a.(Point)//可以 if ok==true{ fmt.Println("convert success") }else{ fmt.Println("convert fail") } }
也可以判断结构体Student和*Student的类型
24.文件
os.Open(file)//打开文件 file.Close()//关闭文件
//创建一个*Reader,是带缓冲的 /* const{ defaultBufSize=4096//默认缓冲区为4096 } */ reader:=bufo.NewReader(file) //循环的读取文件的内容 for{ str,err:=reader.ReadString("\n")//读到一个换行就结束 if err ==io.EOF{//io.EOF表示文件的末尾 break } fmt.Print(str)//输出内容 }
//使用ioutil.ReadFile一次性将文件读取到位 file:="d:test.txt" content,err:=ioutil.ReadFile(file) if err!=nil{ fmt.Printf("read file err=%v",err) } //把读取到的内容显示到终端 fmt.Printf("%v",string(content)) //我们没有显示的Open文件,因此也不需要显式的Close文件 //因为,文件的Open和Close被封装到ReadFile函数内部