GolangGolang 入门资料+笔记Golang语言社区

Golang笔记2--基础语法

2019-08-12  本文已影响4人  奶爸撸代码

Golang笔记2--基础语法

一个大的程序是由很多小的基础构件组成的。变量保存值,简单的加法和减法运算被组合成较复杂的表达式。基础类型被聚合为数组或结构体等更复杂的数据结构。然后使用if和for之类的控制语句来组织和控制表达式的执行流程。然后多个语句被组织到一个个函数中,以便代码的隔离和复用。函数以源文件和包的方式被组织

程序结构

命名

break      default       func     interface   select
case       defer         go       map         struct
chan       else          goto     package     switch
const      fallthrough   if       range       type
continue   for           import   return      var
内建常量: true false iota nil

内建类型: int int8 int16 int32 int64
          uint uint8 uint16 uint32 uint64 uintptr
          float32 float64 complex128 complex64
          bool byte rune string error

内建函数: make len cap new append copy close delete
          complex real imag
          panic recover
const lowerhex = "0123456789abcdef"
//QuoteRuneToASCII ...
func QuoteRuneToASCII(r rune) string

func appendQuotedRuneWith(buf []byte, r rune, quote byte, ASCIIonly, graphicOnly bool) []byte

声明

声明语句定义了程序的各种实体对象以及部分或全部的属性. Go语言主要有四种类型的声明语句:var、const、type和func,分别对应变量、常量、类型和函数实体对象的声明

package main

// 单行
// import "time"
// import log "github.com/sirupsen/logrus"
// 或者:
import(
    "time"
    // third-party包, 别名: log
    log "github.com/sirupsen/logrus"
)

const version = "0.0.1"

// Printer is a exported struct
type Printer struct {
    name string
}

//Print is a exported method
func(p *Printer) Print(){
    _ = printTime(time.Now()) //nolint
}

// 函数
func printTime(t time.Time) error{
    log.Info("now time: ", time.Now(), " version: ", version)
    return nil
}

// 主函数
func main() {
    var printer Printer
    printer.Print()
}

变量

var i,j,k int
var b, f, s = true, 2.3, "four" // bool, float64, string
var f, err = os.Open(name)  //函数返回多个值
i, j := 0, 1
// 无冒号则为赋值操作
i, j = j, i // 交换 i 和 j 的值

指针变量

package main

import (
    "flag"
    "fmt"
    "strings"
)

// flag.Bool函数会创建对应标志参数的变量:
// 三个属性:名字“n”,默认值(这里是false),最后是描述信息
var n = flag.Bool("n", false, "omit trailing newline")
var sep = flag.String("s", " ", "separator")

func main() {
    flag.Parse()
    fmt.Print(strings.Join(flag.Args(), *sep))
    if !*n {
        fmt.Println()
    }
}

变量生命周期和作用域

赋值

// 后续了解
v, ok = m[key]             // map lookup
v, ok = x.(T)              // type assertion
v, ok = <-ch               // channel receive

v = m[key]                // map查找,失败时返回零值
_, exists := m[key] // _占位

类型

type Celsius float64    // 摄氏温度
type Fahrenheit float64 // 华氏温度

func(c Celsius) String() string{
    return fmt.Sprintf("%g°C", c)
}
var c Celsius
var f Fahrenheit
fmt.Println(c == 0)          // "true"
fmt.Println(f >= 0)          // "true"
fmt.Println(c == f)          // compile error: type mismatch
fmt.Println(c == Celsius(f)) // "true"!

控制流结构

gpl并没有专门章节讲解基本控制流, 这里简单列举下吧.

// condition 为真,否则
if condition {
    ...
} else {
    ...
}

// 惯用一
if ok:= function(); ok {
 ...   
}

// 惯用二, 判定是否出错
if val, err:= function(); err!=nil {
    ...
}

switch x := 0; x {
case 0:
    fmt.Println(0)
case 1:
    fallthrough
default:
    fmt.Println("other")
}

// 不止是整型
switch coinflip() {
case "heads":
    heads++
case "tails":
    tails++
default:
    fmt.Println("landed on edge!")
}

// 无tag, 表达式
func Signum(x int) int {
    switch {
    case x > 0:
        return +1
    default:
        return 0
    case x < 0:
        return -1
    }
}

i := 0
for ; i < 10; {
    i++
}

// or
for i:=0; i<10; i++ {
}

// 无限循环
for {
}

//slice、数组的range迭代
for i,value:=range someSlice {
    //...
}

//map range迭代
for k,v:=range someMap {
    ...
}

// goto语句可以无条件地转移到过程中指定的行
if condition {
    goto End
}
End:
   close(xxx)

//跳出内层循环,不在执行循环
for {
    if condition {
        break
    }
}

// continue 继续下一次迭代
for {
    if condition {
        continue
    }
    // other states  skipped
}

// 跳出外层循环, 使用Label

OutLoop:
for {
    for i:=0; i<10;i++ {
        if condition {
            break OutLoop
        }
    }
}

//for-select
Loop:
for {
    select {
        case v, ok:=<-someChan:
            if !ok {
                break Loop
            }
            //...
        case time.After(time.Second):
        break Loop
    }
}

包和文件

基础数据类型

数字、字符串和布尔型。复合数据类型——数组结构体

整型

*      /      %      <<       >>     &       &^
+      -      |      ^
==     !=     <      <=       >      >=
&&
||
+      一元加法 (无效果)
-      负数
&      位运算 AND
|      位运算 OR
^      位运算 二元操作符 XOR, 一元操作符为取反
&^     位清空 (AND NOT)
<<     左移
>>     右移

浮点数

var z float64
fmt.Println(z, -z, 1/z, -1/z, z/z) // "0 -0 +Inf -Inf NaN"

复数

提供两种精度复数: complex64complex128

布尔型

不能直接和整型0, 1转换

字符串

import "unicode/utf8"

w := "世界"
// "\xe4\xb8\x96\xe7\x95\x8c"
// "\u4e16\u754c"
// "\U00004e16\U0000754c"



s := "Hello, 世界"
fmt.Println(len(s))                    // "13"
fmt.Println(utf8.RuneCountInString(s)) // "9"

for i := 0; i < len(s); {
    r, size := utf8.DecodeRuneInString(s[i:])
    fmt.Printf("%d\t%c\n", i, r)
    i += size
}
fmt.Println(string(65))     // "A", not "65"
fmt.Println(string(0x4eac)) // "京"

标准库中有四个包对字符串处理尤为重要:bytes、strings、strconv和unicode包

import "strconv"

x := 123
y := fmt.Sprintf("%d", x)
fmt.Println(y, strconv.Itoa(x)) // "123 123"

x, err := strconv.Atoi("123")             // x is an int
y, err := strconv.ParseInt("123", 10, 64) // base 10, up to 64 bits

常量

const (
    _ = 1 << (10 * iota)
    KiB // 1024
    MiB // 1048576
    GiB // 1073741824
    TiB // 1099511627776             (exceeds 1 << 32)
    PiB // 1125899906842624
    EiB // 1152921504606846976
    ZiB // 1180591620717411303424    (exceeds 1 << 64)
    YiB // 1208925819614629174706176
)

复合数据类型

数组

var a [3]int
var a [...]int={1,2,3}
r := [...]int{99: -1} //100 items

切片slice

make([]T, len)
make([]T, len, cap) // same as make([]T, cap)[:len]

// append
var s,ss []string
s = append(s, 'a')
ss = append(ss, s...)
type IntSlice struct {
    ptr      *int
    len, cap int
}

map

哈希表是一个无序的key/value对的集合,key唯一,通过给定的key可以在常数时间复杂度内检索、更新或删除对应的value, map类型的零值是nil

// 创建,`make`可创建map
ages1 := make(map[string]int)
ages1["alice"] = 32

ages2 := map[string]int{
    "alice":   31,
    "charlie": 34,
}

// 删除对应key元素
delete(ages, "alice") 

// 是否存在
if _, exists:= ages2["alice"]; exists{
    fmt.Println("exists")
}

// access
ages["bob"]++

// range遍历
for k, v:=range ages2{
    fmt.Println(k,v)
}

结构体

// 一般一行对应一个成员,也可以合并, 成员名字在前,类型在后
type Employee struct {
    ID           int
    Name,Address string
}

var dilbert Employee
type tree struct {
    value       int
    left, right *tree
}

// Sort sorts values in place.
func Sort(values []int) {
    var root *tree
    for _, v := range values {
        root = add(root, v)
    }
    appendValues(values[:0], root)
}

// appendValues appends the elements of t to values in order
// and returns the resulting slice.
func appendValues(values []int, t *tree) []int {
    if t != nil {
        values = appendValues(values, t.left)
        values = append(values, t.value)
        values = appendValues(values, t.right)
    }
    return values
}

func add(t *tree, value int) *tree {
    if t == nil {
        // Equivalent to return &tree{value: value}.
        t = new(tree)
        t.value = value
        return t
    }
    if value < t.value {
        t.left = add(t.left, value)
    } else {
        t.right = add(t.right, value)
    }
    return t
}
type Point struct{ X, Y int }

// 按照顺序
p := Point{1, 2}
// 指定成员名字
anim := gif.GIF{LoopCount: nframes}
pp := &Point{1, 2}
// 等价于
pp := new(Point)
*pp = Point{1, 2}
// 原始版本
type Circle struct {
    X, Y, Radius int
}

type Wheel struct {
    X, Y, Radius, Spokes int
}

相同属性独立出来, 便于维护

type Point struct {
    X, Y int
}

type Circle struct {
    Center Point
    Radius int
}

type Wheel struct {
    Circle Circle
    Spokes int
}

但是访问繁琐:

w.Circle.Center.X = 8
w.Circle.Center.Y = 8

结构体内只声明数据类型而不指名成员名,这类成员就叫匿名成员

type Circle struct {
    Point
    Radius int
}

type Wheel struct {
    Circle
    Spokes int
}

这样访问成员(显式形式访问这些内部成员的语法依然有效):

var w Wheel
// 快捷方式
w.X = 8            // equivalent to w.Circle.Point.X = 8
w.Y = 8            // equivalent to w.Circle.Point.Y = 8
w.Radius = 5       // equivalent to w.Circle.Radius = 5
w.Spokes = 20

但字面值定义需要遵循层次:

w = Wheel{Circle{Point{8, 8}, 5}, 20} //or
w = Wheel{
    Circle: Circle{
        Point:  Point{X: 8, Y: 8},
        Radius: 5,
    },
    Spokes: 20, // NOTE: trailing comma necessary here (and at Radius)
}

fmt.Printf("%#v\n", w)

注意:

json

标准库中的encoding/json、encoding/xml、encoding/asn1等包提供支持, 另外还有大量第三方json库可用(protobuf的jsonpb,jsoniter...)

type Movie struct {
    Title  string
    Year   int  `json:"released"`
    Color  bool `json:"color,omitempty"`
    Actors []string
}

struct转换为json的过程叫编码(marshaling):

data, err := json.Marshal(movies)
if err != nil {
    log.Fatalf("JSON marshaling failed: %s", err)
}
fmt.Printf("%s\n", data)

输出无缩紧,难以阅读(注意: 在最后一个成员或元素后面并没有逗号分隔符):

[{"Title":"Casablanca","released":1942,"Actors":["Humphrey Bogart","Ingr
id Bergman"]},{"Title":"Cool Hand Luke","released":1967,"color":true,"Ac
tors":["Paul Newman"]},{"Title":"Bullitt","released":1968,"color":true,"
Actors":["Steve McQueen","Jacqueline Bisset"]}]

因此,还可以使用:

data, err := json.MarshalIndent(movies, "", "    ")
//...
var titles []struct{ Title string }
if err := json.Unmarshal(data, &titles); err != nil {
    log.Fatalf("JSON unmarshaling failed: %s", err)
}
fmt.Println(titles) // "[{Casablanca} {Cool Hand Luke} {Bullitt}]"

基本的JSON类型有数字(十进制或科学记数法)、布尔值(true或false)、字符串,其中字符串是以双引号包含的Unicode字符序列,支持和Go语言类似的反斜杠转义特性,不过JSON使用的是\Uhhhh转义数字来表示一个UTF-16编码(译注:UTF-16和UTF-8一样是一种变长的编码,有些Unicode码点较大的字符需要用4个字节表示;而且UTF-16还有大端和小端的问题),而不是Go语言的rune类型。

这些基础类型可以通过JSON的数组和对象类型进行递归组合。一个JSON数组是一个有序的值序列,写在一个方括号中并以逗号分隔;一个JSON数组可以用于编码Go语言的数组和slice。一个JSON对象是一个字符串到值的映射,写成以系列的name:value对形式,用花括号包含并以逗号分隔;JSON的对象类型可以用于编码Go语言的map类型(key类型是字符串)和结构体

文本和HTML模板

text/template和html/template提供模板相关支持

一个模板是一个字符串或一个文件,里面包含了一个或多个由双花括号包含的{{action}}对象

const templ = `{{.TotalCount}} issues:
{{range .Items}}----------------------------------------
Number: {{.Number}}
User:   {{.User.Login}}
Title:  {{.Title | printf "%.64s"}}
Age:    {{.CreatedAt | daysAgo}} days
{{end}}`

func daysAgo(t time.Time) int {
    return int(time.Since(t).Hours() / 24)
}

// 调用链顺序:
// template.New先创建并返回一个模板;
// uncs方法将daysAgo等自定义函数注册到模板中,并返回模板;
// 最后调用Parse函数分析模板
report, err := template.New("report").
    Funcs(template.FuncMap{"daysAgo": daysAgo}).
    Parse(templ)
if err != nil {
    log.Fatal(err)
}
// 模板解析失败是致命错误(编译前测试好), template.Must辅助函数可以简化处理
var report = template.Must(template.New("issuelist").
    Funcs(template.FuncMap{"daysAgo": daysAgo}).
    Parse(templ))

func main() {
    result, err := github.SearchIssues(os.Args[1:])
    if err != nil {
        log.Fatal(err)
    }
    if err := report.Execute(os.Stdout, result); err != nil {
        log.Fatal(err)
    }
}
import "html/template"

var issueList = template.Must(template.New("issuelist").Parse(`
<h1>{{.TotalCount}} issues</h1>
<table>
<tr style='text-align: left'>
  <th>#</th>
  <th>State</th>
  <th>User</th>
  <th>Title</th>
</tr>
{{range .Items}}
<tr>
  <td><a href='{{.HTMLURL}}'>{{.Number}}</a></td>
  <td>{{.State}}</td>
  <td><a href='{{.User.HTMLURL}}'>{{.User.Login}}</a></td>
  <td><a href='{{.HTMLURL}}'>{{.Title}}</a></td>
</tr>
{{end}}
</table>
`))

函数

声明(见前)

func name(parameter-list) (result-list) {
    body
}
type HandleFunc func(http.ResponseWriter, *http.RequestReader)
// width, height
func Size(rect image.Rectangle) (width, height int)

错误

关于不使用异常的说明:

Go这样设计的原因是由于对于某个应该在控制流程中处理的错误而言,将这个错误以异常的形式抛出会混乱对错误的描述,这通常会导致一些糟糕的后果

错误处理策略

函数值

被看作第一类值(first-class values):函数像其他值一样,拥有类型,可以被赋值给其他变量,传递给函数,从函数返回

    // 匿名函数
    add1:= func(r rune) rune { return r + 1 }
    fmt.Println(strings.Map(add1, "VMS"))      // "WNT"
    fmt.Println(strings.Map(func(r rune)rune {
      return r + 1
    }, "VMS")

警告:匿名函数捕获迭代变量

循环迭代中,函数值中记录的迭代变量(作用域在for词法块,在该循环中生成的所有函数值都共享相同的循环变量)地址而不是值

注意以下赋值: dir := d

var rmdirs []func()
for _, d := range tempDirs() {
    dir := d // NOTE: necessary!
    os.MkdirAll(dir, 0755) // creates parent directories too
    rmdirs = append(rmdirs, func() {
        os.RemoveAll(dir)
    })
}
// ...do some work…
for _, rmdir := range rmdirs {
    rmdir() // clean up
}

后续遇到defer语句或for循环中goroutine(go func(){...})类似!!!

可变参数

unc sum(vals...int) int {
    total := 0
    for _, val := range vals {
        total += val
    }
    return total
}

后续会遇到可变option个数传递

deferred函数

func title(url string) error {
    resp, err := http.Get(url)
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    
    ct := resp.Header.Get("Content-Type")
    if ct != "text/html" && !strings.HasPrefix(ct,"text/html;") {
        return fmt.Errorf("%s has type %s, not text/html",url, ct)
    }
    
    doc, err := html.Parse(resp.Body)
    if err != nil {
        return fmt.Errorf("parsing %s as HTML: %v", url,err)
    }
    // ...print doc's title element…
    return nil

panic和recover

当panic异常发生时,程序会中断运行,并立即执行在该goroutine(可以先理解成线程,在第8章会详细介绍)中被延迟的函数(defer 机制)。随后,程序崩溃并输出日志信息

通常来说,不应该对panic异常做任何处理,但有时,也许我们可以从异常中恢复,至少我们可以在程序崩溃前,做一些操作。举个例子,当web服务器遇到不可预料的严重问题时,在崩溃前应该将所有的连接关闭;如果不做任何处理,会使得客户端一直处于等待状态

如果在deferred函数中调用了内置函数recover,并且定义该defer语句的函数发生了panic异常,recover会使程序从panic中恢复,并返回panic value。导致panic异常的函数不会继续运行,但能正常返回。在未发生panic时调用recover,recover会返回nil。

deferred函数帮助Parse从panic中恢复。在deferred函数内部,panic value被附加到错误信息中;并用err变量接收错误信息,返回给调用者。我们也可以通过调用runtime.Stack往错误信息中添加完整的堆栈调用信息。

func Parse(input string) (s *Syntax, err error) {
    defer func() {
        if p := recover(); p != nil {
            err = fmt.Errorf("internal error: %v", p)
        }
    }()
    // ...parser...
}

注意: 不应该试图去恢复其他包引起的panic(有时难以做到),安全的做法是有选择性的recover

方法

通过嵌套struct继承方法

func (p *Point) ScaleBy(factor float64) {
    p.X *= factor
    p.Y *= factor
}

示例: sync.Mutex的Lock和Unlock方法被引入到匿名结构中:

var cache = struct {
    sync.Mutex
    mapping map[string]string
}{
    mapping: make(map[string]string),
}


func Lookup(key string) string {
    cache.Lock()
    v := cache.mapping[key]
    cache.Unlock()
    return v
}

方法值和方法表达式

distanceFromP := p.Distance        // method value
fmt.Println(distanceFromP(q))      // "5"

bitmap

通常使用map[T]bool来表示集合, 但是用bitmap(byte[]实现)是种更好的选择:

// An IntSet is a set of small non-negative integers.
// Its zero value represents the empty set.
type IntSet struct {
    words []uint64
}

// Has reports whether the set contains the non-negative value x.
func (s *IntSet) Has(x int) bool {
    word, bit := x/64, uint(x%64)
    return word < len(s.words) && s.words[word]&(1<<bit) != 0
}

// Add adds the non-negative value x to the set.
func (s *IntSet) Add(x int) {
    word, bit := x/64, uint(x%64)
    for word >= len(s.words) {
        s.words = append(s.words, 0)
    }
    s.words[word] |= 1 << bit
}

// UnionWith sets s to the union of s and t.
func (s *IntSet) UnionWith(t *IntSet) {
    for i, tword := range t.words {
        if i < len(s.words) {
            s.words[i] |= tword
        } else {
            s.words = append(s.words, tword)
        }
    }
}

// String returns the set as a string of the form "{1 2 3}".
func (s *IntSet) String() string {
    var buf bytes.Buffer
    buf.WriteByte('{')
    for i, word := range s.words {
        if word == 0 {
            continue
        }
        for j := 0; j < 64; j++ {
            if word&(1<<uint(j)) != 0 {
                if buf.Len() > len("{") {
                    buf.WriteByte(' ')
                }
                fmt.Fprintf(&buf, "%d", 64*i+j)
            }
        }
    }
    buf.WriteByte('}')
    return buf.String()
}

注意: bytes.Buffer的String()用法, 定义Strin()有助于fmt.Print会调用打印, 这种机制有赖于接口和类型断言(详见下一章)

封装

OOB编程很重要的一点就是封装(信息隐藏), 三个好处:

回顾上一节的IntSet定义:

type IntSet struct {
    words []uint64
}

其实也可以这样定义:

type IntSet []uint64

但是后者封装性不如前者, 因为words成员是包外不可见的, 无法直接操作

封装并不总是需要的, 比如time包的Duration暴露为int64的纳秒, 这样自定义相关常量成为可能:

const day = 24 * time.Hour

另外如第二种方式暴露内部slice成员, 就可以直接用range迭代

上一篇 下一篇

猜你喜欢

热点阅读