learnGo1-程序结构

变量

作用域

  • 函数内声明 : 函数作用域内有效
  • 函数外声明:
    • 首字母小写:包内可访问
    • 首字母大写:全局可访问
  • 函数也视作变量
  • {} 内的定义都存在作用域
  • 访问权限具有很重要的意义,在 java 中他们使用一组关键字修饰,go 使用了约定使得语法更简洁,但是有时大小写也会为我们添加一些额外的麻烦

声明

包中的变量,有以下两种声明方式
- var name1,name2 type
- var name1,name2 = val1, val2
- 省略了类型则会根据值判断是何种类型
- 变量声明后有默认值:
- 数字型:0
- 布尔型:false
- 字符串:””
- 接口、指针、引用类型:nil
- 在函数中声明变量,可以省略 var 采用短变量声明
- name1,name2 := val1,val2
- 如果前面存在了 name2 则只声明 name1,但是至少有一个是未声明的
- i, j = j, i 注意赋值和运算是从右到左边进行的
- 包级别的变量会在 main 函数开始之前初始化,函数变量随着函数执行初识化
- 包中的变量可以不被使用,函数和词法块里的变量声明后必须被使用,除了 _

多种声明和类型检验方式,使得 go 虽然是静态语言,但是具有不亚于动态语言的开发效率,代码也更简洁,即使它变得不能让人一眼看懂,反过来说,就算 java 能一眼看懂,你能一眼看完?

指针

不是所有的值都有地址,但是所有的变量都有地址

  var p *string
  p = &"kanggege"
  *p == "kanggege"

相比较 Java、js 一切皆对象(引用),go 给予了更灵活的选择,在后面我们还会看到,虽然选择更灵活了,但是使用的方便性却一点也没打折扣

new函数

new(T):创建一个未命名变量,初始化其零值,并返回其地址
每次调用 new(T) 都会返回一个新的地址,但是:如果不携带任何信息,则会是相同地址(因为创建这这样的变量毫无意义)

  new(struct{}) == new([0]int)

new 是一个预声明的函数,并不是关键字,可以被重定义

  func ex(new int){ //重定义为一个函数参数
    fmt.Println(new)
  }

生命周期

  • 包级别的变量是整个程序的执行时间
  • 函数变量只存在函数的运行期间,每次执行时创建,完成后被回收
  • 函数的参数和返回值也是局部变量,他们在其闭包函数被调用时创建
  • 编译器可以选择堆或者栈来为变量分配空间,即使是函数局部变量,如果在函数执行后还能被访问,那么它会被分配堆内存
var golbal *int
func f() {
  x := 1    //会被分配堆内存
  golbal = &x //因为全局变量使用了它
}

如果错误的使用,它并非蜜糖,而是砒霜,在长声明周期对象中,不要保存不必要的短声明周期对象,因为它会妨碍垃圾回收器

赋值

go 的赋值与其它高级语言常见赋值方式相同

  x = 1
  *p = &n
  person.name = "kanggege"
  y *= z
  a++; a--

  func gcd(x, y int) int {
    for y != 0 {
      x, y = y, x%y
    } //求最大公约数,辗转相除
    return x
  }

  func fib(n int) int {
    x, y := 0, 1
    for i = 0; i < n; i++ {
      x, y = x+y, x
    }
    return x
  }

  _, ok = x.(T) //将不需要的值赋值给 _

多重赋值虽然使得代码更紧凑简洁,特别是当一个函数返回多个值时,但是过于复杂的赋值可能会影响代码的可读性

_ 是 go 内建的已声明过的参数,用于处理不需要值(有的时候你不得不接受一些用不到的参数,而参数声明却未使用是会报错的,用 _ 接受就不会报错)

类型声明

type 定义新的命名类型,它实际仍使用已有的底层数据,就像买来的高乐积木,虽然拼出的东西形状各异,实际用的都是那几个积木零件

在我看来,命名类型在某些用法上,就像 java 的配置文件,将实际类型与代码解耦,实际上 java 倒是更需要命名类型,因为 Java 基本类型间存在隐式类型转换,而 go 所有的转换都必须是显示调用 T(x) 进行转换

类型的声明常常出现在包级别,也可导出

不同类型之间的运算也很有意思

  var num1 Num1 = 1
  var num2 Num2 = 2
  num3 := 3
  fmt.Println(num1 > 0)
  fmt.Println(num2 > num1) //报错
  fmt.Println(num3 > num2) //报错
  • 不同类型的变量间,即使底层类型相同,也无法直接运算
  • 可以和相同类型的直接量运算

这样就保证了类型的严格性,避免了代码中可能出现的坑(部分使用命名类型,部分使用底层类型,编写时没问题,如果命名类型的底层类型一旦更换,代码瞬间就崩了)

包和文件

  • 一个目录下的所有文件必须声明同一个包名
  • 目录下的目录可以使用其他的包
  • 通过目录路径导入目录,但实际使用仍旧以目录下命名的包名使用
  • 最佳实践:目录下的包名和目录名保持一致
  • 可能会有不同导入路径,但包名却相同的包,可以为包绑定别名
  _ "strconv"
  f "fmt"

包的初始化从包级别的变量开始,在依赖解析之后按照依赖的顺序进行初始化

任何文件都可以包含任意多个 init 初始化函数,当程序启动时,初始化顺序如下
- 初始化 import 的包,无论该包是否被使用
- 查找依赖关系,初始化包内全局变量和 init 函数

猜你喜欢

转载自blog.csdn.net/weixin_39653200/article/details/80770062