Go 入门基础:类型、变量与运算符

一、Go 代码基本元素

1. 基本代码元素

读代码时,可以先抓住两件事:数据保存在哪里,程序对这些数据做了什么。 变量和常量承载数据,类型约束数据,函数、运算符以及条件和循环组织操作。原文

go 复制代码
var price int = 12
quantity := 3
const Discount = 2

这里,price 和 quantity 是变量,int 是类型,12、3、2 是直接写出的字面量,Discount 是具名常量。值是具体内容,类型决定变量能保存哪类值。

元素 例子 作用
关键字 var、const、func 构成 Go 语法,不能用作自定义名称
标识符 price、Discount 为变量、常量、类型、函数等命名
类型 int、string 约束数据及其操作
字面量 12、"你好" 直接表示一个值
运算符 +、*、< 计算或比较
函数 countBuckets(20) 中的 countBuckets 组织可调用的操作

容易混淆的是:int 虽然由 Go 预先提供,却是预声明的类型名,属于标识符,并不是关键字。Go 语言规范

2. 示例与执行过程

下面的程序生成 20 个随机整数,统计其中小于 5 和大于等于 5 的数量。

go 复制代码
package main

import (
    "fmt"
    "math/rand"
)

const Limit = 10

func countBuckets(samples int) (int, int) {
    var low, high int

    for step := 0; step < samples; step++ {
        number := rand.Intn(Limit)
        if number < Limit/2 {
            low++
        } else {
            high++
        }
    }

    return low, high
}

func main() {
    samples := 20
    low, high := countBuckets(samples)
    fmt.Println("较小组:", low, "较大组:", high)
    fmt.Println("数量核对:", low+high == samples)
}

入口与包

可执行程序从 main 包中的 main() 函数开始执行。import 引入需要使用的包;fmt.Println 是调用 fmt 包的输出函数,rand.Intn 是调用 rand 包的随机数函数。注意:"math/rand" 是导入路径,默认使用的包名是 rand。包命名说明

函数的输入与输出

func countBuckets(samples int) (int, int) 表示:接收一个整数参数,返回两个整数。return low, high 按顺序返回结果,调用处的 low, high := countBuckets(samples) 按相同顺序接收。多返回值说明

循环与分支如何配合

low 和 high 没有显式初始值,使用整数零值 0。零值规则 循环让 step 从 0 走到 19,共执行 20 次。每次抽取一个数,再由 if-else 决定哪个计数加一;++ 表示自增 1。

根据这段代码,每次抽样恰好进入一组,所以两组计数的和一定是 20。各组数量可以不同,并不保证各占一半。完整示例已编译并运行验证。

3. 常见易错点

一、随机数范围和条件边界

rand.Intn(10) 生成的是 0~9,不包含 10。判断 number < 5 时,0~4 进入第一组,5~9 进入第二组,数字 5 归入 else。Intn 官方文档

网页原例使用 rand.Intn(16) 和 < 8,因此实际分组是 0~7 与 8~15。读代码时,尤其要分清"小于"和"小于等于"。

二、声明、赋值和比较

go 复制代码
count := 4                  // 声明并初始化变量
count = 6                   // 给已有变量赋值
fmt.Println(count == 6)     // 比较,输出 true

:= 用于简短变量声明,= 用于赋值,== 用于判断是否相等。比较是产生布尔结果的表达式,赋值是改变变量内容的语句,二者用途不同。Go 语句规则

三、换行不只是格式问题

go 复制代码
if count > 0 {
    fmt.Println(count)
} else {
    fmt.Println("没有数据")
}

函数声明以及这里的 if、三段式 for,左大括号应与前面的内容放在同一行;else 也应跟在前一个右大括号后。Go 会在特定行尾自动插入分号,随意换行可能导致语法错误。写代码时用 gofmt 统一格式即可,不必一开始就记住全部断行规则。分号规则、格式化说明

4. 读代码的思路

先找到程序入口,再看变量保存什么、函数接收和返回什么,最后跟着循环与条件走一遍执行过程。能说明这段程序的数据如何变化,就比单纯记住每个符号的名字更接近真正理解代码。

二、关键字与标识符

1. 关键字与标识符的区别

go 复制代码
var score int = 90

var 告诉编译器要声明变量,是关键字;score 给变量命名,是标识符;int 表示整数类型,也属于标识符。标识符不只用于变量,还用于类型、常量、函数、包等。原文

Go 的 25 个关键字可以按作用来理解,不必一开始全部背下来:

作用 关键字
声明和代码组织 const、func、import、package、type、var
构造类型 chan、interface、map、struct
循环与分支等流程控制 break、case、continue、default、else、fallthrough、for、goto、if、range、return、select、switch
延迟调用与并发执行 defer、go

关键字不能拿来给变量或函数起名字。 var type int 是错误的,可以改成 var kind int。关键字规范

预声明标识符不等于关键字。 int、string、true、nil、len 等名称由 Go 预先提供,却不是保留关键字。语法允许在适当作用域中用同名声明遮蔽它们,但这容易妨碍阅读和调用,实际命名应尽量避免。预声明标识符

2. 命名规则与惯例

标识符由 Unicode 字母、数字和下划线组成,首字符只能是字母或下划线,不能是数字。Go 区分大小写,所以 score 与 Score 是不同的名字。标识符规则

名字 是否合法 原因
userName、user2、_temp 合法 字符和开头都符合规则
年龄 合法 汉字可以属于 Unicode 字母
2users 不合法 数字开头
user-name、$user 不合法 包含不允许的字符
for 不合法 是关键字

user.name 不能作为一个完整的变量名,但点号可以在代码中表达成员访问;fmt.Println 同样可以合法地访问包中的函数。

语法合法不代表适合项目协作。 通常用 userName 这样的驼峰式名称;包名简短、小写;缩写尽量保持一致,例如 userID、HTTPClient。Go 命名惯例

3. 导出与访问范围

对于包级别的函数、类型、变量、常量,以及结构体字段和方法,首字母是 Unicode 大写字母时,名称可以导出供其他包访问。导出规则

go 复制代码
package calculator

func Add(a, b int) int {
    return a + b
}

func subtract(a, b int) int {
    return a - b
}

其他包导入这个包后,可以调用 calculator.Add,不能直接调用 calculator.subtract。但同一个包里的其他文件也可以使用 subtract。未导出的边界是包,不是单个文件。

名字开头才决定是否满足大写条件:User 可以满足,_User 不满足,中文开头也不满足。函数里的局部变量即使叫 Total,也不会因为大写而成为供其他包访问的变量。

4. 空标识符

单独的 _ 叫空标识符。接收多个返回值时,它可以占住不需要的那个位置:空标识符说明

go 复制代码
func limits() (int, int) {
    return 10, 50
}

// 以下代码放在调用函数中
low, _ := limits()
fmt.Println(low) // 10

这里保留第一个返回值 10,忽略第二个返回值 50;函数仍然会正常执行。_ 不保存一个可以再读出的变量,不能写 fmt.Println(_)。

_temp 则是普通名字,没有忽略值的特殊作用。理解关键字、普通名字、导出名字和空标识符各自的角色,就能避免大多数入门命名错误。

三、基本类型与字面量

1. 基本类型与数值范围

类型规定变量能保存哪些值;字面量是值在代码里的直接写法,例如 42、3.5、'好' 和 "Go"。Go 的基本类型分为五类:原文

分类 内置类型 用途
布尔 bool 真假判断
整数 int、int8、int16、int32、int64;uint、uint8、uint16、uint32、uint64;uintptr 整数值
浮点数 float32、float64 近似表示实数
复数 complex64、complex128 带实部和虚部的数
字符串 string 字节序列,常用于文本

整数名称中的数字表示位数,而不是十进制位数。 8 位共有 256 种组合:uint8 的范围是 0~255,int8 的范围是 -128~127。一个字节等于 8 位,所以 uint32 占 4 字节。

int 和 uint 的大小为 32 或 64 位,由实现决定;uintptr 是足以保存指针位模式的无符号整数,入门阶段只需认识它。complex64 的实部和虚部各为 float32,complex128 则各为 float64。数值类型规范

2. 新类型与类型别名

go 复制代码
// 创建新类型
type UserID uint64

// 给原类型起别名
type ID = uint64

UserID 与 uint64 是不同类型,只是底层类型相同;ID 与 uint64 则是同一个类型的两个名字。

go 复制代码
var raw uint64 = 100
var user UserID = UserID(raw) // 新类型需要显式转换
var alias ID = raw           // 别名可以直接使用

同理,type Status bool 创建新布尔类型,而 type Boolean = bool 只是别名。定义新类型可以区分用户 ID、订单 ID 等含义,降低误用的机会。类型声明规则

基本类型中还常见两个预声明别名:byte 就是 uint8,rune 就是 int32。别名不会额外创造一种新类型。

3. 零值与布尔值

没有显式初始化时,基本类型变量使用零值:布尔为 false,数值为 0,字符串为 ""。零值规则

go 复制代码
var enabled bool // false
var count int    // 0
var title string // ""

true 和 false 用来表达真假。严格来说,它们是预声明的布尔常量,Go 规范并没有单独定义布尔字面量。布尔类型规范

4. 数值的不同写法

整数:进制不同,值可以相同

进制 写法 十进制值
十进制 26 26
二进制 0b11010 26
八进制 0o32 26
十六进制 0x1A 26

旧式八进制也可以用前导 0 表示,所以 032 是十进制的 26,不是 32。新代码用 0o 更容易辨认。整数字面量规范

浮点数与复数:先认识常用形式

go 复制代码
3.25     // 小数
3.25e2   // 3.25 × 10²,即 325
3.25e-2  // 3.25 × 10⁻²,即 0.0325
2 + 3i   // 实部为 2,虚部为 3 的复数表达式

十六进制浮点数较少使用,只需记住指数用 p,底数为 2;例如 0x1p-3 等于 1/8。3i 是虚部字面量,而 2 + 3i 整体是表达式。浮点数、虚部字面量

浮点变量不一定精确保存十进制小数,float64 比 float32 精度更高,也不意味着所有小数都能精确表示。

下划线:只增加可读性

1_000_000 与 1000000 相同。下划线通常放在数字之间,不能连续或放在末尾;_1000 则是标识符,不是数值字面量。分段规则

5. rune 与字符串

单引号表示码点,双引号表示字符串

go 复制代码
ch := '好'   // 默认类型为 rune,即 int32
text := "好" // string

rune 保存的是 Unicode 码点的整数编号;string 保存字节序列。'好' 与 "好" 不是同一种值,'你好' 也不是合法的 rune 字面量。

go 复制代码
fmt.Println(len("好"))         // 3 个 UTF-8 字节
fmt.Println(len([]rune("好"))) // 1 个码点

字节数、码点数和人眼看到的字符数需要区分。 一些显示字符由多个码点组成,因此码点数也不总等于显示字符数。Go 字符串常用于 UTF-8 文本,但也允许保存任意字节,不能断言所有字符串都是合法 UTF-8。Go 官方字符串讲解

双引号解释转义,反引号保留原始内容

go 复制代码
message := "第一行\n第二行" // \n 变成换行
path := `C:\notes\go`       // 反斜杠不需要转义

双引号中的 \n、\t、\\、\" 分别表示换行、制表符、反斜杠和双引号;反引号不处理这些转义,可以直接书写多行内容,但不能在内部包含反引号。字符串字面量规范

6. 字面量与目标类型

一个数值字面量能否赋给变量,要看其值能否由目标类型表示,不能只看有没有小数点。

go 复制代码
var whole int = 5.0      // 可以:值恰好是整数 5
var fraction int = 5.2   // 编译错误:不是整数
var small uint8 = 300    // 编译错误:超过 255

上面的错误行用于说明规则,实际运行时应删除或注释。整数目标不允许把常量的小数部分舍掉;浮点目标允许按精度舍入,但不能溢出。常量可表示性规则

字面量常量与已经定型的变量,赋值规则不同。

go 复制代码
value := 5.0         // 推断为 float64
// var n int = value // 错误:不能直接把 float64 变量赋给 int
n := int(value)      // 显式转换

转换非整数的浮点变量时,int(value) 会截掉小数部分,而不是四舍五入。数值转换规则

理解这一页的关键,是把"类型身份""实际数值"和"代码写法"分别看清:不同进制可以表达同一个值,同一常量可以适用于多种目标类型,但已经定型的变量并不会自动变成另一种类型。

四、常量与变量

1. 常量与变量的区别

变量保存程序运行中可以修改的值,常量表示编译阶段就能确定、不能重新赋值的值。两者都可以用名字表达含义,减少没有解释的数字或字符串。原文

go 复制代码
const MaxRetries = 3
var retries = 0
retries++
// MaxRetries = 5 // 错误:不能修改常量

Go 的常量只涉及布尔、数值和字符串值,不能用 const 声明切片或映射。普通函数调用即使总返回相同结果,也不能用于常量初始化;常量表达式和满足特定条件的内置函数可以,例如 const Size = len("Go")。常量规范

2. 常量类型与类型推断

未定类型常量可以适配上下文,变量必须有确定类型。

go 复制代码
const Flexible = 12
const Fixed int = 12

var a int32 = Flexible // 可以:值在 int32 范围内
var b float64 = Flexible
// var c int32 = Fixed // 错误:Fixed 已经是 int 类型
var c int32 = int32(Fixed)

"未定类型"不代表任意值都可以转换,只是尚未固定到具体类型;数值范围及可表示性仍然限制它。常量声明没有显式类型,也不保证未定类型,例如 const X = int64(12) 就是已定类型常量。Go 官方常量讲解

变量声明省略类型时:初始化值已经定型,就沿用该类型;否则使用未定类型值的默认类型。

初始化值 推断出的变量类型
12 int
12.0 float64
'好' rune,即 int32
2i complex128
true bool
"Go" string
int64(12) int64

因此 const N = 12 没有固定具体类型,var n = N 却创建了一个 int 变量。nil 没有默认类型,不能单独用 x := nil 推断变量类型。变量声明规则

常量计算与变量计算也有区别。 常量表达式在编译时求值;例如 const Sum = 0.1 + 0.2 保留精确的常量结果,赋给浮点变量时才按目标精度舍入。未定类型数值常量也可以大于默认类型的范围,但放进具体类型的变量时仍必须能被表示。常量表达式规则

3. 变量声明与赋值

go 复制代码
var count int = 3 // 类型、初始值都指定
var score = 90   // 从初始值推断类型
var total int    // 使用零值 0
name := "Go"     // 简短声明,只能在函数内

使用 var 时,类型和初始值至少要提供一个。变量可以修改值,但类型不会因为重新赋值而改变。变量声明规范

go 复制代码
left, right := 3, 8
left, right = right, left // 交换:得到 8、3

多值赋值先取得右侧的值,再赋给左侧,所以能直接交换变量。Go 不支持 left = right = 8 这样的连等赋值。赋值规则

局部变量声明后需要被使用,只给它反复赋值不算读取;包级变量没有相同的未使用限制。调试时可以用 _ = count 显式忽略值,正式代码中没有用途的变量应删除。

4. 短声明与作用域

同一代码块中,:= 至少要声明一个新的非空变量。

go 复制代码
count := 1
count, label := 2, "Go" // count 沿用原变量,label 是新变量
count = 3              // 只修改已有变量,用 =
// count := 4          // 错误:没有新变量
// count, _ := 4, 0    // 错误:_ 不算新变量

沿用的变量不会改变类型。:= 左侧是变量名,不能把字段访问或索引表达式当作要声明的名字。短声明规则

内层代码块的同名声明,会遮蔽外层变量。

go 复制代码
count := 1
{
    count := 2
    fmt.Println(count) // 2:内层的新变量
}
fmt.Println(count)     // 1:外层变量没有改变

内层若写 count = 2,则是修改能访问到的外层变量。判断一行 := 在做什么,必须结合它所在的代码块,而不能只看名字是否曾经出现过。重声明与赋值

5. 常量组与 iota

常量可以成组声明。第一项之后省略类型与表达式时,会重复前一个完整声明项的类型和表达式:

go 复制代码
const (
    First = 7
    Second // 同样是 7,不会自动变成 8
)

iota 则表示当前 const 声明中的常量声明项序号,从 0 开始;配合表达式重复,可以生成状态编号:

go 复制代码
const (
    Pending = iota // 0
    Running        // 1
    Finished       // 2
)

这里重复的是表达式 iota,不是上一次计算出的数值。每个新的 const 声明重新从 0 计数;没有使用 iota 的声明项以及 _ 占位项也占序号,同一声明项内出现多次 iota 时取值相同。它按声明项计数,不按空行或调用次数计数。常量组与 iota

也可以重复其他表达式,例如 1 << iota 依次生成 1、2、4,适合表示不同的权限位,不必手工列出每个值。

6. 显式转换的边界

类型转换写成 T(value)。常量转换要求结果能被目标类型表示;变量转换可能丢失精度或截掉高位。

go 复制代码
// int(3.8) // 编译错误:常量不能舍掉小数部分
value := 3.8
whole := int(value) // 3:浮点变量转整数,向零截断

var wide uint16 = 300
small := uint8(wide) // 44:只保留低 8 位
// uint8(300)       // 编译错误:常量超出 uint8 范围

因此,"显式转换能编译"不等于"转换后数值保持不变"。不同的基本数值类型也不能仅因为数值一样就直接相互赋值。数值转换规则

整数转字符串不是数字格式化。 string(65) 得到 Unicode 码点 65 对应的 "A";要得到十进制文本 "65",使用 strconv.Itoa(65),并导入 strconv 包。整数到字符串转换、Itoa 文档

五、运算符

1. 算术运算与类型

运算符对值进行计算或判断:a + b 需要两个操作数,是二元运算;-a、!ok 只需要一个操作数,是一元运算。本页关注基本类型上的算术、位运算、比较、逻辑和字符串拼接。原文

运算符 含义 示例
+、-、* 加、减、乘 3 + 2 得到 5
/ 除法 7 / 2 得到 3
% 余数,只用于整数 7 % 2 得到 1

整数除法向零截断,非零余数的符号跟随被除数。 -7 / 2 是 -3,-7 % 2 是 -1;需要小数结果时,应在除法发生前转为浮点数。

go 复制代码
numerator, denominator := 7, 2
fmt.Println(float64(numerator) / float64(denominator)) // 3.5
fmt.Println(float64(numerator / denominator))         // 3:小数早已丢失

常量除数为 0 会导致编译错误,整数变量除数在运行时为 0 会引发 panic。整数运算规则

普通数值运算不会自动混合不同类型的变量。 int 与 int64 相加需要先统一类型;未定类型常量则可以在能够被表示时适配变量类型,例如 int64 变量加常量 1。操作数类型规则

2. 位运算与移位

位运算按整数的二进制位处理数据。以 a = 0b1100、b = 0b1010 为例:

运算符 规则 结果
a & b 两位都为 1,才保留 1 0b1000,即 8
`a b` 至少一位为 1,就得到 1
a ^ b 两位不同,才得到 1 0b0110,即 6
a &^ b 清除 a 中与 b 的 1 对应的位 0b0100,即 4
a << 1 左移一位,低位补 0 0b11000,即 24
a >> 1 右移一位,丢掉最低位 0b0110,即 6

^ 有两个用途,都不是乘方。 a ^ b 是异或,^a 是按位取反。取反结果与位宽、是否有符号有关,例如 ^uint8(0) 是 255,而 ^0 是 -1。Go 没有乘方运算符,需要时使用 math.Pow 等函数。算术运算规范

&^ 常用于清除标志位:若权限的低三位是 111,清除 010 后得到 101。它等价于 a & (^b)。

移位次数必须非负;常量负数不能编译,运行时出现负数会 panic。移位左右操作数的整数类型可以不同。无符号数右移高位补 0,有符号数右移保留符号,因此不能把负数右移简单当成整数除法:-7 >> 1 是 -4,而 -7 / 2 是 -3。

3. 比较与逻辑判断

比较使用 ==、!=、<、<=、>、>=,结果表示真假。布尔和复数支持相等、不等比较,但不支持大小排序;整数、浮点数和字符串可以比较大小。比较规则

字符串按字节顺序比较,不按数值大小或自然语言习惯排序。 因此 "10" < "2" 为 true。浮点数的相等比较有确定语义,但计算误差可能使结果不符合直觉;需要近似相等时,应按业务精度设置误差标准。

逻辑运算 含义
a && b 两个条件都成立
`a
!a 取反

&& 和 || 会短路。 && 左边为 false 时不计算右边;|| 左边为 true 时不计算右边。

go 复制代码
numerator, denominator := 12, 0
ok := denominator != 0 && numerator/denominator > 2
fmt.Println(ok) // false;右侧除法没有执行

把保护条件写在左边,可以避免右边进行非法操作,但右边的代码仍然必须通过编译时检查。逻辑运算规范

4. 拼接与更新变量

字符串使用 + 拼接,也支持 +=:

go 复制代码
message := "Hello"
message += ", Go" // 得到 "Hello, Go"

它不会自动把数字转成文本,"age=" + 18 不合法,数字格式化应另行处理。

count += 2 表示将 count 加 2 后写回;*=、/=、%=、<<= 等同样用于更新变量。复合赋值的左侧只求值一次。赋值规则

go 复制代码
count := 1
count++ // 变成 2
count-- // 回到 1

++、-- 是语句,不产生可供其他表达式使用的结果。 只能后置,不能写 ++count,也不能写 value := count++。自增与自减

5. 优先级与括号

二元运算符按下表从高到低结合,同级从左向右结合;一元运算符优先于二元运算符。优先级规范

优先级 运算符
5,最高 * / % << >> & &^
4 `+ -
3 == != < <= > >=
2 &&
1,最低 `

移位比加减法优先,这是与一些语言不同的地方。

go 复制代码
1 << 2 + 1   // (1 << 2) + 1,结果为 5
1 << (2 + 1) // 结果为 8

括号还可以强调逻辑分组,例如 isAdmin || (isMember && paid)。混合多种运算时,直接写清分组通常比依赖读者记住优先级更容易理解。

6. 常量与变量运算的差别

常量表达式先按自己的规则计算,并不会因为最终需要浮点数就跳过整数除法。

go 复制代码
const A = 3 / 2 * 0.1 // 先做整数除法,结果为 0.1
const B = 0.1 * 3 / 2 // 先得到浮点常量 0.3,结果为 0.15

窄整数变量的运算可能回绕,已定类型常量溢出则不能编译。

go 复制代码
var top uint8 = 255
fmt.Println(top + 1) // 0,结果仍为 uint8
// const Bad = uint8(255) + 1 // 编译错误

移位时也要看左操作数最终采用的类型:

go 复制代码
const fixed = 8
var dynamic = 8
var a uint8 = 1 << fixed / 128   // 2:先完成常量计算
var b uint8 = 1 << dynamic / 128 // 0:uint8 的 1 左移时已丢失高位

若需要先在较宽的类型中计算,再转换到窄类型,应明确写出较宽类型,例如 uint64(1) << dynamic。理解复杂表达式时,除了运算符,还要一起检查操作数类型、是否为常量以及中间结果。移位规则、整数溢出规则

相关推荐
量化分析码农35 分钟前
【Python量化波动率实战 02】波动率会传染:EWMA 指数加权给近期行情更大话语权
后端
冻柠檬飞冰走茶43 分钟前
字母移动游戏题解:贪心求最长可保留连续子串(附 C 实现)
c语言·开发语言·数据结构·算法·动态规划
FRing031 小时前
vue2干货总结7(自定义指令、插槽、单页应用程序、路由的使用(VueRouter))
开发语言·前端·vue.js·vue2
雪落漂泊1 小时前
C++算法 二分查找篇(上)
开发语言·c++·算法
aicat_cn1 小时前
json-render 接入 Chat SDK:Agent 在 Slack 和 Teams 里直接回表格和按钮
ai·大模型
GISer_Jing1 小时前
如何学习Agent
后端·langchain
波力海苔夹心脆6752 小时前
C# WPF 资源字典详解:ResourceDictionary、MergedDictionaries 与自定义控件案例
开发语言·c#·wpf·入门教程·resource·资源字典
不会算法的笨小孩2 小时前
异常的使用以及会出现的问题
开发语言·c++·c++11·异常
JWASX3 小时前
【Go】map 源码解析 - group
后端·golang·map·源码分析
量化分析码农4 小时前
【Python量化波动率实战 01】波动率是怎么算的:年化、滚动与区间估计一次讲清
后端