lang

LangBook

4.变量与声明

1. var 语法

var 的 语法为

var 变量名 类型 =

举个例子,声明一个类型为int (整数类型),值为 1 的变量 num:

var num int = 1

Go 声明变量本质上是在内存中开辟一块空间,那开辟多大的空间呢? int是一个特殊的类型,在 32 位系统中占 32bit, 在 64 位系统占 64bit。 内存中以字节(byte)为基本单位,1byte = 8bit。

'var num int = 1' 在内存的占用

系统位数类型占用内存二进制高位填充 0
32 位int32 bit 即 4 byte1100000000 00000000 00000000 00000001
64 位int64 bit 即 8 byte1100000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001

我的系统是 64 位,就会开辟 8byte 的内存,并把值1存到这个空间。然后给这块空间取个名字叫num

我们可以通过num变量读出内存中的值,也可以赋值给num改变内存中的值。num就是这块空间的代言人,我们通过它操作内存的读和写。

  • 缺省值

如果还不确定这个变量要赋什么值,可以缺省, 例如

var num int // num = 0

这样,Go 会在声明时自动赋给变量零值,int(整数)的零值就是 0

  • 省略类型

如果知道这个变量要赋予什么值,可以省略类型。

var num = 1

Go 会把字面量1自动推断为类型int

什么是字面量?

字面量就是源代码中的值,例如

var num = 1
var str = "hello"

1"hello"就是字面量,每个字面量都有默认的类型,1int类型,"hello"string类型。

与字面量相对的是变量,例如

var num = 1
var num2 = num

num不是字面量。

  • 同时声明多个变量
var a, b = 1, 2
var c, d int // c = 0, d = 0

这在交换两个变量时非常有用,例如,交换 a,b 的值

var a, b = 1, 2
a, b = b, a // a = 2, b = 1, 交换值

小技巧

当我们声明一组相关的变量时,可以使用块包裹

package main
 
var (
	num1 = 1
	num2 = 2
	str  = "hello world"
)

这样可以提高代码可读性,其他关键字import, type,const也适用这个规则。

2. 短声明语句

除了使用var声明,我们还可以使用更简单的短声明语句:=

num := 1

等价于

var num int = 1

:=语句很方便,但只能在函数内使用。

package main
 
import (
	"fmt"
	"os"
)
 
num1 := 1 	 // 不OK, 会在编译期报错
var num2 = 1 // Ok 包级变量使用var
 
func main() {
    num3 := 1 // OK, 在函数内使用
	fmt.Println(num3)
}

我们使用得最多的就是:=,因为大部分声明都是在函数里。

什么是包级变量

包级变量就是在包下直接声明的变量,而不是在函数中。如上述代码的num2, main就是包级变量,而num3是 main 函数内的变量。在 Go 中,函数就是值,能赋值给变量,这部分将会在函数章节讲述。

:=也可以同时声明多个变量

a, b := 1, 2

3. 强类型语言

强类型就是,一个变量一旦声明,它的类型就已确定,并且不能改变。例如:

package main
 
func main() {
	var num int = 1
	num = "hello" // 编译器报错: cannot use "string" (untyped string constant) as int value in assignment
}

num 是int类型,不能把string类型的"hello"赋值给它,这会导致编译期错误。

什么是编译期错误?

  • 编译期错误: 编译期间发生的错误,无法完成编译。

  • 运行时错误: 程序能编译成功,但运行过程中发生错误。

4. 不允许变量声明却不使用

一个变量声明却不使用,就相当于你买了个手机却不用,如果不用,就没有必要买。同样,如果你不使用一个变量就没必要声明(创建)它。

main.go
package main
 
func main() {
	var num int = 1
}

运行该程序,出现编译期错误:

declared and not used: num

空白标识符_

package main
 
func main() {
	var num int = 1
    _ = num
}

当我们不想使用这个变量,可以把他赋值给空白标识符__用来忽略不需要的值。这样就不会触发声明但未使用的错误了_for-range循环经常用到。

5. 变量的作用域

定义三个不同的变量

main.go
package main
 
import "fmt"
 
var p = "包级变量"
 
func sum() {
	s := "sum 函数内的变量"
	_ = s
}
 
func main() {
	m := "main 函数内的变量"
 
	fmt.Println(p)
	fmt.Println(s) // 无法访问到s变量
    fmt.Println(m)
}

运行,编译器报错:

./main.go:16:14: undefined: s

因为变量都有自己的作用域,只有在变量的作用域内才能使用该变量。

变量作用域怎么确定呢? 规则是

  1. 包级变量作用域是整个包。在相同包里的任何地方都可以使用该变量
  2. 其他变量的声明语句在哪个大括号{}里,那个大括号{}所包裹的范围就是该变量的作用域。
  3. Go 为所有包提供了一个全局作用域,存放内置函数,如len(), cap(), make()等。

如图, 不同的颜色对应不同的作用域。

  • 变量p是包级变量,作用范围是整个main包
  • 变量s声明在sum函数的大括号里,其作用范围就是大括号{}包裹的区域,即sum函数的函数体。
  • 变量m声明在main函数的大括号里,其作用范围就是大括号{}包裹的区域,即main函数的函数体。

考你一个问题,下面代码能编译成功吗?

main.go
package main
 
import "fmt"
 
func main() {
	num := 1
 
	{
		num2 := 2
	}
 
	fmt.Println(num)
	fmt.Println(num2)
}

答案是不能。

6. 可导出与不可导出

你在一个包里定义了一个变量, 想让其他的包使用这个变量,如何做到呢?

要新建一个包,你需要新建一个文件夹,一个文件夹就是一个包,一般我们把文件夹的名字作为包名。

建立如下的项目结构:

echo
├── foo
│   └── foo.go
├── go.mod
└── main.go

foo.go声明一个包级变量Num

foo/foo.go
package foo // 当前在foo包
 
var Num = 1 // Num首字母大写,表明Num可导出,其他包可以调用Num变量

我们在main.go中打印 foo.Num

main.go
package main
 
import (
	"fmt"
 
	"github.com/aeilang/echo/foo"
)
 
func main() {
	fmt.Println(foo.Num) // 调用foo包里的Num变量。
}

输出

1

在 Go 中,可导出的变量以大写字母开头,不可导出的变量以小写字母开头。Go 中的隔离级别只有包级别:

  • 可导出: 大写字母开头,对包外可见,其他包能调用这个变量。例如foo包里的Num变量
  • 不可导出: 小写字面开头,对包外不可见,其他包不能调用这个变量。

函数,类型也是一样的规则,(函数名就是变量)。fmt包里的Println函数就是大写开头,我们才能调用它。

为什么要设置包隔离级别呢?

设想一下你创建了一个包,包里有很多函数、变量和类型,你并不想把所有的东西都暴露出去,因为其他包能对你暴露的东西进行更改。你只暴露你想让其他包使用的东西。这能让你的包更安全,也让调用者没有心理负担!这是 Go 伟大的发明,简单,直接!

7. 常量

Go 的常量很简单,除了声明后不能改变,其他的都与变量相同。

使用const关键字声明常量:

const pi float64 = 3.1415

float64是浮点数类型,就是小数的意思。 因为声明后不能改变,我们需要在声明时给其赋值。

const 也可以和 var 一样省略类型,让编译器自动推断:

const pi = 3.1415 // 小数字面量默认推断成float64类型

常量声明后不能改变

main.go
package main
 
import "fmt"
 
const pi = 3.1415
 
func main() {
    pi = 3.15 // 常量不能改变,编译期报错
	fmt.Println(pi)
}

小技巧

和其他语言不一样,常量名不需要全部大写,因为 Go 使用首字母的大小写作为其可导出的依据。

iota

iota 是一个常量生成器,用来生成一组常量自动递增的枚举值(go 不支持枚举类型 enum)。例如:

const (
	Sunday    = iota // 0
	Monday           // 1
	Tuesday          // 2
	Wednesday        // 3
	Thursday         // 4
	Friday           // 5
	Saturday         // 6
)

在一个 const 块中,iota 从 0 开始,每遇到一个声明语句就递增 1。如果后面的语句都与前一个相同,我们可以省略后面的赋值语句,直接写变量名,即上述代码与下述相同:

const (
	Sunday    = iota // 0
	Monday    = iota // 1
	Tuesday   = iota // 2
	Wednesday = iota // 3
	Thursday  = iota // 4
	Friday    = iota // 5
	Saturday  = iota // 6
)

总结

  1. 介绍了var声明和短声明语句:=, :=只能在函数里使用。
  2. 变量的作用域在其所声明的大括号{}里。
  3. 可导出的变量首字母大写,不可导出的变量首字母小写。
  4. 常量在初始化后不允许改变。
  5. Go 是强类型的语言,不同类型的变量不能相互赋值。

On this page