go 语言程序结构

Go语言中的关键字

Go 语言语法简明,所有关键字如下:

  • 包:
    import package
  • 程序实体声明和定义:
    chan const func interface map struct type var
  • 程序流程控制:
    go select break case continue default defer else fallthrough for goto if range return switch
  • 内置的预声明常量,类型,函数
    • 常量:
      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
变量声明
  • var声明创建一个具体类型的变量,然后将其初始化

    var name type = expression
    eg : 
    var s string = ""
    var s = "hello"    // 省略类型
    var s string       // 省略表达式
    

    支持类型推导,所以类型和表达式可以省略一个,但是不能都省略:

    • 如果类型省略,它的类型将由初始化表达式决定

    • 如果表达式省略,其初始值对应于类型的零值。

      1. 对于数字是0
      2. 对于布尔值式false
      3. 对于字符串是""
      4. 对于接口和引用类型(slice,指针,map,通道,函数)是nil
      5. 对于一个数组和结构体,零值是其所有成员的零值

      零值机制保障所有的变量是良好定义,所以Go里面不存在未初始化变量。

  • 短变量
    在函数中,可以采用短变量声明方式,这种方式既包含声明又包含赋值。

    name := expression
    eg: 
    s := ""
    

    name的类型由expression的类型决定,所以局部变量的声明和初始化主要使用短变量
    短变量模式也不总是重新定义变量,也可能是退化赋值的操作:

    f, err := os.Open("/dev/random)
    ...
    buf := make([]byte,, 1024)
    n, err := f.Read(buf)  // err 退化赋值,n新定义
    

    退化赋值的前提条件是:最少有1个新变量被定义,且必须是同一作用域:

    x := 100
    fmt.Println(x)
    x := 200    // 没有新变量被定义
    fmt.Println(x)  
    
  • 多变量赋值
    多变量赋值时,首先计算出所有的右值,然后再完成赋值操作

    x, y := 1, 2
    x, y := y+3, x+2
    
变量的生命周期
  • 包级别变量的生命周期时整个程序的执行时间
作用域
  • 语法块
  • 全局块
  • 文件级别
  • 包级别
包初始化函数

除了 main 这个特殊的函数外,Go 语言还有一个特殊的函数——init,通过它可以实现包级别的一些初始化操作。

init 函数没有返回值,也没有参数,它先于 main 函数执行,

package main

import "fmt"

func init() {
    fmt.Println("init func")
}

func main() {
    fmt.Println("main func")
}

// 输出:
init 
main 

一个包中可以有多个 init 函数,但是它们的执行顺序并不确定,所以如果你定义了多个 init 函数的话,要确保它们是相互独立的,所以这就要求这些 init 函数所做的事不要有顺序上的依赖。

符号优先级

Go的二元操作符分五大优先级,同级别的运算符满足左结合律。
*/%<<>>&&^
+-|^
==!=<<=>>=
&&
||

if 语句
if condition1 {
    // ...
} else if condition2 {
    // ...    
} else {
    // ...
}

Go语言里面对if/else格式对齐要求很严格,如果需要if/else组合,则需要在if语句结束的大括号后面就跟上elseelse if 同理。

if 语句还增加了对初始化语句的支持,可定义局部变量或执行初始化函数

func main() {
    s := "9"
    if err := check(s); err != nil {
        log.Fatalln(err)
    }
    ...
}

func check(s string) error {
    n, err := strconv.ParseInt(s, 10, 64)
    if err != nil || n < 0 {
        return errors.New("invalid number")
    }
    return nil
}
go语言没有三元表达式

Go语言不支持?:,其原因是:
语言的设计者看到这个操作经常被用来创建难以理解的复杂表达式(多层嵌套)。在替代方案上,if-else 形式虽然较长,但无疑是更清晰的,一门语言只需要一个条件控制流结构,所以就没有提供三元表达式。

  • 简单造个三元运算符

    func IFTHEN(expr bool, a, b interface{}) interface{} {
      if expr {
          return a
      } 
      return b
    }
    

    缺点是返回了万能类型 interface{},每次使用都需要断言,不方便性能还差。

  • 利用go 1.18 之后的泛型

    func IFTHEN[T any](expr bool, a, b T) T {
      if expr {
          return a
      }
      return b
    }
    

    这个版本看起来解决了上述的问题,但如果

    var p *Person
    genderDesc := IFTHEN(p == nil, "未知", Any(p.gender == 1, "男", "女")) // panic
    

    以上示例将无可避免地发生 panic ,原因是当p == nil成立时,p.gender将发生 panic 。相信肯定会有人觉得奇怪,明明在访问 gender 字段前已经做了判空操作,怎么还会 panic?

    实际上,这就是函数怎么也无法代替三目运算符语言特性的地方:作为语言特性的三目运算符可以做到惰性计算,而函数做不到。函数 IFTHEN 有3个参数,函数在压栈前必须对它的实参先进行计算并获得相应的值,也就是说,p == nilp.gender == 1都会被求值。

for 语句

for 语句时Go里面地唯一循环语句。

for init; condition; post {
    //...
}

eg: 
for i := 1; i < 5; i++ {
    fmt.Println(i);
}

initialization 和 post省略就是传统的while循环。

for condition {
    //...
}

三个部分都可以省略,那就是无限循环。

for {
    break
}
Go 语言范围语句(range)

Go 语言中 range 关键字用于 for 循环中迭代数组(array)、切片(slice)、字符串、通道(channel) 、集合(map)的元素。

  • 在数组和切片中它返回元素的索引和索引对应的值:

    nums := []int{1, 2, 3}
    for i, num := range nums {
        fmt.Println("index: value",  i, num)
    }
    
    // 输出:
    // index: value 0 1
    // index: value 1 2
    // index: value 2 3
    

    或者直接只有索引:

    nums := []int{1, 2, 3}
    for i := range nums {
        fmt.Println("index: value", i, nums[i])
    }
    
    // 输出:
    // index: value 0 1
    // index: value 1 2
    // index: value 2 3
    
  • 遍历string
    按照rune的方式遍历,rune可以理解为是Unicode字符

    // 返回索引和Unicode字符
    for index, c := range s {
        fmt.Printf("%d: [%c]\t", index, c)
    }
    // 输出:
    0: [中]  3: [国] 
    

    这意味着:index 可能是不连续的。

  • 遍历map返回 key,value

    for name, age : range ages {
        fmt.Printf("%s\t%d\n", name, age)
    }
    
  • 作用于channel

    c := make(chan string, 2)
    c <- "hello"
    c <- "world"
    
    time.AfterFunc(time.Microsecond, func() {
        close(c)
    }
    
    for e := range c {
        fmt.Printf("element: %s\n", e)
    }
    

    range 会阻塞等待 channel 中的数据,直到 channel 被关闭。
    同时,如果 range 作用域值为 nil 的 channel 时,则会永久阻塞。

  • rang 遍历是复制元素

    type Foo struct {
        Num int
    }
    
    func main() {
      fooSlice := []Foo{{1}, {2}}
    
      fmt.Println(fooSlice) // 输出:[{1} {2}]
    
      for _, foo := range fooSlice {
          foo.Num = 100
      }
    
      fmt.Println(fooSlice) // 输出: [{1} {2}]
    }
    

    range 遍历是将每一个元素复制给临时变量,所以需要修改变量时,需要使用索引遍历方式

    for i := range fooSlice {
        fooSlice[i].Num = 100  // 更改生效
    }
    
  • range 语句和 for 迭代的效率

switch 语句

go 语言的switch-case 和其他语言不同,默认是加了 break 语句。

switch i {
    case 0:
        fmt.Println(0)
    case 1:
        fmt.Println(1)
    case 2:
        fmt.Println(2)
    default:
}
// 相当于 C/C++ 中
switch i {
    case 0:
        fmt.Println(0)
        break
    case 1:
        fmt.Println(1)
        break
    case 2:
        fmt.Println(2)
        break
    default:
}
  • fallthrough
    如果真的想执行完一个 case 接着执行下一个 case,只要使用 fallthrough关键字就可以了:
    switch i {
     case 0:
         fmt.Println(0)
         fallthrough
     case 1:
         fmt.Println(1)
     case 2:
         fmt.Println(2)
     default:
    }
    
goto 语句
continue 语句
break语句
++--操作符

go语言中的++--操作符都是后置操作符,必须跟在操作数后面,并且它们没有返回值。

x := i++  // 错误,无返回值。
unsafe.Sizeof unsafe.Alignof unsafe.Offsetof
var x struct {
    a bool
    b int16
    c []int
}
  • unsafe.Sizeof
    返回在内存中占用字节的长度
    unsafe.Sizeof(x)
    
  • unsafe.Alignof
    返回参数类型要求的对齐方式
    unsafe.Alignof(x)
    
  • unsafe.Offsetof
    结算成员相对于结构体的偏移量
    unsafe.Offsetof(x.b)
    
字符串格式化常用动词
动词 功能
%v 按值的本来值输出
%+v 在 %v 基础上,对结构体字段名和值进行展开
%#v 输出 Go 语言语法格式的值
%T 输出 Go 语言语法格式的类型和值
%% 输出 % 本体
%b 整型以二进制方式显示
%o 整型以八进制方式显示
%d 整型以十进制方式显示
%x 整型以十六进制方式显示
%X 整型以十六进制、字母大写方式显示
%U Unicode 字符
%f 浮点数
%p 指针,十六进制方式显示
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容