On branch main
modified: src/ch01.md new file: src/ex-ch01-04.zig new file: src/ex-ch01-05.zig
This commit is contained in:
182
src/ch01.md
182
src/ch01.md
@@ -53,7 +53,7 @@ zig run ex-ch01-01.zig
|
||||
и базируется на 2-х вещах
|
||||
|
||||
* `@import` - это встроенная в компилятор функция
|
||||
* `pub` - ключевое слово для экспортирования чего-то
|
||||
* `pub` - ключевое слово для экспортирования различных определений
|
||||
|
||||
При импортировании модуля нужно указать его имя. Стандартная библиотека Zig доступна
|
||||
по имени `std`. Чтобы импортировать какой-то конкретный файл, нужно использовать
|
||||
@@ -492,3 +492,183 @@ pub fn main() void {
|
||||
"бинарные" данные или строка в кодировке UTF-8. Как бы то ни было,
|
||||
для всех случаев используется один и тот же тип, как для собственно строк,
|
||||
так и для всего прочего.
|
||||
|
||||
Из того, что мы уже знаем про массивы и срезы, мы могли бы заключить,
|
||||
что `[]const u8` это срез неизменяемого массива байтов, беззнаковых
|
||||
8-ми битных целых чисел. Но при инициализации строк мы нигде никакие
|
||||
массивы не использовали, мы просто писали `.name = "Пётр".` Как это работает?
|
||||
|
||||
Длины строковых констант (strings literals, буквальные значения)
|
||||
известны во время компиляции. Компилятор знает, что строка `"Пётр"` имеет
|
||||
длину 8 байт (в UTF-8 на каждую букву кириллицы идёт 2 байта),
|
||||
так что мы можем предположить, что тип этой константы будет `[8]const u8`.
|
||||
Однако, строковые константы обладают парочкой особых свойств, а именно,
|
||||
для их хранения в исполнимом файле отведено специальное место,
|
||||
при этом дубликаты исключаются. Поэтому переменная, через которую
|
||||
доступна та или иная строковая константа, по идее, должна быть указателем
|
||||
в это специальное место. Это означает, что тип строки `"Пётр"` это скорее
|
||||
указатель вида `*const [8]u8`, то есть указатель на неизменяемый массив
|
||||
длиной 8 байт.
|
||||
|
||||
Но это ещё не всё. Строковые константы заканчиваются бинарным нулём (как в C).
|
||||
Собственно, так сделано как раз ради "взаимодействия" с кодом, уже
|
||||
когда-то написанным на языке C. Строка `"Пётр"` в памяти будет выглядеть
|
||||
как `{0xD0, 0x9F, 0xD1, 0x91, 0xD1, 0x82, 0xD1, 0x80, 0x00}` и тогда,
|
||||
возможно, вы подумаете, что её тип это `*const [9]u8`, Но это в лучшем
|
||||
случае будет неоднозначным, а в худшем - даже опасным. Поэтому для представления
|
||||
строк с признаком конца в виде `\0` (null-terminated strings) в Zig есть
|
||||
специальный синтаксис - вообщем, строка `"Пётр"` имеет тип `*const [8:0]u8`,
|
||||
то есть указатель на массив из 8-ми байт с дополнительным нулём на конце.
|
||||
Тут мы невольно сделали акцент именно на C-подобные строки, однако,
|
||||
этот синтаксис более общий - [LENGTH:SENTINEL], где `SENTINEL` это специальное значение,
|
||||
служащее для обозначения конца массива. Вот странный пример с непонятным
|
||||
потенциальным применением, но тем не менее, он вполне корректен с точки
|
||||
зрения Zig:
|
||||
|
||||
```zig
|
||||
const std = @import("std");
|
||||
|
||||
pub fn main() void {
|
||||
// массив из 3 булевских значений с false на конце
|
||||
const a = [3:false]bool{false, true, false};
|
||||
|
||||
// эта строка более сложная, объясняться не будет
|
||||
std.debug.print("{any}\n", .{std.mem.asBytes(&a).*});
|
||||
}
|
||||
```
|
||||
|
||||
Работает, как и задумано (то есть признак конца тоже печатается):
|
||||
|
||||
```
|
||||
$ /opt/zig-0.11/zig run src/ex-ch01-04.zig
|
||||
{ 0, 1, 0, 0 }
|
||||
```
|
||||
|
||||
Весьма вероятно, что у Вас остался ещё один вопрос. Если `"Пётр"` это
|
||||
`*const [8:0]u8`, а поле `name` это `[]const u8`, то как так получается.
|
||||
что мы свободно присваиваем первое второму? Ответ простой - в этом случае
|
||||
Zig делает приведение типов (coercion) автоматически. Это означает,
|
||||
что если функция имеет параметр с типом `[]const u8` или поле структуры
|
||||
имеет типа `[]const u8`, то при передаче параметра или приваивании значения
|
||||
полю можно использовать строковые конатанты. Приведение типов в этом
|
||||
случае не требует больших временных затрат, поскольку строки с нулём
|
||||
на конце это массивы, длина их известна на этапе компиляции, поэтому
|
||||
не нужно проходить по всей строке в поисках признака конца.
|
||||
|
||||
Итак, когда мы говорим о строках, то обычно мы имеем ввиду `[]const u8`.
|
||||
Когда необходимо, мы явно указываем, что строка в стиле C, то есть
|
||||
с нулём на конце и помним, что она автоматически приводится к `[]const u8`.
|
||||
Но опять же, мы помним, что `[]const u8` используется также для
|
||||
произвольных бинарных данных и поэтому в Zig нет специального типа для строк.
|
||||
|
||||
Конечно, в "настоящих" программах большинство строк/массивов неизвестны
|
||||
во время компиляции - данные могут поступать от пользователя, из сети и т.п.
|
||||
К этому вопросу мы ещё вернёмся, когда будем рассматривать работу
|
||||
с оперативной памятью. А сейчас мы пока скажем следующее - для
|
||||
такого рода данных, которые возникают лишь в процессе работы программы,
|
||||
память выделяется динамически. При этом наши переменные всё равно будут
|
||||
иметь тип `[]u8` и указатель в этих срезах будет показывать на динамически
|
||||
(то есть в процессе выполнения программы) выделенную память.
|
||||
|
||||
## comptime и anytype
|
||||
|
||||
В нашем примере осталась ещё одна совершенно не исследованная строчка:
|
||||
|
||||
```zig
|
||||
std.debug.print("{s} обладает силой {d}\n", .{user.name, user.power});
|
||||
```
|
||||
Тут происходит намного больше, чем может показаться на первый взгляд.
|
||||
За этой с виду простой строчкой скрываются 2 из более мощных
|
||||
возможностей Zig, о которых нужно иметь представление, даже
|
||||
если Вы пока ещё не овладели ими в совершенстве.
|
||||
|
||||
Первая из них это выполнение кода во время компиляции, которое
|
||||
обозначается ключевым словом `comptime`. Это основа для метапрограммирования
|
||||
в Zig и, как подразумевает само название, речь идёт о выполнении кода
|
||||
во время компиляции, а не во время исполнения уже откомпилированной прораммы.
|
||||
На протяжении этой книги мы лишь слегка коснёмся этой темы,
|
||||
но надо понимать, что эта особенность всегда незримо присутствует.
|
||||
|
||||
Вам уже, наверное, не терпится спросить, а какое отношение к этому
|
||||
имеет та самая строчка? Давайте посмотрим на сигнатуру функции `print`:
|
||||
|
||||
```zig
|
||||
// обратите внимание на 'comptime' перед первым аргументом
|
||||
pub fn print(comptime fmt: []const u8, args: anytype) void {
|
||||
```
|
||||
|
||||
Ключевое слово `comptime` перед `fmt` означает, что строка формата
|
||||
должна быть известна во время компиляции. Причиной для этого является
|
||||
то, что `print` во время компиляции делает различные проверки.
|
||||
Что это за проверки? Ну, скажем, Вы поменяли формат на `"it's over {d}\n"`,
|
||||
но при этом оставили 2 аргумента. Тогда Вы получите ошибку компиляции.
|
||||
Также делается проверка на соответствие типов: измените формат на
|
||||
`"{s}'s power is {s}\n"` и Вы тоже получите ошибку про несоответствие
|
||||
`u64` спецификатору `{s}`. Подобного рода проверки были бы невозможны
|
||||
во время компиляции, если бы строка формата была бы неизвестна во время
|
||||
этой самой компиляции, отсюда такое требование для параметра `fmt`.
|
||||
|
||||
Одна из ситуаций, где Вы сразу же столкнётесь с `comtime` это
|
||||
типы по умолчанию для целочисленных констант и констант с плавающей точкой -
|
||||
`comptime_int` и `comptime_float`. Например, строчка `var i = 0;`
|
||||
не является корректной, компилятор в это месте скажет, что
|
||||
"переменная типа `comptime_int` должна быть `const` или `comptime`".
|
||||
Любой код, который помечен как `comptime`, может работать с данными,
|
||||
известными во время компиляции и, что касается целых и вещественных чисел,
|
||||
такие данные имеют специальные типы, `comptime_int` и `comptime_float`,
|
||||
соответственно. Однако, вряд ли Вы будете большую часть времени писать
|
||||
код, предназначенный для выполнения во время компиляции, так что
|
||||
это не особо полезное умолчание. То, что Вам нужно, это просто явно задать типы,
|
||||
например
|
||||
|
||||
```zig
|
||||
var i: usize = 0;
|
||||
var j: f64 = 0;
|
||||
```
|
||||
|
||||
Отметим также, что если бы мы написали `const i = 0` (а не `var i = 0`),
|
||||
то ошибки бы не было, поскольку вся её (ошибки) суть в том, что
|
||||
"переменные" типа `comptime_int` _должны_ быть константой.
|
||||
|
||||
В одной из следующих глав мы познакомимся с `comptime` поближе,
|
||||
когда будем изучать обобщённые структуры данных ("generics").
|
||||
|
||||
Ещё одна особенная вещь, которая имеется в нашей строчке, это
|
||||
вот это странное `{user.name, user.power}`, которое, как следует
|
||||
из сигнатуры `print`, передаётся как `anytype`. Этот тип не следует
|
||||
путать с чем-то вроде `Object` из Java или `any` из Go. Здесь
|
||||
дело обстоит так - во время компиляции Zig сгенерирует специализированные
|
||||
варинаты `print` для всех типов, которые Вы передавали `print` в Вашей
|
||||
программе.
|
||||
|
||||
Тут возникает вопрос - а _что_, собственно, мы передаём в функцию `print`?
|
||||
Мы уже видели подобную запись, когда немного говорили об автоматическом
|
||||
выведении типов. Здесь ситуация похожая, но тут такая запись создаёт
|
||||
анонимную структуру. Запустите вот такой код:
|
||||
|
||||
```zig
|
||||
const std = @import("std");
|
||||
pub fn main() void {
|
||||
std.debug.print("{any}\n", .{@TypeOf(.{.year = 2023, .month = 8})});
|
||||
}
|
||||
```
|
||||
|
||||
Он напечатает
|
||||
|
||||
```
|
||||
struct{comptime year: comptime_int = 2023, comptime month: comptime_int = 8}
|
||||
```
|
||||
|
||||
Тут мы дали полям анонимной структуры имена (`year` и `month`).
|
||||
В изначальном варианте мы этого не делали, просто указывали значения.
|
||||
В таких случаях имена полей генерируются автоматически, они будут
|
||||
такие - `"0"`, `"1"`, `"2"` и т.д. Функция `print` ожидает структуру
|
||||
с такими полями и использует позиции спецификаторов в строке формата
|
||||
для получения соответствующего аргумента.
|
||||
|
||||
В Zig нет перегрузки функций ("function overloading"), а также нет
|
||||
функций с переменным числом аргументов ("variadic functions").
|
||||
Вместо этого компилятор Zig умеет создавать специализированные варианты
|
||||
функций, основываясь на передаваемых в "универсальный" вариант типах,
|
||||
как выведенных, так и созданных самим компилятором.
|
||||
|
||||
|
||||
10
src/ex-ch01-04.zig
Normal file
10
src/ex-ch01-04.zig
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
pub fn main() void {
|
||||
// an array of 3 booleans with false as the sentinel value
|
||||
const a = [3:false]bool{false, true, false};
|
||||
|
||||
// This line is more advanced, and is not going to get explained!
|
||||
std.debug.print("{any}\n", .{std.mem.asBytes(&a).*});
|
||||
}
|
||||
6
src/ex-ch01-05.zig
Normal file
6
src/ex-ch01-05.zig
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
pub fn main() void {
|
||||
std.debug.print("{any}\n", .{@TypeOf(.{.year = 2023, .month = 8})});
|
||||
}
|
||||
Reference in New Issue
Block a user