1、注释

1、单行注释

//

块注释,编辑器忽略中间xxxx的内容,Go虽然支持,但用的不多

/*xxxx*/

示例:

// 这是包注释
package main

import "fmt"

/*
x int
y int
returns: int
函数说明
*/
func add(x, y int) int {
    // 这里写下一句或下几句的注释
    return x + y // 本行注释,如果短,可以写这里;如果长,写上面
}

// 函数注释也可以这样多行
// 写在上面
func main() {
    fmt.Println(add(4, 5)) // 打印
    // TODO: 之后完成某某功能
}

// TODO: 将来完成;比如有一些功能没有完善今天时间不够明天来做,以防明天忘记可以使用此方法标记,推荐使用
// NOTE: 请注意;特别重要的信息或提醒
// Deprecated: 告知后人此函数或者方法已经过期,建议不要使用。未来某个版本可能移除

  • 函数、结构体等习惯把注释写在上面
  • 包注释会写在package之上

2、行

Go语言把行分隔符作为一条语句的结尾。也就是说,一般情况下,一行结束,敲回车即可。

3、命名规范

  • 标识符采用CamelCase驼峰命名法

    • 如果仅在包内可用,就采用小驼峰命名
    • 如果要在包内、包外可见,就采用大驼峰命名
  • 简单循环变量可以使用i、j、k、v等
  • 条件变量、循环变量可以是单个字母或单个单词,Go倾向于使用单个字母。Go建议使用更短小
  • 常量命名规范

    • 在其他语言中,常量多使用全大写加下划线的命名方式,Go语言没有这个要求,直接用驼峰命名法即可
    • 也可以按照约定俗成的全大写方式书写,例如PI
  • 函数/方法的参数、返回值应是单个单词或单个字母
  • 函数可以是多个单词命名
  • 类型可以是多个单词命名
  • 方法由于调用时会绑定类型,所以可以考虑使用单个单词
  • 包以小写单个单词命名,包名应该和导入路径的最后一段路径保持一致
  • 接口优先采用单个单词命名,一般加er后缀。Go语言推荐尽量定义小接口,接口也可以组合

4、关键字

官方地址:https://golang.google.cn/ref/spec#Keywordshttps://go.dev/ref/spec#Keywords

breakdefaultfuncinterfaceselect
chanelsegotopackageswitch
casedefergomapstruct
constfallthroughifrangetype
continueforimportreturnvar

5、预定义标识符

类型
boolbytecomplex64complex128error
float32float64intint8int16
int32int64runestringuint
uint8uint16uint32uint64uintptr
anycomparable

常量
truefalseiotacomplex128

零值
nil

函数
appendcapclosecomplexcopy
deleteimaglenmakenew
panicprintprintlnrealrecover
maxminclear

6、标识符

  • 一个名字,本质上是个字符串,用来指代一个值
  • 只能是大小写字母、数字、下划线,也可以是Unicode字符
  • 不能以数字开头
  • 不能是Go语言的关键字
  • 尽量不要使用“预定义标识符”,否则后果难料
  • 大小写敏感

标识符建议:

  • 不要使用中文
  • 非必要不要使用拼音
  • 尽量遵守上面的命名规范,或形成一套行之有效的命名规则

字面常量

字面常量是值,不是标识符,但本身就是常量,不能被修改。
Go语言中,boolean、rune、integer、float、complex、string类型的值都是字面常量。其中,rune、integer、float、complex常量被称为数值常量。

--------------------+
100                 |
1_0000 // 1万       |
0x6162 0X61_62_63    => int(数值)常量
0b0101 0o101        |
3.14                |
3.14e2              |
3.14E-2             |
--------------------+

--------------------+
'测'                |
'\u6d4b'            |
'\x31'               => rune类型字面常量,只允许有一个字符,转义字符除外
'1'                 |
'\n'                |
--------------------+

--------------------+
"abc" "\x61b\x63"   |
"测试" "\u6d4b试"    =>string(字符串)类型字面常量 
"\n"                |
--------------------+

-----+
true |
false =>bool类型字面常量,iota是一个整数
iota |
-----+

以上字面常量在Go中也被称为无类型常量untyped constant。无类型常量的缺省类型为bool、rune、int、float64、complex128或字符串

常量

常量:使用const定义一个标识符,它所对应的值,不允许被修改。
常量并不要求全大写加下划线的命名规则。
示例:

// 赋值等式右边用“无类型常量untyped constant”来赋值
const a int = 100 // 指定类型定义常量并赋值
const (           // 定义常量,等式左边未给出类型,将进行类型推导
    b = "abc"
    c = 12.3
    d = 'T'
)

错误示例:

const a // 错误,const定义常量,必须在定义时赋值,并且之后不能改变。第一次赋值称为初始化
// 数组虽然是固定大小的容器,但其内容会变化,不能在编译期间明确地确定下来。这一点和其它语言不一样
const c = [2]int{1, 2} // 错误

注意:Go语言的常量定义,必须是能在编译期就要完全确定其值,所以,值只能使用字面常量。这和其他语言不同!例如,在其他语言中,可以用常量标识符定义一个数组,因为常量标识符保证数组地址不变,而其内元素可以变化。但是Go根本不允许这样做。

iota

Go语言提供了一个预定义标识符iota[aɪˈoʊ.t̬ə],非常有趣。

// 单独写iota从0开始,iota不用在这里
const a = iota // 0
const b = iota // 0

iota常用在常量批量定义中,是写在批量定义的括号里的,以定义星期常量为例

// 批量写iota从0开始
const (
SUN = iota // 0
MON = iota // 1
TUE = iota // 2
)
// 简化
// 批量写iota从0开始,智能重复上一行公式
const (
SUN = iota // 0
MON
TUE
)

// 比较繁琐的写法,仅作测试
const (
    a = iota // 0
    b // 1
    c // 2
    _ // 按道理是3,但是丢弃了
    d // 4
    e = 10 // 10
    f // 10
    g = iota // 7
    h // 8
)
// 可以认为Go的const批量定义实现了一种重复上一行机制的能力

// 批量写iota从0开始,智能重复上一行公式
const (
    a = 2 * iota // 0
    b            // 2
    c            // 4
    d            // 6
)

// 批量写iota从0开始,智能重复上一行公式
const (
    m = 0
    n
    a= 2 * iota
    b
    c
        d
)
fmt.Println(a, b, c, d, m, n) // 这些常量的值分别是a == 2、b == 6、c == 8、d == 10、m == 0、n == 0

iota用在常量成批定义的时候。一旦在成批定义时用到了iota,它就相当于行索引。

变量

变量:赋值后,可以改变值的标识符。
建议采用驼峰命名法。

var a // 错误,无法推测类型
var b int // 正确,只声明,会自动赋为该类型的零值
var c, d int // 正确,连续的同类型变量可以一并声明,会自动赋为该类型的零值
var b = 200 // 错误,b多次声明,第二行已经声明过了
// 初始化:声明时一并赋初值
var a int = 100 // 正确,标准的声明,并初始化
var b = 200 // 正确,编译根据等式右值推导左边变量的类型
var c = nil // 错误,非法,nil不允许这样用
var d, e int = 11, 22 // 正确,var定义多个变量只能是同类型且类型只能写在最后。详见下面批量赋值
// 用var声明,立即赋值,或之后赋值
var b int // 正确,只声明,会自动赋为该类型的零值
b = 200 // 变量可以被重新赋值,要求类型一致
b = 300
b = "4" // 错误,类型不一致"4"是字符串类似,b是数值类型
// 变量没有定义类型,将使用等号右侧的无类型常量来推断类型
var a = 20 // int
var b = 3.14 // float64

// 指定变量的类型
var a int32 = 20
var b float32 = 3.14
// 批量赋值
var a int, b string // 错误,批量不能这么写
var ( 
    a int
    b string
)// 正确,a、b类型知道了,可以用零值
var ( 
    a b
)// 错误,变量必须有类型,但没有给类型,也不能使用值来推导类型
var a int, b string = 111, "abc" // 错误,多种类型不能这么写,语法不对
var (
    a int = 111
    b string = "abc"
) // 正确,建议批量常量、变量都这么写
var (
    a = 111
    b = "abc"
) // 正确,类型推导
// 短格式 Short variable declarations
// _ 空白标识符,或称为匿名变量
a := 100
b, c := 200, 300
// 交换
b, c = c, b
d, _, f := func() (int, string, bool) { return 300, "ok", true }()

_下划线 是空白标识符(Blank identifier)

短格式

  • 使用 := 定义变量并立即初始化
  • 只能用在函数中,不能用来定义全局变量
  • 不能提供数据类型,由编译器来推断

零值

变量已经被声明,但是未被显式初始化,这是变量将会被设置为零值。其它语言中,只声明未初始化的变量误用是非常危险的,但是,Go语言为了方便使用,尽量做到了“零值可用”。在Go语言中合理利用零值确实带来不小的便利。

  • int为0
  • float为0.0
  • bool为false
  • string为空串""(注意是双引号)
  • 指针类型为nil
// 延迟初始化需要指定类型,用零值先初始化。因为若不给出类型,不知道用什么类型的零值
// 有相同关系的声明可以使用同一批定义
var (
    name string
    age int
)

标识符本质

每一个标识符对应一个具有数据结构的值,但是这个值不方便直接访问,程序员就可以通过其对应的标识符来访问数据,标识符就是一个指代类似于键值对。一句话,标识符是给程序员编程使用的。

变量可见性

1、包级标识符

在Go语言中,在.go文件中的顶层代码中,定义的标识符称为包级标识符。如果首字母大写,包内可见,包外也可见。如果首字母小写,则包内可见。
使用建议:

  • 顶层代码中定义包级标识符

    • 首字母大写作为包导出标识符,首字母小写作为包内可见标识符
    • const定义包级常量,必须在声明时初始化
    • var定义包级变量

      • 可以指定类型,也可以使用无类型常量定义
      • 延迟赋值必须指定类型,不然没法确定零值
  • 有相关关系的,可以批量定义在一起
  • 一般声明时,还是考虑“就近原则”,尽量靠近第一次使用的地方声明
  • 不能使用短格式定义

2、局部标识符

定义在函数中,包括main函数,这些标识符就是局部标识符。
使用建议:

  • 在函数中定义标识符
  • const定义局部常量
  • var定义局部变量

    • 可以指定类型,也可以使用无类型常量定义
    • 延迟赋值必须指定类型,不然没法确定零值
  • 有相关关系的,可以批量定义在一起
  • 在函数内,直接赋值的变量多采用短格式定义

7、布尔型

类型bool,定义了2个预定义常量,分别是true、false。

8、数值型

官方地址:https://golang.google.cn/ref/spec#Numeric_types
复数:complex64、complex128

整型

  • 长度不同有符号:int8、int16(C语言short)、int32、int64(C语言long)

    • 最高位是符号位
    • rune类型,是int32的别名,是4字节有符号整数类型,不是字符串,注意不要和字符串混淆
  • 长度不同无符号:uint8、unit16、uint32、uint64

    • u即unsigned,最高位不是符号位
    • byte类型,它是uint8的别名,是单字节无符号整数类型
  • 自动匹配平台:int、uint

    • int类型它至少占用32位,但一定注意它不等同于int32,不是int32的别名。要看CPU,32位就是4字节,64位就是8字节。但是,也不是说int是8字节64位,就等同于int64,它们依然是不同类型!

整数的进制表示:

  • 十六进制:0x10、0X10
  • 八进制:0o10、0O10。010也行,但不推荐
  • 二进制:0b10、0B10

扩展知识

计算机内部是使用二进制的,也就是只有0和1,那如何表达出正负数的区别呢?
为了方便理解,以单字节为例,1个字节是8位,那么可以表示256种状态。二进制如何表示十进制2呢?它是00000010。那十进制-2,如何表示?把最高位单独解释,最高位定为符号位,0表示正号,1表示负号。-2用二进制表示为10000010
2个字节能表示多少种状态?

1个字节(8位)可以表示256种状态。假设我们使用符号位的表示法:

正数的符号位为0
负数的符号位为1

例如:
正的十进制2表示为:00000010(二进制)
负的十进制-2表示为:10000010(二进制)
现在,我们来考虑2个字节(16位)能表示多少种状态:

每个字节有8位,两个字节共有16位。每个位可以有2种状态(0或1),因此:

2的16次幂=65536
因此,2个字节能表示65536种状态。

使用符号位的表示法:

正数范围:从 00000000 00000000 到 01111111 11111111
负数范围:从 10000000 00000000 到 11111111 11111111
这样表示范围内,我们可以表示的范围是:

正数:0 到 32767 (0 到 01111111 11111111,十进制)
负数:-1 到 -32768 (11111111 11111111 到 10000000 00000000,十进制)
因此,在使用符号位来表示正负数的情况下,2个字节(16位)可以表示65536种状态,包括正数、负数和0。

类型转换

常量分为typed类型常量和untyped常量。
注意下面的常见错误:

var a int = 1
var b float32 = 2.3
fmt.Println(a * b) // 错误,int和float32类型不同,无法计算,除非强制类型转,Go语言中这种情况不会进行隐式类型转换
var a = 1 // int
var b = 2.3 // float64
fmt.Println(a * b) // 错误,int和float61类型不同,无法计算,除非强制类型转换
fmt.Println(1 * 2.3) // 报错吗?

上面的常量被赋给了变量,这些变量就确定了类型,虽然他们指向的值是字面常量,但是计算使用变量,但变量的类型不一致,报错。
再看下面的例子:

var a = 1 * 2.3 // 不报错
fmt.Printf("%T %v\n", s, s) // float64 2.3

等号右边使用的就是不同类型的值计算为什么可以?
因为右边使用的都是无类型常量untyped constant,它会在上下文中隐式转换。Go为了方便,不能过于死板,要减少程序员转换类型的负担,在无类型常量上做了一些贴心操作。

An untyped constant has a default type which is the type to which the
constant is implicitly converted in contexts where a typed value is required,
for instance, in a short variable declaration such as i := 0 where there is
no explicit type.

摘自 https://golang.google.cn/ref/spec#Constants

再看一个变量计算的例子:

package main

import "fmt"

func main() {
    var a = 20
    b := 30
    var c int = 40
    fmt.Printf("%T, %T, %T, %d\n", a, b, c, a+b+c)
    var d int64 = 0x32 // 50
    fmt.Printf("%T, %d\n", d, d)
    fmt.Println(a + d) // 错误,int和int64类型不同不能操作
    fmt.Println(a + int(d)) // 显示强制类型转换才行
}

与其他语言不同,即使同为整型这个大类中,在Go语言中,也不能跨类型计算,不会进行隐式类型转换。如有必要,请强制类型转换。
强制类型转换:把一个值从一个类型强制显式转换到另一种类型,有可能转换失败。

浮点数

  • float32:最大范围约为3.4e38,通过math.MaxFloat32查看
  • float64:最大范围约为1.8e308,通过math.MaxFloat64查看
  • 打印格式化符常用%f
package main

import "fmt"

func main() {
    f := 9999.33333333333333
    fmt.Printf("%T, %f\n", f, f) // 默认精度6
    fmt.Printf("%.3f\n", f)      // 小数点后3位
    fmt.Printf("[%3.2f]\n", f)   // 宽度撑爆了,中括号加上没有特殊含义,只是为了看清楚占位
    //打印宽度
    fmt.Printf("[%20.2f]\n", f)  // 打印数值除[]外总宽度为20.小数点后保留两位,[]不在总宽度范围内,总宽度包括小数点前的整数和小数点后的小数位也包括小数点
    fmt.Printf("[%-20.2f]\n", f) // 左对齐
}

打印结果:

float64, 9999.333333
9999.333
[9999.33]
[             9999.33]
[9999.33             ]

进制及转换

常见进制有二进制、八进制、十进制、十六进制。应该重点掌握二进制、十六进制。
十进制逢十进一;十六进制逢十六进一;二进制逢二进一

每8位(bit)为1个字节(byte)。

一个字节能够表示的整数的范围:
无符号数0~0xFF,即0到255,256种状态
有符号数,依然是256种状态,去掉最高位还剩7位,能够描述的最大正整数为127,那么负数最大就
为-128。也就是说负数有128个,正整数有127个,加上0,共256种。
转为十进制——按位乘以权累加求和
^ 在这里表示求幂,2^3表示2的3次方

0b1110 计算为 1 * (2^3) + 1 * (2^2) + 1 * (2^1) + 0 * (2^0) = 14
0o664 计算为 6 * (8^2) + 6 * (8^1) + 4 * (8^0) = 436
0x41 计算为 4 * 16 + 1 * 1 = 65

十六进制中每4位断开转换

1000 0000 二进制 2 ^ 7 = 128
8 0 十六进制 8 * (16^1) = 128
八进制每3位断开转换
10 000 000 二进制 2 ^ 7 = 128
2 0 0 八进制 2 *(8^2) + 0 + 0 = 128
二进制表示0b十进制数值十六进制表示0x
111
1133
11177
111115F
11111311F
111111633F
11111111277F
11111111255FF
100000000256100

二进制中最低位为1,一定是奇数;最低位为0,一定是偶数

十六进制表示0x十进制数值
99
A10
D13
2032
3048
3149
4165
6197
7F127
FF255
FE254

十六进制转为二进制

0xF8 按位展开即可,得到 0b1111 1000

八进制转为二进制

0o664 按位展开即可,得到0b 110 110 100

十进制转二进制

127 除以基数2,直到商为0为止,反向提取余数
尝试将十进制5、12转换为二进制

十进制转十六进制

127 除以基数16,直到商为0为止,反向提取余数

9、转义字符

每一个转义字符都是一个字符,rune类型(int32)。可以作为单独字符使用,也可以作为字符串中的一个字符。

\a U+0007 alert or bell
\b U+0008 backspace
\f U+000C form feed
\n U+000A line feed or newline
\r U+000D carriage return
\t U+0009 horizontal tab
\v U+000B vertical tab
\\ U+005C backslash
\' U+0027 single quote (valid escape only within rune literals)
\" U+0022 double quote (valid escape only within string literals)

问题:在Go中 'n' 和 "n" 的区别是什么?
'n'为一个字符,在Go语言中它是无类型字面常量,它的缺省类型就是rune类型,而rune就是int32,也就是说它是4字节的整数,里面就只用掉了1个字节来放该字符对应的ASCII,但是占用掉了4个字节的内存空间。
"n"这是字符串类型,它不是整数类型,它底层是一个字节序列。

10、字符串

使用双引号或反引号引起来的任意个字符。它是字面常量。

"abc测试"     // 不能换行,换行需要借助\n
"abc\n测试"   // 换行
`abc
测试`         // 等价下面的字符串
"abc\n\t测试"

`json:"name"` // 字符串里面如果有双引号,使用反引号定义方便
"json:\"name\"" // 和上一行等价
"abc" + "xyz" + `jkmn` // 拼接

注意,反引号内不支持转义字符,那么在反引号定义的字面量字符串内写反引号不可以。

字符串格式化

格式符参考fmt包帮助 https://pkg.go.dev/fmt

  • %v 适合所有类型数据,调用数据的缺省打印格式

    • %+v对于结构体,会多打印出字段名
  • %#v 对于结构体,有更加详细的输出
  • %T 打印值的类型
  • %% 打印百分号本身

整数

  • %b 二进制;%o 八进制;%O 八进制带0o前缀;%x 十六进制小写;%X16 进制大写
  • %U 把一个整数用Unicode格式打印。例如 fmt.Printf("%U, %x, %cn", 27979, 27979,27979) 输出结果: U+6D4B, 6d4b, 测
  • %c 把rune、byte的整型值用字符形式打印
  • %q 把一个整型当做Unicode字符输出,类似%c,不过在字符外面多了单引号。q的意思就是quote

浮点数

  • %e、%E 科学计数法
  • %f、%F 小数表示法,最常用
  • %g 内部选择使用%e还是%f以简洁输出;%G 选择%E或%F

字符串或字节切片

  • %s 字符串输出。如果是rune切片,需要string强制类型转换
  • %q 类似%s,外部加上双引号。q的意思就是quote

指针

  • %p 十六进制地址
    | 类型 | 说明 | 缺省格式符 | 常用格式符 |
bool布尔型%t%t
int/int8/int16/int32/int64整型%d%d、%b%x
uint/uint8/uint16/uint32/uint64无符号整型%d (如果使用 %#v 就等同 %#x)%d%b%x
float32/float64浮点型%g%f%e
complex64/complex128复数%g
byte字节型%c%c%d
rune字符型%d%c%d
string/[]byte字符串%s%s
uintptr指针%p%p
map slice channel error引用%v
slice索引0元素地址%p

特殊格式符写法

a, b, c, d := 100, 200, 300, 400
fmt.Printf("%d, %[2]v, %[1]d, %d", a, b, c, d)

可以认为中括号内写的是索引,是 Printf 的索引,索引0是格式字符串本身,1开始才是参数。如果写了[n],之后默认就是n+1。
执行结果:

100, 200, 100, 200

输出函数

输出到标准输出

  • Print:使用缺省格式输出,空格分割
  • Println:使用缺省格式输出,空格分割,最后追加换行
  • Printf:按照指定的格式符输出

输出到字符串,经常用来拼接字符串用

  • Sprint:相当于Print,不过输出为string
  • Sprintln:相当于Println,不过输出为string
  • Sprintf:相当于Printf,不过输出为string

11、操作符

https://golang.google.cn/ref/spec#Operators_and_punctuationhttps://go.dev/ref/spec#Operators_and_punctuation

逻辑运算真值表

与逻辑 或逻辑 非逻辑
ABFABFAF
00000001
01001110
100101
111111

算数运算符

+、-、*、/、%、++、--
  • 5 / 2、-5 / 2
  • +、-还可以当做正负用,就不是算数运算符了,例如 -s 。
  • 类C语言语法,没有Python // 的除法符号,因为它是注释

++、--只能是i++、i--,且是语句,不是表达式。也就是说,语句不能放到等式、函数参数等地方。例如, fmt.Println(a++) 是语法错误。
Go语言没有++i、--i。

位运算符

&位与、|位或、^异或、&^位清空、<<、>>

fmt.Println(2&1, 2&^1, 3&1, 3&^1) // 0 2 1 2
fmt.Println(2|1, 3^3, 1<<3, 16>>3, 2^1) // 3 0 8 2 3

x&y ,位与本质就是按照y有1的位把x对应位的值保留下来。
x&^y,位清空本质就是先把y按位取反后的值,再和x位与,也就是y有1的位的值不能保留,被清空,原来是0的位被保留。换句话说,就是按照y有1的位清空x对应位

比较运算符

==、!=、>、<、>=、<=

比较运算符组成的表达式,返回bool类型值。成立返回true,不成立返回false。

逻辑运算符

&&、||、!

由于Go语言对类型的要求,逻辑运算符操作的只能是bool类型数据,那么结果也只能是bool型。

// 短路
fmt.Println(false && true, true && true && false)
fmt.Println(false || true, true || false || true)

赋值运算符

=、+=、-=、*=、/=、%=、>>=、<<=、&=、&^=、^=、|=

:= 短格式赋值。

指针操作

数据是放在内存中,内存是线性编址的。任何数据在内存中都可以通过一个地址来找到它。

  • &变量 表示取变量地址
  • *指针变量 表示通过指针取值
a := 123
b := &a // &取地址
c := *b
fmt.Printf("%d, %p, %d\n", a, b, c)
fmt.Println(a == c, b == &c, &c) // 相等吗?
var d = a
fmt.Println(a == d, &a == &d, &a, &d) // 相等吗?
var p *int
fmt.Println(p) // p是nil,即空指针,要小心
p = &a
fmt.Printf("%p, %v %v\n", p, *p, a)
*p = 100 // 通过指针替换内容
fmt.Printf("%p, %v %v\n", p, *p, a)

输出结果:

123, 0xc00000a0a8, 123
true false 0xc00000a0c0
true false 0xc00000a0a8 0xc00000a0e0
<nil>
0xc00000a0a8, 123 123
0xc00000a0a8, 100 100

优先级

CategoryOperatorAssociativity
Postfix后缀() [] -> . ++ --Left to right
Unary单目+ - ! ~ ++ -- (type) * & sizeofRight to left
Multiplicative乘除* / %Left to right
Additive加减+ -Left to right
Shift移位<< >>Left to right
Relational关系< <= > >=Left to right
Equality相等== !=Left to right
Bitwise AND&Left to right
Bitwise XOR^Left to right
Bitwise OR|Left to right
Logical AND&&Left to right
Logical OR||Left to right
Assignment赋值= += -= *= /= %= >>= <<= &= ^= |=Right to left
Comma逗号运算符,Left to right

规则:

  • 表中优先级由高到低
  • 单目 > 双目
  • 算数 > 移位 > 比较 > 逻辑 > 赋值
  • 搞不清,用括号,避免产生歧义
最后修改:2024 年 07 月 30 日
如果觉得我的文章对你有用,请随意赞赏