2018年12月

概念

panicrecover是Go的两个内置函数,这两个内置函数用于处理Go运行时的错误,panic用于主动抛出错误,recover用来捕获panic抛出的错误。

3441746ab2b9d09.jpg

  • 引发panic有两种情况,一是程序主动调用,二是程序产生运行时错误,由运行时检测并退出。
  • 发生panic后,程序会从调用panic的函数位置或发生panic的地方立即返回,逐层向上执行函数的defer语句,然后逐层打印 函数调用堆栈,直到被recover捕获或运行到最外层函数。
  • panic不但可以在函数正常流程中抛出,在defer逻辑里也可以再次调用panic或抛出panicdefer里面的panic能够被后 续执行的defer捕获。
  • recover用来捕获panic,阻止panic继续向上传递。recover()defer一起使用,但是defer只有在后面的函数体内直接 被掉用才能捕获panic来终止异常,否则返回nil,异常继续向外传递。

例子1

//以下捕获失败
defer recover()
defer fmt.Prinntln(recover)
defer func(){
    func(){
        recover() //无效,嵌套两层
    }()
}()

//以下捕获有效
defer func(){
    recover()
}()

func except(){
    recover()
}
func test(){
    defer except()
    panic("runtime error")
}

例子2

多个panic只会捕捉最后一个:

package main
import "fmt"
func main(){
    defer func(){
        if err := recover() ; err != nil {
            fmt.Println(err)
        }
    }()
    defer func(){
        panic("three")
    }()
    defer func(){
        panic("two")
    }()
    panic("one")
}

使用场景

一般情况下有两种情况用到:

  1. 程序遇到无法执行下去的错误时,抛出错误,主动结束运行
  2. 在调试程序时,通过panic来打印堆栈,方便定位错误

其他要说的

go panic与recover分析及错误处理:https://blog.csdn.net/a41888313/article/details/79691728

之前一直用Arduino,现在来尝试一下Python。

烧录

1. 到官网下载固件

http://www.micropython.org/download

2. 安装esptool工具

请确保有Python环境(Linux或Windows)

pip install esptool

3. 清空Flash

这一步很重要,可以避免很多错误:

esptool.py --port /dev/ttyUSB0 erase_flash (Windows下port为COM口)

4. 下载到ESP8266上

下载前先将GPIO0下拉,进入下载模式:

esptool.py --port /dev/ttyUSB0 --baud 460800 write_flash --flash_size=detect 0 esp8266-20180511-v1.9.4.bin

有些模块的Flash是512K的,可能无法下载固件,可以通过esptool来查看:

esptool.py --port /dev/ttyUSB0 flash_id

连接WiFi与远程编程

通过串口连接ESP8266,可以看到串口输出:
25_0.PNG
依次输入:

>>> import network
>>> sta_if = network.WLAN(network.STA_IF)
>>> sta_if.active(True)
>>> sta_if.connect("", "") #SSID,PASSWD
>>> sta_if.isconnected()
>>> print("IP:",sta_if.ifconfig()[0])

连接后可以看到模块的IP。
远程编程需要用到webrepl,你可以使用MicroPython的在线客户端,或从Github获取:

  1. Github:https://github.com/micropython/webrepl
  2. 在线客户端:http://micropython.org/webrepl

在使用webrepl前,需要进行配置,在串口输入:

>>> import webrepl_setup #按提示配置

配置完成后再在串口输入下面的代码,来开启:

>>> import webrepl
>>> webrepl.start()

你可以通过webrepl来上传/下载文件。
webrepl的界面:
25_1.PNG

文件系统

输入下面命令查看当前有的文件:

 >>> os.listdir()
 ['boot.py', 'webrepl_cfg.py', 'main.py']
 boot.py main.py前被执行(开发板启动时将执行这个该脚本,它设置了开发板的多个选项参数) | main.py 主程序

查看剩余空间:

>>> import micropython
>>> micropython.mem_info()
stack: 2112 out of 8192
GC: total: 35968, used: 10000, free: 25968
No. of 1-blocks: 44, 2-blocks: 15, max blk sz: 264, max free sz: 1417

文档

http://docs.micropython.org/en/latest/library/index.html#python-standard-libraries-and-micro-libraries

最近Armbian访问巨慢,刚好有闲置的VPS,就干脆反向代理,来加速访问Armbian源。

Step 1 :你得有个Nginx

Apt直接装:

sudo apt-get install nginx

Step 2 :配置Nginx

由于我只做代理镜像源,所以直接删除默认的配置,重新写了一下:

 server {
         listen 80; #监听端口
         listen [::]:80;
         server_name _;

 location / {
         proxy_pass  https://apt.armbian.com; #源地址
         proxy_redirect     off;
         #替换头
         proxy_set_header   Host             $host;
         proxy_set_header   X-Real-IP        $remote_addr;
         proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
         proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
         proxy_max_temp_file_size 0; #指定当响应内容大于proxy_buffers指定的缓冲区时, 写入硬盘的临时文件的大小
         proxy_connect_timeout      90; #nginx跟后端服务器连接超时时间
         proxy_send_timeout         90; #后端服务器数据回传时间
         proxy_read_timeout         90; #连接成功后,后端服务器响应时间
         proxy_buffer_size          4k; #设置代理服务器保存用户头信息的缓冲区大小
         proxy_buffers              4 32k; #proxy_buffers缓冲区
         proxy_busy_buffers_size    64k; #高负荷下缓冲大小
         proxy_temp_file_write_size 64k; #设定缓存文件夹大小
    }
 }

Step 3 :配置板子源

Armbian的源在/etc/apt/sources.list.d/armbian.list下直接替换就行:

sudo vim /etc/apt/sources.list.d/armbian.list
#分享一下我的反向代理源
deb http://apt.chutian.bid stretch main stretch-utils stretch-desktop
#保存&退出,更新软件源
sudo apt-get update

概念

闭包是由函数及其相关引用环境组合而成的实体,一般提供通过在匿名函数中引用外部函数的局部变量或包全局变量构成。
即:闭包 = 函数 + 引用环境
闭包对闭包外的环境引入是直接引用,编译器检测到闭包,会将闭包引用的外部变量分配到栈上。
如果函数返回的闭包引用了该函数的局部变量(参数或函数内部变量):

  1. 多次调用该函数,返回的多个闭包所引用的外部变量是多个副本,原因是每次调用函数都会为局部变量分配内存
  2. 用一个闭包函数多次,如果该闭包修改了其引用的外部变量,则每一次调用该闭包对该外部变量都有影响,因为闭包函数共享外部引用
package main
func test(a int) func(i int) int{
    return func(i int) int{
        println(&a,a)
        a = a + i
        return a
    }
}
func main(){
    //f,g引用的外部的闭包环境包括本次函数调用的形参a的值1
    f := test(1)
    g := test(1)
    //多次调用的f,g引用的a为同一个a
    println(f(1))
    println(f(1))
    println(g(1))
    println(g(1))
}
//output
0x103100c0 1
2
0x103100c0 2
3
0x103100c4 1
2
0x103100c4 2
3

建议

如果一个函数调用返回的闭包引用修改了全局变量,则每次调用都会影响全局变量。
如果函数返回的闭包引用的是全局变量 a ,则多次调用该函数的返回的多个闭包引用的都是同一个 a 。同理,调用一个闭包多次引用一个 a 。此时如果闭包中修改了 a 值的逻辑,则每次闭包调用都会影响全局变量 a 的值。使用闭包是为了减少全局变量,所以闭包引用 全局变量不是好的编程方式。

 package main
 var a = 0
 func test() func(i int) int{
     return func(i int) int{
         println(&a,a)
         a = a + i
         return a
     }
 }
 func main(){
     f := test()
     g := test()
     //此时f,g引用的闭包环境中的a是同一个
     println(f(1))
     println(g(1))
     println(g(1))
     println(g(1))
     println(f(1))
 }
//output
 0xa9e9c 0
 1
 0xa9e9c 1
 2
 0xa9e9c 2
 3
 0xa9e9c 3
 4
 0xa9e9c 4
 5

价值

闭包最初的目的是减少全局变量,在函数调用的过程中隐式的传递共享变量,有其有用的一面;但是这种隐瞒的共享变量的方式带来的 坏处是不够直接,不够清晰,除非是非常有价值的地方,一般不会使用闭包。
对象是附有行为的数据,而闭包是附有数据的行为,类在定义是就已经显式的集中定义了行为,但是闭包中的数据没有显式的集中 声明的地方,这种数据和行为耦合的模型不是一种推荐的编程模型,闭包只是锦上添花的东西,不是必不可少的。
——《Go 语言核心编程》

其他要说的

什么是闭包?闭包的优缺点?:https://www.cnblogs.com/cxying93/p/6103375.html

SDCC(Small Device C Compiler)是一个优化的 ANSI - C交叉编译器,目标CPU为基于Intel 8051, Maxim 80DS390, Zilog Z80 和Motorola 68HC08 的单片机。

安装

在Debian/Ubuntu下很容易通过Apt来安装:

sudo apt-get install sdcc sdcc-doc

SDCC使用与编译

SDCC到此就安装完了,我们来简单点个灯:

#include "reg51.h"
#define LED P3_0
void Delay1000ms(){             //@11.0592MHz
        unsigned char i, j, k;
        i = 43;
        j = 6;
        k = 203;
        do
        {
                do
                {
                        while (--k);
                } while (--j);
        } while (--i);
}
void main(){
        while(1){
                LED = 0;
                Delay1000ms();
                LED = 1;
        }
}

编译:

sdcc led.c

编译完后看到目录下生成了许多东西:

# ls
led.asm  led.ihx  led.lst  led.mem  led.rst
led.c    led.lk   led.map  led.rel  led.sym

这里我们只关心led.ihx这个文件,我们需要将它转为hex来烧录:

packihx led.ihx > led.hex

在Linux下可以通过stcflash来烧录它:

sudo apt-get install git python python-serial
git clone https://github.com/laborer/stcflash.git
cd stcflash
python stcflash.py [--port /dev/ttyUSB0] <filename.ihx>
//由于我没有支持的单片机,所以无法演示,stcflash的支持列表和更多使用方法详情请看项目的README

SDCC的多文件编译

例如:

//main.c 引用了 test.c中的函数方法
main.c test.c test.h

这样来编译成为一个HEX:

sdcc -c main.c
sdcc -c test.c
sdcc main.rel test.rel

批判一番

手头上只有几片STC15F104E,就是那个定时器有问题的片子,下载只能通过几个特定版本的STC-ISP来下载的刁端货。

函数签名

函数签名

函数类型又叫函数签名,一个函数的类型就是函数定义首行去掉函数名,参数名和{,可以使用fmt.Printf("%T",func)

package main
import "fmt"
func test(a int) int{
    a++
    return a
}
func main(){
    fmt.Printf("%T",test)
}
//output:
func(int) int

特点

  1. 可以用type来定义函数类型,函数类型可以作为函数的参数或返回值。
  2. 函数可以赋值与变量,可以使函数绑定一个变量,通过变量来调用
func test(a int) int{
    a++
    return a
}
func main(){
    t := test
    println(t(1))
}

匿名函数

顾名思义,没有函数名的函数,可直接赋值给变量,可作为实参,也可作为返回值,亦可直接调用。

package main
var moha = func(hath,hath2 int) int{
    return hath + hath2
}
func refunc() func(int)int{
    return func(a int) int{
        a++
        return a
    }
}
func infunc(f func(int) int) int{
    return f(90,2)
}
func main(){
    println(moha(1,2)) //直接调用
    println(refunc()(1)) //调用返回的函数
    println(infunc(moha)) //传入函数
}

延迟调用

defer

Go中提供defer来提供延迟调用,可以注册多个延迟调用,这些延迟调用遵循FILO(先进后出),顺序在函数返回前被执行。defer常用于保证一些资源最终一定能得到回收和释放,比如文件读写。

package main
func main(){
    defer func(){
        println("three")
    }()
    defer func(){
        println("two")
    }()
println("one")
}

特点

  1. FILO(先进后出)
  2. defer后必须是函数或方法的调用,不能为语句,否则报错:expression in defer must be function call
  3. defer函数的实参在注册时通过值拷贝传递进去,后续的操作语句不会对defer的函数造成影响
package main
func main(){
    a := 1
    defer func(a int){
        println(a)
    }(a) //值拷贝
    a++
    println(a)
}
  1. defer只有注册后才能执行,即在return前有效,在return后因为没有被注册而不会执行
  2. 主动退出(os.Exit(int))defer不会被执行
  3. defer最好不要调用有名函数,否则会产生一些奇奇怪怪的错误
  4. defer一般放在错误检查语句后
  5. 最好不要放在循环里

其他要说的

Go 中 defer 的 5 个坑:https://studygolang.com/articles/12061?fr=sidebar

生成公私钥

当然也可以用Rsa模块来生成,不过还是使用openssl吧。
生成一个1024bit的私钥,便于试验,私钥要妥善保管:

openssl genrsa -out private.key 1024

由私钥生成公钥:

openssl rsa -in private.key -pubout -out public.key

你可以使用cat来查看你的私钥或公钥,他应该由如下格式组成:

//私钥
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDhlMzeKVyiyZHVH5a40o......
-----END RSA PRIVATE KEY-----
//公钥
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQ......
-----END PUBLIC KEY-----

Python下

Python2.7环境:

#coding=utf-8
import rsa
import base64
#简单的封装一下
def readfile(filename):
    f = open(filename,'r')
    buf = f.read()
    f.close()
    return buf
def loadkey(private_key,public_key): #加载私钥公钥
    pri = rsa.PrivateKey.load_pkcs1(private_key.encode())
    pub = rsa.PublicKey.load_pkcs1(public_key.encode())
    return pri,pub
def rsa_encrypt(public_key,message): #使用公钥加密,加密后返回的是二进制流,所以用base64模块转成字符串
    return base64.b64encode(rsa.encrypt(message.encode(),public_key))
def rsa_decrypt(private_key,message): #私钥解密,用base64模块转回二进制流解密
    return rsa.decrypt(base64.b64decode(message),private_key).decode()

if __name__ == '__main__':
    private_key,public_key = loadkey(readfile('private.key'),readfile('public.key'))
    message = 'test'
    encrypt_text = rsa_encrypt(public_key,message)
    print encrypt_text
    print rsa_decrypt(private_key,encrypt_text)

返回如下:

HXowzHMPD5vJ4CcAbCoQT1COUnnjqC4w6lu7UH255myj1CZFrCobBDCmDuNRRBxhmOS21SObDt5x5vAaJJcT4WexRNrA0pp3cem+73ea0TD0hH1CoETHo+NxOX0TS2/+sJG0/w8OY1MsXS43RhEt2iUWcUKpbzEI/aBu8T6kzdo=
test

定义

func funcName(param-list)(result-list){
    //code
}

特点

  1. 可无输入输出参数
  2. 多个相邻的相同类型参数可简写

    func test(a int,b int){ ...
    func test(a,b int){ ...

  3. 支持有名的返回值,参数名就相当于函数体内最外层的局部变量,命名返回值变量会被初始化为零值,最后的return可以不带参数名直接返回

    func add(a,b int)(sum int){
    sum = a + b
    return
    //等效于 return sum
    }

  4. 不支持默认值
  5. 不支持函数重载
  6. 不支持命名函数嵌套
  7. func xxx(){,这个{一定要与声明在同一行
  8. 当只有一个返回值时可省去括号

多值返回

与Python相同(或类似?)

func test()(int,int){
    return a,b
}
func main(){
    a,b := test()
}

实参与形参的传递

Go函数实参到形参的传递永远是值拷贝,有时函数调用后实参指向的值发生的改变,那是因为参数传递的是指针值的值拷贝,实参是一个指针变量,传递给形参的是这个指针变量的副本,二者指向同一地址。

不定参数

声明

func funcName(param ...type)

特点

  1. 所有不定参数的类型必须相同
  2. 不定参数必须是函数的最后一个参数
  3. 不定参数在函数内相当于切片,对切片的操作同样适合对不定参数的操作,比如遍历
  4. 切片可以作为参数传递给不变参数,切片名后要加“...”,例子见下面
  5. 形参为不定参数的函数和形参为切片的函数类型不相同
 func sum(arr ...int)(sum int){
    for i,v := range arr{
        sum += v
    }
    return
}
//传参
s := []int{1,2,3,4,5}
sum(s...)

其他要说的

按道理上面的某些代码应该有特殊样式,不过预览看来没有(不会Markdown)。