版權說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權,請進行舉報或認領
文檔簡介
go語言學習筆記(初級)
最近一直在學習g。語言,因此打算學習的時候能夠記錄
一下筆記。我這個人之前是從來沒有記錄筆記的習慣,
一直以來都是靠強大的記憶力去把一些要點記住。
讀書的時候因為一直都是有一個很安靜和很專心的環(huán)境,
因此很多事情都能記得很清楚,思考的很透徹。但是隨著
年紀不斷增加,也算是經(jīng)歷了很多的事情,加上工作有時會讓人
特別煩悶,很難把心好好靜下來去學習,去思考大自然的終極
奧秘,因此需要記錄一些東西,這些東西一方面可以作為一種自我激勵
的機制,另一方面,也算是自己成長的軌跡吧。
一.順序編程
1.變量
g。語言變量定義的關鍵字是var。類型放在變量名后:
varvlint
varv2string
varv3[10]int〃數(shù)組
varv4[]int〃切片
varv5struct{〃結(jié)構(gòu)體
fint
}
varv6*int〃指針
varv7map[string]int//map
varv8func(aint)int〃函數(shù)
每一行不需要以分號作為結(jié)尾。var
關鍵字還有一種是把多個變量的申明放在一起,
var(
vlstring
v2int
)
2.變量初始化
有人說,變量初始化有什么好提的,那么簡單。是的,但是這里面確實
還是有一些值得注意的點。
varaint=10〃完整定義
vara=10〃自動推斷是int型
a:二10〃自動推斷是int型,申明并未該變量賦值
第三種初始化方式無疑是最簡單的。
但是要注意的是,這里面第三種方式是和特別的,比如
varaint
a:=10
等價于
varaint
varaint
a=10
這時候就會報一個重復定義的錯誤。
3.變量賦值
變量賦值和我們通常的語言基本是一致的,但是多了多重賦值功能。
1,J=J,1
這就直接實現(xiàn)了兩個變量的交換。
4.匿名變量
g。語言的函數(shù)是多返回值的,因此可能有些值并沒有被用到,這時我
們就需要一個占位符去忽略這些返回值。
funcGetName()(firstName,lastName,nickNamestring){
return〃May〃,〃Chan〃,〃ChibiMarukoz/
}
nickName:=GetName()
5.定義常量
通過const關鍵字,可以用來定義常量。
constPifloat64=3.1415926
constzero=0.0〃自動推斷類型
const(〃多定義
sizeint64=10
hello=-1
)
constu,vfloat32=0.0,3.0〃多重賦值
consta,b,c=1,2,"hello”〃自動推斷類型
常量的定義也可以跟一個表達式,但是這個表達式應該是編譯的時候
就可以求值的.
constmask=1<<3〃正常
constHome=os.GetEnv("HOME")〃錯誤,運行時才能確定
6.預定義常量
這里要講一個很有意思的東西,叫做iota.
這個東西每一個const出現(xiàn)的位置被置為0,沒出現(xiàn)一個iota出現(xiàn),都自
增1,到寫一個const出現(xiàn)的時候,又置為0.
const(
cl=iota//0
c2=iota//I
c3=iota//2
)
constx=iota//x==0(因為iota又被重設為0了)
consty=iota//y二二0(同上)
如果兩個const賦值表達式是一樣的,可以省略后面的賦值表達式.
const(
cl=iota//0
c2//I
c3//3
)
const(
a=1<<iota//a==1(iota在每個const開頭被重設為0)
b//b==2
c//c==4
6.枚舉
const(
Sunday=iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
numberOfDays//這個常量沒有導出
)
大寫字母開頭的包外可見,小寫字母開頭的包外不可見.
7.類型
?整型
int8,uint8,int16,uint16,int32,uint32,int64,uint64,int,uint,uintptr
不同類型不能相互比較.
?浮點類型
float32,float64
涉及的一些運算比較簡單,我們不做細講.
?字符串類型
下面我們展示一個完整的go語言程序,也是以hello
world為主題,畢竟helloworld是一個萬斤油的主題.
packagemain
importz/fmtzz〃引入依賴包
funcmain(){
fmt.Printin(/zhello,world!/z)
}
這基本上是一個最簡單的程序了,但是對于我們的學習非常有用,用這個
模板可以寫出非常好的東西出來.
字符串串本身非常簡單,主要就是一些字符串操作,比如取特定位置的
字符等.
packagemain
import〃引入依賴包
funcmain(){
varstrstring="hello,world!〃
fmt.Println(str)
ch:=str[0]〃取某個特定位置的字符
fmt.Printf(〃%c\n〃,ch)
length:=len(str)
fmt.Printin(length)//len用來獲取長度
str=〃你好,世界〃
ch=str[0]
fmt.Printf(〃%c\n〃,ch)
length=len(str)
fmt.Printin(length)
)
輸出結(jié)果為:
hello,world!
h
12
9
13
這正好說明□和len都不能處理中文.
字符串連接也是用+.
字符串的遍歷:
packagemain
import〃引入依賴包
funcmain(){
varstrstring="hello,world!〃
n:=len(str)
fori:=0;i<n;i++{
ch:=str[i]
fmt.Printf(〃%c\n〃,ch)
}
}
輸出結(jié)果:
h
e
1
1
o
w
0
r
1
d
I
對于中文,結(jié)果是亂碼,因為是一個字節(jié)一個字節(jié)輸出的,但是默認是
UTF8編碼,一個中文對應3個字節(jié).
這里我們要提到一個range的關鍵字,它可以把字符串按鍵值對的方式
返回.
packagemain
import〃引入依賴包
funcmain(){
varstrstring=z/hello,world!你好,世界!〃
forch:=rangestr{
fmt.Printf(〃%c\n〃,ch)
}
)
輸出結(jié)果為:
h
e
1
1
o
w
o
r
1
d
I
你
好
世
界
!
事實上,字符類型有兩種,一種就是byte(uint8),另一種是rune.第一
種遍歷字符串ch是byte,而第二種是rune.
?數(shù)組
數(shù)組這種類型是非常常見的
[32]byte
[2*N]struct{x,yint32}
[1000]*float64
[3][5]int
[2][2][2]float64
數(shù)組的遍歷和字符串一樣,這里不再重復.
數(shù)組是值類型,在賦值時會拷貝一份.
?數(shù)組切片
數(shù)組切片的概念比較復雜,它有點類似于C++中vector的概念,但又不
完全一樣.
我們這里詳細提幾點.
1.切片的創(chuàng)建
切片有兩種創(chuàng)建方式,一種是基于數(shù)組創(chuàng)建,另一種是用make
創(chuàng)建.
packagemain
import〃fmt〃〃引入依賴包
funcmain(){
//從數(shù)組創(chuàng)建
varmyArray[10]int=[10]int{1,2,3,4,5,6,7,8,9,10)
varsa[]int=myArray[5:]
fore:=rangesa{
fmt.Println(e)
)
fmt.Printin(len(sa))
fmt.Printin(cap(sa))
〃從make創(chuàng)建
varmySlice2[]int=make([]int,5,10)
fore:=rangemySlice2{
fmt.Println(e)
}
fmt.Printin(len(mySlice2))
fmt.Printin(cap(mySlice2))
〃賦值
varmySlice3[]int=[]int{1,2,3)
fore:=rangemySlice2{
fmt.Println(e)
slice是引用類型.
packagemain
import/zfmt,z〃引入依賴包
functest(a[10]int){
a[0]=10
}
funcprintArray(a[10]int){
fore:=rangea{
fmt.Println(e)
}
)
funcmain(){
varmyArray[10]int=[10]int{1,2,3,4,5,6,7,8,9,10}
printArray(myArray)
test(myArray)
printArray(myArray)
)
輸出結(jié)果:
1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
我們發(fā)現(xiàn)數(shù)組確實是按照值來傳遞.那么如果是slice呢,會發(fā)生
什么?
packagemain
import〃引入依賴包
functest(a[]int){
a[0]=10
}
funcprintArray(a[]int){
fore:=rangea{
fmt.Println(e)
}
)
funcmain(){
varmyArray[]int=[]int{1,2,3,4,5,6,7,8,9,10}
printArray(myArray)
test(myArray)
printArray(myArray)
輸出結(jié)果:
1
2
3
4
5
6
7
8
9
10
10
2
3
4
5
6
7
8
9
10
確實是按照引用來傳遞的.
append函數(shù)可以往切片尾部增加元素.
mySlice=append(mySlice,1,2,3)
mySlice=append(mySlice,mySlice2...)
…表示把一個slice拆成元素來處理.
packagemain
import〃fnit〃〃引入依賴包
funcmain(){
varslicel[]int=make([]int,5,10)
varslice2[]int=[]int{1,2,3)
fmt.Println(slicel)
fmt.Printf(〃%p\n〃,slicel)
slicel=append(slicel,slice2...)
fmt.Println(slicel)
fmt.Printf(〃%p\n〃,slicel)
slicel=append(slicel,slice2...)
fmt.Printin(slicel)
fmt.Printf(〃%p\n〃,slicel)
}
輸出結(jié)果:
[00000]
0xc820012190
[00000123]
0xc820012190
[00000123123]
0xc82005e000
在這里我們看到,slice的地址是所隨著內(nèi)內(nèi)存的改變而變化的,因
此是需要仔細思考的.我個人不覺得
go語言這種特性有什么好的,反正也是奇葩極了.不過slice還提
供copy,也算是一些彌補吧.
map
go語言中,map使用非常簡單.基本上看代碼就會了.
packagemain
importz/fmt/z〃引入依賴包
〃定義一個Person的結(jié)構(gòu)體
typePersonstruct{
namestring
ageint
)
funcmain(){
vardiemap[string]Person=make(map[string]Person,100)//初始化
map
dic[〃1234〃]=Person{name:z,lileiz/,age:100)
dic[〃12345〃]=Person{name:zzhanmeimei/z,age:20}
die[〃123456〃]=Person{name:/zdagong/z,age:30}
fmt.Println(dic)
〃刪除dagong
delete(die,“123456")
fmt.Println(dic)
〃查找某個key
value,ok:=dic]〃123456〃]
ifok{
fmt.Printin(value)
}
value,ok=dic]〃1234〃]
ifok{
fmt.Printin(value)
}
fork,v:=rangedie{
fmt.Println(k+〃:〃+v.name)
輸出結(jié)果為:
map[12345:{hanmeimei20}123456:{dagong30}1234:{lilei100}]
map[1234:{lilei100}12345:{hanmeimei20}]
{lilei100)
12345:hanmeimei
1234:lilei
map很簡單吧.數(shù)據(jù)結(jié)構(gòu)我們講完了,接下來可以看看代碼的程序控制
了.
8.程序控制
程序控制本人只提一些關鍵性的東西,不會啰嗦太多.
?switch語句
switch語句不需要在每個case地下寫break,默認就是執(zhí)行break.如果
要執(zhí)行多個case,在case最后加入fallthrough.
條件表達式不限制為常量或者整數(shù).單個case自然可以有多個結(jié)果可以
選.
packagemain
import〃引入依賴包
functest(aint){
switch{
casea<0:
fmt.Printin("hello")
caseEL——10:
fallthrough
casea>10&&a<100:
fmt.Printin("world")
default:
fmt.Println(/,nimazz)
)
}
funcmain(){
test(-1)
test(10)
test(100)
}
?循環(huán)
go語言的循環(huán)比較特別,它用一個for就把for和while的活都干了.
packagemain
import"fmt〃〃引入依賴包
funcmain(){
sum:=0
fori:=0;i<=100;i++{
sum+=i
}
fmt.Println(sum)
sum=0
i:=0
for(i<=100){
sum+=i
i++
)
fmt.Println(sum)
}
break還支持break到指定的label處.
forj:=0;j<5;j++{
fori:=0;i<10;i++{
ifi>5{
breakJLoop
}
fmt.Println(i)
}
)
JLoop:
//...
?函數(shù)
函數(shù)是一個非常重要的概念,也很簡單.go的函數(shù)以func關鍵字定義,
支持不定參數(shù)和多返回值.函數(shù)名首字符的大小寫是很有講究的:
小寫字母開頭的函數(shù)只在本包內(nèi)可見,大寫字母開頭的函數(shù)才能被其他
包使用。這個規(guī)則也適用于類型和變量的可見性。
packagemain
import〃引入依賴包
//...int是不定參數(shù),實際上就是一個slice,a,b是多返回值
funcSumAndAverage(sample...int)(a,bfloat64){
a,b=0,0
ford:=rangesample{
a+=float64(d)
)
iflen(sample)==0{
b=0
}else{
b=a/float64(len(sample))
}
returna,b
}
funcmain(){
a,b:=SumAndAverage(1,2,3)
fmt.Println(a,b)
}
很簡單吧.注意,如果是函數(shù)里面調(diào)了其他函數(shù),那么這個sample怎
么傳給其他喊函數(shù)呢?
sample...//...表示是拆成一個個元素傳遞
匿名函數(shù)的概念也很簡單,只要看代碼就會明白.
packagemain
import/zfmtz/〃引入依賴包
funcmain(){
varmyFuncfunc(...int)(float64,float64)=func(sample...int)(a,b
float64){
a,b=0,0
ford:=rangesample{
a+=float64(d)
}
iflen(sample)二二0{
b=0
}else{
b=a/float64(len(sample))
}
returna,b
}
a,b:=myFunc(1,2,3)
fmt.Println(a,b)
)
下面是關于閉包的概念.這個概念在許式偉的書中被詮釋的非常好:
閉包是可以包含自由(未綁定到特定對象)變量的代碼塊,這些變量不
在這個代碼塊內(nèi)或者任何全局上下文中定義,
而是在定義代碼塊的環(huán)境中定義。
要執(zhí)行的代碼塊(由于自由變量包含在代碼塊中,所以這些自由變量以
及它們引用的對象沒有被釋放)為自由變量提供綁定定的計算環(huán)境(作
用域)。
閉包的實現(xiàn)確保只要閉包還被使用,那么被閉包引用的變量會一直存
在.*
我們來看來兩個閉包的例子.
packagemain
importz/fmtzz〃引入依賴包
functest(iint)func(){
returnfunc(){
fmt.Println(10+i)
fmt.Printf(〃%p\n〃,&i)
)
}
funcmain(){
a:=test(1);
b:=test(2)
a()
b()
)
輸出結(jié)果:
11
0xc82000a288
12
0xc82000a2c0
我們從這個結(jié)果中發(fā)現(xiàn),i的地址是會變的,因為是作為一個局部變量傳
進去的.
packagemain
import/zfmtz/〃引入依賴包
functest(xint)func(int)int(
returnfunc(yint)int{
fmt.Printf(/z%p\n/z,&x)
returnx+y
funcmain(){
a:=test(1);
fmt.Printin(a(10))
fmt.Printin(a(20))
}
輸出結(jié)果:
0xc82000a288
11
0xc82000a288
21
因為X只傳入了一次,因此沒有改變.
packagemain
import(
〃fmt〃
)
funcmain(){
varjint=5
a:=func()(func()){
variint=10
returnfunc(){
fmt.Printf(/zi,j:%d,%d\n〃,i,j)
}
)()
a()
j*=2
a()
此時輸出:
i,j:10,5
i,j:10,10
二.面向?qū)ο缶幊?/p>
這里我們先提值語義和引用語義的概念.
b=a
b.Mofify()
如果b改變,a不發(fā)生改變,就是值語義.如果b改變,a也發(fā)生改變,就
是引用語義.
g。語言大多數(shù)類型都是值語義,比如:
基本類型:byte,int,float32,float64,string
復合類型:struct,array,pointer
也有引用語義的,比如:
slice,map,channel,interface.
這是我們要牢記的.
我們的筆記整體式按照許式偉的書來安排,但是由于許的書提綱性很
強,內(nèi)容上不是很詳細,基本上會重新整理補充一些東西進去.
?結(jié)構(gòu)體
結(jié)構(gòu)體是用struct來申明的,不多廢話,直接上代碼.
packagemain
import(
〃fmt〃
)
〃申明一個結(jié)構(gòu)體
typePersonstruct{
Namestring
Ageint
}
funcmain(){
〃結(jié)構(gòu)體的初始化方式
//I.直接賦值
varpPerson
p.Name="dingding”
p.Age=10
fmt.Println(p)
〃2,順序賦值
pl:=Person{"dingding”,10}
fmt.Println(pl)
//3.keyvalue賦值
p2:=Person{Name:z,dingdingz/,Age:10}
fmt.Println(p2)
〃4.指針賦值
p3:=&Person{Name:z/dingding/z,Age:10}
fmt.Println(p3)
p4:=new(Person)
fmt.Println(p4)
fmt.Println(,z--------------------------〃)
a:=p
a.Name="dongdong”
b:=p3
b.Name="dongdong”
fmt.Println(p)
fmt.Println(p3)
)
輸出結(jié)果:
{dingding10)
(dingding10}
{dingding10)
&{dingding10}
&{0}
(dingding10}
&{dongdong10}
這說明,struct確實是值語義.
下面討論一下結(jié)構(gòu)體的組合問題.這點許的書中并沒有過多涉及,但是
還是很有必要的,因為在實際場合中用的會很多.
packagemain
import(
〃fmt〃
〃申明一個結(jié)構(gòu)體
typeHumanstruct{
Namestring
Ageint
Phonestring
}
//再申明一個結(jié)構(gòu)體
typeEmployeestruct{
PersonHuman//匿名字段Human
Specialitystring
Phonestring//雇員的phone字段
)
funcmain(){
e:=Employee{
Person:Human{
Name:〃dingding”,
Age:11,
Phone:〃6666666”,
},
Speciality:/zaaa/z,
Phone:〃77777777〃,
}
fmt.Println(e.Phone)
}
這段代碼看上去非常ok,但是如果我們稍微改一下代碼呢?比如把
Person改成匿名結(jié)構(gòu),會有些好玩的現(xiàn)象.
packagemain
import(
〃fmt〃
)
〃申明一個結(jié)構(gòu)體
typeHumanstruct{
Namestring
Ageint
Phonestring
}
//再申明一個結(jié)構(gòu)體
typeEmployeestruct{
Human//匿名字段Human
Specialitystring
//Phonestring//雇員的phone字段
}
funcmain(){
e:=Employee{
Human{"dingding”,11,”6666666〃},
〃〃
〃Phone:"77777777”,
)
fmt.Printin(e.Phone)
}
此時輸出的是6666666,因為相當于它把Human的字段Phone繼承下
來了.
如果Employee里也定義Phone字段呢?
packagemain
import(
〃fmt〃
)
〃申明一個結(jié)構(gòu)體
typeHumanstruct{
Namestring
Ageint
Phonestring
}
//再申明一個結(jié)構(gòu)體
typeEmployeestruct{
Human//匿名字段Human
Specialitystring
Phonestring//雇員的phone字段
)
funcmain(){
e:=Employee{
Human{/zdingding/z,11,“6666666〃},
〃〃
“77777777”,
}
fmt.Printin(e.Phone)
}
此時輸出的時77777777,因為相當于是覆蓋.那么怎么輸出6666666
呢?
packagemain
import(
〃fmt〃
)
〃申明一個結(jié)構(gòu)體
typeHumanstruct{
Namestring
Ageint
Phonestring
}
//再申明一個結(jié)構(gòu)體
typeEmployeestruct{
Human//匿名字段Human
Specialitystring
Phonestring//雇員的phone字段
}
funcmain(){
e:=Employee{
Human{"dingding”,11,”6666666〃},
〃〃
“77777777”,
)
fmt.Printin(e.Phone)
fmt.Printin(e.Human.Phone)
)
輸出結(jié)果:
77777777
6666666
所以,匿名結(jié)構(gòu)體的組合相當于有繼承的功能.
?為類型添加方法
這個概念和java或者是C++非常不一樣,它的理念是把似乎是把方法
綁定到特定類型上去.
這個概念已經(jīng)不僅僅是對象的概念了,事實上,
我也不知道google這幫人腦子是怎么想的,這種搓劣的復古風格,
也是讓我打開眼界,我個人覺得,g。雖然仗著google的名氣,似乎顯得
很厲害,但是,
比起java和C++,簡直就是個玩具,說的不好聽一點,
比起scala這樣的一出生就是個大胖子,go更像是個缺胳膊少腿的畸形
兒.
好了,不說了,直接上代碼.
packagemain
import(
〃fmt〃
)
//go綁定方法必須是本包內(nèi)的,int不是main包內(nèi)定義的.
〃因此需要type一下,Integer就是本包內(nèi)定義的類型
typeIntegerint
〃為int綁定一個Print方法
func(iInteger)PrintlnO{
fmt.Println(i)
funcmain(){
varaInteger=10
a.PrintlnO
}
結(jié)果輸出10,如果是如下呢?
packagemain
import(
〃fmt〃
)
//go綁定方法必須是本包內(nèi)的,int不是main包內(nèi)定義的.
〃因此需要type一下,Integer就是本包內(nèi)定義的類型
typeIntegerint
〃為int綁定?個Print方法
func(iInteger)PrintlnO{
fmt.Println(i)
}
funcmain(){
a:=10
a.Printin()
}
輸出結(jié)果:
#command-line-arguments
./main,go:18:a.Printinundefined(typeinthasnofieldormethod
Printin)
因為a:=10,go會把a推斷為int,但int并沒綁上Printin方法.
注意:
〃為int綁定一個Print方法
func(iInteger)PrintlnO{
fmt.Println(i)
)
這里的i并不是引用類型,因此對i的修改不會反應到a上去.
packagemain
import(
〃fmt〃
)
//go綁定方法必須是本包內(nèi)的,int不是main包內(nèi)定義的.
〃因此需要type一下,Integer就是本包內(nèi)定義的類型
typeIntegerint
〃為int綁定一個Print方法
func(iInteger)Add(){
i+=2
}
funcmain(){
varaInteger=10
a.Add()
fmt.Println(a)
輸出10.
如果我們用引用呢?
packagemain
import(
〃fmt〃
)
//go綁定方法必須是本包內(nèi)的,int不是main包內(nèi)定義的.
〃因此需要type一下,Integer就是本包內(nèi)定義的類型
typeIntegerint
〃為int綁定?個Print方法
func(this*Integer)Add(){
*this+=2
)
funcmain(){
varaInteger=10
a.Add()
fmt.Println(a)
)
這時輸出12.我們發(fā)現(xiàn),這個this就像是我們C++里面的this指針一樣.
不過也傻13復古的多.
我們在看一個例子,
packagemain
import(
〃fmt〃
//go綁定方法必須是本包內(nèi)的,int不是main包內(nèi)定義的.
〃因此需要type一下,Integer就是本包內(nèi)定義的類型
typeIntegerint
〃為int綁定一個Print方法
func(this*Integer)Add(){
fmt.Printin(this)
*this+=2
}
funcmain(){
varbInteger=10
vara*Integer=&b
a.Add()
fmt.Println(a)
)
輸出結(jié)果:
0xc82000a288
0xc82000a288
我們發(fā)現(xiàn),
〃為int綁定一個Print方法
func(this^Integer)Add(){
fmt.Printin(this)
*this+=2
)
該方法用指針調(diào)用和用值去調(diào)用,效果是一樣的.this都是指向原來對
象的指針而已.
下面這個例子來自于許的書中,
packagemain
typeIntegerint
func(aInteger)Less(bInteger)bool{
returna<b
)
func(a^Integer)Add(bInteger){
*a+=b
}
typeLessAdderinterface{
Less(bInteger)bool
Add(bInteger)
)
funcmain(){
varaInteger=1
varbLessAdder=a
}
輸出:
#command-1ine-arguments
./main,go:20:cannotusea(typeInteger)astypeLessAdderinassignment:
IntegerdoesnotimplementLessAdder(Addmethodhaspointerreceiver)
這個例子似乎有點奇怪,為什么呢?
packagemain
typeIntegerint
func(aInteger)Less(bInteger)bool{
returna<b
)
func(a*Integer)Add(bInteger){
*a+=b
}
typeLessAdderinterface{
Less(bInteger)bool
Add(bInteger)
}
typeLessinterface{
Less(bInteger)bool
}
typeAdderinterface{
Add(bInteger)
}
funcmain(){
varaInteger=1
varbAdder=a
b.Add(10)
}
我們可以看得更清楚:
./main,go:28:cannotusea(typeInteger)astypeAdderinassignment:
IntegerdoesnotimplementAdder(Addmethodhaspointerreceiver)
但如果是:
packagemain
typeIntegerint
func(aInteger)Less(bInteger)bool{
returna<b
)
func(a^Integer)Add(bInteger){
*a+=b
}
typeLessAdderinterface{
Less(bInteger)bool
Add(bInteger)
)
typeLessinterface{
Less(bInteger)bool
}
typeAdderinterface{
Add(bInteger)
}
funcmain(){
varaInteger=1
varbInteger=a
b.Add(10)
}
就沒有任何問題.對比起來,就是這兩行代碼:
varbInteger=a
varbAdder=a
我們接下去會娓娓道來其中的奧妙.
packagemain
import(
〃fmt〃
)
〃定義對象People、Teacher和Student
typePeoplestruct{
Namestring
)
typeTeacherstruct{
People
Departmentstring
}
typeStudentstruct{
People
Schoolstring
}
〃對象方法實現(xiàn)
func(p*People)SayHi(){
fmt.Printf(Z/Hi,I'm%s.Nicetomeetyou!\n\p.Name)
}
func(t*Teacher)SayHi(){
fmt.Printf(Z/Hi,mynameis%s.I'mworkingin%s.\n〃,t.Name,
t.Department)
func(s*Student)SayHi(){
fmt.Printf(Z/Hi,mynameis%s.rmstudyingin%s.\n/z,s.Name,s.School)
}
func(s*Student)Study(){
fmt.Printf(/zrmlearningGolangin%s.\n/z,s.School)
}
〃定義接口Speaker和Learner
typeSpeakerinterface{
SayHi()
}
typeLearnerinterface{
SayHi()
Study()
}
funcmain(){
〃初始化對象
people:=People{〃張三〃}
//teacher:=&Teacher{People{〃關B智〃},“ComputerScience”}
//student:=&Student{People{〃李明〃},"YaleUniversity”}
varspeakerSpeaker〃定義Speaker接口類型的變量
speaker=people
speaker.SayHi()
}
這時就會出現(xiàn)上面我們提到的錯誤.盡管如果我們這么去調(diào)用:
packagemain
import(
〃fmt〃
)
〃定義對象People>Teacher和Student
typePeoplestruct{
Namestring
)
typeTeacherstruct{
People
Departmentstring
)
typeStudentstruct{
People
Schoolstring
)
//對象方法實現(xiàn)
func(p*People)SayHi(){
fmt.Printf(Z/Hi,I'm%s.Nicetomeetyou!\nz/,p.Name)
}
func(t*Teacher)SayHi(){
fmt.Printf(/zHi,mynameis%s.I'mworkingin%s.\n〃,t.Name,
t.Department)
)
func(s*Student)SayHi(){
fmt.Printf(Z/Hi,mynameis%s.rmstudyingin%s.\n/z,s.Name,s.School)
func(s*Student)Study(){
fmt.Printf(/?rmlearningGolangin%s?\n〃,s.School)
}
〃定義接口Speaker和Learner
typeSpeakerinterface{
SayHi()
)
typeLearnerinterface{
SayHi()
Study()
}
funcmain(){
〃初始化對象
people:=People{〃張三〃}
//teacher:=&Teacher{People{〃關B智〃},“ComputerScience”}
//student:=&Student{People{〃李明〃},“YaleUniversity7/)
//varspeackerSpeaker〃定義Speaker接口類型的變量
//speacker=people
people.SayHi()
)
或者
packagemain
import(
〃fmt〃
〃定義對象People、Teacher和Student
typePeoplestruct{
Namestring
}
typeTeacherstruct{
People
Departmentstring
}
typeStudentstruct{
People
Schoolstring
}
〃對象方法實現(xiàn)
func(p*People)SayHi(){
fmt.Printf(Z/Hi,I'm%s.Nicetomeetyou!\n/z,p.Name)
}
func(t^Teacher)SayHi(){
fmt.Printf(Z/Hi,mynameis%s.I'mworkingin%s.\n〃,t.Name,
t.Department)
}
func(s^Student)SayHi()
fmt.Printf(zzHi,mynameis%s.rmstudyingin%s.\n/z,s.Name,s.School)
func(s*Student)Study(){
fmt.Printf(/?rmlearningGolangin%s?\n〃,s.School)
〃定義接口Speaker和Learner
typeSpeakerinterface{
SayHi()
}
typeLearnerinterface{
SayHi()
Study()
}
funcmain(){
〃初始化對象
people:=People{〃張三〃}
//teacher:=&Teacher{People{〃關B智〃},“ComputerScience”}
//student:=&Student{People{〃李明〃},“YaleUniversity”}
varspeackerSpeaker〃定義Speaker接口類型的變量
speacker=fepeople
speacker.SayHi()
}
這樣就都沒有任何問題,這就是說什么呢?這說明對于對象的方法,無
論接受者是對象還是對象指針,都沒
任何問題.但是如果是借口,如果接口中存在某個方法,綁定的接收者是
對象指針,那么這個接口
也只能被該對象指針賦值.如此奇葩的設計,我只能說,g。的設計者真
是個腦殘.
?繼承
好了,下面正式講繼承的語法,話說那玩意兒的真的算不上繼承,比
C++的繼承真的時不知道low到哪里去了.但是我也不知道為啥這是go
愛好者們最愛標榜的東西,
有時我想想,程序員也真是單純的人,一點點的蠱惑,就會讓他們激動
不已,
感覺就要去參加革命了似的.
g。的繼承非常簡陋,就是一個匿名結(jié)構(gòu)組合的問題.不廢話,直接上代
碼.
packagemain
import(
〃fmt〃
〃基類
typeBasestruct{
Namestring
〃綁定Say方法
func(b*Base)Say()
fmt.Println(b.Name)
〃綁定ok方法
func(b*Base)0k()
fmt.Println(/zokz/)
//Foo有個匿名結(jié)構(gòu)Base
typeFoostruct{
Base
Namestring
)
//重寫Say方法
func(f*Foo)Say(){
f.Base.Say()
fmt.Printin(f.Name)
}
funcmain(){
varf*Foo=&F00{Base{"father"},〃sun〃}
f.0k()
f.Say()
)
輸出結(jié)果:
ok
father
sun
ok,下面我們看看多繼承二義性的問題.
packagemain
import(
〃fmt〃
//father
typeFatherstruct{
)
func(f*Father)Say(){
fmt.Printin("father")
}
//mother
typeMotherstruct{
}
func(f"Mother)Say(){
fmt.Printin("mother")
}
//sun
typeSunstruct{
Father
Mother
}
funcmain(){
vars*Sun=new(Sun)
s.Say()
}
輸出:
#command-1ine-arguments
./main,go:32:ambiguousselectors.Say
果然展現(xiàn)了二義性.消歧義方式也是土的掉渣:
packagemain
import(
〃fmt〃
)
//father
typeFatherstruct{
}
func(f*Father)Say(){
fmt.Printin("father")
}
//mother
typeMotherstruct{
)
func(f*Mother)Say(){
fmt.Printin("mother")
}
//sun
typeSunstruct{
Father
Mother
}
funcmain(){
vars*Sun=new(Sun)
s.Father.Say()
s.Mother.Say()
}
我也是醉了.
此外,我們也會看到還有一種用法,
packagemain
import(
)
//基類
typeBasestruct{
Namestring
}
//綁定Say方法
func(b*Base)Say(){
fmt.Printin(b.Name)
}
//綁定ok方法
func(b*Base)0k(){
fmt.Println(〃ok〃)
}
//Foo有個匿名結(jié)構(gòu)Base
typeFoostruct{
*Base〃base是個指針
Namestring
〃重寫Say方法
func(f*Foo)Say(){
f.Base.Say()
fmt.Println(f.Name)
}
funcmain(){
varf*Foo=&F00{&Base{"father"},〃sun〃}
f.Ok()
f.Say()
}
這里Foo的Base是一個指針類型,因此我們傳入的也必須是個指針,
我們可以看到這種用法.
.接口
這個我不想吐槽什么,前面已經(jīng)吐槽過了,但是這種設計,確實也是詭
異之極.
g。的借口是非侵入式的,只要實現(xiàn)了接口定義的方法,就等于實現(xiàn)了
該接口.換句話說,接口的實現(xiàn)和定義式可以分離
不相關的.
接口的例子還是之前那個:
packagemain
import(
〃fmt〃
typeIntegerint
func(aInteger)Less(bInteger)bool{
returna<b
)
func(a^Integer)Add(bInteger){
*a+=b
}
//定義接口
typeLessAdderinterface{
Less(bInteger)bool〃函數(shù)聲明
Add(bInteger)〃函數(shù)聲明
}
funcmain(){
varaInteger=10
varbLessAdder二&a〃道理我們前面提到過了,Add接收者是個對象指針
fmt.Printin(b.Less(5))
b.Add(20)
fmt.Println(a)
}
輸出:
false
30
只要兩個接口擁
有相同的方法列表(次序不同不要緊),那么它們就是等同的,可以相
互賦值。
接口賦值并不要求兩個接口必須等價。如果接口A的方法列表是接口B
的方法列表的子集,
那么接口B可以賦值給接口Ao
幾個接口也可以組合出一個接口.
typeReadWriterinterface{
Reader
Writer
}
等價于:
typeReadWriterinterface{
Read(p[]byte)(nint,errerror)
Write(p[]byte)(nint,errerror)
}
?Any類型
由于Go語言中任何對象實例都滿足接口interface。,所以interface。
看起來是任何對象的Any類型
varvlinterface{}=1
varv2interface{}=〃abc〃
varv3interface{}=&v2
varv4interface{}=struct{Xint}{1}
varv5interface{}=fcstruct{Xint}{1}
funcPrintf(fmtstring,args...interface{})
funcPrintin(args...interface{})
?接口轉(zhuǎn)換和類型查詢
接口轉(zhuǎn)換
packagemain
import(
〃fmt〃
)
typeIntegerint
func(aInteger)Less(bInteger)bool{
returna<b
}
func(a*Integer)Add(bInteger){
*a+二b
)
〃定義接口
typeLessAdderinterface{
Less(bInteger)bool〃函數(shù)聲明
Add(bInteger)//函數(shù)聲明
)
〃定義接口
typeAdderinterface{
Add(bInteger)〃函數(shù)聲明
}
funcmain(){
varaInteger=10
varbLessAdder=&a〃道理我們前面提到過了,Add接收者是個對象指針
ifc,ok=b.(Adder);ok{
c.Add(10)
fmt.Println(a)
//fmt.Println(c.Less(100))//報錯,c.Lessundefined(typeAdderhas
nofieldormethodLess)
)
)
類型查詢
packagemain
import(
"fmt"
)
funcmain(){
i〃〃
b:=a
varainterface{}=b
switcha.(type){
caseint:
fmt.Printin(z/intz,)
casefloat32:
fmt.Printin(/zfloat32z/)
caseint32:
fmt.Printin(,zint32z/)
casefloat64:
fmt.Printin(zzfloat64/z)
casebool:
fmt.Printin(〃bool〃)
casestring:
fmt.Printin("string")
default:
fmt.Println(/Zok/Z)
?補充
我們補充一些defer-panic-recover的案列.
packagemain
import(
〃fmt〃
)
funcf(){
fmt.Println(〃a〃)
}
funcmain(){
deferfunc(){
iferr:=recover();err!=nil{
fmt.Println(err)
}
)()
f()
fmt.Println(〃b〃)
)
輸出結(jié)果:
a
b
如果我們在f中panic呢?這會發(fā)生什么呢?
packagemain
import(
〃fmt〃
)
funcf(){
fmt.Println(〃a〃)
panic("error!〃)
}
funcmain(){
deferfunc(){
iferr:=recover();err!=nil{
fmt.Println(err)
)
)()
f()
fmt.Println(/Zb/Z)
}
輸出:
a
error!
我們發(fā)現(xiàn),b沒有輸出.panic會拋出一個異常,由recover去捕獲.f拋出
異常后,事實上,
main剩下的部分都不會執(zhí)行,但是因為我們defer了,
defer是一定會執(zhí)行的,因此我們在defer中捕獲了panic拋出的
異常.這就是為什么b沒有輸出.似乎和trycatch很像.如果我們希望
把b也輸出,但也能捕獲異常呢?
packagemain
import(
〃fmt〃
)
funcf(){
fmt.Println(〃a〃)
panic("error!〃)
}
funcmain(){
func(){
deferfunc(){
iferr:=recover();err!=nil{
fmt.Println(err)
}
)()
f()
)()
fmt.Println(〃b〃)
)
輸出結(jié)果:
a
error!
b
如果是這樣呢?
packagemain
import(
〃fmt〃
)
funcf(){
fmt.Println(〃a〃)
panic("error!〃)
}
funcmain(){
func(){
f()
deferfunc(){
iferr:=recover();err!=nil{
fmt.Println(err)
)
}()
)()
fmt.Println(〃b〃)
}
此時,在定義defer之前,f已經(jīng)panic了,沒有recover去捕獲,這個
panic會一直拋出.
直到被go虛擬機捕獲.
輸出:
a
panic:error!
goroutine1[running]:
main,f()
/Users/fengyan/code/go/test/main,go:9+0xlle
main.main,fund()
/Users/fengyan/code/go/test/main,go:14+0x18
main,main()
/Users/fe
go里面有個東西很好玩,nil類似于java的null,那么java如果對null
調(diào)用方法,會直接拋出一個空指針異常.
那么go會怎么樣呢?
packagemain
funcmain(){
nil.Printin(〃a〃)
}
輸出結(jié)果:
#command-line-arguments
./main,go:4:useofuntypednil
看來還是不行的.
所以調(diào)用前我們還是要進行空的判斷.
三.go并發(fā)編程
并發(fā)不是
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經(jīng)權益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
- 6. 下載文件中如有侵權或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 小學五年級數(shù)學小數(shù)乘除法豎式計算練習題
- 土方分包合同范本-合同范本
- 《美容項目專業(yè)知識》課件
- 《醫(yī)院急診科的管理》課件
- 屆每日語文試題精練
- 更新采伐公路護路林許可申請表
- 《家用醫(yī)療用具使用》課件
- 金融產(chǎn)業(yè)電話理財顧問績效總結(jié)
- 快遞公司保安工作總結(jié)
- 醫(yī)療器械行業(yè)安全工作總結(jié)
- 污水處理廠有毒有害氣體檢測記錄表
- 馬克思主義與社會科學方法論課后思考題答案全
- 針灸推拿習題庫+參考答案
- 手術區(qū)皮膚消毒及鋪單法課件
- 血液科侵襲性真菌的治療
- 淺析巖溶地區(qū)工程地質(zhì)勘察手段及應用
- 2023-2024學年六年級上期末數(shù)學考試試卷附答案解析
- 羅伊模式個案護理
- 公益性崗位開發(fā)申請審批表
- 中國馬克思主義與當代知到章節(jié)答案智慧樹2023年西安交通大學
- 組織協(xié)同運用平衡計分卡創(chuàng)造企業(yè)合力
評論
0/150
提交評論