Go基础语法纪要

Go语言圣经《The Go Programming Language》的语法纪要:

一、命名

共25个关键字:

break

可用于跳出for/switch/select, 主要还是用于for中跳出。因为go中switch的case语句默认最后带有break,匹配成功后不会自动向下执行其它case(与c/c++不同)。若需要继续向下执行需要fallthrough

i := 3
switch i {
case 1:
    // todo
    break
default:
    break  
}

case

与select/switch结合int_chan := make(chan int, 1)string_chan := make(chan string, 1int_chan <- string_chan <- "hello"

select {
case value := <-int_chan:
    fmt.Println(value)
case value := <-string_chan:
    panic(value)
default:
    break;
}

chan

用于声明通道,如: var my_in_channel chan int。

channel初始化或赋值需要用make,未显示初始化的chan是nil,无法使用。

in_channel := make(chan int)

内建函数make的作用是为slice/map/chan初始化并返回引用。

const

用于定义常量

continue

结合for循环使用,跳过当前循环执行下一次循环语句。

default

结合switch/select使用

defer

在调用普通函数或方法前加上关键字defer,就完成了defer所需的语法。当defer语句被执行时,跟在defer后面的函数会被延迟至“包含该defer语句的函数执行完毕时”才执行。无论该函数时通过return正常结束还是由于panic导致的异常结束。

我们可以在一个函数中执行多条defer语句,它们的执行顺序与声明顺序相反。使用场合示例:

package ioutil
func ReadFile(filename string) ([]byte, error) {
    f, err := os.Open(filename)
    if err != nil {
        return nil, err
    }
    defer f.Close()
    return ReadAll(f)
}

var mu sync.Mutex
var m = make(map[string]int)
func lookup(key string) int {
    mu.Lock()
    defer mu.Unlock()
    return m[key]
}

else

结合if使用,组成条件语句

cond := 1
if cond == 0 {
    // do something
} else if a > 0 {
    // do something
} else {
    // do something
}

fallthrough

结合switch/case使用,置于case语句块的末尾,用于强制继续执行下一句case。

for

Go语言只有for循环这一种循环语句,for循环有多种形式。

for initialization; condition; post {
    // zero or more statements
}

for循环三个部分不需括号包围。大括号强制要求,左大括号必须和post语句在同一行。

initialization、condition、post都可以省略。

// a traditional "while" loop
for condition {
    // ...
}

// a traditional infinite loop
for {
    // ...
}

for循环还有一种形式,在某种数据类型的区间(range)上遍历,如字符串或slice等。

func main() {
    s, sep := "", ""
    for _, arg := range os.Args[1:] {
        s += sep + arg
        sep = " "
    }
    fmt.Println(s)
}

func

声明函数

go

go语句用来创建新的并发执行单元goroutine。

goto

函数内跳转语句

if

import

导入包

 

import "fmt" // 常规导入
import . "fmt" // 以此方法导入,调用时可以省略包名
import _ "fmt" // 以此方法导入,只调用fmt的init函数,无法使用包中的变量和函数
import x "fmt" // 别名导入

 

interface

用于声明接口类型。

interface是方法的集合

interface是一种类型,并且是指针类型

interface的重要作用在于多态实现

map

声明map类型,是一种无序的键值对的集合。

package

每个Go语言源代码文件开头都必须要有一个package声明,表示源代码属于哪个包,目的是为了支持模块化、封装、单独编译和代码重用。

要生产Go语言可执行程序,必须要有名为main的package,且在该包下必须有且只有一个main函数。

同一个路径下只能存在一个package,一个package可以由多个源代码文件组成。

包还可以让我们通过控制哪些名字是外部可见的来隐藏内部实现信息。如果一个名字是大写字母开头的,则该名字是导出的(即:外部可见)。

range

结合for使用,用于在for循环中迭代数组(array)、切片(slice)、集合(map)、字符串(string)、通道(channel)。

range expression 1st Value 2nd Value(optional) notes                                                     
array [n]E, *[n]E index int value E[i]  
slice []E index int value E[i]  
string  index int rune int 对于string, range迭代的是unicode而不是字节,所以返回的值是rune
map  map[k]v key k value v  
channel element none 遍历channel时,则只有一个返回数据

 

 

 

 

 

 

 

 

return

函数中返回"返回值"

select

一个select语句用来选择哪个case中的发送或接收操作可以被立即执行,类似于switch语句,但是它的case涉及到channel有关的I/O操作。

即:select用来监听和channel有关的IO操作,当IO操作发生时,触发相应的动作。

*每个case都必须是一个通信

*所有channel表达式都会被求值(如: chan1 <- 3+4,会先对3+4求值,即发送7)

*所有被发送的表达式都会被求值

*如果任意某个通信可以进行,它就执行,其他被忽略

*如果有多个case都可以运行,select会随机公平地选出一个执行,其他不会执行。

否则: 若有default子句,则执行该语句。

         若没有default子句,select将阻塞,直到某个通信可以运行;Go不会重新对channel或值进行求值。

select {
case <- chan1:
// 如果chan1成功读到数据,则进行该case处理语句
case chan2 <- 1:
// 如果成功向chan2写入数据,则进行该case处理语句
default:
// 如果上面都没有成功,则进入default处理流程
}

struct

结合type定义结构体。

type Point struct {
    X,Y int
}
type Circle struct {
    Center Point
    Radius int
}
myCircle := Circle{ Point: Point{X: 8,Y: 10}, Radius: 5 }

switch

多路分支控制

type

*定义结构体

type Person struct {
    name string
}

*定义接口

type Employee interface {
    Work()
    WorkOverTime()
}

*类型定义 

type txt string
type handle func(msg txt)

使用类型定义定义出来的类型与原类型不相同,所以除非是用强制类型转换,否则不能用新类型变量赋值给原类型变量。

*类型别名  type name = string

类型别名定义出来的类型和原类型一样,变量可以相互赋值。在golang1.9中引入。

*类型查询

根据变量查询该变量的类型。

// 定义一个interface{}类型变量,并使用string类型值”abc“初始化
var a interface{} = "abc"   
// 在switch中使用 变量名.(type) 查询变量是由哪个类型数据赋值。
switch v := a.(type) {
case string:
    fmt.Println("字符串")
case int:
    fmt.Println("整型")
default:
    fmt.Println("其他类型", v)
}

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

用来为slice/map/chan这三种类型分配内存和初始化一个对象,并返回引用(T)。

len

func len(v Type) int
// returns the length of v // Array: the number of elements in v. // Pointer to array: the number of elements in *v(even if v is nil). // Slice/map: the number of elements in v. // String: the number of bytes in v. // Channel: the number of elements queued (unread) in the channel buffer.

cap

func cap(v Type) int
// returns the capacity of v.
// Array: the number of elements in v.
// Pointer to array: the number of elements in *v.
// Slice: the maximum length the slice can reach when resliced.
// Channel: the channel buffer capacity, in units of elements.

new

func new(Type) *Type

allocated zero value of that type,返回指针。

append

func append(slice []Type, elems ... Type) []Type
// appends elements to the end of a slice.
// If it has sufficient capacity, the destination is resliced to accommodate the
// new elements. If it does not, a new underlying array will be allocated.
// Append returns the updated slice. It is therefore necessary to store the
// result of append, often in the variable holding the slice itself:
// slice = append(slice, elem1, elem2)
// slice = append(slice, anotherSlice ...)
// As a special case, it is legal to append a string to a byte slice, like this:
// slice = append([]byte("hello "), "world" ...)

copy

func copy(dst, src []Type) int
// copies elements from a source slice into a destination slice.
// (As a special case, it also will copy bytes from a string to a slice of bytes.)
// The source and destination may overlap. Copy returns the number of
// elements copied, which will be the minimum of len(src) and len(dst)

close

func close(c chan<- Type)
// closes a channel, which must be either bidirectional or send-only. 
// It should be executed only by the sender, never the receiver, and has
// the effect of shutting down the channel after the last sent value is received.
// After the last value has been received from a closed channel c, any receive
// from c will succeed without blocking, returning the zero value for the channel
// element. The form 
// x, ok := <-c 
// will also set ok to false for a closed channe

delete

// func delete(m map[Type]Type1, key Type)
// deletes the element with the specified key (m[key]) from the map.
// If m is nil or there is no such element, delete is a no-op.

complex 构建复数

real 复数的实部

imag 复数的虚部

panic

recover

定义在函数外部的名字,在当前包的所有文件中都可以访问。用首字母的大小写决定包外的可见性。(大写可见)

 

二、四种类型声明

1. 变量var

举例:

var myName string = "Tom"

var myName string // 声明时未显示赋值,会被赋予零值

零值:数值类型为0,布尔类型为false,字符串类型为空字符串,接口或引用类型(slice/指针/map/chan/函数)为nil,数组或结构体等聚合类型为每个元素或字段都是零值。

var myName = "Tom"

myName := "Tom"

age := 3

p := &age // p被声明为int指针

p := new(int) // *int类型,指向匿名的int变量

上述两种声明方式创建p变量没有什么区别,这一点和C++不同。

在Go语言中,返回函数中局部变量的地址也是安全的。因为一个变量的生命周期只取决于是否可达,因此一个循环迭代内部的局部变量的生命周期可能超出其局部作用域。

编译器会自动选择在栈上海市在堆上分配局部变量的存储空间,这个选择并不是由用var还是new声明变量的方式决定。

2. 常量const

const boilingF = 212.0

常量表达式的值在编译期计算,而不是在运行期。每种常量的潜在类型都是基础类型。

常量间的所有算术运算、逻辑运算和比较运算的结果也是常量,对常量的类型转换操作或以下函数调用都返回常量结果:

len、cap、real、imag、complex和unsafe.Sizeof。

常量声明可以使用iota常量生成器初始化,它用于生成一组以相似规则初始化的常量。

type Weekday int
const (
    Sunday Weekday = iota
    Monday
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
)

周日对应为0,周一为1,依次类推。类似于其它编程语言中的枚举。

其它例子:

type Flags uint
const (
    FlagUp Flags = 1 << iota // is up
    FlagBroadcast                 // supports broadcast access capability
    FlagLoopback                  // is a loopback interface
    FlagPointToPoint             // belongs to a point-to-point link
    FlagMulticast                  // supports multicast access capability
)

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
)

Go语言的常量有个不同寻常之处,虽然一个常量可以有任意一个确定的基础类型(如:int或time.Duration),但是许多常量并没有一个明确的基础类型。编译器为这些没有明确的基础类型的数字常量提供比基础类型更高精度的算术计算。

3. 类型type

一个类型声明语句创建了一个新的类型名称,和现有类型具有相同的底层结构。

type 类型名字 底层类型

type Celsius float64  // 摄氏温度

type Fahrenheit float64 // 华氏温度

4. 函数func

函数声明包括函数名、形式参数列表、返回值列表(可省略)以及函数体

func name(parameter-list) (result-list) {
    body
}

如果函数返回一个无名变量或者没有返回值,返回值列表的括号可以省略。

func add(x int, y int) int   {return x + y}
func sub(x, y int) (z int)   { z = x - y; return}
func first(x int, _ int) int { return x }
func zero(int, int) int      { return 0 }

fmt.Printf("%T\n", add)   // "func(int, int) int"
fmt.Printf("%T\n", sub)   // "func(int, int) int"
fmt.Printf("%T\n", first) // "func(int, int) int"
fmt.Printf("%T\n", zero)  // "func(int, int) int"

Go语言没有默认参数值,实参通过值的方式传递,因此函数的形参是实参的拷贝,对形参进行修改不会影响实参。

但是若实参包括引用类型,如:指针、slice、map、function、channel等类型,实参可能会由于函数的间接引用被修改。

多值返回举例:

func main() {
    for _, url := range os.Args[1:] {
        links, err := findLinks(url)
        if err != nil {
            fmt.Fprintf(os.Stderr, "findlinks2: %v\n", err)
            continue
        }
        for _, link := range links {
            fmt.Println(link)
        }
    }
}

// findLinks performs an HTTP GET request for url, parses the
// response as HTML, and extracts and returns the links.
func findLinks(url string) ([]string, error) {
    resp, err := http.Get(url)
    if err != nil {
        return nil, err
    }
    if resp.StatusCode != http.StatusOK {
        resp.Body.Close()
        return nil, fmt.Errorf("getting %s: %s", url, resp.Status)
    }
    doc, err := html.Parse(resp.Body)
    resp.Body.Close()
    if err != nil {
        return nil, fmt.Errorf("parsing %s as HTML: %v", url, err)
    }
    return visit(nil, doc), nil
}

error为内置的接口类型(interface)

如果一个函数的所有返回值都有显示的变量名,那么该函数的return语句可以省略操作数,称之为bare return。

func CountWordsAndImages(url string) (words, images int, err error) {
    resp, err := http.Get(url)
    if err != nil {
        return
    }
    doc, err := html.Parse(resp.Body)
    resp.Body.Close()
    if err != nil {
        err = fmt.Errorf("parsing HTML: %s", err)
    return
    }
    words, images = countWordsAndImages(doc)
    return
}

 

三、基础类型

1. 整型

2. 浮点数

3. 复数

4. 布尔型

5. 字符串

一个字符串是一个不可改变的字节序列。字符串可以包含任意的数据,包括byte值0,但通常是用来包含人类可读的文本。

文本字符串通常被解释为采用UTF8编码的Unicode码点(rune)序列,内置的len函数可以返回一个字符串中的字节数目(不是rune字符数目)。

子字符串操作s[i:j]基于原始的s字符串的第i个字节开始到第j个字节(不包含j本身)生成一个新字符串。

不过由于字符串的不变性意味着两个字符串共享相同的底层数据是安全的,即一个字符串s和其对应的字符串切片s[7:]的操作也可以安全的共享相同的内存,没有必要分配新内存。不变性使得复制任何长度的字符串代价是低廉的。

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

strings:提供诸如字符串查询、替换、比较、截断、拆分和合并等功能。

bytes:也提供strings中类似功能的函数,针对和字符串有相同结构的[]byte类型(字节slice)。

因为字符串是只读的,因此逐步构建字符串会导致很多分配和复制,使用bytes.Buffer类型将会更有效。

strconv:提供布尔型、整型数、浮点数和对应字符串的相互转化,还提供双引号转义相关的转化。

unicode:提供IsDigit、IsLetter、IsUpper和IsLower等类似功能。

举例:

// intsToString is like fmt.Sprint(values) but adds commas.
func intsToString(values []int) string {
    var buf bytes.Buffer
    buf.WriteByte('[')
    for i, v := range values {
        if i > 0 {
            buf.WriteString(", ")
        }
        fmt.Fprintf(&buf, "%d", v)
    }
    buf.WriteByte(']')
    return buf.String()
}

func main() {
    fmt.Println(intsToString([]int{1, 2, 3})) // "[1, 2, 3]"
}

整数转字符串:

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

fmt.Println(strconv.FormatInt(int64(x), 2)) // "1111011"

对于format,fmt包中的函数更方便(%b、%d、%o、%x)

s := fmt.Sprintf("x=%b", x) // "x=1111011"

字符串解析为整数,可以使用strconv包的Atoi、ParseInt、ParseUint函数

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

有时候也可以使用fmt.Scanf来解析输入的字符串和数字

 

四、复合类型

1. 数组

数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。内置的len函数将返回数组中元素的个数。

var q [3]int = [3]int{1, 2, 3}
var r [3]int = [3]int{1, 2}
fmt.Println(r[2]) // "0"

q := [...]int{1, 2, 3}
fmt.Printf("%T\n", q) // "[3]int"

如果在数组的长度未知出现的是"..."省略号,则表示数组的长度是根据初始化值的个数来计算。

数组的长度是数组类型的一个组成部分,因此[3]int和[4]int是两种不同的数组类型,数组的长度必须是常量表达式。

上面的形式是直接提供顺序初始化值的序列,Go中也可以指定一个索引和对应值列表的方式初始化,如下:

type Currency int

const (
    USD Currency = iota // 美元
    EUR                 // 欧元
    GBP                 // 英镑
    RMB                 // 人民币
)

symbol := [...]string{USD: "$", EUR: "€", GBP: "£", RMB: "¥"}
fmt.Println(RMB, symbol[RMB]) // "3 ¥"

r := [...]int{99: -1} // 定义了一个含有100个元素的数组,最有一个元素为-1,其它为0

Go语言中数组作为函数的参数时,会进行复制,这一个和其它语言(如:C)不同。当然我们可以显示的传入一个数组指针。

func zero(ptr *[32]byte) {
    *ptr = [32]byte{}
}

2. 结构体

type Employee struct {
    ID        int
    Name      string
    Address   string
    DoB       time.Time
    Position  string
    Salary    int
    ManagerID int
}
var dilbert Employee
dilbert.Salary -= 5000 // demoted, for writing too few lines of code

position := &dilbert.Position
*position = "Senior " + *position // promoted, for outsourcing to Elbonia

var employeeOfTheMonth *Employee = &dilbert
employeeOfTheMonth.Position += " (proactive team player)" // 相当于(*employeeOfTheMonth).Position += " (proactive team player)"

匿名成员:Go语言的一个特性,只声明一个成员对应的数据类型而不指名成员的名字。匿名成员的数据类型必须是命名的类型或指向一个命名的类型的指针。

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

五、引用类型

1. 指针

2. Slice

 Slice(切片)代表变长的序列,序列中每个元素都有相同的类型。一个slice是一个轻量级的数据结构,提供了访问数组子序列元素的功能,而且slice底层确实引用一个数组对象。一个slice由三个部分构成:指针、长度和容量。指针指向第一个slice元素对应的底层数组元素的地址,长度对应slice中元素的数目,容量一般是从slice的开始位置到底层数据的结尾位置。内置的len和cap函数分别返回slice的长度和容量。

使用内置的make函数可以创建一个指定元素类型、长度和容量的slice。省略容量部分时,默认等于长度。

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

使用内置的append函数可以向slice追加元素

var runes []rune
for _, r := range "Hello, 世界" {
    runes = append(runes, r) // 通常是将append返回的结果直接赋值给输入的slice变量
}
fmt.Printf("%q\n", runes) // "['H' 'e' 'l' 'l' 'o' ',' ' ' '世' '界']"

3. Map

一个map是一个哈希表的引用。可以通过内置make函数或map字面值的语法创建map。

ages := make(map[string]int) // mapping from strings to ints

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

使用内置的delete函数可以删除元素:

delete(ages, "alice") // remove element ages["alice"]

我们不能对map中的元素进行取址操作,因为map可能随着元素数量的增长而重新分配更大的内存空间,从而导致之前的地址无效。

可以使用range风格的for循环遍历map的元素:

for name, age := range ages {
    fmt.Printf("%s\t%d\n", name, age)
}

判断元素是否存在:

age, ok := ages["bob"]
if !ok { /* "bob" is not a key in this map; age == 0. */ }

4. 函数func

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

注:C语言的函数指针的方式属于第二类值。

对函数值(function value)的调用类似函数调用,如下:

    func square(n int) int { return n * n }
    func negative(n int) int { return -n }
    func product(m, n int) int { return m * n }

    f := square
    fmt.Println(f(3)) // "9"

    f = negative
    fmt.Println(f(3))     // "-3"
    fmt.Printf("%T\n", f) // "func(int) int"

    f = product // compile error: can't assign func(int, int) int to func(int) int

函数类型的零值是nil,函数值可以与nil比较,但函数值之间是不可比较的,也不能用函数值作为map的key。

函数值使得我们不仅可以通过数据来参数化函数,亦可通过行为。如标准库中的strings.Map:

    func add1(r rune) rune { return r + 1 }

    fmt.Println(strings.Map(add1, "HAL-9000")) // "IBM.:111"
    fmt.Println(strings.Map(add1, "VMS"))      // "WNT"
    fmt.Println(strings.Map(add1, "Admix"))    // "Benjy"

函数字面量(function literal)是一种表达式,它的值被称为匿名函数(anonymous function)。语法和函数声明类似,区别在于func关键字后没有函数名。通过这种方式定义的函数可以访问完整的词法环境(lexical environment),意味着在函数中定义的内部函数可以引用该函数的变量,如下:

// squares返回一个匿名函数。
// 该匿名函数每次被调用时都会返回下一个数的平方。
func squares() func() int {
    var x int
    return func() int {
        x++
        return x * x
    }
}
func main() {
    f := squares()
    fmt.Println(f()) // "1"
    fmt.Println(f()) // "4"
    fmt.Println(f()) // "9"
    fmt.Println(f()) // "16"
}

  

 

5. 通道channel

六、接口类型

1. 类型断言x.(T)

经常地我们对一个接口值的动态类型是不确定的,如方法的形参为接口类型时,此时就需要检验它是否符合我们需要的类型。
类型断言是一个使用在接口值上的操作。

断言类型的语法:x.(T),这里x表示一个接口的类型,T表示一个类型(也可为接口类型)。
一个类型断言检查一个接口对象x的动态类型是否和断言的类型T匹配。

类型断言分两种情况:
第一种如果断言的类型T是一个具体类型,类型断言x.(T)就检查x的动态类型是否和T的类型相同。

  • 如果这个检查成功了,类型断言的结果是一个类型为T的对象,该对象的值为接口变量x的动态值。换句话说,具体类型的类型断言从它的操作对象中获得具体的值。
  • 如果检查失败,接下来这个操作会抛出panic,除非用两个变量来接收检查结果,如:f, ok := w.(*os.File)

第二种如果断言的类型T是一个接口类型,类型断言x.(T)检查x的动态类型是否满足T接口。

  • 如果这个检查成功,则检查结果的接口值的动态类型和动态值不变,但是该接口值的类型被转换为接口类型T。换句话说,对一个接口类型的类型断言改变了类型的表述方式,改变了可以获取的方法集合(通常更大),但是它保护了接口值内部的动态类型和值的部分。
  • 如果检查失败,接下来这个操作会抛出panic,除非用两个变量来接收检查结果,如:f, ok := w.(io.ReadWriter)

注意:

  • 如果断言的操作对象x是一个nil接口值,那么不论被断言的类型T是什么这个类型断言都会失败。
  • 我们几乎不需要对一个更少限制性的接口类型(更少的方法集合)做断言,因为它表现的就像赋值操作一样,除了对于nil接口值的情况。

示例代码:

//===接口=====
type Tester interface {
    getName()string
}
type Tester2 interface {
    printName()
}
//===Person类型====
type Person struct {
    name string
}
func (p Person)getName() string {
    return p.name
}
func (p Person) printName() {
    fmt.Println(p.name)
}
//============
func main() {
    var t Tester
    t = Person{"xiaohua"}
    check(t)
}
func check(t Tester)  {
    //第一种情况
    if f, ok1 := t.(Person);ok1 {
        fmt.Printf("%T\n%s\n",f,f.getName())
    }
    //第二种情况
    if t, ok2 := t.(Tester2);ok2 {  //重用变量名t(无需重新声明)
        check2(t) //若类型断言为true,则新的t被转型为Tester2接口类型,但其动态类型和动态值不变
    }
}
func check2(t Tester2)  {
    t.printName()
}

 

待续……

 

posted @ 2018-06-25 20:17  木子锤  阅读(187)  评论(0)    收藏  举报