第3章 Go 基础语法速览:变量与常量、数据类型、控制结构、函数与方法
好,咱们直接上手。国密 SDK 的适配代码说到底还是 Go 语言写的,你得先过了语法这一关才能跟 SM2、SM3 打交道。我带过不少新手,发现大家容易在变量声明、切片用法、以及闭包的坑里卡住。下面我把最核心的几个点串一遍,你跟着走就行。
3.1 变量与常量
Go 的变量声明有好几种方式,我个人最常用的是短声明 :=,省事。但要注意,:= 只能在函数内部用,包级别得用 var。
package main
import "fmt"
var version = "1.0.0" // 包级变量
func main() {
name := "国密SDK" // 短声明
var age int = 3 // 显示声明
fmt.Println(name, age)
}
小技巧:常量用
const 定义,iota 可以用来生成枚举值。我在写算法标识常量时经常用 iota,比如 SM2=0, SM3=1,后续加新算法自动递增。
3.2 基本数据类型
| 类型分类 | 具体类型 | 说明 |
|---|---|---|
| 布尔 | bool | 只有 true 和 false |
| 整数 | int, int8, int16, int32, int64 | 带符号;int 在 64 位机上就是 64 位 |
| 无符号整数 | uint, uint8…uint64 | 适合位运算和字节操作 |
| 浮点数 | float32, float64 | 国密算法里基本用 int 和 []byte |
| 字符串 | string | 不可变,UTF-8 编码 |
| 字节 | byte (即 uint8) | 处理二进制数据的主力 |
| 派生类型 | 数组、切片、map、struct、接口 | 稍后会细讲 |
讲个真实经验:当初适配 SM3 时,有人把 int 和 uint 混用做哈希长度计算,结果在 32 位和 64 位机器上行为不一致。记住:涉及密码学长度计算,尽量用 int 或明确大小的类型。
3.3 复杂数据类型:数组、切片与 map
数组 vs 切片
数组长度固定,切片更灵活。我几乎不用数组,全是切片。来看对比:
// 数组:长度固定
var arr [3]int = [3]int{1, 2, 3}
// 切片:可动态增长
s := []int{1, 2, 3}
s = append(s, 4) // ok
曾经踩过的坑:切片共享底层数组!如果你把一个切片传给函数并修改了元素,外面也会变。想要独立副本,老老实实用
copy()。
map
map 就是键值对,声明时记得初始化,否则会是 nil,写入会 panic。
m := make(map[string]int)
m["SM2"] = 1
v, ok := m["SM4"]
if !ok {
fmt.Println("key not found")
}
3.4 控制结构
Go 只有 for,没有 while。但你可以用 for 模拟一切。
// 标准 for
for i := 0; i < 10; i++ {
fmt.Println(i)
}
// 类似 while
n := 0
for n < 5 {
n++
}
// 无限循环
for {
// 常用在协程监听
}
if 和 switch 也很简洁。我最喜欢的是 switch 不需要 break,默认只命中一个分支。
alg := "SM2"
switch alg {
case "SM2":
fmt.Println("椭圆曲线签名")
case "SM3":
fmt.Println("哈希")
default:
fmt.Println("未知算法")
}
3.5 函数与方法
函数是一等公民,可以有多个返回值。这在国密适配里很常用——函数返回结果和错误。
func divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("除数不能为0")
}
return a / b, nil
}
方法就是带接收者的函数。想象成给类型附加行为。
type SM2Key struct {
D []byte
}
// 方法:接收者是 SM2Key 的指针
func (k *SM2Key) Sign(data []byte) ([]byte, error) {
// 签名逻辑…
return nil, nil
}
我自己的习惯:如果方法需要修改接收者,用指针;如果只是读取,用值。但为了统一,我基本都写指针,避免意外复制。
3.6 本章知识结构
下面我用一张图把这几个部分串起来,方便你整体把握。
3.7 避坑总结
- 变量声明:短声明
:=不能在包级别用,否则编译报错。 - 切片共享:函数内修改切片元素,外面也会变。可以用
copy产生独立数据。 - map 未初始化:写 nil map 会 panic,用
make创建。 - switch 默认穿透:不用写 break,但如果你想继续执行下一个 case,需要显式
fallthrough。
实用提示:在国密 SDK 里,你会经常看到 func (k *Key) Encrypt(data []byte) ([]byte, error) 这种模式。多个返回值的设计让错误处理非常干净,不用像 C 一样检查返回码。