快速入門 go 語言筆記懦鼠,參考了各種大佬的 blog 俐末。
一丧鸯、下載安裝
國內站點 :https://golang.google.cn
IDE : Goland
(2020.12月以后破解有些困難 見 zhile.io, 無法試用的話可以搜一個注冊碼官研,失效的也可以,驗證成功后斷網進入軟件闯睹,再使用 zhile.io)
自行配置 go sdk 戏羽,go root 、go path新建項目楼吃, 創(chuàng)建 main.go
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
注:
右鍵新建文件會遇到如下錯誤 :
https://blog.csdn.net/my_miuye/article/details/103706162
執(zhí)行命令 :go run XXX.go
編譯命令 : go build XXX.go
形成可執(zhí)行文件 exe
二始花、基本語法
1、go 風格 :
一個問題只有一個解決方案
例如 : 逐行編譯(末尾自動加分號)孩锡、 嚴格格式化 gofmt -w XXX.go
(幫助格式化代碼)……
建議每行不要超過 80 個字符酷宵。
2、文檔:
https://golang.google.cn/pkg/
3躬窜、 數據類型 :
(以下開始和 java 產生嚴重分歧)
1)基本數據類型 : 數值型 浇垦、 非數值型
數值型 : 整數數值型 (int、int8斩披、int16…uint溜族、uint8…byte)、 浮點數值型(float32 垦沉、 float64)
非數值型 : 字符型 (用 byte 保存單個字母)煌抒、布爾(bool)、字符串(string 2薇丁寡壮!區(qū)別于java)
2)復雜數據類型 : (派生數據類型)
指針 (Pointer)
數組
結構體(struct)
管道 (Channel)
函數
切片(slice)
接口(interface)
map
注 : 如何查看一個變量的數據類型 ?
var unknow = 123
// 查看類型 %T
fmt.Printf("類型 : %T", unknow)
fmt.Println()
// 查看字節(jié) %d
fmt.Printf("字節(jié) :%d",unsafe.Sizeof(unknow))
go 在保證程序正常運行的前提下讹弯,應使用盡可能小空間變量類型
3况既、浮點數
跟 java 一樣,分為單精度组民、雙精度棒仍, 支持 e 表示科學計數法 , 也會損失精度臭胜。
因為 莫其, 存儲方式是將小數拆分成 “符號位 + 指數位 + 尾數位” (即科學計數法)存儲在計算機中
golang 小數默認類型 float64癞尚, 開發(fā)中推薦使用 float64
(與 java 相同, java 默認 double)
4乱陡、字符如何保存浇揩?
golang 沒有 char 為字符單獨設計的類型,所以憨颠, 字符用 byte 保存胳徽。
golang 的字符串用固定長度的字符連接起來, 即用固定長度的 byte 序列表示 string爽彤。
golang 的字符串也是不可變數據類型养盗。
var str string = "這是一句話 , 天生 utf-8 适篙,沒有亂碼 \n"
fmt.Println(str)
// 對于特殊格式字符串 爪瓜, 反引號輸出帶格式 string
var content string = `這里可以帶任何格式 :
換行!`
fmt.Println(content)
注 : 字符串拼接 + 放在前一行末尾
4匙瘪、基本數據類型的默認值
數值型均為 0 , bool 默認 false 蝶缀, 字符串 ""
5丹喻、基本數據類型之間的轉換
必須強制類型轉換, 不像 java 一樣可以自動轉型N潭肌0邸(低精度到高精度也不可以)
語法 :
//數據類型強制轉換
var convert float64 = float64(unknow)
fmt.Printf("強制轉換 %T -> %T ", unknow, convert)
注 : 變量 + 數字, 此時數字按默認值 即 int64 跟隨OS位數 柄慰。 二次賦值可能報錯鳍悠。
6、基本數據類型 與 string 轉換
fmt.Sprintf("", XX)
go 中只能通過 strconv 包中的函數進行轉換坐搔!
var t string = strconv.FormatFloat(float64(price),'f',1,64)// 方法2
fmt.Printf("%T : %s \n", t, t)
// string 轉基本數據類型
var string2Float, _ = strconv.ParseFloat("12.23445", 64)
fmt.Printf("%T : %f \n", string2Float, string2Float)
注:
Parse 返回兩個值藏研,下劃線表示忽略其中一個返回值。
bit 位對應接收變量的 bit 位 概行。
其他類型同理蠢挡, 需要查看官方文檔。
string 轉基本數據類型凳忙,如果不能轉換业踏, 會直接轉為該基本類型的默認值。
7涧卵、指針
指針變量已經指向了一個內存空間勤家,但存放的是后面變量的地址。
指針變量同樣有自己的地址
var pointer *int8 = &i // 變量i 的內存地址
fmt.Println(pointer)
如何取出指針指向的變量的值柳恐? 利用 *
8伐脖、值類型 與 引用數據類型
(值類型 “一般” 是在棧內存直接分配空間 热幔, 引用類型 通常 在堆也會 gc, 與 java 相同晓殊, 但 string 是個例外)
值類型: 基本數據類型 断凶、 數組、結構體
引用數據類型: 指針巫俺、切片认烁、管道等
代碼
package main
import (
"fmt"
"strconv"
"unsafe"
)
func main() {
// hello world
fmt.Println("hello world")
// 有符號整數
var i int8 = 100
fmt.Println("i : ", i)
// 無符號整數
var j uint16 = 256
fmt.Println("j : ", j)
// 程序員需要根據 數據范圍 對變量進行限制
// 注意 1 : byte ~ uint8
// 注意 2 : int 不限定位數,默認與系統位數相同 (多為 64 位)
var k byte = 255 // 256 會 overflow
fmt.Println("k : ", k)
var unknow = 123
// 查看類型 %T
fmt.Printf("類型 : %T \n", unknow)
// 查看字節(jié) %d
fmt.Printf("字節(jié) :%d \n",unsafe.Sizeof(unknow))
// 浮點型
var price float32 = 12.67
fmt.Println("price : ", price)
// 字符與字符串
var char1 byte = 'a'
fmt.Println(char1)
var char2 byte = '3'
fmt.Println(char2)
fmt.Printf("char1 = %c , char2 = %c \n", char1, char2)
// 溢出情況 : 用范圍更大的數值變量介汹,如int
var char3 int = '李'// 字符常量用單引號表示
fmt.Printf("%c", char3)
var str string = "這是一句話 却嗡, 天生 utf-8 ,沒有亂碼!"
fmt.Println(str)
// 對于特殊格式字符串 嘹承, 反引號輸出帶格式 string
var content string = `這里可以帶任何格式 :
換行窗价!`
fmt.Println(content)
//數據類型強制轉換
var convert float64 = float64(unknow)
fmt.Printf("強制轉換 %T -> %T \n", unknow, convert)
// 基本數據類型轉 string
var float2String string = fmt.Sprintf("%f", price) // 方法1
fmt.Printf("%T \n", float2String)
var t string = strconv.FormatFloat(float64(price),'f',1,64)// 方法2
fmt.Printf("%T : %s \n", t, t)
// string 轉基本數據類型
var string2Float, _ = strconv.ParseFloat("12.23445", 64)
fmt.Printf("%T : %f \n", string2Float, string2Float)
// 指針
var pointer *int8 = &i // 變量i 的內存地址
fmt.Println(pointer)
fmt.Println(*pointer)// 取出指針變量指向的地址的值
fmt.Println()
}
三 、算術運算符
基本與 java 相同
1叹卷、 自增撼港,自減不能參與賦值運算。 只能作為獨立運算
2骤竹、取模 本質 a % b = a - ( a / b ) * b
3帝牡、自增,自減 只能寫在變量后面 蒙揣, a++
4靶溜、運算符的優(yōu)先級
單目運算、賦值運算符 從右到左 懒震, 如 罩息!、 sizeof 个扰;
其余都從左到右瓷炮!
注 :
,
也是運算符, 優(yōu)先級最低递宅;
&
返回變量的地址崭别, 放在變量前 &a
給出實際地址;
*
表示指針變量
5恐锣、不支持 三目運算符 茅主, 與 go 的單一設計理念相違背
6、:=
是聲明并賦值土榴,并且系統自動推斷類型诀姚,不需要var關鍵字
四、 獲取鍵盤輸入
fmt.Scanln() 行輸入
與 java 不同玷禽,是可以直接通過地址賦值給變量
// 鍵盤輸入
var name string
fmt.Println("請輸入姓名:")
fmt.Scanln(&name)
fmt.Println("姓名:", name)
也可以通過占位符 配合 Scanf 作為格式化輸入
占位符號 參考 :
https://www.cnblogs.com/junneyang/p/6069248.html
五赫段、流程控制
1昙读、 分支控制
常規(guī) if else 毛仪, 但是 golang 支持在 if 條件中定義一個變量哮幢,并且只在條件語句作用域中起作用唬党。
if age := 20; age > 18{
fmt.Println("你的年齡是 :",age)
}
注 :
即使只有一條語句也不能省略括號。
也有 switch 给涕, 注意不需要 break 豺憔!case 后面只要是一個表達式, 只要有返回值即可够庙。
switch key{
case XX, XX, XX... :
....
....
...
default
.....
}
switch 也可以不帶表達式恭应, 當 if else 使用
注意:
fallthrough 關鍵字 , 表示 switch 穿透 耘眨, 走到這個關鍵字跳入下一個 case 條件昼榛。
type switch ,等學過 interface 后補充 剔难,x interface x.(type)
胆屿。
2、循環(huán)控制
第一種 偶宫,for 常規(guī)三條件使用莺掠。第二種是單一條件, 第三種無條件即死循環(huán)读宙。
最后兩種使用方式并不習慣 , 結合 for 內部使用 if 實現代替 while楔绞。
s := "abcdefg"
for i := 0; i < len(s); i++ {
fmt.Printf("%c", s[i])
}
注: 此方式遍歷 按照字節(jié)逐個遍歷结闸,漢字 utf - 8 對應 3 個字節(jié),會出現亂碼酒朵!需要使用 []rune
轉換 string桦锄。 newS = []rune(s)
for-range 遍歷不會出現問題,默認按照字符的方式遍歷 蔫耽。 但是 index 會出現不連續(xù)结耀!
// for range
s = "我是誰"
for index, val := range s {
fmt.Printf("index=%d , val=%c \n", index, val)
}
output :
index=0 , val=我
index=3 , val=是
index=6 , val=誰
注 : TMD !golang 中沒有 while 和 do while 匙铡。
3图甜、 break 與 continue
golang 可以指定跳出位置,配合 label 使用鳖眼。
// break 默認跳出最近循環(huán)黑毅, golang 通過 label 指定跳出位置
labelName :
for i := 0; i < 5; i++{
for j := 0; j < 10; j++{
fmt.Printf("i = %d :: j = %d \n", i, j)
if (i == 3 && j == 7) {
//break
break labelName
}
}
}
注 : continue 同理也可以配合標簽
4、goto
golang 支持 goto 钦讳, 還是不推薦使用矿瘦,造成流程混亂枕面。
一般 配合 if 和 label 使用 。
六缚去、函數 和 包
1潮秘、golang 中函數和方法是有區(qū)別的
2、函數與變量的作用域(是否包外可見)是通過名稱首字母大小寫來確定的易结,大寫即包外可見
3枕荞、值傳遞與引用傳遞 : 與 java 類似基本數據類型都是值傳遞, 即復制一份值到棾某模空間买猖, 然后改變棧空間的值滋尉。但是S窨亍!狮惜! go 中的數組是引用傳遞高诺,與 java 不同!因為 go中有指針碾篡,如果希望函數內修改函數外部值虱而,可以傳遞指針變量。不需要像 java 一樣包成引用數據類型开泽。
4牡拇、為了符合 go 的設計初衷, 一種事物有一種解穆律。go 是不支持函數重載的惠呼。(go 有可變參數,所以可以達到調用相同函數峦耘,傳遞不同參數)
5剔蹋、go 中函數也是一種數據類型,可以賦值給一個變量
6辅髓、函數本身可以作為形參泣崩,傳遞給其他函數 func myfun(param func(int,int) int, p1 int, p2 int) {...}
7、自定義數據類型關鍵字 type 洛口, 像對原有的數據類型起別名(相當于矫付,但不是等同于同一個數據類型), type myTypeName string
可以創(chuàng)建 myTypeName 類型的 string 變量第焰,聲明在調用之前技即。
.......
// 函數
sv := sum
fmt.Println(sv(10,5))
.......
func sum(a int, b int) int {
return a + b
}
8、很方便區(qū)別, go 支持對返回值命名
func sumAndSub(a int, b int) (sum int, sub int) {
sum = a + b
sub = a - b
return //對返回值賦值后而叼,不需要返回sum sub
}
// 調用
.....
p1 := 13
p2 := 11
res1, res2 := sumAndSub(p1, p2)
fmt.Printf("變量1 : %d, 變量2 : %d , sum : %v, sub : %v" , p1, p2, res1, res2)
......
注 : 下劃線 占位符可以忽略返回值身笤, day1 提到過。
9葵陵、可變參數 參數名后加 ...
func testFunc(p1 int, args... int) int {....}
表示 1 到多個參數
注 :
args 是 slice 切片液荸, 理解為可變數組。
可變參數需要放在形參列表最后脱篙。
代碼
package main
import "fmt"
func main() {
// ++ --
var a int8 = 56
// var b int8 = a-- // golang 禁止
a--
var b int8 = a - 1
fmt.Printf(" b : %d \n", b)
// 鍵盤輸入
var name string
fmt.Println("請輸入姓名:")
fmt.Scanln(&name)
fmt.Println("姓名:", name)
// 流程控制
if age := 20; age > 18{
fmt.Println("你的年齡是 :",age)
}
for i:= 0; i < 10; i++ {
fmt.Printf("第 %d 次循環(huán) \n", i )
}
s := "abcdefg"
for i := 0; i < len(s); i++ {
fmt.Printf("%c", s[i]) // 此方式遍歷 按照字節(jié)逐個遍歷娇钱,漢字 utf - 8 對應 3 個字節(jié)會出現亂碼
}
fmt.Println()
// for range
s = "我是誰"
for index, val := range s {
fmt.Printf("index=%d , val=%c \n", index, val)
}
// break 默認跳出最近循環(huán), golang 通過 label 指定跳出位置
labelName :
for i := 0; i < 5; i++{
for j := 0; j < 10; j++{
fmt.Printf("i = %d :: j = %d \n", i, j)
if (i == 3 && j == 7) {
//break
break labelName
}
}
}
// 函數
sv := sum
fmt.Println(sv(10,5))
p1 := 13
p2 := 11
res1, res2 := sumAndSub(p1, p2)
fmt.Printf("變量1 : %d, 變量2 : %d , sum : %v, sub : %v" , p1, p2, res1, res2)
}
func sum(a int, b int) int {
return a + b
}
func sumAndSub(a int, b int) (sum int, sub int) {
sum = a + b
sub = a - b
return //對返回值賦值后绊困,不需要返回sum sub
}
init 函數 和 go 文件執(zhí)行順序
1文搂、全局變量初始化最先執(zhí)行
2、init 函數 : 在 main 之前執(zhí)行秤朗, 一般用于完成對只聲明未初始化的變量的初始化工作煤蹭。init 函數在引包的時候就已經被執(zhí)行了了,即 被引入文件的 init 先于 引入文件的 init 函數取视。
3硝皂、main 函數 : 程序主入口
匿名函數
p1 := 100
p2 := 20
// 匿名函數
myFun1 := func(a int, b int) int{
return a * b
} (p1, p2)// 調用位置
fmt.Println(myFun1)
myFun2 := func(a int, b int) int{
return a / b
}
fmt.Println(myFun2(p1, p2))
注 : myFun1 、 myFun2 是函數類型變量
全局匿名函數
var (
// 全局匿名函數
Sum = func (a int,b int) int {
return a + b
}
)
注 : 匿名函數結尾括號 相當于調用這個函數的傳參
作谭。稽物。。
// 2.結果管道
resultChan := make(chan *Result, 128)
// 3.創(chuàng)建工作池
createPool(64, jobChan, resultChan)
// 4.開個打印的協程
go func(resultChan chan *Result) {
// 遍歷結果管道打印
for result := range resultChan {
fmt.Printf("job id:%v randnum:%v result:%d\n", result.job.Id,
result.job.RandNum, result.sum)
}
}(resultChan)
折欠。贝或。。
閉包
閉包不太好理解锐秦,函數式編程中咪奖,如果一個函數引用了外部變量,那么他們構成了一個整體农猬。如果,操作閉包對象的方法改變閉包對象關聯屬性售淡,相當于操作了同一個對象斤葱。
// 累加器
func AddUpper() func(int) int{
// 下面的代碼相當于構成了 java 中的一個 class
var base = 5
return func(x int) int {
base += x // 因為函數引用了函數外的變量 , 跟 base 一起形成了閉包
return base
}
}
// 調用
// 閉包
add := AddUpper() // 函數調用揖闸,此時 add 為累加器的返回函數 (如果不帶形參列表揍堕,則函數主題作為變量,)
fmt.Println(add(1))// 6
fmt.Println(add(1))// 7 由于閉包所以構成了一個整體汤纸,相當于對于同一個對象的方法進行二次調用
fmt.Println(add(1))// 8 閉包的概念相當于 java 中的 class 衩茸,構成了一個實例對象, 此時的 add 相當于對于累加方法的三次調用
fmt.Println()
add2 := AddUpper()// 相當于 java 中 new 了一個新對象
fmt.Println(add2(1))// 6
閉包的好處 : 保存以前使用過的變量
defer
遇到defer 語句先不執(zhí)行 贮泞, defer 存在于單獨的 defer 棧中 楞慈。
函數執(zhí)行完畢后將 defer 語句出棧幔烛。
用于函數的收尾工作, 例如函數結束時需要釋放連接囊蓝。
func testDefer(p1 int, p2 int) int {
defer fmt.Println("defer1 p1: ", p1)// step 3
defer fmt.Println("defer2 p2: ", p2)// step 2
sum := p1 + p2
fmt.Println("sum :", sum) // step 1
return sum
}
....
// 調用
fmt.Println("call test defer : ",testDefer(14, 27)) // step 4
....
注 :
如果 defer 語句涉及到 變量值的話饿悬, 入 defer 棧的時也將“變量當時的值”拷貝一份入棧。
最終 defer 語句出棧時聚霜,還原的是defer 語句入棧時的變量值狡恬。
變量作用域補充
1、只要在函數外面定義的變量就是全局變量蝎宇, 作用域整個包有效
2弟劲、如果全局變量首字母大寫, 那么該變量整個程序有效 (其他包通過包名調用)
常用內置函數 (轉)
常用內置函數
close: 用于發(fā)送方關閉chan姥芥,僅適用于雙向或發(fā)送通道兔乞。
len、cap: 用于獲取數組撇眯、Slice报嵌、map、string熊榛、chan類型數據的長度或數量锚国,len返回長度、cap返回容量玄坦;
new血筑、make: new用于值類型、用戶定義類型的內存分配煎楣,new(T)將分配T類型零值返回其指向T類型的指針豺总;make用于引用類型(Slice、map择懂、chan)內存分配返回初始化的值喻喳,不同類型使用有所區(qū)別。
make(chan int) 創(chuàng)建無緩沖區(qū)的通道
make(chan int,10) 創(chuàng)建緩沖區(qū)為10的通道
make([]int,1,5) 創(chuàng)建長度為1困曙,容量為5的slice
make([]int,5) 創(chuàng)建容量長度都為5的slice
make(map[int] int) 創(chuàng)建不指定容量的map
make(map[int] int,2) 創(chuàng)建容量為2的map
copy表伦、apped: 用于復制slice與為slice追加元素;
print慷丽、println: 用于打印輸出蹦哼;
panic、recover: 用于錯誤處理要糊;
delete: 用于刪除map中的指定key
代碼
package main
import "fmt"
var (
// 全局匿名函數
Sum = func (a int,b int) int {
return a + b
}
)
func main() {
p1 := 100
p2 := 20
// 匿名函數
myFun1 := func(a int, b int) int{
return a * b
} (p1, p2)// 調用位置
fmt.Println("乘 : " , myFun1)
myFun2 := func(a int, b int) int{
return a / b
}
fmt.Println("除 :" , myFun2(p1, p2))
// 調用全局匿名函數
fmt.Println("加 :" , Sum(p1, p2))
// 閉包
add := AddUpper() // 函數調用纲熏,此時 add 為累加器的返回函數 (如果不帶形參列表,則函數主題作為變量,)
fmt.Println(add(1))// 6
fmt.Println(add(1))// 7 由于閉包所以構成了一個整體局劲,相當于對于同一個對象的方法進行二次調用
fmt.Println(add(1))// 8 閉包的概念相當于 java 中的 class 勺拣,構成了一個實例對象, 此時的 add 相當于對于累加方法的三次調用
fmt.Println()
add2 := AddUpper()// 相當于 java 中 new 了一個新對象
fmt.Println(add2(1))// 6
// defer 存在于單獨的 defer 棧中 , 函數執(zhí)行完畢后將 defer 語句出棧容握, 用于函數的收尾工作
fmt.Println("call test defer : ",testDefer(14, 27)) // step 4
}
// 累加器
func AddUpper() func(int) int{
// 下面的代碼相當于構成了 java 中的一個 class
var base = 5
return func(x int) int {
base += x // 因為函數引用了函數外的變量 宣脉, 跟 base 一起形成了閉包
return base
}
}
func testDefer(p1 int, p2 int) int {
defer fmt.Println("defer1 p1: ", p1)// step 3
defer fmt.Println("defer2 p2: ", p2)// step 2
sum := p1 + p2
fmt.Println("sum :", sum) // step 1
return sum
}
七、 錯誤
golang 中錯誤處理比較繁瑣剔氏,沒有 try catch 和 throw 塑猖。 經常需要逐層判斷 err 是否為 nil 特別啰嗦,并且需要顯示傳遞 err 谈跛。
一般成熟的工程對 err 都有統一的封裝方式羊苟、代碼風格和 recover 機制,這里不贅述 感憾。
panic :異常蜡励, 程序直接會掛掉,需代碼 recover阻桅。
error :錯誤凉倚, 不會影響程序的運行。release 程序需要有將 panic 轉成 err 的機制嫂沉。
區(qū)別 java 名稱稽寒, java 異常 exception 是可以 try catch 的;錯誤 error 一般都不可恢復 (死鎖趟章、爆棧)杏糙。
注意 : golang 中的 error 一般放在返回值的最后一個參數。
八蚓土、容器
數組
這數組的定義可真奇怪宏侍。
var name [len] type
名稱 長度 類型
注 :形參是 parameter,實參是 argument
go 中蜀漆, 數組在參數傳遞中是 形參傳遞谅河, 函數運行時會復制數組的值, 值傳遞确丢。
(區(qū)別 java1了!)
func main() {
// 隨機生成三個數 ,放入數組蠕嫁, 并反轉打印
var arr [3] int
rand.Seed(time.Now().UnixNano()) // 當前納秒值作為隨機數種子
for i := 0; i < len(arr); i++ {
arr[i] = rand.Intn(50) // n 為 [ 0, n) 隨機范圍
}
fmt.Println(arr)
// 換位
for i := 0; i < len(arr)>>1; i++ {
swap(&arr, i, len(arr) - 1 - i)
}
fmt.Println(arr)
}
func swap(arr *[3]int, a int, b int) {
t := (*arr)[a] // 指針取用數組實際值
(*arr)[a] = (*arr)[b]
(*arr)[b] = t
}
注意 :
[]int 與 [n]int 是不同類型锨天,不限制數組長度則為切片類型毯盈!
切片 slice
切片是數組的一個引用剃毒, 遵循引用傳遞。使用方式與數組一致, 但是長度是可變的赘阀,可以簡單理解為一個動態(tài)數組益缠。
1、數組得到切片
myslice := arr2[1:4]
fmt.Println("元素 = ", myslice)
fmt.Println("容量 = ", cap(myslice))
注意 : 算頭不算尾 基公。 arr[:]
從頭切到尾幅慌。
2、切片的本質
一個結構體(stuct)轰豆, 包含三個部分 : 指向被引用數組第一個元素地址胰伍, 切片長度, 容量酸休。指向被切位置的同一個地址骂租,而不是拷貝一份。slice 本身還有一個地址斑司,指向上面的結構體渗饮。
注: 切片的容量 cap
切片的容量是可變一般是元素個數的兩倍
3、make 創(chuàng)建切片
var myslice2 []int = make([]int, 4, 5)
myslice2[1] = 123
//myslice2[4] = 46 // 越界宿刮! cap 有啥用呢 互站?
fmt.Println(myslice2)
注 : 程序員不可見,但 make 底層會創(chuàng)建一個數組僵缺, 由 slice 維護 創(chuàng)建引用胡桃。
4、創(chuàng)建時賦值
var myslice3 []int = []int{6,7,8}
fmt.Println(myslice3)
5谤饭、 切片還可以再次切片标捺!
6、擴容函數 append
var myslice2 []int = make([]int, 4, 5)
myslice2[1] = 123
//myslice2[4] = 46 // 越界揉抵! cap 有啥用呢 亡容?
myslice2 = append(myslice2, 46) //超越長度 [0 123 0 0 46]
myslice2 = append(myslice2, 47) //超越長度 [0 123 0 0 46]
fmt.Println(cap(myslice2))// 容量;10 追加超越長度后冤今,容量自動擴容 2 倍
myslice2[4] = 43 // 擴容后切片維護的數組長度發(fā)生變化闺兢,可以進行正常賦值
fmt.Println(myslice2) // [0 123 0 0 43 47]
注: 切片也可以追加切片。
數組都是底層維護戏罢,程序員不可見屋谭。
7、 切片的拷貝問題copy
// 切片的拷貝問題
var myslice4 = make([]int, 2, 2)
copy(myslice4, myslice)
fmt.Println(myslice4) // 由多到少的拷貝會被截斷
8龟糕、string 與切片
string 底層是 byte[] 也可以進行 切片操作桐磁。
map 集合
1、簡介
kv 數據結構 讲岁, 通常 key 是 string 或 int我擂。
var mapName map[k-type]v-type
注 : map 聲明是不會分配內存的衬以,需要 make 分配內存! (數組聲明完分配內存)
var myMap map[string]string
myMap = make(map[string]string, 5)
myMap["姓名"] = "alex"
myMap["年齡"] = "26"
fmt.Println(myMap)
注意 : golang 中的 key 是完全無序的校摩!
2看峻、 重置 map
golang 沒有清空 map 的接口 , 用 myMap = make(map[string]string)// 給一個空 map衙吩,原來的內存變?yōu)槔?gc
清空 map互妓。
判斷值是否存在 :
// 判斷值是否存在
val,ok := myMap["姓名"]
fmt.Println(ok , val)
3、遍歷
只能用 for-range
//遍歷
for key,val := range myMap {
fmt.Println(key, "::", val)
}
4坤塞、map 切片
指的是 slice 的數據類型是 map 冯勉。
5、 排序 map
將 key 放入切片 的摹芙, 對切片排序 (利用 sort 包
) 珠闰。
代碼
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
// 隨機生成三個數 ,放入數組瘫辩, 并反轉打印
var arr [3] int
var arr2 = [...]int{1,2,3,4,5} // ... 代替數組的長度
// for range 遍歷
for _,value := range arr2{
fmt.Println(value)
}
rand.Seed(time.Now().UnixNano()) // 當前納秒值作為隨機數種子
for i := 0; i < len(arr); i++ {
arr[i] = rand.Intn(50) // n 為 [ 0, n) 隨機范圍
}
fmt.Println(arr)
// 換位 與 遍歷
for i := 0; i < len(arr)>>1; i++ {
swap(&arr, i, len(arr) - 1 - i)
}
fmt.Println(arr)
// 切片 : 動態(tài)數組
// 定義與初始化
myslice := arr2[1:4]
fmt.Println("元素 = ", myslice)
fmt.Println("容量 = ", cap(myslice))
var myslice2 []int = make([]int, 4, 5)
myslice2[1] = 123
//myslice2[4] = 46 // 越界伏嗜! cap 有啥用呢 ?
myslice2 = append(myslice2, 46) //超越長度 [0 123 0 0 46]
myslice2 = append(myslice2, 47) //超越長度 [0 123 0 0 46]
fmt.Println(cap(myslice2))// 容量伐厌;10 追加超越長度后承绸,容量自動擴容 2 倍
myslice2[4] = 43 // 擴容后切片維護的數組長度發(fā)生變化,可以進行正常賦值
fmt.Println(myslice2) // [0 123 0 0 43 47]
var myslice3 []int = []int{6,7,8}
fmt.Println(myslice3)
// 切片的拷貝問題
var myslice4 = make([]int, 2, 2)
copy(myslice4, myslice)
fmt.Println(myslice4) // 由多到少的拷貝會被截斷
// map 集合
var myMap map[string]string
myMap = make(map[string]string, 5)
myMap["姓名"] = "alex"
myMap["年齡"] = "26"
fmt.Println(myMap)
// curd
delete(myMap, "年齡")
fmt.Println(myMap)
// myMap = make(map[string]string)// 給一個空 map挣轨,原來的內存變?yōu)槔?gc
// 判斷值是否存在
val,ok := myMap["姓名"]
fmt.Println(ok , val)
myMap["年齡"] = "26"
myMap["身高"] = "180"
//遍歷
for key,val := range myMap {
fmt.Println(key, "::", val)
}
}
func swap(arr *[3]int, a int, b int) {
t := (*arr)[a] // 指針取用數組實際值
(*arr)[a] = (*arr)[b]
(*arr)[b] = t
}
go 中對面向對象編程 與 傳統面向對象編程有很大區(qū)別军熏, 因為沒有類的概念, 需要使用結構體代替卷扮。
注 : 面向對象是一種思想荡澎, go 實現了面向對象功能。
九晤锹、 結構體 struct
1摩幔、 定義
type name struct {
fieldName type
....
}
注 : 結構體在 go 中是值傳遞! name 直接指向內存空間鞭铆。如果field 中有 slice 或 map 需要先 make 再使用結構體或衡。
type Mystruct struct {
name string
mySlice []float64
ptr *int
myMap map[string]string
} // 未分配空間的引用類型是 nil , 無法賦值
...
var v1 MyStruct
v1.mySlice = make([]float64, 5)
v1.mySlice[0] = 5.5
2车遂、 結構體是值類型封断,這里與 java 不同!
如果 struct1 := struct2 舶担, 那么stuct1 和 struct2 不指向同一地址坡疼。(其實是一個copy操作) 如果想像 java 一樣對對象進行引用傳遞, 需要借助指針衣陶。
var p *Person = &Person{XX,"XX"} // 創(chuàng)建時為結構體賦初始值
(*p).XXX = "XX" // 標注使用方式柄瑰,等效于直接 點屬性
3废岂、結構體屬性的地址是連續(xù)的
4、tag
給結構體的字段 加一個 tag 狱意, 序列化 json 的時候,給字段別名拯欧。
type Mystruct struct {
Name string `json:"name"`
}
十详囤、 方法
自定義類型都有方法,方法與類型綁定镐作。 (type 創(chuàng)建的自定義類型藏姐,不只局限于結構體)
1、方法與函數的區(qū)別
函數沒有綁定數據類型该贾, 方法需要指定綁定的數據類型 羔杨。
type Person struct {
Name string
}
func (p Person) hello() {
fmt.Printf("my name is %s", p.Name)
}
func main() {
var man *Person = &Person{"Alex"}
man.hello()
}
注意 :
go 中自定義類型很奇怪, 可以對基本類型進行自定義杨蛋。
誰調用方法 兜材,調用者被當作實參傳遞給方法。
2逞力、String() 方法實現
如果一個類型曙寡,實現了 String() 方法, fmt.Println 會調用 String() 寇荧,類比 java 的
注 : 結構體創(chuàng)建方式要改變
// 重寫了 Person 的 String
var man *Person = &Person{"Alex"}
man.hello()
fmt.Println(man)
fmt.Println(&man) // man 是指針變量賦初始值举庶, & 還顯示地址
var woman Person
woman.Name = "fei"
fmt.Println(woman)
fmt.Println(&woman) // woman 的 & 顯示的事 String() ?揩抡?户侥??
十一峦嗤、面向對象
嵌套結構體(繼承)
繼承的目的是為了歸類和基本的代碼復用蕊唐, 雖然說 java 的繼承在維護復雜度較高的工程時,讓人頭大烁设,但是有總比沒有好刃泌。
golang 通過組合實現繼承,其實就是子類的屬性包含父類的引用或者父類 struct署尤。(這里的父類和子類就是被繼承者和繼承者)
1 耙替、不管首字母大小寫, 嵌套結構體可以使用繼承的全部方法和屬性
type blackMan struct {
Person // 繼承
skill string
}
2曹体、就近原則俗扇,先找自身方法和屬性,(實現繼承的 overwrite)
3箕别、多繼承铜幽,(多嵌套幾個匿名結構體)如果發(fā)生重名問題滞谢,調用需要指定匿名結構體名稱
接口 interface (多態(tài))
golang 的核心就是面向接口編程。 我理解 golang 的設計思想除抛,一種方式只有一種結果狮杨,本身是與多態(tài)相違背的。如果想要使用多態(tài)特性要依賴接口實現到忽¢辖蹋空接口 可以表示任意類型,go 中可以傳空接口喘漏。但是空接口 interface 的傳遞會引發(fā)一些列調試的問題护蝶, 以及難以預料的 panic,這些都需要在代碼編寫的時候做考慮 翩迈。
注 :
golang 的空接口不存值持灰。 golang 中空接口的傳遞,其實發(fā)生了類型轉換负饲。多態(tài)即編譯時不能確定接口類型堤魁,運行時決定調用哪個實現方法。
類型斷言 :
v := i.(T) // 會panic
// 這種方式不會引發(fā) panic
v,ok := i.(T)
“鴨子模式” :
“一個東西看來長得像鴨子返十, 叫聲像鴨子姨涡, 走路像鴨子,我們就認為它是鴨子吧慢√纹”
golang 的接口實現不像 java 一樣需要顯式實現, 認為一個struct 實現了 interface 的所有方法就證明實現了這個接口检诗。因為這種松散耦合會讓 struct 多實現接口匈仗,導致代碼閱讀不流暢。 需要 coder 對代碼進行有效的目錄組織