banner
音小董

哩哔哩哔

这世上的热闹出自孤单

第4章:複雜數據類型

複雜數據類型 / 派生數據類型【指針、數組、結構體、管道、函數、切片、接口、map】#

指針#

什麼是指針#

指針.png

package main

import "fmt"

func main() {
	var age int = 18
	//*int表示ptr對應的指針類型
	//&age表示age變量在內存中的地址
	var ptr *int = &age
	fmt.Println(ptr)
	//&ptr表示ptr指針變量所在內存空間的地址
	fmt.Println(&ptr)
	//*ptr表示ptr指針指向的數據
	fmt.Println(*ptr)
}

總結:

&符號表示取內存地址

* 符號表示根據地址取值

指針的 4 個細節#

1、可以通過指針改變指向值

2、指針變量接收的一定是地址值

3、指針變量的地址不可以不匹配

4、基本數據類型都有對應的指針類型

函數#

函數的引入#

函數的定義:為完成某一功能的程序指令的合集,稱為函數。

作用:提高代碼的重用,減少代碼的冗餘,提高代碼維護性。

基本語法#

Func 函數名 (形參列表) (返回值類型列表) {
執行語句...
Return + 返回值列表
}

package main  
  
import "fmt"  
  
// 自定義cal函數,功能:兩個數相加  
func cal(sum1 int, num2 int) int { //返回值類型列表就一個可以省略括號
var sum int = 0  
sum += sum1  
sum += num2  
return sum  
}  
func main() {  
var n int = 10  
var m int = 20  
s := cal(m, n)  
fmt.Println(s)  
}
細節#
  1. 函數名

    • 遵循標識符命名規範
    • 首字母不能為數字
    • 首字母大寫可以被本包和其他包調用(類似 public)
    • 首字母小寫只能被本包調用(類似 private)
  2. 形參列表

    • 個數:參數可以是 0 個、1 個或 n 個
    • 作用:接收外來數據
  3. 返回值類型列表

    • 返回值類型列表為 0 個直接省略此項

    • 返回值類型列表就一個可以省略括號

    • 如果有返回值不想被接收,可以用_來忽略

  4. 不支持重載

  5. 支持可變參數

package main  
  
import "fmt"  
  
func test(args ...int) {  
for j := 0; j < len(args); j++ {  
fmt.Println(args[j])  
}  
}  
func main() {  
test()  
fmt.Println("________________________")  
test(3)  
fmt.Println("________________________")  
test(2, 5, 6, 9)  
}

運行結果:

________________________
3                       
________________________
2                       
5                       
6                       
9   
  1. 基本數據類型和數組默認都是值傳遞,在函數內修改不會影響原來的值。
package main  
  
import "fmt"  
  
func ex(num int) {  
num = 30  
}  
func main() {  
var num int = 10  
ex(num)  
fmt.Println(num)  
}

運行結果:

10
  1. 以值傳遞方式的數據類型,如果希望函數內的變量修改函數外的變量,可以用 & 傳入變量的地址,函數內以指針方式操作變量。
package main  
  
import "fmt"  
  
func ex(num *int) {  
*num = 30  
}  
func main() {  
var num int = 10  
ex(&num)  
fmt.Println(num)  
}

運行結果:

30
  1. 函數也是一種數據類型,可以賦值給一個變量,可通過該變量調用函數,函數也可以作為形參被調用。
package main  
  
import "fmt"  
  
// 定義一個函數  
func re(num int) {  
fmt.Println(num)  
}  
  
// 定義一個函數,把另一個函數作為形參  
func te(num1 int, num2 float32, testFunc func(int)) {  
fmt.Println("*************")  
}  
func main() {  
a := re  
fmt.Printf("a的類型是:%T,re函數的類型是:%T", a, re) //a的類型是:func(int),re函數的類型是:func(int)  
a(10) //等價於test(10)  
te(1, 2.5, a) //等價於te(1, 2.5, re)  
}
  1. Go 支持自定義數據類型(相當於給現有數據類型起別名)
    基本語法:type 自定義數據類型名數據類型
    例如:type myInt int 【此時的 myInt 等價於 int】
    例如:type mySum func (int, int) int 【此時的 mySum 等價於 func (int, int) int】
  2. 支持對函數返回值命名
package main  
  
func test1(num1 int, num2 int) (int, int) {  
result1 := num1 + num2  
result2 := num1 - num2  
return result1, result2  
}  
  
// 給函數的返回值命名可以寫成  
func test2(num1 int, num2 int) (sum int, sub int) {  
sum = num1 + num2  
sub = num1 - num2  
return  
}

包的引入#

使用包的原因#
  1. 可以把函數分類,放在不同原文件中
  2. 解決同名問題,不同包裡的函數可以同名,通過包來區分
細節#
  1. 建議聲明的包與所在文件夾同名
  2. Main 包是程序入口包,一般 main 函數會在這個包
  3. 打包語法:package 包名
  4. 引入包的語法:import "包的路徑",多個包一次性導入加 {}
  5. 不同包函數調用時要先定位到所在包
  6. 函數名、變量名首字母大寫才可以被其它包訪問
  7. 同目錄下不能有重複函數
  8. 包名和文件夾名可以不一樣
  9. 一個目錄下同級文件歸属于同一個包
  10. 可以給包取別名,一旦取別名,原包名不能使用

Init 函數#

初始化函數,可以用來一些初始化操作。

匿名函數#

如果某個函數希望只使用一次,可以考慮匿名函數

匿名函數使用方式#
  1. 在定義匿名函數時直接調用,這種方式只能調用一次
package main  
  
import "fmt"  
  
func main() {  
//定義匿名函數:定義的同時調用  
sum := func(num1 int, num2 int) int {  
return num1 + num2  
}(10, 20)   
fmt.Println(sum)  
}
  1. 將匿名函數賦值給一個變量(即函數變量),通過此變量來調用
package main  
  
import "fmt"  
  
func main() {  
//定義匿名函數並賦值給變量  
sub := func(num1 int, num2 int) int {  
return num1 - num2  
}  
result := sub(20, 10)  
fmt.Println(result)  
}

閉包#

什麼是閉包#

閉包就是一個函數和與其相關的引用環境組合的一個整體。

package main  
  
import "fmt"  
  
// 定義fucSum函數,參數為空  
// fucSum函數返回一個函數,該函數參數為int類型,返回值也為int類型  
func fucSum() func(int) int {  
var sum int = 0  
return func(p int) int {  
sum += p  
return sum  
}  
}  
  
// 閉包就比如這裡的fucSum函數返回的函數加上變量sum  
func main() {  
f := fucSum()  
fmt.Println(f(1)) //1  
fmt.Println(f(2)) //3  
fmt.Println(f(3)) //6  
fmt.Println(f(4)) //10  
}

> 匿名函數引用的那個變量會保存在內存中,可以一直使用,對內存消耗大,所以不可濫用

閉包的本質#

閉包 = 匿名函數 + 引用的變量 / 參數

Defer 關鍵字#

為了函數執行完畢後及時釋放資源

package main  
  
import "fmt"  
  
func add(m int, n int) int {  
//在golang中,遇到defer關鍵字,不會立即執行defer後的語句,而是將defer後面的語句壓入一個棧中,然後繼續執行函數後面的語句  
//棧的特點:先進後出  
//在函數執行完畢以後,從棧中取出語句開始執行,按照先進後出的規則執行語句  
defer fmt.Println(m)  
defer fmt.Println(n)  
fmt.Println(m + n)  
return m + n  
}  
func main() {  
fmt.Println("結果為:", add(10, 20))  
}

運行結果:

30
20         
10         
結果為: 30

defer 應用場景#

defer 有延遲執行機制(函數執行完畢再執行 defer 壓入棧的語句),所以需要關閉資源時,直接隨手 defer 省心省事。

系統函數#

字符串相關函數#
統計字符串長度
len(str)
字符串遍歷
r := []rune(str)
字符串轉整數
n, err := strconv.Atoi("66")
整數轉字符串
str := strconv.Itoa(88)
判斷字符串中是否包含另一字符串
strings.Contains("javaandgolang", "go")
統計某字符串在另一字符串中出現次數
strings.Count("javaandgolang", "a")
不區分大小判斷兩個字符串是否一致
fmt.Println(strings.EqualFold("go", "Go"))
返回字符串在另一字符串中第一次出現的索引值
strings.Index("javaandgolang", "a")

如果沒有返回 - 1

字符串的替換
strings.Replace("goandjavagogo", "go", "golang", n)

N 表示指定替換幾個,如果 n 為 - 1 表示全部替換

按指定字符串把另一字符串拆分分割成字符串數組
strings.Split("go-java-python", "-")
字符串中字母大小寫替換
strings.ToLower("GO")
strings.ToUpper("go")
去掉字符串左右兩邊空格
strings.TrimSpace(" go and java ")
去掉字符串左右兩邊指定字符
strings.Trim("~go~", "~")
去掉字符串左邊指定字符
strings.TrimLeft("~go", "~")
去掉字符串右邊指定字符
strings.TrimRight("go~", "~")
判斷字符串是否以指定字符串開頭
strings.HasPrefix("https://libilibi.eu.org", "https")
判斷字符串是否以指定字符串結束
strings.HasSuffix("https://libilibi.eu.org", "org")
日期和時間相關函數#
package main  
  
import (  
"fmt"  
"time"  
)  
  
func main() {  
//獲取當前本地時間Now(),返回值是一個結構體,類型是time.Time  
now := time.Now()  
fmt.Println(now) //2023-06-27 23:11:41.8027164 +0800 CST m=+0.001038201  
fmt.Printf("%T \n", now) //time.Time  
//調用結構體中的方法:  
fmt.Printf("年:%v \n", now.Year()) //年:2023  
fmt.Printf("月:%v \n", now.Month()) //月:June  
fmt.Printf("月:%v \n", int(now.Month())) //月:6  
fmt.Printf("日:%v \n", now.Day()) //日:27  
fmt.Printf("時:%v \n", now.Hour()) //時:23  
fmt.Printf("分:%v \n", now.Minute()) //分:20  
fmt.Printf("秒:%v \n", now.Second()) //秒:56  
fmt.Println("_____________________________________________")  
//Printf 是直接輸出字符串  
fmt.Printf("當前年月日:%d-%d-%d 時分秒:%d:%d:%d \n", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())  
//Sprintf可以得到字符串,以便調用  
dateStr := fmt.Sprintf("當前年月日:%d-%d-%d 時分秒:%d:%d:%d \n", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())  
fmt.Println(dateStr) //當前年月日:2023-6-27 時分秒:23:25:37  
fmt.Println("_____________________________________________")  
dateStr2 := now.Format("2006/01/02 15/04/05") //各個位置數字必須固定,可以省略但不可更改  
fmt.Println(dateStr2) //2023/06/27 23/38/24  
}
內置函數#

內置函數在 builtin 包下,不用導包直接使用

常用內置函數:

  1. Len 函數:按字節統計字符串長度
  2. New 函數:分配內存,主要分配值類型
package main  
  
import "fmt"  
  
func main() {  
//new分配內存,new函數的實參是一個類型而不是數值,new函數返回值是對應類型的指針  
num := new(int)  
fmt.Printf("num的類型:%T,num的值是:%v,num的地址是:%v,num指針指向的值是:%v", num, num, &num, *num)  //num的類型:*int,num的值是:0xc0000a6058,num的地址是:0xc0000ca018,num指針指向的值是:0
}
  1. Make 函數:分配內存,主要分配引用類型

數組#

數組的聲明#

var 變量名 [索引值] 數據類型

package main  
  
import (  
"fmt"  
)  
  
func main() {
//聲明一個變量名為scores,索引值為5(即表示能存5個變量)的int類型的數組
var scores [5]int  
scores[0] = 89  
scores[1] = 79  
scores[2] = 92  
scores[3] = 95  
scores[4] = 88  
var sum int  
//求和  
for i := 0; i < len(scores); i++ {  
sum += scores[i]  
}  
//求平均  
avg := sum / len(scores)  
fmt.Println(sum) //443  
fmt.Println(avg) //88  
}

複習 for rang

package main  
  
import "fmt"  
  
func main() {  
var scores [5]int  
//手動輸入數組內每個變量的值  
for i := 0; i < len(scores); i++ {  
fmt.Printf("請輸入第%d個值------", i+1)  
fmt.Scanln(&scores[i])  
}  
for m, n := range scores {  
fmt.Printf("第%d個值為:%d\n", m+1, n)  
}  
}

數組的初始化#

package main  
  
import "fmt"  
  
func main() {  
//第一種  
var a [3]int = [3]int{1, 2, 3}  
fmt.Println(a)  
//第一種  
var b = [3]float32{2.5, 3.6, 5.5}  
fmt.Println(b)  
//第三種  
var c = [...]byte{4, 5, 6}  
fmt.Println(c)  
//第四種  
var d = [...]string{1: "jia", 2: "ming", 0: "wang"}  
fmt.Println(d)  
}

運行結果:

[1 2 3]
[2.5 3.6 5.5]  
[4 5 6]        
[wang jia ming]

數組注意事項#

  1. 數組長度是數組類型的一部分
package main  
  
import "fmt"  
  
func main() {  
//第一種  
var a [3]int = [3]int{1, 2, 3}  
fmt.Printf("數組類型為:%T\n", a)  
//第一種  
var b = [2]float32{2.5, 3.6}  
fmt.Printf("數組類型為:%T\n", b)  
//第三種  
var c = [...]byte{4, 5, 6, 7}  
fmt.Printf("數組類型為:%T\n", c)  
//第四種  
var d = [...]string{1: "jia", 2: "ming", 0: "wang"}  
fmt.Printf("數組類型為:%T\n", d)  
}

運行結果:

數組類型為:[3]int
數組類型為:[2]float32
數組類型為:[4]uint8  
數組類型為:[3]string 
  1. Go 中數組屬於值類型,默認情況是值傳遞,因此會進行值拷貝,所以想在其他函數修改數組,需要使用引用傳遞(即指針方式)
package main  
  
import "fmt"  
  
func main() {  
var arr = [3]int{7, 8, 9}
test1(arr)
fmt.Println(arr) //[7,8,9] test函數賦值arr[0]後數組沒有變化
test2(&arr) //傳入數組地址  
fmt.Println(arr)  //[5,8,9] 此時數組有變化了
} 
func test1(arr [3]int) { //取出數組的值進行賦值  
arr[0] = 5  
}
func test2(arr *[3]int) { //取出數組的值進行賦值  
arr[0] = 5  
}

二維數組的聲明#

var 變量名 [索引值][索引值] 數據類型

package main  
  
import "fmt"  
  
func main() {  
var a [2][3]int  
fmt.Println(a) //[[0 0 0] [0 0 0]] 表示這個數組有2個長度為3一維數組組成  
}

二維數組的初始化#

package main  
  
import "fmt"  
  
func main() {  
var arr = [2][3]string{{"a", "b", "c"}, {"a", "b", "c"}}  
fmt.Println(arr)  
}

二維數組的遍歷#

package main  
  
import "fmt"  
  
func main() {  
var arr = [3][3]string{{"a", "b", "c"}, {"d", "e", "f"}, {"g", "h", "i"}}  
//雙層for循環  
for i := 0; i < len(arr); i++ {  
for j := 0; j < 3; j++ {  
fmt.Println(arr[i][j])  
}  
}  
println("______________________________________________________")  
//雙層for rang 如果不需要索引可以用下劃線忽略  
for _, m := range arr {  
for _, n := range m {  
fmt.Println(n)  
}  
}  
}

運行結果:

a
b                                                     
c                                                     
d                                                     
e                                                     
f                                                     
g                                                     
h                                                     
i                                                     
______________________________________________________
a                                                     
b                                                     
c                                                     
d                                                     
e                                                     
f                                                     
g                                                     
h                                                     
i                                                     

切片#

切片是什麼#

切片是一個結構體,len 表示底層數組長度,cap 是預設總的分配的內存空間長度(即底層數組和預分配內存的長度總和),預分配內存的長度是可變的,一旦超出預設,以預設總內存長度為單位繼續加長
image.png
image.png

切片的聲明和初始化#

var 變量名 [] 數據類型

package main  
  
import "fmt"  
  
func main() {  
var arr []int  
arr = make([]int, 3, 5)  
arr[0], arr[1], arr[2] = 7, 8, 9  
//打印底層數組長度和預設總的分配的內存空間長度  
fmt.Printf("%d %d\n", len(arr), cap(arr)) //3 5  
//打印切片  
fmt.Println(arr) //[7 8 9]  
}

切片的細節#

package main  
  
import "fmt"  
  
func main() {  
var arr = make([]int, 3, 4)  
arr[0], arr[1], arr[2] = 7, 8, 9  
//打印底層數組長度和預設總的分配的內存空間長度  
fmt.Printf("%d %d\n", len(arr), cap(arr)) //3 4  
//打印切片  
fmt.Println(arr) //[7 8 9]  
//把切片arr賦值給切片brr  
brr := arr  
fmt.Printf("%p %p\n", arr, brr) //0xc00000c330 0xc00000c330 發現這兩個切片內存地址一樣,所以是共享一個內存地址  
//在arr切片基礎上追加上個10放到預分配內存然後賦值給brr切片  
brr = append(arr, 10)  
fmt.Printf("%d %d %d\n", len(brr), cap(brr), brr) //4 4 [7 8 9 10] 此時底層數組發生改變,預分配內存已經填滿  
//在brr切片原基礎上追加上個11,預分配內存不夠,需要增加,之前內存總長4,再開辟添加長度為4的內存  
brr = append(brr, 11)  
fmt.Printf("%d %d %d\n", len(brr), cap(brr), brr) //5 8 [7 8 9 10 11] 此時底層數組發生改變,分配內存總長度也改變  
fmt.Printf("%p %p\n", arr, brr) //0xc000012180 0xc00000e3c0 此時內存地址也改變,所以不共享內存地址了  
}

切片的遍歷#

package main  
  
import "fmt"  
  
func main() {  
var arr = make([]int, 3, 4)  
arr[0], arr[1], arr[2] = 7, 8, 9  
for i, value := range arr {  
fmt.Printf("%p %p %d %d\n", &value, &arr[i], value, arr[i])  
}  
}

運行結果:

0xc0000a6058 0xc0000d2020 7 7
0xc0000a6058 0xc0000d2028 8 8
0xc0000a6058 0xc0000d2030 9 9

遍歷過程 value 內存地址不變

map#

map 的聲明和初始化#

var 變量名 map [鍵的數據類型] 值的數據類型

package main  
  
import "fmt"  
  
func main() {  
var m map[string]int  
fmt.Println(m)  
}

map 的細節#

package main  
  
import "fmt"  
  
func main() {  
var m map[string]int  
m = map[string]int{"a": 7, "b": 8, "c": 9}  
m["a"] = 6  
fmt.Println(m["a"])  
//刪除m中a映射的值,雖然刪除了但是返回默認值0  
delete(m, "a")  
fmt.Println(m["a"])  
//判斷m中a映射的值到底刪除了沒  
if v, exists := m["a"]; exists { //初始化v和exists,等價m["a"],判斷exists是否存在  
fmt.Println(v)  
} else {  
fmt.Println("m[\"a\"]已被刪除")  
}  
}

運行結果:

6
0             
m["a"]已被刪除

map 的遍歷#

package main  
  
import (  
"fmt"  
)  
  
func main() {  
var m = map[string]int{"a": 1, "b": 2, "c": 3}  
for key, value := range m {  
fmt.Println(key, value)  
}  
}

運行結果:

a 1
b 2
c 3

結構體#

結構體的定義和運用#

package main  
  
import "fmt"  
  
type Teacher struct {  
Name string  
Age int  
School string  
}  
type User struct {  
Name string  
Age int  
School string  
}  
  
func main() {  
t := Teacher{"張三", 18, "清華"}  
fmt.Println(t) //{張三 18 清華}  
u := *new(User)  
u.Name = "李四"  
u.Age = 19  
u.School = "北大"  
fmt.Println(u) //{李四 19 北大}  
}

接口#

接口的定義和運用#

接口是一組行為規範的集合

package main  
  
import "fmt"  
//Math接口可以定義一些規則
type Math interface {  
Formula(int, int) int  
}  
  
func test(math Math) {  
m := math.Formula(6, 3)  
fmt.Println(m)  
}  
func main() {
//Add和Sub都是Math接口的具體實現
m := Add{}  
n := Sub{}  
test(m)  //9
test(n)  //3
}  

type Add struct {  
}  
//給結構體安裝方法  
func (Add) Formula(a int, b int) int {  
return a + b  
}  
  
type Sub struct {  
}  
//給結構體安裝方法    
func (Sub) Formula(a int, b int) int {  
return a - b  
}

空接口#

空接口可以直接寫成 interface {},接受一切行為規範 (即包容一切),可以隨意賦值

package main  
  
import "fmt"  
  
func main() {  
var inter interface{}  
var str = "golang"  
inter = str  
fmt.Println(inter) //golang  
var a = 0  
inter = a  
fmt.Println(inter) //0  
var f = true  
inter = f  
fmt.Println(inter) //true  
}
載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。