golang 基础笔记二


网络编程

网络编程有两种:

1) TCP socket 编程,是网络编程的主流。之所以叫 Tcp socket 编程,是因为底层是基于 Tcp/ip 协 议的.

比如: QQ 聊天 [示意图]

2) b/s 结构的 http 编程,我们使用浏览器去访问服务器时,使用的就是 http 协议,而 http 底层依 旧是用 tcp socket 实现的。比如: 京东商城 【这属于 go web 开发范畴 】

 端口:

1)0 是保留端口 

2)1-1024 是固定端口,例如:22是SSH远程登录端口,23是telnet使用

3)1025 - 65535 是动态端口,程序员可以使用的端口

TCP编程

service.go

package main
import (
    "fmt"
    "io"
    "net"
)
func process(conn net.Conn)  {
    defer conn.Close()
    for{
        buf :=make([]byte,1024)
        //1.等待客户端通过conn发送消息
        //2.如果客户端没有wrtie[发送],那么协程就会阻塞在这里
        n,err:=conn.Read(buf) //从conn读取
        if err ==io.EOF{
            fmt.Println("客户端退出了")
            return
        }
        //3.显示客户端发送的内容到服务器的终端
        fmt.Print(string(buf[:n]))
    }
}
func main()  {
     fmt.Println("服务器开始监听.....")
     listen,err :=net.Listen("tcp","127.0.0.1:8888")
     if err !=nil{
         fmt.Println("listen err = ",err)
         return
     }
     defer listen.Close()//延时关闭链接
     for{
         //等待客户端链接
         conn,err:=listen.Accept()
         if err!=nil{
             fmt.Println("链接失败 err=",err)
        }else {
            fmt.Printf("客户端来链接了,客户端的IP=%v\n",conn.RemoteAddr().String())
        }
        go process(conn)
     }
}

client.go

package main
import (
    "bufio"
    "fmt"
    "net"
    "os"
    "strings"
)
func main()  {
    conn,err := net.Dial("tcp","127.0.0.1:8888")
    if err!=nil{
        fmt.Println("客户端链接失败 err=",err)
        return
    }
    defer conn.Close()
    reader :=bufio.NewReader(os.Stdin)
    //从终端读取一行用户输入,并准备发送给服务器
    for{
        line,err:=reader.ReadString('\n')
        if err !=nil{
            fmt.Println("readstring err=",err)
        }
        line = strings.Trim(line,"\r\n")
        if line=="exit"{
            fmt.Println("客户端退出了")
            break
        }
        //再将line 发送给服务器
        _,err = conn.Write([]byte(line+"\n"))
        if err!=nil{
            fmt.Println("发送信息失败err=",err)
        }
    }
}

 go 链接 redis 一

安装第三方开源 Redis 库

1) 使用第三方开源的 redis 库: github.com/garyburd/redigo/redis

2) 在使用 Redis 前,先安装第三方 Redis 库,在 GOPATH 路径下执行安装指令: D:\goproject>go get github.com/garyburd/redigo/redis

特别说明: 在安装 Redis 库前,确保已经安装并配置了 Git, 因为 是从 github 下载安装 Redis 库的, 需要使用到 Git。 如果没有安装配置过 Git,请参考: 如何安装配置 Git

 redis 链接操作 Set Get

package main
import (
    "fmt"
    "github.com/garyburd/redigo/redis"
)
func main()  {
    c,err:=redis.Dial("tcp","localhost:6379")
    if err!=nil{
        fmt.Println("redis conn err = ",err)
        return
    }
    defer c.Close()
    _,err=c.Do("Set","key1",998)
    if err!=nil{
        fmt.Println("写入错误err=",err)
        return
    }
    r,err :=redis.Int(c.Do("Get","key1"))  //r 返回的是 interface{}
    if err!=nil{
        fmt.Println("get key failed err=",err)
    }
    fmt.Println(r)
}

对redis hash 的操作

package main
import (
    "fmt"
    "github.com/garyburd/redigo/redis"
)
func main()  {
    c,err:=redis.Dial("tcp","localhost:6379")
    if err!=nil{
        fmt.Println("redis conn err = ",err)
        return
    }
    defer c.Close()
    _,err=c.Do("HSet","user01","name","tianxin")
    if err!=nil{
        fmt.Println("hset err=",err)
        return
    }
    _,err=c.Do("HSet","user01","age",18)
    if err!=nil{
        fmt.Println("hset err=",err)
        return
    }
    name,err :=redis.String(c.Do("HGet","user01","name"))  //r 返回的是 interface{}
    if err!=nil{
        fmt.Println("HGet key failed err=",err)
    }
    fmt.Println(name)
    age,err :=redis.Int(c.Do("HGet","user01","age"))  //r 返回的是 interface{}
    if err!=nil{
        fmt.Println("HGet key failed err=",err)
    }
    fmt.Println(age)
}

批量的Set Get数据

package main
import (
    "fmt"
    "github.com/garyburd/redigo/redis"
)
func main()  {
    c,err:=redis.Dial("tcp","localhost:6379")
    if err!=nil{
        fmt.Println("redis conn err = ",err)
        return
    }
    defer c.Close()
    _,err=c.Do("HMSet","user02","name","tianxingege","age",30)
    if err!=nil{
        fmt.Println("HMSet err=",err)
        return
    }
    r,err :=redis.Strings(c.Do("HMGet","user02","name","age"))  //r 返回的是 interface{}
    if err!=nil{
        fmt.Println("HGet key failed err=",err)
    }
    for i,v:=range r{
        fmt.Printf("r[%d]=%s\n",i,v)
    }
}

redis 链接池

说明:通过Golang 对Redis操作,还可以通过Redis连接池,流程如下:

1) 事先初始化一定数量的链接,放入连接池

2) 当GO需要操作Redis时,直接从Redis连接池取出链接即可。

3) 这样可以节省临时获取Redis链接的实际,从而提高效率。

package main
import (
    "fmt"
    "github.com/garyburd/redigo/redis"
)

//定义一个全局的pool
var pool *redis.Pool
//当启动程序时,就初始化连接池
func init()  {
    pool = &redis.Pool{
        Dial: func() (redis.Conn,error) {
            return  redis.Dial("tcp","localhost:6379")
        },
        MaxIdle:         8,//最大空闲连接数
        MaxActive:       0,//表示和数据库的最大链接数,0 表示没有限制
        IdleTimeout:     100,//最大空闲实际
    }
}
func main()  {
    //pool.Close() //如果连接池关闭了就不能取到链接了
    //先从pool 取出一个链接
    conn := pool.Get()
    defer conn.Close()
    _,err :=conn.Do("Set","name","中华人民共和国")
    if err !=nil{
        fmt.Println("conn.Do err= ",err)
    }
    //取出
    r,err := redis.String(conn.Do("Get","name"))
    if err !=nil{
        fmt.Println("conn.Do GET err= ",err)
    }
    fmt.Println(r)
}

go 链接 redis 二

使用的是 https://github.com/go-redis/redis 这个 golang 客户端, 因此安装方式如下:

go get gopkg.in/redis.v4
package main
import (
    "fmt"
    "gopkg.in/redis.v4"
    "sync"
    "time"
)
func main() {
    client := createClient()
    defer client.Close()
    stringOperation( client )
    listOperation( client )
    setOperation( client )
    hashOperation( client )
    connectPool( client )
}
// 创建 redis 客户端
func createClient() *redis.Client {
    client := redis.NewClient( &redis.Options{
        Addr:     "localhost:6379",
        Password: "",
        DB:       0,
        PoolSize: 5,
    } )
    pong, err := client.Ping().Result()
    fmt.Println( pong, err )
    return client
}
// String 操作
func stringOperation( client *redis.Client ) {
    // 第三个参数是过期时间, 如果是 0, 则表示没有过期时间.
    err := client.Set( "name", "xys", 0 ).Err()
    if err != nil {
        panic( err )
    }
    val, err := client.Get( "name" ).Result()
    if err != nil {
        panic( err )
    }
    fmt.Println( "name", val )
    // 这里设置过期时间.
    err = client.Set( "age", "20", 1 * time.Second ).Err()
    if err != nil {
        panic( err )
    }
    client.Incr( "age" ) // 自增
    client.Incr( "age" ) // 自增
    client.Decr( "age" ) // 自减
    val, err = client.Get( "age" ).Result()
    if err != nil {
        panic( err )
    }
    fmt.Println( "age", val ) // age 的值为21
    // 因为 key "age" 的过期时间是一秒钟, 因此当一秒后, 此 key 会自动被删除了.
    time.Sleep( 1 * time.Second )
    val, err = client.Get( "age" ).Result()
    if err != nil {
        // 因为 key "age" 已经过期了, 因此会有一个 redis: nil 的错误.
        fmt.Printf( "error: %v\n", err )
    }
    fmt.Println( "age", val )
}

// list 操作
func listOperation( client *redis.Client ) {
    client.RPush( "fruit", "apple" ) // 在名称为 fruit 的list尾添加一个值为value的元素
    client.LPush( "fruit", "banana" ) // 在名称为 fruit 的list头添加一个值为value的 元素
    length, err := client.LLen( "fruit" ).Result() // 返回名称为 fruit 的list的长度
    if err != nil {
        panic( err )
    }
    fmt.Println( "length: ", length ) // 长度为2
    value, err := client.LPop( "fruit" ).Result() //返回并删除名称为 fruit 的list中的首元素
    if err != nil {
        panic(err)
    }
    fmt.Println( "fruit: ", value )
    value, err = client.RPop( "fruit" ).Result() // 返回并删除名称为 fruit 的list中的尾元素
    if err != nil {
        panic(err)
    }
    fmt.Println( "fruit: ", value )
}

// set 操作
func setOperation( client *redis.Client ) {
    client.SAdd( "blacklist", "Obama" ) // 向 blacklist 中添加元素
    client.SAdd( "blacklist", "Hillary" ) // 再次添加
    client.SAdd( "blacklist", "the Elder" ) // 添加新元素
    client.SAdd( "whitelist", "the Elder" ) // 向 whitelist 添加元素
    // 判断元素是否在集合中
    isMember, err := client.SIsMember( "blacklist", "Bush" ).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println( "Is Bush in blacklist: ", isMember )
    // 求交集, 即既在黑名单中, 又在白名单中的元素
    names, err := client.SInter( "blacklist", "whitelist" ).Result()
    if err != nil {
        panic(err)
    }
    // 获取到的元素是 "the Elder"
    fmt.Println( "Inter result: ", names )
    // 获取指定集合的所有元素
    all, err := client.SMembers( "blacklist" ).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println( "All member: ", all )
}

// hash 操作
func hashOperation( client *redis.Client ) {
    client.HSet( "user_xys", "name", "xys" ); // 向名称为 user_xys 的 hash 中添加元素 name
    client.HSet( "user_xys", "age", "18" ); // 向名称为 user_xys 的 hash 中添加元素 age
    // 批量地向名称为 user_test 的 hash 中添加元素 name 和 age
    client.HMSet( "user_test", map[string]string{"name": "test", "age":"20"} )
    // 批量获取名为 user_test 的 hash 中的指定字段的值.
    fields, err := client.HMGet( "user_test", "name", "age" ).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println( "fields in user_test: ", fields )
    // 获取名为 user_xys 的 hash 中的字段个数
    length, err := client.HLen( "user_xys" ).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println( "field count in user_xys: ", length ) // 字段个数为2
    // 删除名为 user_test 的 age 字段
    client.HDel( "user_test", "age" )
    age, err := client.HGet( "user_test", "age" ).Result()
    if err != nil {
        fmt.Printf( "Get user_test age error: %v\n", err )
    } else {
        fmt.Println( "user_test age is: ", age ) // 字段个数为2
    }
}
// redis.v4 的连接池管理
func connectPool( client *redis.Client ) {
    wg := sync.WaitGroup{}
    wg.Add( 10 )
    for i := 0; i < 10; i++ {
        go func() {
            defer wg.Done()
            for j := 0; j < 100; j++ {
                client.Set( fmt.Sprintf( "name%d", j ), fmt.Sprintf( "xys%d", j ), 0 ).Err()
                client.Get( fmt.Sprintf( "name%d", j ) ).Result()
            }
            fmt.Printf( "PoolStats, TotalConns: %d, FreeConns: %d\n", client.PoolStats().TotalConns, client.PoolStats().FreeConns );
        }()
    }
    wg.Wait()
}

 数据结构

稀疏数组

package main
import (
    "bufio"
    "fmt"
    "io"
    "os"
    "strconv"
    "strings"
)
type ValNode struct {
    row int
    col int
    val int
}
func main()  {
    //原始数组
    var chessMap [11][11]int
    chessMap[1][2] = 1 //黑子
    chessMap[2][3] = 2 //篮子
    for _,v := range chessMap{
        for _,v2 := range v{
            fmt.Printf("%d\t",v2)
        }
        fmt.Println()
    }
    //稀疏数组
    var sparseArr []ValNode
    sparseArr = append(sparseArr,ValNode{11,11,0})//初始规模
    for i,v := range chessMap{
        for j,v2 := range v{
            if v2!=0{
                sparseArr = append(sparseArr,ValNode{i,j,v2})
            }
        }
    }
    //fmt.Println(sparseArr)
    //将稀疏数组存入文件
    filepath :="c:/goprojects/arr.data"
    file,err :=os.OpenFile(filepath,os.O_WRONLY|os.O_TRUNC,0666) //2.写 和 清空 的模式
    if err != nil{
        fmt.Printf("打开文件错误,err=",err)
    }
    //及时关闭file句柄
    defer file.Close()
    var str string
    for _,valNode:=range sparseArr{
           str += strconv.Itoa(valNode.row)+" "+strconv.Itoa(valNode.col)+" "+strconv.Itoa(valNode.val)+"\n"
    }
    //fmt.Println(str)
    writer := bufio.NewWriter(file)
    writer.WriteString(str)
    writer.Flush()
    //从文件取出数据恢复数组
    file1,err :=os.OpenFile(filepath,os.O_RDONLY,0666) //.读写 和 追加 的模式
    defer file1.Close()
    reader :=bufio.NewReader(file1)
    var strarr []ValNode
    for {
        str,err := reader.ReadString('\n')
        if err == io.EOF{
            break
        }
        if str!="\n"{
            arr := strings.Fields(str)
            row,_:=strconv.Atoi(arr[0])
            col,_:=strconv.Atoi(arr[1])
            val,_:=strconv.Atoi(arr[2])
            strarr = append(strarr,ValNode{row,col,val})
        }
    }
    fmt.Println(strarr)
    var cheeMap2 [11][11]int
    for i,valNode:=range strarr{
        if i!=0{
            cheeMap2[valNode.row][valNode.col] = valNode.val
        }
    }
    for _,v := range cheeMap2{
        for _,v2 := range v{
            fmt.Printf("%d\t",v2)
        }
        fmt.Println()
    }
}

队列

1) 队列是一个有序列表,可以用数组或者链表来实现
2) 先入先出的原则,即:先存入队列的数据,要先取出,后存入的要后取出

数组模拟队列

1) 队列本身是有序表,若使用数组的结构来存储队列的数据,则队列数组的声明如下 其中 maxSize 是该队列的最大容量
2) 因为队列的输出,输入时分别从前后端来处理,因此需要两个变量front 及 rear 分别记录队列前后端的下标,front会随着数据输出而改变
而rear则是随着数据输入而改变

先完成一个非环形的队列(数组来实现)

package main
import (
    "errors"
    "fmt"
    "os"
)

//使用一个结构体管理队列
type Queue struct {
    maxSize int    //队列最大长度
    array   [5]int //数组
    front   int    //指向前的标识
    rear    int    //指向尾的标识
}

//添加数据到队列
func (this *Queue) AddQueue(val int) (err error) {
    //先判断队列是否已满
    if this.rear == this.maxSize-1 { //重要提示 rear 是队列尾部 包含最后元素
        return errors.New("queue full")
    }
    this.rear++
    this.array[this.rear] = val
    return
}
//从队列里面取出数据
func (this *Queue)GetQueue()(val int,err error)  {
    //先判断是否为空
    if this.front==this.rear{
        return -1,errors.New("Queue empty")
    }
    this.front++
    return this.array[this.front],nil
}
//显示当前队列
func (this *Queue) ShowQueue() {
    fmt.Println("队列当前的情况是:")
    //首标识不包含元素,尾标识包含元素
    for i := this.front + 1; i <= this.rear; i++ {
        fmt.Printf("array[%d]=%d\n", i, this.array[i])
    }
}
func main() {
    queue := &Queue{
        maxSize: 5,
        array:   [5]int{},
        front:   -1,
        rear:    -1,
    }
    var key string
    var val int
    for {
        fmt.Println("1.输入add 表示添加数据到队列")
        fmt.Println("2.输入get 表示从队列获取数据")
        fmt.Println("3.输入show 表示显示队列")
        fmt.Println("4.输入exit 表示对出队列")
        fmt.Scanln(&key)
        switch key {
        case "add":
            fmt.Println("输入你要入队列数")
            fmt.Scanln(&val)
            err := queue.AddQueue(val)
            if err != nil {
                fmt.Println(err.Error())
            } else {
                fmt.Println("加入队列成功")
            }
        case "get":
            val,err:=queue.GetQueue()
            if err !=nil{
                fmt.Println("取出数据出错 err=",err)
            }else {
                fmt.Println("从队列取出了一个数据 =",val)
            }
        case "show":
            queue.ShowQueue()
        case "exit":
            os.Exit(0)//Exit让当前程序以给出的状态码code退出。一般来说,状态码0表示成功,非0表示出错。程序会立刻终止,defer的函数不会被执行。
        }
    }
}

数组模拟环形队列
对前面的数组模拟队列优化,充分利用数组,因此将数组看做是一个环形的(通过取模的方法来实现即可)
提醒:
1) 尾索引的下一个为头索引时,表示队列 满,即将队列容量空出一个作为约定,这个在做判断队列满的时候需要注意(tail+1)%maxsize == head 满]
2) tail == head[空]

分析思路:

分析思路:
1) 什么时候表示队列满(rear+1)%maxSize = front
2) front = rear 空
3) 初始化 front = rear = 0
4) 怎么统计该队列有多少个元素 (rear+maxSize-front)%maxSize

package main
import (
    "errors"
    "fmt"
    "os"
)

//使用一个结构体管理队列
type Queue struct {
    maxSize int    //队列最大长度
    array   [5]int //数组
    front   int    //指向前的标识
    rear    int    //指向尾的标识
}

//添加数据到队列
func (this *Queue) AddQueue(val int) (err error) {
    //先判断队列是否已满
    if (this.rear+1)%this.maxSize == this.front {
        return errors.New("queue full")
    }
    this.array[this.rear] = val
    this.rear++ //rear没有包含最后元素
    return
}
//从队列里面取出数据
func (this *Queue)GetQueue()(val int,err error)  {
    //先判断是否为空
    if this.front==this.rear{
        return 0,errors.New("Queue empty")
    }
    val = this.array[this.front] //front包含首元素
    this.front++
    return
}
//显示当前队列
func (this *Queue) ShowQueue() {
    fmt.Println("队列当前的情况是:")
    size := this.Size()
    if(size==0){
        fmt.Println("队列为空")
    }
    tempHead :=this.front
    for i := 0; i < size; i++ {
        fmt.Printf("array[%d]=%d\n", tempHead, this.array[tempHead])
        tempHead = (tempHead+1)%this.maxSize
    }
    fmt.Println()
}
//取出环形队列又多少个元素
func (this *Queue) Size() int  {
  return (this.rear+this.maxSize-this.front)%this.maxSize
}
func main() {
    queue := &Queue{
        maxSize: 5,
        array:   [5]int{},
        front:   0,
        rear:    0,
    }
    var key string
    var val int
    for {
        fmt.Println("1.输入add 表示添加数据到队列")
        fmt.Println("2.输入get 表示从队列获取数据")
        fmt.Println("3.输入show 表示显示队列")
        fmt.Println("4.输入exit 表示对出队列")
        fmt.Scanln(&key)
        switch key {
        case "add":
            fmt.Println("输入你要入队列数")
            fmt.Scanln(&val)
            err := queue.AddQueue(val)
            if err != nil {
                fmt.Println(err.Error())
            } else {
                fmt.Println("加入队列成功")
            }
        case "get":
            val,err:=queue.GetQueue()
            if err !=nil{
                fmt.Println("取出数据出错 err=",err)
            }else {
                fmt.Println("从队列取出了一个数据 =",val)
            }
        case "show":
            queue.ShowQueue()
        case "exit":
            os.Exit(0)//Exit让当前程序以给出的状态码code退出。一般来说,状态码0表示成功,非0表示出错。程序会立刻终止,defer的函数不会被执行。
        }
    }
}

 知识点:

return 结束当前函数,并返回指定值
runtime.Goexit结束当前goroutine,其他的goroutine不受影响,主程序也一样继续运行
os.Exit 会结束当前程序,不管你三七二十一

链表

 单向链表

头节点一般 不存放数据

package main
import "fmt"
type HeroNode struct {
    no  int
    name string
    nickname string
    next *HeroNode //这个表示指向下一个节点
}
//给链表尾部插入一个节点
func InsertHeroNode(head *HeroNode,newHeroNode *HeroNode)  {
  //找到最后节点
  temp :=head
  for{
      if temp.next == nil{ //找到了最后一个
          break
    }
    temp = temp.next //往下找
  }
  //将元素加入到最后
  temp.next = newHeroNode
}
//根据no 从小到大排序的插入
func InsertHeroNode2(head *HeroNode,newHeroNode *HeroNode)  {
    temp :=head
    //让插入的节点的no 和 temp的下一个节点的no比较
    flag :=true
    for{
        if temp.next == nil{ //找到了最后一个
            break
        }else if(temp.next.no>newHeroNode.no){
              break
        }else if temp.next.no==newHeroNode.no{
            flag = false
            break
        }
        temp = temp.next
    }
    if !flag{
        fmt.Println("已经存在改排名 no=",newHeroNode.no)
    }else {
        newHeroNode.next = temp.next
        temp.next = newHeroNode
    }
}
//删除节点
func DelHeroNode(head *HeroNode,id int)  {
    temp :=head
    flag :=false
    for{
        if temp.next == nil{
            break
        }else if(temp.next.no == id){
            flag = true
            break
        }
        temp = temp.next
    }
    if flag{
        temp.next = temp.next.next
    }else {
        fmt.Println("没有找到节点 id=",id)
    }
}
//显示链表的所有信息
func ListHeroNode(head *HeroNode)  {
    temp :=head
    if temp.next == nil{
        fmt.Println("空链表")
    }
    for{
         fmt.Printf("[%d,%s,%s]==>\n",temp.next.no,temp.next.name,temp.next.nickname)
         temp = temp.next
         if temp.next==nil{
             break
         }
    }
}
func main()  {
    //头节点
    head :=&HeroNode{}
    //创建一个新的HeroNode
    hero1 :=&HeroNode{
        no:       1,
        name:     "宋江",
        nickname: "及时雨",
        next:     nil,
    }
    hero2 :=&HeroNode{
        no:       2,
        name:     "卢俊义",
        nickname: "玉麒麟",
        next:     nil,
    }
    hero3 :=&HeroNode{
        no:       3,
        name:     "林冲",
        nickname: "豹子头",
        next:     nil,
    }
    hero4 :=&HeroNode{
        no:       4,
        name:     "吴用",
        nickname: "智多星",
        next:     nil,
    }
    InsertHeroNode2(head,hero4)
    InsertHeroNode2(head,hero3)
    InsertHeroNode2(head,hero2)
    InsertHeroNode2(head,hero1)
    DelHeroNode(head,5)
    ListHeroNode(head)
}

 双向链表

package main
import "fmt"
type HeroNode struct {
    no       int
    name     string
    nickname string
    next     *HeroNode //这个表示指向下一个节点
    pre      *HeroNode
}

//给链表尾部插入一个节点
func InsertHeroNode(head *HeroNode, newHeroNode *HeroNode) {
    //找到最后节点
    temp := head
    for {
        if temp.next == nil { //找到了最后一个
            break
        }
        temp = temp.next //往下找
    }
    //将元素加入到最后
    temp.next = newHeroNode
    newHeroNode.pre = temp
}

//根据no 从小到大排序的插入
func InsertHeroNode2(head *HeroNode, newHeroNode *HeroNode) {
    temp := head
    //让插入的节点的no 和 temp的下一个节点的no比较
    flag := true
    for {
        if temp.next == nil { //找到了最后一个
            break
        } else if (temp.next.no > newHeroNode.no) {
            break
        } else if temp.next.no == newHeroNode.no {
            flag = false
            break
        }
        temp = temp.next
    }
    if !flag {
        fmt.Println("已经存在改排名 no=", newHeroNode.no)
    } else {
        newHeroNode.pre = temp
        newHeroNode.next = temp.next
        if temp.next != nil { //后面还有节点
            temp.next.pre = newHeroNode
        }
        temp.next = newHeroNode
    }
}

//删除节点
func DelHeroNode(head *HeroNode, id int) {
    temp := head
    flag := false
    for {
        if temp.next == nil {
            break
        } else if (temp.next.no == id) {
            flag = true
            break
        }
        temp = temp.next
    }
    if flag { //找到并删除
        temp.next = temp.next.next
        if temp.next != nil { //判断不然导致空指针
            temp.next.pre = temp
        }
    } else {
        fmt.Println("没有找到节点 id=", id)
    }
}

//显示链表的所有信息
func ListHeroNode(head *HeroNode) {
    temp := head
    if temp.next == nil {
        fmt.Println("空链表")
    }
    for {
        fmt.Printf("[%d,%s,%s]==>\n", temp.next.no, temp.next.name, temp.next.nickname)
        temp = temp.next
        if temp.next == nil {
            break
        }
    }
}

//逆序打印
func ListHeroNode2(head *HeroNode) {
    temp := head
    //把temp移动到链表最后面
    for {
        if temp.next == nil {
            break
        }
        temp = temp.next
    }
    i := 1
    for {
        fmt.Printf("[%d,%s,%s]==>\n", temp.no, temp.name, temp.nickname)
        temp = temp.pre
        if temp.pre == nil {
            break
        }
        i++
    }
}
func main() {
    //头节点
    head := &HeroNode{}
    //创建一个新的HeroNode
    hero1 := &HeroNode{
        no:       1,
        name:     "宋江",
        nickname: "及时雨",
    }
    hero2 := &HeroNode{
        no:       2,
        name:     "卢俊义",
        nickname: "玉麒麟",
    }
    hero3 := &HeroNode{
        no:       3,
        name:     "林冲",
        nickname: "豹子头",
    }
    hero4 := &HeroNode{
        no:       4,
        name:     "吴用",
        nickname: "智多星",
    }
    /*InsertHeroNode(head,hero1)
    InsertHeroNode(head,hero2)
    InsertHeroNode(head,hero3)
    InsertHeroNode(head,hero4)
    fmt.Println("顺序打印:")
    ListHeroNode(head)
    fmt.Println("逆序打印:")
    ListHeroNode2(head)*/
    InsertHeroNode2(head, hero1)
    InsertHeroNode2(head, hero2)
    InsertHeroNode2(head, hero3)
    InsertHeroNode2(head, hero4)
    DelHeroNode(head, 2)
    ListHeroNode2(head)
}

 环形单向链表