複雜數據類型 / 派生數據類型【指針、數組、結構體、管道、函數、切片、接口、map】#
指針#
什麼是指針#
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)
}
細節#
-
函數名
- 遵循標識符命名規範
- 首字母不能為數字
- 首字母大寫可以被本包和其他包調用(類似 public)
- 首字母小寫只能被本包調用(類似 private)
-
形參列表
- 個數:參數可以是 0 個、1 個或 n 個
- 作用:接收外來數據
-
返回值類型列表
-
返回值類型列表為 0 個直接省略此項
-
返回值類型列表就一個可以省略括號
-
如果有返回值不想被接收,可以用_來忽略
-
-
不支持重載
-
支持可變參數
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
- 基本數據類型和數組默認都是值傳遞,在函數內修改不會影響原來的值。
package main
import "fmt"
func ex(num int) {
num = 30
}
func main() {
var num int = 10
ex(num)
fmt.Println(num)
}
運行結果:
10
- 以值傳遞方式的數據類型,如果希望函數內的變量修改函數外的變量,可以用 & 傳入變量的地址,函數內以指針方式操作變量。
package main
import "fmt"
func ex(num *int) {
*num = 30
}
func main() {
var num int = 10
ex(&num)
fmt.Println(num)
}
運行結果:
30
- 函數也是一種數據類型,可以賦值給一個變量,可通過該變量調用函數,函數也可以作為形參被調用。
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)
}
- Go 支持自定義數據類型(相當於給現有數據類型起別名)
基本語法:type 自定義數據類型名數據類型
例如:type myInt int 【此時的 myInt 等價於 int】
例如:type mySum func (int, int) int 【此時的 mySum 等價於 func (int, int) int】 - 支持對函數返回值命名
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
}
包的引入#
使用包的原因#
- 可以把函數分類,放在不同原文件中
- 解決同名問題,不同包裡的函數可以同名,通過包來區分
細節#
- 建議聲明的包與所在文件夾同名
- Main 包是程序入口包,一般 main 函數會在這個包
- 打包語法:package 包名
- 引入包的語法:import "包的路徑",多個包一次性導入加 {}
- 不同包函數調用時要先定位到所在包
- 函數名、變量名首字母大寫才可以被其它包訪問
- 同目錄下不能有重複函數
- 包名和文件夾名可以不一樣
- 一個目錄下同級文件歸属于同一個包
- 可以給包取別名,一旦取別名,原包名不能使用
Init 函數#
初始化函數,可以用來一些初始化操作。
匿名函數#
如果某個函數希望只使用一次,可以考慮匿名函數
匿名函數使用方式#
- 在定義匿名函數時直接調用,這種方式只能調用一次
package main
import "fmt"
func main() {
//定義匿名函數:定義的同時調用
sum := func(num1 int, num2 int) int {
return num1 + num2
}(10, 20)
fmt.Println(sum)
}
- 將匿名函數賦值給一個變量(即函數變量),通過此變量來調用
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 包下,不用導包直接使用
常用內置函數:
- Len 函數:按字節統計字符串長度
- 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
}
- 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]
數組注意事項#
- 數組長度是數組類型的一部分
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
- 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 是預設總的分配的內存空間長度(即底層數組和預分配內存的長度總和),預分配內存的長度是可變的,一旦超出預設,以預設總內存長度為單位繼續加長
切片的聲明和初始化#
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
}