Skip to content

基本语法

Go 的基本语法十分简单易懂,让我们从一个最简单的例子开始。

go
package main

import "fmt"

func main() {
   fmt.Println("Hello 世界!")
}
  • package关键字声明了是当前 go 文件属于哪一个包,入口文件都必须声明为main包,入口函数是main函数,在自定义包和函数时命名应当尽量避免与之重复。

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

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

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

以上就是一个简单的语法介绍,下面就来略微细致地去了解里面的概念。

在 Go 中,程序是通过将包链接在一起来构建的。Go 中进行导入的最基本单位是一个包,而不是.go文件。包其实就是一个文件夹,英文名 package,包内共享所有变量,常量,以及所有定义的类型。包的命名风格建议都是小写字母,并且要尽量简短。

可见性

前面提到过包内共享所有变量,常量,以及所有定义的类型,但对于包外而言并不是这样,有时候你并不想让别人访问某一个类型,所以就需要控制可见性。你可能在其它 OOP 语言中见过PublicPravite等关键字,不过在 Go 语言中没有这些,它控制可见性的方式非常简单,规则如下

  • 名称大写字母开头,即为公有类型/变量/常量
  • 名字小写或下划线开头,即为私有类型/变量/常量

比如下面的一个例子,常量MyName就是公开的,而常量mySalary就是私有的。

go
package example

// 公有
const MyName = "jack"

// 私有
const mySalary = 20_000

这个可见性的规则适用于整个 Go 语言的任何地方。

导入

导入一个包来使用这个包中的类型/方法/函数/变量,导入的语法就是import加上包名

go
package main

import "example"

当导入多个包时,你可以这么写

go
package main

import "example"
import "example1"

也可以用括号括起来,下面这种方法在实践中更加常用。

go
package main

import (
  "example"
  "example1"
)

如果有包名重复了,或者包名比较复杂,你也可以给它们起别名

go
package main

import (
  e "example"
  e1 "example1"
)

别名为下划线_时就是匿名导入,匿名导入的包无法被使用,这么做通常是为了加载包下的init 函数,但又不需要用到包中的类型,一个常见的例子就是注册数据库驱动,但是你并不需要去手动使用驱动。

go
package main

import (
  e "example"
  _ "mysql-driver-go"
)

当你导入后,想要访问包中的类型时,通过包名.标识符去访问即可,比如下面这个例子,若你尝试去访问一个私有的类型,编译器就会告诉你无法访问。

go
package main

import (
  "example"
   "fmt"
)

func main() {
    fmt.Println(example.MyName)
}

有一种特殊的导入方式就是将该包中的所有类型都导入到当前包作用域,以这种方法导入的类型不再需要.运算符去访问,但是如果有重名的类型将会无法通过编译。

go
package main

import (
  . "example"
)

WARNING

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

内部包

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 支持单行注释和多行注释,注释与内容之间建议隔一个空格,例如

go
// 这是main包
package main

// 导入了fmt包
import "fmt"

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

标识符

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

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

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

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

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

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

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

TIP

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

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

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

字面量

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

整型字面量

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

go
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

浮点数字面量

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

go
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)

复数字面量

go
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

go
'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 双引号转义 (该转义仅在字符串内有效)

字符串字面量

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

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

函数

Go 中的函数声明方式通过func关键字来进行,跟大多数语言类似

go
func main() {
  println(1)
}

不过 Go 中的函数有两个不同的点,第一个是参数类型后置,像下面这样

func Hello(name string) {
  fmt.Println(name)
}

第二个不同的点就是多返回值,而且可以带名字

go
func Pos() () (x, y float64) {
    ...
}

风格

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

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

函数花括号换行

关于函数后的花括号到底该不该换行,几乎每个程序员都能说出属于自己的理由,在 Go 中所有的花括号都不应该换行

go
// 正确示例
func main() {
  fmt.Println("Hello 世界!")
}

如果你真的这么做了,像下面这样

go
// 错误示例
func main()
{
  fmt.Println("Hello 世界!")
}

这样的代码连编译都过不了,所以 Go 强制所有程序员花函数后的括号不换行。

代码缩进

Go 默认使用Tab也就是制表符进行缩进,仅在一些特殊情况会使用空格。

代码间隔

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

2*9 + 1/3*2

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

花括号省略

在其它语言中的 if 和 for 语句通常可以简写,像下面这样

c
for (int i=0; i < 10; i++) printf("%d", i)

但在 Go 中不行,你可以只写一行,但必须加上花括号

go
for i := 0; i < 10; i++ {fmt.Println(i)}

三元表达式

Go 中没有三元表达式,所以像下面的代码是无法通过编译的

go
var c = a > b ? a : b

通过这篇文章你可以对 Go 的语法有一个初步的认知,后续的内容中会进行更细致的展开。

Golang学习网由www.golangdev.cn整理维护