Недавно я собираюсь написать несколько технических постов в блоге о golang. Эта статья представляет собой технический перевод golang, который я уже видел на GitHub. Он кажется очень полезным. Позвольте мне сначала поделиться им с читателями.
предисловие
Go — простой и интересный язык программирования, как и другие языки, он неизбежно столкнется со многими подводными камнями при использовании, но большинство из них не являются недостатками дизайна самого Go. Если вы только что перешли на Go с другого языка, то на большинство ям в этой статье наступят.
Если вы потратите время на изучение официального документа, вики,список рассылки обсуждений,Rob PikeБольшое количество статей и исходный код Go, вы обнаружите, что ямы в этой статье очень распространены, и новичок, пропуская эти ямы, может сэкономить много времени на отладке кода.
Начинающие: 1-35
1. Левая скоба{Обычно не в одну строку
В большинстве других языков{Место зависит от вас. Go является особенным и следует правилу автоматической инъекции точки с запятой: компилятор добавит определенный разделитель в конце каждой строки кода.;для разделения нескольких операторов, например, в)Добавьте точку с запятой после:
// 错误示例
func main()
{
println("hello world")
}
// 等效于
func main(); // 无函数体
{
println("hello world")
}
./main.go: missing function body
./main.go: syntax error: unexpected semicolon or newline before {
// 正确示例
func main() {
println("hello world")
}
Обратите внимание на особые случаи, такие как блоки кода:
// { 并不遵守分号注入规则,不会在其后边自动加分,此时可换行
func main() {
{
println("hello world")
}
}
Ссылаться на:Специальный разделитель для автоматической точки с запятой в Golang
2. Неиспользуемые переменные
Если в коде тела функции есть неиспользуемые переменные, она не скомпилируется, но глобальные переменные, объявленные, но не используемые, в порядке.
Даже если переменная объявлена и ей присвоено значение, она все равно не скомпилируется, ее нужно где-то использовать:
// 错误示例
var gvar int // 全局变量,声明不使用也可以
func main() {
var one int // error: one declared and not used
two := 2 // error: two declared and not used
var three int // error: three declared and not used
three = 3
}
// 正确示例
// 可以直接注释或移除未使用的变量
func main() {
var one int
_ = one
two := 2
println(two)
var three int
one = three
var four int
four = four
}
3. Неиспользованный импорт
Если вы импортируете пакет, но ни одна из переменных, функций, интерфейсов и структур пакета не используется, компиляция завершится ошибкой.
можно использовать_Символ подчеркивания используется как псевдоним для игнорирования импортированных пакетов, что позволяет избежать ошибок компиляции.init()
// 错误示例
import (
"fmt" // imported and not used: "fmt"
"log" // imported and not used: "log"
"time" // imported and not used: "time"
)
func main() {
}
// 正确示例
// 可以使用 goimports 工具来注释或移除未使用到的包
import (
_ "fmt"
"log"
"time"
)
func main() {
_ = log.Println
_ = time.Now
}
4. Коротко объявленные переменные можно использовать только внутри функций
// 错误示例
myvar := 1 // syntax error: non-declaration statement outside function body
func main() {
}
// 正确示例
var myvar = 1
func main() {
}
5. Используйте короткие объявления, чтобы повторять объявления переменных
Невозможно повторить объявление переменной только с коротким объявлением,:=Слева есть по крайней мере одна новая переменная, позволяющая повторять объявления нескольких переменных:
// 错误示例
func main() {
one := 0
one := 1 // error: no new variables on left side of :=
}
// 正确示例
func main() {
one := 0
one, two := 1, 2 // two 是新变量,允许 one 的重复声明。比如 error 处理经常用同名变量 err
one, two = two, one // 交换两个变量值的简写
}
6. Вы не можете использовать короткое объявление для установки значения поля
Невозможно использовать переменные поля структуры:=чтобы назначить использование предопределенной переменной, чтобы избежать разрешения:
// 错误示例
type info struct {
result int
}
func work() (int, error) {
return 3, nil
}
func main() {
var data info
data.result, err := work() // error: non-name data.result on left side of :=
fmt.Printf("info: %+v\n", data)
}
// 正确示例
func main() {
var data info
var err error // err 需要预声明
data.result, err = work()
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("info: %+v\n", data)
}
7. Случайное переопределение переменной
Для разработчиков, переключающихся с динамических языков, хорошо работают короткие объявления, которые могут ввести в заблуждение.:=является оператором присваивания.
Если вы неправильно используете новый блок кода, как показано ниже:=, компилируется без ошибок, но переменные работают не так, как вы ожидаете:
func main() {
x := 1
println(x) // 1
{
println(x) // 1
x := 2
println(x) // 2 // 新的 x 变量的作用域只在代码块内部
}
println(x) // 1
}
Это распространенная ошибка разработчиков Go, и ее нелегко обнаружить.
быть пригодным для использованияvetИнструменты для диагностики такого покрытия переменных, Go не выполняет проверку покрытия по умолчанию, добавьте-shadowвозможность включить:
> go tool vet -shadow main.go
main.go:9: declaration of "x" shadows declaration at main.go:5
Обратите внимание, что vet не будет сообщать обо всех переопределенных переменных, вы можете использоватьgo-nyetДля дальнейшего тестирования:
> $GOPATH/bin/go-nyet main.go
main.go:10:3:Shadowing variable `x`
8. Переменные явного типа нельзя инициализировать nil
nilявляется начальным значением по умолчанию для переменных типа интерфейса, функции, указателя, карты, среза и канала. Однако без указания типа при объявлении компилятор не может вывести конкретный тип переменной.
// 错误示例
func main() {
var x = nil // error: use of untyped nil
_ = x
}
// 正确示例
func main() {
var x interface{} = nil
_ = x
}
9. Используйте срез и карту напрямую с нулевым значением
Позволяет добавлять элементы в срез с нулевым значением, но добавление элемента в карту с нулевым значением вызовет панику во время выполнения.
// map 错误示例
func main() {
var m map[string]int
m["one"] = 1 // error: panic: assignment to entry in nil map
// m := make(map[string]int)// map 的正确声明,分配了实际的内存
}
// slice 正确示例
func main() {
var s []int
s = append(s, 1)
}
10. емкость карты
Емкость можно указать при создании переменной типа map, но ее нельзя использовать как срезcap()Чтобы определить размер выделенного пространства:
// 错误示例
func main() {
m := make(map[string]int, 99)
println(cap(m)) // error: invalid argument m1 (type map[string]int) for cap
}
11. Значение переменной типа string не может быть нулевым
для тех, кто любит использоватьnilДля тех, кто инициализирует строки, это яма:
// 错误示例
func main() {
var s string = nil // cannot use nil as type string in assignment
if s == nil { // invalid operation: s == nil (mismatched types string and nil)
s = "default"
}
}
// 正确示例
func main() {
var s string // 字符串类型的零值是空串 ""
if s == "" {
s = "default"
}
}
12. Значение типа массива как параметр функции
В C/C++ массивы (имена) являются указателями. При передаче массива в качестве параметра в функцию это эквивалентно передаче ссылки на адрес памяти массива, а значение массива будет изменено внутри функции.
В Go массивы — это значения. При передаче в функцию в качестве параметра передается копия исходного значения массива, и в это время массив не может быть обновлен внутри функции:
// 数组使用值拷贝传参
func main() {
x := [3]int{1,2,3}
func(arr [3]int) {
arr[0] = 7
fmt.Println(arr) // [7 2 3]
}(x)
fmt.Println(x) // [1 2 3] // 并不是你以为的 [7 2 3]
}
Если вы хотите изменить массив параметров:
- Передайте тип указателя непосредственно в этот массив:
// 传址会修改原数据
func main() {
x := [3]int{1,2,3}
func(arr *[3]int) {
(*arr)[0] = 7
fmt.Println(arr) // &[7 2 3]
}(&x)
fmt.Println(x) // [7 2 3]
}
- Использовать срез напрямую: даже если функция получает копию значения среза, она все равно обновляет исходные данные среза (базовый массив).
// 会修改 slice 的底层 array,从而修改 slice
func main() {
x := []int{1, 2, 3}
func(arr []int) {
arr[0] = 7
fmt.Println(x) // [7 2 3]
}(x)
fmt.Println(x) // [7 2 3]
}
13. Диапазон пересекает срез и массив, запутывая возвращаемое значение
с другими языками программированияfor-in,foreachОператоры обхода в Go разные.rangeПри обходе генерируются 2 значения, первое — это индекс элемента, а второе — значение элемента:
// 错误示例
func main() {
x := []string{"a", "b", "c"}
for v := range x {
fmt.Println(v) // 1 2 3
}
}
// 正确示例
func main() {
x := []string{"a", "b", "c"}
for _, v := range x { // 使用 _ 丢弃索引
fmt.Println(v)
}
}
14. Срезы и массивы на самом деле являются одномерными данными
Похоже, что Go поддерживает многомерные массивы и слайсы, вы можете создавать массивы из массивов, слайсы из слайсов, но это не так.
Для приложений, которые полагаются на динамические вычисления значений многомерных массивов, реализация в Go не идеальна с точки зрения производительности и сложности.
Динамические многомерные массивы могут быть созданы с использованием примитивных одномерных массивов, «независимых» срезов и срезов «общего базового массива».
-
Используйте исходный 1D-массив: выполняйте проверки индексов, обнаружение переполнения и перераспределение при добавлении значений, когда массив заполнен.
-
Использование «независимых» срезов выполняется в два этапа:
- Создать внешний срез
-
выделение памяти для каждого внутреннего слайса
Обратите внимание, что внутренние срезы не зависят друг от друга, так что любой внутренний срез не повлияет на другие срезы.
// 使用各自独立的 6 个 slice 来创建 [2][3] 的动态多维数组
func main() {
x := 2
y := 4
table := make([][]int, x)
for i := range table {
table[i] = make([]int, y)
}
}
- Нарезка с использованием «общего базового массива»
- Создайте фрагмент контейнера, содержащий необработанные данные
- создать другие фрагменты
- Вырежьте исходный фрагмент, чтобы инициализировать другие фрагменты
func main() {
h, w := 2, 4
raw := make([]int, h*w)
for i := range raw {
raw[i] = i
}
// 初始化原始 slice
fmt.Println(raw, &raw[4]) // [0 1 2 3 4 5 6 7] 0xc420012120
table := make([][]int, h)
for i := range table {
// 等间距切割原始 slice,创建动态多维数组 table
// 0: raw[0*4: 0*4 + 4]
// 1: raw[1*4: 1*4 + 4]
table[i] = raw[i*w : i*w + w]
}
fmt.Println(table, &table[1][0]) // [[0 1 2 3] [4 5 6 7]] 0xc420012120
}
Дополнительные ссылки на многомерные массивы
go-how-is-two-dimensional-arrays-memory-representation
what-is-a-concise-way-to-create-a-2d-slice-in-go
15. Доступ к ключу, которого нет на карте
Как и в других языках программирования, ожидается, что он вернет nil, если будет осуществлен доступ к ключу, которого нет в карте, например, в PHP:
> php -r '$v = ["x"=>1, "y"=>2]; @var_dump($v["z"]);'
NULL
Go вернет нулевое значение соответствующего типа данных элемента, напримерnil,'',falseИ 0, операция значения всегда возвращает значение, поэтому невозможно судить о том, находится ли ключ в карте по полученному значению.
Проверьте, существует ли ключ и доступен ли он напрямую с помощью карты, просто проверьте второй возвращаемый параметр:
// 错误的 key 检测方式
func main() {
x := map[string]string{"one": "2", "two": "", "three": "3"}
if v := x["two"]; v == "" {
fmt.Println("key two is no entry") // 键 two 存不存在都会返回的空字符串
}
}
// 正确示例
func main() {
x := map[string]string{"one": "2", "two": "", "three": "3"}
if _, ok := x["two"]; !ok {
fmt.Println("key two is no entry")
}
}
16. Значение типа string является постоянным и не может быть изменено
Попытка пройти по строке с использованием индекса для обновления отдельных символов в строке не допускается.
Значение типа string представляет собой двоичный фрагмент байта, доступный только для чтения.Если вы действительно хотите изменить символы в строке, преобразуйте строку в []byte и измените ее, а затем преобразуйте в строку:
// 修改字符串的错误示例
func main() {
x := "text"
x[0] = "T" // error: cannot assign to x[0]
fmt.Println(x)
}
// 修改示例
func main() {
x := "text"
xBytes := []byte(x)
xBytes[0] = 'T' // 注意此时的 T 是 rune 类型
x = string(xBytes)
fmt.Println(x) // Text
}
Уведомление:Вышеприведенный пример не является правильной позицией для обновления строки, потому что символ в кодировке UTF8 может занимать несколько байтов, например, китайские символы, для хранения которых требуется 3–4 байта, и неправильно обновлять один из байтов в этот момент. время. .
Правильная поза для обновления строки: преобразовать строку в фрагмент руны (одна руна в это время может занимать несколько байтов) и напрямую обновить символы в руне
func main() {
x := "text"
xRunes := []rune(x)
xRunes[0] = '我'
x = string(xRunes)
fmt.Println(x) // 我ext
}
17. Преобразование между строкой и байтовым срезом
При преобразовании между строками и фрагментами байтов в преобразовании участвует исходное значение копии. Этот процесс преобразования отличается от операций приведения типов в других языках программирования, а также отличается от того факта, что новый слайс разделяет базовый массив со старым слайсом.
Go оптимизирует два момента в преобразовании между строкой и байтовым срезом, избегая дополнительного выделения памяти:
- существует
map[string]При поиске ключа в соответствующий[]byte, избегать делатьm[string(key)]выделение памяти - использовать
for rangeПеребрать итерируемую строку, преобразованную в []byte:for i,v := range []byte(str) {...}
туман:обратитесь к исходному тексту
Эта статья воспроизведена изGitHub.com/no-yin/blog/…