C++无痛转go第一天,从hello world到切片
文章目录
-
- 1. Go 程序的最小结构
- 2. 输出:Println、Printf、Sprintf
- 3. 读取配置:环境变量更像后端写法
- 4. 变量声明:Go 不喜欢隐式和模糊
- 5. 类型转换:Go 不自动帮你转
- 6. 字符串:byte、rune、len
- 7. if:条件不用括号,可以带短语句
- 8. switch:默认 break,比 C++ 安全
- 9. for:Go 只有 for
- 10. 多行切片最后为什么要逗号?
- 11. 数组:定长,长度是类型的一部分
- 12. 切片:不是数组,但可以先当“变长数组”理解
- 13. nil 切片和 make
- 14. 二维切片:`[][]int`
- 15. copy 字符串:目标应该是 `[]byte`
- 16. 字符串切片表达式:左闭右开
- 17. 今天犯的错误汇总
- 18. C++ 转 Go 第一天的核心感受
这篇文章基于我今天写的代码整理,包含 hello world、输入输出、变量、字符串、条件、循环、数组、切片,以及我今天踩过的坑
1. Go 程序的最小结构
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
核心就三个东西:
package main:声明当前文件属于main包main包可以编译成可执行程序import "fmt":导入格式化输出包,类似 C++ 的#include <iostream>,但 Go 的 import 是包导入,不是文本展开func main():程序入口
注意:package main 必须是文件第一条非注释语句,import 必须在函数声明前面
我今天犯过的错是把 package main 和 import "fmt" 写到了函数后面,这是不合法的
错误:
func str() {
}
package main
import "fmt"
正确:
package main
import "fmt"
func str() {
}
本质:Go 编译器必须先知道这个文件属于哪个包,才能继续解析后面的代码
2. 输出:Println、Printf、Sprintf
Println
fmt.Println("hello")
fmt.Println("go", "backend", 8080)
Println 会自动换行,多个参数之间自动加空格
Printf
model := "Openai"
qps := 200
enabled := true
fmt.Printf("model: %s\nqps: %d\nenabled: %t\n", model, qps, enabled)
常用格式:
| 格式 | 含义 |
|---|---|
%s |
字符串 |
%d |
整数 |
%t |
bool |
%f |
浮点数 |
%v |
通用格式 |
%T |
打印类型 |
%q |
带引号字符串 |
%c |
字符 |
Sprintf
msg := fmt.Sprintf("service: %s, port: %d", "ai-backend", 8090)
fmt.Println(msg)
Sprintf 不直接输出,而是生成字符串
3. 读取配置:环境变量更像后端写法
命令行输入可以用 fmt.Scanln,但后端服务更常见的是读环境变量:
package main
import (
"fmt"
"os"
)
func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
fmt.Println("port =", port)
}
这个逻辑很常用:优先读环境变量,没有就给默认值
4. 变量声明:Go 不喜欢隐式和模糊
几种写法:
var age int = 20
var name = "Vect"
hobby := "hiking"
说明:
var age int = 20:显式声明类型var name = "Vect":自动类型推导hobby := "hiking":短变量声明,只能在函数内部使用
一次声明多个变量:
var a1, a2, a3 int = 1, 2, 3
零值
Go 变量只声明不赋值,也有默认值:
var cnt int16
var name string
var ok bool
fmt.Printf("count=%d\n", cnt)
fmt.Printf("name=%q\n", name)
fmt.Printf("ok=%v\n", ok)
输出:
count=0
name=""
ok=false
C++ 局部变量不初始化可能是随机值,Go 不会 Go 会给零值
常见零值:
| 类型 | 零值 |
|---|---|
| int / float | 0 |
| string | "" |
| bool | false |
| pointer / slice / map | nil |
5. 类型转换:Go 不自动帮你转
var a int16 = 10
var b int32 = 20
fmt.Println(int32(a) + b)
必须写 int32(a)
下面这样不行:
fmt.Println(a + b)
因为 a 是 int16,b 是 int32,类型不同
C++ 里经常自动类型提升,Go 不这么干 Go 的态度是:不同类型就是不同类型,想算就明确转换
Go 讨厌隐藏行为,强调代码显式、清楚
6. 字符串:byte、rune、len
这是今天最容易踩坑的地方
len 返回字节数,不是字符数
s := "Go语言"
fmt.Println(len(s))
结果是 8,不是 4
原因:
G:1 字节o:1 字节语:UTF-8 下 3 字节言:UTF-8 下 3 字节
所以总共 1 + 1 + 3 + 3 = 8
s[0] 取的是 byte
s := "golong"
fmt.Println("first byte: ", s[0])
输出:
first byte: 103
为什么不是 g?
因为 s[0] 取的是第 0 个字节,类型是 byte,本质是 uint8 字符 g 的 ASCII / UTF-8 编码值就是 103
如果想按字符输出:
fmt.Printf("first byte: %c\n", s[0])
range 字符串拿到的是 rune
for index, r := range s {
fmt.Printf("index=%d rune=%c\n", index, r)
}
index是字节下标r是 rune,也就是 Unicode 码点
总结:
| 写法 | 含义 |
|---|---|
len(s) |
字节数 |
s[i] |
第 i 个字节,byte |
range s |
按 rune 遍历字符串 |
处理中文时,不能随便用 s[i] 当字符
7. if:条件不用括号,可以带短语句
基础写法:
num := 100
if num <= 80 {
fmt.Println("low")
} else {
fmt.Println("high")
}
Go 的 if 条件不需要小括号
还可以在 if 前写短语句:
if score := 92; score >= 90 {
fmt.Println("优秀")
} else {
fmt.Println("平常")
}
这里 score 只在这个 if-else 内部有效
这个设计很适合临时变量,避免污染外层作用域
8. switch:默认 break,比 C++ 安全
status := "running"
switch status {
case "queued":
fmt.Println("waiting")
case "running":
fmt.Println("processing")
case "done":
fmt.Println("finished")
default:
fmt.Println("unknown")
}
Go 的 switch-case 默认不会向下穿透,不需要手写 break
C++ 如果忘了 break,会继续执行后面的 case;Go 默认不会
多个 case 可以合并:
switch status {
case "created", "queued":
fmt.Println("not started")
case "running":
fmt.Println("running")
case "done", "failed":
fmt.Println("finished")
}
9. for:Go 只有 for
Go 没有 while,也没有 do while,只有 for
基础 for:
for i := 0; i < 3; i++ {
fmt.Printf("%d ", i)
}
while 写法:
retry := 0
for retry < 3 {
fmt.Println("retry", retry)
retry++
}
死循环:
for {
fmt.Println("running")
}
range 遍历:
models := []string{
"qwen",
"openai",
"claude",
"deepseek",
}
for index, model := range models {
fmt.Println(index, model)
}
如果不要下标,用 _ 忽略:
for _, model := range models {
fmt.Println(model)
}
10. 多行切片最后为什么要逗号?
正确写法:
models := []string{
"qwen",
"openai",
"claude",
"deepseek",
}
最后一个元素后面也要有逗号
如果写成多行,最后一个元素后面必须保留逗号,原因是 Go 会在行尾自动插入分号,如果没有逗号,编译器会把最后一个元素那一行当成语句结束,导致复合字面量解析失败
单行可以不写最后逗号:
models := []string{"qwen", "openai", "claude", "deepseek"}
多行保留最后逗号还有一个好处:以后追加元素时,只新增一行,不用修改上一行
11. 数组:定长,长度是类型的一部分
var nums [3]int = [3]int{8080, 8081, 8082}
fmt.Println(nums)
也可以写:
nums := [3]int{8080, 8081, 8082}
Go 数组是定长的,[3]int 和 [4]int 是两种不同类型
这一点和 C++ 数组有点像,长度固定 但 Go 实际开发里更常用的是切片
12. 切片:不是数组,但可以先当“变长数组”理解
models := []string{"qwen", "openai"}
models = append(models, "llama")
fmt.Println(models)
fmt.Println("len ", len(models))
[]string 里面没有长度,所以这是切片
数组:
[3]string
切片:
[]string
初学时可以把切片理解成“变长数组”,但本质上切片不是数组本身
切片底层大概由三部分组成:
指向底层数组的指针 + 长度 len + 容量 cap
所以切片更像是对底层数组某一段的描述
append 要接收返回值:
models = append(models, "llama")
不要只写:
append(models, "llama")
因为 append 可能会返回新的切片,不接住就丢了
13. nil 切片和 make
var s []string
fmt.Println("uninit:", s, s == nil, len(s) == 0)
这里 s 是 nil 切片
它满足:
s == nil
len(s) == 0
然后用 make 创建切片:
s = make([]string, 3)
fmt.Println("emp:", s, "len: ", len(s), "cap:", cap(s))
make([]string, 3) 表示创建长度为 3 的字符串切片 里面每个元素是 string 的零值,也就是空字符串
len 和 cap:
| 名称 | 含义 |
|---|---|
len |
当前切片长度,可以访问的元素个数 |
cap |
从切片起点到底层数组末尾的容量 |
14. 二维切片:[][]int
func slice3() {
t := make([][]int, 3)
for i := 0; i < 3; i++ {
rowLen := i + 1
t[i] = make([]int, rowLen)
for j := 0; j < rowLen; j++ {
t[i][j] = i + j
}
}
fmt.Println("二维切片: ", t)
}
输出大概是:
二维切片: [[0] [1 2] [2 3 4]]
这里严格来说不是二维数组,而是二维切片
数组写法:
[3][4]int
切片写法:
[][]int
二维切片的每一行长度可以不同,所以它可以表示不规则矩阵
另外,不建议把变量命名为 len:
len := i + 1
虽然能写,但会遮蔽 Go 内置函数 len 更好的名字是:
rowLen := i + 1
15. copy 字符串:目标应该是 []byte
我一开始写过类似这样的代码:
s := "hello, golong"
str := make([]string, len(s))
copy(str, s)
这是错的
原因:
s是stringstr是[]stringcopy不支持copy([]string, string)
字符串本质是字节序列,所以如果要复制字符串内容,目标应该是 []byte:
s := "hello, golong"
str := make([]byte, len(s))
copy(str, s)
fmt.Println("copy: ", str)
输出的是字节值:
copy: [104 101 108 108 111 44 32 103 111 108 111 110 103]
如果想重新看成字符串:
fmt.Println(string(str))
16. 字符串切片表达式:左闭右开
s := "hello, golong"
l := s[2:5]
fmt.Println("sl1: ", l)
l = s[:5]
fmt.Println("sl2: ", l)
l = s[2:]
fmt.Println("sl3: ", l)
切片规则是左闭右开:
s[low:high]
包含 low,不包含 high
字符串下标:
h e l l o , g o l o n g
0 1 2 3 4 5 6 7 8 9 10 11 12
所以:
s[2:5]
取到的是下标 2、3、4,也就是:
llo
但是要注意,字符串切片是按字节切,不是按字符切 英文没问题,中文要小心
如果要安全处理中文,建议转成 []rune:
s := "你好"
r := []rune(s)
fmt.Println(string(r[:1]))
17. 今天犯的错误汇总
1. package main 和 import 写错位置
本质:Go 文件必须先声明包,再导入包,再写函数
2. s[0] 输出 103
本质:s[0] 是 byte,不是字符 g 的编码值是 103 想输出字符用 %c
3. 多行切片最后一个元素漏逗号
本质:Go 会自动插入分号,多行复合字面量最后要保留逗号
4. fmt.Println("二维数组: " t) 少逗号
本质:函数多个参数之间必须用逗号隔开
正确:
fmt.Println("二维切片: ", t)
5. copy([]string, string) 类型错
本质:字符串是字节序列,要复制到 []byte,不是 []string
6. 把二维切片叫成二维数组
本质:[][]int 是二维切片,[3][4]int 才是二维数组
7. 用 len 当变量名
本质:会遮蔽内置函数 len,不推荐
18. C++ 转 Go 第一天的核心感受
Go像是,那晚C和Python都喝多了的样子
Go 很克制:
- 没有隐式类型转换
- 只有
for switch默认不穿透- 未使用变量会报错
- 包结构必须清晰
- 格式基本交给
gofmt - 数组和切片分得很清楚
Go 的目标不是让语法多炫,而是让工程代码统一、清楚、好维护
感觉学了C++,后面啥语言都清爽好多,世人苦C++久矣 😭
第一天最该记住的本质:
- Go 文件从
package开始 - 可执行程序是
package main+func main() :=很常用,但只能在函数内- Go 有零值,不会出现 C++ 未初始化局部变量那种随机值
- Go 不喜欢隐式转换,不同类型要显式转
- 字符串是字节序列,
len返回字节数 s[i]是 byte,range s是 rune- Go 只有
for - 数组
[N]T定长,切片[]T动态 - 切片本质是底层数组的视图,包含指针、长度、容量
- 多行字面量最后保留逗号
- 中文字符串处理要注意 UTF-8,必要时用
[]rune
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)