Базовые конструкции языка Go¶
«Теперь забудь, что ты вояка, теперь ты дитя малое — по шажку, по шажку учись ходить; сначала с костыликами, потом по стеночкам, потом с палочкой».
Борис Полевой, «Повесть о настоящем человеке».
Материал написана по мотивам главы «Basics» руководства «A Tour of Go».
Код в Go должен быть внутри пакета (package). Даже программа из одного файла должна быть внутри пакета. Внутри пакета создается общая область видимости, что позволяет удобно разбивать проект на отдельные файлы.
Имена пакетам принято давать только в нижнем регистре, camelCase не приветствуется; snake_case или kebab-case также не рекомендуются, но можно использовать аббревиатуры, вроде syncio или inputcash.
Функции
package main
import "fmt"
func mul(x int, y int) int {
return x * y
}
func main() {
fmt.Println(mul(64, 42))
}
2688
Если несколько входных параметров функции одинакового типа, то тип можно указать только один раз.
package main
import "fmt"
func add(x, y int) int {
return x + y
}
func main() {
fmt.Println(add(64, 128))
}
192
Функция может возвращать несколько значений, которые можно поименовать. Если возвращаемые переменные поименованы, то можно их вернуть просто при помощи простого «return», без указания имён (попробуйте модифицировать код, показанный ниже). Но этой возможностью лучше не злоупотреблять, так как она делает код менее читаемым, а можно и не пользоваться вовсе.
package main
import (
"fmt"
"math"
)
// Функция возвращает корни квадратного уравнения. Не работает для комплексно-сопряжённых корней
func quad(a, b, c float64) (x1, x2 float64) {
var D = b*b - 4*a*c
x1 = 2 * c / (-b + math.Sqrt(D))
x2 = 2 * c / (-b - math.Sqrt(D))
return x1, x2
}
func main() {
a, b := quad(2, -5, 3)
fmt.Println(a, b)
}
1 1.5
Переменные в Go можно инициализировать либо при помощи полного синтаксиса, либо сокращенно:
var f1 float64 = 1.0 // Полная запись
var f2 = 1.0 // Сокращенная запись
f3 := 1.0 // Сокращенная запись
Во втором случае компилятор сам выводит тип переменной. Сокращенная запись может использоваться только на уровне функции, но не на уровне пакета.
Переменные могут объявляться блоками:
var (
a bool = false
b uint32 = (1 << 32) & (1 << 16)
c float32 = 0.01
)
Типы Go:
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64
uintptr
byte // То же самое, что и uint8
rune // То же самое, что и int32, соответствует одному символу Unicode
float32 float64
complex64 complex128
Разрядность int, uint и uintptr зависит от разрядности системы, 32 бита для 32-х разрядных систем и 64 бита для 64-х разрядных.
Можно декларировать переменную без значения, тогда ей будет присвоено значение по умолчанию. Это будут, соответственно, false для bool, нули для численных переменных и пустая строка для string.
var (
boo bool
num uint
flo float32
str string
)
func main(){
fmt.Println(boo, num, flo, str)
}
false 0 0
Все преобразования типов, даже нисходящие, происходящие без потери точности, должны быть явными. Например, если вы попробуете удалить явное приведение float64() в коде, показанном ниже, то получите ошибку «cannot use (x * x + y * y) (value of type int) as float64 value in argument to math.Sqrt».
var x, y float32 = 3, 4
var f float64 = math.Sqrt(float64(x*x + y*y))
fmt.Println(x, y, f)
3 4 5
Константы определяются при помощи ключевого слова const. Если при определении константы опущен тип, то он выводится компилятором самостоятельно.
const j uint16 = 0
const k = 1.001
Циклы
package main
import "fmt"
func main() {
mul := 1
for i := 2; i < 10; i++ {
mul *= i
}
fmt.Println(mul)
}
362880
В Go нет цикла while, вместо него используется for с одним условием:
package main
import "fmt"
func main() {
mul := 2
for mul < 10_000 {
mul *= mul
}
fmt.Println(mul)
}
65536
Бесконечный цикл тоже организуется при помощи for:
package main
func main() {
for {
}
}
Условное ветвление
package main
import "fmt"
func detect(x float64) string {
s := ""
if x >= 0 {
s = "Positive"
} else {
s = "Negative"
}
return s
}
func main() {
fmt.Println(detect(2), detect(-2))
}
Positive Negative
Оператор case
В отличие от других языков, в Golang оператор break не нужен.
package main
import "fmt"
func detect(x uint) string {
s := ""
switch x {
case 0:
s = "Zero"
case 1:
s = "One"
default:
s = "More"
}
return s
}
func main() {
fmt.Println(detect(0), detect(1), detect(100))
}
Zero One More
При помощи пустого switch можно организовать компактный и аккуратный аналог множественного выбора if-elif-elif.
package main
import (
"fmt"
"strconv"
)
func fizzbuzz(n int) string {
s := ""
switch {
case n%15 == 0:
s = "FizzBuzz"
case n%5 == 0:
s = "Buzz"
case n%3 == 0:
s = "Fizz"
default:
s = strconv.Itoa(n)
}
return s
}
func main() {
fmt.Println(fizzbuzz(3), fizzbuzz(5), fizzbuzz(15), fizzbuzz(16), fizzbuzz(17))
}
Fizz Buzz FizzBuzz 16 17
Defer
Оператор defer откладывает выполнение помеченной функции до тех пор, пока окружающая функция не достигнет точки выхода, при этом аргументы для помеченной функции будут вычислены сразу, в момент вызова. Множественные вызовы defer стекируются по принципу LIFO.
package main
import "fmt"
func mem(n int) int {
return n
}
func main() {
for i := 0; i < 6; i++ {
fmt.Println(mem(i))
defer fmt.Println(mem(i))
}
fmt.Println("Bye")
}
0
1
2
3
4
5
Bye
5
4
3
2
1
0