1. 注释
1.1 多行注释
1.1.1 方式一(不推荐使用)
package main
/* 多行注释
test函数的作用
参数a类型和作用
参数b类型和作用
参数c类型和作用
*/
func test1(a int, b string, c bool){
}
1.1.2 方式二(推荐)
go的源码库中也是使用这种多行注释方式
package main
// test函数的作用,
// 参数a类型和作用,
// 参数b类型和作用,
// 参数c类型和作用.
func test2(a int, b string, c bool){
}
1.2 单行注释
package main
import "fmt"
func main() {
// 单行注释
fmt.Println("fmt") // 打印什么的
}
1.3 TODO 提示功能没有完成
1.3.1 安装插件 todo tree
1.3.2 最终效果
package main
import "fmt"
func main() {
// TODO: 还有一个功能待完成
fmt.Println("fmt")
}
类似的还有:
// NOTE: 请注意
// Deprecated: 告知已经过期,建议不要使用。未来某个版本可能移除
不过todo比较常用。
1.4 注释总结
(1)函数、结构体等习惯把注释写在函数或结构体上面
(2)包注释会写在package之上
2. 命名规范
(1)标识符采用CamelCase驼峰命名法
- 如果只在包内可用,就采用小驼峰命名(userName)
- 如果要在包外可见(另一个包中可见),就采用大驼峰命名(UserName),也被称为包级别的全局变量或者导出变量。
- 大小驼峰在包内外都能用,但是如果要在包外可见,就必须用大驼峰。
(2)简单循环变量可以使用i、j、k、v等,就是单独的字母也能使用。
(3)条件变量、循环变量可以是单个字母或单个单词,Go倾向于使用单个字母。
(4)常量驼峰命名即可
- 在其他语言中,常量多使用全大写加下划线的命名方式,Go语言没有这个要求。
- 对约定俗成的全大写,例如PI。
(5)函数/方法的参数、返回值应是单个单词或单个字母。
(6)函数可以是多个单词命名。
(7)类型可以是多个单词命名。
(8)方法由于调用时会绑定类型,所以可以考虑使用单个单词。
(9)包以小写单个单词命名,包名应该和导入路径的最后一段路径保持一致。
(10)接口优先采用单个单词命名,一般加er后缀。Go语言推荐尽量定义小接口(就是接口中的功能尽可能的少),最后用若干个小接口来组成一个大接口。
3. 关键字
官网:https://golang.google.cn/ref/spec
// 所谓的关键字就是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
4. 预定义标识符
官网:https://golang.google.cn/ref/spec#Predeclared_identifiers
// 预定义标识符可以用,但是不建议使用,会出问题。
Types:
any bool byte comparable
complex64 complex128 error float32 float64
int int8 int16 int32 int64 rune string
uint uint8 uint16 uint32 uint64 uintptr
Constants:
true false iota
Zero value:
nil
Functions:
append cap close complex copy delete imag len
make new panic print println real recover
5. 标识符
在编程中,标识符(Identifier)是用来表示变量、常量、函数、类型、接口或其他用户定义的实体的名称。标识符可以被用于命名各种程序元素,以便在代码中引用和识别它们。
在Go语言中,标识符的命名规则如下:
- 一个名字,本质上是个字符串,用来指代一个值。
- 只能是大小写字母、数字、下划线,也可以是Unicode字符
- 不能以数字开头
- 不能是Go语言的关键字
- 尽量不要使用“预定义标识符”,否则后果难料
- 大小写敏感
标识符建议:
- 不要使用中文
- 非必要不要使用拼音
- 尽量遵守上面的命名规范,或形成一套行之有效的命名规则
总结:
- 标识符是专门给开发人员用的,用来指代内存中的一个值,如a = 123。
- 标识符在编译过后,就看不到了,因为被替换成了内存地址,这个内存地址中,存放着对应的值,如123。
5.1 字面常量
字面常量是值。
5.1.1 字面常量含义
在Go语言中,字面常量(Literal Constants)是直接写在代码中的固定值,无需使用变量声明。对于整数、浮点数、布尔值、字符串、空值等,都可以作为字面常量。
所谓字面的意思,就是你一看就知道这是什么类型的数据,它是值,但不是标识符。
但你不可以去修改这个值,因为它是常量,只有变量才可以修改。
对于能不能修改,取决于你如何定义标识符,如下:
如a := 100,这个100就是一个数值类型的字面常量,而a只是一个标识符,同时a也是一个变量,那么此时,100在程序运行过程中是可变的。
又如:const b = 200,这里的200依然是一个数值类型的字面常量,b也依然是标识符,但同时,由于使用了const,所以b也是一个常量,所以这里的200,在程序运行过程中不可变。
5.1.2 字面常量示例
/* 数值常量
// 数值类的字面常量
100
0x6162 0x61_62_63
3.14
3.14e2
3.14E-2
// 字符(rune)类的字面常量
// 字符不管是多少个组成,如'xxx',都视为一个字符
'测'
'\u6d4b'
'\x31'
'1'
'\n'
*/数值常量
/* 字面常量
// 字符串类的字面常量
"abc" "\x61b\x63"
"测试" "\u6d4b试"
"\n"
*/字面常量
// 其他类型的字面常量
tue
false
iota
5.2 常量
5.2.1 什么是常量
什么是常量?
首先它是一个标识符,这个标识符有一个值,但这个值在程序运行过程中是不可改变的,如const a = 100,这个a就是常量,100为常量值。
是在其他语言中,指的是元素地址不可变,内容可变。
但在go中,要求更加严格,要求的是内容都不能变。
常量:使用const定义一个标识符,它所对应的值,不允许被修改。
对常量并不要求全大写加下划线的命名规则。
在定义常量时,它的值只能是字面常量,如果是其他值,就直接报错了
5.2.2 常量示例
5.2.2.1 单个常量定义
const a int = 100 // 指定类型定义并赋值
5.2.2.2 多个(批量)常量定义
const ( // “无类型常量untyped constant”定义,推荐
b = "abc" // 不写数据类型时,go会根据值自动判断常量是什么类型。
c = 12.3
d = 'T'
)
5.2.2.3 错误的定义方式
// 错误,const定义常量时,必须在定义时赋值,并且之后不能改变值,换一种说法就是常量在定义时,必须被初始化,=赋值就是初始化。
const a
// 错误,数组的容器内容{1, 2}会变化,凡是不能在编译期间明确地确定下来的,在go中是不被允许被定义为常量的。
const c = [2]int{1, 2}
5.3 iota
5.3.1 iota介绍
在Go语言中,iota 是一个被预定义的无类型整数常量,它用于在常量声明中生成一系列递增的值。
在每个const关键字出现时,iota会被重置为0(但它本身也是从0开始),然后在每个连续的常量声明中逐步递增。
5.3.2 单iota演示
package main
import "fmt"
func main() {
const a = iota // 0,因为有const
const b = iota // 0,因为有const
fmt.Println(a, b)
}
=================调试结果=================
0 0
5.3.3 多iota演示
批量iota操作的时候,才会出现递增效果,0、1、2……
func main() {
const ( // 多iota递增
c = iota // iota=0
d // iota=1
e // iota=2
_ // 下划线为特殊标识符,在go中可以用来做标识符,但是不能使用它,如print _,会报错。iota=3
_ // 也可以叫空白标识符或匿名变量。iota=4
f // iota=5
g = iota // iota=6
h // iota=7
)
fmt.Println(c, d, e, f, g, h)
}
=================调试结果=================
0 1 2 5 6 7
代码变化一
package main
import "fmt"
func main() {
const ( // 多iota递增
c = iota
d
e
_
_
f
// 下面开始变化
g = iota + 10 // iota=6+10=16
h // iota=7,但是会复用上面iota+10的公式,也就是iota=7+10=17
)
fmt.Println(c, d, e, f, g, h)
}
=================调试结果=================
0 1 2 5 16 17
代码变化二
package main
import "fmt"
func main() {
const ( // 多iota递增
c = 5 // 改变这里,会发现直到g,都是5,因为一个多iota一旦被定义,不管位置在哪儿,都会从第一行开始
d
e
_
_
f
g = iota + 10
h
i = 20
j
k = iota
l
)
fmt.Println(c, d, e, f, g, h, i, j, k, l)
}
=================调试结果=================
5 5 5 5 16 17 20 20 10 11
_下划线 是空白标识符
下划线和其他标识符使用方式一样,但它不会分配内存,不占名词空间
为匿名变量赋值,其值会被抛弃,因此,后续代码中不能使用匿名变量的值,也不能使用匿名变量为其他变量赋值。
说白了就是只能用_占位,不能调用它。
5.3.4 iota应用场景
枚举类型:iota可以用于创建枚举类型的常量集。例如,可以使用iota来定义一周中每一天的常量。
const (
Sunday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
5.4 变量
5.4.1 变量介绍
变量:赋值后,可以改变值的标识符,但是对于go这种强类型语言来说,不能换数据类型,比如a = 1,然后下面又a = “a”。
建议采用驼峰命名法。
定义方式:
(1)长格式定义
var a = 100
(2)短格式定义
a := 100
5.4.2 变量定义示例
5.4.2.1 单个变量定义
package main
import (
"fmt"
)
func main() {
//var a // 错误的声明方式。声明变量时,必须要指定数据类型
var a int // 正确的声明方式。没有指定具体的变量值时,会自动初始化一个零值(整数0),因为go中不允许有空值的变量。
fmt.Println(a)
var b = 200 // 正确的声明方式。没有指定具体的数据类型时,声明(var b)+初始化(b = 200)后,go会自动推导出值的数据类型
fmt.Println(b)
}
=================调试结果=================
0
200
5.4.2.2 多个变量定义
func main() {
//var a int, b int // 错误的声明方式
var a, b int // 正确的声明方式。批量同类型合并
fmt.Println(a, b)
var ( // 正确的声明方式
c int = 300
d int
)
fmt.Println(c, d)
var g, h int = 100, 200 // 正确的声明方式
fmt.Println(g, h)
var ( // 正确的声明方式
m int
n int
t string = "abc"
)
m, n = 300, 400
fmt.Println(m, n, t)
m, n = n, m // 交换写法。但是要注意Go语言的多重赋值是同时执行的
fmt.Println(m, n)
//var a = 100 // 错误的声明方式,因为上面a已经被var声明过了
a = 100 // 正确的声明方式
}
=================调试结果=================
0 0
300 0
100 200
300 400 abc
400 300
5.4.2.3 多变量定义的注意事项
Go语言的多重赋值是同时执行的
5.4.2.4 短格式变量定义
推荐使用这种方式来声明变量,但是它只能在函数内部使用,不能用来全局变量。
使用 := 定义变量并立即初始化
只能用在函数中,不能用来定义全局变量、不能提供数据类型,具体的数据类型由编译器来推断
func main() {
a := 100 // 正确的声明方式。:=,表示声明变量+定义变量
//a := 200 // 不能这样!因为上面已经声明和定义过了,不能重复声明定义。
a = 300 // 可以这样,单纯的赋值。
fmt.Println(a)
}
=============
300
5.5 零值
变量已经被声明,但是未被显式初始化(var a int),这个变量将会被设置为零值。
var a int = 100
声明:var a int
初始化:= 100
其它语言中,只声明未初始化的变量误用非常危险,但是,Go语言却坚持“零值可用”理念。
- int为0
- float为0.0
- bool为false
- string为空串""(注意是双引号)
- 指针类型为nil
- 其它类型数据零值,学到再说
5.6 变量作用域
5.6.1 包级标识符(全局)
在Go语言中,在.go文件中的顶层代码中(函数体外部),定义的标识符称为包级标识符。
全局变量可以在整个包或者包外被使用,如果首字母大写,可在包外可见。如果首字母小写,则包内可见。
// 无类型常量定义(包外可见)
var A = 20 // int
var B = 3.14 // float64
// 无类型常量定义(包内可见)
var a = 20 // int
var b = 3.14 // float64
// 指定类型
var a int32 = 20
var b float32 = 3.14
// 延迟初始化需要指定类型,用零值先初始化,因为不给类型,不知道用什么类型的零值
// 有相同关系的声明可以使用同一批定义
var (
name string
age int
)
使用建议:
(1)顶层代码中定义包级标识符
- 首字母大写作为包导出标识符,首字母小写作为包内可见标识符。
- const定义包级常量,必须在声明时初始化
- var定义包级变量,可以指定类型,也可以使用无类型常量定义,延迟赋值必须指定类型,不然没法确定零值。
(2)有相关关系的,可以批量定义在一起
(3)一般声明时,还是考虑“就近原则”,尽量靠近第一次使用的地方声明
(4)不能使用短格式定义
5.6.2 局部标识符
定义在函数体内部,包括main函数,这些标识符就是局部标识符。
使用建议:
- 在函数中定义的标识符
- const定义局部常量
- var定义局部变量,可以指定类型,也可以使用无类型常量定义,延迟赋值必须指定类型,不然没法确定零值。
- 有相关关系的,可以批量定义在一起。
- 在函数内,直接赋值的变量多采用短格式定义。
5.6.3 形式参数
函数定义中的变量称为形式参数。
func sum(a, b int) int { // 这里的ab就是形式参数
xxx
}
6. 布尔型
类型bool,定义了2个预定义常量,分别是true、false。
在其他语言中,布尔型可以和其他类型的数据进行运算,但是在go中,不可以的,bool就是bool。
6.1 布尔表达式符号
逻辑与:&&
逻辑或:||
逻辑非:!
运算符:==、!=、>、<、>=、<=
7. 数值型
官网文档:https://golang.google.cn/ref/spec#Numeric_types
7.1 整型
(1)长度不同有符号
int8(1个字节)、int16(C语言short,2个字节)、int32(4个字节)、int64(C语言long,8个字节)
(2)长度不同无符号
uint8(1个字节)、uint16(2个字节)、uint32(4个字节)、uint64 (个字节)。byte类型,它是uint8的别名
(3)自动匹配平台:int、uint
int类型它至少占用32位,但一定注意它不等同于int32,不是int32的别名。要看CPU,32位就是4字节,64位就是8字节。但是也不是说int是8字节64位,就等同于int64,它们依然是不同类型!
7.1.1 什么是有符号和无符号
这里用int8(1字节)举例
首先这里介绍下进制,如在10进制中,是看不到10的,因为计数是从0开始,到9,当计数超过9时,需要使用一个1和一个0来表示10。同理,其他进制也相同。
那么如何理解有无符号呢?
首先说下有符号,这里先以二进制为例:
如00000001,首先最左边属于最高位,最右边属于最低位,1byte=8bits,1个字节,就是8个位,每个位置上只有2种可能,也就是0或1,这就是二进制。
二进制中,用最高位的0或1来表示正负符号,其中0为正,1为负。那么如下:
10000001,这个二进制的最高位就是-1(负符号)。
那么无符号呢?
和有符号相反,最高位不表示符号。
如00000001,就是1,因为它没有符号位,所有位都用于表示非负整数。
为啥是1?
其实这里是涉及到了进制转换计算,首先在进制中,是由权重一说的。
如二进制,它的权重底数是2,权重从右到左依次是:1、2、4、8、16、32等等以此类推。
那么二进制00000001转换成十进制方式就是如下图:
7.1.2 有无符号整型范围
7.1.2.1 有符号
计算方式:
取值范围 = -(2^(n-1)) 到 (2^(n-1))-1,其中n为类型的位数:如int8,n就是8。
-(2^(8-1)) = -128
2^(8-1)-1 = 2^7 = 128 -1 = 127
数据类型 | 位数 | 占用内存空间 | 取值范围 |
---|---|---|---|
int8 | 8 | 1字节 | -128 到 127 |
int16 | 16 | 2字节 | -32,768 到 32,767 |
int32(别名rune) | 32 | 4字节 | -2,147,483,648 到 2,147,483,647 |
int64 | 64 | 8字节 | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 |
7.1.2.2 无符号
计算方式:
取值范围=0~2^n-1,n为位数。
数据类型 | 位数 | 占用内存空间 | 取值范围 |
---|---|---|---|
uint8(别名byte) | 8 | 1字节 | 0 到 255 |
uint16 | 16 | 2字节 | 0 到 65,535 |
uint32 | 32 | 4字节 | 到 4,294,967,295 |
uint64 | 64 | 8字节 | 0 到 18,446,744,073,709,551,615 |
7.1.3 Go中的进制前缀表示法
前缀对于编译器解析数字文字非常重要。
如果没有前缀,编译器会将数字解释为十进制数。
进制类型 | 前缀 |
---|---|
二进制 | 0b(b可以大写可小写) |
八进制 | 0或0o(可以省略,但是不要,避免歧义,o可以大写也可以小写) |
十进制 | 默认无前缀 |
十六进制 | 0x(X可以大写也可以小写) |
十六进制字符 | ‘\x’ |
例如:
二进制数:0b1010
八进制数:010、0o10
十进制数:10
十六进制数:0x10
7.1.4 实际代码中的运用
package main
import "fmt"
func main() {
a := 0x20 // 0x为16进制,表示20是个16进制类型
fmt.Println(a) // 这里Println会把16进制自动转换为10进制并输出结果
}
=================调试结果=================
32
7.1.5 查看对应值的类型(格式化输出)
package main
import "fmt"
func main() {
a := 0x20
fmt.Println(a)
b := 0b100000
fmt.Println(b) // Println= print line,打印完后换行
var c = 40
d := a + b + c
// Printf=print format,格式化输出,不换行。
// %T对应第一个d,%d对应第二个d。
// %T表示type类型。%d表示digital数字。
// 也就是把d的数据类型和对应的值打印出来。
fmt.Printf("%T %d", d, d)
fmt.Printf("%T %v", d, d) // 当不能确定值的类型时,可以使用%v,v表示value,就是一个占位符,适用于任何场景。
}
=================调试结果=================
32
32
int 104
int 104
7.1.6 Go不支持跨类型计算说明
package main
import "fmt"
func main() {
a := 100 // 这种不指定数据类型的,自动识别为int,int会根据当前的系统类型,来使用32位还是64位
var b int64 = 100 // 这里就指明了int64,但是注意,假设上面的int自动识别为int64也和这里的int64不同。
// a + b // 这里可以使用a+b,然后鼠标移到上面,会看到一个报错:invalid operation: a + b (mismatched types int and int64)compilerMismatchedTypes,
// 意思是a是int,b是int64,不同数据类型不能相加。
// 但是可以通过强制类型转换,来实现上面的需求。
}
7.1.7 强制类型转换
7.1.7.1 int和长度不同有符号互转
package main
import "fmt"
func main() {
a := 100 // int
var b int64 = 100 // int64
fmt.Println(a + int(b)) // 直接把b的int64转换成了int
fmt.Println(int64(a) + b) // 这样也可以,只要数据类型相同就行
}
=================调试结果=================
200
200
7.1.7.2 int转string
int转string会有一个黄色下划线警告提示:大概意思是说把一个int转成了字符串。不影响代码运行。
当把int类型强制转换为string时,Go 语言会将该整数解释为一个 ASCII 码值,并返回相应的字符。
package main
import "fmt"
func main() {
var a = 50
fmt.Printf("%T %[1]v\n", string(a)) // %v是比较万能的数据类型显示,字符串的类型为%S。
// %[1]v,表示的是索引:"%T索引默认为1,对应string(a),%v默认对应第二个值,但是这里并没有,所以修正索引为1
}
=================调试结果=================
string 2 // 当把int类型强制转换为string时,Go 语言会将该整数解释为一个 ASCII 码值,并返回相应的字符。
7.1.7.3 int转其他类型
package main
import "fmt"
func main() {
var a = 50
fmt.Printf("%T %[1]v\n%T %[2]v\n%T %[3]f\n", string(a), rune(a), float32(a))
}
=================调试结果=================
package main
import "fmt"
func main() {
var a = 50
fmt.Printf("%T %[1]v\n%T %[2]v\n%T %[3]f\n", string(a), rune(a), float32(a))
}
=================调试结果=================
string 2 // 字符串,实际应该是"2",但是从int转到str显示的是ascll码值
int32 50 // 字符,实际应该是'50'
float32 50.000000 // 浮点数
7.1.8 为什么要分有符号和无符号及自适应
首先说说自适应,自适应int和uint,纯粹是为了方便,当内存充足时,可以使用该方式,也是工作中大多数时使用的方式。
然后是有无符号,需要精细使用内存时,就要根据实际情况来选择不同的有无符号整型。
实际工作中,优先考虑的是程序运行速度快、无BUG,其次才是内存占用优化。
7.2 字符和整数
7.2.1 什么是字符
字符是在计算机系统中表示单个书写符号(例如字母、数字或符号)的基本单位。它是一个抽象概念,可以由不同的字符编码(例如 ASCII、Unicode)表示。
ASCII范围:0-127
Unicode范围:100万字符以上
字符’'使用Unicode编码,并且Unicode是包含ASCII码的。
在Go中,字符表达,必须使用单引号引住一个字符,如:‘a’、‘1’、‘@‘等。
但是在计算机中,字符也可以用数字来表示,如ASCII码表。
ASCII码表中的内容,是某个数字映射后的某个字符,那到底是数字还是字符呢?取决于我们编程时如何使用。
比如97,我们可以单纯的把它看成是一个整型数字,但是如果提前限定它的类型为string字符类型,那么此时对照ASCII码表,97对应的字符就是’a’,这个时候实际上就是操作的这个字符’a’,而不是97.
计算机种只有二进制的数据,到底要怎么展示出来,取决于我们定义为什么类型的数据。
type byte = uint8 // byte定义为uint8的别名,1个字节
type rune = int32 // rune定义为int32的别名,4个字节,可以是Unicode字符
type myint int // 这种是定义新类型,不是定义别名
package main
import "fmt"
func main() {
var a = '测' // 由于是中文,所以会先去查询Unicode表,然后返回对应的值27979
fmt.Printf("%T %[1]v %[1]c\n", a)
b := '2' // 将字符 '2' 的 ASCII 码(即二进制00110010=十进制50)赋值给变量 b。
fmt.Printf("%T %[1]v %[1]c\n", b) // %c表示打印一个字符。此时2在rune类型下就应该是2,虽然rune就是int32
// 换句话说,就是内存中'2'=50 int32,但只要我们打印的时候,把它当成字符,那输出出来就是字符'2'。
// 内存中的数据到底应该是什么类型,取决于我们赋予它什么样的类型。
// 总结:如果b := 'value',这个'value'字符字面量,那默认就是rune也就是int32,4字节.
// 但实际上,存储50使用无符号1字节就可以了,如下:
var c byte = '2' // 定义类型只能用var,不能用:=
fmt.Printf("%T %[1]d %[1]c\n", c)
}
=================调试结果=================
int32 27979 测
int32 50 2
uint8 50 2
特别注意:字符串在内存中使用utf-8,rune输出是unicode。
7.3 浮点数
float32:最大范围约为3.4e38,通过math.MaxFloat32查看
float64:最大范围约为1.8e308,通过math.MaxFloat64查看
打印格式化符常用%f
package main
import "fmt"
func main() {
f := 3.1415 // 默认类型:float64
fmt.Printf("%T %[1]v %[1]f", f) // v:默认格式。f:浮点数,默认小数点后六位
}
=================调试结果=================
float64 3.1415 3.141500
7.3.1 浮点数格式化打印
package main
import "fmt"
func main() {
f := 3.1415
fmt.Printf("|%3f|\n", f) // 打印宽度为3,但是由于f的值长度已经超过了3,所以看着没有变化
fmt.Printf("|%20f|\n", f) // 打印宽度为20,其中f占8个位置,剩下的12个位置在左边
fmt.Printf("|%-20f|\n", f) // 加个-,就表示左对齐,默认为右对齐
fmt.Printf("|%-20.3f|\n", f) // 左对齐,并显示小数点后3位,并且会自动四舍五入
fmt.Printf("|%.3f|\n", f) // 只显示小数点后3位,没有对齐要求,并且会自动四舍五入
}
=================调试结果=================
|3.141500|
| 3.141500|
|3.141500 |
|3.142 |
|3.142|
7.3.2 浮点型与无符号整数字面量运算
前面说过,go中不同类型的数据不能互相运算,但是,当一个数字没有明确定义类型时,它此时是无符号的整数字面量,是可以和其他整型进行运算的。
package main
import "fmt"
func main() {
f := 3.1415 // 默认类型:float64
fmt.Println(f + 100) // 这个100就是无符号的整数字面量,编译器会根据另一个变量的类型,自动对它做隐式转换。
// 也就是f是浮点,100也会被转换为浮点,从而完成计算。
}
=================调试结果=================
103.1415
8. 转义字符
转义字符是一种特殊的字符序列,以反斜杠()开头,后跟一个字符,用来表示一些特殊的含义。在Golang中,常见的转义字符包括:
(换行符)、 (制表符)、'(单引号)、"
(双引号)等。通过使用转义字符,我们可以在字符串中表示一些特殊的字符和控制字符。
具体常见类型如下:
转义字符 | Unicode字符的十六进制数字序列 | 作用 |
---|---|---|
\a | U+0007 | 当解释器遇到转义字符 \a 时,它会发出哔哔声。 |
\b | U+0008 | 表示退格(BS)字符。当解释器遇到转义字符\b时,它会将光标向左移动一格,覆盖前一个字符。 |
\f | U+000C | 表示换页(FF)字符。当解释器遇到转义字符\f时,它会将光标移动到当前页面的顶部,并清除页面上的所有文本。 |
\n | U+000A | 换行符,将光标移动到下一行的开头,从而开始新的一行。 |
\r | U+000D | 回车符,将光标移动到当前行的行首,单独使用看不到效果。 |
\t | U+0009 | 横向制表符,相当于2个tab,也就是8个空格。 |
\v | U+000B | 垂直制表符 |
\\双斜杠 | U+005C | \会转义紧随其后的字符 |
\’ | U+0027 | 单引号,当解释器遇到转义字符’时,它会插入一个单引号字符到字符串中。 |
\" | U+0022 | 双引号,作用同上。 |
9. 字符串
使用双引号或反引号引起来的任意个字符。它是字面常量。
9.1 定义字符串的方式
package main
import "fmt"
func main() {
a := "" // 定义一个空串
a = "abc" // 这里表面看是abc覆盖了"",实际上:a最开始在内存中指向""的地址,后面重新指向了"abc",所以""和"abc"都是独立的个体,不违背常量不可修改的特性。最终""会被丢弃。
fmt.Println(a)
}
=================调试结果=================
abc
9.2 结合转义字符使用
9.2.1 "
package main
import "fmt"
func main() {
a := ""
// 这里由于引号的特殊性,必须用\把"转义成普通字符串才能成功打印,不然"""就会引起界定符冲突问题。
a = "ab\"c"
fmt.Println(a)
a = "ab'c" // 双引号中可以包含单引号,不会引起界定符冲突的问题
fmt.Println(a)
a = `json: "name"` // 通过反引号的方式,也可以避免界定符冲突
fmt.Println(a)
}
=================调试结果=================
ab"c
ab'c
json: "name"
9.2.2 \t
package main
import "fmt"
func main() {
a := ""
a = "ab\tc"
fmt.Println(a)
}
=================调试结果=================
ab c
9.2.3 \r\n
package main
import "fmt"
func main() {
a := ""
a = "ab\r\n\tc"
fmt.Println(a)
a = `ab
c` // 该方式也可以表示上面的\r\n\t
fmt.Println(a)
}
=================调试结果=================
ab
c
ab
c
9.3 字符串拼接
package main
import "fmt"
func main() {
a := ""
a = "123" + "xyz" // a="123"是一个常量,不能改变,内存中还有一个xyz,这俩组合起来,诞生了一个新的字符串"123xyz",并不是覆盖率原来的a
fmt.Println(a)
}
=================调试结果=================
123xyz
9.4 字符串格式化
格式符参考fmt包帮助 https://pkg.go.dev/fmt
9.4.1 字符串
格式化符号 | 作用 |
---|---|
%v | 打印变量对应的值。不同的类型,产生不同的输出内容 |
%+v | 对于结构体,会打印出字段名和值 |
%#v | 对于结构体,有更加详细的输出 |
%T | 打印值的类型 |
%% | 打印百分号本身 |
9.4.2 整数
格式化符号 | 作用 |
---|---|
%b | 整型以二进制方式显示 |
%o | 整型以八进制方式显示 |
%O | 八进制带0o前缀 |
%x | 十六进制小写 |
%X | 十六进制大写 |
%U | 把一个整数用Unicode格式打印。例如 fmt.Printf(“%U, %x, %c\n”, 27979, 27979,27979) 输出 U+6D4B, 6d4b |
%c | 把rune、byte的整型值用字符形式打印 |
%q | 把一个整型当做Unicode字符输出,类似%c,不过在字符外面多了单引号。q的意思就是quote。 |
9.4.3 浮点数
格式化符号 | 作用 |
---|---|
%e、%E | 科学计数法 |
%f、%F | 小数表示法,最常用 |
%g | 内部选择使用%e还是%f以简洁输出 |
%G | 选择%E或%F |
9.4.4 字符串或字节切片
格式化符号 | 作用 |
---|---|
%s | 字符串输出。如果是rune切片,需要string强制类型转换 |
%q | 类似%s,外部加上双引号。q的意思就是quote |
9.4.5 指针
格式化符号 | 作用 |
---|---|
%p | 指针,十六进制方式显示 |
9.4.6 总结
9.5 特殊格式写法
package main
import "fmt"
func main() {
a, b, c, d := 100, 200, 300, 400
// %[2]v,这个2可以省略的,因为索引号是递增的
fmt.Printf("%d, %[2]v, %[1]d, %d", a, b, c, d)
}
=================调试结果=================
100, 200, 100, 200
9.6 Print函数
9.6.1 输出到标准输出
(1)Print:接受任意数量的参数,使用空格分割,并将它们转换为字符串后连接在一起,然后输出到控制台。
(2)Println:同上,最后追加换行
(3)Printf:按照指定的格式符输出
9.6.2 输出到字符串
经常用来拼接字符串用
(1)Sprint:相当于Print,不过输出为string
(2)Sprintln:相当于Println,不过输出为string
(3)Sprintf:相当于Printf,不过输出为string
package main
import "fmt"
func main() {
a := '测'
// fmt.Sprintf("%c", a)的作用并不是直接输出到控制台,而是输出为一个字符串,要想使用,必须用一个变量接住它
b := fmt.Sprintf("%c", a)
fmt.Println(b) // 输出变量b的值到控制台
b = fmt.Sprintf("%c%s", a, "xyz") // 也可以这样实现字符串拼接
fmt.Println(b)
}
=================调试结果=================
测
测xyz
10. 操作符
参考:https://golang.google.cn/ref/spec#Operators_and_punctuation
10.1 逻辑运算真值表
逻辑运算中,只有真和假,也就是bool中的true和false。
1为真,0为假。
10.1.1 与逻辑
可以理解为乘法或者理解为双方都为真结果才为真
A | B | F |
---|---|---|
0 | 0 | 0(假) |
0 | 1 | 0(假) |
1 | 0 | 0(假) |
1 | 1 | 1(真) |
10.1.2 或逻辑
可以理解为加法或理解为任意一方为真既最终结果为真
A | B | F |
---|---|---|
0 | 0 | 0(假) |
0 | 1 | 0(真) |
1 | 0 | 0(真) |
1 | 1 | 1(真) |
10.1.3 非逻辑
这个就是取反
A | F |
---|---|
0 | 1(真) |
1 | 0(假) |
10.2 算数运算符
主要就是+、-、*、/、%、++、–。
10.2.1 单目运算符(+、-、++、–)
上面说到了运算符种类,其中+、- 还可以当做正负用,不过就不是算数运算符了,而是单目运算符。
package main
import "fmt"
func main() {
// 这里的-,代表的是负号,不是减号,并且-只作用在5上,这种就是单目运算符
fmt.Println(5/2, -5/2)
}
=================调试结果=================
2 -2 // 整数除法丢弃余数,只返回整数
package main
import "fmt"
func main() {
a := 5
// 注意:a++只是一个语句,并不是表达式,所以不能像这里这样定义,会报错
//fmt.Println(5/2, -a/2, a++)
a ++ // 正确的定义方式(不可以++a)。a ++=a+=1=a=a+1
fmt.Println(a)
}
=================调试结果=================
6
10.2.2 双目运算符
双目运算符其实指的是运算符作用在双方,比如5/2,/需要的是除数和被除数,这种就是双目运算符。
package main
import "fmt"
func main() {
a := 5
fmt.Println(5/2, -a/2)
}
=================调试结果=================
2 -2
10.2.3 常量计算问题
常量分为有类型常量和untyped无类型常量。
不同类型的常量是不能相互运算的(除非强制类型转换),但是无类型常量之间可以相互运算。
package main
import "fmt"
func main() {
a := 5
b := 3.5
fmt.Println(a * b) // 这种是不可以的,因为5是int,b是float64
// 这样可以,因为1和2.3都属于无符号字面常量
// 其实也就是语法糖,编译器自动做了隐式类型转换,但是隐式类型转换往往是往精度更大的转,所以这里会转为浮点。
// 1为无符号整型常量,2.3为无符号浮点型常量
fmt.Println(1 * 2.3) // 官方称为无类型常量
// 或者这样也行
c := 1 * 2.5 // 官方称为无类型常量
fmt.Println(c)
fmt.Printf("%T %v\n", c, c)
}
=================调试结果=================
2.3
3.5
float64 3.5
10.3 位运算符
主要是这几个:
&位与、|位或、异或(二进制同为0,异为1)、&位清空、<<、>>
10.3.1 &位与(按位与运算)
应用场景:如判断奇偶数。
package main
import "fmt"
func main() {
a := 0b10 // 0b10二进制对应十进制2
fmt.Println(a & 1) // 2 与 1,与就是按位做乘法,实际的运算过程如下:
// 2 0b 1 0
// * *
// 1 0b 0 1
// ----------
// 0 0
// 所以,a & 1 = 0
}
=================调试结果=================
0
10.3.2 |位或(按位或运算)
按位或运算,如果两个位都为 0,则结果为 0;否则,结果为 1。
package main
import "fmt"
func main() {
a := 0b10 // 0b10二进制对应十进制2
fmt.Println(a | 1) // 2 或 1,或就是按位做加法,但也并不完全是加法,0+0=0,其他都为1,实际的运算过程如下:
// 2 0b 1 0
// + +
// 1 0b 0 1
// ----------
// 1 1
// 二进制0b11转十进制为3
// 所以,a | 1 = 3
}
=================调试结果=================
3
10.3.3 >>(右移位)
package main
import "fmt"
func main() {
fmt.Println(2 >> 2) // 把2向右移动2位。过程如下:
// 2对应的二进制:0010,向右移动1位:0001,向右移动2位:0000 1,这个1已经没有位置了,所以为0
}
=================调试结果=================
0
10.3.4 &^位清空
&^位清空可以理解为&位与得出结果的取反值。
重点记住这句话:&^位清空的运算方式以第二个数为标准: 第二个数字的位值为1,则结果的对应位清零;如果第二个数字的位值为0,则结果的对应位采用第一个数的位值。
package main
import "fmt"
func main() {
a := 0b1001
fmt.Println(a & 3)
fmt.Println(a &^ 3)
// &^位清空逻辑如下
// 首先是a & 3
// 1 0 0 1
// * * * *
// 0 0 1 1
// 0 0 0 1 = 1
// 然后到a &^ 3,运算方式以第二个数为标准: 第二个数字的位值为1,则结果的对应位清零;如果第二个数字的位值为0,则结果的对应位采用第一个数的位值。
// 1 0 0 1
// 0 0 1 1
// 1 0 0 0 = 8
}
=================调试结果=================
1
8
package main
import "fmt"
func main() {
fmt.Println(15 & 5)
// 15 1111
// 5 0101
// 5 0101
fmt.Println(15 &^ 5)
// 15 1111
// 5 0101
// 10 1010
}
=================调试结果=================
5
10
10.4 比较运算符
比较运算符组成的表达式,返回bool类型值。
成立返回True,不成立返回False。
主要是以下类型:
==、!=、>、<、>=、<=
但是注意:不同数据类型不能进行比较。
package main
import "fmt"
func main() {
fmt.Printf("%T, %[1]v", "ABC" == "abc")
}
=================调试结果=================
bool, false
10.5 逻辑运算符
由于Go语言对类型的要求,逻辑运算符操作的只能是bool类型数据,那么结果也只会是bool型。
主要是一下几个:
&&、||、!
逻辑运算符优先级
(1)!:最高
(2)&&:第二
(3)||:最低
10.5.1 &&
与运算中,有短路这个概念,就是第一个条件为假的话,就触发短路,结束了。
所以实际运用中,可以把短路条件配置在最前面,失败就停止运行,减少运算量。
package main
import "fmt"
func main() {
fmt.Println(123 > 234 && "abc" > "ABC")
// 逻辑真值表中,只有真真为真,其他都为假。
// 所以123 > 234为假,触发了逻辑运算中的短路,就直接结束了,不会再执行"abc" > "ABC"了。
}
=================调试结果=================
false
10.5.2 ||
package main
import "fmt"
func main() {
// 逻辑真值表中,或逻辑中只要有一方为真,结果就为真。
fmt.Println(123 > 234 || "abc" > "ABC") // 字符串的比较,实际上对比的是ASCII码值
// a=97,b=98,c=99
// A=65,B=66,C=67
}
=================调试结果=================
true
10.5.2.1 使用||时的注意事项
package main
import "fmt"
func main() {
fmt.Println(1 || "abc" > "ABC") // 注意这样是不可以的
// 在逻辑运算中,GO要求操作数必须是bool类型,否则不能逻辑运算,并且不提供隐式转换。
}
10.6 赋值运算符
符号 | 含义 | 示例 |
---|---|---|
= | 将等号右边的值,赋值给等号左边的标识符 | var a = 1 |
:= | 短格式赋值 | a := 1 |
+= | 将等号右边的值相加,得出的结果赋值给等号左边的标识符 | var a += 1 var a = a + 1 |
-= | 将等号右边的值相减,得出的结果赋值给等号左边的标识符 | var a -= 1 var a = a - 1 |
*= | 将等号右边的值相乘,得出的结果赋值给等号左边的标识符 | var a *= 1 var a = a * 1 |
/= | 将等号右边的值相除,得出的结果赋值给等号左边的标识符 | var a /= 1 var a = a / 1 |
%= | 将等号右边的值取余,得出的结果赋值给等号左边的标识符 | var a %= 1 var a = a % 1 |
>>= | 右位移后赋值 | var a >>= 1 var a = a >> 1 |
<<= | 左位移后赋值 | var a <<= 1 var a = a << 1 |
&= | 按位与后赋值 | var a &= 1 var a = a & 1 |
&^= | 位清空后赋值 | var a &^= 1 var a = a &^ 1 |
^= | 位异或后赋值(二进制同为0,异为1) | var a ^= 1 var a = a ^ 1 |
|= | 位或后赋值 | var a |= 1 var a = a | 1 |
10.7 三元运算符
Go 中没有三元运算符。
没有 ?: 的原因是,语言的设计者看到这个操作经常被用来创建难以理解的复杂表达式。
在替代方案上,if-else 形式虽然较长,但无疑是更清晰的。
一门语言只需要一个条件控制流结构。
10.8 指针操作
数据是放在内存中,内存是线性编址的。任何数据在内存中都可以通过一个地址来找到它。
&:取出内存地址
*:解析内存地址(就是把地址中的内容拿出来),表示通过指针取值
10.8.1 & 取出内存地址
10.8.1 & 取出内存地址
package main
import "fmt"
func main() {
a := 100
b := &a // 注意:这里的&是一个单目运算符,不是位与双目运算符。主要就是为了取出a的内存地址。
fmt.Println(b)
}
=================调试结果=================
0xc000120058 // 这就是100对应的内存地址。
10.8.2 * 解析内存地址
package main
import "fmt"
func main() {
a := 100
b := &a
c := *b
fmt.Println(c)
}
=================调试结果=================
100
10.8.3 问题思考:上述赋值后,所有变量的内存地址是否相同
package main
import "fmt"
func main() {
a := 100 // a指向100,并且100的内存地址为0xc0000b6058
b := &a // b指向100的内存地址0xc0000b6058,相当于b = 0xc0000b6058
fmt.Println(b)
// 但是,*b,是把内促地址下的值拿出来了,就相当于c = 100
// c = 100,会在系统中重新开辟一块内促来存储这个100,让c指向这个新的内存地址
// 所以b == &c不成立
c := *b
fmt.Println(a == c, b == &c)
var d = a // d 指向100
// a和b都指向100,所以a == d
// b指向0xc0000b6058,但是d也是在一个新的内存地址中来存储100的,所以b == &d不成立
fmt.Println(a == d, b == &d)
}
=================调试结果=================
0xc0000b6058
true false
true false
10.9 运算符优先级
表中优先级由高到低。
单目 > 双目。
算数 > 移位 > 比较 > 逻辑 > 赋值。
搞不清,用括号,避免产生歧义