Go基础语法纪要

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

一、命名

共25个关键字:

break

case

chan

const

continue

default

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

fallthrough

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

goto

if

import

interface

map

package

range

return

select

struct

switch

type

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

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

 

二、四种类型声明

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

六、接口类型

待续……

 

转载于:https://www.cnblogs.com/xiaochuizi/p/9216734.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值