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