1. 字面量
字面量,按照计算机科学的术语来讲是用于表达源代码中一个固定值的符号,也叫字面值。两个叫法都是一个意思,写了什么东西,值就是什么,值就是"字面意义上"的值。
1.1 整型字面量
为了便于阅读,允许使用下划线_
来进行数字划分,但是仅允许在前缀符号之后 和数字之间使用。
go
24 // 24
2_4 // 24
0_2_4 // 24
10_000 // 10k
100_000 // 100k
0O24 // 20
0b00 // 0
0x00 // 0
0x0_0 // 0
1.2 浮点数字面量
通过不同的前缀可以表达不同进制的浮点数
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)
1.3 复数字面量
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
1.4 字符字面量
字符字面量必须使用单引号括起来''
,Go中的字符完全兼容utf8
。
go
'a'
'ä'
'你'
'\t'
'\000'
'\007'
'\377'
'\x07'
'\xff'
'\u12e4'
'\U00101234'
1.5 转义字符
Go中可用的转义字符
text
\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 双引号转义 (该转义仅在字符串内有效)
1.6 字符串字面量
字符串字面量必须使用双引号""
括起来或者反引号(反引号字符串不允许转义)
go
`abc` // "abc"
`\n
\n` // "\\n\n\\n"
"\n"
"\"" // `"`
"Hello, world!\n"
"今天天气不错"
"日本語"
"\u65e5本\U00008a9e"
"\xff\u00FF"
1.7 Code
go
package main
import "fmt"
/*
字面量
*/
func main() {
// 整数
var a = 100_000
var b = 0b00
var c = 0x00
fmt.Println(a, b, c)
// 浮点
var d = 0.
fmt.Println(d)
// 复数
var e = 0i
var f = .25i
fmt.Println(e, f)
}
2. 风格
关于编码风格这一块Go是强制所有人统一同一种风格,Go官方提供了一个格式化工具gofmt
,通过命令行就可以使用,该格式化工具没有任何的格式化参数可以传递,仅有的两个参数也只是输出格式化过程,所以完全不支持自定义,也就是说所有通过此工具的格式化后的代码都是同一种代码风格,这会极大的降低维护人员的心智负担,所以在这一块追求个性显然是一个不太明智的选择。
下面会简单列举一些规则,平时在编写代码的时候也可以稍微注意一下。
- 花括号,关于花括号
{}
到底该不该换行,几乎每个程序员都能说出属于自己的理由,在Go中所有的花括号都不应该换行。
go
// 正确示例
func main() {
fmt.Println("Hello 世界!")
}
// 错误示例
func main()
{
fmt.Println("Hello 世界!")
}
-
缩进,Go默认使用
tabs
也就是制表符进行缩进,仅在一些特殊情况会使用空格。 -
间隔,Go中大部分间隔都是有意义的,从某种程度上来说,这也代表了编译器是如何看待你的代码的,例如下方的数学运算
go2*9 + 1/3*2
众所周知,乘法的优先级比加法要高,在格式化后,
*
符号之间的间隔会显得更紧凑,意味着优先进行运算,而+
符号附近的间隔则较大,代表着较后进行运算。 -
还是花括号,花括号在任何时候都不能够省略,就算是只有一行代码,例如
go// 正确示例 if a > b { a++ } // 错误示例 if a > b a++
3. 常量
常量的值无法在运行时改变,一旦赋值过后就无法修改,其值只能来源于:
- 字面量
- 其他常量标识符
- 常量表达式
- 结果是常量的类型转换
- iota
常量只能是基本数据类型,不能是
- 除基本类型以外的其它类型,如结构体,接口,切片,数组等
- 函数的返回值
常量的值无法被修改,否则无法通过编译
3.1 初始化
常量的声明需要使用 const 关键字,常量需要在声明时就初始化一个值,并且常量的类型可以省略
go
const a = "张三"
const num byte = 1
const bExpression = (1+2+3)/2%100 + num
fmt.Println(a, num, bExpression) // 张三 1 4
如果仅仅只是声明而不确定值,将会无法通过编译
编译器报错
bash
missing init expr for b
在Go中,可以批量声明变量并赋值,定义常量或者变量时都没有问题
go
const (
name = "张三"
age = 13
)
const c,d,e = 1,"老吴",false
在同一个常量分组中,在已经赋值的常量后面的常量可以不用赋值,其值默认就是前一个的值,比如
go
const (
A = 1
B // 1
C // 1
D // 1
E // 1
)
3.2 iota
iota
是一个内置的常量标识符,通常用于表示一个常量声明中的无类型整数序数,一般都是在括号中使用。
go
const iota = 0
看几个使用案例
go
const (
Num = iota // 0
Num1 // 1
Num2 // 2
Num3 // 3
Num4 // 4
)
也可以这么写
go
const (
Num = iota*2 // 0
Num1 // 2
Num2 // 4
Num3 // 6
Num4 // 8
)
还可以
go
const (
Num = iota << 2*3 + 1 // 1
Num1 // 13
Num2 // 25
Num3 = iota // 3
Num4 // 4
)
通过上面几个例子可以发现,iota
是递增的,第一个常量使用iota
值的表达式,根据序号值的变化会自动的赋值给后续的常量,直到用新的iota
重置,这个序号其实就是代码的相对行号,是相对于当前分组的起始行号,看下面的例子
go
const (
Num = iota<<2*3 + 1 // 1 第一行
Num2 = iota<<2*3 + 1 // 13 第二行
_ // 25 第三行
Num3 //37 第四行
Num4 = iota // 4 第五行
_ // 5 第六行
Num5 // 6 第七行
)
例子中使用了匿名标识符_
占了一行的位置,可以看到iota
的值本质上就是iota
所在行相对于当前const
分组的第一行的差值。而不同的const
分组则相互不会影响
3.3 枚举
Go 语言没有位枚举单独设计一个数据类型,不像其他语言通常会有一个enum
来表示。一般在Go中,都是通过自定义类型+const+iota来实现枚举
go
type season byte
func main() {
const (
Spring season = iota
Summer
Autumn
Winter
)
}
这些枚举实际上就是数字,Go也不支持直接将其转换为字符串,但我们可以通过给自定义类型添加方法来返回其字符串表现形式,实现Stringer
接口即可。
go
func (s Season) Test() string {
switch s {
case Spring:
return "spring"
case Summer:
return "summer"
case Autumn:
return "autumn"
case Winter:
return "winter"
}
return ""
}
这样一来就是一个简单的枚举实现了。你也可以通过官方工具Stringeropen in new window来自动生成枚举。
不过它有以下缺点:
-
类型不安全,因为
Season
是自定义类型,可以通过强制类型转换将其他数字也转换成该类型goSeason(6)
-
繁琐,字符串表现形式需要自己实现
-
表达能力弱,因为
const
仅支持基本数据类型,所以这些枚举值也只能用字符串和数字来进行表示
4. 变量
变量是用于保存一个值的存储位置,允许其存储的值在运行时动态的变化。每声明一个变量,都会为其分配一块内存以存储对应类型的值
4.1 声明
在go中的类型声明是后置的,变量的声明会用到var
关键字,格式为var 变量名 类型名
,变量名的命名规则必须遵守标识符的命名规则
go
package main
import "fmt"
var intNum int
var str string
var char byte
/*
变量的定义
*/
func main() {
var i int = 15
i = 10
fmt.Print(i)
}
在go语言中,变量的定义是以 var 变量名 数据类型 = 值
来确定的。
把它看作是强制规定数据类型的js语法就可以了
注意事项:
-
变量表示内存中的一个存储区域
-
该区域有自己的名称(变量名)和类型(数据类型)
-
Golang变量使用的三种方式
-
指定变量类型,声明后如果不赋值,使用默认值
int 数据结构 的默认值是 0
-
根据值自行判定变量类型(类型推导)
govar num = 12 var str = "demo1" fmt.Print(num, "\n", str)
跟 JS 语法差不多,使用var 可以让go语言自行推导值是什么数据类型
-
省略var,注意:=左侧的变量不应该是已经声明过的,否则会导致编译错误
goname := "tom" fmt.Print("name is ? ", name)
:= 这种省略方式,等价于 var name string name = "tom"
-
-
多变量声明
在编程中,有时我们需要一次性声明多个变量,Golang也提供这样的语法
govar n1, n2, n3 int fmt.Print("n1=", n1, "n2=", n2, "n3=", n3) var name, age, sex = "张三", "18", "男" fmt.Print("name=", name, "age=", age, "sex=", sex) name1, age1, sex1 := "张三", "18", "男" fmt.Print("name=", name1, "age=", age1, "sex=", sex1)
-
定义全局变量
同时,当要声明多个不同类型的变量时,可以使用
()
进行包裹,可以存在多个()
。govar ( address = "湖南" father = "老刘" son = "老八" ) var ( name string age int address string ) var ( school string class int ) fmt.Print(address, father, son)
当要声明多个相同类型的变量时,可以只写一次类型
govar numA, numB, numC int
-
该区域的数据值可以在同一类型范围内不断变化
-
变量在同一作用域内不能重名
-
变量=变量名+值+数据类型
-
Go的变量如果没有赋予初始值,编译器会使用默认值,比如int默认值0,string默认值为空
-
一个变量如果只是声明而不赋值,那么变量存储的值就是对应类型的零值。
4.2 赋值
赋值会用到运算符=
,例如
go
var name string
name = "jack"
也可以声明的时候直接赋值
go
var name string = "jack"
或者这样也可以
go
var name string
var age int
name, age = "jack", 1
第二种方式每次都要指定类型,可以使用官方提供的语法糖:短变量初始化,可以省略掉var
关键字和后置类型,具体是什么类型交给编译器自行推断。
go
name := "jack" // 字符串类型的变量。
虽然可以不用指定类型,但是在后续赋值时,类型必须保持一致,下面这种代码无法通过编译。
go
a := 1
a = "123"
// 报错
// cannot use "123" (untyped string constant) as int value in assignment
还需要注意的是,短变量初始化不能使用nil
,因为nil
不属于任何类型,编译器无法推断其类型。
go
name := nil // 无法通过编译
短变量声明可以批量初始化
go
name, age := "jack", 1
短变量声明方式无法对一个已存在的变量使用,因为它本身所代表的就是 var a int = 1,多次使用就代表重复声明一个变量
go
// 错误示例
var a int
a := 1
// 错误示例
a := 1
a := 2
但是有一种情况除外,那就是在赋值旧变量的同时声明一个新的变量,比如
go
a := 1
a, b := 2, 2
这种代码是可以通过编译的,变量a
被重新赋值,而b
是新声明的。
在go语言中,有一个规则,那就是所有在函数中的变量都必须要被使用,比如下面的代码只是声明了变量,但没有使用它。那么在编译时就会报错,提示你这个变量声明了但没有使用
go
func main() {
a := 1
}
// a declared and not used
这个规则仅适用于函数内的变量,对于函数外的包级变量则没有这个限制,下面这个代码就可以通过编译。
go
var a = 1
func main() {
}
4.3 匿名
用下划线可以表示不需要某一个变量
go
Open(name string) (*File, error)
比如os.Open
函数有两个返回值,你不需要某一个变量时,就可以使用下划线_
代替
go
func main() {
a := "张三变量"
// 当test方法有两个返回值,想要不接收哪个值就可以使用 下划线_ 来舍弃它
c, _ := test(a)
fmt.Println(c)
}
func test(str string) (string, bool) {
fmt.Println(str)
str += " 进行了修改"
return str, false
}
4.4 变换
在Go中,如果想要交换两个变量的值,不需要使用指针,可以使用赋值运算符直接进行交换,语法上看起来非常直观,例子如下
go
num1, num2 := 25, 36
num1, num2 = num2, num1
思考下面这一段代码,这是计算斐波那契数列的一小段代码,三个变量在计算后的值分别是什么
go
a, b, c := 0, 1, 1
a, b, c = b, c, a+b
// 答案: 1 1 1
为什么不是 1 1 2 呢?
明明a已经被赋予b的值了,为什么a+b的结果还是1?go在进行多个变量赋值运算时,它的顺序是先计算值再赋值,并非从左到右计算。
go
a, b, c = b, c, a+b
// a, b, c = 1, 1, 0+1
4.5 比较
变量之间的比较有一个大前提,那就是它们之间的类型必须相同,go语言中不存在隐式类型转换,像下面这样的代码是无法通过编译的
go
func main() {
var a uint64
var b int64
// 编译器会告诉你两者之间类型并不相同
// 错误:invalid operation: a == b (mismatched types uint64 and int64)
fmt.Println(a == b)
}
在没有泛型之前,早期go提供的内置min
,max
函数只支持浮点数,到了1.21版本,go才终于将这两个内置函数用泛型重写,现在可以使用min
函数比较最小值、使用max
函数比较最大值
go
minVal := min(1, 2, -1, 1.2)
maxVal := max(100, 22, -1, 1.12)
它们的参数支持所有的可比较类型,go中的可比较类型有
- 布尔
- 数字
- 字符串
- 指针
- 通道 (仅支持判断是否相等)
- 元素是可比较类型的数组(切片不可比较)
- 字段类型都是可比较类型的结构体(仅支持判断是否相等)
除此之外,还可以通过导入标准库cmp
来判断,不过仅支持有序类型的参数,在go中内置的有序类型只有数字和字符串。
go
import "cmp"
func main() {
cmp.Compare(1, 2)
cmp.Less(1, 2)
}
4.6 代码块
在函数内部,可以通过花括号建立一个代码块,代码块彼此之间的变量作用域是相互独立的。例如下面的代码
go
func main() {
a := 1
{
a := 2
fmt.Println(a)
}
{
a := 3
fmt.Println(a)
}
fmt.Println(a)
}
它输出的顺序:2 3 1
块与块之间的变量相互独立 ,不受干扰,无法访问,但是会受到父块中的影响。
go
func main() {
a := 1
{
a := 2
fmt.Println(a)
}
{
fmt.Println(a)
}
fmt.Println(a)
}
它的输出的顺序:2 1 1
5. 类型强转
5.1 整型
Go 和 java/c 不同,Go在不同类型的变量之间赋值需要显式转换,也就是说Go中数据类型不能自动转换
在Go中的强制转换会有对应类型的标准函数 例如:int类型 有 int()函数,来转换数据类型 var c int = int(u)
go
/*
强制类型转换
*/
func main() {
var i int = 2
var f float64 = float64(i)
var u uint = uint(f)
fmt.Println(i, f, u)
}
在Go中,低精度 转------》搞精度 没有问题,但是 高精度 转------》低精度,虽然编译不会报错,但是转换出来的结果是按溢出处理,和希望的结果不一样
go
var num1 int64 = 9999999
var num2 int8 = int8(num1)
fmt.Println(num2) // 127
- Go中,数据类型的转换可以从 表示范围小》表示范围大,也可以 范围大》范围小
- 被转换的是变量存储的数据(值),变量本身的数据类型并没有变化
5.1.1 练习一
go
func main() {
var n1 int32 = 12
var n2 int64
var n3 int8
n2 = n1 + 20 // n2 类型不一致
n3 = n1 + 20
fmt.Println(n1, n2, n3)
}
当类型不同时,接受变量的数据类型也必须一直,否则如上图报错
5.2.1 练习二
go
var n1 int32 = 12
var n2 int8
var n3 int8
n2 = int8(n1) + 127 // 编译通过,但是会发生溢出
n3 = int8(n1) + 128 // 内存超出上限,直接报错
fmt.Println(n3, n2)
5.2 string 字符串
在程序开发中,我们经常需要将基本类型转成string类型或者string转基本数据类型
占位符名 | 表示 |
---|---|
%d | 十进制整数 |
%f | 浮点数(默认保留六位小数) |
%s | 字符串 |
%t | 布尔型 |
%b | 二进制数 |
%o | 八进制数 |
%x | 小写十六进制数 |
%X | 大写十六进制数 |
%q | ASCII 转 字符 |
%c | ASCII 转 字符串(去引号) |
5.2.1 方法一:fmt.Sprintf("%参数",表达式)
- 参数需要和表达式的数据类型相匹配
- fmt.Sprintf()... 会返回转换后的字符串
go
var str string
var num1 int = 10
var num2 float64 = 123.345
var b bool = true
var c byte = 'a'
str = fmt.Sprintf("%d", num1)
fmt.Println("int 转 string:", str)
str = fmt.Sprintf("%f", num2)
fmt.Println("float64 转 string:", str)
str = fmt.Sprintf("%t", b)
fmt.Println("bool 转 string:", str)
str = fmt.Sprintf("%q", c)
fmt.Println("byte 转 string:", str)
str = fmt.Sprintf("%c", c)
fmt.Println("byte 转 string(去引号):", str)
5.2.2 方法二:strconv
go
var str string
var num1 int = 10
var num2 float64 = 123.345
var b bool = true
//var c byte = 'a'
str = strconv.FormatInt(int64(num1), 10)
fmt.Println("int 转 string:", str)
/*
参数一:转换的变量
参数二: 'f' 代表转换的格式
参数三:表示保留小数位个数
参数四:表示这个小数是float64
*/
str = strconv.FormatFloat(num2, 'f', -1, 64)
fmt.Println("float64 转 string:", str)
str = strconv.FormatBool(b)
fmt.Println("bool 转 string:", str)
5.2.3 string转基本数据类型
go
var b bool
var strBool string = "true"
// ParseBool(value bool,err error) 方法返回两个值,第二个返回值隐藏掉就可以了
b, _ = strconv.ParseBool(strBool)
fmt.Println("字符串转bool:", b)
var n1 int
var n2 int64
var strInt string = "12138"
// ParseInt(value int64,err error)也是返回两个值,但是它返回的整型是int64类型,如果想要其他类型必须根据接收的变量然后再转一次
n2, _ = strconv.ParseInt(strInt, 10, 64)
n1 = int(n2)
fmt.Println(n1)
var strFloat string = "123.345"
var f1 float64
f1, _ = strconv.ParseFloat(strFloat, 64)
fmt.Println(f1)
5.2.4 注意
当转换不成功时,转换后的结果会是那个类型的默认值
go
var str4 string = "hello"
var n3 int64 = 11
n3, _ = strconv.ParseInt(str4, 10, 64)
fmt.Println(n3)// 0
6. 运算符
下面是Go语言中支持的运算符号的优先级排列
Precedence Operator
5 * / % << >> & &^
4 + - | ^
3 == != < <= > >=
2 &&
1 ||
有一点需要稍微注意下,go语言中没有选择将~
作为取反运算符,而是复用了^
符号,当两个数字使用^
时,例如a^b
,它就是异或运算符,只对一个数字使用时,例如^a
,那么它就是取反运算符。go也支持增强赋值运算符,如下。
go
a += 1
a /= 2
a &^= 2
在Go语言中,没有自增和自减运算符,它们被降级为了语句
statement
并且规定了只能位于操作数的后方,所以不用再去纠结 i++ 和 ++i 这样的蠢问题(个人意见)。
goa++ // 正确 ++a // 错误
另外,当使用 a++ 这样的语法时,它们将不再具有返回值
意思是说
a = b++
这种语法是错误的。
6.1 取反运算符
go
func main() {
var a int = 2
a = ^a
fmt.Println(a) // -3
}
在Go中,整数是用补码表示的,对于一个有符号整数a,其按位取反的结果是 -x -1
6.2 其他的运算
至于其他的运算符本文就不多说了,因为与其他编程语言差异不大
go
i := 1
j := 2
c := i + j
c = i*j
c = i/j
c = i%j
....
7. 😍前篇知识回顾
8. 💕👉 其他好文推荐
- 还不了解Git分布式版本控制器?本文将带你全面了解并掌握
- 带你认识Maven的依赖、继承和聚合都是什么!有什么用?
- 2-3树思想与红黑树的实现与基本原理
- !全网最全! ElasticSearch8.7 搭配 SpringDataElasticSearch5.1 的使用
- 全面深入Java GC!!带你完全了解 GC垃圾回收机制!!
- 全面了解Java的内存模型(JMM)!详细清晰!
- 在JVM中,类是如何被加载的呢?本篇文章就带你认识类加载的一套流程!
全文资料学习全部参考于:Golang中文学习文档