- 关于Go语言、VS-code的一些Tips
- undefined1、struct的序列化
- undefined2、VS-CODE的Debug配置
- undefined3、flag包是解析命令行参数输入的很好的工具
关于Go语言、VS-code的一些Tips
undefined1、struct的序列化
struct中的变量命名必须以大写字母开头,否则在使用以下方法序列化时候,其值不会被序列化到[]byte之中,也就是说,反序列化后,其值会丢失。
func (b *Block) Serialize() []byte {varresult bytes.Buffer //定义一个buffer存储序列化后的数据//初始化一个encoder,gob是标准库的一部分//encoder根据参数的类型来创建,这里将编码为字节数组encoder := gob.NewEncoder(&result)err := encoder.Encode(b) //编码if err != nil {log.Panic(err) //如果出错,将记录log后,Panic调用,立即终止当前函数的执行}return result.Bytes()}
undefined2、VS-CODE的Debug配置

如果程序是通过命令行参数运行的,配置文件参考如下:
{"version": "0.2.0","configurations": [{"name": "Launch","type": "go","request": "launch","mode": "auto","remotePath": "","port": 5546,"program": "${fileDirname}","env": {},"args": ["createblockchain","-address","Ivan"]//main的执行命令:main createblockchain -address Ivan//"args": ["printchain"]//main的执行命令:main printchain}]}
undefined3、flag包是解析命令行参数输入的很好的工具
(1)定义CLI处理器
typeCLIstruct{}
(2)检测命令的合法性(普通检查)
//validateArgs 校验命令,如果无效,打印使用说明//参数是否符合命令要求,不在这里进行检查,而在执行命令前检查func (cli \*CLI) validateArgs() {iflen(os.Args) < 2 { //所有命令至少有两个参数,第一个是程序名称,第二个是命名名称cli.printUsage()os.Exit(1)}}
(3)命令行使用说明
//printUsage 打印命令行帮助信息func (cli \*CLI) printUsage() {fmt.Println("Usage:")fmt.Println(" getbalance -address ADDRESS - 获得某个地址的余额")fmt.Println(" createblockchain -address ADDRESS - 创建一个新的区块链并发送创始区块奖励给到address")fmt.Println(" printchain - 打印区块链中的所有区块")fmt.Println(" send -from FROM -to To -amount - 发送amount数量的币,从地址FROM到TO")}
(4)Run 读取命令行参数,执行相应的命令
//使用标准库里面的 flag 包来解析命令行参数:func (cli *CLI) Run() {cli.validateArgs()//定义名称为"getbalance"的空的flagset集合getBalanceCmd := flag.NewFlagSet("getbalance", flag.ExitOnError)//定义名称为"createBlockchainCmd"的空的flagset集合createBlockchainCmd := flag.NewFlagSet("createblockchain", flag.ExitOnError)//定义名称为"sendCmd"的空的flagset集合sendCmd := flag.NewFlagSet("send", flag.ExitOnError)//定义名称为"printchain"的空的flagset集合printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError)//String用指定的名称给getBalanceAddress 新增一个字符串flag//以指针的形式返回getBalanceAddressgetBalanceAddress := getBalanceCmd.String("address", "", "获得金钱的地址")createBlockchainAddress := createBlockchainCmd.String("address", "", "接受挖出创始区块奖励的的地址")sendFrom := sendCmd.String("from", "", "钱包源地址")sendTo := sendCmd.String("to", "", "钱包目的地址")sendAmount := sendCmd.Int("amount", 0, "转移资金的数量")//os.Args包含以程序名称开始的命令行参数switch os.Args[1] { os.Args[0]为程序名称,真正传递的参数index从1开始,一般而言Args\[1\]为命令名称case"getbalance"://Parse调用之前,必须保证getBalanceCmd所有的flag都已经定义在其中err := getBalanceCmd.Parse(os.Args\[2:\]) //仅解析参数,不含命令if err != nil {log.Panic(err)}case"createblockchain":err := createBlockchainCmd.Parse(os.Args\[2:\])if err != nil {log.Panic(err)}case"printchain"://Parse调用之前,必须保证addBlockCmd所有的flag都已经定义在其中//根据命令设计,这里将返回nil,所以在前面没有定义接收解析后数据的flag//但printChainCmd的parsed=trueerr := printChainCmd.Parse(os.Args\[2:\]) //仅仅解析参数,不含命令if err != nil {log.Panic(err)}case"send":err := sendCmd.Parse(os.Args\[2:\])if err != nil {log.Panic(err)}default:cli.printUsage()os.Exit(1)}if getBalanceCmd.Parsed() {if *getBalanceAddress == "" {getBalanceCmd.Usage()os.Exit(1)}cli.getBalance(\*getBalanceAddress)}if createBlockchainCmd.Parsed() {if *createBlockchainAddress == "" {createBlockchainCmd.Usage()os.Exit(1)}cli.createBlockchain(\*createBlockchainAddress)}if printChainCmd.Parsed() {cli.printChain()}if sendCmd.Parsed() {if *sendFrom == "" || *sendTo == "" || *sendAmount <= 0 {sendCmd.Usage()os.Exit(1)}cli.send(\*sendFrom, \*sendTo, \*sendAmount)}}
undefined4、github与vs-code集成,同时打开多个github控制的项目
有时候,我们需要在一个vs-code界面上,同时打开多个受github控制的项目(每个项目为独立的github仓库),其做法如下:
(1)在本地磁盘建立一个顶级文件夹;
(2)打开VS-CODE,克隆存储库

刚刚建立的本地顶级文件夹,输入项目github地址。
(3)VS-CODE关闭文件夹,然后打开刚建立的顶级文件夹
这时候,所有的子文件夹会自动导入进来了。
顶级文件夹下的各子文件夹对应于子项目,独立进行github更新。
平时工作,除非建立分支,一般操作三板斧:Add(+)、Commit、Push,将本地修改同步到云端。
undefined5、map类型的使用
map是go里面非常强大的类型,对于值为struct类型、数组或其他非常复杂的结构的key-value列表,非常适合使用它来进行存储处理。
undefined6、append的两种用法
x := []int {1,2,3}y := []int {4,5,6}//注意下面这两个区别fmt.Println(append(x,4,5,6))fmt.Println(append(x,y...));
第一种用法中,第一个参数为slice,后面可以添加多个参数,这种情况一般是将一个或多个元素加入到现有的数组之中:
x=append(x,4,5,6)
第二种用法,是将两个slice拼接在一起,在第二个slice的名称后面加三个点,构建一个新的数组,这时候append只支持两个参数,不支持任意个数的参数。
z:=append(x,y…)
undefined7、数组的切片引用方式:复用
我们一般在构建了数组实例后,通过函数或方法返回给调用者使用,一般不会直接返回数组指针,而是以切片形式,返回给调用者。这种方式是Go里面的普遍方式。函数或方法内部也是这么使用。
func checksum(payload []byte) []byte {firstSHA := sha256.Sum256(payload)secondSHA := sha256.Sum256(firstSHA[:])//引用firstSHA 数组的全部切片return secondSHA[:addressChecksumLen]//返回用secondSHA的部分切片}
undefined8、判断文件是否存在
if_, err := os.Stat(walletFile); os.IsNotExist(err) {return err}
undefined9、[]byte转为字符串:
hex.EncodeToString(prevTX.ID)//prevTX.ID为[]byte类型
undefined10、break语句
(1)break//直接退出当前层次的循环
(2)break WORK//退出WORK标签对应的代码块,标签要求必须定义在对应的 for、switch 和 select 的代码块上
undefined11、vs-code多命令窗口运行
vs-code可以多窗口运行。

每一个窗口都可以独立设置环境变量
如第一个窗口设置:set NODE_ID=3000,第二个窗口设置环境变量:set NODE_ID=3001,第三个窗口设置为set NODE_ID=3002。
在命令行窗口运行服务后,如希望暂停,通过键盘上ctrl+c组合键执行。
undefined12、go env
可以使用命令行查看 Go 开发包的环境变量配置信息,这些配置信息里可以查看到当前的 GOPATH 路径设置情况。在命令行中运行go env。
undefined13、gobEncode
go内置的gobEncode,可以将一个stuct转换成一个[]byte数组,一个典型的用途是传输远程过程调用(RPC)的参数和结果:
data := block{nodeAddress, b.Serialize()}payload := gobEncode(data)
func gobEncode(data interface{}) []byte {varbuff bytes.Bufferenc := gob.NewEncoder(&buff)err := enc.Encode(data)if err != nil {log.Panic(err)}return buff.Bytes()}
与此对应的是## GobDecoder。
undefined14、通道chan
chan 可以理解为队列,遵循先进先出的规则,用于在不同goroutine之间通信。
// 声明不带缓冲的通道ch1 := make(chan string)// 声明带10个缓冲的通道ch2 := make(chan string, 10)// 声明只读通道ch3 := make(<-chan string)// 声明只写通道ch4 := make(chan<- string)
注意:
不带缓冲的通道,进和出都会阻塞。
带缓冲的通道,进一次长度 +1,出一次长度 -1,如果长度等于缓冲长度时,再进就会阻塞。
undefined写入 chan
ch1 := make(chan string, 10)ch1 <- "a"
undefined读取 chan
val, ok := <- ch1 // 或val := <- ch1
undefined关闭 chan
close(chan)
注意:
- close 以后不能再写入,写入会出现 panic
- 重复 close 会出现 panic
- 只读的 chan 不能 close
- close 以后还可以读取数据
