Int i 0 что это значит
Перейти к содержимому

Int i 0 что это значит

Int i 0 что это значит

Циклы. Оператор for

Циклы. Оператор for

Часть 1. Простые Циклы.

Представим, что нас нужно вывести какое-нибудь число не один, а десять раз. Это можно сделать при помощи циклического оператора for.

Например если a = 5, то в выходном файле мы увидим

Рассмотрим оператор for поподробней.

В круглых скобках указывается условие, при котором цикл будет выполняться, а в фигурных скобках сами действия.

В круглые скобках находятся три параметра.

Первый — это начальное значения некоторой переменной, наша отправная точка. В примере мы прямо в цикле объявляем переменную i целочисленного типа int. Если бы переменная была объявлена раньше, то параметр начало выглядел бы так: i = 0.

Второй параметр это — условие при котором будет выполняться цикл. В нашем примере это i < 10 — т.е. пока i будет меньше 10 цикл будет работать.

И последний параметр это изменение значения переменной. Здесь переменная i будет увеличиваться на 1 за одну итерацию. i++ означает, что i увеличивается на 1. С тем же успехом можно написать i = i + 1;

Далее я приведу несколько примеров с описанием того, что будет происходить в цикле.

1) Программа, увеличивающая некоторое значение n на 5 три раза подряд. После выполнения цикла получим

2) Программа, 5 раз выводящая слово Hello. Каждый раз слово появляется в новой строке. Это происходит из-за команды endl, которая и переводит строку. Сама команда endl, как нетрудно догадаться, расшифровывается как end line. Если её убрать то в выходном файле получим

Часть 2. Вложенные циклы.

Теперь поговорим о несколько более сложной конструкции — о вложенных циклах. Что будет если один цикл поместить в другой? Например вот так:

Вложенные циклы можно сравнить, например, с календарем. Год — это цикл из 12 месяцев. А в каждом месяце есть вложенный цикл в виде дней. То есть что бы пройти один круг на внешнем цикле нужно сначала полностью отмотать внутренний цикл. Рассмотрим программу, написанную выше.

Здесь внешний цикл — это тот, в котором принимает участие переменная i. Внутренний цикл соответственно с переменной j. Сначала, при i = 0, j пробежит по всем своим значениям от 0 до 4. Потом i перейдет на 1, а j вновь начнет нарезать круги с 0 до 4. И так, пока не закончится внешний цикл. То есть всего будет 3 * 5 = 15 итераций. А n в конце будет равно 30.

Примеры с вложенными циклами:

  1. Программа, выводящая строку «01234» три раза подряд, в три разные строки. Внутренний цикл будет увеличивать значение j и выводить эту переменную, а после завершения внутреннего цикла следует перевод строки, который повторится 3 раза.

2) Необязательно должно быть так, чтобы был только один вложенный цикл. Можно во вложенный цикл вложить еще один, тогда у нас появится как бы еще один внутренний круг. В этом примере у нас три цикла, а значение n принимает значения от 0 до 999

Совершенный цикл for

Сегодня необычный для меня формат статьи: я скорее задаю вопрос залу, нежели делюсь готовым рецептом. Впрочем, для инициирования дискуссии рецепт тоже предлагаю. Итак, сегодня мы поговорим о чувстве прекрасного.

Я довольно давно пишу код, и так вышло, что практически всегда на C++. Даже и не могу прикинуть, сколько раз я написал подобную конструкцию:

Хотя почему не могу, очень даже могу:

Наш текущий проект содержит 43 тысячи циклов. Проект пилю не я один, но команда маленькая и проект у меня не первый (и, надеюсь, не последний), так что в качестве грубой оценки пойдёт. А насколько такая запись цикла for хороша? Ведь на самом деле, важно даже не то количество раз, когда я цикл написал, а то количество раз, когда я цикл прочитал (см. отладка и code review). А тут речь очевидно идёт уже о миллионах.

На КПДВ узел под названием «совершенная петля» (perfection loop).

image

Так каков он, совершенный цикл?

А в чём проблема?

Мы пишем много кода для математического моделирования; код довольно плотный, с огромным количеством целых чисел, которые являются индексами ячеек, функций и прочей лабуды. Чтобы был понятен масштаб проблемы, давайте я просто приведу крохотный кусочек кода из нашего проекта:

У нас есть некая область, разбитая на тетраэдры, и мы на ней моделируем некий процесс. Для каждой итерации процесса мы проходимся по всем тетраэдрам сетки, затем по всем вершинам каждого тетраэдра, бла-бла-бла, и всё венчает цикл по всем трём измерениям нашего окружающего мира.

Мы обязаны иметь дело с кучей вложенных циклов; вышеприведённые пять вложенных далеко не предел. Мы уже довольно давно (лет пятнадцать как) пришли к выводу, что стандартный for (int i=0; i<size; i++) — это очень громоздкая конструкция: те самые пять вложенных заголовков for превращаются в совершенно нечитаемую кашу, и даже подсветка синтаксиса не спасает.

Когда мы читаем стандартный for(;;) , мы должны на каждой строчке обратить внимание на три вещи: на инициализацию, на условие выхода и собственно на инкремент. Но ведь это совершеннейший оверкилл для тех случаев, когда нам нужно пройтись от 0 до size-1 , а это подавляющее большинство всех циклов. Скажите, как часто вам приходится писать обратный цикл или итерацию с другими границами? Как мне кажется, один раз из десяти — это ещё щедрая оценка.

До появления c++11 мы в итоге пришли к страшной вещи, а именно ввели в самый верхний заголовок вот такой дефайн:

И тогда вышеприведённый кусок кода превращается из тыквы в кабачок:

Польза такой трансформации в том, что когда я встречаю for (;;) , я знаю, что мне нужно насторожиться и внимательно смотреть на все три места (инициализацию, условие, инкремент). В то время как если я вижу FOR(,) то это совершенно стандартный пробег от 0 до n-1 без каких-либо тонкостей. Я совершенно не предлагаю пользоваться вышеприведённым дефайном, но точно знаю, что для нашей команды он сберёг много ресурсов мозга, поскольку мы кода гораздо больше читаем (см. отладка), нежели пишем (как, наверное, и все программисты).

То есть, вопрос, которым я задаюсь, звучит так: «Как выглядит цикл, имеющий минимальную когнитивную нагрузку при чтении кода?«

Жизнь после 11го года, или range for

А как дела обстоят у соседей? Вы знаете, местами довольно недурно. Например, в лагере питонистов стандартный цикл выглядит следующим образом:

Что любопытно, до третьего питона range() создавал в памяти массив индексов, и проходился по нему. И со времён c++11 мы вполне можем делать точно так же!

Разумеется, явно создавать в памяти массив индексов это несерьёзно, и с третьей версии в питоне это тоже поняли. Но и в C++ мы можем сделать не хуже!

Давайте посмотрим на следующую функцию range(int n) :

Пожалуйста, не начинайте int vs size_t , разговор не об этом. Если скомпилировать этот код при помощи gcc 10.2 с флагами компиляции -std=c++17 -Wall -Wextra -pedantic -O1 , то мы получим следующий ассемблерный код (проверьте тут):

То есть, компилятор начисто убрал все эти обёртки и оставил голый инкремент, ровно как если бы мы написали обычный for (int i=0; i<13; i++) .

Лично мне кажется, что for (int i: range(n)) справляется с подчёркиванием обычности цикла чуть хуже, нежели FOR(,) , но тоже вполне достойно, и за это не нужно платить дополнительными тактами процессора.

Продолжаем подглядывать в замочную скважину: enumerate

Range for в c++11 нанёс большую пользу. Давайте скажем, что у меня есть массив трёхмерных точек, и мне нужно распечатать икс координаты каждой точки, это можно сделать следующим образом:

for (vec3 &p: points) это прекрасная конструкция, никаких костылей, сразу из стандарта языка. Но что если у меня каждая точка из массива имеет цвет, вес или вкус? Это можно представить ещё одним массивом того же размера, что и массив точек. И тогда для доступа к атрибуту мне всё же понадобится индекс, который мы можем сгенерировать, например, вот таким образом:

Для этого кода компилятор генерирует следующий ассемблер:

В принципе, имеет право на жизнь, но гулять так гулять, давайте снимем с программиста заботу о создании параллельного индекса, ровно как сделали в питоне, благо стандарт c++17 имеет structural binding!

Итак, можно сделать следующим образом:

Функция enumerate() определена в следующем заголовочном файле:

При компиляции с флагами -std=c++17 -Wall -Wextra -pedantic -O2 мы получим следующий ассемблерный код (проверьте тут):

И снова компилятор начисто убрал обёртку (правда, для этого пришлось поднять уровень оптимизации с -O1 на -O2 ).
Кстати, в c++20 появился std::ranges , что ещё больше упрощает написание такой функции, но я пока не готов переходить на этот стандарт.

Вопрос залу

На ваш взгляд, каким должен быть совершенный цикл в 2020м году? Научите меня!

Если вы ещё не задавались этим вопросом, то скопируйте к себе в пет-проект заголовочный файл range.h и попробуйте его поиспользовать хотя бы несколько дней.

Лекция 1. C.

Scratch помог нам изучить основные концепции программирования и теперь мы можем применить их в настоящем языке программирования, Cи.

Помните, на прошлой неделе, когда мы хотели запустить наше приложение в Scratch, мы знали, что нам нужно было использовать блок с названием когда щелкнут по зеленому флажку.

Наш пример реализации блока сказать : hello,world можно реализовать в Cи следующим образом:

printf похож на блок Scratch’a сказать , и он отобразит на экране всё, что будет внутри его кавычек.

Вот так мы начинаем наше знакомство с синтаксисом, двойные кавычки и точка с запятой — всё потихоньку.

В Scratch’e блок сказать представлял из себя функцию, которая принимала аргумент или параметр. В Си это можно сделать как-то так:

Управляющий символ \n создает новую строку — все равно, что нажать на Enter, находясь в блокноте.

В случае с циклами, в Scratch у нас есть блок всегда , который позволяет бесконечно повторять какое-то действие. В Си мы будем делать так:

Выражение, находящееся внутри фигурных скобок, будет выполняться снова и снова пока(while) условие, находящееся внутри скобок, будет истиной , и так как истина (true) всегда будет истиной, цикл никогда не завершится, бесконечно повторяя себя.

Чтобы цикл имел определенное количество повторов, надо будет немного усложнить код:

Мы вернемся к этому циклу чуть позже, а пока достаточно знать, что слово for или “для”, является особым, обозначающим начало цикла.

В Scratch, мы использовали блоки задать [i] значение [0] для представления переменных, способные хранить значения. В Си мы сделаем это так:

int обозначает integer , в нем мы создаем переменную для хранения целых чисел. i — это название нашей новой переменной, и 0 — это значение, которое мы ему присвоим.

Наконец, точка с запятой обозначает завершение данного выражения.

Булево выражения по сути своей являются вопросами, которые дают один результат: истину (true) или ложь (false) . В Scratch’e это выглядело как i < 50 или i меньше 50 . В Си все также просто :

С x < y тоже самое. Выражение одинаковое как в Scratch, так и в Си, до тех пор пока оба x и y будут представлять из себя созданные нами переменные, которым были присвоены значения.

Также можно использовать условия, позволяющие выбрать одну из нескольких дорог. В Scratch’e мы показывали что-то похожее на это:

image alt text

Всё это можно намного легче выразить в Cи:

Scratch не поленился показать нам списки, в которых мы могли хранить сразу несколько значений. В Си тоже самое проделывают массивы, в которых мы можем упорядоченно хранить огромное число элементов.

В прошлый раз Scratch представил нам такой блок как элемент (1) из [argv] , который доставал первый элемент из списка под названием argv . В Си (мы начинаем считать начиная с 0 , т.к. это самое маленькое неотрицательное число) мы гораздо легче сможем проделать вышеприведенную операцию argv[0] .

hello, C

Вернемся к нашему примеру:

main представляет из себя блок когда щелкнут по зеленой кнопке , и помечает основной (main) кусочек кода, который будет выполняться при любых условиях.

Этот код, читабельный для “обычных” людей (или понятный “людям”), необходимо перевести в машинный код, который выглядит как-то так:

Кто бы мог подумать…

Нельзя забывать: как бы мы не пытались убежать от той простой истины, что компьютер работает только с бинарными числами, 0 ми и 1 ми, каждая такая последовательность (шаблон) 0 ей и 1 иц представляет из себя специальные инструкции CPU (процессора — центрального процессорного устройства компьютера). Одна последовательность 0 и 1 будет обозначать “выведи это на экран”, другая последовательность — “сложи эти два числа” или любую другую операцию из огромного числа возможных.

Нам не нужно самим писать все эти числа, т.к. есть программы compilers (компиляторы), которые берут код, написанный на понятном для людей языке Си (source code или исходный код), и переводит его в машинный.

У каждого из нас установлены на рабочем компьютере, мало отличающимися друг от друга, различные операционные системы, такие как macOS, Windows или другие. Поэтому, чтобы избежать всевозможных несовместимостей, мы будем использовать “облачную” систему CS50 IDE.

Что это означает? Хотелось бы сказать “не ваше дело” и “просто поверьте на слово и хватит всем интересоваться”, но черт с вами… Это среда разработки, а точнее веб-среда разработки основанная на платформе Cloud9, позволяющая установить и настроить программное обеспечение, к которому потом может получить доступ огромное количество пользователей, у каждого из которых будет своя индивидуальная среда.

Заходим на страницу сайта, создаем бесплатный аккаунт и перед вами будет:

image alt text

Теперь у нас есть файл под названием hello.c . Привыкайте к тому, что файлы с исходным кодом написанным на Си, имеют расширение .c . Мы также напишем в редакторе:

Мы привыкли запускать программы на своих компьютерах двойным нажатием по иконке. Облачные компьютеры, которыми мы теперь будем пользоваться, используют операционную систему linux, которая часто поставляется с графическим интерфейсом (GUI), но больше она известна своим интерфейсом командной строки — ее мы и будем использовать.

В нижней части нашего рабочего пространства мы напишем clang hello.c :

clang (в языке программирования Си) является компилятором, которого мы просим скомпилировать наш файл hello.c .

/workspace/ обозначает, что мы находимся в папке под названием workspace , в которой находится hello.c (в этом можно удостовериться, посмотрев на список файлов в левой панели). Знак $ дает нам знать, что мы можем начинать писать команды.

После того как мы нажмем enter , покажется что ничего не произошло:

Но в итоге clang создаст программу, присвоив ей по-умолчанию имя a.out . Давайте её запустим:

Все отобразилось так, как мы и хотели. Также наш курсор переместился на новую строку. Вспомните, что в нашем исходном коде был дополнительный символ \n , который и создал эту новую строку.

Если бы мы его убрали из нашего исходного кода, сохранили, повторно скомпилировали, а затем и запустили — мы увидели бы следующее:

Программа бы запустилась, но без новой строки.

Поэтому мы оставим все как есть. И помните — каждый раз, когда мы хоть что-то меняем в исходном коде, его обязательно нужно повторно скомпилировать.

Мы еще можем попросить нашего нового друга clang ‘a, скомпилировать программу, присвоив ей красивое имя. Нам помогут аргументы (по-другому флаги или переключатели) командной строки:

Итак, по середине мы добавили -o для данных на выходе (т.е. для создаваемой программы), указав, что хотим присвоить им название hello .

Теперь можно нажать enter и запустить ./hello .

Но как-то слишком все это выглядит обременяющим, не говоря уже о том, что чем тяжелее будет наша программа, тем больше аргументов нам придется использовать. Поэтому гениальные (ленивые) разработчики создали программу make , которую мы и будем в дальнейшем использовать.

Но сперва кое-какие поправки. Мы исполним команду ls , чтобы показать все файлы нашей директории (папки) workspace :

Она выводит на экран список файлов, которые мы можем увидеть на левой стороне нашего рабочего пространства. Но на этом мы не остановимся:

Данная команда rm удаляет файл. Она просит нас подтвердить удаление — и как же тут отказать, пишем y (означает “yes” или “да”).

Выполнимые программы, т.е. те, что мы можем запустить, тоже выводятся командой ls , помеченные * (звездочкой) и особым цветом.

Давайте воспользуемся make :

Эта программа создаст запускаемый исполняемый файл с названием hello , который формируется из файла с исходным кодом hello.c . Все это она проделывает, использую только одно слово ( hello ).

После нажатия enter мы увидим длинную команду начинающуюся с clang . Тут огромное количество дополнительных аргументов (опций, которые нам пригодятся в будущем), но сейчас для нас самое главное то, что мы опять получили файл с названием hello , который мы можем запустить.

Другая командная строка linux, ой неправильно, остальные команды:

cd change directory (изменить папку), так мы перемещаемся по папкам

mkdir make directory (создать папку), создает папку

rm remove (удалить), удаляет файл

rmdir remove directory (удалить папку), удаляет папку

Библиотека CS50

Давайте создадим больше интересных программ.

Чтобы принимать вводимые пользователем данные, мы создали для вас несколько функций:

Создадим файл string.c (string или строка, представляет из себя последовательность символов):

Первые строчки кода включают библиотеки или группы написанных кем-то функций, которые мы можем использовать в нашем коде. Библиотека cs50.h содержит в себе приведенные выше функции, а stdio.h (Standard Input and Output/Стандартный Ввод и Вывод) содержит основные функции Си, как например printf . Библиотека cs50.h также дает нам возможность пользоваться особым типом переменной под названием string , которого нету в Си.

В нашей функции main мы сперва создадим переменную string под названием name и используем функцию get_string . Нам нужно закончить функцию () , т.к. мы можем использовать функцию, даже если ей не предоставить никаких аргументов (скобки оставляем пустыми). Результат полученный функцией get_string (какой-то текст) будет помещен в переменную name .

Затем на следующей строке мы воспользуемся странным синтаксисом %s , чтобы включить в текст, который отобразится на экране, значение переменной. Если бы мы просто написали printf(«hello, name\n») , на экране отобразилось бы hello, name . Но с %s мы можем добавить (или включить в текст) значение переменной name .

Теперь можно написать make string , а потом и ./string . Кажется что ничего не происходит. Программа просто ждет, что мы что-нибудь введем, а если быть точнее: она ждет текст (вы понимаете почему?). Давайте напишем David , нажмите enter и вы увидите, что она (программа) ответит вам hello, David , как и планировалось. Отлично!

Но давайте сделаем нашу программу немного понятнее. До того как мы попросим текст (string) от пользователя с помощью get_string , давайте сперва дадим ему понять, что мы от него хотим:

И получится, что вводить данные пользователь будет сразу после текста Name: , в той же строке.

Шаг за шагом, строчка за строчкой, маленькими детскими шагами мы построили нашу простую программу и в общем это отличная техника написания любой программы. Делая так, мы будем проверять наш код на каждом этапе, чтобы убедиться что все работает, как нам нужно.

Давайте пойдем немного другим путем:

Теперь мы получаем целое число ( integer ), хранящаяся в переменной i , затем отдаем ее printf в виде %i , т.к. %s является заполнителем для текста ( string ), а i хранит в себе не string (текст), а integer (целое число).

Если мы скомпилируем, запустим и напишем David , программа нам скажет Retry (повторить попытку), пока мы не дадим ей (программе или, если быть еще точнее, функции get_int ) число.

Эти первые примеры проведут нас (медленно, но с огромной пользой) через необходимые основы разработки, чтобы дальше мы могли создавать поистине потрясающие программы.

В действительности, Си позволяет нам получить еще больший контроль над процессами, происходящими в компьютере, и “смотреть под капот” (как код взаимодействует с железом) без каких-либо проблем.

Напишем другую короткую программу:

Итак, мы получили от пользователя два числа, x и y , создали новую переменную z , которая хранит сумму двух чисел, и вывели результат на экран.

Но мы создали лишнее целое число, которое, на самом деле, можно просто пропустить:

Обратите внимание на то, как мы реализуем все эти операции в Си. % дает нам остаток от деления первого числа на второе.

Что же мы ждем? Компилируем, запускаем и пишем 1 для x , после чего нажимаем Enter , далее 10 для y и опять Enter :

Все выглядит приемлемо кроме этой одной строки! Правильным ответом должен быть 0.1 , не так ли? Но помните что мы работаем с integer ‘ами (целыми числами) x и y , выводя их на экран и используя заполнитель %i , поэтому числа после запятой отбрасываются или вырезаются. (вместо 0.1 нам выдают 0 .)

Мы можем исправить данный недочет, используя другой тип переменной под названием float , для числа с плавающей запятой (нецелое число):

Теперь все правильно!

Типы Данных

Существует множество различных типов данных:

bool для булевого значения (true или false / истина или ложь)

char для одного символа

double для крупного нецелого числа с бо́льшим количеством битов, чем у обычного float

long long для крупного целого числа с бо́льшим количеством битов, чем у обычного int

Давайте напишем еще одну программу, чтобы показать сколько байтов выделяется под каждый из этих типов данных

Оказывается, если брать конкретно нашу систему Cloud9, bool будет занимать целый байт, char (character или символ) тоже возьмет 8 битов и т.д.

Но подождите, string (текст) не больше 8 байтов? Не волнуйтесь, совсем скоро мы расскажем вам как string может быть больше представленной величины.

Память в компьютере не может быть бесконечной и поэтому мы ограничены в количестве доступных байтов, поэтому мы можем хранить только ограниченное число цифр. К примеру представьте, что у нас есть бинарное число с 8 битами:

Если мы добавим к нему 1 , то получим 1 1 1 1 1 1 1 1 . Но что произойдет если мы добавим еще одну 1 ? Мы начнем переносить единицу над каждой цифрой пока не получим 0 0 0 0 0 0 0 0 , но у нас не осталось слева битов чтобы там сохранить это большее число.

В программах такое поведение встречается при работе с integer ‘ами:

Мы знаем, что для int выделяется 4 байта или 32 бита, т.e. 2^32 возможных значений, это около 4 миллиардов значений. Но половина этих значений отрицательная, поэтому самое высокое положительное значение около 2 миллиардов.

В этой программе мы начинаем с n равного 1 , каждый раз удваивая его:

Теперь мы знаем, что рано или поздно, если наше число будет только увеличиваться и станет больше размера выделенных для него битов, произойдет что-то ужасное. Этот “ужас” называют overflow (переполнение).

Мы можем поменять тип переменной n на long long и вывести его значение на экран, используя заполнитель %lld . Но, опять же, мы увидим, что дойдя до последнего шага — число “перевернется” и станет отрицательным.

В реальном мире, некоторые игры могут использовать integer ‘ы для обозначения значений (чисел), порождая на свет безумных диктаторов !

Еще более серьезные баги (ошибки) могут дать нам выключающиеся самолеты.

Другой баг floating-point imprecision (неточность нецелых чисел, следующих после запятой). У float ‘ов ограниченное количество битов. Но цифры следующие в числе после запятой — неограниченные, поэтому компьютеру приходится округлять и неточно отображать некоторые числа.

Давайте напишем программу, которая сможет нам это продемонстрировать:

Новая часть в коде %.55f просто говорит функции printf отобразить 55 чисел после запятой.

Мы использовали 1.0 и 10.0 только для того, чтобы удостоверится, что типы данных действительно будут float ‘ами (так как мы не задали их как переменные). Мы могли бы написать (float) 10 , чтобы изменить целое число 10 и превратить его в 10 с плавающей запятой (или точкой).

Запустим программу и получим:

Интересно, оказывается так выглядит приблизительное представление компьютера, числа 0.1 .

Вот клип о последствиях компьютерной неточности.

Давайте повторим — мы знаем, что есть несколько типов данных, которые мы имеем у себя в арсенале и можем представить (или отобразить в тексте) через символы (заполнители):

В дополнение к \n , который служит для добавления новой строки, мы можем использовать определенные escape sequences (управляющие последовательности) или символы, которые применяются в printf для добавления в текст кавычек, табуляции и т.п.:

Больше Си

Напишем программу, в которой гораздо шире используются идеи Scratch’а, что позволит нам реализовать более сложные конструкции кода Си:

Так как переменная i в последнем else (иначе) не больше и не меньше нуля, нам не нужно уточнять условие else if (i == 0) , по логике отсюда вытекает, что переменная i будет равна нулю. Обратите внимание, что мы используем == для сравнения двух переменных или значений из-за того, что один знак = служит для присвоения одного значения другому.

Можем немного поиграться с логикой:

Мы получаем от пользователя символ (character) c и сравниваем его либо с Y , либо с y или либо с N , либо с n . В Си мы используем || для указания логического или, где только одно из выражений должно быть истиной для выполнения условия. && для логического и, где оба выражения должны быть истиной, чтобы условие было выполнено.

Заметьте, мы используем одинарные кавычки вокруг символов, чтобы отличить их от текста (string), где есть только один символ и двойные кавычки соответственно.

Давайте по-другому создадим предыдущую программу:

Мы используем switch (переключатель), у которого есть различные случаи ( case ). Совпав с одним из них, переменная запустит выполнение кода, находящегося ниже случая ( case ‘а). И выполнение остановится только тогда, когда дойдет до выражения break ; (остановить, сломать), который выведет нас из конструкции switch ‘a (переключателя).

Удалим все строчки с break; выражениями и посмотрим что произойдет:

И в этом случае (хе-хе), все выражения, находящиеся ниже первого совпавшего случая ( case ), будут выполнены.

Давайте посмотрим, как разрабатывать дизайн кода, создав свою функцию:

Заметьте, что ниже нашей функции main мы создали новую, назвав ее print_name . Эта функция принимает переменную с типом string (текст или строку) в виде параметра, к которому она будет в дальнейшем обращаться как к name . Никаких значений эта функция не возвращает, поэтому мы зададим ее тип возвращаемого значения как void (пустота/ничего). В то время как main возвращает значение с типом int . Потом еще поговорим на эту тему.

Но теперь, если мы скажем программе print_name(s) в нашей функции main , то мы все равно получим ошибку. Все это из-за того, что компилятор читает код сверху вниз, по порядку. Поэтому, когда main вызывает функцию print_name , она еще не существует для компилятора. Поэтому нам нужно объявить ее, используя prototype (прототип):

Мы пишем над функцией main прототип void print_name(string name) , указывая то, какие параметры она получит, а затем и вернет, для того чтобы компилятор поискал ее (функцию) немного позже.

И внутри наших библиотек cs50.h и stdio.h находятся точно такие же прототипы — однострочные выражения, объявляющие функции подобные get_string и printf с их имплементацией (implementation или реализацией) в другом файле.

Чтобы продемонстрировать возвращаемые значения, мы можем написать программу следующим образом:

Функция square принимает в виде параметра переменную типа int и возвращает что-то с типом данных int . Внутри функции выполняется только return n * n .

Теперь мы можем использовать функцию square(x) в нашей другой функции printf , которая будет ждать результат с типом данных int .

Если мы вернемся к нашей начальной функции get_string , мы можем кое-что понять. Например, что у функции get_string возможно есть прототип, который выглядит как-то так string get_string(void) , так как она выполняется не принимая никаких параметров или аргументов (с пустыми скобками), но возвращает введенный пользователем текст:

Давайте немного улучшим наш блок cough , который мы создавали в Scratch’e на 0-ой неделе:

Здесь мы пытаемся отобразить на экране текст cough три раза.

Итак, добавим в код цикл, т.к. у нас идет повтор одинаковых выражений:

Напишем нашу собственную функцию, чтобы использовать ее когда нам угодно, любое количество раз:

Кажется, что мы потратили излишне много усилий для создания данного примера, но чем сложнее будет становится наша программа, тем больше мы будем нуждаться в таких блоках и созданных нами функциях (custom functions).

К примеру, если бы мы хотели вызвать cough несколько раз, а еще и achoo определенное количество раз, то для нас это не должно составить никакого труда:

Заметьте, что функция main может без труда вызвать cough и achoo определенное количество раз, указывая этот параметр в вызове каждой функции. Они же вызывают другую функцию say , которая содержит в себе реализацию цикла for и отображение текста на экране через printf .

Посмотрите на say — эта функция теперь тоже принимает два аргумента: один string (текст), а другой int (число). Поэтому каждый раз, когда вызывается эта функция, оба аргумента должны быть указаны.

Мы называем эту концепцию abstraction (абстракцией). В ней мы строим различные слои, в каждом из которых одновременно могут работать несколько людей, в итоге выполняя общую работу, где каждый человек реализовывает свою часть кода.

На самом деле, все это время мы пользовались абстракцией вызывая get_string или printf , т.к. мы знаем, как в действительности эти функции имплементированы (реализованы) в других файлах, которые мы включаем (задействуем). Теперь мы можем их использовать, зная их предназначение.

Давайте теперь посмотрим, чем же занимается make . Компиляция включает в себя несколько этапов.

препроцессирование (preprocessing)

  • Строчки, которые начинаются с #, как например #include, препроцессируются. #include заставляет наш компилятор искать файл в нашем компьютере и буквально включить (include), или добавить, внутрь нашего кода строчки из препроцессируемого файла (т.е. скопировать и вставить все содержимое).

компиляция

  • Мы пропишем команду clang -S hello.c и увидим, как наша Си программа компилируется в другой язык (код), именуемый ассемблером. Он оснащен простыми инструкциями, которые понимает процессор или CPU (как например сложение чисел, их передвижение в памяти и т.д.). Они прописаны в текстовом формате, чтобы люди могли прочитать и изменить ход работы этих инструкций.

ассемблирование

  • Промежуточный ассемблеровский код переводится в машинный код, 0 и 1 , которые процессор действительно может понимать.

соединение

  • Этот финальный шаг соединяет машинный код нашей программы и машинный код всех библиотек, которые мы ранее подключили и использовали. Соединяет их, в итоге выдавая нам финальную версию программы со всеми необходимыми кусочками кода. (Препроссированные через #include файлы, представляют из себя заголовочные (header) файлы, которые содержат в себе только прототипы используемых нами функций. Настоящая имплементация, или реализация, в нашем случае уже машинного кода находится в других файлах.)

Итак, мы разобрали огромное количество новых концепций программирования, но будем надеяться, что со временем вы освоите то, что было приведено здесь и сможете легко их использовать!

В конце концов, вы сможете видеть в любой поставленной проблеме определенный шаблон действий, создавать дизайн кода и абстракции, для написания качественных программ.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *