五行解决反转链表问题 以及其引伸

题目如下

  1. 反转链表

反转一个单链表。
示例:
  输入: 1->2->3->4->5->NULL
  输出: 5->4->3->2->1->NULL
进阶:
  你可以迭代或递归地反转链表。你能否用两种方法解决这道题?

力扣206题,传送门

解法:

func reverseList(head *ListNode) *ListNode {
    cur := head        //当前节点
    var prev *ListNode //前一个节点(”第一个“前一个节点为nil)
    for cur != nil {
        cur.Next, prev, cur = prev, cur, cur.Next
    }
    return prev
}

没错,就是这么简单XD。

解法详解:

2、3行,定义变量就不用说了。
4行,当当前节点不为空的时候,进行循环。

重点 第5行:该行完成了翻转、指针移动的这两个关键步骤。

使用了连等式(多值赋值)。在这一行,看样子像是给三个变量进行更新赋值,实则它还隐藏着三个临时变量。
这里就需要先了解一下 多值赋值 的运算过程了:
  多值赋值会在当前变量所拥有的值的基础上,先计算等号右侧表达式的各个值,并在必要的情况下存储在临时变量中。再将右侧表达式的各个值,依次赋给等号左侧的变量(顺序为从左到右)。

详细分析多变量的赋值,请看这篇文章→《深入探讨Go多变量赋值》

  1. 先计算(备份)变量的值 到 临时变量中
    即:先备份当前的 prev, cur,记作 Temp_prev, Temp_cur
    计算当前的 cur.Next,存入 Temp_cur_Next中。
  2. 再对节点进行翻倒(赋值)
    cur.Next = Temp_prev  //将 当前节点的后继指针 指向 前一个节点。(完成翻转)
    prev = Temp_cur    //将 前一个节点 定义为 当前节点。(完成前一个节点指针的移动)
    cur = Temp_cur_Next  //将 当前节点 定义为 旧的“当前节点”的下一个节点。(完成当前节点指针的移动)

次重点 第7行:返回的结果为什么是prev(前一个节点),而不是cur(当前节点)?

在 最后一个节点 进行计算前,链表是这样的,1 <- 2 <- 3 <- 4 5 -> nil

  1. 计算后,赋值前的情况(计算了等号右侧的变量值,并存储在了临时变量里,还没有给等号左侧的变量修改值的时候):
    此时:prev = 4; cur = 5; Temp_prev = 4; Temp_cur = 5; Temp_cur_Next = nil;

  2. 计算后,开始对等号左侧的变量进行赋值(开始修改等号左侧变量的时候):
    备注:括号中的值代表的指针当前指向的元素
    cur.Next(5.Next) = prev(4)  //完成了最后两个元素的翻转
    prev = Temp_cur(5)     //即:翻转后得到的新链表的 head
    cur = Temp_cur_Next(nil)  //达到 for解除循环的临界条件:cur = nil

显然,cur = nilcur不能作为结果返回给被调用的函数


作者能力有限,若有哪些地方有误,请在评论区里指正。愿我们一同进步!

深入探讨 Go 多变量赋值

使用go语言时,会经常把一些变量放在同一行来声明、赋值或计算。那么,这种赋值方法到底是以什么样的实现方法来赋值的呢?

先说结论,就两步:
一、先计算等号右侧所有表达式的值,将结果存储临时变量中。
二、将临时变量的值赋给等式左侧的变量。

多个变量一同声明并赋值

a, b := 1, 5        //情况一
c, d := a+b, a-b    //情况二

对于情况一来说,很简单,直接按照从左到右的顺序,把1、2两个值赋值给a、b

  ; -----情况一  相关汇编代码  开始----------
  (.\main.go:4)     MOVQ    1, "".a+24(SP)             ; 将1赋值给a
  (.\main.go:4)     MOVQ5, "".b+16(SP)           ; 将5赋值给b
  ; -----情况一  相关汇编代码  结束----------

对于情况二来说,赋值的过程是这样的:
1. 计算a+b、a-b的值,并存储在两个临时变量中
2. 将运算结果从临时变量中调出来,从左到右依次赋给c, d两个变量。

  ; -----情况二  相关汇编代码  开始----------
  (.\main.go:5)     MOVQ    "".a+24(SP), AX           ; 将a赋值给AX寄存器
  (.\main.go:5)     ADDQ    $5, AX                    ; 将5和AX寄存器里的值相加(结果还保存在AX寄存器里)
  (.\main.go:5)     MOVQ    AX, ""..autotmp_4+56(SP)  ; 将AX寄存器里的值赋值给autotmp_4变量
  (.\main.go:5)     MOVQ    "".a+24(SP), AX           ; 将a赋值给AX寄存器
  (.\main.go:5)     SUBQ    "".b+16(SP), AX           ; 用AX里的值,减去b的值
  (.\main.go:5)     MOVQ    AX, ""..autotmp_5+48(SP)  ; 将AX里的计算结果赋值给autotmp_5变量
  (.\main.go:5)     MOVQ    ""..autotmp_4+56(SP), AX  ; 将autotmp_4变量放入AX寄存器中
  (.\main.go:5)     MOVQ    AX, "".c+8(SP)            ; 将AX寄存器中的值赋给c
  (.\main.go:5)     MOVQ    ""..autotmp_5+48(SP), AX  ; 将autotmp_5变量放入AX寄存器中
  (.\main.go:5)     MOVQ    AX, "".d(SP)              ; 将AX寄存器中的值赋给d
  ; -----情况二  相关汇编代码  结束----------

多个变量一同计算、互换变量值

func main() {
    a, b := 1, 5            //定义a, b
    c, d := a+b, a-b        //定义c ,d = 6, -4
    b, c, d = c, b, b+c     //探究单行多赋值计算
    _ = d                   //在本探究中没啥用的空行
}

对情况三来说,赋值的结果实际上是跟情况二是一样的:
1. 计算c、b、b+c的值(前两个无需计算),并存储在两个临时变量中(解释:为什么不是三个临时变量)
2. 将运算结果从临时变量中调出来,从左到右依次赋给b, c, d三个变量。

为什么不是三个临时变量?
对于前半部分 b, c = c, b ,想达成互换两个的值,只需要一个临时变量即可
temp := b
b = c
c = temp

  ; -----情况三  相关汇编代码  开始----------
  (.\main.go:6)     MOVQ    "".b+16(SP), AX           ; 将b的值,放入AX寄存器
  (.\main.go:6)     ADDQ    "".c+8(SP), AX            ; AX中的值加c的值(结果还保存在AX寄存器里)
  (.\main.go:6)     MOVQ    AX, ""..autotmp_6+40(SP)  ; 把上述计算结果赋值给autotmp_6变量
  (.\main.go:6)     MOVQ    "".b+16(SP), AX           ; 将b的值赋给AX
  (.\main.go:6)     MOVQ    AX, ""..autotmp_7+32(SP)  ; 把AX的值赋给autotmp_7变量
  (.\main.go:6)     MOVQ    "".c+8(SP), AX            ; 将c的值赋给AX
  (.\main.go:6)     MOVQ    AX, "".b+16(SP)           ; 把AX的值(c)赋给b
  (.\main.go:6)     MOVQ    ""..autotmp_7+32(SP), AX  ; 把autotmp_7变量的值赋给AX
  (.\main.go:6)     MOVQ    AX, "".c+8(SP)            ; 把AX的值(autotmp_7)赋给c
  (.\main.go:6)     MOVQ    ""..autotmp_6+40(SP), AX  ; 把autotmp_6变量的值赋给AX
  (.\main.go:6)     MOVQ    AX, "".d(SP)              ; 把AX的值(autotmp_6)赋给d
  ; -----情况三  相关汇编代码  结束----------

汇编代码详解:

实际应用(算法题)

《五行解决反转链表问题》


作者能力有限,若有哪些地方有误,请在评论区里指正。愿我们一同进步!

Go命令行程序(CLIs)框架——Cobra

什么是 CLIs

命令行界面(英语:Command-Line Interface,缩写:CLI)是在图形用户界面得到普及之前使用最为广泛的用户界面,它通常不支持鼠标,用户通过键盘输入指令,计算机接收到指令后,予以执行。也有人称之为字符用户界面(character user interface, CUI)。

虽然现在很多操作系统都提供了图形化的操作方式,但是却都保留了命令行界面,甚至有很多系统更加强了这部分功能。例如服务器端常用的Linux系统,目前就连以可视化桌面为主的Windows,也在强化自己命令行操作指令的功能和数量。
常见的CLI:
* bash、sh、ksh(Unix-like系统)
* COMMAND.COM(MS-DOS系统)
* cmd.exe(Windows命令提示符)
* Windows PowerShell(最新Windows NT系统搭载的CLI)

参考——《wikipedia——命令行界面》

常见的CLI程序
git、apt(Debian系系统的软件包管理器)等

CLI程序的结构

执行命令行程序的一般格式为:
APPNAME COMMAND ARG --FLAG
例如:
* git commit -m '简短的提交说明'
其中,git是程序名(APPNAME);commit是行为(COMMAND);-m是命令行选项,即对行为的改变(FLAG),'简短的提交说明'就是命令行的参数(ARG)。
* git clone URL --bare
其中,git是APPNAME;clone是COMMAND;URL是ARG;--bare是FLAG。


cobra

cobra github代码仓库

cobra是一个可以创建强大的CLI应用程序的一个库,cobra可以用来生成一个支持使用命令控制的程序。

cobra被用在了许多Go开发的程序,比如Kubernetes, Hugo 和 Github CLI 等程序。

cobra提供的功能:

  • 简单易用的基于子程序的CLI,比如app serverapp fetch等。
  • 完全兼容POSIX标志(包括短和长版标志)
  • 支持嵌套子命令
  • 支持全局标志、局部标志、和传递标志
  • 可以非常简单地生成应用程序(cobra init appname)和命令(cobra add cmdname
  • 可以智能纠错(输入错误的命令app servr,会提示“did you meanapp server?”)
  • 自动生成有关命令(COMMEND)和标志(FLAG)的帮助
  • 自动识别帮助标志,例如-h--help
  • 可以自动生成bash、zsh、fish、PowerShell的命令补全功能
  • 自动生成该程序的手册
  • 支持命令别名
  • 可以灵活地定义自己的帮助、用法等信息
  • Optional tight integration with viper for 12-factor apps

简要了解了cobra以及它可以提供的功能以后,我们现在安装cobra吧!

安装cobra(导入cobra包)

安装cobra包:

$ go get -u github.com/spf13/cobra/cobra

使用cobra生成应用程序框架

  1. 在要生成代码的目录下运行(要把程序生成在./appname/下,就要在./的目录下运行命令)
$ cobra init [appname] --pkg-name [pkgname]

此时我们就能在目录下看到由框架自动生成的程序啦!
不过在build这个程序之前,还需要初始化一下go mod,随后就能build了:

$ go mod init [moduleName]

如果要添加子命令,那么就在程序目录(./appname/)下运行:

$ cobra add [commandName]

上述命令执行完毕以后,我们再来看一下目录结构:

  ▾ appName/
    ▾ cmd/
        root.go
        your.go
        commands.go
      main.go

这就是一个cobra程序最基础的目录结构。

编写一个最基础的cobra程序!

我们再来看一下我们刚才通过cobra add [commandName]这条命令添加的新程序的代码:

为什么不看通过cobra init [appname] --pkg-name [pkgname]这条命令生成的./cmd/root.go这个代码?
通过init生成的root.go并不是最简单的cobra程序,其中包括了另一个函数initConfig(),以及一些有关于读取cfgFile的内容。这些内容我认为属于进阶的内容。

/*
    作者代码自带的版权信息:略
*/
package cmd

import (
    "fmt"
    "github.com/spf13/cobra"
)

// miaoCmd represents the miao command
var miaoCmd = &cobra.Command{
    Use:   "miao",//这里需要写commandName,也就是响应这个操作的命令名。

    Short: "A brief description of your command【关于这条命令的简要说明】",
    Long: `A longer description that spans multiple lines and likely contains examples【一个长的说明,并且你可以使用跨越多行的文字,就像现在这个现在这个例子一样】
and usage of using your command. For example:【还有你这条命令的详细用法,例如:】
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.【巴拉巴拉巴拉】`,

    Run: func(cmd *cobra.Command, args []string) {//这里定义了如果执行了这条命令,所要运行的函数。
        fmt.Println("miao called")
    },
}

func init() {
    rootCmd.AddCommand(miaoCmd)

    // Here you will define your flags and configuration settings.
    //【在这里,你将定义你自己的flags和配置结构】

    // Cobra supports Persistent Flags which will work for this command
    //【cobra支持全局的flags,这个flag可以在这条命令,还有它的子命令中被使用】
    // and all subcommands, e.g.:
    miaoCmd.PersistentFlags().String("foo", "", "A help for foo")


    // Cobra supports local flags which will only run when this command
    //【cobra支持局部flags,这类flags只在你运行这条命令的时候被运行。且它会立刻被执行】
    // is called directly, e.g.:
    miaoCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

从生成代码的结构看,它有几这么几个变量和函数:

1. var miaoCmd = &cobra.Command

通过这个变量,定义了一个新的命令,这个命令里有四个

.
.
.
.
.
.
.

Gin网络请求与路由处理——Go Gin框架(二)

创建Engine(引擎)

Engine代表Gin框架的一个结构体定义。
其中包括了路由组、中间件、页面渲染接口、框架配置设置等相关内容

Engine 有以下两种创建方式:

engine1 = gin.Default() //通常使用,会默认使用Logger和Recovery中间件
engine2 = gin.New()

Recovery中间件的作用是:如果程序执行过程中遇到了panic中断了服务,Recovery会恢复程序的执行,并返回服务器500内部错误。
Logger负责打印并输出日志的中间件,方便我们开发调试。

实际上 gin.Default() 内部也是调用 gin.New() 来实现的,但是前者添加了上述两个中间件。

用Engine处理HTTP请求

在实例engine中,包含很多种方法可以直接处理不同类型的HTTP请求。

HTTP协议中义工定义了八种操作方式,分别是:OPTIONS、HEAD、GET、POST、PUT、DELETE、TRACE、CONNECT。
实际上开发上常用的也只有GET、POST、DELETE等几种操作方式。

通用处理请求

engine中可以直接进行HTTP请求的处理,在engine中,可以使用Handle方法进行HTTP请求的处理。Handle方法包含三个参数,如下所示:

func (group *RouterGroup) Handle(httpMethod, relativePath string, handles ...HandlerFunc) IRoutes
  • httpMethod string:第一个参数表示要处理的HTTP的请求类型,是"GET""POST""DELETE"等八种操作方式的其中一种
  • relativePath string:第二个参数表示要解析的接口,由开发者定义。
  • handlers Handlerfunc:第三个参数是处理对应的请求的代码的定义。

分类处理请求

func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes
func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes
//等几个常用的请求处理方法

每个参数的具体含义,请参考通用处理请求中的参数说明。

实例

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
)

func main() {
    engine := gin.Default()
    //第一种处理http请求的方法,使用handle
    //访问 /hello?name=xxxxx
    engine.Handle("GET", "/hello", func(context *gin.Context) {
        //context里面封装了一个上下文环境变量,在这里面当中有我们经常操作的属性或者方法。
        path := context.FullPath() //获取我们本次请求的接口
        fmt.Println(path)
        //获取GET中的参数
        name := context.DefaultQuery("name", "null") //获取具体参数,1.哪个字段 2.若获取不到的默认值
        fmt.Println(name)
        //输出
        _, _ = context.Writer.Write([]byte("hello, " + name))
    })
    //第二种处理http请求的方法(分类处理)
    //访问 /login
    engine.POST("/login", func(context *gin.Context) {
        fmt.Println(context.FullPath())
        //解析POST
        username := context.PostForm("username")
        //这种方法会返回是否成功获取Form里的值,以进行判断
        password, 获取成功 := context.GetPostForm("password")
        if 获取成功 {
            fmt.Println(username, "n", password)
            _, _ = context.Writer.Write([]byte(username + "登录,密码为:" + password))
        }
    })

    //delete
    //在访问路径中,指定id的方法
    //即:/delete/user/123或/delete/user/456(最后一项是一个变量)
    engine.DELETE("/delete/user/:id", func(context *gin.Context) {
        //获取路径中的变量值
        userID := context.Param("id")
        fmt.Println("删除用户:" + userID)
        context.Writer.Write([]byte("删除用户:"+userID))
    })
    //运行这个引擎
    _ = engine.Run(":8088")
}

上述示例代码中,包含了几个新的函数:

  • func (c *Context) FullPath() string: 该函数返回被访问路由的全路径。对于没有找到的路径,返回空
  • func (c *Context) DefaultQuery(key string, defaultValue string) string: 该函数用于GET请求中,用于获取GET请求中的参数。
    • key string:要获取GET请求中的哪个字段
    • defaultValue string:若获取不到则返回的默认值
  • func (c *Context) PostForm(key string) string
  • func (c *Context) GetPostForm(key string) (string, bool): 这两个函数用于获取POST请求中的参数。
    • key string:要获取POST请求中的哪个字段
    • GETPostForm 的第二个bool返回值,用于返回是否成功获取到该字段
  • func (c *Context) Param(key string) string: 用于获取访问路径中的参数值。
    • key string:要获取访问路径请求中的哪个字段,该字段在路径中的表示方法为/:key
  • func (ResponseWriter) Write([]byte) (int, error):用于向浏览器端输出。(返回值占时不知道含义)