Go语言基础语法(一) | 青训营笔记

85 阅读8分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第9天。

简介

1、Go是一门编译型语言,Go语言的工具链将源代码及其依赖转换成计算机的机器指令(译注:静态编译)。Go语言提供的工具都通过一个单独的命令go调用,go命令有一系列子命令。最简单的一个子命令就是run。这个命令编译一个或多个以.go结尾的源文件,链接库文件,并运行最终生成的可执行文件。

go run helloworld.go

如果不只是一次性实验,你肯定希望能够编译这个程序,保存编译结果以备将来之用。可以用build子命令:

$ go build helloworld.go

2、Go语言不需要在语句或者声明的末尾添加分号,除非一行上有多条语句。实际上,编译器会主动把特定符号后的换行符转换为分号,因此换行符添加的位置会影响Go代码的正确解析(译注:比如行末是标识符、整数、浮点数、虚数、字符或字符串文字、关键字break、continue、fallthrough或return中的一个、运算符和分隔符++、--、)、]或}中的一个)。举个例子,函数的左括号{必须和func函数声明在同一行上,且位于末尾,不能独占一行,而在表达式x + y中,可在+后换行,不能在+前换行(译注:以尾的话不会被插入分号分隔符,但是以x结尾的话则会被分号分隔符,从而导致编译错误)。

3、

image.png

4、Go语言主要有四种类型的声明语句:var,const,type和func,分别对应变量、常量、类型和实体对象的声明。

变量

变量的命名

一个名字必须以一个字母(Unicode字母)或下划线开头,后面可以跟任意数量的字母、数字或下划线。 严格区分大小写! 不能使用关键字命名!

名字的长度没有逻辑限制,但是Go语言的风格是尽量使用短小的名字,对于局部变量尤其是这样;你会经常看到i之类的短名字,而不是冗长的theLoopIndex命名。通常来说,如果一个名字的作用域比较大,生命周期也比较长,那么用长的名字将会更有意义。

变量的作用范围

1、函数外部声明的、包一级范围声明的语句(包变量)可以在整个包所在的源文件种访问(同一个包可以访问)

2、函数中声明的变量(局部变量),出了函数的大括号之后就失效了,不能访问了。

3、名字的开头字母的大小写决定了名字在包外的可见性。如果一个名字是大写字母开头的(译注:必须是在函数外部定义的包级名字;包级函数名本身也是包级名字),那么它将是导出的,也就是说可以被外部的包访问,例如fmt包的Printf函数就是导出的,可以在fmt包外部访问。包本身的名字一般总是用小写字母。

4、推荐使用驼峰式命名(大驼峰或小驼峰)

声明一个变的四种方式

注意:变量必须先声明后使用!

1、s := " "

短变量声明,最简洁,但是只能用在函数内部,而不能用于包变量

2、var s string

依赖于字符串的默认初始化零值机制,默认为" "

3、var s = " "

很少用,除非同时声明多个变量

4、var s string = " "

显式标明了指定变量的类型,当变量类型和初值相同时,类型冗余,如果两者不同,变量类型就必须指明,否则使用隐式初始化。

var声明语句

var声明语句可以创建一个特定类型的变量,然后给变量附加一个名字,并且设置变量的初始值。变量声明的一般语法如下:

var 变量名字 类型 = 表达式 复制代码 其中“类型”或“= 表达式”两个部分可以省略其中的一个。如果省略的是类型信息,那么将根据初始化表达式来推导变量的类型信息。如果初始化表达式被省略,那么将用零值初始化该变量。 数值类型变量对应的零值是0,布尔类型变量对应的零值是false,字符串类型对应的零值是空字符串,接口或引用类型(包括slice、指针、map、chan和函数)变量对应的零值是nil。数组或结构体等聚合类型对应的零值是每个元素或字段都是对应该类型的零值。

零值初始化机制可以确保每个声明的变量总是有一个良好定义的值,因此在Go语言中不存在未初始化的变量。这个特性可以简化很多代码,而且可以在没有增加额外工作的前提下确保边界条件下的合理行为。例如:

var s string fmt.Println(s) // "" 复制代码 这段代码将打印一个空字符串,而不是导致错误或产生不可预知的行为。

也可以在一个声明语句中同时声明一组变量,或用一组初始化表达式声明并初始化一组变量。如果省略每个变量的类型,将可以声明多个类型不同的变量(类型由初始化表达式推导):

var i, j, k int                 // int, int, int
var b, f, s = true, 2.3, "four" // bool, float64, string

var形式的声明语句往往是用于需要显式指定变量类型的地方,或者因为变量稍后会被重新赋值而初始值无关紧要的地方。

包变量会在main函数执行之前完成初始化

局部变量会在声明语句被执行到的时候完成初始化

一组变量也可以通过调用一个函数,由函数返回的多个返回值初始化:


var f, err = os.Open(name) // os.Open returns a file and an error

简短变量声明

常用于局部变量的声明和初始化!

语法:

变量名 := 表达式

简短变量声明语句也可以用来声明和初始化一组变量:

i, j := 0, 1

注意! “:=”是一个变量声明语句,而“=”是一个变量赋值操作。也不要混淆多个变量的声明和元组的多重赋值(§2.4.1),后者是将右边各个表达式的值赋值给左边对应位置的各个变量:

i, j = j, i // 交换 i 和 j 的值

和普通var形式的变量声明语句一样,简短变量声明语句也可以用函数的返回值来声明和初始化变量,像下面的os.Open函数调用将返回两个值:

f, err := os.Open(name)
if err != nil {
    return err
}
// ...use f...
f.Close()

简短变量声明左边的变量可能并不是全部都是刚刚声明的。如果有一些已经在相同的词法域声明过了,那么简短变量声明语句对这些已经声明过的变量就只有赋值行为了。

在下面的代码中,第一个语句声明了in和err两个变量。在第二个语句只声明了out一个变量,然后对已经声明的err进行了赋值操作。

in, err := os.Open(infile)
// ...
out, err := os.Create(outfile)

简短变量声明语句中必须至少要声明一个新的变量,下面的代码将不能编译通过:

f, err := os.Open(infile)
// ...
f, err := os.Create(outfile) // compile error: no new variables

解决的方法是第二个简短变量声明语句改用普通的多重赋值语句。

简短变量声明语句只有对已经在同一个域声明过的变量才和赋值操作语句等价,如果变量是在外部域声明的,那么简短变量声明语句将会在当前词法域重新声明一个新的变量。

指针

一个指针的值是另一个变量的地址。一个指针对应变量在内存中的存储位置。并不是每一个值都会有一个内存地址,但是对于每一个变量必然有对应的内存地址。通过指针,我们可以直接读或更新对应变量的值,而不需要知道该变量的名字(如果变量有名字的话)。

x := 1 //x的值是int类型的1
p := &x         // p指针对应的数据类型是*int, 指向变量 x
fmt.Println(*p) // *p,表示取p指针对应的变量值,即 "1"
*p = 2          // 改变p指针指向的变量值,为 x = 2
fmt.Println(x)  // x的值变为"2",这里没有使用变量名x重新赋值,而是使用指向x的指针直接操作x的值。
 

new函数

另一个创建变量的方法是调用内建的new函数。表达式new(T)将创建一个T类型的匿名变量,初始化为T类型的零值,然后返回变量地址,返回的指针类型为*T。

p := new(int)   // p, *int 类型, 指向匿名的 int 变量
fmt.Println(*p) // "0"
*p = 2          // 设置 int 匿名变量的值为 2
fmt.Println(*p) // "2"

用new创建变量和普通变量声明语句方式创建变量没有什么区别,除了不需要声明一个临时变量的名字外,我们还可以在表达式中使用new(T)。

下面的两个newInt函数有着相同的行为:

func newInt() *int {
    return new(int)
}
 
func newInt() *int {
    var dummy int
    return &dummy
}

每次调用new函数都是返回一个新的变量的地址,因此下面两个地址是不同的:

p := new(int)
q := new(int)
fmt.Println(p == q) // "false"