Это статья об основах программирования на Go. На канале я рассказываю об опыте перехода в IT с нуля, структурирую информацию и делюсь мнением.
Хой, джедаи и амазонки!
Встречайте вашу любимую рубрику: синтез IT и социально-гуманитарных наук. Ладно-ладно, мою любимую рубрику. Рассмотрим типографские знаки * и &, называемые астериск и амперсанд: для чего их изобрели, когда, как они попали в сферу IT. А также код на Go с этими знаками и применим мнемонику для понимания связи символов и языка программирования. Это будет большая статья.
Такие знания расширяют общую инженерную насмотренность и помогают взглянуть на вещи с разных сторон:
Помните, как пел Вячеслав Бутусов в песне Тутанхамон, что правда всегда одна? Правды обычно минимум две.
1. Астериск
Звёздочка, или астери́ск - типографский знак в виде небольшой, обычно пяти- или шестиконечной звёздочки (*), расположенной в строке или поднятой над строкой.
Википедия вежливо предупреждает не путать этот термин с вымышленным персонажем Астериксом:
Далее представлены три глифа астериска в шрифтах (слева-направо) Felix Titling, Calibri и Arial Rounded MT Bold (шрифт - набор глифов, глиф - единица графики):
Астериск введён во II веке до н. э. в текстах Александрийской библиотеки (в Египте) античным филологом Аристофаном Византийским для обозначения неясностей: с этого момента в текстах * звёздочка указывала на сноску с пояснениями к тексту.
Аристофан Византийский в возрасте 62-65 лет начал заведовать Александрийской библиотекой. Был филологом, лексикографом и библиографом. Жил в период с 257 (или 260) до н.э. до около 180 до н.э. - вдумайтесь, до нашей эры он прожил 80 лет (по другим источникам 77 лет).
Аристофан Византийский внёс значимый вклад в античную филологию, например подготовил комментированное издание Гомера, впервые издал труды Платона. Не удивительно, что ему понадобился особый символ, чтобы указывать на непонятные в тексте вещи - чтобы делать сноски. Так появился астериск.
Человек, живший более двух тысячелетий назад, придумал знак, который имеет важную роль в Go в качестве указателя.
Также существует отдельный типографский символ из трёх звёздочек треугольником — астеризм (⁂). Обычно служит для привлечения внимания к повествованию или разделения подразделов либо эпизодов в книге.
2. Амперсанд
Амперса́нд (&) - логограмма, заменяющая союз «и». Возник как лигатура (объединение) буквосочетания et (с лат. - «и»).
Далее представлены три глифа амперсанда в шрифтах (слева-направо) Felix Titling, Arial Rounded MT Bold и Trebuchet MS, на котором отчётливо видно объединение буквосочетания et:
2.1. Появление графемы амперсанд
Марк Ту́ллий Тиро́н (также известный как Тирон) - придумывает графему амперсанд (графема - единица письменности). Как называлась эта графема - неясно, возможно и вовсе названия не имела.
Марк Туллий Тирон жил в Древнем Риме в период с около 103 до н.э. по 4 до н.э., т.е. прожил около 99 (!) лет.
Род деятельности Тирона:
- секретарь-референт;
- писатель;
- стенограф.
Тирон был рабом, смог получить образование и затем получил свободу. Неясно, каким образом стал свободным - либо накопил на выкуп (в т.ч., возможно, за счёт своей образованности) и сам себя выкупил, либо его освободили.
Считается, что Марк Туллий Тирон был другом другого Марка Туллия - Цицерона - римского государственного и политического деятеля. И записывал его изречения.
Благодаря Тирону до нас дошли изречения Цицерона, например:
- Каждый человек может заблуждаться, но упорствовать в заблуждении может только глупец.
- Жить — значит мыслить.
- Величайшее из достоинств оратора — не только сказать то, что нужно, но и не сказать того, что не нужно.
- Когда гремит оружие, законы молчат.
- Счастливая жизнь начинается со спокойствия ума.
- Иные думают, что старую любовь надо выбивать новой любовью, как клин клином.
- Если у тебя есть сад и библиотека, то у тебя есть все, что тебе нужно.
- Любовь к родителям — основа всех добродетелей.
- Их молчание — громкий крик.
- Что посеешь, то и пожнешь.
Чтобы поспевать за ходом мысли Цицерона и успевать быстро записывать, Тирон изобретает множество значков-сокращений, и в целом считается основателем скоростного конспектирования (стенографии).
Стеногра́фия - способ письма посредством особых знаков и ряда сокращений, дающий возможность быстро записывать устную речь.
Стенографическая запись превышает скорость обычного в 4-10 раз. Одним из значков-сокращений, изобретённых Тироном для записей мысли Цицерона, было объединение буквосочетания et в знак, близкий к &.
Впервые официальная запись с применением стенографии была осуществлена 5 декабря 63 года до н.э. на заседании Сената.
Следует отметить, что в Риме существовала традиционная система сокращений прописных букв, применяемая с VI века до н.э. для обозначения календарных дат, имен, юридических терминов и других подобных вещей.
В моём понимании, Тирон взял традиционную систему сокращений, систематизировал и дополнил собственными разработками. Синтез изобретательности Тирона и влияния Цицерона позволил закрепить за Тироном статус основателя стенографии.
2.2. Тироновы значки
До 1550 г. знаки Тирона почему-то назывались "значками" Цицерона или "значками" Сенеки. В 1550 г. французский адвокат Жак Гоори вводит термин "Тироновы значки" для обозначения системы стенографии.
До этого момента, стенография Тирона значительно усовершенствовалась, и к III веку включала примерно 5000 символов. С III-IV веков система стенографии Тирона применяется в канцеляриях, монастырях и при переписи книг. В рукописях IX-X веков насчитывается около 13 000 символов. Т.е. система развивалась и распространялась в других странах.
С середины XV века знак & активно используется в типографии, перейдя туда от переписчиков книг.
2.3. Алфавит
К концу XIX века, знак & встал на последнее место в букварях стран Европы и Северной Америки, поскольку стал привычной частью письма в качестве сокращения "и", т.е. and: вместо трёх букв, писали одну:
Когда ребята в школах заучивали алфавит, произносили следующее:
«X, Y, Z, and per se and» («„экс“, „уай“, „зед“ и сама по себе „и“»).
Per se с лат. - «сама по себе», «как таковая».
В 1837 году в словарях было зафиксировано слово ampersand, означающий графему &.
Такова история появления амперсанда. Графема & появилась две тысячи лет назад, а её название двести лет назад.
Сегодня амперсанд используется главным образом в двух направлениях:
- В IT;
- В обозначении брендов, например M&M’s или Johnson & Johnson.
Также следует отметить, что юникод (о нём я рассказывал здесь) содержит 10 разновидностей амперсанда: U+0026, U+214B, U+FE60, U+FF06, U+1F670, U+1F671, U+1F672, U+1F673, U+1F674, U+1F675. В программировании применяется первая разновидность с числовым кодом U+0026.
3. Астериск и амперсанд в IT
Сперва информация - как * и & используются в Go в общих чертах, затем немного истории о языках программировании и далее - код.
3.1. Общее применение & и * в Go
Я знаю о трёх видах применения астериска в Go:
- Оператор умножения;
- Указатель;
- Комментирование в сочетании со слэшем (косой чертой).
Первый и третий пункт, думаю, пояснений не требует.
Правда, комментирование с астериском редко используют, в IDE проще выделить фрагмент кода и закоментировать его сочетанием горячих клавишь Ctrl+/. Но есть и такой способ:
Второй же пункт для астериска - "Указатель" подразделяется ещё на два подпункта:
- Для определения типа указателя (*int, *string и т.д.);
- Для получения значения переменной, на которую ссылается указатель (*a = b).
Амперсанд в Go применяется для следующих целей:
- && - это логический оператор "и";
- & - взятие адреса переменной.
Адрес переменной нам нужен, например в функции считывания с консоли: fmt.Scan(&a); а также для создания указателя: b := &a, - синтаксис в этих конструкциях одинаков, применение - разное.
Итак, наша цель разобраться - как амперсанд и астериск появились в IT. Первое логичное объяснение, что на печатных машинках, клавиатура из которых перешла в IT, располагали минимально необходимым набором символов для того времени:
Как видим, вариантов немного. А применять буквосочетания или фразы для реализации требуемого функционала не хотелось - всегда хочется упростить жизнь, а не усложнить.
Подробнее об указателях и взятии адреса в Go, их взаимодействие с астериском и амперсандом поговорим далее на примере истории развития языков программирования.
3.2. История языков программирования
Астериск стал появляться в IT раньше амперсанда, и конечно первая его ипостась - в качестве умножения, а не указателя. Вопрос - с какого этапа это произошло и как это связано с Go?
Итак, первой по-настоящему программируемой вычислительной машиной была разработка немецкого инженера Конрада Цузе - Z1, изготовленная за счёт собственных средств в 1938 г. На тот момент Конраду 28 лет.
В ходе бомбардировок Берлина во время Второй мировой войны, машина и вся документация были уничтожены. И позднее воссозданы фирмой Siemens, при участии Конрада Цузе. Макет машины ниже:
Характеристика Z1:
- Тактовая частота 1 Гц;
- ОЗУ 0,17 КБ;
- Масса 500 кг.
Складывал Z1 за 1 секунду, умножал за 5 секунд. Ни о каком программировании речи пока не было. Информация вводилась с пишущей машинки. Выводилась с помощью панели из лампочек. Машина снабжалась устройством чтения перфокарт и работала от двигателя пылесоса.
Ниже можно посмотреть как выглядят перфокарты (в целом, не для Z1):
Машинный код в программировании
Затем вычислительная техника получила резкий скачок в развитии. Примерно так выглядели ЭВМ 1940-1950х гг: по-нынешним меркам громоздкие, неудобные, и на перфокартах:
Для передачи команд на них использовался бинарный код: наличие напряжения = 1, отсутствие напряжения = 0. Пока-что ни о каких знаках астериска говорить не приходится. Такие цепочки нулей и единиц называются машинным кодом.
Программы писать в таком режиме - скажем, так, трудно. И не только потому, что легко допустить опечатку, но и потому, что нужно хорошо знать архитектуру ЭВМ. Учёные и инженеры начали автоматизировать написание машинного кода.
Ассемблер
В 1949 году появился ассемблер - первый язык программирования низкого уровня. Низкий уровень означает близость "к железу", но что уже проще воспринимать человеку: для обозначения команд и объектов, над которыми эти команды выполняются, вместо двоичных кодов, в ассемблере использовались буквы или сокращенные слова, которые отражали суть команды.
В ассемблере, например, сложение обозначалось кодом ADD, а умножение MUL.
Ассемблер - это не один язык. Ассеблеров огромное количество.
С середины 1950х годов начинается создание первых языков программирования высокого уровня - удобных для работы программиста. Отличительная черта таких языков - они машинонезависимы, т.е. не привязаны к "железу" конкретной ЭВМ.
ФОРТРАН
В конце 1953 Джон Бэкус предложил руководству IBM начать разработку эффективной альтернативы ассемблеру для программирования на ЭВМ IBM 704.
В 1957 году компания IBM выпускает FORTRAN, разработанный под руководством Джона Бэкуса. На тот момент Джону 33 года.
Fortran до сих пор востребован в Data Sciensce. С момента издания вышло множество версий, улучшающих язык. И на момент изобретения, практически сразу, язык также стал чрезвычайно успешным.
С момента появления высокоуровневых языков - в программирование вошёл астериск в качестве оператора умножения.
АЛГОЛ
В Европе опасались доминирования американской фирмы IBM. Так, после встречи с 27 мая по 2 июня 1958 г. в Цюрихе с участием Ассоциации вычислительной техники (ACM) и немецкого общества прикладной математики и механики (Gesellschaft für Angewandte Mathematik und Mechanik), было решено создать европейский язык высокого уровня.
К концу этого же 1958 года был создан работающий компилятор ALGOL 58 для компьютера Z22 - над этим компьютером работал Конрад Цузе, это была его седьмая модель (ранее были изданы Z1, Z2, Z3, Z4, Z5 и Z11).
От АЛГОЛ 58 начинается прослеживаться нить, тянущаяся к Go.
Интересно, что одним из активных сторонников языка АЛГОЛ был Джон Бэкус - казалось, бы, зачем продвигать конкурента собственному языку ФОРТРАН?
В 1960 г. появился АЛГОЛ 60 стал стандартом для публикации алгоритмов среди учёных и инженеров Европы и США. Но не применялся в коммерческих целях.
Так, Сэр Чарльз Энтони Ричард Хоар - разработчик алгоритма быстрой сортировки - самого популярного алгоритма сортировки на данный момент (на момент разработки алгоритма Энтони Хоару было 26 лет), высказался о языке АЛГОЛ 60:
Этот язык настолько опередил свое время, что улучшил не только своих предшественников, но и почти всех своих преемников.
Tony Hoare remarked: "Here is a language so far ahead of its time that it was not only an improvement on its predecessors but also on nearly all its successors.
Язык АЛГОЛ 60 дал начало многим другим языкам программирования, включая CPL и Паскаль. Далее АЛГОЛ развивался своим путём, появилась версия АЛГОЛ 68, но нас интересует другое направление.
CLP
Язык программирования CLP - совместная разработка кембриджского и лондонского университетов, появился в 1963 г. Основной автор - Кристофер Стрейчи, на момент разработки языка ему было 47 лет. Другой автор - Дэвид Бэррон, ему было на тот момент было 28 лет.
CPL попытался выйти за рамки АЛГОЛА, включив в него управление производственными процессами, обработку бизнес-данных и некоторые другие вещи.
Считается, что CPL так и не был полностью реализован в 1960-х годах, существуя как теоретическая конструкция с некоторыми исследовательскими работами. Но его наследником стал язык BCPL и в этом заслуга языка. Но перед тем как перейти к BCLP, посмотрим на другой язык.
PL/I
ПЛ/1 или PL/I (произносится пи-эль-один), а расшифровывается, как "Язык программирования №1", разработан фирмой IBM в 1964 г. За основу взяты работы Фортрана, Алгола и Кобола.
Из-за сложности языка и реализации, не получил распространения.
В языке впервые появляется символика /* */ для комментирования кода - которая перекочевала в Go.
Для указателей же используется нотация «->».
Пример кода на PL/I:
BCPL
Интерпретируемый язык BCPL разработан Мартином Ричардсом в 1966 г. в возрасте 26 лет. Мартин Ричардс создал BCPL, удалив из языка CLP те функции, которые усложняли компиляцию. Первая реализация компилятора работала на IBM 7094 под операционной системой CTSS.
Ниже пример кода на BCLP для печати факториала:
GET "libhdr"
LET start() = VALOF
{ FOR i = 1 TO 5 DO writef("fact(%n) =%i4*n", i, fact(i))
RESULTIS 0
}
AND fact(n) = n=0 -> 1, n*fact(n-1)
Сейчас BCPL не используется, но в то время был важен благодаря портируемости. Урезанная версия языка BCPL с несколько изменённым синтаксисом, стала языком программирования B.
Ещё мы знаем, что астериск начинает применяться в BCLP в качестве указателя. Была ли звёздочка в CLP в качестве указателя - не могу сказать.
Язык B
На дворе был 1969 год - прошёл 31 год с момента появления первой программируемой вычислительной машиной Z1 в Германии.
26-летний Кен Томпсон и 28-летний Денис Ритчи, работая в лаборатории Белла (ныне финско-американская корпорация), в этот код делают несколько крупных разработок.
>>> На языке ассемблера пишут операционную систему (позже названная Unics, а ещё позднее - ставшая UNIX), для недорогого и мощного для своего времени мини-компьютера PDP-7 стоимостью 72000$. Ниже фото мини-компьютера:
Первая модель PDP-7 изготовлена в 1965 г. американской компанией Digital Equipment Corporation, было продано 120 экземпляров - и это считалось успешной моделью.
Готовился к производству новый более мощный мини-компьютер PDP-11, и для него нужна была своя операционная система, т.к. операционная система Unics не имела встроенного компилятора с языком высокого уровня.
>>> Считается, что именно для написания операционной системы Unix под PDP-11, Кен Томпсон и Денис Ритчи разрабатывают в том же 1969 г. интерпретируемый язык B (произносится - би) на основе BCPL. Отсюда и название языка - B: ребята взяли урезанную версию BCPL, хотя и добавили кое-что своё в синтаксис. Никакого языка A не было, если что :) По крайней мере в генеалогическом дереве Си-подобных языков.
Следует отметить, что для PDP-11 разработано несколько десятков операционных систем помимо Unix. Среди них DOS-11, ANDOS, и даже наши отечественные РАФОС, ДЕМОС (клон Unix), ИНМОС и другие.
Пример кодан на B:
main()
{
auto a, b, c, sum;
a = 1; b = 2; c = 3;
sum = a+b+c; putnumb(sum);
}
Уже напоминает Go, верно?
При этом в языке B имеется и * и появляется &. Много лет спустя, после разработки языка С, у Кена Томпсона спросили, связано ли близкое расположение * и & на клавиатуре для обозначения указателей в Go? Вот что он ответил:
From: Ken Thompson < ken@google.com >
near on the keyboard: no.
c copied from b so & and * are same there.
b got * from earlier languages - some assembly,
bcpl and i think pl/1.
i think that i used & because the name (ampersand)
sounds like "address." b was designed to be run with
a teletype model 33 teletype. (5 bit baud-o code)
so the use of symbols was restricted.
Что мы видим - астериск появляется в языке B из предыдущих языков - BCPL и PL/1. Амперсанд появляется в языке B благодаря созвучию со словом адрес и небольшим выбором знаков в устройстве ввода:
Вот такое простое объяснение. А дальше появился язык С. Супер-коротко о нём.
Язык C
Разрабатывался в период 1969-1973 г. Исходя из этого можно сделать вывод, что язык B оказался не очень удобным для реализации своих целей. Название взято просто как следующая буква в алфавите после B.
Автором этого языка уже считается только Деннис Ритчи.
Язык C - развитие языка B. Язык получился крайне успешным и до сих пор применяется там, где нужен прямой доступ к "железу" - например, его используют в SpaceX для софта, отвечающего за полёт.
Значки амперсанда и звёздочки в качестве указателей перешли из Би в Си. А оттуда в "плюсы" в 1983 г. Оба этих языка - С и С++ оказали огромное влияние на другие языки.
Go
Кен Томпсон стал работать над Go в 2007 году. Go часто ассоциируют как замену языкам С и С++ с учётом изменений компьютерных технологий и опыта разработки крупных систем, устраняющий недостатки и сохраняющий достоинства предшественников.
На практике всё не так просто, но если уж ребята делали язык для тех, кто работал в С и С++, то неудивительно, что и синтаксис языка остаётся близким к предшественникам.
Далее поговорим об указателях на примерах кода в Go.
4. Реализация кода на Go
Вот мы и добрались до кода на Go.
4.1. Адрес и указатели
Рассмотрим код и вывод программы в терминал:
В программе я использовал четыре спецификатора:
- %d - для вывода целых чисел в 10-ой системе счисления;
- %p - для вывода значения указателя в 16-ой системе счисления;
- %x - для вывода целых чисел в 16-ой системе счисления;
- %T - для вывода типа данных.
Разберём строку подробнее
>>> Первое значение строки 0xc00009e058 c00009e058 824634368088
0x в 0xc00009e058, вызванная спецификатором %p, обозначает принадлежность к 16-ричной системе счисления. Такая особенность спецификатора, добавлять 0x. Вторая часть - c00009e058 - само 16-ричное число - это адрес хранения переменной num. Т.е. число 12 хранится в памяти по адресу c00009e058. Адрес, где хранить переменные, выбирает компилятор.
>>>Второе значение строки 0xc00009e058 c00009e058 824634368088
То же самое число, но уже без нотации принадлежности к 16-ричной системе. Вызвано спецификатором %x.
>>>Третье значение строки 0xc00009e058 c00009e058 824634368088 - адрес хранения переменной num в десятичной системе счисления.
Кстати, как легко перевести одну систему счисления в другую без онлайн калькулятора? Запускаем обычный калькулятор и выбираем "Программист". В ленте ниже три иллюстрации:
Вводим/копируем число и выбираем систему счисления для перевода. Доступно четыре системы:
- Hex - 16-ричная;
- Dec - 10-тичная;
- Oct - 8-ричная;
- Bin - бинарная (двоичная).
Со выводом в терминале разобрались. Осталось ещё разобраться с типом переменной *int. Оказывается, есть и такой тип переменной в дополнение к привычным int, float и т.д., который хранит что-то, что является int-овым значением.
Скажем так, мы уже разобрались, что это что-то - адрес переменной. Для чего это нужно в Go кроме того, чтобы использовать для считывания данных из консоли в функции fmt.Scan(&a)?
4.2. Наискосок о функциях
Напишем код. Ожидаем получить на выходе peace:
Но получили другой результат - war. Вопрос: внутри функции мы присвоили переменной значение peace, но на выходе всё равно war. Почему?
Ответ: связано это с особенностью передачи аргументов в функцию для этого языка программирования, т.е для Go. Переменная a, с которой мы работаем в функции changeSituation - это копия переменной situation, которую мы подаём, как аргумент. Соответственно, работа внутри функции changeSituation не изменяет саму переменную situation, а меняет локальную переменную a.
Если нам нужно в функции менять исходную переменную, мы должны передавать аргумент по-указателю. А теперь, внимание: вспоминаем, для чего придумали знак астериск, он же звёздочка:
Астериск введён во II веке до н. э., где он указывал на пояснения к тексту.
Используя астериск, в Go мы тоже указываем - но уже не на пояснение к тексту, а на исходную переменную. Через её адрес. А чтобы передать эту исходную переменную, как аргумент в другую функцию, нам нужно использовать оператор амперсанд &, который берёт адрес исходной переменной.
Амперсанд сигнализирует, что нам нужен адрес переменной (помним, что амперсанд - созвучен со словом адрес).
Рассмотрим код:
Что здесь происходит:
- Вызывая функцию changeSituation в функции main, мы за счёт символа & перед наименованием переменной situation, в качестве аргумента передаём адрес хранения этой переменной - 16-ричное число. Это число примерно такое же, которое разбирали в примере выше с калькулятором.
- В функции changeSituation, астериск в строке //func changeSituation(a *string) {// является сигналом о том, что мы работаем с адресом на исходную переменную. Переменная a здесь является указателем.
- Может возникнуть логичный вопрос: мы хотим не адрес переменной изменить, а значение переменной, хранимой по этому адресу.
- Чтобы получить доступ к тому, что хранится по адресу переменной-указателя a, мы также используем тот же оператор астериск в связке с переменной в теле функции //*a = "peace"// - так мы можем работать с исходной переменной, а не с её адресом.
Если астериск используется перед типом (*int) - это означает тип указателя на интовое число;
Если астериск используется перед переменной (*a) - это означает, что мы получаем доступ уже не к адресу исходной переменной, а к самой переменной.
Вот такая мнемоника: * используется в книгах, как указатель на неясности в тексте, а в Go * является признаком указателя - переменной, которая хранит адрес другой переменной. Для получения адреса переменной - мы используем амперсанд: наименование символа созвучно со словом адрес.
5. Итоги
Вот такая история - от древних времён до современного применения * и &. Удивительно то, как на протяжении более двух тысячелетий люди изобретали знаки для упрощения своей жизни, принимали решения и как всё это повлияло на современную IT-сферу.
Лайфхаки
- Когда видим запись в Go: &a //читаем так "адрес переменной а".
- Астериск перед типом данных: b *string := &a //читаем так "b сохраняет в память адрес хранения переменной a и является указателем на а";
- Астериск перед переменной: *b = c //читаем так "переменная с записана в память по адресу, на который указывает переменная b"
Такие дела, Бро. Всех благ.
--//--//--
Напоминаю, если захотите купить курс от SkillBox, воспользуйтесь моей реферальной ссылкой. Вы получите огромную скидку на курс и плюс в карму за помощь каналу.
Бро, ты уже здесь? 👉 Подпишись на канал для новичков «Войти в IT» в Telegram, будем изучать IT вместе 👨💻👩💻👨💻