網(wǎng)絡(luò)公司給別人做網(wǎng)站的cms是買的授權(quán)么自媒體怎么入門
面向?qū)ο?/h5>
-
Golang 也支持面向?qū)ο缶幊?OOP),但是和傳統(tǒng)的面向?qū)ο缶幊逃袇^(qū)別,并不是純粹的面向?qū)ο笳Z言。
-
Golang 沒有類(class),Go 語言的結(jié)構(gòu)體(struct)和其它編程語言的類(class)有同等的地位,Golang 是基于 struct 來實(shí)現(xiàn) OOP 特性的,去掉了傳統(tǒng) OOP 語言的繼承、方法重載、構(gòu)造函數(shù)和析構(gòu)函數(shù)、隱藏的 this 指針等等
-
Golang 仍然有面向?qū)ο缶幊痰睦^承,封裝和多態(tài)的特性,只是實(shí)現(xiàn)的方式和其它 OOP 語言不一樣,比如繼承 :Golang 沒有 extends 關(guān)鍵字,繼承是通過匿名字段來實(shí)現(xiàn)。
-
Golang 面向?qū)ο?OOP)很優(yōu)雅,OOP 本身就是語言類型系統(tǒng)(type system)的一部分,通過接口(interface)關(guān)聯(lián),耦合性低,也非常靈活。
一、基礎(chǔ)知識
數(shù)據(jù)類型
Golang 也支持面向?qū)ο缶幊?OOP),但是和傳統(tǒng)的面向?qū)ο缶幊逃袇^(qū)別,并不是純粹的面向?qū)ο笳Z言。
Golang 沒有類(class),Go 語言的結(jié)構(gòu)體(struct)和其它編程語言的類(class)有同等的地位,Golang 是基于 struct 來實(shí)現(xiàn) OOP 特性的,去掉了傳統(tǒng) OOP 語言的繼承、方法重載、構(gòu)造函數(shù)和析構(gòu)函數(shù)、隱藏的 this 指針等等
Golang 仍然有面向?qū)ο缶幊痰睦^承,封裝和多態(tài)的特性,只是實(shí)現(xiàn)的方式和其它 OOP 語言不一樣,比如繼承 :Golang 沒有 extends 關(guān)鍵字,繼承是通過匿名字段來實(shí)現(xiàn)。
Golang 面向?qū)ο?OOP)很優(yōu)雅,OOP 本身就是語言類型系統(tǒng)(type system)的一部分,通過接口(interface)關(guān)聯(lián),耦合性低,也非常靈活。
## golang字符類型
字符類型的本質(zhì)是一個(gè)整數(shù),占8個(gè)字節(jié)(Go 的字符串是由字節(jié)組成的,根據(jù)utf-8編碼)
字符型 存儲到 計(jì)算機(jī)中,需要將字符對應(yīng)的碼值(整數(shù))找出來
存儲:字符—>對應(yīng)碼值---->二進(jìn)制–>存儲
讀取:二進(jìn)制----> 碼值 ----> 字符 --> 讀取
字符和碼值的對應(yīng)關(guān)系是通過字符編碼表決定的(是規(guī)定好)
## golang字符串類型
兩種表現(xiàn)形式:
(1) 雙引號, 會(huì)識別轉(zhuǎn)義字符
(2) 反引號,以字符串的原生形式輸出,包括換行和特殊字符,可以實(shí)現(xiàn)防止攻擊、輸出源代碼等效果
溢出問題
a := int8(127)b := int8(1)fmt.Println(a + b) // 輸出-128,不會(huì)報(bào)錯(cuò)a := uint8(255)b := uint8(1)fmt.Println(a + b) //輸出0,不會(huì)報(bào)錯(cuò)
rune 類型:相當(dāng)int32,由于golang中的字符串底層實(shí)現(xiàn)是通過byte數(shù)組的,中文字符在unicode下占2個(gè)字節(jié),在utf-8編碼下占3個(gè)字節(jié)
- byte 等同于int8,常用來處理ascii字符
- rune 等同于int32,常用來處理unicode或utf-8字符
數(shù)組和切片
數(shù)組:
- 數(shù)組的地址可以通過數(shù)組名來獲取 &intArr
- 數(shù)組的第一個(gè)元素的地址,就是數(shù)組的首地址
- 數(shù)組的各個(gè)元素的地址間隔是依據(jù)數(shù)組的類型決定,比如 int64 -> 8 int32->4…
切片:
slice 底層數(shù)據(jù)結(jié)構(gòu)是由一個(gè) array 指針指向底層數(shù)組,len 表示切片長度,cap 表示切片容量
當(dāng)擴(kuò)容時(shí):
- 假如 slice 容量夠用,則追加新元素進(jìn)去,slice.len++,返回原來的 slice。
- 當(dāng)原容量不夠,則 slice 先擴(kuò)容,擴(kuò)容之后 slice 得到新的 slice,將元素追加進(jìn)新的 slice,slice.len++,返回新的 slice。
擴(kuò)容規(guī)則:當(dāng)切片比較小時(shí)(容量小于 1024),則采用較大的擴(kuò)容倍速進(jìn)行擴(kuò)容(新的擴(kuò)容會(huì)是原來的 2 倍),避免頻繁擴(kuò)容,從而減少內(nèi)存分配的次數(shù)和數(shù)據(jù)拷貝的代價(jià)。當(dāng)切片較大的時(shí)(原來的 slice 的容量大于或者等于 1024),采用較小的擴(kuò)容倍速(新的擴(kuò)容將擴(kuò)大大于或者等于原來 1.25 倍),主要避免空間浪費(fèi)
和切片的區(qū)別:
- 1)數(shù)組是定長,訪問和復(fù)制不能超過數(shù)組定義的長度,否則就會(huì)下標(biāo)越界,切片長度和容量可以自動(dòng)擴(kuò)容
- 2)數(shù)組是值類型,切片是引用類型,每個(gè)切片都引用了一個(gè)底層數(shù)組,切片本身不能存儲任何數(shù)據(jù),都是這底層數(shù)組存儲數(shù)據(jù),所以修改切片的時(shí)候修改的是底層數(shù)組中的數(shù)據(jù)。切片一旦擴(kuò)容,指向一個(gè)新的底層數(shù)組,內(nèi)存地址也就隨之改變
Channel
go中的channel是一個(gè)隊(duì)列,遵循先進(jìn)先出的原則,負(fù)責(zé)協(xié)程之間的通信,channel 是 goroutine 之間數(shù)據(jù)通信橋梁,而且是線程安全的,寫入,讀出數(shù)據(jù)都會(huì)加鎖。
三種類型:只讀 channel、只寫 channel(意義在于在參數(shù)傳遞時(shí)候指明管道可讀還是可寫,即使當(dāng)前管道是可讀寫的)、可讀可寫 channel
channel 中只能存放指定的數(shù)據(jù)類型
channle 的數(shù)據(jù)放滿后,就不能再放入了
在沒有使用協(xié)程的情況下,如果 channel 數(shù)據(jù)取完了,再取,就會(huì)報(bào) dead lock
goroutine 中使用 recover,解決協(xié)程中出現(xiàn) panic,導(dǎo)致程序崩潰問題
應(yīng)用場景:
- 停止信號監(jiān)聽
- 定時(shí)任務(wù)
- 生產(chǎn)方和消費(fèi)方解耦
- 控制并發(fā)數(shù)
底層原理:
有緩沖的channel使用ring buffer(環(huán)形緩沖區(qū))來緩存寫入的數(shù)據(jù),本質(zhì)是循環(huán)數(shù)組(為啥用循環(huán)數(shù)組?普通數(shù)組容量固定、更適合指定的空間,且彈出元素時(shí),元素需要全部前移)
流程:
## 寫數(shù)據(jù)
如果channel的讀等隊(duì)列存在接受者goroutine
將數(shù)據(jù)直接發(fā)送給第一個(gè)等待的goroutine,喚醒接收的goroutine
如果channel的讀等隊(duì)列不存在接受者goroutine如果循環(huán)數(shù)組的buf未滿,那么將數(shù)據(jù)發(fā)送到循環(huán)數(shù)組的隊(duì)尾如果循環(huán)數(shù)組的buf已滿,將當(dāng)前的goroutine加入寫等待對列,并掛起等待喚醒接收
## 讀數(shù)據(jù)
如果channel的寫等待隊(duì)列存在發(fā)送者goroutine如果是無緩沖channel,直接從第一個(gè)發(fā)送者goroutine那里把數(shù)據(jù)拷貝給接收變量,喚醒發(fā)送的gorontine如果是有緩沖channel(已滿),將循環(huán)數(shù)組buf的隊(duì)首元素拷貝給接受變量,將第一個(gè)發(fā)送者goroutine的數(shù)據(jù)拷貝到循環(huán)數(shù)組隊(duì)尾,喚醒發(fā)送端goroutine如果channel的寫等待隊(duì)列不存在發(fā)送者goroutine如果循環(huán)數(shù)組buf非空,將循環(huán)數(shù)據(jù)buf的隊(duì)首元素拷貝給接受變量如果循環(huán)數(shù)組buf為空,這個(gè)時(shí)候就會(huì)走阻塞接收的流程,將當(dāng)前goroutine加入讀等隊(duì)列,并掛起等待喚醒
## 相比較共享內(nèi)存共享內(nèi)存訪問需要加鎖,若持鎖失敗,要么忙等重試,要么待會(huì)兒再來。降低耦合:channel以消息傳遞通信,消息發(fā)出后就不用管了,除非它希望得到回饋,完全異步。
Map
原理:底層使用 hash table,每個(gè) map 的底層結(jié)構(gòu)是 hmap,是有若干個(gè)結(jié)構(gòu)為 bmap(鏈表) 的 bucket 組成的數(shù)組。用鏈表來解決沖突 ,出現(xiàn)沖突時(shí),不是每一個(gè) key 都申請一個(gè)結(jié)構(gòu)通過鏈表串起來,而是以 bmap 為最小粒度掛載,一個(gè) bmap 可以放 8 個(gè) kv。在哈希函數(shù)的選擇上,會(huì)在程序啟動(dòng)時(shí),檢測 cpu 是否支持 aes,如果支持,則使用 aes hash,否則使用 memhash。
key 可以是很多種類型,比如 bool, 數(shù)字,string, 指針, channel ,,接口, 結(jié)構(gòu)體, 數(shù)組,slice, map 還有 function 不可以,因?yàn)檫@幾個(gè)沒法用 == 來判斷
聲明是不會(huì)分配內(nèi)存的,初始化需要 make ,分配內(nèi)存后才能賦值和使用
map對象不是線程安全的,并發(fā)讀寫的時(shí)候運(yùn)行時(shí)會(huì)有檢查,遇到并發(fā)問題就會(huì)導(dǎo)致panic
## 內(nèi)存回收
1. go 底層map 是由若干個(gè)bmap(桶)構(gòu)成的,桶只會(huì)擴(kuò)容,不會(huì)縮容 ,所以 map中占用的內(nèi)存不會(huì)被釋放
以上只針對值類型的數(shù)據(jù)結(jié)構(gòu) 例如:基本類型 int string slice struct 等
2. 如果key為 指針變量 刪除后這個(gè)指針變量內(nèi)存不會(huì)釋放,但是這個(gè)指針指向的對象,引用計(jì)數(shù)會(huì) -1 如果引用計(jì)數(shù)為0 在gc的時(shí)候就會(huì)被釋放!## 元素有序性
map 因擴(kuò)張?重新哈希時(shí),各鍵值項(xiàng)存儲位置都可能會(huì)發(fā)生改變,順序自然也沒法保證了,所以官方避免大家依賴順序,直接打亂處理,每次遍歷,得到的輸出 可能不一樣。
for range map 在開始處理循環(huán)邏輯的時(shí)候,就做了隨機(jī)播種(要想有序遍歷,可以先將 key 進(jìn)行排序,然后根據(jù) key 值遍歷)## 線程安全
map對象不是線程安全的,并發(fā)讀寫的時(shí)候運(yùn)行時(shí)會(huì)有檢查,遇到并發(fā)問題就會(huì)導(dǎo)致panic
解決方法:使用sync.Map、使用讀寫鎖
結(jié)構(gòu)體
type Person struct {Name string `json:name-field`Age int
}
- 結(jié)構(gòu)體指針訪問字段的標(biāo)準(zhǔn)方式應(yīng)該是:(*結(jié)構(gòu)體指針).字段名 ,但 go 做了一個(gè)簡化,也支持 結(jié)構(gòu)體指針.字段名, 更加符合程序員使用的習(xí)慣,go 編譯器底層 對 person.Name 做了轉(zhuǎn)化 (*person).Name。
- 結(jié)構(gòu)體的所有字段在內(nèi)存中是連續(xù)的
- 結(jié)構(gòu)體進(jìn)行 type 重新定義(相當(dāng)于取別名),Golang 認(rèn)為是新的數(shù)據(jù)類型,但是相互間可以強(qiáng)轉(zhuǎn)(和其它類型進(jìn)行轉(zhuǎn)換時(shí)需要有完全相同的字段(名字、個(gè)數(shù)和類型)
- struct 的每個(gè)字段上,可以寫上一個(gè) tag, 該 tag 可以通過反射機(jī)制獲取,常見的使用場景就是序
列化和反序列化。
函數(shù)與方法
//函數(shù)
func getArea(R int) float64 {return math.Pi * math.Pow(R, 2)
}
//方法
func (c Circle)getArea() float64 {return math.Pi * math.Pow(c.R, 2)
}
方法的調(diào)用和傳參機(jī)制和函數(shù)基本一樣,不一樣的地方是方法調(diào)用時(shí),會(huì)將調(diào)用方法的變量,當(dāng)做實(shí)參也傳遞給方法,體現(xiàn)了封裝性。函數(shù)則是無狀態(tài)的代碼塊。
Go 的函數(shù)參數(shù)傳遞都是值傳遞:調(diào)用函數(shù)時(shí)將實(shí)際參數(shù)復(fù)制一份傳遞到函數(shù)中,這樣在函數(shù)中如果對參數(shù)進(jìn)行修改,將不會(huì)影響到實(shí)際參數(shù)。
對象
make和new
1)作用變量類型不同,new給string,int和數(shù)組分配內(nèi)存,make給切片,map,channel分配內(nèi)存;2)返回類型不一樣,new返回指向變量的指針,make返回變量本身;3)new 分配的空間被清零。make 分配空間后,會(huì)進(jìn)行初始化;
繼承
type Person struct {id intname stringage int
}type Student struct {Personid intscore intclassName string
}
- 使用匿名屬性,來實(shí)現(xiàn)繼承:即將父類作為子類的匿名屬性
- 如果父類和子類中有重復(fù)字段,則優(yōu)先使用子類自身的屬性
- 方法的重寫(方法名,參數(shù),返回值類型都必須一樣)此時(shí)調(diào)用方法綁定的對象不在時(shí)父類而是子類本身
接口
- 空接口
// fmt包中的方法 Println底層
func Println(a ...interface{}) (n int, err error) {return Fprintln(os.Stdout, a...)
}
// 接納任意對象
var i interface{} = 45
i=[...]int{1,2,3}
可以接納任意對象,類似java中的Object
- 接口
可以定義一些通用的方法,將被繼承和實(shí)現(xiàn)的接口以匿名屬性傳入即可,但不必將所有的方法都實(shí)現(xiàn)
type annimal interface {eat()sleep()run()
}
type cat interface {annimalClimb()
}
- 多態(tài)
可以在調(diào)用方法時(shí)會(huì)因傳入對象的不同而得到不同的效果
// 使用 對象.(指定的類型) 判斷改對象是否時(shí)指定的類型
if data,ok :=v.(cat);ok{data.eat()fmt.Println("this is HelloKitty : ")}
實(shí)現(xiàn)接口中的方法可以通過指針和結(jié)構(gòu)體綁定
type animal interface {eat()
}
type Dog struct {Name stringAge int
}
//func (d Dog) eat() { 結(jié)構(gòu)體綁定
//}
func (d *Dog) eat() { 指針綁定
}func main() {var a animaldPoint := &Dog{Name: "susan",Age: 12,}dStruct := Dog{Name: "susan",Age: 12,}a = dPoint// 使用指針接收者實(shí)現(xiàn)接口不能存結(jié)構(gòu)體類型變量// a = dStruct
}
區(qū)別:使用值接受者實(shí)現(xiàn)接口,結(jié)構(gòu)體類型和結(jié)構(gòu)體指針類型的變量都能存,指針接收者實(shí)現(xiàn)接口只能存指針類型的變量
異常
- 編譯時(shí)異常:在編譯時(shí)拋出的異常,編譯不通過,語法使用錯(cuò)誤,符號填寫錯(cuò)誤等等。。。
- 運(yùn)行時(shí)異常:在程序運(yùn)行時(shí)拋出的異常,這個(gè)才是我們將要說的,程序運(yùn)行時(shí),有很多狀況發(fā)生,例如:讓用戶輸入一個(gè)數(shù)字,可用戶偏偏輸入一個(gè)字符串,導(dǎo)致的異常,數(shù)組的下標(biāo)越界,空指針等等。。。。
編譯時(shí)異常很容易找到,而運(yùn)行時(shí)異常不容易提前發(fā)現(xiàn),通過if err != nil
判斷,但是依然會(huì)漏掉很多異常,因此我們需要在運(yùn)行過程中動(dòng)態(tài)的捕獲異常
defer和recover
defer:延時(shí)執(zhí)行,即在方法執(zhí)行結(jié)束(出現(xiàn)異常而結(jié)束或正常結(jié)束)時(shí)執(zhí)行
recover:恢復(fù)的意思,如果是異常結(jié)束程序不會(huì)中斷,返回異常信息,可以根據(jù)異常來做出相應(yīng)的處理
recover必須放在defer的函數(shù)中才能生效
func test(a int, b int) int {defer func() {err := recover()fmt.Println("err:",err)}()a = b / areturn a
}
func main() {i := test(0, 1)fmt.Println("====main方法正常結(jié)束!!====",i)
}//結(jié)果:
err: runtime error: integer divide by zero
====main方法正常結(jié)束!!==== 0
手動(dòng)拋出異?!猵anic
有些異常是不應(yīng)該恢復(fù)的,應(yīng)該拋出異常,可以讓這個(gè)異常一層層的返回給調(diào)用方的程序,使其不能繼續(xù)執(zhí)行,從而起到保護(hù)后面業(yè)務(wù)的目的
func test(a int) int {i:=100 - aif i<0{panic(errors.New("賬戶金額不足!!!!"))}fmt.Println("=======賬戶扣款=====")return i
}