Go語言基礎
GO開發
-
Golang被譽為21世紀的C語言
- 2012.3 – 2020.2 1.0 – 1.14版本
-
為什麼選擇GO
- 繼承python的簡潔 & C語言的性能於一身
-
環境搭建
-
執行golang程式碼。
- go run **.go
-
或者go文件中 go build 會生成一個文件 在執行可執行文件
- 再或者 go install。會將可執行文件放到bin目錄
-
創建目錄
目錄結構如下:
xxx
- bin
- pkg
- src //用於存放項目程式碼的目錄
- 環境變數
GOROOT, GO編譯器安裝目錄
GOPATH, 用於存放項目程式碼, 編譯後的可執行文件, 編譯後的包文件(go 1.11版本後 ==> go.mod).
GOBIN, 編譯後的可執行的文件存放的目錄
-
開發工具
- goland ide
- vscode 編輯器
-
配置
- 字體
- 參數提示
-
項目開發
-
新項目
-
打開已經存在的項目
注意: 項目放在$GOPATH/src目錄。
-
GO語法的使用
-
Go包管理初識 : 知道項目中文件和文件 文件和文件夾之間的關係
-
輸出 , 寫程式碼 在go編譯器運行時會在螢幕顯示內容
-
Go的數據類型
- 整型
- 字元串
- 布爾類型
-
變數 & 常量。當作是昵稱 別名
-
輸入
-
條件語句 if else elif
1. GO包管理
- 一個文件夾可以稱為一個包
- 在文件夾(包)中可以創建多個文件
- 在同一個包下的每個文件中必須指定包名稱 且必須相同
重點:
- main包。如果是main包 必須寫一個main函數 此函數就是項目的入口(main) 編譯生成可生成可以執行進位文件
- 非main包
2. 輸出
在終端將想要展示的數據顯示出來, 例如歡迎登錄, 請輸入用戶名等
- 內置函數
- println
- fmt包(推薦使用)
- fmt.print
- fmt.println
擴展: 進程中有 stdin/stdout/stderr
fmt格式化輸出
fmt.printf()
佔位符
%s 字元串
%d 整型
%f 小數
%.2f 保留兩位小數
注釋: 單行注釋 // 多行注釋 /**/
3. 數據類型
- 整型
- 字元串
- 布爾型 false true
4. 變數var
就是給各種類型的數據起別名 為了方便引用。還可以暫時存儲數據
-
變數名必須只包含:字母、數字、下劃線
-
變數不能以數字開頭
-
不能使用golang中的內置變數 關鍵字
5. 輸入
fmt.scanf()
6. 變數簡寫
var name string = "ha"
var name = "ha"
name := "ha"
因式分解
var(
name = "zj"
age = 18
hobby = "sing"
)
go編譯器會認為如果聲明或者聲明且賦值的變數沒有進行引用, 就要被刪除掉
7. 變數作用域 (namespace名稱空間)
如果我們定義了大括弧, 那麼在大括弧中定義的變數
- 不能被上級使用
- 可以在同級中引用使用
- 子級可以引用上級的變數
- 函數內的方法都是局部變數
- 定義全局變數不能使用 := 簡寫
全局變數和局部變數(都可以進行因式分解):
- 全局變數: 在函數外的定義的非簡寫變數稱為全局變數
- 局部變數: 在{}內定義的變數為局部變數
8. 變數的賦值及記憶體相關
name := "zj"
fmt.println(name, &name) //列印變數內容及記憶體地址
9. 常量const
不可修改的變數
// 常量 - 不可修改的變數
const age = 12
//age = 13
fmt.Println(age)
const (
va = 123
vb = "111"
)
10. iota
可有可無的東西 可以理解為計數器
// iota 計數累加器 從0開始
const (
v1 = iota
v2
v3
v4
)
fmt.Println(v1, v2, v3, v4)
11. 輸入
// 讓用戶輸入數據, 完成數據交互
fmt.scanf
fmt.scan
fmt.scanln
Scan
var name string
var age int
fmt.Println("請輸入用戶名,姓名:")
_, error := fmt.Scan(&name, &age) // _是count輸入的總值 , error 是報錯nil報錯是輸入爭取 如果不是就是報錯的
//fmt.Println(name, age)
//fmt.Println(error)
if error == nil {
fmt.Println(name, age)
} else {
fmt.Println("輸入值錯誤", error)
}
- 常用的為scanln
- 但是scanln的問題在於如果輸入的變數存在空格 默認取空格之前的問題 所以我們要使用os.stdin標準輸入
// os.stdin標準輸入
fmt.Println("請隨便輸入點東西:")
reader := bufio.NewReader(os.Stdin)
// line 從stdin中讀取一行的數據 數據類型為byte 可以轉換為字元串
// reader默認一次只能讀取4096個位元組 如果讀取的位元組數小於4096 isprefix=false
line, _, _ := reader.ReadLine()
// 通過string可以將byte類型的數據換換成字元串 類似於python的decode
fmt.Println(string(line))
12. 條件語句
-
最基本的條件語句
if 條件{ 條件成立執行語句 } else { 條件不成立執行語句 } // 例子 var ( username string passwd string ) fmt.Print("username:") fmt.Scanln(&username) fmt.Print("password:") fmt.Scanln(&passwd) if username == "zj" && passwd == "123456"{ fmt.Printf("用戶名%s登錄成功", username) }else { fmt.Println("用戶名或密碼錯誤") }
-
多條件判斷
if {
} else if {
} else {
}
- 嵌套
if {
if {
} else {
}
} else {
}
-
Switch case 語句 類似於shell中的switch case 語句
-
for循環語句
-
goto 語法 , 不建議使用
-
字元串的格式化 ,
-
運算符的優先順序
1.switch case 語句
package main
import "fmt"
func main() {
var number int
_, err := fmt.Scanln(&number)
if err == nil {
switch number {
case 1 :
fmt.Println("1111")
case 2:
fmt.Println("2222")
case 3:
fmt.Println("3333")
default:
fmt.Println("無法識別你輸入的內容")
}
}
}
switch 數據類型必須一致
2.for 循環
//1.死循環 for 或者 for true
//fmt.Println("開始")
//for true {
// fmt.Println("123")
// time.Sleep(time.Second * 2) // 一秒為單位 time.second
// break
//}
//fmt.Println("end")
number := 1
for number < 5 {
fmt.Println(number)
number += 1
}
fmt.Println("end")
// for 循環條件判斷
for i:=1; i<10{
}
// 循環10次 進行++
for i:=1; i<10; i++{
fmt.Println(i)
}
count := 0
for i:=1; i<100; i++{
fmt.Println(i)
count += i
}
print(count)
- continue
// 退出本次循環
for i := 1; i <= 10; i++ {
if i == 7 {
continue
}
fmt.Println(i)
}
- break
//跳出整個循環
for i := 1; i < 5; i++ {
if i == 4 {
fmt.Println("bye")
break
}
fmt.Println(i)
}
*對 for 進行打標籤, 然後可以通過break和continue就可以時間多層循環的跳出和中止
test1:
for i := 1; i < 5; i++ {
for j := 1; j < 3; j++ {
if j == 2 {
//continue test1
break test1
}
fmt.Println(i, j)
}
}
goto 語句
//跳躍到指定的行向下執行程式碼
package main
import "fmt"
func main() {
shibai:
var name string
fmt.Print("請輸入用戶名:")
_, err := fmt.Scanln(&name)
fmt.Println(name)
if len(name) < 1 {
goto shibai
}
if err == nil {
if name == "zj" {
fmt.Println("牛皮")
} else {
fmt.Println("弟弟")
goto shibai
}
}
}
- 字元串格式化
// 格式化字元串
package main
import "fmt"
func main() {
var name string
var age int
var score float64
fmt.Println("please input name")
fmt.Scanln(&name)
fmt.Println("please input name")
fmt.Scanln(&age)
fmt.Println("please input name")
fmt.Scanln(&score)
result := fmt.Sprintf("name is %s, age is %d, score is %.2f", name, age, score)
fmt.Println(result)
}
必備基礎知識
- 進位
- 單位
- 字元編碼
golang 數據類型
1.整型
var v1 int8 = 10
var v2 int16 = 20
v3 := int16(v1) + v2
注意:
- 低位轉高位是沒有問題
- 但是高位轉成低位 是很有可能出問題的
整型轉換成字元串類型:Itoa
v1 := 19
result := strconv.Itoa(v1)
fmt.Println(result, reflect.TypeOf(result))
2.字元串轉換成整形Atoi
v2 := "19"
res, err := strconv.Atoi(v2)
if err == nil {
fmt.Println(res)
} else {
fmt.Println("轉換失敗請檢查原數據")
}
- 進位轉換
go:
- 10進位是以整型的方式存在
- 其他進位 都是以字元串形式存在的
- 整型 : 10進位數 轉換成其他進位 (FormatInt)
// 使用方法進行進位轉換
v1 := 9
// 通過strconv.FormatInt方法進行進位數轉換 int類型必須是int64 所以必須進行聲明 第二個參數代表的是轉換成幾進位
// 10進位轉換成其他進位
v2 := strconv.FormatInt(int64(v1), 8)
fmt.Println(v2, reflect.TypeOf(v2))
- 其他進位轉換成10進位parseint
// 其他進位轉換成整形=> 10進位
// parseint(其他進位數據, 原本的進位類型, 要轉換成的進位類型 )
// 只要轉換成功 轉化出來的數據類型就是int64類型
data := "10001000"
res, err := strconv.ParseInt(data, 2, 10)
//fmt.Println(err)
if err == nil {
fmt.Println(res, reflect.TypeOf(res))
}
- 自己出的小練習
將2進位的10001000轉換成8進位數
思路:因為2進位和8進位程式碼層面不能相互轉換 所以我們先將2進位轉換成10進位 在將10進位裝換成8進行
data := "10001000"
res, err := strconv.ParseInt(data, 2, 10)
//fmt.Println(err)
if err == nil {
fmt.Println(res, reflect.TypeOf(res))
test := strconv.FormatInt(int64(res), 8)
fmt.Println(test)
}

常見的數據運算
math 函數
math abs
math.Pow(2 ,5) // 2的五次方
math.max(1, 2) // 兩個值相比較 取大值
math.min(1, 2) // 兩個值相比較 取小值
指針/nil/聲明變數/ new
- 聲明變數
### 指針/nil/聲明變數/new
* 聲明變數
var v1 int
v2 := 999
* 指針 (作用就是節省記憶體)
var v3 *int
v4 := new(int)
nil
v3 -> ^
0x0
0
v4 -> ^
0x0
* new 關鍵字
* new用於創建記憶體並進行內部數據的初始化。並返回一個指針類型
* nil
* nil指go語言中的空值
* var v6 *int
* var v7 * int32
* 超大整型
var v1 big.int
var v2 *big.int
var new(big.int)
v1.setint64(1000)
var v1 big.Int
v1.SetString("123456767898786898092355262738453726315", 2)
fmt.Println(v1.String(), reflect.TypeOf(v1.String()))
go Array數組
數組是固定長度的特定類型元素組成的序列
一個數字由零個或多個元素組成
數組的長度是固定的,因此Go更常用slice(切片, 動態的增長或收縮序列)
數組是值類型, 用索引下標訪問每一個元素, 範圍是0 - len-1, 訪問超出數組長度範圍 會panic異常
// Go Array 數組中沒有複製的數組 會有相應的默認值
// 聲明數組 , 並且個數組中的元素賦值
var intArr [5]int
fmt.Println(intArr)
intArr[0] = 12
intArr[1] = 34
fmt.Println(intArr)
// 聲明數組 並且直接賦值
var namestr [5]string = [5]string{"1", "2"}
fmt.Println(namestr)
var namestr2 = [5]string{"1"}
fmt.Println(namestr2)
// 取數組最後一個元素 順便展示指定索引賦值
var namestr2 = [5]string{"1", 4: "124124"}
fmt.Println(namestr2, namestr2[len(namestr2)-1])
// 自適應數組大小[...]
var namestr3 = [...]string{"zj", "zjj", "zjjj"}
fmt.Println(namestr3)
// 數據結構題類型數組
var namestr5 = [...]struct{
name string
age int
}{
{"zj", 18},
{"ccn", 18},
}
fmt.Println(namestr5)
// 數組循環
for i:=0; i < len(namestr3);i++{
fmt.Println("for " + namestr3[i])
}
for index, value := range namestr3{
fmt.Println(index, value)
}
** 數組注意事項:
數組是多個相同數據的組合, 且長度固定, 無法擴容 [5]int
數組使用步驟:
1.聲明數組
2.給數組元素賦值
3.使用數組
4.數組索引從0開始 不能index of range
5.Go數組是值類型, 變數傳遞默認是值傳遞, 因此會進行值拷貝
6.修改原本的數組, 可以使用引用傳遞(指針)
- 字元串的本質
電腦中所有的操作和數據最終都是二進位 : 010101010…
package main
import (
"fmt"
"strconv"
"unicode/utf8"
)
func main() {
var name string = "張無忌"
fmt.Println(name[1], strconv.FormatInt(int64(name[1]), 2))
fmt.Println(len(name))
age := 123
res := strconv.Itoa(age)
// len位元組長度
fmt.Println(len(res + ""))
// 字元串轉換為一個 "位元組集合"
byteset := []byte(name)
fmt.Println(byteset) // [229 188 160 230 151 160 229 191 140]
// 位元組集合轉換成字元串string
fmt.Println(string(byteset))
// rune集合
testName := "11223ddd"
runeset := []rune(testName)
fmt.Println(runeset,)
fmt.Println(string(runeset[:5]))
// 字元串長度獲取
// len方法獲取的是位元組的長度
test2Name := "122333zghang張鑒"
runeset2 := []rune(test2Name)
fmt.Println(len(runeset2))
// or 使用utf8的方法獲取長度
test2Namelength := utf8.RuneCountInString(test2Name)
fmt.Println(test2Namelength)
}
字元串常見的使用方法
// 獲取長度
var name string = "張無極"
fmt.Println(name)
fmt.Println(utf8.RuneCountInString(name))
// 是否以xx開頭
name2 := "張無忌"
reslut := strings.HasPrefix(name2, "張")
fmt.Println(reslut)
// 是否以xx結尾
result := strings.HasSuffix(name2, "無忌")
fmt.Println(result)
// 是否包含 類似 python in
result2 := strings.Contains(name2, "無")
fmt.Println(result2)
fmt.Println("===============")
// 全變大寫 類似 python upper
stringTest1 := "fff afa fUUUU 漲"
fmt.Println(strings.ToUpper(stringTest1))
// 全變小寫
fmt.Println(strings.ToLower(stringTest1))
// 替換所有是-1 是左到右第一個 是1
fmt.Println(strings.Replace(stringTest1, "f", "", 1))
fmt.Println(strings.Replace(stringTest1, "f", "", 2))
fmt.Println(strings.Replace(stringTest1, "f", "", -1))
// 分割 split
splitTest := strings.Split(stringTest1, " ")
fmt.Println(splitTest[len(splitTest)-1])
// 拼接
mes := "你好" + "我好" // 不建議使用
fmt.Println(utf8.RuneCountInString(mes))
// 高效率的字元串拼接方法 非常推薦使用
var builder strings.Builder
builder.WriteString("我愛你")
builder.WriteString("中國")
value := builder.String()
fmt.Println(value, utf8.RuneCountInString(value))
// 字元串轉換成int
var num int = 12
fmt.Println(strconv.Itoa(num), reflect.TypeOf(strconv.Itoa(num)))
textStr := "2141414141414"
fmt.Println(strconv.Atoi(textStr))
textStr2 := "0101010010"
fmt.Println(strconv.ParseInt(textStr2, 2 ,10))
fmt.Println(strconv.FormatInt(int64(num), 16))
// 字元串 轉換
v1 := string(100)
fmt.Println(v1)
v2, size := utf8.DecodeRuneInString("d")
fmt.Println(v2, size)
- 索引切片和循環
package main
import (
"fmt"
)
func main() {
var name3 string = "你好你好"
// 位元組索引 切片
vs1 := name3[0:3]
fmt.Println(vs1)
// 手動循環所有的字元 range
for index, item := range name3 {
if index == 6 || index == 3 {
fmt.Println(index, string(item))
}
//fmt.Println(index, string(item))
}
// 轉換成rune集合
dataList := []rune(name3)
fmt.Println(dataList, string(dataList))
}
- 數組
數組, 定長且元素類型一致的數據集合 (類型必須相同)
// 聲明加賦值自適應變數
var numbers = [...]int{1, 2, 3}
numbers[0] = 2
fmt.Println(numbers)
// 聲明定長的數組 不足的用空格代替 len為5
var numbers = [5]string{"test", "alex", "didi"}
fmt.Println(numbers)
// 指定索引賦值
var namestr2 = [5]string{"1", 4: "124124"}
fmt.Println(namestr2, namestr2[len(namestr2)-1])
// 聲明指針類型的數組 (指針類型, 不會開闢記憶體初始化數組中的值)
var numbers *[]int
數組的特性(可變和拷貝)
- 元素值是可以被修改的 但是數組一旦聲明開闢了記憶體空間之後 數組的大小就不可以改變
- 可以進行copy
// 修改重新賦值
var numbers = [...]int{1, 2, 3}
numbers[0] = 2
fmt.Println(numbers)
- 長度切片[0:2]
- 數組的嵌套
二維數組
// 含義為 數組中有3個元素 每個元素是一個含有2個int元素的數組
var nestData [3][2]int
var nestData = [3][2]int{{1, 2}, {3, 4}}
nestData[2] = [2]int{11, 22}
nestData[2][1] = 6666
fmt.Println(nestData)
最後三種數據結構概覽
- 切片
- 字典
- 指針
#切片
切片, 動態數組
切片是Go中重要的數據類型,每個切片對象內部都維護著 : 數組指針,切片長度,切片容量 三個數據
- 創建切片
// 每一個切片內部都存儲了三個數據
數組指針*array 、 切片長度len 切片容量[:3]
再向切片中追加數據個數大於容量時, 內部會自動擴容且每次擴容都是當前容量的2倍 [:3] -> [:6]
// 創建切片
var nums []int
// 創建切片 並賦值
var nums = []int{1,2,3}
// 創建切片
// make只用於 切片、字典、channel
// 推薦使用
int 類型 默認長度為2 容量為3 cap()
var users = make([]int,2,3)
- 自動擴容
v1 := make([]int,1,3)
v2 := append(v1, 123)
注意: 擴容前和擴容後的切片記憶體地址 是不同的
- 切片追加高級用法
// 基本相當於python中的extend 擴展列表
arrayList = append(arrayList, []int{100,200,300,400}...)
fmt.Println(arrayList)
- 切片刪除
// 切片數據實際上是沒有刪除操作的 我們可以使用append來拼接生成新的切片 代替刪除
newAarrayList := append(arrayList[:6])
newAarrayList = append(newAarrayList, arrayList[7:]...)
fmt.Println(newAarrayList)
// or
newAarrayList := append(arrayList[:6], arrayList[7:]...)
fmt.Println(newAarrayList)
- 切片插入
newAarrayList := append(arrayList[:6])
newAarratList = append(arrayList, 123356)
newAarrayList = append(newAarrayList, arrayList[7:]...)
fmt.Println(newAarrayList)
// 刪除插入 效率低下 不使用。應該使用鏈表
- 切片嵌套
// 嵌套
testList := [][]int{[]int{1, 2}, []int{66, 77, 99}}
fmt.Println(testList)
目前為止 上面所學的數據類型中, 在修改切片的內部元素時, 會造成所有的賦值的變數同時修改 (不擴容的前提下)
字典類型(Map)
- 任何程式語言中都會存在字典或者映射 , 同python中的字典 是以鍵值對的形式存在的數據集合
{
'name' : 'zj',
'age': 15,
}
- 字典的特性map
- 鍵不能重複
- 鍵必須可hash(切片和map都不可hash所以不能做鍵)
- 無序
- map聲明
// 第一個string代表鍵的類型 第二個string代表值的類型
userInfo := map[string]string{
"name": "zj",
"age": "18",
}
fmt.Println(userInfo["name"])
//or
// dataInfo := make(map[string]string, 10)
dataInfo := make(map[string]string)
dataInfo["name"] = "ccn"
fmt.Println(dataInfo["name"])
- map 長度和容量
len(userInfo)
dataInfo := make(map[string]string, 10)
// 根據穿參10 計算出合適的容量
// 一個map 中會包含很多筒,每個筒可以存放8個鍵值對
- map 增刪改查
userInfo := map[string]string{
"name": "zj",
"age": "18",
}
// 增
userInfo["test"] = "123ff"
// 改
userInfo["name"] = "ccc"
// 刪
delete(userInfo, "name")
// 查
for key, value := range userInfo {
fmt.Println(key, value)
}
fmt.Println(userInfo["name"])
- map變數賦值
…
注意 : 無論是否存在擴容都指向同一個地址
- map初始化詳解
info = make(map[int]int, 10)
- 第一步 創建一個hmap的結構體對象
- 第二部 生成一個hash因子 hash0 並賦值到hmap對象中(用於後續為key創建hash值)
- 第三部 根據hint=10, 並根據演算法來創建 2的B次方筒數,當前應該是1 所以就是創建兩筒
- map擴容原理
再向map中添加數據時, 當達到某一個條件, 則會引發字典擴容
擴容的條件:
- map中數據總個數/筒個數 > 6.5時, 便會引發翻倍擴容
指針數據
什麼是指針 一個指針變數指向了一個記憶體地址, 類似於變數和常量, 在使用指針前需要聲明指針。指針聲明格式如下
// 指針類型生來就是用來節省記憶體的
// 聲明指針類型的數據只需要在聲明數據類型前加* 即可聲明為指針類型
package main
import "fmt"
func main() {
//var a int = 10
//fmt.Printf("%x" , &a)
var name string = "yunZhOngKeXin"
// 查看記憶體地址為&符號
fmt.Printf("name的記憶體地址: %v\n", &name)
// 指針變數, 存的是記憶體地址
// ptr 指針變數指向變數name的記憶體地址
var ptr *string
ptr = &name
fmt.Printf("指針變數ptr的記憶體地址: %v \n", ptr)
// 獲取指針變數的值, 用*ptr
// *ptr表示讀取指針指向變數的值
// *ptr 代表真正的數據
fmt.Printf("指針變數ptr指向的值是: %v\n", *ptr)
}
- 指針
指針 , 是一種數據類型, 用於表示數據的記憶體地址。
// 聲明一個 字元串類型的變數 (默認初始化為空字元串)
var v1 string
// 聲明一個字元串的指針類型
var v2 *string
// 聲明一個字元串類型的變數並賦值
var name string = "zj"
// 聲明一個字元串的指針類型的變數, 值為name對應的記憶體地址
var pointer *string = &name
- 指針存在的意義
相當於創建了一個**引用**, 以後可以根據這個引用來獲取他裡面的值
如果原本的數據發生變化引用也會發生變化,類似於軟連接, 快捷方式這種
v1 := "zh"
v2 := &v1
fmt.println(v1, v2, *v2)
- 使用指針的場景
// 下邊的這種函數傳參的方式testData字元串會重新copy一份
func main {
Test("zjjjj")
}
func Test(testData string) {
testData = "hahah"
fmt.println(testData)
}
//
func main {
Test("zzjj")
}
func Test(ptr *string){
ptr = "fffff"
fmt.println(ptr) // 修改指針的內容上邊的內容也會被修改
}
- 指針的指針
n1 := "zhjjj"
n2 := &n1
fmt.Println(n1, *n2)
n3 := &n2
fmt.Println(*n2, **n3)
n4 := &n3
fmt.Println(**n3, ***n4)
- 指針的小高級操作
// 指針的相加操作
func Compl() {
// 指針的相加操作
// 定義一個int8數組
dataList := [3]int8{11, 22, 33}
// 取出第一個元素的記憶體地址
var firstData *int8 = &dataList[0]
// 將第一個鐵元素的記憶體轉化成pointer類型
ptr := unsafe.Pointer(firstData)
// pointer類型 +1
targetAddress := uintptr(ptr) + 1
// 相加之後重新轉換成pointer類型
newPtr := unsafe.Pointer(targetAddress)
// 將pointer對象轉換成int8 指針類型
value := (*int8)(newPtr)
// 使用指針類型獲取數據
fmt.Println(*value)
}
結構體
什麼是結構體?
| 結構體是一個複合類型, 用於表示一組數據
| 結構體由一系列屬性組成, 每個數據都有自己的類型和 值
// 定義
type Person struct {
name string
age int
email string
}
// 初始化
var p1 = Person{"zh", 12, "153"}
fmt.Println(p1, reflect.TypeOf(p1))
結構體基本格式
type 結構體名稱 struct{
欄位 類型
...
}
- 結構體的定義
type Person struct {
name string
age int
hobby []string. // 可以是切片哦
}
結構體
package main
import (
"fmt"
)
func main() {
// 定義結構體
type Person1 struct {
name, sex string
age int
}
var p1 = Person1{"ccn", "sunhat", 18}
fmt.Println(p1)
// 結構體嵌套
type Person2 struct {
score int
p1 Person1
}
v2 := Person2{99, Person1{"zj", "nan", 18}}
fmt.Println(v2)
//結構體匿名
type Person3 struct {
city string
hobby string
Person1 // 匿名結構體
}
v3 := Person3{city: "CN", hobby: "students", Person1:Person1{"11","nan",18}}
// 由於結構體是匿名的所以可以直接取p1中的name 否則必須v3.變數名.name
fmt.Println(v3.city, v3.name)
// 注意結構體賦值的時候 數據會全部都的拷貝一份 無論什麼類型
// 指針結構體 是不拷貝的 軟連接
// 切片和map 數據拷貝的的時候可能會發現感覺並沒有拷貝 但其實不是的是因為切片和map沒有擴容的時候使用的都是同一塊地址導致的 數據的特型
}
// 如果我們想要在賦值時不拷貝數據 我們可以將數據變成指針類型數據即可
*[2]string
*map[string]int
- 結構體指針
type Person2 struct {
lambda string
*Person
}
p3 := Person2{lambda: "這是一個匿名函數", Person: &Person{"Miho", "naming ", 12}}
p4 := p3
p3.name = "testzhizhen"
fmt.Println(p3, *p3.Person, p4, *p4.Person)
- 結構體標籤
// 標籤
type Person2 struct {
lambda string "聲明匿名"
*Person "我是Person"
}
p3 := Person2{lambda: "這是一個匿名函數", Person: &Person{"Miho", "naming ", 12}}
p4 := p3
p3.name = "testzhizhen"
// 標籤1
testStruct := reflect.TypeOf(p3)
fmt.Println(testStruct.Field(1).Tag)
// 標籤2
field, _ := testStruct.FieldByName("lambda")
fmt.Println(field.Tag)
// 循環獲取tag
fieldNum := testStruct.NumField()
for i := 0; i < fieldNum; i++ {
fmt.Println(testStruct.Field(i).Tag)
}
函數
可以把函數當作一個公用的程式碼塊,用於實現某一個功能. 並且提高程式碼的重用性和可讀性(函數傳參數據會重新拷貝 相當於賦值)
// 函數使用方法
func 函數名(參數 類型) 返回值類型 {
函數執行體
}
package main
import "fmt"
// 定義了返回值的類型時必須有return , name為接收的參數 string 為返回值的類型
func FirstFun(name string) string {
if len(name) > 2 {
//fmt.Println(name)
return name
}
return "error"
}
func main() {
res := FirstFun("zjccn")
fmt.Println(res)
}
- 函數調用指針類型 (節省記憶體)
package main
import "fmt"
func FirstFun(name *string) string {
*name = "123"
if len(*name) > 2 {
//fmt.Println(name)
return *name
}
return "error"
}
func main() {
name := "ZjCcn"
res := FirstFun(&name)
fmt.Println(res, name)
}
- 函數也可以作為參數
func main() {
TestProxy(10, Add100)
}
func Add100(data int) (int, bool) {
return data + 100, true
}
func TestProxy(data int, function func(int) (int, bool)) string {
resData , flag := function(data)
if flag{
fmt.Println(resData, flag)
}else {
fmt.Println("error")
}
return "complete"
}
- 函數傳遞可變長參數 可以不確定傳多少參數 (變長參數必須放在最後 類似 **kwargs 且只能存在一個)
// 這個num的類型實際上是切片類型。可以任意擴容
func Do(num ... int) int {
sum := 0
for _, value := range num{
sum += value
}
return sum
}
func main() {
numcount := Do(1,2,2,2,2,2,2,2,2,3,3,3,3,3,3)
fmt.Println(numcount)
}
- 匿名函數
func main() {
// 匿名函數
f1 := func(data int) int {
return data
}
fmt.Println(f1(11))
// 第二種匿名函數表達
f2 := func(data int) string {
return strconv.Itoa(data)
}(110)
fmt.Println(f2, reflect.TypeOf(f2))
}
- 函數不僅能做傳遞的參數 也可以作為返回值
test1 := Test1()
fmt.Println(test1(120))
func Test1() func(t1 int) int {
funtion := func(t1 int) int {
return t1
}
return funtion
}
- 閉包函數(通過一個函數將我們需要的數據固定包裹在一個包里)
// 閉包函數的作用就是將生成好的函數封裝好值也傳好, 之後調用的函數的值返回值 都是定義好的無需再次傳參 可直接調用
package main
import (
"fmt"
)
func main() {
// 這個切片會存儲著五個函數 分別列印 0 1 2 3 4
var funtionList []func()
for i := 0; i < 5; i++ {
funtion := func(data int) func(){
return func() {
fmt.Println(data)
}
}(i)
funtionList = append(funtionList, funtion)
}
// 運行封裝好的包
funtionList[0]()
funtionList[2]()
funtionList[3]()
}
- defer
用於在一個函數執行體完成之後自動觸發的語句, 一般用於結束操作之後釋放資源
// go中稱為延遲執行 可以隨意定義位置
// 相當於python中的 __del__ 釋放資源
// 類似於堆棧。先進後出
package main
import "fmt"
func main() {
TestDefer()
}
func TestDefer() {
fmt.Println("這是一次測試")
for i := 0; i < 10; i++ {
defer fmt.Println(i)
}
}
- 自執行函數 (類似於python init)
// ~= python __init__
// 自執行函數就是匿名函數
func TestDefer() {
result := func(age int) int{
return age
}(18)
fmt.Println(result)
}
- 結構體做函數返回值
type funstruct struct {
name string
age int
}
func StructTest(name string) (funstruct) {
return funstruct{name: name, age: 18}
}
func main(){
t1 := StructTest("testzh")
fmt.PrintLn(t1)
}
類型方法
項目開發中可以為type聲明的類型編寫一些方法, 從而實現對象.方法
的操作
// 可以定義執行自己的程式碼的方法
package main
import "fmt"
// 聲明類型
type myInt int
func main() {
var testMy myInt = 110
res := testMy.Dosomething(1,2)
fmt.Println(res)
}
// 這個因為前邊加上了(i *myInt) 所以他就不是函數了 而是自我定義的方法
myInt可以是指針 也可以是正常的 為了節省記憶體我們是用指針
func (i *myInt) Dosomething (data1 int , data2 int) int {
return data1+data2+int(*i)
}
- 方法繼承(類似於面向對象的繼承)
type Base struct {
name string
}
type Son struct {
Base // 匿名結構體
age int
test string
}
func (b1 *Base) Test1() int {
return 123
// return b1.name name: "張無忌"
}
func (s1 *Son) Test2() int {
return 222
}
func main() {
son := Son{Base:Base{name: "張無忌"}, age:13, test:"測試"}
fmt.Println(son.Test1(), son.Test2())
}
>>>>>>>>>>>>>>>執行結果:
123 222
- 結構體工廠
聲明任何數據類型的時候。只要變數名稱為小寫 就是私有變數 大寫就是公共變數。私有就是只有當前這個.go文件可以使用 公有都能使用
- Go介面類型
GO語言中的介面是一種特殊的數據類型, 定義格式如下:
type name interface{
方法名稱() 返回值
}
例如:
type Name interface {
f1()
f2() int
f3() (int, bool)
}
定義介面的特點是 不需要寫任何邏輯程式碼 只需要將方法的名和返回值傳參數 寫好即可
-
介面的作用
在程式開發中介面一般有兩大作用 : 代指類型 和 約束。
-
空介面, 代指任意類型
package main
import (
"fmt"
"reflect"
)
// 定義個空介面 空介面中可以傳任意類型的值
type base interface {
}
func main() {
//dataList := make([]base, 0)
// 上邊這些介面定義可以簡寫為:
dataList := make([]interface{}, 0)
dataList = append(dataList, "test")
dataList = append(dataList, 234)
dataList = append(dataList, map[string]string{"key": "values"})
fmt.Println(dataList[2], reflect.TypeOf(dataList))
something("test")
something("test")
something("test")
caseSth("haha")
caseSth(123)
caseSth([]string{"xii", "haha"})
}
// 像python一樣 弱類型
type person struct {
name string
}
// 接收到數據類型都是介面類型 所以我們要使用.(類型或者類型結構體)轉換成我們想要的格式
func something(arg interface{}) {
s,ok := arg.(string)
if ok{
fmt.Println(s, reflect.TypeOf(s))
} else {
fmt.Println("轉換失敗")
}
}
func caseSth(arg interface{}) {
switch data := arg.(type) {
case string:
fmt.Println(data)
case int:
fmt.Println(data)
case []string:
fmt.Println(data)
default:
fmt.Println("WOZHAOBUDAO")
}
}
- 非空介面 規範約束
type Ibase interface {
test1 () int
}
type Person struct {
name string
age int
}
func (p *Person) test1() int {
return 123
}
type User struct {
username string
password string
}
func (u *User) test1() int {
return 567
}
func main() {
p := Person{"zj", 18}
//u := User{"1533333", "520cnn.."}
p.test1()
List := []Ibase{&Person{"COMPELTE",12}, &User{"完成", "LE"}}
fmt.Println(List)
for _ , value := range List{
fmt.Println(value, reflect.TypeOf(value))
}
}
- flag包
基於os.Args可以獲取傳入的所有參數
package main
import (
"flag"
"fmt"
)
func main() {
//fmt.Println(os.Args)
/* >>>結果
mac$ ./main 213 123414155 5125
[./main 213 123414155 5125]
*/
// h 是命令行要-h 默認是127.0.0.1 後邊是備註
host := flag.String("h", "127.0.0.1", "主機名")
port := flag.Int("p", 1234, "埠號")
//host := flag.String("h", "127.0.0.1", "主機名")
flag.Parse() // 必須parse 才能將命令行傳入的數據傳進來
fmt.Println(*host, *port)
/* >>結果
mac$ ./main -h 192.168.1.1 -p 1212
192.168.1.1 1212
*/
}
- Go regexp 正則表達式
package main
import (
"fmt"
"regexp"
)
func main() {
// 根據字元串匹配返回 是否成功
matchString, err := regexp.MatchString(".*?:(.*?)n.*?", "vaiuhguifg:xixizhangjiannihaonfaoghaiug")
if err == nil {
fmt.Println(matchString) // true
}
// 根據匹配值字元串查找
reg1 := regexp.MustCompile(`\d{11}`)
res1 := reg1.FindString("1242563837613") // 結果 12425638376
fmt.Println(res1)
res2 := reg1.FindAllString("1241245569005837163414115", -1) // 結果就是找所有的11個數字的集合[12412455690 05837163414]
fmt.Println(res2)
// 獲取分組資訊
}
文件路徑相關的內置包
- 文件增刪改查
// 創建單級目錄 付給許可權
os.Mkdir("testDir", 0755)
// 創建多級目錄
os.MkdirAll("/test/123/code", 0755)
// 刪除文件或空文件夾, 文件夾下存在內容數據 就會報錯
os.Remove("testDir")
// 刪除文件或文件夾 (同時刪除子目錄中的數據)
os.RemoveAll("test123")
- 判斷目錄 或文件是否存在. os.IsNotExist(err)
_, err := os.Stat("test1/test2/123.go")
if err != nil {
if os.IsNotExist(err) {
fmt.Println("文件或文件夾不存在")
}else{
fmt.Println("存在")
}
}
- 判斷是否是文件夾IsDir()
file, _ := os.Stat("test/123/")
res := file.IsDir()
- 獲取絕對路徑
abs, err := filepath.Abs("test/123/")
// 獲得的結果就是絕對路徑+test/123/
// 獲得絕對路徑
abs, err := filepath.Abs(".")
if err == nil {
// 獲得上層路徑
fmt.Println(filepath.Dir(abs))
}
- 遍歷目錄下的文件
package main
import (
"fmt"
"io/ioutil"
"path/filepath"
)
func main() {
abs, _ := filepath.Abs(".")
dir, err := ioutil.ReadDir(abs)
fmt.Println(dir)
if err == nil {
for _, value :=range dir{
if !value.IsDir(){
fmt.Println(value.Name())
}
}
}
}
- walk
// walk會深層遞歸的查詢數據
filepath.Walk(abs, func(path string, info os.FileInfo, err error) error {
fmt.Println(info.Name())
fmt.Println(path)
return nil
})
- 路徑拼接 or 文件擴展名
// 拼接
filePath := path.Join("1","2","3","7.dmg")
// 文件擴展名
ext := path.Ext("/fres/faf/xxx.txt")
獲取到的就是擴展名txt