1-Go - 基础语法

Go语言项目结构

再深入研究hello world之前,还是要再来说说Go语言的项目结构的。

在进行Go语言开发的时候,我们的代码总是会保存在$GOPATH/src目录下。在工程经过go buildgo installgo get等指令后,会将下载的第三方包源代码文件放在$GOPATH/src目录下, 产生的二进制可执行文件放在 $GOPATH/bin目录下,生成的中间缓存文件会被保存在 $GOPATH/pkg 下。

如果我们使用版本管理工具(Version Control System,VCS。常用如Git)来管理我们的项目代码时,我们只需要添加$GOPATH/src目录的源代码即可。bin pkg 目录的内容无需版本控制。

Go语言中也是通过包来组织代码文件,我们可以引用别人的包也可以发布自己的包,但是为了防止不同包的项目名冲突,我们通常使用顶级域名来作为包名的前缀,这样就不担心项目名冲突的问题了。

但因为不是每个个人开发者都拥有自己的顶级域名,所以目前流行的方式是使用个人的github用户名来区分不同的包,如下图:

大致对项目结构有了了解之后,并伴随后续学习,会对项目结构有更深的理解。

从hello world说Go语言的代码结构

Go语言的代码结构

现在我们来创建第一个Go语言项目——hello。在你的GOPATH路径下的src目录中创建hello目录,然后在该hello目录下新建一个main.go的文件,写代码:

package main 	// 声明 main 包,表明当前是一个可执行程序

import "fmt"	// 导入内置的 fmt 包

// main 函数是程序的入口
func main() {
    // 这是单行注释
    /*
        这里是多行注释
    */
    fmt.Println("hello world!")		// 在终端打印 hello world!
}

Go语言代码都是存放在以.go为后缀名的文件中,每个go语言源代码文件大致组成如上例所示。

  • 第一行package关键字定义了包名。注意,必须在源代码文件中非注释的第一行指明这个文件属于哪个包。上例的package main表示这是一个可独立执行的程序,编译后会得到一个可执行文件。
  • import "fmt"表示这个程序需要使用fmt包。
  • func main()定义了程序执行的入口函数,每一个可执行程序都必须包含main函数。
  • //表示单行注释,以/*开头并且以*/结尾的是多行注释,多行注释也叫块注释,多行注释一般用于包的文档描述或者注释成块的代码。另外,块注释不能嵌套使用,而且,注释也是代码中必不可少的一部分。
  • fmt.Println("hello world!")是调用fmt包的Println函数在终端输出指定的hello world!,并在最后自动添加换行符\n

知道了各行代码的意思,我们就可以在hello目录内打开终端,执行go build命令,该命令表示将Go源代码编译成可执行文件,然后你可以在终端中执行生成的可执行文件了。

默认的go build命令编译生成的二进制文件名和go的代码文件名一致,这里还可以加参数-o来为编译后生成的二进制文件自定义名称:

// 会在 当前目录下生成 main.exe 可执行文件
D:\MyGo\src\hello❯ go build main.go

// 通过 -o file_name 自定义生成的二进制文件名
D:\MyGo\src\hello❯ go build -o hello.exe main.go

再来补充一个不常用的命令:

// install 命令会将生成的可执行文件存放在项目的bin目录中
D:\MyGo\src\hello❯ go install .\main.go

Go语言语法特点

再来说说Go代码本身的一些特点:

  • Go语言自带代码格式化,所以对代码中的缩进并无强制要求。
  • Go语言中的左花括号{不能单独放在一行,它必须紧跟在其他语句的右侧。
  • Go语言中一行代码就表示一个Go语句的结束,不需要在后面添加;分隔符。
  • Go语言中的逻辑语句必须放在函数中。
  • Go语言中不允许存在未使用的变量,如果存在,在编译时会报错。所以,变量一经定义就必须调用。
  • Go语言中推荐使用驼峰式命名规则。
  • Go语言默认使用UTF-8编码。

标识符与关键字

标识符

在编程语言中标识符就是程序员定义的具有特殊意义的词,比如变量名、常量名、函数名等等。 Go语言中标识符由字母数字和下划线组成,并且只能以字母或下划线开头。 举几个例子:abc __123a123

关键字

关键字是指编程语言中预先定义好的具有特殊含义的标识符。Go语言中有25个关键字:

break	default	func	interface	select
case	defer	go	map	struct
chan	else	goto	package	switch
const	fallthrough	if	range	type
continue	for	import	return	var

此外,Go语言还有37个保留字:

Constants:
	true false iota nil

Types:
	int int8 int16 int32 int64 uint
	uint8 uint16 uint64 uintptr
	float32 float64 complex128 complex128
	bool byte rune string error

Functions:
	make len cap new append copy close
	delete complex real imag panic recover

通常关键字和保留字都不建议用作标识符。

另外,Go对大小写敏感!其次,Go语言中,双引号表示字符串,单引号表示字符。

变量

我们通过一个名字,指向一个人,或者指向一个具体的事物。这在Go中是同样适用。在Go中,这个名字称为变量,而指向的对象为一串字符串,一个具体的数值等等。变量也是可变状态的、对内存地址一种抽象。

变量(Variable)的功能是存储数据。不同的变量保存的数据类型可能会不一样。经过半个多世纪的发展,编程语言已经基本形成了一套固定的类型,常见变量的数据类型有:整型、浮点型、布尔型等。

Go语言中的每一个变量都有自己的类型,并且变量必须经过声明才能开始使用。

声明与初始化

Go语言中的变量需要声明后才能使用,同一作用域内不支持重复声明。 并且Go语言的变量声明后必须使用,否则无法通过编译。

声明变量

标准声明

// var 变量名 变量类型

变量声明以关键字var开头,变量类型放在变量的后面,行尾无需分号,示例:

package main

import "fmt"

func main()  {
    var a int       // 声明时没有赋值, int:0
    var b string	// 默认为: ""
    fmt.Println(a, b)
}

如上,只有声明,但没有给该变量赋值,打印时会有默认值。

批量声明

每声明一个变量就需要写var关键字会比较繁琐,go语言中还支持批量变量声明:

var (
    a string
    b int
    c bool
)

其他声明方式
一行声明多个同类型的变量:

var x, y int

初始化变量

Go语言在声明变量的时候,会自动对变量对应的内存区域进行初始化操作。每个变量会被初始化成其类型的默认值,例如:

  • 整型和浮点型变量的默认值为0
  • 字符串变量的默认值为""
  • 布尔型变量默认为false
  • 切片、函数、指针变量的默认为nil

我们也可以在声明时为其指定初始值:

var 变量名 变量类型 = 表达式

// 示例
var name string = "张开"
var age int = 18

也可以一次声明多个值:

var name, age = "张开", 18

再次强调,同一个作用域内,变量不支持重复声明,否则无法通过编译:

package main

import "fmt"

func main() {
    var name = "张开"
    var age = 18
    var name = "张不开" // 无法通过编译,报错
    fmt.Println(name, age)
}

类型推导

有时候我们会将变量的类型省略,这个时候编译器会根据等号右边的值来推导变量的类型完成初始化。

var name = "张开"   // 编译器根据 "张开" 的类型推导 name 的类型是 string
var age = 18

短变量声明

在函数内部,可以使用更简略的 := 方式声明并初始化变量:

package main

import "fmt"

var name = "张不开"   // 在全局可以使用var来声明变量
//name2 := "张开了"   // 在全局不可以使用短变量声明

func main() {
    name := "张开"
    fmt.Println(name)
}

匿名变量

有些情况下,在多个值中,如果想要忽略某个值,可以使用匿名变量(anonymous variable)。 匿名变量用一个下划线_表示,例如:

package main

import "fmt"

func main() {
    name, _ := "张开", 18
    _, age := "张开", 18
    fmt.Println(name, age)
}

匿名变量不占用命名空间,不会分配内存,所以匿名变量之间不存在重复声明问题。 (在Lua等编程语言里,匿名变量也被叫做哑元变量。)

注意事项:

  1. 函数外的每个语句都必须以关键字开始(varconstfunc等)
  2. :=不能使用在函数外。
  3. _多用于占位,表示忽略值。

常量

声明

相对于变量,常量是恒定不变的值,多用于定义程序运行期间不会改变的那些值。 常量的声明和变量声明非常类似,只是把var换成了const,常量在定义的时候必须赋值:

const pi = 3.1415
const ip = 127.0.0.1
const port = 8080

当变量在声明后,在整个程序运行期间它们的值都不能再发生变化了。

多个常量也可以一起声明:

const(
    pi = 3.1415
    ip = 127.0.0.1
    port = 8080
)

const同时声明多个常量时,如果省略了值则表示和上面一行的值相同。 例如:

package main

import "fmt"

func main() {
    const(
        n1 = 100
        n2
        n3
    )
    fmt.Println(n1, n2, n3)  // 100 100 100
}

iota

iota是go语言的常量计数器,只能在常量的表达式中使用

iotaconst关键字出现时将被重置为0。const中每新增一行常量声明将使iota计数一次(iota可理解为const语句块中的行索引)。 使用iota能简化定义,在定义枚举时很有用。

package main

import "fmt"

func main() {
    const(
        n1 = iota	// iota遇到const重置为0,所以 n1 = 0
        n2			// 每新增一行常量声明 iota加1,所以 n2 = 1
                    // 空行不算,因为"每新增一行常量声明,iota加1",空行当然不算了
        n3			// 每新增一行常量声明 iota加1,所以 n2 = 2
    )
    fmt.Println(n1, n2, n3)  // 0 1 2
}

再来看几种常见的iota示例。

使用_跳过忽略某些值:

package main

import "fmt"

func main() {
    const(
        n1 = iota	// iota遇到const重置为0,所以 n1 = 0
        n2			// 每新增一行常量声明 iota加1,所以 n2 = 1
        _			// 也算一行声明,只不过这个值我们不要 _ = 2
        n3			// 每新增一行常量声明 iota加1,所以 n2 = 3
    )
    fmt.Println(n1, n2, n3)  // 0 1 3
}

一匹黑马强势插入iota中:

package main

import "fmt"

func main() {
    const(
        n1 = iota	// iota遇到const重置为0,所以 n1 = 0
        n2 = 100	// n2 = 100
        n3 = iota	// iota加1 n3 = 2
        n4			// 如果声明时,常量的值被省略,则它的值和上一行的常量值一样 n4 = iota ---> n4 = 3
        /*
        n4这里理解起来比较麻烦,先看规则,在多个常量声明时,某个常量的值被省略,则它的值和上一行的常量值一样
        这里的值指的是:
        n3 = iota
        n4				// 指的是n4的值跟n3的值一样,但n3的值是iota,而不是iota的值,相当于n4 = iota
        */
    )
    fmt.Println(n1, n2, n3, n4)  // 0 100 2 3
}

iota加位移运算的使用,1<<10相当于210,依次类推:

package main

import "fmt"

func main() {
    const(
        _  = iota					// iota = 0
        KB = 1 << (10 * iota)		// iota = 1; 1<<10 2 **10 ---> 1024
        MB = 1 << (10 * iota)		// iota = 2; 1<<20 2 ** 20 ---> 1048576
        GB = 1 << (10 * iota)		// iota = 3; 1<<30 2 ** 30 ---> 1073741824
        TB = 1 << (10 * iota)		// iota = 4; 1<<40 2 ** 40 ---> 1099511627776
        PB = 1 << (10 * iota)		// iota = 5; 1<<50 2 ** 50 ---> 1125899906842624
    )
    fmt.Println(KB, MB, GB, TB, PB)  // 1024 1048576 1073741824 1099511627776 1125899906842624
}

多个iota定义在一行:

package main

import "fmt"

func main() {
    const(
        a, b = iota + 1, iota + 2    // iota = 0  a = 0 + 1; b = 0 + 2  ---> a = 1; b = 2
        c, d						// iota = 1 c = 1 + 1; d = 1 + 2  ---> c = 2; d = 3
        e, f						// iota = 2 c = 2 + 1; d = 2 + 2  ---> c = 3; d = 4
    )
    fmt.Println(a, b, c, d, e, f)  // 1 2 2 3 3 4
}

如果你对上面几个示例不太明白,那你一定要牢记iota的特性:

  • 遇到const初始化为0
  • const每新增一行变量声明iota就递增1
  • const声明如果不写,默认就和上一行一样

跨平台编译

默认我们go build的可执行文件都是当前操作系统可执行的文件,如果我想在windows下编译一个linux下可执行文件,那需要怎么做呢?

只需要在go build之前,指定目标操作系统的平台和处理器架构即可:

SET CGO_ENABLED=0  // 禁用CGO
SET GOOS=linux  // 目标平台是linux
SET GOARCH=amd64  // 目标处理器架构是amd64

PS:使用了cgo的代码是不支持跨平台编译的。

然后再执行go build命令,得到的就是能够在Linux平台运行的可执行文件了。

Mac 下编译 Linux 和 Windows平台 64位 可执行程序:

CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build

Linux 下编译 Mac 和 Windows 平台64位可执行程序:

CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build

Windows下编译Mac平台64位可执行程序:

SET CGO_ENABLED=0
SET GOOS=darwin
SET GOARCH=amd64
go build

批量导包

在之前的示例中,只使用了fmt这个包来做打印操作,那么如果同时用到多个包该怎么导入呢?跟批量声明变量一样使用括号:

package main

// 在括号内导入多个包
import (
    "fmt"
    "math"
)

func main() {
    fmt.Println(math.MaxFloat64)
}

that's all, see also:

Go语言基础之变量和常量

posted @ 2020-11-07 15:20  听雨危楼  阅读(245)  评论(0编辑  收藏  举报