Go语言编程

Example

  • https://gobyexample.com/
  • https://gobyexample.xgwang.me/

  • 环境,Mac,IDE,命令以及注意事项
brew install go
go env 
GOROOT="/usr/local/Cellar/go/1.9.3/libexec"
vi .bash_profile  && export GOPATH=/Users/UserNmae/IdeaProjects/Golang
cd /Golang && mkdir src, bin, pkg
  • 注意:主函数的必须如下,此处折腾好久,固定思维害人不浅
package main //主函数包名必须为 main
import "fmt"
func main() {
  fmt.Println("Hello,Go.")
}

第1章 初始Go语言

  • 贝尔实验室-Limbo编程语言-Go
  • 2012年第一个正式版本Go发布Google开源
  • 语言特性
    • GC
    • 丰富的内置类型 (简单内置类型,字典类型map,新增数据类型:数组切片(Slice),减少导入包)
    • 函数多个返回值
  func getName()(firstName, middleName, lastName, nickName string){
    return "May", "M", "Chen", "Babe"
  }
  fn, mn, ln, nn := getName()
  // _, _, lastName, _ := getName()
  • 错误处理 (defer, panic, recover,可以减少代码量无需try-catch,方便阅读和维护)
  • 匿名函数和闭包
  f := func(x, y int) int {
    return x + y
  }
  • 类型和接口(不支持继承和重载,支持基本类型组合功能)
//比如java在实现一个接口之前必须先定义该接口,并且将类型和接口紧密绑定,即接口的修改会影响到所有实现类,GO的接口体系避免了这类问题
  type Bird struct {
    // ....
  }
  func (b *Bird) Fly() {
    //以鸟的方式飞行
  }
  type IFly interface {
      Fly()
  }
  func main() {
    var fly IFly = new (Bird)
    fly.Fly()
  }
//虽然Bird类型实现的时候没有生命与接口IFly的关系,但接口和类型可以直接转换,甚至接口的定义都不用在类型定义之前,这种比较松散的对应关系可以大幅降低因为接口调整而导致的大量代码调整工作。  
  • 并发编程
    • goroutine:使得并发编程非常简单。使用gorutine而不是裸用操作系统的并发机制,以及使用消息传递来共享内存而不是使用共享内存来通信。 goroutine是一种比线程更轻盈、省资源的协程。使用关键字go执行。
  //goroutine和channel实例,两个goroutine并行累加计算,待两个计算过程完成后打印计算结果
  package main
  import "fmt"
  func sum(values [] int, resultChan chan int) {
    sum := 0
    for _, value := range values {
      sum += value
    }
    resultChan <- sum //将计算结果发送到channel中
  }

  func main () {
    values := [] int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    resultChan := make(chan int, 2)
    go sum(values[:len(values)/2], resultChan)
    go sum(values[len[(values)/2]:], resultChan)
    sum1, sum2 := <-resultChan, <-resultChan //接收结果
    fmt.Println("Result:", sum1, sum2, sum1 + sum2)
  }

  • 反射(反射最常见的使用场景是做对象的序列化(serialization,有时候也叫Marshal & Unmarshal)。例如,Go语言标准库的encoding/json、encoding/xml、encoding/gob、encoding/binary等包就大量依赖于反射功能来实现。)

  • 语言交互性 (Cgo指的是与C语言的混合特性与Java的JNI不同,更简单)

    package main
    /*
    #include <stdio.h>
    */
    import "C"
    import "unsafe"
    func main () {
      cstr := C.CString("Hello, word")
      C.puts(cstr)
      C.free(unsafe.Pointer(cstr))
    }
  • Go Helloword
  package main   //所属包
  import "fmt"   // 导入依赖包,不得导入没有使用到的包,否则Go便以其器报编译错误
  func main () { //可执行入口
    fmt.Println("Hello, world")
  }

  //Go 函数定义
  func 函数名 (参数列表) (返回值列表) {
    //函数体
  }
  func Comput (value int, value2 float64) (result float64, err error) {
    //函数体
  }

  //go 注释
  /*
  块注释
  */
  // 行注释
  //Go没有分号
  //左花括号 { 不允许另起一行放置
  • Go版本-编译-运行
    • $ go version # go version go1
    • $ go run hello.go # 直接运行(编译、链接、运行合为一步,不会产生中间文件盒可执行文件)
    • $ go build hello.go
    • $ ./hello
    • go test xxx
  • Go资料

第2章 顺序编程

  • 变量
  var v1 int
  var v2 string
  var v3 [10]int  //数组
  var v4 []int    //数组切片
  var v5 struct {
    f int
  }
  var v6 *int   //指针
  var v7 map[string]int  //map, key为string类型,value为int类型
  var v8 func(a int) int

  var (
    v1 int
    v2 string
  )

  //初始化
  var v1 int = 10
  var v2 = 10   //编译器可以推导出v2类型
  v3 := 10      //编译器可以推导出v2类型
  //注意: 出现在:=左侧的变量不应该是已经被声明过的,否则会导致编译错误

  //变量赋值
  var v10 int
  v10 = 123
  //多重赋值
  i, j = j, i //交换i和j变量,否则需要引入中间变量才能交换,比如 t = i; i = j; j = t;  使用得到可以减少代码行数
  • 常量编译期间就已知且不可改变的值。常量可以使整型、浮点和复数、布尔、字符串类型
  //常量定义
  const PI float64 = 3.1415926
  const zero = 0.0    //无类型浮点常量
  const (
    size int64 = 1024
    eof = -1
  )
  const u, v float32 = 0, 3 //u=0.0, v=3.0 多重赋值
  const a, b, c = 3, 4, "foo" //a=3, b=4, c="foo"
  const mask = 1 << 3
  //注意:常量的赋值是一个编译期行为,所以右值不能出现任何需要运行期才能得出结果的表达式
  const Home = os.GetEnv("Home") //编译错误

  //预定义常量 : true、false、1ota(可被编译器修改的常量,在么一个const关键字出现时被重置为0,然后在下一个const出现之前,每出现一次1ota,其所代表的数字自动增1)
  const (           // 1ota被重设置为0
    c0 = 1ota       // c0 == 0
    c1 = 1ota       // c1 == 1
    c2 = 1ota       // c2 == 2
  )
  //如果两个const的赋值语句的表达式是一样的,那么可以省略后一个赋值表达式。因此,上面的语句可以简写为:
  const (           // 1ota被重设置为0
    c0 = 1ota       // c0 == 0
    c1              // c1 == 1
    c2              // c2 == 2
  )
// 枚举
  const (
    Sumday = 1ota
    Monday
    Tuesday
    Wednesday
    Thursday
    Friday
    Staturday
    numberOfDays
    //以大写字母开头的常量在包外可见,以上例子中numberOfDays为包内私有,其他符号则可被其他包访问
  )  

  • 类型
    • 布尔类型 : bool
    • 整型: int8、byte、int16、int、uint、uintptr等
    • 浮点类型: float32、float64
    • 复数类型: complex64、complex128
    • 字符串: string
    • 字符类型: rune
    • 错误类型: error
    • 复合类型:
      • 指针 pointer
      • 数组 array
      • 切片 slice
      • 字典 map
      • 通道 chan
      • 结构体 struct
      • 接口 interface
  • 布尔类型:
  var v1 bool
  v1 = true
  v2 := (1 == 2)  //v2 也会被推导为bool类型
  • 整型
  • 浮点数
  • 复数类型
  • 字符串
  • 数组
  • map
  • 流程控制
    • 选择
    • 循环
    • 跳转
    • 条件语句 if、else和else if
    • 选择语句 switch, case select
    • 循环语句 for和range
    • 跳转语句 goto
  • 循环语句
  sum := 0
  for i := 0; i < 10; i++ {
      sum += 1
  }
//无限循环
  sum := 0
  for {
    sum++
    if sum > 100 {
      break
    }
  }
// 除了支持continue和break,还有更高级的break
for j := 0; j < 5; j++ {
    for i := 0; i < 10; i++ {
      if i > 5 {
        break JLoop
      }
      fmt.Println(i)
    }
}  

JLoop:
//... break语句终止的是JLoop标签处的外层循环

  • 跳转语句 goto
func myFunc() {
  i := 0
  HERE:
  fmt.Println(i)
  i++
  if i < 10 {
    goto HERE
  }
}
  • 函数
func Add(a int, b int) (ret int, err error) {
  if a < 0 || b < 0 {
    err = errors.New("Should be non-negative numbers!")
    return
  }
  return a + b, nil // 支持多重返回值
}

//函数调用
//....
import "mymath"  //假设Add被放在 mymath包中
c := mymath.Add(1, 2)

注意: 小写字母开头的函数只在本包内可见,大写字母开头的函数才能被其他包使用。这个规则也同样适用于类型和变量的可见性。
  • 不定参数
func myfunc(args ...int) {
	for _, arg := range args {
		fmt.Println(arg)
	}
}

//任意类型的不定参数
func Print(format string, args ...interface{}) {
  //....
}
  • 多返回值

  • 匿名函数与闭包

  • 闭包:闭包是可以包含自由(未绑定到特定对象)变量的代码块,这些变量不再这个代码块内或者任何全局上下文中定义,而是在定义代码块的环境中定义。要执行的代码块(由于自由变量包含在代码块中,所以这些自由变量以及他们引用的对象没有被释放)为自由变量提供绑定的计算环境(作用域)。
  • 闭包的价值:在于可以作为函数对象或者匿名函数,对于类型系统而言,这意味着不仅要表示数据还要表示代码。支持闭包的多数语言都将函数作为第一级对象,就是说这些函数可以存储到变量中作为参数传递给其他函数,最重要的是能够被函数动态创建和返回。
  • 错误处理
    • error接口
    • defer
  • panic(), recover()

第3章 面向对象编程

  • 类型系统
  • 结构体 struct
  • 匿名组合
  • 可见性 可见大写字母开头。
  • 接口
    • 接口赋值有两种情况:
      1. 将对象实例赋值给接口
      2. 将一个接口赋值给另一个接口
  • 接口查询
  • 类型查询
  • 接口组合
  • Any类型
  • TODO Finshed MusicsPlayer

第4章 并发编程

  • 多进程,多线程,基于回调的非阻塞/异步IO,协程
  • 在工程上常见的两种并发通信模式: 共享内存系统、消息传递系统
  • CSP commnicating sequential processes
  • 基本语法
var chanName chan ElementType
var ch chan int //比如int类型的channel
var m map[string] chan bool //一个map元素是bool类型的channel
ch := make(chan int) //定义一个channel,int类型
ch <- value   //将一个数据写入(发送)至channel的语法,向channel中写入数据通常会导致程序阻塞直到有其他gorutine从这个channel中读取数据
value := <-ch //从channel中读取数据,如果channel之前没有写入数据,那么从channel中读取数据也会导致程序阻塞,导致channel中被写入数据为止
  • select
select {
  case <-chanl:
    //如果chanl成功读到数据,则记性该case处理语句
  case chan2 <- 1:
    //如果成功向chann2写入数据,则进行该case处理语句
  default:
    //如果上面都没有成功,则进行default处理流程
}
  • 缓冲机制,传输大量数据以达到消息队列的效果
c := make(chan int, 1024) //make第二个参数就是缓冲区大小,这种情况,即使没有读取方,写入方也可以一直往channel里写入,在缓冲区被填完之前都不会阻塞
for i := range c {  //读取缓冲区channel数据
  fmt.Println("Received:", i)
}
  • 超时机制
timeouot := make(chan bool, 1)
go func(){  //执行一个匿名超时等待函数
  time.Sleep(1e9) //等待1s
  timeouot <- true
}{}

select {
case <- ch:
  //从ch中读取到数据
case <- timeouot:
  //一直没有从ch中读取到数据,但从timeouot中读取到了数据  
}
  • channel的传递
type PipeData struct {
  value int
  handler func(int) int
  next chan int
}

func handle(queue chan *PipeData) {
  for data := range queue {
    data.next <- data.handler(data.value)
  }
}
  • 单向channel
var ch1 chan int  //normal channel
var ch2 chan<- float64 //单向channel只用于写float64数据
var ch3 <-chan int  //ch3单向channel,只用于读取int数据
ch4 := make(chan int)
ch5 := <-chan int (ch4) //ch5就是一个党项读取channel
ch6 := chan<- int (ch4) //ch6是一个单向写入channel

func Parse (ch <-chan int) {
  for value := range ch {
    fmt.Println("Parsing value", value)
  }
}
  • 关闭channel,cloose(ch),如何判断一个channel是否已被关闭 x, ok := <-ch

  • 多核并行化
  • 出让时间片,我们可以再每个goroutine中控制何时主动出让时间片给其他goroutine,可以使用runtime包中的Gosched()函数实现
  • 同步
    • 同步锁,Go语言包中的sync包提供两种锁类型: sync.Mutex和sync.RWMutex; 当一个goroutine获得了Mutex后,其他goroutine就只能等这个goroutine释放该Mutex,RWMutex相对好一些,单写多读锁模型。RWMutex类型其实组合了Mutex
//锁的典型用法
var lock sync.Mutex
func foo () {
  lock.Lock()
  defer lock.Unlock()
  //...
}
  • 全局唯一性操作只需要运行一次的代码
var a string
var once sync.one
func setup() {
  a = "Hello,world"
}
func doprint() {
  once.Do(setup)  //保证setup只调用一次
  print(a)
}
func twoprint() {
  go doprint()
  go doprint()
}
  • 完整示例 TODO finished。

第5章 网络编程

  • Scket编程
func Dial(net, addr string) (conn, error)//net:网络协议名字,addr:IP地址域名,而端口号以“:”的形式跟随在地址或域名的后面,端口号凯旋,连接成功返回连接对象,否则返回error
conn, err := net.Dial("tcp", "192.168.0.10:2100")
conn, err := net.Dial("udp", "192.168.0.12:975")
conn, err := net.Dial("ip4:icmp", "www.baidu.com")
conn, err := net.Dial("ip4:1", "10.0.0.3")
Dial()目前支持如下几种网络协议:tcp, tcp4(仅限IPv4), tcp6(仅限IPv6), udp(仅限IPv4), udp6(仅限IPv6), ip, ip4(仅限IPv4), ip6(仅限IPv6)
发送数据使用conn.Write().接收使用Read()
  • ICMP 示例
  • TCP示例
  • HTTP编程
//net/http包的Client类型提供一下几个方法
func (c *Client) Get(url string) (r *Response, err error)
func (c *Client) Post(url string, bodyType string, body io.Reader) (r *Response, err
error)
func (c *Client) PostForm(url string, data url.Values) (r *Response, err error)
func (c *Client) Head(url string) (r *Response, err error)
func (c *Client) Do(req *Request) (resp *Response, err error)
//http.Client结构
type Client struct {
// Transport用于确定HTTP请求的创建机制。
// 如果为空,将会使用DefaultTransport
Transport RoundTripper
// CheckRedirect定义重定向策略。
// 如果CheckRedirect不为空,客户端将在跟踪HTTP重定向前调用该函数。
// 两个参数req和via分别为即将发起的请求和已经发起的所有请求,最早的
// 已发起请求在最前面。
// 如果CheckRedirect返回错误,客户端将直接返回错误,不会再发起该请求。
// 如果CheckRedirect为空,Client将采用一种确认策略,将在10个连续
// 请求后终止
CheckRedirect func(req *Request, via []*Request) error
// 如果Jar为空,Cookie将不会在请求中发送,并会
// 在响应中被忽略
Jar CookieJar
}

type Transport struct {
// Proxy指定用于针对特定请求返回代理的函数。
// 如果该函数返回一个非空的错误,请求将终止并返回该错误。
// 如果Proxy为空或者返回一个空的URL指针,将不使用代理
Proxy func(*Request) (*url.URL, error)
// Dial指定用于创建TCP连接的dail()函数。
// 如果Dial为空,将默认使用net.Dial()函数
Dial func(net, addr string) (c net.Conn, err error)
// TLSClientConfig指定用于tls.Client的TLS配置。
// 如果为空则使用默认配置
TLSClientConfig *tls.Config
DisableKeepAlives bool
DisableCompression bool
// 如果MaxIdleConnsPerHost为非零值,它用于控制每个host所需要
// 保持的最大空闲连接数。如果该值为空,则使用DefaultMaxIdleConnsPerHost
MaxIdleConnsPerHost int
// ...
}

type RoundTripper interface {
// RoundTrip执行一个单一的HTTP事务,返回相应的响应信息。
// RoundTrip函数的实现不应试图去理解响应的内容。如果RoundTrip得到一个响应,
// 无论该响应的HTTP状态码如何,都应将返回的err设置为nil。非空的err
// 只意味着没有成功获取到响应。
// 类似地,RoundTrip也不应试图处理更高级别的协议,比如重定向、认证和
// Cookie等。
//
// RoundTrip不应修改请求内容, 除非了是为了理解Body内容。每一个请求
// 的URL和Header域都应被正确初始化
RoundTrip(*Request) (*Response, error)
}
  • RPC编程 net/rpc
  • JSON处理 encoding/json net/rpc/jsonrpc
  • 网站开发
package main
import (
	"io"
	"log"
	"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, "Hello, GO, Web.")
}
func main() {
	http.HandleFunc("/hello", helloHandler)
	err := http.ListenAndServe(":80", nil)
	if err != nil {
		log.Fatal("ListenAndServe:", err.Error())
	}
}
  • 开发一个简单的相册网站 TODO

第6章 安全编程

  • 数据加密:
    • 单密钥的加密算法称为对称加密,整个系统由如下几个部分构成:需要加密的明文、加密算法和密钥。在加密和解密中,使用的密钥只有一个,常见的单密钥加密算法有DES、AES、RC4等
    • 双密钥称为非对称加密,系统由:需要加密的明文、加密算法、私有密钥和公密钥。在该系统中,私钥和公钥都可以被用作加密或解密,但是用私钥加密的明文,必须要对应的公钥解密,用公钥加密的明文,必须用对应的私钥解密。常见双密钥算法有RSA等。私钥不能暴漏。
  • 数字签名
  • 数字证书
  • PKI体系:PKI,全称公钥基础设施,是使用非对称加密理论,提供数字签名、加密、数字证书等服务的体系,一般包括权威认证机构(CA)、数字证书库、密钥备份及恢复系统、证书作废系统、应用接口(API)等。围绕PKI体系,建立了一些权威的、公益的机构。它们提供数字证书库、密钥备份及恢复系统、证书作废系统、应用接口等具体的服务。比如,企业的数字证书,需要向认证机构申请,以确保数字证书的安全。
  • 加密通信SSL(Secure Sockets Layer)
    1. 在浏览器中输入HTTPS协议的网址
    2. 服务器向浏览器返回证书,浏览器检查改证书的合法性
    3. 验证合法性。
    4. 浏览器使用证书中的公钥加密一个随机对称密钥,并将加密后的密钥和使用密钥加密后的请求URL一起发送到服务器
    5. 服务器用私钥解密随机对称密钥,并用获取的密钥解密加密的请求URL
    6. 服务器把用户请求的网页用密钥加密,并返回用户
    7. 用户浏览器用密钥解密服务器发来的网页数据,并将其显示
    • 示例

第7章 工程管理

  • 代码风格
  • 文档风格和管理
  • 单元测试与性能测试方法
  • 项目工程结构
  • 跨平台开发
  • 打包分发

第8章 开发工具

  • gedit

第9章 进阶话题

  • 对所有的接口进行反射,Type和Value:Type表达的是被反射的这个变量本身的类型信息,Value是该变量实例本身的信息。
package main
import (
	"fmt"
	"reflect"
)
func main() {
	var x float64 = 3.4
	fmt.Println("type:", reflect.TypeOf(x))
  fmt.Println("value:", reflect.ValueOf(x))
}
  • 对结构的反射操作
type T struct {
  A int
  B string
}
  t := T{203, "mh203"}
  s := reflect.ValueOf(&t).Elem()
  typeOfT := s.Type()
  for i := 0; i < s.NumField(); i++ {
  f := s.Field(i)
  fmt.Printf("%d: %s %s = %v\n", i,
  typeOfT.Field(i).Name, f.Type(), f.Interface())
  }
//0:A int = 203
//1:B string = mh203