
👨💻 关于作者:会编程的土豆
"不是因为看见希望才坚持,而是坚持了才看见希望。"
你好,我是会编程的土豆,一名热爱后端技术的Java学习者。
📚 正在更新中的专栏:
-
《数据结构与算法》😊😊😊
-
《leetcode hot 100》🥰🥰🥰🤩🤩🤩
-
《数据库mysql》
💕作者简介:后端学习者
作为一名有 C/C++ 基础的开发者,初学 Go 语言时最大的挑战不是"从零开始",而是"摆脱惯性思维"。本文记录了我从 C/C++ 转向 Go 过程中遇到的核心语法差异、思维转变和常见坑点,希望能帮助同样背景的读者快速上手。
1. 类型后置:Go 的"反直觉"语法
Go 和 C/C++ 最直观的区别就是类型声明的位置。
Go
// Go:变量名在前,类型在后
var age int
var name string
var scores []int
// C/C++:类型在前,变量名在后
int age;
string name;
vector<int> scores;
函数声明也是同理:
Go
// Go:参数类型后置,返回值类型在最后
func add(a int, b int) int {
return a + b
}
记住一句话:Go 里永远是 名字 类型 的顺序。
2. 变量声明:var vs :=
Go 有两种声明变量的方式:
Go
// 方式一:var 声明,可以只声明不赋值
var x int // x = 0(零值)
var name string // name = ""
// 方式二::= 短变量声明,必须声明+赋值同时进行
y := 10 // 自动推断类型为 int
name := "小明" // 自动推断为 string
关键区别:
-
:=只能在函数内部 使用,包级别必须用var -
:=约等于 C++ 的auto,但更简洁 -
Go 的分号由编译器自动插入,写代码时不需要分号
3. 数组 vs 切片:最容易混淆的概念
这是从 C/C++ 转 Go 最大的思维转变。
| 类型 | 写法 | 长度 | 类比 C++ |
|---|---|---|---|
| 数组 | [3]int |
固定 | int arr[3] |
| 切片(Slice) | []int |
动态 | vector<int> |
核心区别:数组的长度是类型的一部分。 [2]int 和 [3]int 是完全不同的类型,就像 int 和 string 的关系。
实际开发中 99% 的情况用切片:
Go
// 创建切片
s := make([]int, 0, 10) // len=0, cap=10
s = append(s, 1) // 类似 push_back
// 字面量创建(注意没有数字在括号里)
s := []int{1, 2, 3} // 这是切片
arr := [3]int{1, 2, 3} // 这是数组
4. make 函数:Go 的内存分配器
make 专门用来创建切片、map、channel 三种类型:
Go
make([]int, 长度) // 只指定长度
make([]int, 长度, 容量) // 指定长度和容量
len vs cap 的理解:
-
len:当前已有的元素个数 -
cap:底层数组的总容量(已用 + 预留) -
当
append超出cap时,Go 自动扩容(小容量翻倍,大容量缓慢增长)
Go
s := make([]int, 0, 3) // len=0, cap=3
s = append(s, 1, 2, 3) // len=3, cap=3
s = append(s, 4) // len=4, cap=6(自动翻倍)
5. 二维切片:外层和内层是独立的
Go
s := make([][]int, 1, 10)
// 外层:1 行,容量 10(可以动态扩容)
// 内层:s[0] 此时是 nil,需要单独初始化
s[0] = append(s[0], 1) // 给第 0 行加元素
s = append(s, []int{4, 5}) // 添加一整行
关键点: make([][]int, 1, 10) 中的 10 是行容量,每行的列数独立且无限。
6. 输入输出:告别 cin/cout
Go
// 输入
var a, b int
fmt.Scan(&a, &b) // 类似 cin >> a >> b
fmt.Scanf("%d %d\n", &a, &b) // 类似 scanf,注意 \n 吃掉换行符
// 输出
fmt.Println("hello") // 自动换行
fmt.Printf("%d %.1f", a, b) // 格式化输出,类似 printf
重要坑点: fmt.Scanf 容易在缓冲区残留换行符,导致下一次输入出错。建议:
-
纯数字输入用
fmt.Scan -
Scanf使用时在格式串末尾加\n -
读取带空格的字符串用
bufio.Scanner
7. 流程控制的两个死规则
规则 1:左括号必须同行
Go
// 正确
if x > 0 {
// ...
}
// 错误
if x > 0
{
// ...
}
规则 2:else 必须紧跟在 } 后
Go
// 正确
if x > 0 {
// ...
} else {
// ...
}
// 错误
if x > 0 {
// ...
}
else {
// ...
}
原因:Go 编译器会在行尾自动插入分号,换行会导致语法错误。
8. fmt.Printf vs fmt.Println
Go
// Printf:格式化输出,认识 %s %c %d %f 等占位符
fmt.Printf("%c%c%c\n", c, c, c)
// Println:原样输出,写什么就输出什么,自动换行
fmt.Println("hello")
9. 显式类型转换
Go 不允许隐式类型转换,不同类型运算必须显式转换:
Go
var n int = 3
var t float64 = 500.0
result := t / float64(n) // 显式转换
// result := t / n // 编译错误
这与 C/C++ 自动隐式转换完全不同,是 Go 设计哲学"明确胜过隐晦"的体现。
10. 常用标准库速查
| 包 | 功能 | 常用函数 |
|---|---|---|
fmt |
输入输出 | Scan, Printf, Println |
math |
数学运算 | Sqrt, Ceil, Floor, Pow |
sort |
排序 | Ints, Float64s, Strings, Slice |
strconv |
字符串转换 | Atoi, Itoa, ParseFloat |
strings |
字符串处理 | Split, Contains, Replace |
总结
从 C/C++ 转 Go 的核心思维转变:
-
类型后置 :
名字 类型的顺序 -
切片代替数组 :
[]int=vector<int> -
显式转换:不同类型不允许隐式转换
-
没有分号:编译器自动插入
-
括号必须同行:这是语法硬规定
-
make分配内存 :len是当前元素数,cap是总容量