上一篇把环境跑通之后,我开始正儿八经写点 Go 代码。这篇就把变量和基本类型这块梳理清楚,顺便记录一下我踩过的几个坑。
1. 变量声明的几种姿势
Go 里声明变量有好几种写法,刚开始可能觉得乱,其实核心就两种:var 和 :=。
1.1 用 var 老老实实声明
最标准、最啰嗦的写法:
var age int = 25
var name string = "小明"
类型写在变量名后面,和 C/Java 完全相反,官方说法是为了避免复杂的类型声明读起来费劲(比如函数指针),习惯就好。
如果觉得类型多余,可以省略,让编译器自己推导:
var age = 25 // 编译器知道这是 int
var name = "小明" // 编译器知道这是 string
还有一种“偷懒”的写法,把多个变量放在一个 var 块里,适合包级别或者一堆变量一起声明:
var (
host string = "localhost"
port int = 8080
debug bool = false
)
1.2 短变量声明 :=
这个是最常用的,特别是函数内部。写法很简洁:
func main() {
age := 25
name := "小明"
fmt.Println(age, name)
}
:= 相当于声明并赋值,但它只能在函数内部使用,包级别(全局变量)不能用。我一开始不知道,在 main 外面写了个 version := "1.0",编译器直接报错,后来才知道全局变量必须用 var。
1.3 多个变量同时声明
Go 支持一次声明多个变量,而且类型可以不同:
var x, y int = 1, 2
a, b := "hello", true
1.4 匿名变量 _
有些函数的返回值我们不想用,但 Go 不允许声明了变量却不使用(编译会报错),这时候可以用下划线 _ 来忽略:
file, _ := os.Open("test.txt") // 忽略错误返回值
_ 是一个特殊的变量,任何赋给它的值都会被丢弃,它本身也不能被读取。面试偶尔会问“Go 里怎么忽略函数返回值”,答 _ 就行。
2. 零值:Go 没有未初始化的变量
这个特性我挺喜欢的,Java 里成员变量有默认值,但局部变量必须手动初始化,Go 则一视同仁:所有变量声明后都会被自动赋予零值。
各种类型的零值:
var i int // 0
var f float64 // 0
var b bool // false
var s string // ""(空字符串)
var p *int // nil
var m map[string]int // nil
var sl []int // nil
var ch chan int // nil
注意一点:nil 只能赋值给指针、slice、map、channel、函数、接口这些引用类型,不能赋值给 int、string 之类的值类型。比如 var s string = nil 会编译报错。
这个零值机制在写结构体的时候特别有用,很多字段不用管初始化,直接用默认值就行。面试常问“Go 的零值是什么”,能说清楚就是加分项。
3. 常用基本类型速览
Go 的内置基本类型不多,掌握下面这些就够了:
3.1 整数
| int8, int16, int32, int64 | 有符号整数,位数不同 |
| uint8, uint16, uint32, uint64 | 无符号整数 |
| int, uint | 和平台相关,32位系统是32位,64位系统是64位 |
| byte | uint8 的别名,常用于处理字节 |
| rune | int32 的别名,常用于处理 Unicode 字符 |
平时写代码直接 int 就完事,除非你在意内存或者需要明确位数(比如和二进制协议交互),否则没必要纠结。
3.2 浮点数
float32 和 float64,和 C 一样,没有单独的 float。一般用 float64,精度更高。
var pi float64 = 3.1415926
3.3 布尔
bool 类型,只有 true 和 false,不能用 0 和 1 代替。这点和 C 不一样,也别想用 if 1 {} 这种写法。
3.4 字符串
string 类型,用双引号或者反引号(原样字符串)表示:
s1 := "hello"
s2 := `多行字符串
可以包含换行
和 "引号" 不用转义`
Go 的字符串是不可变的,一旦创建就不能修改里面的字符,要修改得转成 []byte 或 []rune。这个后面讲字符串的时候再细说。
4. 类型转换:必须显式,而且容易踩坑
Go 是强类型语言,不支持隐式类型转换。比如一个 int 和一个 int64 相加,直接写会报错:
var a int = 5
var b int64 = 10
// c := a + b // 编译错误:mismatched types int and int64
c := int64(a) + b
所有类型转换都要显式写出来。这个一开始会让习惯了 Java 自动装箱拆箱的人很不爽,但好处是类型关系一目了然,不会出现莫名其妙的精度丢失。
两个特别容易踩的坑:
数字转字符串:string(65) 的结果不是 "65",而是 "A"!因为 string() 这个转换会把整数当作 Unicode 码点。想把数字变成字符串,得用 strconv.Itoa(65) 或者 fmt.Sprintf("%d", 65)。
fmt.Println(string(65)) // A
fmt.Println(strconv.Itoa(65)) // 65
浮点转整数会截断:int(3.9) 的结果是 3,不是四舍五入。需要四舍五入可以用 math.Round。
fmt.Println(int(3.9)) // 3
fmt.Println(math.Round(3.9)) // 4
5. 类型别名 vs 定义新类型
Go 里可以用 type 关键字基于已有类型定义新类型,也可以声明别名,两者有细微差别。
type MyInt int // 定义新类型,和 int 不是同一类型
type AliasInt = int // 定义别名,就是 int 的另一个名字
区别在于:MyInt 需要显式转换才能赋值给 int,而 AliasInt 和 int 完全通用。byte 和 rune 就是 Go 内置的别名类型。
var x MyInt = 5
var y int = int(x) // 必须转换
var z AliasInt = 5
var w int = z // 可以直接赋值
这个面试偶尔会问,了解即可。
6. 常量简单入门
常量用 const 声明,不能修改,不需要 :=。
const pi = 3.14
const (
statusOK = 200
statusNotFound = 404
)
常量可以是无类型的(比如 const pi = 3.14),也可以是显式指定类型的(const pi float64 = 3.14)。无类型常量在使用时会根据上下文自动转换,比如 var x float32 = pi 不会报错。
还有一个神器 iota,可以自动生成递增常量,常用于枚举。这个我打算放到后面讲枚举和常量生成器的时候再详细写,这里先混个脸熟。
go
const (
Monday = iota + 1 // 1
Tuesday // 2
Wednesday // 3
)
7. 新手容易犯的错(含个人血泪史)
-
短变量声明不能用全局:前面提了,:= 只能在函数内,全局变量必须 var。
-
:= 至少需要一个新变量:在同一个作用域里,如果左边变量都已经声明过了,用 := 会报错,但如果有新变量,旧变量会被重新赋值而不是声明。这个容易搞混,建议别在同一个作用域对同一组变量反复用 :=。
-
导入的包必须使用:Go 编译器很严格,import 了但没用会报错;变量声明了但没用也会报错。写代码时要及时删掉没用的东西,或者用 _ 忽略。
-
大括号风格:if、for 这些的左大括号必须和关键字同一行,不能换行。这是 Go 的硬性语法要求,一开始我不小心换行,编译器直接报错,后来才知道是为了省掉分号插入的歧义。
8. 小结
这一篇把变量和基本类型过了一遍,核心就几句:
-
声明变量要么 var,要么 :=(仅函数内)。
-
所有变量都有零值,不会有未初始化。
-
类型转换必须显式,注意 string(int) 的坑。
-
常量用 const,iota 可以生成枚举。
下一篇准备聊聊流程控制:if、for、switch。Go 的 for 很万金油,一个关键字打天下,没有 while,写起来还蛮爽的。到时候继续记录。
如果这篇文章帮到了你,欢迎点赞收藏,也欢迎留言指出我说错的地方,一起进步。
网硕互联帮助中心




评论前必须登录!
注册