Лекция 2. Основные команды в терминале. Компиляция программ

ruticker 05.03.2025 11:07:44

Текст распознан YouScriptor с канала Мещерин Илья

распознано с видео на ютубе сервисом YouScriptor.com, читайте дальше по ссылке Лекция 2. Основные команды в терминале. Компиляция программ

Я ещё должен нам рассказать про литералы и литеральные суффиксы. Вот смотрите, есть такое понятие **Литерал**. Что такое Литерал? Это последовательность символов в коде программы, которая обозначает некоторые конкретное значение какого-то типа. Например, **пять** — это литерал. А, нужно, черный маркер, наверное, достать. Да, вот я говорил о том, что такое литерал. Вот, например, **пятерка** — это литерал, или **0**, не знаю, **2** — это литерал, или **"A B C"** в кавычках двойных — это литерал, или вот, например, **'A'** в одинарных кавычках — это литерал. Для каждого литерала захардкожено, к какому типу он относится. Вот это **int**, вот это **double**, или **террасплавающей точки** по умолчанию относится к типу **double**. Вот это тип **char**. Вот и еще существует понятие литеральных суффиксов. Вы можете попросить, чтобы литерал поменял свой тип. Например, если вы хотите, чтобы литерал с плавающей точкой у вас считался **float**, а не **double**, вы можете написать **0.2F** в конце. Вот это **F** — это литеральный суффикс, так называемый. По умолчанию, если вы пишете **0.2**, это считается **double**, а если вы хотите, чтобы она считалась **float**, то вы пишите **0.2F**. Зачем может быть нужно явно просить, чтобы тип литерала стал чуть другим? Ну, например, если вы **float** обрабатываете по-разному. Если у вас есть функция для **float**, которая принимает **float**, и функция с таким же названием, которая принимает **double**, то вот вы ее вызовете от **0.2** — вы попадете в версию для **double**, а если вызовете **0.2F** — вы попадете в версию для **float**. Понятно? То же самое есть еще полезный литерал. Например, есть такой, который означает... Зачем может быть надо просить, например, чтобы переполнение работало так, как вы хотите? Вот, например, если вы сложите два очень больших **int** и произойдет переполнение, то это формально неопределенное поведение. А если вы сложите два больших **unsigned int** и произойдет переполнение, то это определенное поведение: **unsigned** переполняются регламентированно, как мы просто по модулю берется, вот, а знаковые переполняются — у беда. Вот существует также неявные преобразования типов. То есть, если вы написали **int** и проинициализировали ее каким-нибудь значением, которое не тип **int** имеет, ну, например, тем же, проинициализировали, произойдет неявное преобразование. Некоторые неявные преобразования безопасны, а некоторые опасные. Вот, например, преобразование потенциально опасно, потому что некоторые значения имеют хранить, которые обычно не умеет, и теоретически преобразование может привести к переполнению, которое приведет к неприятностям. Поэтому компилятор вам может иногда в орден кинуть: "Вы тут неявно преобразовываете". Вот какие преобразования безопасны? Вы можете безопасно преобразовывать **char** в **int**, **int** в **long long**, **long long** в **long long**. То есть преобразование, которое повышает диапазон, они еще называются **promotion**. Они называются **integer promotions** — это преобразование, которое повышает вид **int** до более широкого. Вот они безопасные, их можно безопасно делать, они называются **integer promotions**, и в общем-то они неявно делают. Если вы складываете два **int** разной ширины, если вы складываете **char** с **int**, то вы получите **int**. **Char** повысится до **int**. Если вы складываете **int** с **long long**, то **int** повысится до **long long** и вернется вам **long long**. То же самое есть **floating point promotion** — это когда вы **float** повышаете до **double**. Понятно, что все, что представимо **float**, представимо и **double**. Поэтому, если вы делаете действия над **float** и **double**, то получается в результате **double**. А если вы делаете действия с **int**, то в результате получается **long double**. Понятно? А также есть неявное преобразование между **bool** и **int**. Про это мы уже говорили. Ну, между **bool** и целочисленными другими типами это мы уже обсуждали. Сегодня они тоже неявно делаются. Если вдруг в каком-то месте ожидается **bool**, вы передадите тип, то произойдет неявная конверсия. И есть преобразование между целочисленными и типами с плавающей точкой в одну сторону. Ну, как я уже говорил, если вы из **int** конвертируетесь в **double**, ну, или если вы там конвертируетесь из каких-то других целочисленных типов в плавающую точку, так что вы попадаете в более широкое множество, то это безопасное преобразование. Это все неявные допустимые преобразования. А вот если в обратную сторону конвертируетесь, вы теряете точность. Можно сконвертировать неявно **double** в **int** или **float**, ну, или **long long**, как хотите, вы потеряете точность в том смысле, что дробная часть отбросится у вас. Если там, например, вы **4.9**, если вы напишете **X = 4.9**, будет **4**. Вот, потому что, понятно, отбросить дробную часть. По-хорошему, вообще компилятор обычно кидает предупреждение. Если там компилятор хорошо настроен на разные максимальные предупреждения, то он кидает предупреждение в случае, когда у вас неявные конверсии происходят, которые вы, возможно, не замечаете. И чтобы вы понимали, что неявные конверсии — это зло. Вообще это зло. Я вам приведу один пример и одну цитату. Значит, пример такой: вот как вы думаете, что происходит, если вы складываете **int** с **unsigned int**? Какой тип у результата? **unsigned int**? Неправильно! **int**. В частности, если вы делаете так, то получается **4 миллиарда с лишним**. Вот, да, если написать **-1**, это уже будет **4 миллиарда**, потому что, понятно. Но если у вас есть какой-то маленький **int**, и вы из него вычитаете **unsigned**, или наоборот, у вас есть маленький **unsigned int**, как, например, **vector.size**, и вы из него вычитаете **int**, вот у вас есть размер вектора **5**, и вы решили обойти все элементы вектора, кроме последних **10**. В **-10** вы из... То есть вы читаете, получаете **unsigned** и **4 миллиарда**. В итоге цикл до **4 миллиардов**. Здравствуйте! Вот эта история однажды выстрела. Я помню, когда я в своей жизни запомнил, что сложение **int** с **unsigned** — это не просто **int**, это я уже работал в **Яндекс.Такси** в то время. Да, не это было хобби, вообще-то я был бизнесменом, но вообще-то я преподом работал. **Яндекс.Такси** — это было хобби, да, а я разработчиком работал в **Яндекс.Такси**, конечно, и писал на **плюсах**. И там однажды в каком-то коде нужно было вычислять время в пути водителя, и там нужно было делать какие-то поправки. Вот, что там, на сколько секунд быстрее он приедет. И что вы думаете, там был значит **unsigned int**, только секунду доберется, и вот из него вычитался, что в некоторых ситуациях приводило к тому, что он доберется за **4 миллиарда секунд**. Казалось бы, время в пути водителя — это знак знаков. Нет, беззнаковый вычитаем из него что? Ну, наверное, **int**, потому что поправка может быть как положительной, так и отрицательной. Все **4 миллиарда** получилось. Вот, ну был такой баг. Вот я вам обещал еще цитату. Тут как-то несколько лет назад было интервью с создателем, где-то очередное. Его спросили: "Если бы вы делали язык **C++** заново, вообще все с нуля начинали, то что бы вы сделали по-другому?" Он сказал, задумался, сказал: "Трудно. Значит, просто, наверное, он сказал, я бы запретил неявные конверсии между примитивными типами, потому что тогда никто не думал, насколько много бед они в современном мире принесут". Короче, избегайте конверсии. Явные конверсии — это плохо. Ну что еще вам сказать? Еще, наверное, он вот что скажу. Вот вы знали, что можно целые числа записывать в шестнадцатеричной системе счисления? А можно и восьмеричной. Вот целые числа можно записывать в шестнадцатеричной системе. Для этого нужно начать с префикса **0x**. Ноликс, и дальше, например, написать **FF** — это будет значительный литерал, который означает **255**. Это просто вот литерал, начинающийся с ноликса — это целочисленный литерал, записанный в системе. Если вы записываете число с нуля, то это воспринимается как восьмеричная система. Например, **0 1 2 3** — это вовсе не **123**, как можно было бы подумать, а это **64 плюс 16 плюс 3** — **83**. То есть если вы так напишите, литералы с нуля считаются записанными в восьмеричной системе. В двоичной системе я не помню, можно ли записывать **0b**. По-моему, начиная с какого-то стандарта все-таки можно. Да, **0b101**. В общем, вот как будто. Да, мне казалось, что это не работает. Год назад тоже спорили, я так и не помню, к чему пришли. Ну, в общем, мне лень проверять. Будем считать, что нет. Давайте, наконец, что-нибудь на ноуте попишем. Ну да, начиная с какой-то версии вроде оно как бы работает, но вот как-то... А, да, кто не знал, но вы услышали, начиная с **14 плюсов** можно, если записываете длинное число, использовать апостроф, или как правильно говорить, апостроф для разделения разрядов. То есть можно писать не **1 миллион**, а **1'000'000**. Это удобно для чтения. Так, господа, добро пожаловать в командную строку **Linux**. Как вы, наверное, знаете, мы все презираем **Windows** здесь и поэтому рекомендуем вам перейти на **Linux** или, в крайнем случае, на **Mac OS**. В ближайшее время, ну, если серьезно, то с **Виндой** не то чтобы вам будет совсем плохо на первом курсе. Давайте так, у кого **Винда**, а у кого там **Linux**? Ну или **Linux** что-то, **Убунта**? А у кого **macOS**? Окей. Ну вот, у кого **Убунта** или **Linux**, что-то семейство **Linux** — вы молодцы, вам будет удобно и хорошо. Все то, что я буду показывать, будет у вас один в один работать. Да, есть **WSL**, сейчас про него скажу. У кого **macOS**, у кого **Винда** — вам не то чтобы будет прям совсем плохо, но я рекомендую установить **Linux**. Не потому что вы как бы не справитесь, курс сдать без него, а потому что просто чем раньше вы к нему сами привыкните, тем лучше для вас же в будущем. На втором курсе вам все равно придется очень плотно сидеть под **Linux**, потому что когда у вас на втором курсе будет операционная система, вы будете писать именно под **Linux**. Под **Виндой** ничего это невозможно сделать и удобно. Если вы к началу второго курса уже будете, ну или каким-то другим **Linux** пользоваться свободно, вот, просто для вас же самих это будет полезно. Ну и когда вы придете даже работать, если вы устроитесь в какую-нибудь желтую или, или там, я не знаю, синюю компанию, вот вы, ну, может и в зеленую, то вы, скорее всего, обнаружите, что там все равно все под **Linux**, и мало кто **Виндой** пользуется. И да, этот человек на работе, ему приходится сидеть под **Виндой**, но не будем об этом. Вот, короче, вам же будет удобнее лучше привыкнуть пораньше. Вот, я буду показывать примеры все под **Убунтой**. У меня **Убунта 20** или **21**, не помню. Если вы находитесь в странной ситуации, потому что, с одной стороны, почти все у вас будет работать, и вам не стоит ставить, тем более я не уверен, что можно сделать на **MacBook**, с другой стороны, некоторые вещи у вас будут работать не совсем так, как у меня, и возможно, это будет вызывать некоторую особенную боль. Она вроде почти так, но ввиду особенности операционки не совсем так. Вам приходится разбираться, что же именно работает, иначе местами это может вызывать некоторые небольшие боли, но не такие большие, как под **Виндой**. Хорошо, сейчас мы переходим к параграфу **1.1**, который пропустили тогда, и параграф называется **знакомство с компилятором и первая программа**. Вот, но на самом деле это будет даже знакомство не столько с компилятором, сколько с терминалом. Вот я сейчас покажу некоторые базовые команды для работы в терминале. Ну, потому что буду **Убунты**, и вообще в мире программирования принято много чего делать из командной строки. Вот и такой джентльменский набор начинающего разработчика входит знание некоторых базовых команд **Linux**, которая позволяет вам какие-то базовые операции делать над файлами. Вот сейчас я нахожусь в командной строке. Вот это название моего компьютера, вот это то, как меня зовут, то есть я на таком-то компьютере, да, находясь в такой-то директории, говорю следующее, и вот здесь могу вводить команды. А самая, наверное, распространенная команда — это перейти в другую директорию, и она записывается **cd** от слов **change directory**. Вот я говорю **cd** и говорю, куда. Ну, я могу назвать директорию, в которой я хочу перейти, а могу, например, назвать мнемонику для какой-то специальной директории. Вот у меня есть всегда из любой директории две точки, означает подняться на директорию вверх. То, что нажимаешь кнопочка вверх, там везде это пишется. **cd ..** — две точки — это директория выше текущей. Вот я нажимаю, оказываюсь в директории. Тильда — это тоже специальная директория, это сокращение, обозначающее домашнюю директорию. У каждого пользователя есть домашняя директория. Вот я сейчас нахожусь в домашней директории, в ней у меня есть директория **2023**, которую я заблаговременно создал для вас, для нас. Да, можно узнать полный адрес директории, в которой сейчас нахожусь, да, то есть команда **pwd** — **print working directory**. Вот в **Линуксе** вы будете, в **Линукс** подобных системах не так устроена дерево директории, как в **Винде**. В **Винде** там есть, значит, все начинается как бы с диска, у вас там с двоеточия, а потом слэш какие-то папки. В **Убунте**, в **Линуксе** есть так называемая корневая директория, которая обозначается просто слешом. Из нее растет вся файловая система. Файловая система вообще — это некоторые абстракция, которая позволяет вам воображать все, что хранится в вашем компьютере. Она вот как бы в виде дерева представлена. При этом, как оно соответствует физическим дискам — это отдельный вопрос, который мы не будем сейчас поднимать. В общем, как-то. И у меня есть директория, которая начинается с флешом — это корневая директория. Вот, и я могу из нее растет все мое дерево директории. Я могу сказать **cd home/ваше_имя_пользователя**. Вот, сразу же очень важный пункт. Да, если у кого из вас **Убунта** или у кого **macOS**, то вы можете сразу за мной повторять. Это, ну, в принципе, я думаю, если у кого **Убунта** или **macOS**, вы так это все умеете делать, скорее всего. А у кого **Винда** или что-то другое, то вы можете, не знаю, записывать, ну, или в каком-нибудь терминале в интернете пробовать это воспроизводить. Значит, я могу использовать клавишу **Tab**, чтобы дополнить, чтобы он мне подсказал, чем могу дальше написать. Вот, когда пишут **Tab**, он мне продолжает слово, которое начал писать, исходя из того, что есть там, ну, либо предлагает варианты дальше. Вот, например, я хочу дальше **2020**, и вот он мне предлагает, какие варианты есть. Тут **2023** прод, и вот я перешел сюда. Я могу также писать **cd ..** — две точки, две точки, две точки, и это как бы я на три директории вверх прошел. Вверх, потом еще вверх, относительно еще вверх, относительно вернулся в корень. Вот, я могу, кстати, да, спасибо, это очень полезное замечание, чуть не забыл. Очень полезная команда, которую не все далеко знают, но она супер полезна. Я не узнал тоже уже, когда был стажёром в **Яндексе** своему, потому что тогда никто на первом курсе таких вещей нам не рассказывал, и на втором, и на третьем. В общем, есть замечательная команда **cd -**. Очень полезная штука, возвращать туда, где были до этого. Но, к сожалению, работает только один раз. Если напишу **cd -** еще, то я, ну, буду ходить между ними просто. Вот, если вам хочется ходить, ну, как бы вот назад, назад, назад относительно, где вы были, то есть специальные команды **pushd** и **popd**. Я не буду про них сейчас подробно говорить, но если вам интересно, погуглите. Команды поддерживают стек директорий, в которых вы были, позволяют по нему ходить взад-вперед. Хорошо, еще одна полезная команда — посмотреть, что лежит в текущей директории, список файлов просто отобразить. Это команда **ls**. Вот команды, которые я сейчас озвучиваю, от команды **cd**. Она и в терминале **Винды** такая же. **cd** в **Винде** тоже есть. На **макосе** все то, что я сейчас говорю, работает также. Ну, короче, все то, что сейчас говорю, актуально для **Linux**, для **макос** и для **Винды**. **pwd**, по-моему, называем вместо **pwd** команда **dir**, которая отображает текущую директорию, а вместо **ls** команда **dir**. А вместо **pwd** не помню, какая команда. Ну, неважно. **ls** от слова **list**. Название команд в **Линуксе** порой странноватые. Ну, в общем, **ls** означает отобразить список файлов текущей директории. Вот список файлов текущей директории, они все почти синим цветом. Да, можно писать... Сейчас, подожди, подожди. Вот почти все файлы здесь синим цветом, это значит, они сами директории. Вот синим вот таким цветом отображается директория, вот таким голубеньким цветом отображаются ссылки. Ну, в терминах **Винды** это ярлыки. То есть это файл, который является ссылками на другие файлы на самом деле. А вот так я не помню, что отображается вот такой зеленой подсветкой. Ну, неважно. В общем, красным цветом отображаются архивы. Ну, короче, если мы не будем углубляться, вы можете почитать любой мануал по **Unix** для новичка, там будут все эти перечисленные штуки. Нам это не нужно, не будем углубляться в подробности, какие бывают файлы — это все будет у нас потом. Ну или потом на втором курсе у вас будет полезная команда. Можно добавить параметр, не знаю, сокращение какого слова **-l**, но **ls -l** означает отобразить подробно про каждый файл. Отображает даже позже, да. Или с **-l** — это значит отобразить подробно про каждый файл. Пишется, вот это какие у него есть права, какие права на доступ к этому файлу. Это, это даже не знаю, что это такое. Что это за поле? Нет, размер здесь. А это, ладно, давайте, к сожалению, не помню, не буду вас обманывать, что это. Это кто владелец этого файла. Это, по-моему, группы владельцев этого файла. Это размер, это дата последнего изменения. Вот про ярлыки, то есть по ссылке здесь написано, что, собственно, они ссылаются. Наверное, стоит поподробнее сказать по поводу прав. Вот эта строка, обозначающая права доступа к файлу, здесь есть несколько символов. Первый символ — это буква, обозначающая тип файла. Типы файлов в **Линуксе** бывают. Это бывают обычные файлы. Вот это, например, обычный файл. И такое, что она обычный. Если задуматься, но с точки зрения **Линукса** это обычный файл, поэтому здесь стоит прочерк. Вот директория — это особый тип файлов, для них написано **D**. Для файлов, которые являются ссылками, это тоже особый вид файлов, это **L** от слова **Link**. Вот бывают еще другие виды файлов, но мы не будем, опять-таки, углубляться. Пока достаточно этих. Дальше перечислены права доступа к файлам: **R**, **W** и **X**. **R** — от слова **read**, **W** — от слова **write**, **X** — от какого слова? От слова **execute**. Значит, буква **R** обозначает, что присутствует право на чтение этого файла. **W** — это значит, присутствует право на запись в этот файл. **X** означает, что присутствует право на выполнение этого файла. Почему-то это дублируется два раза. Вот почему у каждого файла есть три группы, ну, права доступа к файлу бывают трех категорий, скажем так. Первые три буквы обозначают права доступа к этому файлу у владельца этого файла. Владелец этого файла — **root**. То есть не важно, кто такой **root**. В общем, есть некоторые пользователи, которые зовут **root**. Пока что абстрактно. Ну вот, меня зовут вот так, да, **миссиярик**. И если бы я был владельцем, то вот эти три буквы обозначали бы права мои на действия с этим файлом. Я имею право читать этот файл, писать этот файл, выполнять этот файл. Вот эти вторые три буквы обозначают, то какие есть права к этому файлу у так называемых группы владельцев этого файла. В **Линуксе** у каждого файла есть не только пользователь-владелец, но еще и группа-владелец. Ну, просто когда пользователей в системе слишком много, они иногда объединяются в группы, и группы могут права отдельно настраиваться. Например, может быть группа администраторы или группа там разработчики или группа тестировщики, если мы говорим о каком-нибудь индексовом серваке. Вот, но в нашем случае это неприменимо, когда у вас будут большие сложные, там, большие сервера, там, где будет много пользователей, возможно, группа владельца вам будет о чем-то говорить. Последние три буквы — это права у всех остальных. Если вы, если вы владелец этого файла, то вам применяются вот эти вот права. Если вы в группе владельцев, применяются эти права. Если вы кто-то другой, только он применяется эти права. Количество харлинков на этот файл, я понял. Ну да, это количество, значит, файлов файловой системе, которые жестко ссылаются на этот же файл. Окей, хорошо. А, ну вот вопрос, например. Вопрос такой: почему? Ну что такое? Понятно, читать, писать — это понятно, что значит. Ну, читать — смотреть содержимое, писать — ты знаешь, менять содержимое. Что такое **X**? Почему **X** — это какое-то отдельное действие, на которое нужно отдельное право? Вот что за право? Особенно на выполнение. Почему, типа, почему если я читаю, то я выполнить не могу этот файл? В чем разница между чтением и выполнением? Кто может объяснить? Да, применительно к директориям **X** означает, что я имею право открыть директорию, а применительно к обычным файлам — что означает право на выполнение? Ну вот, например, давайте так скажу. Вот смотрите, вот у меня есть папочка **bin**. Давайте перейду в папочку **bin**. Я напишу **ls**. Тут очень много всего лежит. Ну, **bin** — это директория, в которой хранятся по сути все бинарники. То есть все, все программы, все исполняемые файлы данного компьютера, которые как бы в ней. Ну, в ней, не знаю, что лежит. Ну, например, в ней лежит такой файл, который называется **cd**, а **cd**, наверное, лежит **user**. Ну, хорошо, вот файл **cat**, например, лежит. **Cat** — это тоже команда, которая выводит на экран содержимое файла. Ну, короче, все команды на самом деле — это некоторые же программы, и они вот сами файлы этих программ лежат здесь. Не знаю, может быть, ты прав, но я не знаю. Не будем об этом, лучше. В общем, здесь лежат разные программы, всякие там у меня много всего установлено. Сейчас весь мир, весь мир увидит, какие у меня программы установлены. Вроде здесь ничего такого компрометирующего нет. В общем, это все некоторые программы. Да, вот, кстати, зеленым отображаются исполняемые файлы, запустить можно. А вот это ссылки, опять-таки, голубенькие — это ссылки. Так вот, ой, вот и вот если я напишу **ls -l** в этой директории, то тут у большинства файлов есть право на выполнение. Но, а владелец у них у всех **root**. Но почему же мне отдельным правом все-таки нужно выполнение? Почему чтение недостаточно? Ответ таков: дело в том, что выполнение по сути представляет из себя загрузку в оперативную память и восприятие того, что написано в этом файле, как инструкции процессора. Одно дело — вы можете просто посмотреть, что там написано, а другое дело — вы можете заставить процессор воспринять это как бинарный код и начать исполнять это, как будто это инструкция для процессора. Это совершенно разные вещи. Если у вас какой-нибудь вирус очень вредный или там какой-нибудь файл, в котором потенциально опасные какие-нибудь содержатся действия, ну, процессор бинарным кодом кодируется то, что он делает. И, допустим, у вас есть файл, который содержится очень опасный вирус, который там все крушит, ломает. Если у вас есть право на чтение этого файла, вы можете безопасно читать, но у вас не будет права, пока у вас нет права **X**. Это значит, что вы не сможете процессор заставить начать эти инструкции исполнять, как будто это бинарный код. Исполняемый. В этом разница между правом на чтение и правом на выполнение. Иметь право читать какой-то файл — это еще не значит иметь право выполнить его. Вот, и на самом деле все эти права нужны для того, чтобы безопасность была. То есть как раз право на выполнение лучше не давать тем файлам, которые потенциально возможно что-то плохое делают. Да, если у тебя есть право на запись в какой-то файл, ты можешь создать файл, который ты можешь читать, писать и выполнять. Ты берешь какой-то плохой файл и с помощью него копируешь содержимое в файл, который ты не мог выполнять, и выполняешь этот файл. Да, ты можешь так сделать. Но смысл же ограничений в том, чтобы ты случайно не сделала что-то плохое, а не в том, чтобы ты намеренно что-то плохое не сделал. Понятно, что таким способом ты можешь намеренно запустить что-то плохое. Команда `cat` — следующая полезная команда. Что вы думаете, она делает? Как следует из названия, это программа, которая происходит от слова "конкатенация". Но по факту это программа, чаще всего используемая, чтобы вывести содержимое файла на экран. Например, давайте выведем на экран какой-нибудь файл, который не очень большой. `cat Z` — всего лишь 1984 байта в этом файле. Давайте прочитаем их. Ой, ничего не вышло. Вот оно что! Для бинарных файлов эта команда выводит их содержимое на экран. А это не бинарный файл, это скрипт. Значит, `cat` — это был не бинарный файл, а просто скрипт с таким содержимым. Давайте сделаем `cat` на файл, который мы создали. Вот мы вывели на экран содержимое файла `cat`. Это был бинарный файл, и в нем была собственно исполняемая программа, записанная бинарным кодом. Как вы видите, здесь в общем-то ничего не понятно. Ладно, давайте вернемся в домашнюю директорию. Кстати, я написал `cd` без параметров. Заметьте, без параметров это значит вернуться домой. Да, вернулись на базу, так сказать. Честно говоря, я даже, наверное, больше не буду вам показывать другие команды на первое время. Мне хватит этих. Я потом, наверное, в какой-то момент вернусь к разговору про терминал, и мы с вами поделаем что-то более интересное. Пока что мне достаточно только вот этих базовых команд. Я умею водить, переходить в другие директории, смотреть через директории. Теперь я хочу из терминала написать какую-нибудь простейшую программу на плюсах, скомпилировать и запустить. Для этого я должен использовать какой-то текстовый редактор. Как знают давние почитатели моих видеолекций, мы будем использовать `vim`. Нет, вы будете использовать некоторые встроенные текстовые редакторы. Один из таких текстовых редакторов — это `nano`. Можете самостоятельно упражняться в его использовании. Но, пожалуй, самый такой трушный текстовый редактор для настоящих пацанов — это `vim`. А дальше название файла, который хотите создать. Если этот файл уже существовал, то он откроется, а если нет, то он будет создан. В этой директории у меня ничего нет, она сейчас пустая, и я сейчас открою файл `vim 1.cpp`. Я буду файлы нумеровать согласно пунктам нашей программы. Это файл, соответствующий первому параграфу первой главы. Теперь это значит расширение, чаще наш любимый язык программирования. Вот я открыл `vim`, теперь могу писать код. Да, кстати, я вижу, что увеличил и уменьшил шрифт. Чтобы увеличить, нажимаю `Ctrl` и `+`, чтобы уменьшить — `Ctrl` и `-`. А самое главное знание — как выйти из него. Есть такой анекдот, который рассказывают. Первый из них: мой приятель пользуется им уже 20 лет, потому что не знает, как из него выйти. Как выйти из `vim`? Нужно писать `:wq` и `Enter`. А теперь вы знаете, как выйти из `vim`. Но это был выход без сохранения изменений. Чтобы выйти с сохранением изменений, нужно написать `:wq` и `Enter`. Я сейчас снова зайду. Да, сейчас у меня никакого файла не создалось, потому что ничего не сохранил. Я сейчас снова открою тот же самый файл, на этот раз запишу и сохраню изменения. Я хочу повторить команду, которая была до этого. У меня есть стрелочка вверх, чтобы повторять команды. Я могу вверх-вниз делать, нажимая на клавиатуре стрелочку вверх, и отображается последняя команда, которую я вводил. Нажимаю еще раз вверх, и предпоследняя отображается. Так могу ходить вверх-вниз. В какой-то момент вы узнаете, какие команды вводил до того, как началась наша пара. Вы уже это увидели. Но я теперь иду вниз, дохожу до низа. Можно еще много интересного делать с историей команд, но про это потом расскажу вам. Пока это не нужно. Вот я повторяю эту команду, нажимаю. Теперь я хочу начать вводить текст. Чтобы начать вводить текст, мне нужно перейти в режим вставки. По умолчанию я нахожусь в режиме команд. Если сейчас буду какие-то буквы набирать на клавиатуре, то это будут не символы, которые печатаю, а будут какие-то странные вещи происходить, потому что он будет воспринимать мои нажатия как команды. Я хочу перейти в режим вставки. Чтобы перейти в режим вставки, мне нужно нажать клавишу `i`. Вот я ее нажал, и у меня внизу появилась `INSERT`. Это значит, я нахожусь в режиме вставки. Теперь я могу печатать, как будто я в обычном текстовом редакторе. Впрочем, тут есть несколько странных подводных камней. Некоторые вещи работают не так, как в обычном текстовом редакторе, но в целом, в основном, я могу писать, как в текстовом редакторе. Две полезных комбинаций клавиш, которые сразу же озвучу: если я хочу стереть целое слово, то я нажимаю `Ctrl + W`, а если я хочу стереть целую строку в режиме вставки, нажимаю `Ctrl + U`. Кстати, это же работает в терминале. Если у вас очень длинная команда и вы хотите стереть последнее слово, то вы нажимаете `Ctrl + W`, а если хотите стереть всю строку, то нажимаете `Ctrl + U`. Это полезно, особенно на `macOS`, на котором очень медленно стрелочка влево ездит. Если вы стираете на `macOS`, на котором по умолчанию медленный повтор клавиш, это меня бесит. Если вы хотите стереть целую длинную команду в терминале, используйте `Ctrl + U`. Так, ладно, третья попытка. Я попытаюсь написать какую-то программу на плюсах. Давайте будет такая программа: я объявлю целочисленную переменную `x`, введу с клавиатуры и выведу на экран ее же, увеличенную на 5. Очень интеллектуальная программа. Понятно, что `std::cout` — это такое заклинание, которое позволяет мне начать пользоваться стандартным выводом в моей программе. Понятно также, что моя программа начинается с функции `main`. Это точка входа в программу. Эта функция `main` обычно не будет принимать параметров, у нее возвращаемый тип `int`, потому что так надо. Возможно, в какой-то момент поподробнее остановимся на том, что именно может принимать функция `main`, что она может возвращать, что это все означает. Но пока что мы будем считать, что `main` у нас ничего не принимает и возвращает `int`. И `#include ` мы тоже будем всегда писать. Считаем, что это необходимое зло, пока что мы не очень понимаем, как оно работает, но будем им пользоваться. Вот благодаря `#include ` я могу использовать `std::cout`. Вот я ввел `x`, вывел `x + 5`. Чтобы из режима вставки вернуться обратно в нормальный режим, нажимаю `Esc`. Дальше я вышел с сохранением изменений. Теперь у меня есть вот этот файл, который здесь лежит. Я могу спросить, когда у него последние изменения были. Заметьте, что его владелец — я, и занимает он 90 байт. Я могу с помощью команды `cat` посмотреть, что в нем. А, да, кстати, такая небольшая уточнение. Вот вы видите, что у меня `vim` довольно-таки умный, приятный, вообще молодец. Он мне сразу подсвечивает синтаксис, подсвечивает синтаксис плюсов, ключевые слова. А еще он умеет делать автоотступ. То есть если я нажимаю `Enter`, он сохраняет мне отступ. А еще он все табы автоматически мне заменяет на пробелы и так далее. Это всё `vim` по умолчанию не делает, это просто у меня так настроено. Хорошо, это просто `vim` так настроен. Теперь давайте компилируем и запустим на плюсах. У меня есть моя программа. Давайте ее скомпилируем. Чтобы скомпилировать ее, нам нужен компилятор. Какие вы знаете компиляторы? Да, пожалуй, три самых распространенных компилятора плюсов — это GNU компилятор, компилятор `clang` и Microsoft компилятор `MSVC`. Я не знаю, никогда не пользовался. Возможно, вы можете, но если вдруг у вас будет компилироваться в контексте, я как бы предупреждал. Короче, `g++` — это компилятор, который мы будем использовать. Если его у вас нет, по-моему, по умолчанию он не идет в комплекте с `Ubuntu`. Если у вас его нет, то надо написать `sudo apt install g++`. Будем считать, что он есть, но если у вас его нет, то можете ввести эту команду, и он подскажет, что надо установить. Если что, `sudo` — это команда, которая используется для установки новых пакетов и приложений. Теперь давайте скомпилируем. Я пишу `g++ -std=c++11` и просто нажимаю `Tab`, потому что файл всего один. Вот он скомпилировал. Ничего не вывелось, это значит, все хорошо, ошибок не произошло. У вас такое в контексте будет редко. Теперь давайте посмотрим, что в папке появилось. У нас появился замечательный файл с названием `a.out`. Это файл, который получается на выходе после компиляции. Если вы специально не скажете, как он должен по-другому называться, я могу явно сказать, как я хочу, чтобы он назывался. Добавлю параметр `-o`, чтобы он назывался по-другому, например, `b.out`. Теперь у меня будет еще и `b.out`. Да, давайте я удалю `b.out` командой `rm`. Теперь я хочу запустить. Как видите, он уже побольше, чем моя программа, целых 17 килобайт занимает. Что же в нем интересного такого? Ну, в нем как минимум есть `iostream`, а это уже, так скажем, немало. Да, поэтому ничего удивительного, что файл занимает много места, потому что код вывода на консоль не тривиален. Давайте его запустим. Видите, у него есть право на выполнение, в отличие от вот этого файла, у которого нет права на выполнение. Кстати, я могу отобрать право на выполнение этого файла, и он перестанет быть зелененьким. Да, вот есть название. Да, и можно сказать, что я поменяю владельца файла. Можно, а можно чему-то. `chmod` позволяет. Вы, наверное, по синтаксису догадались, `-x` — это значит отобрать право на выполнение. Я могу сказать `chmod -w`, и тем самым отберу право на запись. Теперь я не смогу его запустить, но давайте я обратно верну `+x`. Да, не у всех было, всем вернул. Хорошо, как его запустить? Мне надо что написать, чтобы запустить? `./a.out`. Вопрос на понимание: почему нельзя написать просто `a.out`? Почему? Потому что если я запускаю что-то без указания пути, то по умолчанию файл для исполнения ищется в стандартных директориях, которым относится `/bin`, `/usr/bin`, `/usr/local/bin` и так далее. Почему так? Лучше не писать. Короче, да, не будем об этом. В общем, я пишу явно `./a.out`. Точка — это мнемоническое название, обозначающее текущую директорию. Он говорил, что две точки обозначают директорию на уровень выше, а одна точка обозначает текущую директорию. Запускаю `./a.out`, нажимаю, запускается. Ввожу 5, выводится 10. Счастье, радость! Мы научились писать программу на плюсах. Ой, не то хотел. Не это хотел. 1.5. В принципе, в этом файле, наверное, у вас ничего не вызывает вопрос. Или кое-что вызывает? Не хотите ли вы что-нибудь спросить про этот код? Да, вот самый стандартный вопрос, который задают обычно: не стоит ли написать `return 0`? Но ответ — нет, потому что начиная с `C++11` гарантируется, что если в конце `main` не написано `return 0`, то считается, что оно как будто там написано. `main` — это единственная функция, которая имеет право, несмотря на то, что эти пункты не писать возвращаемое значение. Оно дописывается по умолчанию, считается, что мы возвращаем 0. А что вообще значит это? Что мы возвращаем 0? Кому оно его возвращает? Ну, не операционной системе, скорее, а башу. То есть мы как бы из какого-то откуда-то запускаем мою программу, а именно из терминала, и 0 возвращается вышестоящей вызывающей программе. Наша программа для него называется как бы функцией. Да, и вот код возврата, с которым она вызвалась, возвращается ей, и 0 означает, что все хорошо, ничего не случилось. А если мы вернули не 0, то это бы как бы можно было воспринять, что как будто какая-то ошибка произошла. Мы почти всегда будем возвращать 0. Когда будем не 0 возвращать, тогда отдельно поговорим об этом. Вот еще некоторые из вас, наверное, любят писать `using namespace std`. Вот здесь что будет, если так написать? Если так написать, то можно не писать `std` вот здесь нигде. `using` говорит вам, что все сущности, которые объявлены в пространстве, теперь как бы привнесены в вашу область видимости. Но на самом деле у нас как раз следующий пункт должен быть про объявление и области видимости. Если вкратце, лучше так не писать. Но подробности мы сейчас обсудим. В общем, можно написать, тогда все штуки, перед которыми вам приходилось писать, они как бы будут по умолчанию. Вот так не надо делать. Почему? Ну, сейчас мы об этом и поговорим как раз. У нас осталось примерно 8 минут. Да, давайте. Вот что я успею рассказать из этого пункта, то я расскажу. Я на этом закончил введение в компиляцию программ на плюсах. Первую программу успешно скомпилировали, запустили, и этот пункт мы уже целиком не успеем сегодня озвучить. Но тем не менее начнем. Пункт будет называться **Declarations, Definitions and Scope**. Здесь мы поговорим о том, как что вообще можно объявлять в программе на плюсах, в чем разница между декларацией и определением, и что такое scope — область видимости. То есть в том числе мы здесь поговорим про пространство имен. А сейчас я скажу очень глубокую важную мысль, которая, возможно, вас несколько шокирует, но я думаю, что вы поразмыслите минутку и смиритесь с этой мыслью и осознаете, что это правда. Мысль следующая: все, что вы пишете на плюсах, вообще любая программа на плюсах, если там убрать препроцессор, это не что иное, как последовательность объявлений. Программа на плюсах — это последовательность деклараций. Глубоко правда. Смотрите, что такое программа на плюсах? Программа на плюсах состоит из чего? Вы сначала объявляете какие-то сущности. Ну, что такое? Там могут быть препроцессорные директивы, `#include`, например, или `#define`. Но мы считаем, что препроцессор как бы уже отработал. Вот препроцессор. Он что делает? Он просто по тексту берет и подставляет. Когда я говорю `#include`, препроцессор просто берет этот файл `iostream`, который лежит там, где-то по стандартному адресу, и содержимое этого файла впихивает целиком в мою программу. При этом, возможно, в нем самом были другие `#include`. Точнее, они точно были, и они тоже также раз шифровываются и впиливаются целиком в программу. Но когда препроцессор отработал, `#define` точно также заменяются по тексту. Когда препроцессор отработал, во что превращается программа? У вас объявлены какие-то сущности подряд. У вас объявлены, возможно, какие-то пространства имен, `namespace`, еще что-то объявлено. И в ней внутри них еще что-то объявлено и так далее. Потом у вас, возможно, объявлены какие-то глобальные переменные, объявлены какие-то классы, объявлены какие-то функции, объявлены `using`. Потом объявлена функция `main`. То есть по сути программа — это последовательность объявлений. Если так синтаксически на нее смотреть с точки зрения компилятора и на глобальном уровне, опять-таки, если не говорить про препроцессор, компилятор от вас ожидает, что вы на глобальном уровне что-то объявляете. Все время, что вы можете объявлять. Вы можете объявлять переменные, вы можете объявлять функции, классы, структуры, пространства имен, `using`, еще можете объявлять, то есть алиасы можете объявлять, еще юнионы и кое-что еще. Но в целом вот почти список этим исчерпывается, что вы можете на глобальном уровне делать. Потом, когда вы какую-то функцию объявили, например, `main`, внутри нее вы опять начинаете что-то объявлять. Но там уже внутри функции помимо объявлений вы можете другие вещи делать. Вот внутри функции вы пишете уже не объявления, так называемые statements. И могут быть как объявлениями, так и чем-то другим. Но на глобальном уровне у вас все, что есть, это объявления. Объявление — это вот некоторые новые сущности. Вы можете объявлять переменные. Самое простое, что вы можете объединить — это переменную. Как объявить переменную? Вы говорите тип, название, точка с запятой. Например, `int x = 5;`. Я объявил глобальный `int`, который равен 5. Глобальный — это значит, что находится в глобальной области видимости. Глобально значит не внутри ничего. Вот в самом верхнем уровне на самом верхнем уровне программы находится так называемый global scope. Я не нахожусь не внутри ничего. Вот я могу открыть фигурную скобку, там, например, начать класс или `namespace`, или функцию. Внутри фигурных скобок уже будет не глобальный scope. Но пока я ничего, никакую фигурную скобку не открыл, я нахожусь в глобальной области видимости. Да, в глобальной области видимости, если я объявляю какую-нибудь переменную, то она по умолчанию инициализируется. Если `int` объявляю глобальный, то он нулем инициализируется. Если я объявляю строку, то она проинициализируется пустой строкой. Если я объявляю массив, то он тоже, по-моему, инициализируется. Кстати, я уже не уверен. Да, значит, что неверно для локальных переменных. Если внутри `main` объявлю `int` и не напишу, чему равен, он будет содержать рандомное значение. А вот в глобальной области видимости гарантируется, что он будет содержать 0. Я могу объявлять функции. Вот сейчас я глобально объявил функцию `f`, у которой принимаемый тип `int`, возвращаемый тип `void`. `void` — это такой особый тип, который переменных такого типа не бывает, но он используется для функций. Для возвращаемого типа означает, что функция ничего не возвращает. `void` вообще с английского переводится как пустота, вакуум. Вот значит для функций тип `void` пишется в возвращаемом значении, означает, что ничего не возвращается. Так решил комитет по стандартизации, возможно, так решил еще создатель свои первые годы. Вот дальше я могу объявить что-нибудь еще. Например, могу объявить класс, но я могу объявить структуру. Я сейчас не буду объяснять, что классы и структуры, но, наверное, догадываетесь, где-то так сказать. Я подозреваю, что многие из вас и так догадываются, что это такое, но мы будем делать вид, что мы не знаем, что это такое. Могу объявить класс, могу объявить структуру, да, могу объявить юнион, могу объявить и нам класс. В общем, если вы не знаете, что это такое, это не страшно, вы узнаете из курса. Но, короче, пока просто не будем об этом. Все это можно объявлять, пока мы будем выходить без этого. Можно объявить `namespace`. Вот когда я объявляю `namespace`, я его должен сразу же определить. В отличие от класса, функции и всего перечисленного, нельзя объявить и вот не открыть фигурную скобку. Что такое `namespace`? Это специальное изобретение C++, которого не было в C. Ладно, структуры, юнионы и по-моему были, и, ну, тоже, кажется, были в какой-то момент. В общем, `namespace` — это такое изобретение, которое позволяет более удобно организовывать код, а для того, чтобы у вас именно не в глобальной области видимости находились, а вот в какой-то вложенной. Если у вас слишком большая программа, большой проект, и в нем есть пересекающиеся имена, то удобно бывает их разделить на `namespace`. Это просто штука для удобства, созданная, чтобы у вас имена случайно не совпали, относящиеся к разным логическим кускам вашей программы. Вот вы можете объявить переменную, а потом в каком-то другом `namespace` объявить тоже переменную, разные переменные. Именно это и предоставляет вам стандартная библиотека. Если вы заглянете в его `iostream`, кстати, я не уверен... А, да, у меня это работает. Вот я сейчас открыл файл `iostream`. Я сейчас прыгнул просто на файл `iostream`, он открылся. К сожалению, открылся он не очень так, как я бы хотел. Вот он открылся получше. Вот я сейчас открыл файл `iostream`, и я нажал `F12`, чтобы открыть файл, если что. У меня `vim` так настроен. И здесь тоже есть некоторые классы. Его `iostream` по сути начинается с того, что в него интуится сначала там какие-то еще два файла, а потом написано `namespace std`, какое-то там дополнительное слово, фигурная скобка открылась. То есть по сути `iostream` — это файл, в котором объявлены дальше в нем какие-то сущности. Вот, а поэтому, когда вы используете что-то из стандартной библиотеки, например, `std::cin`, это тип `std::cin`, название переменной. Вот у вас и получается, что если вы заинклудили `iostream`, то в `namespace std` у вас появилась переменная `cin`, к которой вы можете обращаться, как она там определена. Как определить операции на дне — это уже вопросы следующего порядка, там еще есть другие файлы, там это все, но это сложно. Но так или иначе, в стандартной библиотеке именно это сделано. Все эти сущности объявлены внутри, поэтому, когда вы к ним обращаетесь, вы пишите `std::` и дальше уже обращаетесь к именам оттуда. Вы можете вводить свои `namespace` и объявлять сущности в них, и таким образом, когда вы будете хотеть обращаться к этим сущностям, вам надо будет писать. Да, я уже немножечко превзошел все время. Сейчас я через пару минут закончу. Да, могу ли я сделать? Могу ли я еще раз сделать? Конечно, да. Если бы это было не так, то просто не работали бы инклуды. Напоминаю, что инклуды работают. Так что я просто вот в этом файле написано еще два файла, в каждом из которых то же самое сделаем. Вот тут тоже открою. Когда я написал `iostream`, на самом деле у меня вот эта огромная шняга с `namespace` и фигурная скобка открылась, файл вставилась, но перед ним еще два инклуда, и там еще фактически написано два разных инклуда. Еще куча всего понаписано, поэтому на самом деле, да, по факту именно это и происходит, когда инклуды раскрываю, там написано. Да, и что-то в него дописывается. Более того, можно даже самостоятельно дописывать кое-что в `namespace std` при определенных ограничениях. Может, даже кто-то знает пример, когда может быть полезно что-то дописать. Правильно, когда вы хотите переопределить функцию для своих типов, вы можете написать хеш для этого типа, тогда будет... Ну да, специализировать, тогда оно будет спокойно находить хеш в своем пространстве для вашего типа. Ну и можно еще `using` объявлять. Я уже потом не буду, ладно, сильнее задерживать. Мы потом поговорим про `using` в следующий раз, начнем с этого, наверное. Я вам и так за сегодня еще рассказал. Хотя я думаю, большая часть того, что сегодня рассказывал, вы и так знали. Значит, короче, это не то чтобы домашнее задание, это просто совет к следующему разу. Если у вас у кого-то трудности с терминалом, вот с этим вот компиляцией, короче, рекомендую всем либо перейти на `Ubuntu`, либо там на `macOS`, либо на `Windows` установить `WSL` и научиться, вот то, что сегодня показывал, воспроизводить своими руками. То есть установить `g++`, научиться запускать из командной строки, научиться открывать и выходить из него.

Назад

Залогинтесь, что бы оставить свой комментарий

Copyright © StockChart.ru developers team, 2011 - 2023. Сервис предоставляет широкий набор инструментов для анализа отечественного и зарубежных биржевых рынков. Вы должны иметь биржевой аккаунт для работы с сайтом. По вопросам работы сайта пишите support@ru-ticker.com