@octopus
2019-03-11T03:51:09.000000Z
字数 22776
阅读 15591
go
变量要先声明,再赋值
// 声明:var a int // 声明 int 类型的变量var b [10] int // 声明 int 类型数组var c []int // 声明 int 类型的切片var d *int // 声明 int 类型的指针// 赋值:a = 10b[0] = 10// 同时声明与赋值var a = 10a := 10a,b,c,d := 1,2,true,"def"
:=这个符号直接取代了var和type,这种形式叫做简短声明。不过它有一个限制,那就是它只能用在函数内部;在函数外部使用则会无法编译通过,所以一般用var方式来定义全局变量。
const filename = "ab"const a,b = 3,4 // 常量可作为各种类型调用,此处即可用作int类型,也可用作 floatconst(java = 1php = 2python = 3)const(java = iota // 自增值,初始为0phppython)
if a > 100 {return 100}else if a >50 {return 50}else{return 0}if a,b := 1,2; a+b>3{fmt.Println(a,b)}fmt.Println(a,b) // 错误! a,b的是 if 条件里定义的,作用域仅限于 if 中使用
利用 if 语句判断读取文件时是否有异常
import ("io/ioutil""fmt")func main() {const filename = "2.txt"content, err := ioutil.ReadFile(filename)if(err!=nil){fmt.Println(err)}else {fmt.Printf("%s", content)}}
go中的 switch 不需要手动 break
var grade string = "B"switch marks {case 90: grade = "A"case 80: grade = "B"case 50,60,70 : grade = "C"default: grade = "D"}switch {case grade == "A" :fmt.Printf("优秀!\n" )case grade == "B", grade == "C" :fmt.Printf("良好\n" )case grade == "D" :fmt.Printf("及格\n" )case grade == "F":fmt.Printf("不及格\n" )default:fmt.Printf("差\n" );}fmt.Printf("你的等级是 %s\n", grade );
// 赋值语句;判断语句;递增语句for i:=100; i>0; i--{fmt.Println(i)}// 无赋值func test(n int){for ; n>0 ; n/=2 {fmt.Println(n);}}// 仅赋值scanner := bufio.NewScanner(file)for scanner.Scan(){fmt.Println(scanner.Text);}// 死循环for{fmt.Println(1);}
func eval(a,b int, s string) int{ ... }
// 当有返回多个值时func test1(a,b int) (int, int){return a+b, a*b}// 为多个返回值起名字(仅用于简单函数)func test2(a,b int) (q, r int){q = a+br = a*breturn // 自动对号入座返回相应变量}q, r := test2(1,2)// 输出错误func test(a,b int)(int, error) {if a+b>100{return a+b, fmt.Errorf("%s","error!")}else{return a+b, nil}}
go语言的参数传递是值传递
func main() {a,b:=1,2swap_test(&a,&b)}func swap_test(a,b *int) {fmt.Println(a, *b) // 0xc420014050 2}// 理解: a,b *int 存的是 int 类型值的地址,当对指针类型的变量 *a 时,就是取出地址对应的值
var arr [3]int // 会初始化为 [0,0,0]arr := [3]{1,2,3}arr := [...]{1,2,3,4,5}arr := [2][4]int // 2行4列
arr := [3]{1,2,3}for k,v := range(arr){fmt.Println(k, v) // 0,1 1,2 2,3}
注意,[5]int 与 [10]int 是不同的类型
go 语言一般不直接使用数组,而是使用切片
func printArr(arr [5]int) {for k,v:=range (arr){fmt.Println(k,v)}}func main() {arr :=[5] int {6,7,8,9,10}printArr(arr)}
// 前开后闭arr := [...]{0,1,2,3,4,5,6,7}arr1 := arr[1:2] // 1arr2 := arr[:5] // 0,1,2,3,4arr3 := arr[2:] // 2,3,4,5,6,7arr4 := arr[:] // 0,1,2,3,4,5,6,7
切片是数组的“视图”,即引用
func updateArr(arr []int) { // []中不写具体大小,表示是切片,引用传递arr[0] = 100}func main() {arr :=[5] int {0,1,2,3,4}arr1 := arr[1:3]fmt.Println(arr,arr1) // [0 1 2 3 4] [1 2]updateArr(arr1)fmt.Println(arr,arr1) // [0 100 2 3 4] [100 2]}

// 切片的切片依然是对一个数组的引用func updateArr(arr []int) {arr[0] = 100}func main() {arr :=[5] int {0,1,2,3,4}arr1 := arr[1:3]arr2 := arr1[0:3]fmt.Println(arr,arr1,arr2) // [0 1 2 3 4] [1 2] [1 2 3]updateArr(arr1)fmt.Println(arr,arr1,arr2) // [0 100 2 3 4] [100 2] [100 2 3]}// 查看扩展arr :=[5] int {0,1,2,3,4}arr1 := arr[1:3]arr2 := arr1[0:3]fmt.Println(arr1,len(arr1),cap(arr1)) // [1 2] 2 4fmt.Println(arr2,len(arr2),cap(arr2)) // [1 2 3] 3 4
1) s := []int{1,2,3}2) var s []int // 会初始化为 nil3) s := make([]int, 16, 32) // make(切片类型,切片长度,切片cap长度)
// 若添加元素个数不超过cap值,则对原数组进行修改arr :=[5] int {0,1,2,3,4}arr1 := arr[1:3]arr2 := append(arr1, 10, 11)fmt.Println(arr1,arr2,arr) // [1 2] [1 2 10 11] [0 1 2 10 11]// 若添加元素个数超过cap值,则开辟新数组,拷贝并添加arr :=[5] int {0,1,2,3,4}arr1 := arr[1:3]arr2 := append(arr1, 10, 11, 12)fmt.Println(arr1,arr2,arr) // [1 2] [1 2 10 11 12] [0 1 2 3 4]func main() {var s []intfor i:=0; i<10; i++ {s = append(s,i)fmt.Println(s, cap(s))}}结果:(当cap超出,就会重新分配cap值更大的新数组)[0] 1[0 1] 2[0 1 2] 4[0 1 2 3] 4[0 1 2 3 4] 8[0 1 2 3 4 5] 8[0 1 2 3 4 5 6] 8[0 1 2 3 4 5 6 7] 8[0 1 2 3 4 5 6 7 8] 16[0 1 2 3 4 5 6 7 8 9] 16
s1 := []int{0,1,2,3}s2 := make([]int, 6)copy(s2,s1)fmt.Println(s1,s2) // [0 1 2 3] [0 1 2 3 0 0]
1) m := map[string]int{} // nil2) var m map[string]string // nil3) m := make(map[string]string) // empty mapm2 := map[string]string{"name":"zy","age":"10",}fmt.Println(m2) // map[name:zy age:10]
map 是无序的hash map,所以遍历时每次输出顺序不一样
m := map[string]string{"name":"zy","age":"10",}for k,v := range m{fmt.Println(k,v)}
m := map[string]string{"name":"zy","age":"10",}name := m["name"]fmt.Println(name) // “zy”// 获取一个不存在的值value := m["aaa"]fmt.Println(value) // “” 返回一个空值// 判断key是否存在value, ok := m["aaa"]fmt.Println(value, ok) // "" false// 标准用法:if v,ok:= m["name"]; ok{fmt.Println(v)}else{fmt.Println("key not exist!")}
delete(m, "name")
go语言的面向对象仅支持封装,不支持继承和多态
// 定义一个结构体type treeNode struct {value intleft,right *treeNode}func main() {root := treeNode{1,nil,nil}node1 := treeNode{value:3}root.left = &node1root.left.right = new(treeNode) // 内建函数初始化nodenodes := []treeNode{{1,nil,nil},{2,&root,&node1},}fmt.Println(nodes[1].left.left.value) // 3}
由于没有构造函数,所以可以用工厂函数代替
func createNode(value int) *treeNode {return &treeNode{value:value}}func main(){node := createNode(10)fmt.Println(node) // &{10 <nil> <nil>}
结构体方法并不是写在结构体中,而是像函数一样写在外面,它实际上久是定义了[接收对象]的函数
由于本质依然是函数,所以也是按值传递,若要改变对象,用指针
type treeNode struct {value intleft,right *treeNode}// func (接收对象) 方法名(参数) 返回值{}func (node treeNode) get() int{return node.value}func (node *treeNode) set(value int) {node.value = value}func main() {root := treeNode{2,nil,nil}res := root.get()fmt.Println(res) // 2}
- 名字一般用 CamelCase
- 首字母大写是 public 方法
- 首字母小写是 private 方法(2和3 对于变量常量也依然适用)
每个目录只有一个包 (package)
main包包含程序入口
为某结构体定义的方法必须放在同一个包内,但可以放不同文件
/* 目录结构go|__tree1 // 为了讲解,目录名,文件名,包名不同|——treeNode.go|__main|_main.go*/// treeNode.gopackage treetype TreeNode struct { // 外部可用的结构体value int // 私有属性left TreeNoderight TreeNode}func (node TreeNode) Get() int{ // 公有方法return node.Value}func (node *treeNode) Set(value int) {node.value = value}// main.gopackage mainimport "../../tree1"func main() {node := tree.TreeNode{}node.Set(2)res := node.Get(); // 2}// 总结:import 包所属目录名,就可以使用 包名.结构体/方法 ,与目录下的文件名无关
go语言没有继承,如何扩展系统类型或者自定义类型呢?
- 定义别名
- 使用组合
// vim .bash_profileexport GOPATH=/Users/yuzhang/goPATH=$PATH:/usr/local/mysql/bin:$GOPATH/bin> source .bash_profile> go install ./... // 将本目录下所有的go文件编译成可执行文件,放在 $GOPATH/bin 下
go get xxx 从官方下载第三方库,需要翻墙
gopm 可以获取国内镜像
// 首先需要下载 go // -v 表示详细信息,可省略2. 将 gopm 可执行文件放在 path 环境变量可找到地方3. gopm get -g -v golang.org/x/tools/cmd/goimports4. go install golang.org/x/tools/cmd/goimports
type xxx interface{FuncName() string // 定义接口方法与返回类型}
结构体不需要显示“实现”接口,只要定义好接口方法即可
// interface/interface.gopackage filetype File interface {Read() stringWrite(str string)}// interface/implament.go// File1 结构体实现了接口规定的方法package filetype File1 struct {Content string}func (file File1) Read() string{return file.Content}func (file *File1) Write(str string) {file.Content = str}// interface/entry/main.gopackage mainimport ("../../interface""fmt")func get(f file.File) string { // 只有实现了 File 接口的结构体实例才能调用此方法res := f.Read()return res}func main() {f := file.File1{}f.Write("www")fmt.Println(get(f))}
查看类型: i.(type)
var i AnimalInterface // 定义变量 i 是动物接口类型i = Cat{"cat"} // 假设 Cat 结构体实现了 AnimalInterface 接口i.(type) // Cati = Dog{"dog"} // 假设 Dog 结构体实现了 AnimalInterface 接口i.(type) // Dog
约束接口类型:i.(xxx)
var i AnimalInterface // 定义变量 i 是动物接口类型i = Cat{"cat"} // 假设 Cat 结构体实现了 AnimalInterface 接口cat := i.(Cat) // 如果 i 是Cat类型的,则拷贝赋值给 cat变量,否则报错)if dog, ok := i.(Dog); ok{// ok}else{// i isn't dog}
泛型: interface{}
type Queue []int // 定义了一个 int 类型的切片func (q *Queue) Push(v int){*q = append(*q, v)}func (q *Queue) Pop() int{head := (*q)[0]*q = (*q)[1:]return head}// 将上面的切片改成可以接受任意类型:type Queue []interface{} // 定义了一个 int 类型的切片func (q *Queue) Push(v interface{}){*q = append(*q, v)}func (q *Queue) Pop() interface{}{head := (*q)[0]*q = (*q)[1:]return head}// 强制类型转换:head.(int)
type Cat interface{cat()}type Dog interface{dog()}type Animal interface{ // 要实例既实现 Cat 又实现 DogCatDog}func get(i Animal){i.cat()i.dog()}
// 1. 类似 toString() 的信息打印接口type Stringer interface{String() string}// 2. Readertype Reader interface{Read(p []byte) (n int, err error)}// 3. Writertype Writer interface{Write(p []byte) (n int, err error)}
func add() func(int) int {sum := 0 // 此处的 sum 为自由变量return func(v int) int {sum += v // 指向外层sumreturn sum}}func main(){add := add()for i:=0; i<10; i++ {fmt.Printf(add(i)) // 从 0 到 10 的累加}}
// 一个斐波那契数列的生成器func fib() func() int{a, b := 0, 1return func() int{a, b = b, a+breturn a}}func main(){f = fib()f() // 1f() // 1f() // 2f() // 3}
package mainimport ("io""bufio""fmt""strings")type funcType func() intfunc fib() funcType{a, b := 0, 1return func() int{a, b = b, a+breturn a}}func (f funcType) Read(p []byte) (n int, err error) {next := f()if next > 1000 {return 0, io.EOF}s := fmt.Sprintf("%d ", next)return strings.NewReader(s).Read(p)}func scan(read io.Reader) {scanner := bufio.NewScanner(read)for scanner.Scan() {text := scanner.Text()fmt.Printf(text)}}func main() {f := fib()scan(f)}
有 defer 关键字修饰的语句,会在 return 前执行
若有多个 defer 则保持栈的性质,先进后出,最先定义的最后执行
func writeFile(filename string) {file, err := os.Create(filename)if err!=nil {panic("创建文件失败!") // 打印错误信息}defer file.Close() // 函数执行完毕前。关闭文件句柄writer := bufio.NewWriter(file)defer writer.Flush() // 函数执行完毕前,将缓冲区中的内容刷新到文件中去for i:=0; i<100; i++ {fmt.Fprintln(writer,i) // 写入缓冲区}}func main() {writeFile("1.txt")}
panic:
1. 停止当前函数执行
2. 停止之前执行每层的 defer
3. 如果没有 recover 程序直接退出
recover:
1. 仅在 defer 中调用
2. 可以获取 panic 的值
3. 如果无法处理,可重新 panic
(个人理解就像 try cache 中的 cache 可以捕获异常)
// 例一:捕获 panicfunc dopanic(){defer func() {err := recover()fmt.Println(err) // error!!}()panic("error!!")}func main() {dopanic()}// 例二:捕获其他异常func dopanic(){defer func() {err := recover()fmt.Println(err) // runtime error: integer divide by zero}()a := 0b := 1/afmt.Println(b)}func main() {dopanic()}// 例三:无异常处理func dopanic(){defer func() {err := recover()if err, ok := err.(error); ok{fmt.Println(err)}else{fmt.Println("no error!")}}()a := 0fmt.Println(a)}func main() {dopanic()}
go func(){}()
用 go 关键字开启协程,协程是非抢占式多任务处理,由协程主动交出控制权
func main() {for i:=0; i<1000; i++{go func(i int) {for {fmt.Println(i); // IO 操作,会主动交出控制权}}(i)}time.Sleep(time.Microsecond);}// 如果没有 time.Sleep,在for执行完 i 的自增后就会立刻结束程序,此时协程还没来得及处理就结束了,没有任何打印
func main() {var arr [10]intfor i:=0; i<1000; i++{go func(i int) {for {arr[i]++ // 不会交出控制权,所以会在这里死循环}}(i)}time.Sleep(time.Microsecond)}
goroutine可能交出控制权的点:
- I/O操作,select
- channel
- 等待锁
- 函数调用时(有时,不一定)
- runtime.Gosched()
channel其实就是传统语言的阻塞消息队列,可以用来做不同goroutine之间的消息传递
- 声明一个channel:
var c chan int
c := make(chan int)
- 使用 channel:
c <- 1 // 向管道中写入
d := <-c // 从管道读出
func worker(c chan int) {for {r := <-cfmt.Println(r)}}func main() {c := make(chan int)go worker(c)c <- 1c <- 2time.Sleep(time.Microsecond)}
func workerFactory(i int) chan int{c := make(chan int)go func(c chan int) {fmt.Println( <-c )}(c)return c}func main() {var arr [10] chan intfor i:=0; i<10 ; i++ {arr[i] = workerFactory(i) // 创建 channel 并监听}for i:=0; i<10 ; i++ {arr[i] <- i // 向各channel中输入}time.Sleep(time.Microsecond)}
func workerFactory() chan <- int{ // 返回的是只写channelc := make(chan int)go func(c chan int) {fmt.Println( <-c )}(c)return c}c := workerFactory(0)r := <- c // 报错!// 同理, `<- chan int` 是只读channel
向管道中写入就必须定义相应的输出,否则会报错
有缓冲区与无缓冲区的区别是 一个是同步的 一个是非同步的,即阻塞型队列和非阻塞队列 详解:https://blog.csdn.net/samete/article/details/52751227
c := make(chan int, 3) // 缓冲区长度3c<-1c<-2c<-3 // 在这之前都在缓冲区中,不报错c<-4 // 报错
当确定不再向缓冲区中发送数据,可以关闭管道,若还有协程在不断接收 管道数据,则会源源不断的收到 0 即空串,直到程序结束。
func workerFactory() chan int{c := make(chan int, 3)go func(c chan int) {for {fmt.Println( <-c ) // 一直读取管道}}(c)return c}func main() {var c chan intc = workerFactory()c <- 1c <- 2close(c) // 关闭管道time.Sleep(time.Second)}// 结果:// 1,2,0,0,0,0,0...// 改进:for {n, ok := <-cif !ok {break}fmt.Println( n )}// 或for n := range c{fmt.Println( n )}
上面的例子使用 time.Sleep(time.Microsecond)来等待任务结束,不精确且耗时
package mainimport ("fmt")type worker struct {in chan int // in 用来读写done chan bool // done 用来表示已读取完成}func createWorker() worker{worker := worker{in:make(chan int),done:make(chan bool),}doWorker(worker);return worker}func doWorker(w worker) {go func(w worker) {for {fmt.Println(<-w.in)go func(w worker) { // 注意此处也要 go,不然阻塞w.done<-true}(w)}}(w)}func main() {var arr [10] workerfor i:=0; i<10; i++ {arr[i] = createWorker()}for i:=0; i<10; i++ {arr[i].in<-i}for i:=0; i<10; i++ {arr[i].in<-i}for i:=0; i<10; i++ {<-arr[i].done<-arr[i].done}}
package mainimport ("fmt""sync")type worker struct {in chan intwg *sync.WaitGroup // *}func createWorker(wg *sync.WaitGroup) worker{worker := worker{in:make(chan int),wg:wg,}doWorker(worker);return worker}func doWorker(w worker) {go func(w worker) {for {fmt.Printf("%c \n", <-w.in)w.wg.Done() // 发送任务结束的信号}}(w)}func main() {var wg sync.WaitGroup // 定义WaitGroupvar arr [10] workerfor i:=0; i<10; i++ {arr[i] = createWorker(&wg) //按址传递,用一个引用来开始和结束}for i:=0; i<10; i++ {wg.Add(1) // 开始一个任务前,计时器加一(一定要在开始前加)arr[i].in <- 'a'+i}for i:=0; i<10; i++ {wg.Add(1)arr[i].in <- 'A'+i}wg.Wait() // 阻塞等待所有任务 done}
func main() {var c1,c2 chan intselect {case n:=<-c1:fmt.Println(n)case n2:=<-c2:fmt.Println(n2)default:fmt.Println("no value") // ✔️}}
func create(i int) chan int{c := make(chan int)go func(c chan int, i int) {for {c <- 'a'+i}}(c,i)return c}func main() {c1,c2 := create(1),create(2)for {select { // 由于没有 default,会一直阻塞读case n1 := <-c1:fmt.Printf("%c \n",n1)case n2 := <-c2:fmt.Printf("%c \n",n2)}}}
func main() {c1,c2 := create(1),create(2)tm := time.After(2*time.Second) // 定时器,2秒后触发tm2 := time.Tick(1*time.Second) // 每1秒触发一次for {select {case n1 := <-c1:fmt.Printf("%c \n",n1)case n2 := <-c2:fmt.Printf("%c \n",n2)case t := <- tm2:fmt.Println("------- ",t," -----------")case <- tm:fmt.Println("bye")return}}}
查看标准库文档的方式:
1. 自己启动一个文档服务器: > godoc -http :8888 然后访问 localhost:8888
2. 标准库中文版:https://studygolang.com/pkgdoc
func main() {res, err := http.Get("https://www.baidu.com/"); // 返回响应对象指针if err != nil {panic(err)}defer res.Body.Close() // 最后关闭 响应 资源str, err := httputil.DumpResponse(res, true)if err != nil {panic(err)}fmt.Printf("%s", str)}resp, err := http.Get("http://example.com/") // GETresp, err := http.Post("http://example.com/") // POSTresp, err := http.PostForm("http://example.com/", url.Values{"foo": "bar"}) // 提交表单
func main() {request, err := http.NewRequest(http.MethodGet,"https://www.baidu.com/", nil); // 返回请求对象指针request.Header.Add("User-Agent","Mozilla/5.0 ...") // 添加头信息res, err := http.DefaultClient.Do(request) // 默认简易版客户端访问if err != nil {panic(err)}defer res.Body.Close()str, err := httputil.DumpResponse(res, true)if err != nil {panic(err)}fmt.Printf("%s", str)}
func main() {request, err := http.NewRequest(http.MethodGet,"https://www.baidu.com/", nil);request.Header.Add("User-Agent","Mozilla/5.0...")client := http.Client{ // 自定义客户端CheckRedirect: func(req *http.Request, via []*http.Request) error {fmt.Println(req)return nil},// ...}res, err := client.Do(request)if err != nil {panic(err)}defer res.Body.Close()str, err := httputil.DumpResponse(res, true)if err != nil {panic(err)}fmt.Printf("%s", str)}
package mainimport ("net/http""io/ioutil""log""io")func EchoServer(w http.ResponseWriter, req *http.Request) {body, _ := ioutil.ReadAll(req.Body)io.WriteString(w, string(body))}func main() {http.HandleFunc("/echo/", EchoServer) // 路由处理函数log.Fatal(http.ListenAndServe(":8080", nil))}
func main() {fmt.Println(time.Now())}// 2018-12-28 09:28:41.5305037 +0800 CST m=+0.027019001
解析:
time.Now() 返回了 time 结构体本身,该结构体实现了很多方法,如可以获得年月日信息等。

func main() {fmt.Println(time.Now().Unix())}// 1545961249
func main() {now := time.Now().Unix()fmt.Println( time.Unix(now, 0) )}// 2018-12-28 10:33:43 +0800 CST
time.Unix(unixtime, 0) 可以将时间戳转化为标准时间,第二个参数只有在 [0,1e9] 范围外有效,推测是时间的偏移量,一般情况下传0即可
func main() {now := time.Now()res := now.Format("2006-01-02 15:04:05")fmt.Println(res)}// 2018-12-28 11:54:54
其中,“2006-01-02 15:04:05” 是固定日期参照格式,不可以写成其他数字。
func main() {now := "2018/12/28 11:54:54"res,_ := time.Parse("2006/01/02 15:04:05", now)fmt.Println(res)}// 2018-12-28 11:54:54 +0000 UTC
注意time.Parse的第一个参数是时间模板,要与第二个参数,也就是待转化的字符串格式一模一样。返回 Time 类型
func main() {loc, _ := time.LoadLocation("Asia/Shanghai")fmt.Println(loc)}// Asia/Shanghai
返回一个 *Location 结构体
func main() {now := "2018/12/28 11:54:54"loc, _ := time.LoadLocation("Asia/Shanghai")res,_ := time.ParseInLocation("2006/01/02 15:04:05",now,loc)fmt.Println(res)}
time.ParseInLocation 有三个参数:时间格式模板,需要转化的时间字符串,时区对象,返回标准时间。
ioutil.ReadDir(dirname string) 列出文件夹与文件名
func main() {files, err := ioutil.ReadDir(".")if err != nil {log.Fatal(err)}for _, file := range files {fmt.Println(file.Name(), file.IsDir())}}/*main.go falsepipline trueprocess truetmp true*/
ReadDir() 返回一个 []os.FileInfo ,是一个 os.FileInfo 类型的切片,FileInfo 结构体如下
type FileInfo interface {Name() string // base name of the fileSize() int64 // length in bytes for regular files;Mode() FileMode // file mode bitsModTime() time.Time // modification timeIsDir() bool // abbreviation for Mode().IsDir()Sys() interface{} // underlying data source (can return nil)}
ioutil.ReadFile(filename string) 读取文件内容
func main() {data, err := ioutil.ReadFile("tmp/access.log")if err!=nil{os.Exit(1)}fmt.Println(string(data))}/*第一行数据第二行数据第三行数据*/
ioutil.ReadFile 的本质是:
1. os.Open(filename) 用os打开文件2. ioutil.readAll(r io.Reader, capacity int64) 读取所有
func ReadAll(r io.Reader) ([]byte, error) 读取读取器内的内容
func main() {reader := strings.NewReader("hello word widuu") //返回*strings.Readerdata, _ := ioutil.ReadAll(reader)fmt.Println(string(data))}
os 包提供了不依赖平台的操作系统函数接口。
// Hostname返回内核提供的主机名func Hostname() (name string, err error)// Environ返回表示环境变量的格式为”key=value”的字符串的切片拷贝func Environ() []string// Getenv检索并返回名为key的环境变量的值func Getenv(key string) string// Getpid返回调用者所在进程的进程IDfunc Getpid() int// Exit让当前程序以给出的状态码code退出。一般来说,状态码0表示成功,非0表示出错。程序会立刻终止,defer的函数不会被执行func Exit(code int)// 获取文件信息func Stat(name string) (fi FileInfo, err error)// Getwd返回一个对应当前工作目录的根路径func Getwd() (dir string, err error)// 使用指定的权限和名称创建一个目录func Mkdir(name string, perm FileMode) error// 使用指定的权限和名称创建一个目录,包括任何必要的上级目录,并返回nil,否则返回错误func MkdirAll(path string, perm FileMode) error// 删除name指定的文件或目录func Remove(name string) error// 返回一个用于保管临时文件的默认目录func TempDir() string
// Create采用模式0666(任何人都可读写,不可执行)创建一个名为name的文件,如果文件已存在会截断它(为空文件)func Create(name string) (file *File, err error)// Open打开一个文件用于读取。如果操作成功,返回的文件对象的方法可用于读取数据;对应的文件描述符具有O_RDONLY模式func Open(name string) (file *File, err error)// OpenFile用指定模式打开一个文件,参数:文件路径、打开模式、文件权限func OpenFile(name string, flag int, perm FileMode) (*File, error)// Stat返回描述文件f的FileInfo类型值func (f *File) Stat() (fi FileInfo, err error)// Readdir读取目录f的内容,返回一个有n个成员的[]FileInfo,这些FileInfo是被Lstat返回的,采用目录顺序func (f *File) Readdir(n int) (fi []FileInfo, err error)// Read方法从f中读取最多len(b)字节数据并写入bfunc (f *File) Read(b []byte) (n int, err error)// 向文件中写入字符串func (f *File) WriteString(s string) (ret int, err error)// Sync递交文件的当前内容进行稳定的存储。一般来说,这表示将文件系统的最近写入的数据在内存中的拷贝刷新到硬盘中稳定保存func (f *File) Sync() (err error)// Close关闭文件f,使文件不能用于读写func (f *File) Close() error
// 将布尔值转换为字符串 true 或 falsefunc FormatBool(b bool) string// 将字符串转换为布尔值// 它接受真值:1, t, T, TRUE, true, True// 它接受假值:0, f, F, FALSE, false, False// 其它任何值都返回一个错误。func ParseBool(str string) (bool, error)// 将整数转换为字符串形式。base 表示转换进制,取值在 2 到 36 之间。func FormatInt(i int64, base int) string// 将字符串解析为整数,ParseInt 支持正负号,ParseUint 不支持正负号。// base 表示进位制(2 到 36),如果 base 为 0,则根据字符串前缀判断,// 前缀 0x 表示 16 进制,前缀 0 表示 8 进制,否则是 10 进制。// bitSize 表示结果的位宽(包括符号位),0 表示最大位宽。func ParseInt(s string, base int, bitSize int) (i int64, err error)func ParseUint(s string, base int, bitSize int) (uint64, error)// 将整数转换为十进制字符串形式(即:FormatInt(i, 10) 的简写)func Itoa(i int) string// 将字符串转换为十进制整数,即:ParseInt(s, 10, 0) 的简写)func Atoi(s string) (int, error)
func main() {res := strings.HasPrefix("test.com", "te")fmt.Println(res)}// true
func main() {res := strings.HasSuffix("test.com", "com")fmt.Println(res)}// true
func main() {res := strings.Contains("test.com", ".c")fmt.Println(res)}// true
func main() {res := strings.Index("test.com", ".c")fmt.Println(res)}// 4
func main() {res := strings.LastIndex("test.com", "t")fmt.Println(res)}// 3
func main() {s := "test.com.es"res := strings.Replace(s, "es","oo",-1)fmt.Println(res)}// toot.com.oo
strings.Replace(s, old, new, time) 参数分别是:字符串,要被替换的子串,新子串,若有多个满足条件的子串替换次数(若为-1则全部替换)
func main() {s := "test.com.es"res := strings.Count(s, "c")fmt.Println(res)}// 1
func main() {s := "Test.Com.Es"res := strings.ToLower(s)fmt.Println(res)}// test.com.es
func main() {s := "Test.Com.Es"res := strings.ToUpper(s)fmt.Println(res)}// TEST.COM.ES
func main() {s := " Test.Com.Es \n"res := strings.TrimSpace(s)fmt.Println(res)}// Test.Com.Es (前后无空格和换行符)
func main() {s := "A.Test.Com.Es.A"res := strings.Trim(s,"A")fmt.Println(res)}// .Test.Com.Es.
trim 家族还有其他方法,如 TrimRight,TrimLeft 等,带有第二个参数,可以自定义去除的子串。
func main() {s := "A.Test.Com.Es.A"res := strings.Split(s,".")fmt.Println(res)}// [A Test Com Es A]
func main() {s := []string{"hello","world"}res := strings.Join(s,",")fmt.Println(res)}// hello,world
url.Parse 返回一个 *URL 结构体,结构体的成员变量如下:
type URL struct {Scheme stringOpaque stringUser *UserinfoHost stringPath stringRawPath stringForceQuery boolRawQuery stringFragment string}
func main() {str := "https://root:123456@www.baidu.com:8080/login/xxx?&name=xiaoqing&age=24#fff"u, err := url.Parse(str)if err == nil {scheme := u.Schemefmt.Println(scheme) // "https"opaque := u.Opaquefmt.Println(opaque) // ""user := u.Userfmt.Println(user) // "root:123456"host := u.Hostfmt.Println(host) // "www.baidu.com:8080"path := u.Pathfmt.Println(path) // "/login/xxx"rawPath := u.RawPathfmt.Println(rawPath) // ""forceQuery := u.ForceQueryfmt.Println(forceQuery) // "false"rawQuery := u.RawQueryfmt.Println(rawQuery) // "&name=xiaoqing&age=24"fragment := u.Fragmentfmt.Println(fragment) // "fff"}}
# test/main.go 【被测试文件】package mainfunc add(a,b int) int{return a+b}# test/main_test.go 【测试文件】package mainimport ("testing""fmt")func TestAdd(t *testing.T) {res := add(1,2)if res != 3{t.Errorf("error!")}}
顺序执行测试函数
package mainimport ("testing""fmt")func testAdd1(t *testing.T) { // 开头小写,会被跳过res := add(1,2)if res != 3{t.Errorf("error!")}}func testAdd2(t *testing.T) {res := add(2,3)if res != 5{t.Errorf("error!")}}func TestAll(t *testing.T){t.Run("testAdd1", testAdd1) // 在此处顺序执行t.Run("testAdd2", testAdd2)}func TestMain(m *testing.M) { // 当定义了 TestMain,只执行此函数,其他均不执行fmt.Println("Tests begin...")m.Run() // 跑其他各测试函数}# go test -v // 查看测试详情Tests begin...=== RUN TestAll=== RUN TestAll/testAdd1=== RUN TestAll/testAdd2--- PASS: TestAll (0.00s)--- PASS: TestAll/testAdd1 (0.00s)--- PASS: TestAll/testAdd2 (0.00s)PASSok _/D_/study/go/test 1.620s
/**将字符串 s 从 sub 子串开始截取 len 个字节,支持中文1. 获取子串位置2. 用 []byte 去掉所有子串前面的的字符3. 将剩余部分转化成 rune ,截取 len 个字节4. 最后转化成 string*/func SubCn(s, sub string, len int) (string, bool){subIndex := strings.Index(s, sub)if subIndex>0 {sByte := []byte(s)[subIndex:]sRune := []rune(string(sByte))[0:len]return string(sRune), true}return "", false}
byte 等同于int8,常用来处理ascii字符
rune 等同于int32,常用来处理unicode或utf-8字符,类似c语言中的char
s:="Go编程"fmt.Println(len(s)) // 8
解析:string底层是用byte数组存的,并且是不可以改变的。中文占3个字节
s:="Go编程"s2 := []rune(s)fmt.Println(len(s2)) // 4
// Go语言的字符串是一个用UTF-8编码的变宽字符序列,它的每一个字符都用一个或多个字节表示// 双引号用来创建可解析的字符串字面量(支持转义,但不能用来引用多行)str := "hello,world"//单引号则用于表示Golang的一个特殊类型:runer := 'c'// 另外,反引号用来创建原生的字符串字面量,这些字符串可能由多行组成(不支持任何转义序列),原生的字符串字面量多用于书写多行消息、HTML以及正则表达式func main() {s :=`hi, am xxx, \nccccccccccccccccccccccccc`fmt.Println(s)}/*输出:hi, am xxx, \nccccccccccccccccccccccccc*/