当前位置 博文首页 > Go语言基础知识总结(语法、变量、数值类型、表达式、控制结构等

    Go语言基础知识总结(语法、变量、数值类型、表达式、控制结构等

    作者:admin 时间:2021-02-09 12:05

    一、语法结构

    golang源码采用UTF-8编码。空格包括:空白,tab,换行,回车。

    - 标识符由字母和数字组成(外加'_'),字母和数字都是Unicode编码。

    - 注释:

    复制代码 代码如下:

    /* This is a comment; no nesting */
    // So is this.

    二、字面值(literals)类似C语言中的字面值,但数值不需要符号以及大小标志:

    复制代码 代码如下:

    23
    0x0FF

    1.234e7类似C中的字符串,但字符串是Unicode/UTF-8编码的。同时,\xNN总是有2个数字;\012总是3;两个都是字节:

    复制代码 代码如下:
    "Hello, world\n"
    "\xFF" // 1 byte
    "\u00FF" // 1 Unicode char, 2 bytes of UTF-8

    原生字符串:`\n\.abc\t\` == "\\n\\.abc\\t\\"

    三、语法概述

    golang基本上就是类C的语法,但使用反转的类型和声明,并使用关键字作为每个声明的开头。

    复制代码 代码如下:

    var a int
    var b, c *int // 注意与C的不同
    var d []int
    type S struct { a, b int }

    基本的控制结构也十分熟悉:

    复制代码 代码如下:

    if a == b { return true } else { return false }
    for i = 0; i < 10; i++ { … }

    注意:没有圆括号,但需要大括号。

    后续会有更多有关这方面的内容。

    四、分号

    分号作为语句终止符号,但:

    - 如果前一个符号是语句的结尾,那词法分析程序将自动在行尾插入一个分号
    - 注意:比JavaScript的规则更清晰和简单

    因此,下面的程序不需要分号:

    复制代码 代码如下:

    package main

    const three = 3
    var i int = three

    func main() { fmt.Printf("%d\n", i) }

    在实际中,Go源码在for和if子句之外几乎都没有用到分号。

    五、数值类型

    golang数值类型(numeric types)是原生内置的,也是为大家所熟知的:

    复制代码 代码如下:

    int uint
    int8 uint8 = byte
    int16 uint16
    int32 uint32 float32 complex64
    int64 uint64 float64 complex128

    还有uintptr,一个大小足够存储一个指针的数值。

    这些都是互不相同的类型;int不等于是int32,即便是在一个32位的机器上。

    没有隐式类型转换(不过不要恐慌)。

    Bool

    普通的布尔类型bool,取值true和false(预定义的常量)。

    if语句等使用布尔表达式。

    指针类型和整型不是布尔类型。

    string

    原生内置的string类型代表不可改变的字节数组,即文本。string类型是用长度定界的,而不是以结尾0终止的。

    字符串字面值是string类型。

    和整型一样不可改变。可重新赋值,但不能修改其值。

    正如"3"总是3,"hello"也总是"hello"。

    Go语言对字符串操作提供了良好的支持。

    六、表达式(Expressions)

    大多都是类C语言的操作符。

    二元操作符:

    优先级 操作符 备注
    5 * / % << >> & &^ &^是位清理操作符
    4 + – | ^ ^是异或(xor)
    3 == != < <= > >=
    2 &&
    1 ||

    一元操作符包括:& ! * + – ^(外加用于通信的<-)
    一元操作符^是求补码/反码操作。

    Go vs. C表达式

    可以让C程序员惊喜的是:

    更少的优先级层次(应该容易)。
    ^替代了~
    ++和–不再是表达式操作符(x++是一个语句,不是表达式;*p++是(*p)++,而不是*(p++))
    &^是新操作符,在常量表达式中很有用
    <<和>>等需要一个无符号的移位计数。

    无惊喜的是:

    赋值操作与所期望的一样:+= <<= &^=等
    表达式总体看起来相似(下标、函数调用等)

    例子:

    复制代码 代码如下:

    +x
    23 + 3*x[i]
    x <= f()
    ^a >> b
    f() || g()
    x == y + 1 && <-ch > 0
    x &^ 7 // x with the low 3 bits cleared
    fmt.Printf("%5.2g\n", 2*math.Sin(PI/8))
    7.234/x + 2.3i

    "hello, " + "world" // concatenation
    // no C-like "a" "b"

    数值转型

    将一个数值从一个类型转换为另一个类型称为一次转型,其语法形式有点类似函数调用:

    复制代码 代码如下:

    uint8(intVar) //截断到相应的大小
    int(float64Var) //片段截断
    float64(intVar) //转为float64

    一些涉及string类型的转型:
    复制代码 代码如下:

    string(0×1234) // == "\u1234"
    string(sliceOfBytes) // bytes -> bytes
    string(sliceOfInts) // ints -> Unicode/UTF-8
    []byte("abc") // bytes -> bytes
    []int("日本語") // Unicode/UTF-8 -> ints

    切片(slice)与数组相关,稍后会有更多相关内容。

    七、常量

    数值常量是"理想数":没有大小或标志,因此没有U、L或UL作结尾。

    复制代码 代码如下:

    077 // 八进制
    0xFEEDBEEEEEEEEEEEEEEEEEEEEF //十六进制
    1 << 100

    下面是整数和浮点数值,字面值的语法决定其类型:
    复制代码 代码如下:

    1.234e5 // 浮点
    1e2 // 浮点
    3.2i // 浮点虚数
    100 // 整数

    常量表达式

    浮点和整型常量可以任意组合,最终表达式的类型由常量的类型决定。操作自身也取决于类型。

    复制代码 代码如下:

    2*3.14 // 浮点: 6.28
    3./2 // 浮点:1.5
    3/2 // 整型:1
    3+2i // 复数:3.0 + 2.0i

    // 高精度
    const Ln2 = 0.69314718055994530941723212145817656807
    const Log2E = 1/Ln2


    数值的表示范围足够大(目前最大用1024位表示)。

    理想数的结果

    Go语言允许无需显式转型的情况下使用常量,前提是常量值可以被其类型表示(没有必要进行转型;其值表示起来没问题):

    复制代码 代码如下:

    var million int = 1e6 //float语法在这里可以使用
    math.Sin(1)

    常量必须可以被其类新表示。例如:^0的值为-1,不在0-255的范围内。
    复制代码 代码如下:

    uint8(^0) //错误:-1无法用uint8类型表示
    ^uint8(0) //OK
    uint8(350) //错误:350无法用uint8类型表示
    uint8(35.0) //OK: 35
    uint8(3.5) //错误:3.5无法用uint8类型表示

    八、声明

    golang声明以一个关键字开头(var, const,type和func),并且与C中的声明次序相反:

    复制代码 代码如下:

    var i int
    const PI = 22./7.
    type Point struct { x, y int }
    func sum(a, b int) int { return a + b }

    为何要以相反次序声明呢?早期的一个例子:
    复制代码 代码如下:

    var p, q *int

    p和q的类型都是*int。并且函数读起来更佳,并且与其他声明一致。还有一个原因,马上道来。

    Var

    变量声明以var开头。

    它们可以有一个类型或一个初始化表达式;至少应有一个或二者都有。初始化表达式应该与变量匹配(还有类型!)。

    复制代码 代码如下:

    var i int
    var j = 365.245
    var k int = 0
    var l, m uint64 = 1, 2
    var nanoseconds int64 = 1e9 // float64 constant!
    var inter, floater, stringer = 1, 2.0, "hi"

    分派var

    总是输入var让人生厌。我们可以通过括号让多个变量声明成为一组:

    复制代码 代码如下:

    var (
    i int
    j = 356.245
    k int = 0
    l, m uint64 = 1, 2
    nanoseconds int64 = 1e9
    inter, floater, stringer = 1, 2.0, "hi"
    )

    这种形式适用于const,type, var,但不能用于func。

    =:"短声明"

    在函数内(只有在函数内这一种情况下),下面形式的声明:

    复制代码 代码如下:

    var v = value

    可以被缩短成:
    复制代码 代码如下:

    v := value

    (这就是另外一个名字、类型倒序的原因)

    类型就是值的类型(对于理想数,相应的类型是int或float64或complex128)

    复制代码 代码如下:

    a, b, c, d, e := 1, 2.0, "three", FOUR, 5e0i

    这种形式的声明使用很频繁,并且在诸如for循环初始化表达式中也可以使用。

    Const

    常量声明以const开头。

    它们必须有一个常量表达式,可在编译期间求值,作为初始化表达式,可以拥有一个可选的类型修饰符。

    复制代码 代码如下:

    const Pi = 22./7.
    const AccuratePi float64 = 355./113
    const beef, two, parsnip = "meat", 2, "veg"
    const (
    Monday, Tuesday, Wednesday = 1, 2, 3
    Thursday, Friday, Saturday = 4, 5, 6
    )

    Iota

    常量声明可以使用计数器:iota,每个const块中的iota都从0开始计数,在每个隐式的分号(行尾)自增。

    复制代码 代码如下:

    const (
    Monday = iota // 0
    Tuesday = iota // 1
    )

    速记:重复上一个类型和表达式。
    复制代码 代码如下:

    const (
    loc0, bit0 uint32 = iota, 1<<iota //0,1
    loc1, bit1 //1,2
    loc2, bit2 //2,4
    )

    Type

    类型声明以type开头。

    我们后续会学习更多类型,不过先这里举几个例子:

    复制代码 代码如下:

    type Point struct {
    x, y, z float64
    name
    string
    }
    type Operator func(a, b int) int
    type SliceOfIntPointers []*int

    我们稍后会回到函数。

    New

    内置函数new用于分配内存。其语法类似一个函数调用,以类型作为参数,与C++中的new类似。返回一个指向已分配对象的指针。

    复制代码 代码如下:

    var p *Point = new(Point)
    v := new(int) // v的类型为*int

    稍后我们将看到如何构建切片(slice)

    Go语言中没有用于内存释放的delete或free。Go具备垃圾回收功能。

    赋值

    赋值是容易和熟悉的:

    复制代码 代码如下:

    a = b

    但Go还支持多项赋值:
    复制代码 代码如下:

    x, y, z = f1(), f2(), f3()
    a, b = b, a //交互a,b的值

    函数支持多个返回值(稍后有更多细节):
    复制代码 代码如下:

    nbytes, error := Write(buf)

    九、控制结构

    与C类似,但很多地方有不同。

    Go支持if、for和switch。

    正如之前说的,无需小括号,但大括号是必要的。

    如果将它们看为一组,它们的用法很规律。例如,if、for和switch都支持初始化语句。

    控制结构的形式

    后续会有细节,但总体上:

    if和switch语句以1元素和2元素形式呈现,后面详细讲解。

    for循环具有1元素和3元素的形式:

    1元素形式等价于C语言中的while:

    复制代码 代码如下:

    for a {}

    3元素形式等价于C语言中的for:

    复制代码 代码如下:

    for a;b;c {}

    在所有这些形式里,任何元素都可以是空。

    if

    基本形式是大家所熟知的,但已经没有了"else悬挂"问题了:

    复制代码 代码如下:

    if x < 5 { less() }
    if x < 5 { less() } else if x == 5 { equal() }

    支持初始化语句;需要分号。

    复制代码 代码如下:

    if v := f(); v < 10 {
    fmt.Printf("%d less than 10\n", v)
    } else {
    fmt.Printf("%d not less than 10\n", v)
    }

    与多元函数一起使用更有益处:

    复制代码 代码如下:

    if n, err = fd.Write(buf); err != nil { … }

    省略条件意为true,在这里没有什么用。但在for,switch语句中尤其有用。

    for

    基本形式是大家所熟知的:

    复制代码 代码如下:

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

    省略条件意为true:

    复制代码 代码如下:

    for ;; { fmt.Printf("looping forever") }

    而且你还可以省略分号:

    复制代码 代码如下:

    for { fmt.Printf("Mine! ") }

    不要忘记多项赋值:

    复制代码 代码如下:

    for i,j := 0,N; i < j; i,j = i+1,j-1 {…}

    (Go中没有像C中那样的逗号操作符)

    switch细节

    switch与C中的switch有些类似。

    不过,有一些语法和语义的重要不同之处:

    - 表达式不必一定是常量,甚至可以不必是int。
    - 没有自动的fall through
    - 但作为替代,语法上,最后的语句可以为fallthrough
    - 多case可以用逗号分隔

    复制代码 代码如下:

    switch count%7 {
    case 4,5,6: error()
    case 3: a *= v; fallthrough
    case 2: a *= v; fallthrough
    case 1: a *= v; fallthrough
    case 0: return a*v
    }

    Switch

    Go中的switch要远比C中的强大。常见的形式:

    复制代码 代码如下:

    switch a {
    case 0: fmt.Printf("0")
    default: fmt.Printf("non-zero")
    }

    switch表达式可以是任意类型,如果为空,则表示true。结果类似一个if-else链:

    复制代码 代码如下:

    a, b := x[i], y[j]
    switch {
    case a < b: return -1
    case a == b: return 0
    case a > b: return 1
    }

    复制代码 代码如下:

    switch a, b := x[i], y[j]; { … }

    Break,continue等

    break和continue语句的工作方式与C中的类似。

    它们可以指定一个label并影响外层结构:

    复制代码 代码如下:

    Loop: for i := 0; i < 10; i++ {
    switch f(i) {
    case 0, 1, 2: break Loop
    }
    g(i)
    }

    是的,那是一个goto。

    十、函数

    函数以func关键字开头。

    如果有返回类型,返回类型放在参数的后面。return的含义和你期望的一致。

    复制代码 代码如下:

    func square(f float64) float64 { return f*f }

    函数支持返回多个值。这样,返回类型就是一个括号包围的列表。
    复制代码 代码如下:

    func MySqrt(f float64) (float64, bool) {
    if f >= 0 { return math.Sqrt(f), true }
    return 0, false
    }

    空标识符

    如果你只关心MySqrt函数返回的第一个值?你仍然需要将第二个值放在一个地方。

    解决方法:使用空标识符_(下划线)。它是预声明的,可以被赋予任何无用的值。

    复制代码 代码如下:

    // Don't care about boolean from MySqrt.
    val, _ = MySqrt(foo())

    在空标识符其他的适用场合中,我们仍然会展示它。

    带结果变量(result variable)的函数

    如果你给结果参数命名了,你可以将它当作实际变量使用。

    复制代码 代码如下:

    func MySqrt(f float64) (v float64, ok bool) {
    if f >= 0 { v,ok = math.Sqrt(f), true }
    else { v,ok = 0,false }
    return v,ok
    }

    结果变量被初始化为"0"(0,0.0,false等。根据其类型;稍后有更多有关内容)

    复制代码 代码如下:

    func MySqrt(f float64) (v float64, ok bool) {
    if f >= 0 { v,ok = math.Sqrt(f), true }
    return v,ok
    }

    空返回

    最后,一个没有返回表达式的return将返回结果变量的当前值。下面是另外两个MySqrt的版本:

    复制代码 代码如下:

    func MySqrt(f float64) (v float64, ok bool) {
    if f >= 0 { v,ok = math.Sqrt(f), true }
    return // must be explicit
    }
    func MySqrt(f float64) (v float64, ok bool) {
    if f < 0 { return } // error case
    return math.Sqrt(f),true
    }

    0是什么

    Go中的内存都是被初始化了的。所有变量在执行之前的声明时被初始化。如果没有显式的初始化表达式,我们将使用对应类型的"0值"。下面的循环:

    复制代码 代码如下:

    for i := 0; i < 5; i++ {
    var v int
    fmt.Printf("%d ", v)
    v = 5
    }

    将打印0 0 0 0 0。

    0值取决于类型:数值是0;布尔是false;空字符串是"";指针,map、切片、channel是nil;结构体是0等。

    Defer

    defer语句负责在其所在的函数返回时执行一个函数(或方法)。其参数在到达defer语句那个时刻被求值;其函数在返回时被执行。

    复制代码 代码如下:

    func data(fileName string) string {
    f := os.Open(fileName)
    defer f.Close()
    contents := io.ReadAll(f)
    return contents
    }

    在关闭文件描述符、解互斥锁等场合十分有用。

    每Defer执行一个函数

    Go按按后入先出(LIFO)次序执行一组defer函数。

    复制代码 代码如下:

    func f() {
    for i := 0; i < 5; i++ {
    defer fmt.Printf("%d ", i)
    }
    }

    上面代码将输出4 3 2 1 0。你可以在最后关闭所有文件描述符以及解锁所有互斥锁。

    用defer跟踪代码:

    复制代码 代码如下:

    func trace(s string) { fmt.Println("entering:", s) }
    func untrace(s string) { fmt.Println("leaving:", s) }

    func a() {
     trace("a")
    defer untrace("a")
    fmt.Println("in a")
    }

    func b() {
    trace("b")
    defer untrace("b")
    fmt.Println("in b")
    a()
    }

    func main() { b() }

    不过我们可以实现的更灵巧一些。

    参数当即求值,defer稍后执行

    复制代码 代码如下:

    func trace(s string) string {
    
    fmt.Println("entering:", s)
    return s
    }
    func un(s string) {
    fmt.Println("leaving:", s)
    }
    func a() {
    defer un(trace("a"))
    fmt.Println("in a")
    }
    func b() {
    defer un(trace("b"))
    fmt.Println("in b")
    a()
    }
    func main() { b() }

    函数字面值

    和在C中一样,函数不能在函数内部声明。但函数字面值却可以被赋值给变量。

    复制代码 代码如下:

    func f() {
    for i := 0; i < 10; i++ {
    g := func(i int) { fmt.Printf("%d",i) }
    g(i)
    }
    }

    函数字面值是闭包(closure)

    函数字面值实际上是闭包。

    复制代码 代码如下:

    func adder() (func(int) int) {
    var x int
    return func(delta int) int {
    x += delta
    return x
    }
    }

    f := adder()
    fmt.Print(f(1))
    fmt.Print(f(20))
    fmt.Print(f(300))


    输出1 21 321 – f中的x累加。

    js
    下一篇:没有了