zoukankan      html  css  js  c++  java
  • 【Go语言】《GO语言入门经典》阅读笔记

    未归档知识点

    字符串末尾可以追加其他数据,但是不能修改原来的值

    使用reflect包可以输出变量的类型
    浮点数不指定默认是float64

    package main 
    import ( "fmt";"reflect" )
    func main() {
        s := "aaa"
        i := 10
        f := 1.2
        fmt.Println(reflect.TypeOf(s))
        fmt.Println(reflect.TypeOf(i))
        fmt.Println(reflect.TypeOf(f))
    }
    

    数据转换 资料

    string ↔ bool

    package main 
    import ( "fmt";"reflect";"strconv")
    func main() {
        var s string = strconv.FormatBool(true) // 举例:将bool类型转换成string
        b,err := strconv.ParseBool(s) //举例:将string转化成bool, 会有两个返回值
        fmt.Println(reflect.TypeOf(b),b,err)
    }
    
    output:
    bool true <nil>
    

    string ↔ int

    package main 
    import ( "fmt";"strconv")
    func main() {
        
        s := strconv.Itoa(32))  // int -> string
        i,_ := strconv.Atoi("3") // string -> int 成功 
        i,err := strconv.Atoi("a") // string -> int 失败
        if err != nil { //判断是否成功
            fmt.Println("converted failed", i) //i必须使用,这里的值为 0
        }
    }
    

    Parse类函数
    用于转换字符串为给定类型的值:ParseBool()、ParseFloat()、ParseInt()、ParseUint()

    由于字符串转换为其它类型可能会失败,所以这些函数都有两个返回值,第一个返回值保存转换后的值,第二个返回值判断是否转换成功。

    b, err := strconv.ParseBool("true")
    f, err := strconv.ParseFloat("3.1415", 64) //ParseFloat()只能接收float64类型的浮点数。
    i, err := strconv.ParseInt("-42", 10, 64) // func ParseInt(s string, base int, bitSize int) (i int64, err error)
    u, err := strconv.ParseUint("42", 10, 64) // func ParseUint(s string, base int, bitSize int) (uint64, error)
    
    

    bitSize参数表示转换为什么位的int/uint,有效值为0、8、16、32、64。
    当bitSize=0的时候,表示转换为int或uint类型。例如bitSize=8表示转换后的值的类型为int8或uint8。

    base参数表示以什么进制的方式去解析给定的字符串,有效值为0、2-36。
    当base=0的时候,表示根据string的前缀来判断以什么进制去解析:0x开头的以16进制的方式去解析,0开头的以8进制方式去解析,其它的以10进制方式解析。

    // 以10进制方式解析"-42",保存为int64类型
    i, _ := strconv.ParseInt("-42", 10, 64) 
    // 以5进制方式解析"23",保存为int64类型
    i, _ := strconv.ParseInt("23", 5, 64)
    // 以16进制解析23,保存为int64类型
    i, _ := strconv.ParseInt("23", 16, 64)
    // 以15进制解析23,保存为int64类型
    i, _ := strconv.ParseInt("23", 15, 64)
    

    Format类函数

    将给定类型格式化为string类型:FormatBool()、FormatFloat()、FormatInt()、FormatUint()

    s := strconv.FormatBool(true)
    s := strconv.FormatFloat(3.1415, 'E', -1, 64) //func FormatFloat(f float64, fmt byte, prec, bitSize int) string
    s := strconv.FormatInt(-42, 16) //func FormatInt(i int64, base int) string
    s := strconv.FormatUint(42, 16) //func FormatUint(i uint64, base int) string
    

    FormatInt的第二个参数base指定将第一个参数转换为多少进制,有效值为2<=base<=36。当指定的进制位大于10的时候,超出10的数值以a-z字母表示。

    FormatFloat()的参数

    • bitSize表示f的来源类型(32:float32、64:float64),会据此进行舍入
    • fmt表示格式:'f'(-ddd.dddd)、'b'(-ddddp±ddd,指数为二进制)、'e'(-d.dddde±dd,十进制指数)、'E'(-d.ddddE±dd,十进制指数)、'g'(指数很大时用'e'格式,否则'f'格式)、'G'(指数很大时用'E'格式,否则'f'格式)
    • prec控制精度(排除指数部分):对'f'、'e'、'E',它表示小数点后的数字个数;对'g'、'G',它控制总的数字个数。如果prec 为-1,则代表使用最少数量的、但又必需的数字来表示f。

    Append类函数
    AppendTP类函数用于将TP转换成字符串后append到一个slice中:AppendBool()、AppendFloat()、AppendInt()、AppendUint()。

    Append类的函数和Format类的函数工作方式类似,只不过是将转换后的结果追加到一个slice中

        // 声明一个slice
    	b10 := []byte("int (base 10):")
        
        // 将转换为10进制的string,追加到slice中
    	b10 = strconv.AppendInt(b10, -42, 10)
    	fmt.Println(string(b10))
    
    	b16 := []byte("int (base 16):")
    	b16 = strconv.AppendInt(b16, -42, 16)
    	fmt.Println(string(b16))
    

    函数

    函数可以有多个返回值

    不定参数函数

    // 实现求最大值函数
    package main 
    import ( "fmt" )
    func max(numbers ... int) int{
        res := numbers[0]
        for _,it := range numbers {
            if res < it {
                res = it
            }
        }
        return res
    }
    func main() {
        
        result := max(1,3,5,2,6)
        fmt.Println(result)
    }
    

    具名返回值
    指定返回值的名称,并在return前进行赋值,return时只需要裸return语句,可以增加函数的可读性

    func sayHi() (x,y string) {
        x = "Hello"
        y = "world"
        return
    }
    func main() {
        fmt.Println(sayHi())
    }
    /*
    output:
    Hello world
    */
    

    将函数作为值传递

    package main 
    import ( "fmt")
    
    func B(f func() string) string {
        return f()
    }
    func main() {
        fn := func() string {
            return "ok"
        }
        fmt.Println(B(fn))
    }
    /*
    output:
    ok
    */
    

    B接受一个签名类型为func() string的函数作为函数参数f,在B里面调用这个参数的函数f(),就执行了这个函数,返回了一个string,再把这个函数作为返回值返回到main.

    它同样也可以写成下面这种形式

    func B(f func() string) string {
        return f()
    }
    func A() string {
        return "ok"
    }
    func main() {
        fn := A
        fmt.Println(B(fn))
    }
    

    流程控制

    Golang中没有while关键词,while循环可以用for实现

    // 打印1~10
    func main() {
        i:=0
        for i<10 {
            i++
            fmt.Println(i)
        }
    }
    

    类似C语言的迭代遍历,golang可以用for遍历数据结构

    func main() {
        a :=[]int{1,2,3,4}
        for id,num := range a { //如果不需要序号'id',可以用'_'代替
            fmt.Println(id,"::",num)
        }
    }
    /*
    OUTPUT:
    0 :: 1
    1 :: 2
    2 :: 3
    3 :: 4
    */
    

    defer语句
    被defer指定的语句将在函数返回前执行,无论它被写在哪个位置

    func main() {
        defer fmt.Println("this is a defer function")
        fmt.Println("hello world")
    }
    /*
    OUTPUT:
    hello world
    this is a defer function
    */
    

    如果程序里有多条defer语句,将从后往前执行

    func main() {
        defer fmt.Println("defer A")
        defer fmt.Println("defer B")
        fmt.Println("hello world")
        defer fmt.Println("defer C")
    }
    /*
    OUTPUT:
    hello world
    defer C
    defer B
    defer A
    */
    

    一些需要使用defer语句的例子:

    • 在读取文件后将其关闭
    • 收到来自Web服务器的响应后对其进行处理以及建立连接后向数据库请求数据
    • 需要在某项工作完成后执行特定的函数

    数组,切片

    切片类似于数组,但可以改变长度
    append函数
    使用append函数可以增大切片长度,也可以删除元素,不能将delete用于切片元素

    func main() {
        var d = make([]string, 2)
        d[0] = "aaa"
        d[1] = "bbb"
        d = append(d,"ccc","ddd","eee") //向d的末尾添加多个元素
        d = append(d[:2],d[3:]...) //删除d[2],将后面所有元素前移
        for id,it := range d {
            fmt.Println(id,it)
        }
    }
    /*
    OUTPUT:
    0 aaa
    1 bbb
    2 ddd
    3 eee
    */
    

    copy函数
    copy可以在新切片中创建元素的副本,但是不能改变新切片的长度

    func main() {
        a := []int {1,2,3,4} 
        a = append(a,5) //a的长度为5
        b := make([]int, 2) //b的长度为2
        copy(b,a) 
        fmt.Println(b)
    
    }
    /*
    OUTPUT:
    [1 2]
    */
    

    映射

    类似于c++的map

    声明string → int空映射

    var mp  = make(map[string]int) 
    

    添加元素

    mp["cook"] = 32
    

    结构体

    简单的例子

    type Movie struct {
        Name string
        Year int
        Rating float32
    }
    
    func main() {
        m := Movie{Rating: 10, Name: "name", Year: 1999}
        fmt.Println(m)// 直接打印是按照结构体定义成员变量的顺序打印
    }
    
    /*
    OUTPUT:
    {name 1999 10}
    */
    

    还可以用new方法创建实例,但是创建的是指针变量
    但是在golang中指针变量访问成员变量还是用'.'

    type Movie struct {
        Name string
        Rating float32
    }
    
    func main() {
        a := new(int)
        //var a int 
        *a = 2333
        fmt.Println(reflect.TypeOf(a))
    
        m := new(Movie)
        *m = Movie{
            Name:"aaa",
            Rating:9.9,
        }
        fmt.Println(reflect.TypeOf(m))
        fmt.Println(m.Name, m.Rating)
    }
    
    /*
    *int
    *main.Movie
    aaa 9.9
    */
    

    结构体也是可以嵌套的,当作一个普通的数据类型,类似c++

    创建结构体时,如果没有给数据字段指定值,那么为golang默认的初始值

    golang没有提供自定义默认值的内置方法,但可以使用自定义函数来实现这个目标

    方法、接口

    类似于c语言的结构体,我们可以为结构体添加成员方法,在golang要写在结构体外面,在func之后,函数名之前添加“接收者”变量,传递指针还是传递值根据是否需要改变原值来决定。

    type Movie struct {
    	Name   string
    	Rating float64
    }
    
    func (m *Movie) summary() string {
    	r := strconv.FormatFloat(m.Rating, 'f', 1, 64) //将float64转化成string
    	return m.Name + ", " + r
    }
    
    func main() {
    	m := Movie{
    		Name:   "Spiderman",
    		Rating: 3.2,
    	}
    
    	fmt.Println(m.summary())
    }
    /*
    OUTPUT:
    Spiderman, 3.2
    */
    

    接口描述了方法集中的所有方法,制定了它们的签名,但并没有实现他们。

    要使用接口必须先实现它,接口定义了方法的规范,有助于代码理解

    // 编写启动机器人的接口
    type Robot interface {
    	PowerOn() error //不接受任何参数,返回一种错误类型
    }
    
    //编写两种机器人的启动方法
    type T850 struct {
    	Name string
    }
    func (a *T850) PowerOn() error {
    	return nil
    }
    type R2D2 struct {
    	Broken bool
    }
    func (r *R2D2) PowerOn() error {
    	if r.Broken {
    		return errors.New("R2D2 is broken")
    	} else {
    	0	return nil
    	}
    }
    
    //编写一个可用于启动任何机器人的函数, 这个函数将接口的实现作为参数
    func Boot(r Robot) error {
    	return r.PowerOn()
    }
    
    //各种调用方法
    func main() {
    
    	t := T850{Name: "The Terminator"}
    	r := R2D2{Broken: true}
    	err := Boot(&r)
    	if err != nil {
    		fmt.Println(err)
    	} else {
    		fmt.Println("Robot is powered on!")
    	}
    	err = Boot(&t)
    	if err != nil {
    		fmt.Println(err)
    	} else {
    		fmt.Println("Robot is powered on!")
    	}
    }
    
    /*
    OUTPUT:
    R2D2 is broken
    Robot is powered on!
    */
    

    接口有助于代码重用,还能完全更换实现,在golang中,接口一声明的方式提供了多态

    字符串

    使用缓冲区拼接字符串,速度比直接+起来快得多

    package main
    
    import (
    	"bytes"
    	"fmt"
    )
    
    func main() {
    	var buffer bytes.Buffer
    
    	for i := 0; i < 500; i++ {
    		buffer.WriteString("z")
    	}
    
    	fmt.Println(buffer.String())
    }
    
    /* OUTPUT:
    zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
    */
    
    

    将整个字符串转换为小写,使用ToLower函数
    将整个字符串转换为小写,使用ToUpper函数

    package main
    
    import (
    	"fmt"
    	"strings"
    )
    
    func main() {
    	fmt.Println(strings.ToLower("AAA bbb"))
    	fmt.Println(strings.ToUpper("AAA bbb"))
    }
    
    /*
    OUTPUT:
    aaa bbb
    AAA BBB
    */
    

    字符串查找

    package main
    
    import (
    	"fmt"
    	"strings"
    )
    
    func main() {
    	fmt.Println(strings.Index("surface", "face"))
    	fmt.Println(strings.Index("moon", "aer"))
    }
    
    

    其中Index函数的实现如下,时间复杂度应该是O(n^2)的,具体一些函数的实现待考证

    // Index returns the index of the first instance of substr in s, or -1 if substr is not present in s.
    func Index(s, substr string) int {
        n := len(substr) //先获取substr的长度 赋给n
        switch {
        case n == 0:    //如果 substr的长度为0 ,则返回0,
            return 0
        case n == 1: 
            return IndexByte(s, substr[0]) // 后面再看一下IndexByte()的源码
        case n == len(s): // 如果 s和substr长度相等,直接判断俩字符串是否一模一样
            if substr == s {
                return 0
            }
            return -1
        case n > len(s): // 如果 substr的长度大于s的长度,那肯定不存在了,返回-1,说明substr不存在于s中
            return -1
        case n <= bytealg.MaxLen: // 后面得看bytealg.MaxLen 
            // Use brute force when s and substr both are small
            if len(s) <= bytealg.MaxBruteForce {   // const型 :const MaxBruteForce = 64
                return bytealg.IndexString(s, substr)
            }
            c0 := substr[0]
            c1 := substr[1]
            i := 0
            t := len(s) - n + 1
            fails := 0
            for i < t {
                if s[i] != c0 {
                    // IndexByte is faster than bytealg.IndexString, so use it as long as
                    // we're not getting lots of false positives.
                    o := IndexByte(s[i:t], c0) //这个的实现?
                    if o < 0 {
                        return -1
                    }
                    i += o
                }
                if s[i+1] == c1 && s[i:i+n] == substr {
                    return i
                }
                fails++
                i++
                // Switch to bytealg.IndexString when IndexByte produces too many false positives.
                if fails > bytealg.Cutover(i) { //这个的实现?
                    r := bytealg.IndexString(s[i:], substr)
                    if r >= 0 {
                        return r + i
                    }
                    return -1
                }
            }
            return -1
        }
        c0 := substr[0]
        c1 := substr[1]
        i := 0
        t := len(s) - n + 1
        fails := 0
        for i < t {
            if s[i] != c0 {
                o := IndexByte(s[i:t], c0)
                if o < 0 {
                    return -1
                }
                i += o
            }
            if s[i+1] == c1 && s[i:i+n] == substr {
                return i
            }
            i++
            fails++
            if fails >= 4+i>>4 && i < t {
                // See comment in ../bytes/bytes_generic.go.
                j := indexRabinKarp(s[i:], substr)
                if j < 0 {
                    return -1
                }
                return i + j
            }
        }
        return -1
    }
    

    删除字符串开头和末尾的空白字符可以用TrimSpace函数

    package main
    
    import (
    	"fmt"
    	"strings"
    )
    
    func main() {
    	fmt.Println(strings.TrimSpace("   I don't need all this space     "))
    }
    /*
    OUTPUT:
    I don't need all this space
    */
    

    错误处理

    不同于java等其他语言,go语言不支持传统的try-catch-finally控制结构,go语言将错误作为返回值。

    读取文件时处理错误

    package main
    
    import (
    	"fmt"
    	"io/ioutil"
    )
    
    func main() {
    	var file []byte
    	var err error
    	file, err = ioutil.ReadFile("foo.txt")
    	if err != nil {
    		fmt.Println(err)
    		return
    	}
    	fmt.Printf("%s", file)
    }
    /*
    OUTPUT:
    open foo.txt: The system cannot find the file specified.
    */
    

    自定义一个错误类型

    import (
    	"errors"
    	"fmt"
    )
    
    func main() {
    	err := errors.New("Something went wrong")
    	if err != nil {
    		fmt.Println(err)
    	}
    }
    /*
    OUTPUT:
    Something went wrong
    */
    

    fmt包提供了方法Errorf,可用于设置返回的错误字符串的格式,可以创建更有意义的错误字符串

    import (
    	"fmt"
    )
    
    func main() {
    	name := "name_aaa"
    	err := fmt.Errorf("The %v quit",name)
    	if err != nil {
    		fmt.Println(err)
    	}
    }
    
    /*
    The name_aaa quit
    */
    

    自定义从函数返回错误类型

    // returns half the value
    func Half(numberToHalf int) (int, error) {
    	if numberToHalf%2 != 0 {
    		return -1, fmt.Errorf("Cannot half %v", numberToHalf)
    	}
    	return numberToHalf / 2, nil
    }
    
    func main() {
    	n, err := Half(19)
    	if err != nil {
    		fmt.Println(err)
    		return
    	}
    	fmt.Println(n)
    }
    /*
    OUTPUT:
    Cannot half 19
    */
    
    // returns half the value
    func Half(numberToHalf int) (int, error) {
    	if numberToHalf%2 != 0 {
    		return -1, fmt.Errorf("Cannot half %v", numberToHalf)
    	}
    	return numberToHalf / 2, nil
    }
    
    func main() {
    	n, err := Half(18)
    	if err != nil {
    		fmt.Println(err)
    		return
    	}
    	fmt.Println(n)
    }
    /*
    OUTPUT:
    9
    */
    

    panic函数可以让程序直接崩溃推出,通常不建议使用,如果继续运行程序会有更坏的结果可以使用(拔电源效果?)

    • 程序处于无法恢复的状态
    • 发生了无法处理的错误
    package main
    
    import "fmt"
    
    func main() {
    	fmt.Println("aaaaa")
    	panic("Oh no. I can do no more. Goodbye.")
    	fmt.Println("bbbbb")
    }
    /*
    OUTPUT:
    aaaaa
    panic: Oh no. I can do no more. Goodbye.
    
    goroutine 1 [running]:
    main.main()
            C:/Users/..../...:7 +0x9c 
    exit status 2
    */
    

    并发编成

    通过time.sleep函数可以使程序暂停指定的时间

    time.S``leep(time.Second*2) //让程序暂停两秒
    

    go 语言提供了Goroutine,可以处理并发操作。通过使用Goroutine,可在调用函数print1后立即执行main函数中的第二行代码print2,在这种情况下,函数print1依然会执行,但不会阻塞程序中其他代码的执行,于是print1和print2并发执行,交错打印"aaa"和"bbb"

    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    func print1() {
    	for i:=1;i<=5;i++  {
    		fmt.Println("aaa")
    		time.Sleep(time.Second)
    	}
    }
    func print2() {
    	for i:=1;i<=5;i++  {
    		fmt.Println("bbb")
    		time.Sleep(time.Second)
    	}
    }
    func main() {
    	go print1()
    	print2()
    }
    
    /*
    OUTPUT:
    bbb
    aaa
    bbb
    aaa
    bbb
    aaa
    aaa
    bbb
    aaa
    bbb
    */
    

    通道

    使用通道进行通信

    c:=make(chan,string) // 创建通道c,可以接受字符串数据
    c <- "Hello World" //向通道发送消息
    msg := <- c
    

    下面这个例子,两秒后会输出msg字符串
    给通道指定消息接收者是一个阻塞操作,它将阻止函数返回,直到收到一条消息为止
    程序并发执行main函数和slowFunc函数时,会先执行到msg:= <- c,此时该进程被阻塞,直到c <- "slowFunc() finished"被执行,msg立即收到通道的消息。

    func slowFunc(c chan string) {
    	time.Sleep(time.Second * 2)
    	c <- "slowFunc() finished"
    }
    
    func main() {
    	c := make(chan string)
    	slowFunc(c)
    	msg := <- c
    	fmt.Println(msg)
    }
    

    可以在创建通道时指定容量,这样可以将消息暂时缓存在通道中,但是不能超过通道容量
    使用close函数关闭通道,不再接收消息,这里不关闭通道就会产生协程死锁的错误(原因待查)

    func receiver(c chan string) {
    	for msg := range c {
    		fmt.Println(msg)
    	}
    }
    
    func main() {
    
    	messages := make(chan string, 2)
    	messages <- "hello"
    	messages <- "world"
    	close(messages)
    	fmt.Println("Pushed two messages onto channel with no receivers")
    	receiver(messages)
    
    }
    /*
    OUTPUT:
    Pushed two messages onto channel with no receivers
    hello
    world
    */
    

    将通道函数用作函数参数,可以指定它为只读,只写或读写的

    //通道c只读,<-位于chan左边
    func OR(c <- chan string) string{
    	msg := <-c
    	return msg
    	
    }
    //通道c只读,<-位于chan右边
    func OW(c  chan <- string) {
    	c <- "hello world"
    }
    //通道c只读,无 <-
    func WR(c chan string) string {
    	msg := <-c
    	c <- "hello world"
    	return msg
    }
    

    假设当前有多个Goroutine,可以使用select语句为通道创建一系列接收者,并执行最先收到消息的接收者.
    下面的程序,第二个函数休眠阻塞,所以先接受到第一个通道的消息,执行了case 1

    func ping1(c chan string) {
    	c <- "ping on channel1"
    }
    
    func ping2(c chan string) {
    	time.Sleep(time.Second * 2)
    	c <- "ping on channel2"
    }
    
    func main() {
    
    	channel1 := make(chan string)
    	channel2 := make(chan string)
    
    	go ping1(channel1)
    	go ping2(channel2)
    
    	select {
    	case msg1 := <-channel1:
    		fmt.Println("received", msg1)
    	case msg2 := <-channel2:
    		fmt.Println("received", msg2)
    	}
    
    }
    /*
    OUTPUT:
    received ping on channel1
    */
    

    也可以给select指定一个超时时间,在指定时间后不再阻塞。

    select {
    	case msg1 := <-channel1:
    		fmt.Println("received", msg1)
    	case msg2 := <-channel2:
    		fmt.Println("received", msg2)
    	case <-time.After(500 * time.Millisecond):
    		fmt.Println("no messages received. giving up.")
    }
    

    在select语句中,如果同时从两个通道收到消息,将随机地选择并执行一条case,且只执行被选中的case

    退出通道——程序需要使用select语句实现无限制阻塞,但同时要求能够随时返回。通过在select语句中添加一个退出通道,可向退出通道发送消息来结束该语句,从而停止阻塞。

    func sender(c chan string) {
    	t := time.NewTicker(1 * time.Second)
    	for {
    		c <- "I'm sending a message"
    		<-t.C
    	}
    }
    
    func main() {
    	messages := make(chan string)
    	stop := make(chan bool)
    	go sender(messages) //每秒向msg通道发送一条消息
    	go func() { //三秒后向退出通道发送消息
    		time.Sleep(time.Second * 3)
    		fmt.Println("Time's up!")
    		stop <- true
    	}()
    
    	for {
    		select {
    		case <-stop:
    			return
    		case msg := <-messages:
    			fmt.Println(msg)
    		}
    	}
    }
    /*
    I'm sending a message
    I'm sending a message
    I'm sending a message
    I'm sending a message
    Time's up!
    */
    

    下载第三方包,在配置好git的情况下: go get ...(包名)

    导入包可用import语句

    创建HTTP服务器

    使用Go语言编写基本HTTP服务器

    import (
    	"net/http"
    )
    
    func print(w http.ResponseWriter, r *http.Request) { //查看或操作请求,再将响应返回给客户端
    	w.Write([]byte("Crush On You
    ")) //生成HTTP状态响应包,包含状态,报头,响应体
    }
    
    func main() {
    	http.HandleFunc("/", print) //创建路由'/',这个方法接受一个模式和一个函数,前者描述了路径,后者指定如何对发送到该路径的请求做出响应
    	http.ListenAndServe(":8000", nil) //启动服务器,监听localhost和端口8000
    }
    

    然后访问就可以在屏幕看到字符串Crush On You

    使用命令行工具发起HTTP请求 curl http://localhost:8000
    可以收到来自Web服务器的响应

    StatusCode        : 200
    StatusDescription : OK
    Content           : Crush On You
    
    RawContent        : HTTP/1.1 200 OK //使用协议,状态码
                        Content-Length: 13  //响应长度
                        Content-Type: text/plain; charset=utf-8 //内容类型及编码
                        Date: Mon, 08 Jun 2020 05:26:05 GMT
    
                        Crush On You  //响应体
    
    Forms             : {}
    Headers           : {[Content-Length, 13], [Content-Type, text/plain; charset=utf-8],  
                        [Date, Mon, 08 Jun 2020 05:26:05 GMT]}
    Images            : {}
    InputFields       : {}
    Links             : {}
    ParsedHtml        : mshtml.HTMLDocumentClass
    RawContentLength  : 13
    

    原本默认情况下,所有当前路由的子路由都可以定向到它,可以在处理默认路由的函数中检查路径,如果路径不对就直接返回404错误,

    func helloWorld(w http.ResponseWriter, r *http.Request) {
    	if r.URL.Path != "/" {
    		http.NotFound(w, r)
    		return
    	}
    	w.Write([]byte("Hello World
    "))
    }
    

    go语言支持设置响应的报头。假设服务器将发送一些JSON数据,通过设置Content-Type报头,服务器可告诉客户端,发送的是JSON数据。处理程序函数可使用ResponseWriter来添加报

    func helloWorld(w http.ResponseWriter, r *http.Request) {
    	if r.URL.Path != "/" {
    		http.NotFound(w, r)
    		return
    	}
    	w.Header().Set("Content-Type", "application/json; charset=utf-8") //添加报头
    	w.Write([]byte(`{"hello": "world"}`))
    }
    
    func main() {
    	http.HandleFunc("/", helloWorld)
    	http.ListenAndServe(":8000", nil)
    }
    

    通过curl请求后得到结果

    StatusCode        : 200
    StatusDescription : OK
    Content           : {"hello": "world"}
    RawContent        : HTTP/1.1 200 OK
                        Content-Length: 18
                        Content-Type: application/json; charset=utf-8
                        Date: Mon, 08 Jun 2020 07:32:08 GMT
    
                        {"hello": "world"}
    Forms             : {}
    Headers           : {[Content-Length, 18], [Content-Type, application/json; charset=ut 
                        f-8], [Date, Mon, 08 Jun 2020 07:32:08 GMT]}
    Images            : {}
    InputFields       : {}
    Links             : {}
    ParsedHtml        : mshtml.HTMLDocumentClass
    RawContentLength  : 18
    

    可以通过switch语句控制,根据不同类型的内容响应不同的东西

    func helloWorld(w http.ResponseWriter, r *http.Request) {
    	if r.URL.Path != "/" {
    		http.NotFound(w, r)
    		return
    	}
    	switch r.Header.Get("Accept") {
    	case "application/json":
    		w.Header().Set("Content-Type", "application/json; charset=utf-8")
    		w.Write([]byte(`{"message": "Hello World"}`))
    	case "application/xml":
    		w.Header().Set("Content-Type", "application/xml; charset=utf-8")
    		w.Write([]byte(`<?xml version="1.0" encoding="utf-8"?><Message>Hello World</Message>`))
    	default:
    		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
    		w.Write([]byte("Hello World
    "))
    	}
    
    }
    
    func main() {
    	http.HandleFunc("/", helloWorld)
    	http.ListenAndServe(":8000", nil)
    }
    

    创建HTTP客户端

    发出GET请求

    import (
    	"fmt"
    	"io/ioutil"
    	"log"
    	"net/http"
    )
    
    func main() {
    	response, err := http.Get("http://localhost:8000")
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer response.Body.Close()
    	body, err := ioutil.ReadAll(response.Body)
    	if err != nil {
    		log.Fatal(err)
    	}
    	fmt.Printf("%s", body)
    
    }
    

    发出POST请求

    import (
    	"fmt"
    	"io/ioutil"
    	"log"
    	"net/http"
    	"strings"
    )
    
    func main() {
    	postData := strings.NewReader(`{ "some": "json" }`)
    	response, err := http.Post("http://localhost:8000", "application/json", postData)
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer response.Body.Close()
    	body, err := ioutil.ReadAll(response.Body)
    	if err != nil {
    		log.Fatal(err)
    	}
    	fmt.Printf("%s", body)
    
    }
    

    使用自定义客户端:

    • 不使用net/http包里的快捷方法Get,创建一个HTTP客户端
    • 使用方法NewRequest发出GET请求
    • 使用方法Do发送请求并处理响应
    package main
    
    import (
    	"fmt"
    	"io/ioutil"
    	"log"
    	"net/http"
    )
    
    func main() {
    	client := &http.Client{}
    	request, err := http.NewRequest("GET", "https://ifconfig.co", nil)
    	if err != nil {
    		log.Fatal(err)
    	}
    	response, err := client.Do(request)
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer response.Body.Close()
    	body, err := ioutil.ReadAll(response.Body)
    	if err != nil {
    		log.Fatal(err)
    	}
    	fmt.Printf("%s", body)
    
    }
    

    使用自定义HTTP客户端意味着可对请求设置报头、基本身份验证和cookies,建议使用。

  • 相关阅读:
    装饰器函数(一)
    面向对象的初阶复习
    内置函数/反射/内置方法(单例类面)
    property特殊属性/类方法/静态方法
    多态/封装
    接口类抽象类
    初始继承之顺序/深度优先及广度优先
    类涉及的空间关系及组合(可变项地址面)
    <head></head>
    让IE6 IE7 IE8 IE9 IE10 IE11支持Bootstrap的解决方法
  • 原文地址:https://www.cnblogs.com/greenty1208/p/13049328.html
Copyright © 2011-2022 走看看