1. 字面量
字面量,按照计算机科学的术语来讲是用于表达源代码中一个固定值的符号,也叫字面值。两个叫法都是一个意思,写了什么东西,值就是什么,值就是“字面意义上“的值。
1.1 整型字面量
为了便于阅读,允许使用下划线_来进行数字划分,但是仅允许在前缀符号之后和数字之间使用。
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 浮点数字面量
通过不同的前缀可以表达不同进制的浮点数
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 复数字面量
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。
'a' 'ä' '你' '\t' '\000' '\007' '\377' '\x07' '\xff' '\u12e4' '\U00101234'
1.5 转义字符
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 双引号转义 (该转义仅在字符串内有效)
1.6 字符串字面量
字符串字面量必须使用双引号""括起来或者反引号(反引号字符串不允许转义)
`abc` // "abc" `\n \n` // "\\n\n\\n" "\n" "\"" // `"` "Hello, world!\n" "今天天气不错" "日本語" "\u65e5本\U00008a9e" "\xff\u00FF"
1.7 Code
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中所有的花括号都不应该换行。
// 正确示例 func main() { fmt.Println("Hello 世界!") } // 错误示例 func main() { fmt.Println("Hello 世界!") }
- 缩进,Go默认使用
tabs也就是制表符进行缩进,仅在一些特殊情况会使用空格。 - 间隔,Go中大部分间隔都是有意义的,从某种程度上来说,这也代表了编译器是如何看待你的代码的,例如下方的数学运算
2*9 + 1/3*2
- 众所周知,乘法的优先级比加法要高,在格式化后,
*符号之间的间隔会显得更紧凑,意味着优先进行运算,而+符号附近的间隔则较大,代表着较后进行运算。 - 还是花括号,花括号在任何时候都不能够省略,就算是只有一行代码,例如
// 正确示例 if a > b { a++ } // 错误示例 if a > b a++
3. 常量
常量的值无法在运行时改变,一旦赋值过后就无法修改,其值只能来源于:
- 字面量
- 其他常量标识符
- 常量表达式
- 结果是常量的类型转换
- iota
常量只能是基本数据类型,不能是
- 除基本类型以外的其它类型,如结构体,接口,切片,数组等
- 函数的返回值
常量的值无法被修改,否则无法通过编译
3.1 初始化
常量的声明需要使用 const 关键字,常量需要在声明时就初始化一个值,并且常量的类型可以省略
const a = "张三" const num byte = 1 const bExpression = (1+2+3)/2%100 + num fmt.Println(a, num, bExpression) // 张三 1 4
如果仅仅只是声明而不确定值,将会无法通过编译
编译器报错
missing init expr for b
在Go中,可以批量声明变量并赋值,定义常量或者变量时都没有问题
const ( name = "张三" age = 13 ) const c,d,e = 1,"老吴",false
在同一个常量分组中,在已经赋值的常量后面的常量可以不用赋值,其值默认就是前一个的值,比如
const ( A = 1 B // 1 C // 1 D // 1 E // 1 )
3.2 iota
iota是一个内置的常量标识符,通常用于表示一个常量声明中的无类型整数序数,一般都是在括号中使用。
const iota = 0
看几个使用案例
const ( Num = iota // 0 Num1 // 1 Num2 // 2 Num3 // 3 Num4 // 4 )
也可以这么写
const ( Num = iota*2 // 0 Num1 // 2 Num2 // 4 Num3 // 6 Num4 // 8 )
还可以
const ( Num = iota << 2*3 + 1 // 1 Num1 // 13 Num2 // 25 Num3 = iota // 3 Num4 // 4 )
通过上面几个例子可以发现,iota是递增的,第一个常量使用iota值的表达式,根据序号值的变化会自动的赋值给后续的常量,直到用新的iota重置,这个序号其实就是代码的相对行号,是相对于当前分组的起始行号,看下面的例子
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来实现枚举
type season byte func main() { const ( Spring season = iota Summer Autumn Winter ) }
这些枚举实际上就是数字,Go也不支持直接将其转换为字符串,但我们可以通过给自定义类型添加方法来返回其字符串表现形式,实现Stringer接口即可。
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是自定义类型,可以通过强制类型转换将其他数字也转换成该类型
Season(6)
- 繁琐,字符串表现形式需要自己实现
- 表达能力弱,因为
const仅支持基本数据类型,所以这些枚举值也只能用字符串和数字来进行表示
4. 变量
变量是用于保存一个值的存储位置,允许其存储的值在运行时动态的变化。每声明一个变量,都会为其分配一块内存以存储对应类型的值
4.1 声明
在go中的类型声明是后置的,变量的声明会用到var关键字,格式为var 变量名 类型名,变量名的命名规则必须遵守标识符的命名规则
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
- 根据值自行判定变量类型(类型推导)
var num = 12 var str = "demo1" fmt.Print(num, "\n", str)
- 跟 JS 语法差不多,使用var 可以让go语言自行推导值是什么数据类型
- 省略var,注意:=左侧的变量不应该是已经声明过的,否则会导致编译错误
name := "tom" fmt.Print("name is ? ", name)
- := 这种省略方式,等价于 var name string name = “tom”
- 多变量声明
在编程中,有时我们需要一次性声明多个变量,Golang也提供这样的语法
var 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)
- 定义全局变量
同时,当要声明多个不同类型的变量时,可以使用()进行包裹,可以存在多个()。
var ( address = "湖南" father = "老刘" son = "老八" ) var ( name string age int address string ) var ( school string class int ) fmt.Print(address, father, son)
- 当要声明多个相同类型的变量时,可以只写一次类型
var numA, numB, numC int
- 该区域的数据值可以在同一类型范围内不断变化
- 变量在同一作用域内不能重名
- 变量=变量名+值+数据类型
- Go的变量如果没有赋予初始值,编译器会使用默认值,比如int默认值0,string默认值为空
- 一个变量如果只是声明而不赋值,那么变量存储的值就是对应类型的零值。
4.2 赋值
赋值会用到运算符=,例如
var name string name = "jack"
也可以声明的时候直接赋值
var name string = "jack"
或者这样也可以
var name string var age int name, age = "jack", 1
第二种方式每次都要指定类型,可以使用官方提供的语法糖:短变量初始化,可以省略掉var关键字和后置类型,具体是什么类型交给编译器自行推断。
name := "jack" // 字符串类型的变量。
虽然可以不用指定类型,但是在后续赋值时,类型必须保持一致,下面这种代码无法通过编译。
a := 1 a = "123" // 报错 // cannot use "123" (untyped string constant) as int value in assignment
还需要注意的是,短变量初始化不能使用nil,因为nil不属于任何类型,编译器无法推断其类型。
name := nil // 无法通过编译
短变量声明可以批量初始化
name, age := "jack", 1
短变量声明方式无法对一个已存在的变量使用,因为它本身所代表的就是 var a int = 1,多次使用就代表重复声明一个变量
// 错误示例 var a int a := 1 // 错误示例 a := 1 a := 2
但是有一种情况除外,那就是在赋值旧变量的同时声明一个新的变量,比如
a := 1 a, b := 2, 2
这种代码是可以通过编译的,变量a被重新赋值,而b是新声明的。
在go语言中,有一个规则,那就是所有在函数中的变量都必须要被使用,比如下面的代码只是声明了变量,但没有使用它。那么在编译时就会报错,提示你这个变量声明了但没有使用
func main() { a := 1 } // a declared and not used
这个规则仅适用于函数内的变量,对于函数外的包级变量则没有这个限制,下面这个代码就可以通过编译。
var a = 1 func main() { }
4.3 匿名
用下划线可以表示不需要某一个变量
Open(name string) (*File, error)
比如os.Open函数有两个返回值,你不需要某一个变量时,就可以使用下划线_代替
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中,如果想要交换两个变量的值,不需要使用指针,可以使用赋值运算符直接进行交换,语法上看起来非常直观,例子如下
num1, num2 := 25, 36 num1, num2 = num2, num1
思考下面这一段代码,这是计算斐波那契数列的一小段代码,三个变量在计算后的值分别是什么
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在进行多个变量赋值运算时,它的顺序是先计算值再赋值,并非从左到右计算。
a, b, c = b, c, a+b // a, b, c = 1, 1, 0+1
4.5 比较
变量之间的比较有一个大前提,那就是它们之间的类型必须相同,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函数比较最大值
minVal := min(1, 2, -1, 1.2) maxVal := max(100, 22, -1, 1.12)
它们的参数支持所有的可比较类型,go中的可比较类型有
- 布尔
- 数字
- 字符串
- 指针
- 通道 (仅支持判断是否相等)
- 元素是可比较类型的数组(切片不可比较)
- 字段类型都是可比较类型的结构体(仅支持判断是否相等)
除此之外,还可以通过导入标准库cmp来判断,不过仅支持有序类型的参数,在go中内置的有序类型只有数字和字符串。
import "cmp" func main() { cmp.Compare(1, 2) cmp.Less(1, 2) }
4.6 代码块
在函数内部,可以通过花括号建立一个代码块,代码块彼此之间的变量作用域是相互独立的。例如下面的代码
func main() { a := 1 { a := 2 fmt.Println(a) } { a := 3 fmt.Println(a) } fmt.Println(a) }
它输出的顺序:2 3 1
块与块之间的变量相互独立,不受干扰,无法访问,但是会受到父块中的影响。
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)
/* 强制类型转换 */ func main() { var i int = 2 var f float64 = float64(i) var u uint = uint(f) fmt.Println(i, f, u) }
在Go中,低精度 转——》搞精度 没有问题,但是 高精度 转——》低精度,虽然编译不会报错,但是转换出来的结果是按溢出处理,和希望的结果不一样
var num1 int64 = 9999999 var num2 int8 = int8(num1) fmt.Println(num2) // 127
- Go中,数据类型的转换可以从 表示范围小》表示范围大,也可以 范围大》范围小
- 被转换的是变量存储的数据(值),变量本身的数据类型并没有变化
5.1.1 练习一
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 练习二
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()… 会返回转换后的字符串
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
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转基本数据类型
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 注意
当转换不成功时,转换后的结果会是那个类型的默认值
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也支持增强赋值运算符,如下。
a += 1 a /= 2 a &^= 2
在Go语言中,没有自增和自减运算符,它们被降级为了语句
statement并且规定了只能位于操作数的后方,所以不用再去纠结 i++ 和 ++i 这样的蠢问题(个人意见)。
a++ // 正确 ++a // 错误
- 另外,当使用 a++ 这样的语法时,它们将不再具有返回值
意思是说a = b++这种语法是错误的。
6.1 取反运算符
func main() { var a int = 2 a = ^a fmt.Println(a) // -3 }
在Go中,整数是用补码表示的,对于一个有符号整数a,其按位取反的结果是 -x -1
6.2 其他的运算
至于其他的运算符本文就不多说了,因为与其他编程语言差异不大
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中文学习文档