Конверсии в Go
Бывают случаи, когда желательно преобразовать значение в другой тип. Голанг не позволяет делать это произвольно. Это определенные правила, соблюдаемые системой типов. В этой истории мы увидим, какие виды конверсии возможны, какие нет и когда конверсии действительно полезны.
Go - это строго типизированный язык программирования. Когда речь идет о типах, он строг, и во время компиляции будут сообщаться об ошибках:
package main import "fmt" func main() { monster := 1 + "2" fmt.Printf("monster: %v\n", monster) } > go build # github.com/mlowicki/lab ./lab.go:6: cannot convert "2" to type int ./lab.go:6: invalid operation: 1 + "2" (mismatched types int and string)
JavaScript - один из слабо (слабо) типизированных языков. Давайте посмотрим, как это работает:
var monster = 1 + "foo" + function() {}; console.info("type:", typeof monster) console.info("value:", monster);
Сложение числа, строки и даже функции может показаться странным. Не беспокойтесь, JavaScript сделает это за вас без нареканий:
type: string value: 1foofunction () {}
При определенных обстоятельствах может возникнуть необходимость преобразовать значение в другой тип, например f.ex. передать его как аргумент или, возможно, поместить в выражение:
func f(text string) { fmt.Println(text) } func main() { f(string(65)) // integer constant converted to string }
Выражение из вызова функции - это пример преобразования, и мы увидим больше из них ниже. Вышеупомянутый код работает нормально, но без преобразования:
f(65)
вызывает ошибку времени компиляции: «невозможно использовать 65 (тип int) в качестве строки типа в аргументе для f».
Базовый тип
Базовый тип - строка, логический, числовой или буквальный тип - это один и тот же тип. В противном случае это базовый тип из объявления типа:
type A string // string type B A // string type C map[string]float64 // map[string]float64 (type literal) type D C // map[string]float64 type E *D // *D
(базовые типы помещаются внутри комментариев)
Если базовые типы совпадают, тогда преобразование действительно на 100%:
package main type A string type B A type C map[string]float64 type C2 map[string]float64 type D C func main() { var a A = "a" var b B = "b" c := make(C) c2 := make(C2) d := make(D) a = A(b) b = B(a) c = C(c2) c = C(d) var _ map[string]float64 = map[string]float64(c) }
Ничто не мешает компилировать такую программу. Определение базовых типов не рекурсивно:
type S string type T map[S]float64 ... var _ map[string]float64 = make(T)
поскольку он дает ошибку времени компиляции:
cannot use make(T) (type T) as type map[string]float64 in assignment
Это происходит потому, что базовым типом T является не map [string] float64, а map [S] float64. Конверсия тоже не будет работать:
var _ map[string]float64 = (map[string]float64)(make(T))
Вызывает ошибку при построении:
cannot convert make(T) (type T) to type map[string]float64
Возможность присвоения
В спецификациях Go используется термин возможность назначения. Он определяет, когда значение v может быть присвоено переменной типа T. Давайте посмотрим на его одно правило в действии, которое говорит об одних и тех же базовых типах, если хотя бы один тип не назван:
package main import "fmt" func f(n [2]int) { fmt.Println(n) } type T [2]int func main() { var v T f(v) }
Эта программа печатает «[0 0]». Преобразование возможно во всех случаях, разрешенных законом о переуступке. Таким образом, программист может явно отметить свое сознательное решение:
f([2]int(v))
Использование вышеуказанного вызова дает тот же результат, что и раньше. Подробнее о возможности назначения в предыдущем посте.
Первое правило преобразования (один и тот же базовый тип) перекрывается с одним из правил присваивания - когда базовые типы одинаковы и хотя бы один тип не имеет имени (первый пример в этом разделе). Более слабое правило затмевает более строгое. Итак, наконец, при преобразовании должны быть одинаковыми только базовые типы - именованный / безымянный тип не имеет значения.
Константы
Константу v можно преобразовать в тип T, когда v можно представить значением типа T:
a := uint32(1<<32 – 1) //b := uint32(1 << 32) // constant 4294967296 overflows uint32 c := float32(3.4e38) //d := float32(3.4e39) // constant 3.4e+39 overflows float32 e := string("foo") //f := uint32(1.1) // constant 1.1 truncated to integer g := bool(true) //h := bool(1) // convert 1 (type int) to type bool i := rune('ł') j := complex128(0.0 + 1.0i) k := string(65)
Подробное знакомство с константами доступно в официальном блоге.
Числовые типы
число с плавающей запятой → целое
var n float64 = 1.1 var m int64 = int64(n) fmt.Println(m)
Дробная часть удаляется, поэтому код печатает «1».
Для других преобразований:
- число с плавающей запятой → число с плавающей запятой,
- целое → целое,
- целое → число с плавающей запятой,
- сложный → сложный.
значение округляется с точностью до пункта назначения:
var a int64 = 2 << 60 var b int32 = int32(a) fmt.Println(a) // 2305843009213693952 fmt.Println(b) // 0 a = 2 << 30 b = int32(a) fmt.Println(a) // 2147483648 fmt.Println(b) // -2147483648 b = 2 << 10 a = int64(b) fmt.Println(a) // 2048 fmt.Println(b) // 2048
Указатели
Возможность присвоения работает с типами указателей так же, как и с литералами других типов:
package main import "fmt" type T *int64 func main() { var n int64 = 1 var m int64 = 2 var p T = &n var q *int64 = &m p = q fmt.Println(*p) }
Программа работает нормально и выводит «2», полагаясь на уже рассмотренное правило назначаемости. Базовые типы * int64 и T одинаковы, и, кроме того, * int64 не имеет имени. Конверсия добавляет больше возможностей. Для безымянных типов указателей достаточно, чтобы базовые типы указателей имели один и тот же базовый тип:
package main import "fmt" type T int64 type U W type W int64 func main() { var n T = 1 var m U = 2 var p *T = &n var q *U = &m p = (*T)(q) fmt.Println(*p) }
* T заключен в скобки, иначе он интерпретировался бы как * (T (q)) .
Так же, как и раньше, программа печатает «2». Базовые типы U и T одинаковы, и они используются в качестве базовых типов в * U и * T. Задание:
p = q
не будет работать, потому что он имеет дело с двумя разными базовыми типами: * T и * U. В качестве упражнения давайте рассмотрим, что произойдет, если мы немного изменим объявления:
type T *int64 type U *W type W int64 func main() { var n int64 = 1 var m W = 2 var p T = &n var q U = &m p = T(q) fmt.Println(*p) }
Декларации U и W были изменены. Подумайте на мгновение, что произойдет ...
Компилятор указывает на ошибку «не удается преобразовать q (тип U) в тип T»:
p = T(q)
Это потому, что базовым типом p типа является * int64, но для типа q это * W. Тип q имеет имя (U), поэтому правило о получении базового типа базового типа указателя здесь не применяется.
Струны
целое число → строка
Передача числа N во встроенную функцию string преобразует его в строку в кодировке UTF-8, содержащую символ, представленный N.
fmt.Printf("%s\n", string(65)) fmt.Printf("%s\n", string(322)) fmt.Printf("%s\n", string(123456)) fmt.Printf("%s\n", string(-1))
выход:
A ł � �
Два первых преобразования используют полностью допустимые кодовые точки. Вы можете задаться вопросом, что за странный символ отображается в последних двух строках. Это замещающий символ, который входит в блок Unicode, называемый specials. Его код \ uFFFD (подробнее).
Краткое введение в струнные
Строки - это в основном кусочки байтов:
text := "abł" for i := 0; i < len(text); i++ { fmt.Println(text[i]) }
выход:
97 98 197 130
97 и 98 - это символы «a» и «b» в кодировке UTF-8. 3-я и 4-я строки представляют собой кодировку буквы «ł», которая в UTF-8 занимает два байта.
Цикл диапазона помогает перебирать кодовые точки в соответствии с определением Unicode (кодовая точка в Go называется руной):
text := "abł" for _, s := range text { fmt.Printf("%q %#v\n", s, s) }
выход:
'a' 97 'b' 98 'ł' 322
Чтобы узнать больше о глаголах форматирования, таких как% q или% # v, см. Документацию пакета fmt.
Гораздо больше информации в Строки, байты, руны и символы в Go ». Преобразование между строками и кусочками байтов или рун больше не должно быть таким странным после этого краткого объяснения.
строка ↔ кусок байтов
bytes := []byte("abł") text := string(bytes) fmt.Printf("%#v\n", bytes) // []byte{0x61, 0x62, 0xc5, 0x82} fmt.Printf("%#v\n", text) // "abł"
Срез содержит байты преобразованной строки UTF-8.
нить ↔ кусочек рун
runes := []rune("abł") fmt.Printf("%#v\n", runes) // []int32{97, 98, 322} fmt.Printf("%+q\n", runes) // ['a' 'b' '\u0142'] fmt.Printf("%#v\n", string(runes)) // "abł"
Срез, созданный из преобразованной строки, содержит кодовые точки Unicode (руны).
Пожалуйста, подпишитесь на меня, если вам понравилась история, и вы хотите получать уведомления о новых. Помогите другим найти этот контент, нажав ❤ ниже.