基本语法

寒江蓑笠翁大约 15 分钟

基本语法

Go的基本语法十分简洁且简单,下面通过一个简单的示例来进行讲解。

package main

import "fmt"

func main() {
   fmt.Println("Hello 世界!")
}

package关键字代表的是当前go文件属于哪一个包,启动文件通常是main包,启动函数是main函数,在自定义包和函数时命名应当尽量避免与之重复。

import是导入关键字,后面跟着的是被导入的包名。

func是函数声明关键字,用于声明一个函数。

fmt.Println("Hello 世界!")是一个语句,调用了fmt包下的Println函数进行控制台输出。


以上就是一个简单的语法介绍。

在Go中,程序是通过将包链接在一起来构建的,也可以理解为最基本的调用单位是包,而不是go文件。包其实就是一个文件夹,包内共享所有源文件的变量,常量,函数以及其他类型。包的命名风格建议都是小写字母,并且要尽量简短。


导入

例如创建一个example包,包下有如下函数

package example

import "fmt"

func SayHello() {
   fmt.Println("Hello")
}

main函数中调用

package main

import "example"

func main() {
   example.SayHello()
}

还可以给包起别名

package main

import e "example"

func main() {
   e.SayHello()
}

批量导入时,可以使用括号()来表示

package main

import (
   "fmt"
   "math"
)

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

或者说只导入不调用,通常这么做是为了调用该包下的init函数。

package main

import (
   "fmt"
    _ "math" // 下划线表示匿名导入
)

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

注意

在Go中完全禁止循环导入,不管是直接的还是间接的。例如包A导入了包B,包B也导入了包A,这是直接循环导入,包A导入了包C,包C导入了包B,包B又导入了包A,这就是间接的循环导入,存在循环导入的话将会无法通过编译。


导出

在Go中,导出和访问控制是通过命名来进行实现的,如果想要对外暴露一个函数或者一个变量,只需要将其名称首字母大写即可,例如example包下的SayHello函数。

package example

import "fmt"

// 首字母大写,可以被包外访问
func SayHello() {
   fmt.Println("Hello")
}

如果想要不对外暴露的话,只需将名称首字母改为小写即可,例如下方代码

package example

import "fmt"

// 首字母小写,外界无法访问
func sayHello() {
   fmt.Println("Hello")
}

对外暴露的函数和变量可以被包外的调用者导入和访问,如果是不对外暴露的话,那么仅包内的调用者可以访问,外部将无法导入和访问,该规则适用于整个Go语言,例如后续会学到的结构体及其字段,方法,自定义类型,接口等等。


内部包

go中约定,一个包内名为internal 包为内部包,外部包将无法访问内部包中的任何内容,否则的话编译不通过,下面看一个例子。

/home/user/go/
    src/
        crash/
            bang/              (go code in package bang)
                b.go
        foo/                   (go code in package foo)
            f.go
            bar/               (go code in package bar)
                x.go
            internal/
                baz/           (go code in package baz)
                    z.go
            quux/              (go code in package main)
                y.go

由文件结构中可知,crash包无法访问baz包中的类型。

注释

Go支持单行注释和多行注释,注释与内容之间建议隔一个空格,例如

// 这是main包
package main

// 导入了fmt包
import "fmt"

/*
*
这是启动函数main函数
*/
func main() {
	// 这是一个语句
	fmt.Println("Hello 世界!")
}

标识符

标识符就是一个名称,用于包命名,函数命名,变量命名等等,命名规则如下:

  • 只能由字母,数字,下划线组成
  • 只能以字母和下划线开头
  • 严格区分大小写
  • 不能与任何已存在的标识符重复,即包内唯一的存在
  • 不能与Go任何内置的关键字冲突

下方列出所有的内置关键字,也可以前往参考手册-标识符open in new window查看更多细节

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语言中支持的运算符号的优先级排列,也可以前往参考手册-运算符open in new window查看更多细节。

Precedence    Operator
    5             *  /  %  <<  >>  &  &^
    4             +  -  |  ^
    3             ==  !=  <  <=  >  >=
    2             &&
    1             ||

有一点需要稍微注意下,go语言中没有选择将~作为取反运算符,而是复用了^符号,当两个数字使用^时,例如a^b,它就是异或运算符,只对一个数字使用时,例如^a,那么它就是取反运算符。go也支持增强赋值运算符,如下。

a += 1
a /= 2
a &^= 2

提示

Go语言中没有自增与自减运算符,它们被降级为了语句statement,并且规定了只能位于操作数的后方,所以不用再去纠结i++++i这样的问题。

a++ // 正确
++a // 错误
a-- // 正确

还有一点就是,它们不再具有返回值,因此a = b++这类语句的写法是错误的。


字面量

字面量,按照计算机科学的术语来讲是用于表达源代码中一个固定值的符号,也叫字面值。两个叫法都是一个意思,写了什么东西,值就是什么,值就是“字面意义上“的值。


整型字面量

为了便于阅读,允许使用下划线_来进行数字划分,但是仅允许在前缀符号之后数字之间使用。

24 // 24
024 // 24
2_4 // 24
0_2_4 // 24
10_000 // 10k
100_000 // 100k
0O24 // 20
0b00 // 0
0x00 // 0
0x0_0 // 0

浮点数字面量

通过不同的前缀可以表达不同进制的浮点数

0.
72.40
072.40       // == 72.40
2.71828
1.e+0
6.67428e-11
1E6
.25
.12345E+5
1_5.         // == 15.0
0.15e+0_2    // == 15.0

0x1p-2       // == 0.25
0x2.p10      // == 2048.0
0x1.Fp+0     // == 1.9375
0X.8p-0      // == 0.5
0X_1FFFP-16  // == 0.1249847412109375
0x15e-2      // == 0x15e - 2 (integer subtraction)

复数字面量

0i
0123i         // == 123i
0o123i        // == 0o123 * 1i == 83i
0xabci        // == 0xabc * 1i == 2748i
0.i
2.71828i
1.e+0i
6.67428e-11i
1E6i
.25i
.12345E+5i
0x1p-2i       // == 0x1p-2 * 1i == 0.25i

字符字面量

字符字面量必须使用单引号括起来'',Go中的字符完全兼容utf8

'a'
'ä'
'你'
'\t'
'\000'
'\007'
'\377'
'\x07'
'\xff'
'\u12e4'
'\U00101234'

转义字符

Go中可用的转义字符

\a   U+0007 响铃符号(建议调高音量)
\b   U+0008 回退符号
\f   U+000C 换页符号
\n   U+000A 换行符号
\r   U+000D 回车符号
\t   U+0009 横向制表符号
\v   U+000B 纵向制表符号
\\   U+005C 反斜杠转义
\'   U+0027 单引号转义 (该转义仅在字符内有效)
\"   U+0022 双引号转义 (该转义仅在字符串内有效)

字符串字面量

字符串字面量必须使用双引号""括起来或者反引号(反引号字符串不允许转义)

`abc`                // "abc"
`\n
\n`                  // "\\n\n\\n"
"\n"
"\""                 // `"`
"Hello, world!\n"
"今天天气不错"
"日本語"
"\u65e5本\U00008a9e"
"\xff\u00FF"

风格

关于编码风格这一块Go是强制所有人统一同一种风格,Go官方提供了一个格式化工具gofmt,通过命令行就可以使用,该格式化工具没有任何的格式化参数可以传递,仅有的两个参数也只是输出格式化过程,所以完全不支持自定义,也就是说所有通过此工具的格式化后的代码都是同一种代码风格,这会极大的降低维护人员的心智负担,所以在这一块追求个性显然是一个不太明智的选择。


下面会简单列举一些规则,平时在编写代码的时候也可以稍微注意一下。

  1. 花括号,关于花括号{}到底该不该换行,几乎每个程序员都能说出属于自己的理由,在Go中所有的花括号都不应该换行。
// 正确示例
func main() {
	fmt.Println("Hello 世界!")
}

// 错误示例
func main() 
{
	fmt.Println("Hello 世界!")
}
  1. 缩进,Go默认使用tabs也就是制表符进行缩进,仅在一些特殊情况会使用空格。

  2. 间隔,Go中大部分间隔都是有意义的,从某种程度上来说,这也代表了编译器是如何看待你的代码的,例如下方的数学运算

    2*9 + 1/3*2
    

    众所周知,乘法的优先级比加法要高,在格式化后,*符号之间的间隔会显得更紧凑,意味着优先进行运算,而+符号附近的间隔则较大,代表着较后进行运算。

  3. 还是花括号,花括号在任何时候都不能够省略,就算是只有一行代码,例如

    // 正确示例
    if a > b {
    	a++
    }
    // 错误示例
    if a > b a++