分类 编程/笔记 下的文章

生成公私钥

当然也可以用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)。

Python下自带的Socket的模块是阻塞型的,每次只能处理一个连接,给应用造成了极大的不便,幸好有Gevent这个基于 协程的网络库,来使默认的Socket支持并发。

代码如下:

#coding=utf-8
from gevent import monkey; monkey.patch_all() //猴子魔法[1]
import socket
import gevent

def server_req(conn):
        while True:
                data = conn.recv(1024) #等待传入数据
                if not data:
                        conn.close() #关闭连接
                        break
                print('recv:%s'%data)
                conn.send(data) #发送数据

def server_mian(s_addr,s_port):
        s = socket.socket()
        s.bind((s_addr,s_port)) #绑定端口
        s.listen(500) #最大连接数
        while True:
                cli,addr = s.accept() #等待一个客户
                gevent.spawn(server_req,cli) #创建一个协程,将客户传入server_req函数

if __name__ == '__main__':
    server_mian('0.0.0.0',3000)

[1]猴子魔法(Monkey Patch):https://www.gevent.org/api/gevent.monkey.html

条件语句

if 语句

用法

if 条件 {
     //当条件为真时执行
}

if 条件 {
     //当条件为真时执行
} else {
     //当条件为假时执行
}

if 条件 {
     //当条件为真时执行
} else if 条件2 {
     //当条件2为真时执行
} else {
     //全部不满足时执行
}

特点

  1. if后的条件不需要用小括号括起来
  2. {必须放在行末,与if或if else放在一行
  3. if后可带一个简单的初始化语句,并以分号分割,所声明变量的作用域为整个if语句块(包括后面的分支)
  4. Go语言没有条件运算符
  5. 遇到return后直接返回,遇到break则跳过break下方的if语句块

编程哲学

  1. 尽量减少条件语句的复杂度,如果条件语句太多,太复杂,则建议放到函数里封装起来
  2. 尽量减少if语句的嵌套层次,通过重构代码来使代码变得扁平,便于阅读

switch 语句

用法

switch {
     case 条件:
         //执行语句
     case 条件:
         //执行语句
     default:
         //所有条件不满足时执行
}

特点

  1. 同if一样,switch后可带一个简单的初始化语句
  2. switch后的表达式也是可选的,如果没有表达式,则case的子句是一个布尔表达式,而不是一个值,就像多重if...else...语句
  3. switch可以任意支持相等比较运算的类型变量
  4. 通过fallthough来强制执行下一个case子句(不判断下一case分支的条件)
  5. switch支持default语句,当所有条件不支持时执行default中的语句,default的位置不影响switch的判断逻辑
  6. switch和.(type)结合可以进行类型的查询(这个以后会学到)

循环语句

for 语句

Go语言只有一个循环语句——for

用法

//1.类似C中的for语句
for init;condition;post { }
//2.类似C中的while语句
for condition { }
//3.死循环
for { }
//for也可以对数组,切片,字符串,map和通道的访问

标签与跳转

Go使用标签来标识一个语句的位置,用于goto,break,continue语句的跳转。

标签

Lable: Statement

goto

goto用来跳转到函数内指定的标签

goto Lable

特点

  1. 只能在函数内跳转
  2. goto不能跳过内部变量声明语句
  3. goto只能跳转到同级作用域或上层作用域内,不能跳转到内部作用域

break

break用于函数内跳出for,switch,select语句的执行,有两种使用格式:

  1. 单独使用,用于跳出break当前所在的for,switch,select语句的执行
  2. 和标签一起使用,用于跳出标签所标识的for,switch,select语句的执行,可用于跳出多重循环,但标签与break必须在同一个函数内

continue

continue用于跳出for循环的本次迭代,跳到for循环下一次迭代的post语句执行,也有两种使用格式:

  1. 单独使用,用于跳出continue当前所在的for循环的本次迭代
  2. 和标签一起使用,用于跳出标签所标识的for语句的本次迭代,但标签与continue必须在同一个函数内

切片

在Go中由于数组的定长性和值拷贝的特性限制了数组的使用场景,使用Go提供了另外一种数据类型slice(切片),这是一种变长数组。

声明

//由make创建
SliceName := make([]Type,Len,Cap)  //可不填写Cap,但会使Cap=Len
//由数组创建
ArrayName := [...]Type{value1,value2,value3}
SliceName := Array[b,e]  //b:开始索引 e:结束索引 都可不指定
//直接声明切片类型变量是没有意义的
var a []int  //打印结果:[]
//此时array=nil,len=0,cap=0

切片的操作

操作

//长度
len(Slice)
//底层数组容量
cap(Slice)
//添加元素
append(Slice,Value)
//复制切片
copy(Slice1,Slice2)

例子

package main
import "fmt"

func PrintSliceInfo(s1,s2 []int){
        fmt.Println("--------------")
        fmt.Println("s1:",s1)
        fmt.Println("s2:",s2)
        fmt.Println("s1 cap:",cap(s1))
        fmt.Println("s2 cap:",cap(s2))
        fmt.Println("s1 len:",len(s1))
        fmt.Println("s2 len:",len(s2))
        fmt.Println("--------------")
}
func main(){
        a := [...]int{0,1,2,3,4,5}
        s1 := make([]int,3,6)
        s2 := a[3:]
        PrintSliceInfo(s1,s2)
        s1 = append(s1,23)
        s2 = append(s2,233)
        PrintSliceInfo(s1,s2)
        s3 := make([]int,3,4)
        copy(s3,s2)
        fmt.Println("--------------")
        fmt.Println("s3:",s3)
        fmt.Println("s3 len:",len(s3))
        fmt.Println("s3 cap:",cap(s3))
        fmt.Println("--------------")
}
//打印结果
--------------
s1: [0 0 0]
s2: [3 4 5]
s1 cap: 6
s2 cap: 3
s1 len: 3
s2 len: 3
--------------
--------------
s1: [0 0 0 23]
s2: [3 4 5 233]
s1 cap: 6
s2 cap: 8
s1 len: 4
s2 len: 4
--------------
--------------
s3: [3 4 5]
s3 len: 3
s3 cap: 4
--------------

字典

声明

//通过字面量创建
MapName := map[Type1]Type2{"key":value,"key2",value2}
//通过Make创建
MapName := make(map[Type1]Type2)

用法

//添加与更新,访问一个键
MapName[key] = value
value := MapName[key]
//删除
delete(MapName,key)
//长度
len(MapName)
//迭代
for key,value := range MapName{
    //code
    fmt.Println("key=",key,"value=",value)
}

注意

  1. Go内置的Map不是并发安全的,并发安全可以使用标准包sync中的map
  2. 不要直接修改map value中的某个元素的的值,如果需要修改,则必须整体重新赋值
  3. 可以使用range迭代,但不保证每次的迭代顺序相同

结构

Go中的struct(结构)与C类似。

声明

//通过字面量,这种方法用的较少
struct{
FeildName  FeildType
FeildName  FeildType
FeildName  FeildType
}
//通过自定义类型
type TypeName struct{
FeildName  FeildType
FeildName  FeildType
FeildName  FeildType
}

示例

type test struct{
Name String
Age int
}
//不推荐下面的方式来初始化
a := test{"test",23}
//建议使用下面的方法,避免新增字段导致报错,没有赋值的字段默认为零值
c := test{
Name:"test",
Age:23,
}

访问

Value := TypeName.FeildName

注意

  1. struct中的类型可以是任意类型
  2. struct的存储空间是连续的,其字段按照声明时的顺序存放(注意字段之间对齐!)

其他要说的

切片的原理

请务必看一下,了解切片的工作原理!
https://www.cnblogs.com/hutusheng/p/5492418.html

变量

声明

在Go中这样来声明变量:

var Name Type [ = value]  //不指定默认值,默认为零值
var Name = [ = value]
var (
a int = 0
b string = "test"
)

或这样简单的声明,这种方法只能用在函数(或方法)中:

Name := value  //自动判断类型
Name1,Name2 := value1,value2

变量类型与命名规则请看上一篇文章。

存储与生存期

Go语言提供自动内存管理,通常不需要特别关注变量的生存期和存放位置。Go使用了栈逃逸技术能为变量自动分配空间:可能在栈上,有可能在堆上。
关于栈逃逸技术:https://studygolang.com/articles/10026

类型转换

在Go中,不同于js之类的语言,不同类型的变量要类型转换:

TypeName(expression)  //expression:表达式或变量名

例:

var a int = 1
var b int8 = 2

a = b  //非法
a = int(b)

常量

常量中的内容不可改变:

const Name [ = value/iota]

例:

const (Name1 = iota  //Name1 = 0
       Name2  //Name2 = 1
)
//关于iota请看上一篇文章

指针

Go支持指针,也支持多级指针,指针的声明类型为T(多级:*T),在变量名前加&来获取地址:

var a = 233
b := &a
println(a)
println(b)
println(*b)
//值打印结果
233
0x11f3bfb4  //内存地址
233

又一个例子:

package main
func test(a,b int) *int {
    c := a * b  //c的作用域在函数内
    println(&c)
    return &c
}
func main() {
    p := test(2,3)
    println(p)
}
//打印结果
0x1233bfb8
0x1233bfb8
//Go编译器使用栈逃逸使这种局部变量空间分配到栈上。

由于Go的垃圾回收机制,所以Go不支持指针运算。

数组

声明

var Name [Size]type

例:

arr := [3]int{1,2,3}
arr2 := []int{1,2,3,4,5,6,7}  //自动判断长度
arr3 := [3]int{1:1,2:3}  //指定默认值,没有指定的使用类型默认值
arr4 := []int{1:2,6:4}  //指定判断长度,由最大决定

特性

  1. 数组一旦创建就固定了,不能再追加元素
  2. 数组是值类型的,数组赋值或做为函数参数都是值拷贝
  3. 数组长度是数组的组成部分,[10]int != [20]int
  4. 可根据数组创建切片

元素访问

a := [...]int{1,2,3}
for index,value := range a {
    fmt.Println(value)
}

数组长度

len(Name)

range

Go 语言中 range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对的 key 值。

其他要说的

字符串运算

参考:

  1. https://blog.csdn.net/hatlonely/article/details/79156311
  2. https://www.cnblogs.com/yshyee/p/8810931.html

控制结构

啊?这么快就到for了?

标识符

标识符用来标示变量,类型,常量等语法对象的符号名称,在语法分析的时候作为一个token存在。(token会在文末解释)

Go 的关键字

Go只有25个关键字(当然,Go是一门极简的语言):

break  default  func  interface  select  case  defer  go  map  struch  chan  else  goto
package  switch  const  fallthrough  if  range  type  continue  for  import  return  var

数据类型与常量标识符

这些同样不可用作声明变量等:

数据类型

数值(16):
    整形(12):
        #int与uint的区别是有无符号,int(uint),int8(uint8)等的区别为bit数。
        byte  int  int8  int16  int32  int64
        uint  uint8  uint16  uint32 uint64 uintptr
    浮点(2):
        float32  float64
    复数(2):
        complex64  complex128
字符与字符串(2):
    string rune
接口型(1):
    error
布尔型(1):
    bool

常量

bool型: true false
常量计数器: iota
空: nil

空白标识符

声明匿名变量,占位标识等: _

标识符规则

标识符开头可以为“A-Z”,“a-z”以及“_”,标识符不能为纯数字(或数字开头),如以下例子:

aaaaa  //合法
aaaaa2  //合法
_aaaaa //合法
-aaaaa  //非法标识符
9aaaaa  //非法
_aaaaa2  //非法

运算符

Go语言有47个操作符,同样作为一个token存在:

算数运算符

+  -  *  /

位运算符

&  |  ^  &^  >>  <<

赋值与复核运算符

:=  =  +=  -=  *=  /=  %=  &=  |=  ^=  &^=  >>=  <<=

比较运算符

>  >=  <=  <  ==  !=

括号

(  )  {  }  [  ]

逻辑运算符

&&  ||  !

自增自减操作符

++  --

其他运算符

:  ,  ;  .  ...  <-

token

引用《Go语言核心编程》:

token是构成源程序的基本不可再分割的单元。编译器第一步就是将源程序分割为一个个独立的token,这个过程就是语法分析。Go语言的token可以分为关键字,标识符,操作符,分隔符和字面常量。

如下面个例子:

fmt.Printf("Hello World!\n")
这里有6个token
分别为:
fmt
.
Printf
(
"Hello World!\n"
)

iota

iota只能在常量表达式中使用,在枚举中应用广泛:

const (
a = iota
b
c
)
你可以试着打印这几个常量的值

9_0.png

其他要说的

运算符资料

  1. https://www.jb51.net/article/129776.htm
  2. https://studygolang.com/articles/12463

数据类型

  1. https://blog.csdn.net/qq_31179577/article/details/81486883

环境安装

在一般情况下,可以使用系统自带的包管理器来安装,不过版本肯定会老一些:

sudo apt-get install golang

也可以从 https://golang.google.cn/dl/ 下载最新的二进制包:

//截止目前最新为1.11.2,以Linux amd64为例
wget https://dl.google.com/go/go1.11.2.linux-amd64.tar.gz
tar -C /usr/local -xzf go1.11.2.linux-amd64.tar.gz
//切换到用户目录,设置环境变量
cd && sudo vi .bashrc
//在末尾加入
PATH=$PATH:/usr/local/go/bin
//保存退出,更新环境变量
source .bashrc

Hello World

package main //包名
import "fmt" //标准输入输出包
func main() { //主函数
     fmt.Printf("Hello World!\n"); //这里也可以改成中文的“你好,世界”,因为环境默认utf-8
}

保存,输入go run Filename来运行。

root@ubuntu:~/go# go run t1.go
Hello World!