Go語言3
strings 包
strings 包實現了用於操作字串的簡單函式。
HasPrefix
strings.HasPrefix(s string, prefix string) bool
判斷字串s是否以prefix開頭
HasSuffix
strings.HasSuffix(s string, suffix string) bool
判斷字串s是否以suffix結尾
Index
strings.Index(s string, str string) int
判斷str在s中首次出現的位置,如果沒有出現,則返回-1
LastIndex
strings.LastIndex(s string, str string) int
判斷str在s中最後出現的位置,如果沒有出現,則返回-1
Replace
strings.Replace(str string, old string, new string, n int) string
字串替換,對原始字串str進行替換,把原始的old替換成new。
最後一個n是替換的次數,如果n<0
則替換所有。一般就-1。
Count
strings.Count(str string, substr string) int
字串計數,返回 substr 在 str 裡出現的次數。
Repeat
strings.Repeat(str string, count int) string
返回一個新的字串,由 str 重複 count 次構成。或者這麼講,將 count 個字串 str 連線成一個新的字串。
ToLower
strings.ToLower(str string) string
轉為小寫
ToUpper
strings.ToUpper(str string) string
轉為大寫
TrimSpace
strings.TrimSpace(s string) string
去掉字串首尾空白字元,空白字元除了空格至少還包括: “\n\t\r” 。
相當於下面的這個Trim方法的簡寫:
strings.Trim(s string, " \n\t\r") string
Trim
strings.Trim(s string, cutset string) string
去掉字串首尾的 cutset 字串裡包含的字元
示例:
package main import ( "fmt" "strings" ) func main() { fmt.Printf("[%q]", strings.Trim(" !!! Achtung! Achtung! !!! ", "! ")) } // 上面的cutset包含2個字元: ! 和 空格, //所以會把字串 s 兩邊的這兩個字元都去掉,直到遇到這兩個字元以外的字元為止 /* 執行結果: H:\Go\src\go_dev\day3\strings>go run test_trim.go ["Achtung! Achtung"] H:\Go\src\go_dev\day3\strings> */
如果只需要去掉前面的部分,或者只去掉末尾的部分,還有下面2個方法:
strings.TrimLeft(s string, cutset string) string strings.TrimRight(s string, cutset string) string
Split
strings.Split(s string, sep string) []string
返回 sep 分隔的所有字串 s 的子串的slice
如果是用空格分隔的,那麼可以用下面的 Fields 方法
strings.Fields(s string) []string
Join
strings.Join(a []string, sep string) string
用 sep 把 a 裡的所有元素拼接起來。是上面的Split的逆向操作
strconv 包
strconv 包提供了字串和基本資料型別之間的轉換操作。
Itoa
strconv.Itoa(i int) string
整數轉字串
Atoi
strconv.Atoi(s string) (i int, err error)
字串轉整數。注意,這個返回2個值。成功轉換,返回整數,err為空(nil);如果s是空或者無效數字,就會有err,並且返回值是0。
時間和日期型別
time包提供顯示和計算時間用的函式。
獲取當前時間
獲取當前時間用time.Now()
:
package main import ( "fmt" "time" ) func main(){ now := time.Now() fmt.Println(now) fmt.Printf("%T\n", now) } /* 執行結果: H:\Go\src\go_dev\day3\time>go run now.go 2018-10-07 12:51:52.9706623 +0800 CST m=+0.001999101 time.Time H:\Go\src\go_dev\day3\time> */
這個資料型別是 time.Time
獲取日期資訊
包括但不只有下面這些方法,全部的方法去官網查吧:
- func (t Time) Date() (year int, month Month, day int) {} // 返回時間的日期資訊
- func (t Time) Year() int {} // 返回年
- func (t Time) Month() Month {} // 月
- func (t Time) Day() int {} // 日
- func (t Time) Weekday() Weekday {} // 星期
- func (t Time) ISOWeek() (year, week int) {} // 返回年,星期範圍編號
- func (t Time) Clock() (hour, min, sec int) {} // 返回時間的時分秒
- func (t Time) Hour() int {} // 返回小時
- func (t Time) Minute() int {} // 分鐘
- func (t Time) Second() int {} // 秒
- func (t Time) Nanosecond() int {} // 納秒
- func (t Time) YearDay() int {} // 一年中對應的天
- func (t Time) Location() *Location {} // 時間的時區
- func (t Time) Zone() (name string, offset int) {} // 時間所在時區的規範名和想對UTC 時間偏移量
- func (t Time) Unix() int64 {} // 時間轉為時間戳
- func (t Time) UnixNano() int64 {} // 時間轉為時間戳(納秒)
示例:
package main import ( "fmt" "time" ) func main(){ now := time.Now() fmt.Println(now.Year(), now.Month(), now.Day()) } /* 執行結果 H:\Go\src\go_dev\day3\time>go run time_info.go 2018 October 7 H:\Go\src\go_dev\day3\time> */
時間格式化
用(Time) Format格式化
首先,預設提供這麼多格式,都定義在time的常量裡:
const ( ANSIC= "Mon Jan _2 15:04:05 2006" UnixDate= "Mon Jan _2 15:04:05 MST 2006" RubyDate= "Mon Jan 02 15:04:05 -0700 2006" RFC822= "02 Jan 06 15:04 MST" RFC822Z= "02 Jan 06 15:04 -0700" // RFC822 with numeric zone RFC850= "Monday, 02-Jan-06 15:04:05 MST" RFC1123= "Mon, 02 Jan 2006 15:04:05 MST" RFC1123Z= "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone RFC3339= "2006-01-02×××5:04:05Z07:00" RFC3339Nano = "2006-01-02×××5:04:05.999999999Z07:00" Kitchen= "3:04PM" // Handy time stamps. Stamp= "Jan _2 15:04:05" StampMilli = "Jan _2 15:04:05.000" StampMicro = "Jan _2 15:04:05.000000" StampNano= "Jan _2 15:04:05.000000000" )
這些預定義的常量,其實使用的都是一個特定的時間。這個時間是Unix time 1136239445,因為MST是GMT-0700,所以這個指定的時間也可以看做:
01/02 03:04:05PM '06 -0700
選一個上面的常量字串,或者自己根據這個特定時間照著寫一個格式化字串,呼叫Format方法進行格式化:
package main import ( "fmt" "time" ) func main(){ now := time.Now() fmt_now := now.Format(time.RFC1123Z) fmt.Println(fmt_now) fmt_str := "2006/1/2 15:04:05 Monday" fmt.Println(now.Format(fmt_str)) fmt.Println(now.Format("01/02 03:04:05PM '06 -0700")) } /* 執行結果 H:\Go\src\go_dev\day3\time>go run format.go Sun, 07 Oct 2018 13:39:27 +0800 2018/10/7 13:39:27 Sunday 10/07 01:39:27PM '18 +0800 H:\Go\src\go_dev\day3\time> */
用fmt.Printf格式化
package main import ( "fmt" "time" ) func main(){ now := time.Now() fmt.Printf( "%02d/%02d/%02d %02d:%02d:%02d %v", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second(), now.Weekday(), ) }
時間段
time.Duration 型別代表兩個時間點之間經過的納秒數,可表示的最長時間段約為290年。
涉及到下面這些常量:
const ( NanosecondDuration = 1 Microsecond= 1000 * Nanosecond Millisecond= 1000 * Microsecond Second= 1000 * Millisecond Minute= 60 * Second Hour= 60 * Minute )
程式執行時間
用time.Now()獲取程式執行之前和執行之後的時間。然後用Sub方法計算出這2個時間之間間隔的時間(時間段),最後打印出來。
另外還有個方法,就是直接獲取兩個時間點的時間戳的納秒數,然後相減得到的就是執行時間的納秒數。
package main import ( "fmt" "time" ) func test(){ var sum int for i := 1; i <= 100; i++ { sum += i time.Sleep(2 * time.Nanosecond)// 每一步都計算後都加上2納秒的延遲 } fmt.Println(sum) } func main(){ t0 := time.Now() test() t1 := time.Now() fmt.Printf("程式執行的時間是: %v\n", t1.Sub(t0)) t := t1.Sub(t0) fmt.Println("換算成微秒", float64(t) / float64(time.Microsecond), "微秒") // 另外一個方法 nt0 := time.Now().UnixNano()// 當前時間戳的納秒數 test() nt1 := time.Now().UnixNano() fmt.Println("程式執行時間是:", nt1 - nt0, "納秒") } /* 執行結果 H:\Go\src\go_dev\day3\time>go run sub_time.go 5050 程式執行的時間是: 148.3263ms 換算成微秒 148326.3 微秒 5050 程式執行時間是: 150043400 納秒 H:\Go\src\go_dev\day3\time> */
做單位換算的時候,需要注意資料型別。
參與除法的兩個數如果資料型別不同會報錯。或者像n / 1000
這樣,n是變數一定是有型別的,1000的資料型別會根據n的型別做型別推導。
如果是int型別相除,得到的結果也是int型別。如果要保留小數,就要浮點型別,比如float64。不能對結果做型別轉換(此時小數位已經丟失了),而是要對做參與除法的int型別做型別轉換。
指標型別
普通型別,變數存的就是值,也叫值型別。
獲取變數的地址,用&。比如:var a int
,獲取a的地址:&a
指標型別,變數存的是一個地址,這個地址存的才是值
獲取指標型別所指向的值,用*。比如:var p *int
,獲取p指向的值:*p
package main import "fmt" func main() { var a int=5 fmt.Println("變數a的地址:", &a) var p *int = &a// 定義指標型別p,p的值就是a的地址 fmt.Println("通過p拿到a的值:", *p)// 要獲取p指向的值,用*p *p = 100// 通過*p修改值 fmt.Println("a現在的值是:", a)// 列印確認,上面改的就是a的值 } /* 執行結果 H:\Go\src\go_dev\day3\pointer>go run check_pointer.go 變數a的地址: 0xc04204e058 通過p拿到a的值: 5 a現在的值是: 100 H:\Go\src\go_dev\day3\pointer> */
流程控制
if / else 分支判斷
語法:
// 第一種 if condition1 { } // 第二種 if condition1 { } else { } // 第三種 if condition 1 { } else if condition2 { } else if condition3 { } else { }
switch case 語句
語法:
switch var1 { case val1: case val2: case val3. val4 : default: }
go語言了,case結尾不需要加break,並且語句也不會繼續匹配後面的條件。如果需要繼續匹配之後的條件,可以新增fallthrough關鍵字
package main import "fmt" func main() { fmt.Print("請輸入:") var choices int fmt.Scanf("%d", &choices) switch choices { case 1: fmt.Println("你的選擇是一") case 2: fmt.Println("你的選擇是二") case 3, 4: fmt.Println("你的選擇是三或四") default: fmt.Println("沒變對應的選擇") } }
switch後面的表示式不是必需的。在此種情況下,整個switch結構與多個 if / else 的邏輯作用等同:
package main import "fmt" func main() { fmt.Print("請輸入分數(1-100):") var score int fmt.Scanf("%d", &score) fmt.Println(score) switch { case score > 100: fmt.Println("分數超出範圍") case score == 100: fmt.Println("你的成績是:A+滿分") case score > 90: fmt.Println("你的成績是: A") case score > 80: fmt.Println("你的成績是:B") case score > 70: fmt.Println("你的成績是:C") case score > 60: fmt.Println("你的成績是:D") case score > 0: fmt.Println("你的成績是:E") default: fmt.Println("0分或者輸入無效") } }
練習(猜數字程式)
猜數字,寫一個程式,隨機生成一個0到100的整數n。然後使用者在終端輸入數字,如果和n相等,則提示使用者猜對了。如果不相等,則提示使用者,大了還是小了。
package main import ( "fmt" "math/rand" "time" ) func create_num() int { rand.Seed(time.Now().UnixNano()) return rand.Intn(101) } func main() { n := create_num() //fmt.Println(n) var input int var flag bool = false // 迴圈結構還沒講,先用for寫個死迴圈 for { fmt.Print("請輸入數字(0-100):") fmt.Scanf("%d\n", &input) switch { case input == n: fmt.Println("猜對了", n) //break// case裡不需要break,似乎也支援寫break,不會報錯 flag = true// 上面的break是switch裡的break,無法跳出for迴圈。等switch結束後判斷flag case input > n: fmt.Println("大了,再小點") case input < n: fmt.Println("小了,再大點") } if flag { break// 這裡是switch外面,這裡的break是跳出for迴圈的 } } }
for 語句
寫法1
for 初始化語句; 條件判斷; 變數修改 { }
練習
寫一個程式,在終端列印如下圖形
A AA AAA AAAA AAAAA
下面有兩個實現方法:
package main import ( "fmt" "strings" ) // 自己的思路 func print1(tag string, n int) { for i := 1; i <= n; i++ { fmt.Println(strings.Repeat(tag, i)) } } // 講師的例子,這段是學for迴圈,這個方法把for迴圈用的更透 func print2(tag string, n int) { for i := 1; i <= n; i++ { for j := 0; j < i; j++ { fmt.Print(tag) } fmt.Println() } } func main(){ print1("A", 5) print2("B", 6) }
寫法2
for 條件 { } // 如果條件時true,就是個死迴圈 for true { } // 上面可以簡寫,省掉true for { }
寫法3
for range 用來遍歷陣列、slice、map、chan
用下面的例子舉例了,遍歷後獲得2個值,i是下標,v是值:
package main import ( "fmt" ) func main() { s := "abcdefg"// 陣列什麼的還沒學,不過字串也能遍歷 for i, v := range s { // fmt.Println(i, v)// 遍歷之後變成字元了 fmt.Println(i, string(v)) } }
break: 跳出當前迴圈
continue:結束本次迴圈,進入下一次繼續
label
for、switch 或 select 語句都可以配合標籤(label)形式的識別符號使用,即某一行第一個以冒號(:)結尾的單詞(gofmt 會將後續程式碼自動移至下一行)。標籤的名稱是大小寫敏感的,為了提升可讀性,一般建議使用全部大寫字母。
在多級巢狀語句中,非常有用。下面例子的continue後面指定了標籤,這樣就直接作為外層的for迴圈的continue操作:
package main import "fmt" func main() { LABEL1: for i := 0; i <= 5; i++ { for j := 0; j <= 5; j++ { if j == 4 { continue LABEL1 } fmt.Printf("i is: %d, and j is: %d\n", i, j) } } }
break 標籤的效果和 continue 是一樣的,差別只是break和continue的動作。
下面是goto的示例:
package main func main() { i:=0 HERE: print(i) i++ if i==5 { return } goto HERE }
上面的這種是逆向的使用goto,使用標籤和 goto 語句是不被鼓勵的。而且總有更加可讀的替代方案來實現相同的需求。
如果必須使用 goto ,應當只使用正序的標籤(標籤位於 goto 語句之後),但注意標籤和 goto 語句之間不能出現定義新變數的語句,否則會導致編譯失敗。
函式
宣告語法:func 函式名 ([引數列表]) [(返回值列表)] {}
// 沒有引數也沒有返回值 func test() { } // 有引數,沒有返回值 func test(a int, bint) { } // 有引數,有一個返回值 func test(a int, b int) int { } // 有引數,有多個返回值 func test(a int, b int) (int, int) { } // 可以省略前面的引數型別 fun test(a, b int) { }
函式的特點
Golang函式的特點:
- 不支援過載,一個包不能有兩個名字一樣的函式
- 函式是一等公民,函式也是一種型別,一個函式可以賦值給變數
- 支援匿名函式
-
多返回值
函式賦值給變數使用的示例
package main
import "fmt"
// 函式也是一種型別,下面用type做一個型別定義
// type關鍵字,什麼要做定義
// add_func,定義的名字是add_func
// func,函式也是一種型別,所以要定義的型別是函式
// 後面是這個函式型別的引數型別和返回值型別
type add_func func(int, int) int
// 寫一個函式,函式的型別和上面的自定義型別一樣
func add(a, b int) int {
return a + b
}
// 再寫一個減法的吧
func sub(a, b int) int {
return a - b
}
// 在寫一個函式,第一個引數是函式型別
// 後面的引數是要用來給引數1的函式進行計算的
func operator(op add_func, a, b int) int {
return op(a, b)
}
// 主函式
func main() {
// 第一個變數add,是一個函式
sum := operator(add, 10, 20)
fmt.Println(sum)
var ( c add_func res int ) // 函式也可以賦值給變數 c = add res = operator(c, 110, 40) fmt.Println(res) c = sub // 這次呼叫方法不變,但是變數c變數,換了一個函式進行計算 res = operator(c, 110, 40) fmt.Println(res)
}
## 引數傳遞的方式 函式引數傳遞的方式下面2種: + 值傳遞:各種基本資料型別 + 引用傳遞:map、slice(切片)、chan(管道)、指標、interface **注意:**無論是值傳遞還是引用傳遞,傳遞給函式的都是變數的副本。不過,值傳遞是值的拷貝,引用傳遞是地址的拷貝。一般來說,地址拷貝更為高效。而值拷貝取決於拷貝的物件的大小,物件越大,則效能越低。 ## 命名返回值的名字 也可以在宣告函式的時候給返回值命名: ```go func add (a, b int) (c int) { c = a + b// 這裡變數c不用宣告,可以直接使用 return// 直接return就好了,也不用說明要返回什麼 }
多返回值的函式中,更加方法:
func calc (a, b int) (sum int, avg int) { sum = a + b avg = sum / 2 return }
忽略返回值
使用下劃線(_),可以忽略不使用的返回值:
func calc (a, b int) (sum int, avg int) { sum = a + b avg = sum / 2 return } func main() { sum, _ := calc(10, 20) }
可變引數
在引數型別前加3個點,可以把引數定義為可變引數:
// 0個或多個引數 func sum(arg ...int) int { } // 1個或多個引數 func sum(a int, arg ...int) int { } // 2個或多個引數 func sum(a int, b int, c ...int) int { }
變數 arg 是一個 slice (切片)。可以理解為陣列,通過 arg[index] 訪問所有引數,通過 len(arg) 來判斷傳遞引數的個數
使用可變引數求和,可以傳1個或多個int引數:
package main import "fmt" func sum(a int, arg ...int) (res int) { res = a for i := 0; i < len(arg); i++ { res += arg[i] } return } func main() { var res int res = sum(1) fmt.Println(res) res = sum(1, 2) fmt.Println(res) res = sum(1, 2, 3) fmt.Println(res) }
defer用途
當函式返回時,會執行defer語句。因此,可以用來做資源清理
多個defer語句,按先進後出的方式執行
defer語句中的變數的值,在defer宣告時就決定了
舉例說明:
package main import "fmt" func main() { var i int = 1 // 下面這句並不立刻執行,而是等函式返回時再執行 // 但是函式的引數的值,在此時就已經決定了,也就是i=1 defer fmt.Println(i) i = 22 fmt.Println(i) } /* 執行結果 H:\Go\src\go_dev\day3\function>go run defer_demo.go 22 1 H:\Go\src\go_dev\day3\function> */
用途
- 關閉檔案控制代碼
- 操作前給資源上鎖,用defer釋放鎖
- 資料庫連線釋放
以關閉檔案控制代碼舉例:
func read () { file := open(filename)// 打開了一個檔案 defer file.Close()// 緊接著用defer關閉這個檔案 // 之後再寫各種檔案操作 // 到檔案返回時,defer就會執行,關閉這個檔案控制代碼 }
使用defer不踩坑
函式返回的過程是這樣子的:先給返回值賦值,然後呼叫defer表示式,最後才是返回到呼叫函式中。
defer表示式可能會在設定函式返回值之後,在返回到呼叫函式之前,修改返回值,使最終的函式返回值與你想象的不一致。
課後作業
一、編寫程式,在終端輸出九九乘法表
二、一個數如果恰好等於他的因子只和,那麼這個數就稱為“完數”。例如6=1+2+3。程式設計找出1000以內的所有完數。
三、輸入一個字串,判斷其是否為迴文。迴文字串是指從左到右讀和從右到左讀完全相同的字串。
四、輸入一行字元,分別統計出其中英文字母、空格、數字和其它字元的個數。
五、計算兩個大數相加的和,這兩個大數會超過int64的表示範圍。