go(二)
string 字符串
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
s := "Yes我愛Go語言"
fmt.Println(len(s))
for _, b := range []byte(s) {
fmt.Printf("%X ", b)
}
fmt.Println()
for i, ch := range s { // ch is a rune 4字節的 將utf-8解碼再轉成unicode
fmt.Printf("(%d %X) ", i, ch)
}
fmt.Println()
fmt.Println("Rune count:", utf8.RuneCountInString(s))
bytes := []byte(s)
for len(bytes) > 0 {
ch, size := utf8.DecodeRune(bytes) // 一個字符一個字符解
bytes = bytes[size:]
fmt.Printf("%c ", ch)
}
fmt.Println()
for i, ch := range []rune(s) {
fmt.Printf(" %d->%c ", i, ch)
}
fmt.Println()
}
strings包可以字符串的常用操作
- Fields,Split,Join
- Contains,Index
- ToLower,ToUpper
- Trim,TrimRight,TrimLeft
結構體和方法
go 語言只支持封裝,不支持多態和繼承
沒有 class,只有 struct
不需要知道是在堆創建還是在棧上創建;
package main
type TreeNode struct {
value int
left, right *TreeNode
}
func main() {
// 實例化node
var root TreeNode
root = TreeNode{value: 3}
root.left = &TreeNode{}
root.right = &TreeNode{5, nil, nil}
root.right.right = new(TreeNode{value:0}) // 不論地址還是結構,一律採用.來訪問成員
root.left.right = createNode(2)
}
//沒有構造函數,我們可以通過工廠函數來創建實例
func createNode(value int) *TreeNode {
return &TreeNode{value: value}
}
- 要改變內容必須使用指針接收者
- 結構過大也考慮使用指針接收者,必須複製一份數據是要佔用資源的
值接收者纔是 go 語言所特有的
封裝
- 首字母大寫 public
- 首字母小寫 private
- 包是根據目錄(目錄和包名可以不一致,但一個目錄一個包名即一個目錄只能放一個包),main 包包含可執行的入口
- 爲結構定義的方法必須放在同一個包內,但可以分不同的文件
改造一下目錄結構
- tree
- entry
- entry.go
- node.go
package tree
import (
"fmt"
)
type Node struct {
Value int
Left, Right *Node
}
//沒有構造函數,我們可以通過工廠函數來創建實例
func CreateNode(value int) *Node {
return &Node{Value: value}
}
//爲結構創建方法 就可以看成是一個普通函數如func Print(node TreeNode){}
//注意值拷貝
func (node Node) Print() {
fmt.Println(node.Value)
}
//注意不寫*不無法修改node的值的,因爲是傳遞都是值拷貝,所以(node *TreeNode)
func (node *Node) SetValue(val int) {
if node == nil {
fmt.Println("Setting value to nil node. Ignored.")
return
}
node.Value = val
}
//中序遍歷 先左在中在右
func (node *Node) Traverse() {
if node==nil {
return
}
node.Left.Traverse()
node.Print()
node.Right.Traverse()
}
// entry.go
package main
import "gobasic/tree"
func main() {
// 實例化node
var root tree.Node
root = tree.Node{Value: 3}
root.Left = &tree.Node{}
root.Right = &tree.Node{5, nil, nil}
root.Right.Right = new(tree.Node) // 不論地址還是結構,一律採用.來訪問成員
root.Left.Right = tree.CreateNode(2)
//root.Print()
root.Traverse()
}
依賴此包擴展一個後序遍歷
package main
import (
"fmt"
"gobasic/tree"
)
// 小寫不給別人用
type myTreeNode struct {
node *tree.Node
}
//自定義的私有的後序遍歷
func (myNode *myTreeNode) postOrder() {
if myNode == nil || myNode.node == nil {
return
}
left := myTreeNode{myNode.node.Left} // cannot call pointer method on myTreeNode literal
right := myTreeNode{myNode.node.Right}
left.postOrder()
right.postOrder()
myNode.node.Print()
}
func main() {
// 實例化node
var root tree.Node
root = tree.Node{Value: 3}
root.Left = &tree.Node{}
root.Right = &tree.Node{5, nil, nil}
root.Right.Right = new(tree.Node) // 不論地址還是結構,一律採用.來訪問成員
root.Left.Right = tree.CreateNode(2)
//root.Print()
root.Traverse()
fmt.Println()
myRoot := myTreeNode{&root}
myRoot.postOrder()
fmt.Println()
}
包裝一個隊列
package queue
type Queue []int
func (q *Queue) Push(v int) {
//q是指針 *q 是指向的object本身,下面就是改變了對象本身
*q = append(*q,v)
}
func (q *Queue) Pop() int {
head:=(*q)[0]
*q = (*q)[1:]
return head
}
func (q *Queue) IsEmpty() bool {
return len(*q) == 0
}
GOPATH 環境變量
默認~/go(unix,linux) %USERPROFILE%\go(windows)
官方推薦:所有項目和第三方庫都放在同一個 GOPATHG 下,也可以將每個項目放在不同的 GoPATH
- go get 獲取第三方庫
- go get -v github.com/gpmgo/gopm 可以用來拉取 google 服務器上的資源,之前通過 dep 的翻牆來實現(夠 2B)
- 這樣我們就可以得到
goimportsgopm get -g -v golang.org/x/tools/cmd/goimports因爲之前用的是 go get 或 dep 翻牆安裝的,所以安裝沒成功,提示已經有一個 golang.org 的庫存在 - 第一個 main 文件都放在一個單獨目錄中
接口
duck typing
使用者來定義接口
// main.go
package main
import (
"fmt"
"gobasic/retriever/moke"
real2 "gobasic/retriever/real"
)
type Retriever interface {
Get(url string) string
}
func download(r Retriever) string {
return r.Get("www.baidu.com")
}
func main() {
var r Retriever
r = moke.Retriever{"====This is a fake content==="}
fmt.Println(download(r))
r = real2.Retriever{}
fmt.Println(download(r))
}
// moke/mockretriever.go 有Get方法(返回string)就說明實現了這個接口
package moke
type Retriever struct {
Contents string
}
func (r Retriever) Get(url string) string {
return r.Contents
}
// real/reltriever.go
package real
import (
"net/http"
"net/http/httputil"
"time"
)
type Retriever struct {
UserAgent string
TimeOut time.Duration
}
func (r Retriever) Get(url string) string {
//panic("implement me")
resp, err := http.Get("http://www.baidu.com")
if err != nil {
panic(err)
}
result, err := httputil.DumpResponse(resp, true)
resp.Body.Close()
if err != nil {
panic(err)
}
return string(result)
}
接口變量裏指向什麼
- 接口變量
- 實現者類型
- 實現者指針-->實現者
接口變量自帶指針,接口變量同樣採用值傳遞,幾乎不需要使用接口的指針;指針接收者只能實現指針方式,值接收者可以會用值或地址
任何類型我們可以指定它爲 interface{}
查看接口變量
- 表示任何類型 interface{}
- Type Assertion
- Type Switch
go 的一些標準接口
- Stringer
- Reader
- Writer
函數式編程
- 函數是一等公民,參數,變量,返回值都可以是函數
- 高階函數
- 函數-->閉包
正統函數式編程:不可變性(不能有狀態,只有常量和函數),函數只能有一個參數,
package main
import "fmt"
func adder() func(int) int {
sum:=0
return func(i int) int {
sum+=i
return sum
}
}
func main() {
a:=adder()
for i:=0;i<=10;i++{
fmt.Printf("0+1+...+%d = %d\n",i,a(i))
}
}
斐波那切
package main
import "fmt"
// 1,1,2,3,5,8,13,21
// a,b
// a,b
func fibonacci() func() int {
a,b:=0,1
return func() int {
a,b=b,a+b
return a
}
}
func main() {
f:= fibonacci()
for i:=0;i<10;i++ {
fmt.Printf("%v \t",f())
}
}
對閉包的應用
- 更爲自然,不需要修飾如何訪問自由變量
- 沒有 lambda 表達式,但有匿名函數
改造tree node
package tree
import "fmt"
//中序遍歷 先左在中在右
func (node *Node) Traverse() {
/*if node==nil {
return
}
node.Left.Traverse()
//只能作打印,
node.Print()
node.Right.Traverse()*/
node.TraverseFunc(func(n *Node) {
n.Print()
})
fmt.Println()
}
//改造一下
func (node *Node) TraverseFunc(f func(*Node)) {
if node == nil {
return
}
node.Left.TraverseFunc(f)
f(node)
node.Right.TraverseFunc(f)
}
defer
資源管理比如文件打開和關閉,資源管理和出錯處理,defer是確保函數結束時發生
package main
import "fmt"
func tryDefer() {
// 不怕中間 panic 或return
defer fmt.Println(1)
defer fmt.Println(2)
fmt.Println(3)
panic("error occoured") // return 正常的程序不要panic而是要處理panic
fmt.Println(4)
}
func main() {
tryDefer()
}
將fibonacci數列寫到fib.txt中
package main
import (
"bufio"
"fmt"
"gobasic/functional/fib"
"os"
)
func WriteFile(filename string) {
file, err := os.Create(filename)
if err != nil {
panic(err)
}
defer file.Close() //關閉文件
// 先把內容寫到buffer中
writer:=bufio.NewWriter(file) //打開一個資源,就想著把它關閉掉
defer writer.Flush() // 就近寫一句關閉
f:=fib.Fibonacci()
for i:=0;i<20;i++{
fmt.Fprintln(writer,f())
}
}
func main() {
//tryDefer()
WriteFile("fib.txt")
}
return 或 panic或函數執行結束它都會執行
defer file.Close()
defer是一個棧
確保調用在函數結束時發生
參數在defer語句時計算,而不是在退出時計算
defer列表爲後進先出
- 何時調用
- Open/Close
- Lock/Unlock
- PrintHeader/PrintFooter
錯誤處理
panic實際上是讓程序掛掉,爲了避免這種掛掉我們可以把錯誤日誌打印出來,並return,err只是一個值,
package main
import (
"bufio"
"fmt"
"gobasic/functional/fib"
"iris_source/core/errors"
"os"
)
func WriteFile(filename string) {
//file, err := os.Create(filename)
/*
O_CREATE int = syscall.O_CREAT // 如果不存在將創建一個新文件
O_EXCL int = syscall.O_EXCL // 和O_CREATE配合使用,文件必須不存在
*/
file, err := os.OpenFile(filename, os.O_EXCL|os.O_CREATE, 0666)
//自己聲明的error
err = errors.New("this is a custom error") // 自己聲明的不是一個pathError就走panic了
if err != nil {
//panic(err) //掛掉會很難看
//fmt.Println("file already exists.")
//fmt.Println("Error: ", err.Error())
if pathError, ok := err.(*os.PathError); !ok {
panic(err) //真的不知道是什麼錯誤,我們就panic吧
} else {
fmt.Printf("%s,%s,%s\n", pathError.Op, pathError.Path, pathError.Err)
}
return
}
writer := bufio.NewWriter(file)
defer file.Close() //關閉文件
// 先把內容寫到buffer中,函數執行完需,要執行defer的棧
defer writer.Flush()
f := fib.Fibonacci()
for i := 0; i < 20; i++ {
fmt.Fprintln(writer, f())
}
}
func main() {
WriteFile("fib.txt")
}
統一錯誤處理
package main
import (
"io/ioutil"
"net/http"
"os"
)
func main() {
http.HandleFunc("/list/", func(writer http.ResponseWriter, request *http.Request) {
path:= request.URL.Path[len("/list/"):]
file,err:=os.Open(path)
if err != nil{
panic(err)
}
defer file.Close()
all,err:=ioutil.ReadAll(file)
if err!=nil {
panic(err)
}
writer.Write(all)
})
err:=http.ListenAndServe(":8888",nil)
if err!=nil {
panic(err)
}
}
當你輸入個錯誤的網址或不是
/list/
//filelistingserver/web.go
package main
import (
"gobasic/errhandling/filelistingserver/filelisting"
"net/http"
"os"
"github.com/gpmgo/gopm/modules/log"
)
//定義一個類型
type appHandler func(writer http.ResponseWriter, request *http.Request) error
func errWrapper(handler appHandler) func(writer http.ResponseWriter, request *http.Request) {
return func(writer http.ResponseWriter, request *http.Request) {
err := handler(writer, request)
if err != nil {
log.Warn("Error handling request %s",err.Error())
code := http.StatusOK
switch {
case os.IsNotExist(err):
code = http.StatusNotFound
case os.IsPermission(err):
code = http.StatusForbidden
default:
code = http.StatusInternalServerError
}
http.Error(writer, http.StatusText(code), code)
}
}
}
func main() {
http.HandleFunc("/list/", errWrapper(filelisting.HandleFileList))
err := http.ListenAndServe(":8888", nil)
if err != nil {
panic(err)
}
}
// filelistingserver/filelisting/handler.go 類似controller
panic&recover
停止當前函數執行,一直向上返回,執行每一層的defer,如果沒有遇見recover,程序退出;
recover僅在defer調用中使用,獲取panic的值,如果處理不了,可以再次panic
package main
import (
"fmt"
)
func tryRecover() {
defer func() {
r:=recover()
if err,ok:=r.(error);ok{
fmt.Println("Error occurred: ",err)
}else{
panic((r))
}
}()
//panic(errors.New("this is an error."))
//panic(123) 123不是一個error,就是觸發repanic
b:=0
a:=5/b
fmt.Println(a)
}
func main() {
tryRecover()
}
我們將請請求路徑改成/ 而不是/list/會出現什麼問題
func main() {
http.HandleFunc("/", errWrapper(filelisting.HandleFileList))
err := http.ListenAndServe(":8888", nil)
if err != nil {
panic(err)
}
}
// runtime error: slice bounds out of range
// handler
package filelisting
import (
"io/ioutil"
"iris_source/core/errors"
"net/http"
"os"
"strings"
)
const prefix = "/list/"
func HandleFileList(writer http.ResponseWriter, request *http.Request) error { //返回error 有錯誤交給上層去處理,返回就好了
if strings.Index(request.URL.Path, prefix) != 0 {
return errors.New("path must start with " + prefix)
}
path := request.URL.Path[len(prefix):]
file, err := os.Open(path)
if err != nil {
//panic(err) //這樣雖然程序沒有掛掉,但對用戶的提示不是很友好,我們可以進一步優化
//http.Error(writer,err.Error(),http.StatusInternalServerError) // 這樣也不合適,我們要把錯誤分爲內部和用戶顯示兩部分
return err
}
defer file.Close()
all, err := ioutil.ReadAll(file)
if err != nil {
//panic(err)
return err
}
writer.Write(all)
return nil
}
// web
package main
import (
"gobasic/errhandling/filelistingserver/filelisting"
"log"
"net/http"
"os"
)
//定義一個類型
type appHandler func(writer http.ResponseWriter, request *http.Request) error
func errWrapper(handler appHandler) func(writer http.ResponseWriter, request *http.Request) {
return func(writer http.ResponseWriter, request *http.Request) {
err := handler(writer, request)
defer func() {
if r:=recover();r!=nil {
//log.Error("panic: %v", r)
log.Printf("panic: %v",r)
http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
}()
if err != nil {
//log.Warn("Error handling request %s",err.Error())
log.Printf("Error handling request %s",err.Error())
code := http.StatusOK
switch {
case os.IsNotExist(err):
code = http.StatusNotFound
case os.IsPermission(err):
code = http.StatusForbidden
default:
code = http.StatusInternalServerError
}
http.Error(writer, http.StatusText(code), code)
}
}
}
func main() {
http.HandleFunc("/", errWrapper(filelisting.HandleFileList))
err := http.ListenAndServe(":8888", nil)
if err != nil {
panic(err)
}
}
區分給用戶看的,和不能給用戶看的
package main
import (
"gobasic/errhandling/filelistingserver/filelisting"
"log"
"net/http"
"os"
)
//定義一個類型
type appHandler func(writer http.ResponseWriter, request *http.Request) error
func errWrapper(handler appHandler) func(writer http.ResponseWriter, request *http.Request) {
return func(writer http.ResponseWriter, request *http.Request) {
err := handler(writer, request)
defer func() {
if r:=recover();r!=nil {
//log.Error("panic: %v", r)
log.Printf("panic: %v",r)
http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
}()
if err != nil {
//log.Warn("Error handling request %s",err.Error())
log.Printf("Error handling request %s",err.Error())
//處理userError
if userError,ok:=err.(userError);ok{
http.Error(writer,userError.Message(),http.StatusBadRequest)
return
}
code := http.StatusOK
switch {
case os.IsNotExist(err):
code = http.StatusNotFound
case os.IsPermission(err):
code = http.StatusForbidden
default:
code = http.StatusInternalServerError
}
http.Error(writer, http.StatusText(code), code)
}
}
}
//定義用戶看的錯誤
type userError interface {
error //系統看的
Message() string //給用戶看的
}
func main() {
http.HandleFunc("/", errWrapper(filelisting.HandleFileList))
err := http.ListenAndServe(":8888", nil)
if err != nil {
panic(err)
}
}
// handler.og
package filelisting
import (
"io/ioutil"
"net/http"
"os"
"strings"
)
const prefix = "/list/"
type userError string
func (e userError) Error() string {
return e.Message()
}
func (e userError) Message() string {
return string(e)
}
func HandleFileList(writer http.ResponseWriter, request *http.Request) error { //返回error 有錯誤交給上層去處理,返回就好了
if strings.Index(request.URL.Path, prefix) != 0 {
//return errors.New("path must start with " + prefix)
return userError("path must start with " + prefix)
}
path := request.URL.Path[len(prefix):]
file, err := os.Open(path)
if err != nil {
//panic(err) //這樣雖然程序沒有掛掉,但對用戶的提示不是很友好,我們可以進一步優化
//http.Error(writer,err.Error(),http.StatusInternalServerError) // 這樣也不合適,我們要把錯誤分爲內部和用戶顯示兩部分
return err
}
defer file.Close()
all, err := ioutil.ReadAll(file)
if err != nil {
//panic(err)
return err
}
writer.Write(all)
return nil
}
接口定義是各自用各自的,不需要互相看得到
意料之中的:使用error。如:文件打不開,意料之外的:使用panic。如數組越界
主題測試文章,只做測試使用。發佈者:Walker,轉轉請注明出處:https://walker-learn.xyz/archives/6734