前言
本文记录训练营第一课的相关内容,对于课程中讲的不够充分的地方,结合了go入门指南中的详细介绍进行了补充。笔记同步更新在我的博客
本篇讲述了变量,控制结构, 值类型。
介绍
golang的特点
| 特点 | 描述 |
|---|---|
| 高性能,高并发 | 接近C++的性能,标准库原生高并发支持。 |
| 语法简单,学习曲线缓 | 学习周期短至周计 |
| 丰富的标准库 | 可以解决大部分需求 |
| 工具链 | 编译,代码格式化,代码检查,测试 |
| 静态编译 | 体积小,部署编译 |
| 快速编译 | 支持增量编译 |
| 快平台 | 交叉编译简单 |
| GC | 原生GC支持 |
开发环境
这里我使用的是 gvm 在debian12上进行开发。开发环境使用vscode + neovim。
gvm 的github链接
我的实验代码仓库
数据类型
表达式是一种特定的类型的值,它可以由其它的值以及运算符组合而成。每个类型都定义了可以和自己结合的运算符集合,如果你使用了不在这个集合中的运算符,则会在编译时获得编译错误。
Bool
一个简单的例子:var b bool = true。
布尔型的值只可以是常量 true 或者 false。
两个类型相同的值可以使用相等 == 或者不等 != 运算符来进行比较并获得一个布尔型的值。
当相等运算符两边的值是完全相同的值的时候会返回 true,否则返回 false,并且只有在两个的 值的类型相同的情况下才可以使用。
var aVar = 10
aVar == 5 -> false
aVar == 10 -> true
数字类型
Go 语言支持整型和浮点型数字,并且原生支持复数,其中位的运算采用补码。
Go 也有基于架构的类型,例如:int、uint 和 uintptr。
这些类型的长度都是根据运行程序所在的操作系统类型所决定的:
int和uint在 32 位操作系统上,它们均使用 32 位(4 个字节),在 64 位操作系统上,它们均使用 64 位(8 个字节)。uintptr的长度被设定为足够存放一个指针即可。
Go 语言中没有 float 类型。(Go语言中只有 float32 和 float64)没有double类型。
整数:
- int8(-128 -> 127)
- int16(-32768 -> 32767)
- int32(-2,147,483,648 -> 2,147,483,647)
- int64(-9,223,372,036,854,775,808 -> 9,223,372,036,854,775,807)
无符号整数:
- uint8(0 -> 255)
- uint16(0 -> 65,535)
- uint32(0 -> 4,294,967,295)
- uint64(0 -> 18,446,744,073,709,551,615)
浮点型(IEEE-754 标准):
- float32(+- 1e-45 -> +- 3.4 * 1e38)
- float64(+- 5 * 1e-324 -> 107 * 1e308)
int 型是计算最快的一种类型。
o 中不允许不同类型之间的混合使用,但是对于常量的类型限制非常少,因此允许常量之间的混合使用,下面这个程序很好地解释了这个现象(该程序无法通过编译):
package main
func main() {
var a int
var b int32
a = 15
b = a + a // 编译错误
b = b + 5 // 因为 5 是常量,所以可以通过编译
}
如果尝试编译该程序,则将得到编译错误 cannot use a + a (type int) as type int32 in assignment。int的字节数取决于操作系统,32位系统中为4字节,64位系统中为8字节。可以使用unsafe.Sizeof(a)查看。不论a和b的字节数是否相同,这种转换都是不允许的。
如要进行复制,可以进行显式类型转换。数值转换会造成精度丢失,这里不做详细论述。
数据类型的位运算和逻辑算数运算和C++一致。
字符类型
严格来说,这并不是 Go 语言的一个类型,字符只是整数的特殊用例。byte 类型是 uint8 的别名,对于只占用 1 个字节的传统 ASCII 编码的字符来说,完全没有问题。例如:var ch byte = 'A';字符使用单引号括起来。可以将数字赋值给 byte类型,这点和C++一致。
类型别名
当你在使用某个类型时,你可以给它起另一个名字,然后你就可以在你的代码中使用新的名字(用于简化名称或解决名称冲突)。
在 type TZ int 中,TZ 就是 int 类型的新名称(用于表示程序中的时区),然后就可以使用 TZ 来操作 int 类型的数据。
package main
import "fmt"
type TZ int
func main() {
var a, b TZ = 3, 4
c := a + b
fmt.Printf("c has the value: %d", c) // 输出:c has the value: 7
}
实际上,类型别名得到的新类型并非和原类型完全相同,新类型不会拥有原类型所附带的方法。
常量
常量使用关键字 const 定义,用于存储不会改变的数据。
存储在常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。
常量的定义格式:const identifier [type] = value,例如:
const Pi = 3.14159
在 Go 语言中,你可以省略类型说明符 [type],因为编译器可以根据变量的值来推断其类型。
- 显式类型定义:
const b string = "abc" - 隐式类型定义:
const b = "abc"
一个没有指定类型的常量被使用时,会根据其使用环境而推断出它所需要具备的类型。换句话说,未定义类型的常量会在必要时刻根据上下文来获得相关类型。
常量的值必须是能够在编译时就能够确定的;你可以在其赋值表达式中涉及计算过程,但是所有用于计算的值必须在编译期间就能获得。
- 正确的做法:
const c1 = 2/3 - 错误的做法:
const c2 = getNumber()// 引发构建错误:getNumber() used as value
因为在编译期间自定义函数均属于未知,因此无法用于常量的赋值,但内置函数可以使用,如:len()。
数字型的常量是没有大小和符号的,并且可以使用任何精度而不会导致溢出:
const Ln2 = 0.693147180559945309417232121458\
176568075500134360255254120680009
const Log2E = 1/Ln2 // this is a precise reciprocal
const Billion = 1e9 // float constant
const hardEight = (1 << 100) >> 97
不过需要注意的是,当常量赋值给一个精度过小的数字型变量时,可能会因为无法正确表达常量所代表的数值而导致溢出,这会在编译期间就引发错误。
控制结构
IF 语句
if 可以包含一个初始化语句(如:给一个变量赋值)。这种写法具有固定的格式(在初始化语句后方必须加上分号):
例如:
if val := 10; val > max {
// do something
}
For 语句
基本使用
package main
import "fmt"
func main() {
for i := 0; i < 5; i++ {
fmt.Printf("This is the %d iteration", i)
}
}
for 初始化语句; 条件语句; 修饰语句 {}和C++中的范式是一致的。
For-range
这是 Go 特有的一种的迭代结构,您会发现它在许多情况下都非常有用。它可以迭代任何一个集合。语法上很类似其它语言中 foreach 语句,但您依旧可以获得每次迭代所对应的索引。一般形式为:for ix, val := range coll { }。
要注意的是,val 始终为集合中对应索引的值拷贝,因此它一般只具有只读性质,对它所做的任何修改都不会影响到集合中原有的值。
例子,迭代字符串
for pos, char := range str {
...
}
循环控制
break和continue和C++一致。
Switch
相比较 C 和 Java 等其它语言而言,Go 语言中的 switch 结构使用上更加灵活。其有三种主要形式
形式一
值比较
switch var1 {
case val1:
...
case val2:
...
default:
...
}
形式二
直接判别条件
switch {
case condition1:
...
case condition2:
...
default:
...
}
形式三
包含初始化语句
switch result := calculate(); {
case result < 0:
...
case result > 0:
...
default:
// 0
}
Goto
go中有goto语句,但不鼓励在编程实践中使用。