Пусть художники понимают указатели, если они хотят.
Вперед
Когда-то давно я участвовал в академической войне, которая изменила STEM на STEAM. В настоящее время моя активность направлена на равенство трансгендеров. В некотором смысле с трудом извлеченные уроки политики «левое полушарие» против «правого полушария» стали для меня хорошей подготовкой к борьбе с гендерным бинаризмом, гораздо более несправедливой парадигмой.
Я вырос в то время, когда дизайнеры не занимались программированием. Вроде даже не HTML. Позже в колледже я внес свой вклад в движение искусства / дизайна, которое фактически преодолело это культурное невежество и пожинало плоды этого перекрестного опыления. И мы сделали это рано, в то время как все остальные критиковали нас за то, что мы слишком отвратительны или недостаточно креативны. К счастью, наша индустрия дизайна превратилась в сплющенный плот междисциплинарных единорогов, и теперь я сливаюсь с ним.
Приблизительно в 2013 году я изначально планировал этот графический тур как главу 2 Книги, более масштабного совместного проекта, который продолжается и сегодня. Эта публикация представляет собой снимок этой главы, взятый из книги на Github. Большое спасибо сообществу за бесчисленные правки. Большое спасибо ideone за то, что он не развивается так быстро :-)
Хотя в книге определенно есть ссылка на OpenFrameworks, и хотя этот проект очень полезен, пожалуйста, рассматривайте это письмо как более общее, случайное и переносимое введение в C ++. В конце я дам ссылки на канонические чтения, если вы захотите повторить это еще раз более подробно и строго.
Посмотрите вживую!
Эта глава знакомит вас с написанием небольших компьютерных программ с использованием языка C ++. Хотя я очень мало предполагаю ваши предыдущие знания, грамотность, которую вы приобретете из этой главы, напрямую повлияет на ваше понимание в последующих главах книги, так как большинство других тем ложится на плечи этой. Кроме того, уроки здесь являются накопительными, что означает, что вы не можете пропустить ни одну из тем, иначе вы заблудитесь. Если вы застряли на одной из концепций, обратитесь за помощью в понимании именно той части, которая не имела смысла, прежде чем переходить к следующей теме. Следование урокам с такой строгостью гарантирует, что вы получите максимальную отдачу от OpenFrameworks, а также от компьютеров в целом.
Итерация
Я рисовал и рисовал большую часть своего времени в середине девяностых, ученица художественной школы средней школы с длинным черным хвостом, выбритым ступенькой, круглыми очками и ни одной одеждой без пролития, швыряния, разбрызгивания или пятен. акриловой краски Liquitex Basics. Я потерял сознание на уроках экономики, играя со своим графическим калькулятором TI-82, и обнаружил нечто, от чего в моем сердце загорелась лампочка. В отличие от небольших калькуляторов, которые жили в моем доме, когда я рос, у TI-82 было толстое руководство по эксплуатации. Среди разделов этого руководства о триггерных функциях и другой сухой недоступной науке кое-что привлекло мой жаждущий молодой глаз: сексуальная пирамида черного на белом с меньшими перевернутыми пирамидами, бесконечно вложенными внутри, показанная на рисунке 1.
Этот фрактал, знаменитый треугольник Серпинского, сопровождал около двадцати пяти компьютерных инструкций, составляющих полную программу SIERPINS. Я внимательно присмотрелся к коду и увидел несколько числовых операций - ничего слишком сложного, и в основном это были командные слова, вроде сделай это или если что-то сделай, то сделай другое. Я смог ввести код из книги в графический калькулятор и запустить программу. Сначала просто пустая ЖК-панель. Медленно несколько случайных пикселей тут и там стали черными, на самом деле не показывая никакого рисунка. Еще через несколько секунд сцена наполнилась, и я уже мог видеть слабые очертания треугольников. Спустя долгое время мой калькулятор наконец соответствовал изображению в книге. Мой разум был официально взорван. Некоторые вещи не имели смысла. Какое чудо природы привело к появлению такой сложной формы из-за такого небольшого количества наставлений? На экране было более шести тысяч пикселей, так почему же для создания этого удивительного, похожего на организм произведения искусства потребовалось всего двадцать пять инструкций? Чье это было произведение искусства? Могу ли я извлечь из этого новую работу? Я редко когда-либо видел такую магическую награду за такую небольшую работу. Я нашел свои новые основы. Я чувствовал необходимость понять программу, потому что (я решил) это было важно. Я вернулся к коду и изменил некоторые числа, а затем снова запустил программу. Экран погас, затем нарисовал другую картинку, только на этот раз перекошенную влево, выпадающую из окна просмотра. Чувствуя себя более смелым, я попытался изменить одну из инструкций на английском языке, и машина показала ошибку: не запускалась.
Цикл, показанный на рисунке 2, представляет собой бесконечно повторяющийся цикл, который я с большим удовольствием выполняю в течение нескольких десятилетий, и мне до сих пор нравится то, что я делаю. Каждый новый цикл меня никогда не перестает удивлять. По мере того, как я пытаюсь понять, что значит создать программу и что значит создать программное обеспечение, процесс итеративного развития списка компьютерных инструкций всегда представляет собой столько же логических проблем, сколько и художественное вознаграждение. Очень немногие из этих проблем было невозможно решить, особенно с другими людьми, готовыми сотрудничать и помогать, или путем разделения моей головоломки на более мелкие головоломки. Если вы уже написали код в другой среде, такой как Processing, Javascript или даже HTML с CSS, то этот первый важный урок может показаться слишком очевидным.
Для тех, кто только что знакомится с тем, что значит писать небольшие программы, важно понимать итеративную природу процесса написания кода. Анекдот на экране 3 показывает, чем этот процесс не. Редко вы когда-нибудь вводите код в редактор только один раз, ожидая, что нажмете компиляцию и увидите готовый результат. Естественно и общепринято, что программы начинаются с малого, имеют множество ошибок (багов) и медленно развиваются в направлении желаемого результата или поведения. На самом деле это настолько банально, что делать первое предположение - явная ошибка программиста. Даже в прежние времена, когда программы писались от руки на бумаге, автору все еще приходилось упорно следить за кодом, чтобы исправить ошибки; поэтому процесс был повторяющимся. Изучая язык C ++, я буду предлагать крошечные примеры кода, которые вы будете компилировать на своей машине. Ненормальная часть - это набирать код из книги в редактор, и (при условии, что ваши пальцы не скользят) программа волшебным образом запускается. Я намеренно исключаю возможность устранения неполадок, чтобы изолировать тему самого языка C ++. Позже мы рассмотрим обычную задачу отладки (исправления ошибок) как отдельную тему.
Компиляция моего первого приложения
Давайте начнем с создания самой маленькой, самой быстрой программы на C ++, а затем воспользуемся удобной средой для тестирования небольших фрагментов кода C ++ на протяжении всей этой главы. Для этого у нас должен быть компилятор, который представляет собой программу, переводящую некоторый код в реальное запускаемое приложение, иногда называемое исполняемым файлом. Компиляторы C ++ в основном можно загрузить бесплатно, а во многих случаях - с открытым исходным кодом. Создаваемые нами приложения не будут автоматически отображаться в таких местах, как Apple App Store, Google Play, Steam, Ubuntu Apps Directory или Pi Store. Вместо этого они являются вашими личными, частными программными файлами, и вы должны будете позже поделиться ими вручную. В следующей главе Настройка и структура проекта компилятор будет находиться на вашем локальном компьютере и работать в автономном режиме. А пока мы проявим нетерпение и скомпилируем немного случайного C ++ в Интернете с помощью удобного инструмента от Sphere Research Labs. Откройте браузер и перейдите на ideone (https://ideone.com).
Вы сразу заметите, что есть редактор, уже содержащий некоторый код, но он может быть установлен на другой язык. Давайте переключим язык на C ++ 11, если он еще не находится в этом режиме. Внизу в левом нижнем углу редактора нажмите кнопку слева от «stdin», как показано на рисунке 4. Ярлыком для этой кнопки может быть любое количество элементов.
Выпадающее меню со списком языков программирования. Выберите C ++ 11, показанный на рисунке 5.
Обратите внимание, что код в редакторе изменился и выглядит примерно как на рисунке 6.
Это просто пустой шаблон кода, который ничего не делает и не создает ошибок. Цифры в левом желобе указывают номер строки кода. Нажмите зеленую кнопку с надписью Выполнить. Вы увидите копию кода «Успешно» в комментариях, а раздел с пометкой stdin (стандартный ввод) будет пуст. stdout (стандартный вывод) также будет пустым.
Интерлюдия о типографике
Большинство шрифтов в Интернете имеют переменную ширину, то есть буквы имеют разную ширину; глазу это удобно для чтения. Шрифты также могут иметь фиксированную ширину, то есть все буквы (даже W и строчная i) имеют одинаковую ширину. Хотя это может выглядеть забавно и количественно, как пишущая машинка, оно служит важной цели. Шрифт фиксированной ширины превращает блок текста в своего рода игровую доску, например, шахматные квадраты или графическую бумагу. Код компьютерного программирования обычно представлен набором фиксированной ширины, потому что это форма ascii-art. Отступы, символы пробелов и повторяющиеся шаблоны - все это важно сохранить, и их легко можно будет сравнить. Все известные мне программисты, кроме художника Джереми Ротзтейна, используют в своем коде какой-то моноширинный шрифт. Некоторые варианты шрифтов: Courier, Andale Mono, Monaco, Profont, Monofur, Proggy, Droid Sans Mono, Deja Vu Sans Mono, Consolas и Inconsolata. С этого момента вы увидите, что стиль шрифта переключился на this inline style
. . .
and this style encased in a block . . .
. . . а это просто означает, что вы смотрите на какой-то код.
Комментарии
Теперь нажмите Edit (рисунок 7) в левом верхнем углу редактора кода.
Вы увидите немного другую конфигурацию редактирования, но тот же код шаблона все еще будет доступен для редактирования вверху. Теперь мы отредактируем код. Найдите строку 5, где написано:
// your code goes here .
Строка, начинающаяся с двойной косой черты, называется комментарием. Вы можете ввести все, что вам нужно, чтобы аннотировать свой код так, как вы понимаете. Иногда полезно «закомментировать код», поместив перед ним две косые черты, потому что это деактивирует код C ++, не удаляя его. Комментарии в C ++ также могут занимать несколько строк или вставляться как тег. Синтаксис для начала и окончания режима комментариев различается. Все, что находится между /* and the */
, становится комментарием:
/* this is a multi-line comment. still in comment mode. */
Удалите код в строке 5 и замените его следующим утверждением:
cout << "Hello World" << endl;
Эта строка кода сообщает компьютеру сказать «Hello World» в подразумеваемое текстовое пространство, известное как стандартный вывод (также известное как stdout). При написании программы можно с уверенностью ожидать существования stdout. Программа сможет «распечатать» в нем текст. В других случаях это просто окно в вашем инструменте кодирования, используемое только для устранения неполадок.
Между этими кавычками вы можете поместить что угодно. Цитированная фраза - это так называемая строка текста. В частности, это строковый литерал c. Подробнее о струнах мы поговорим позже в этой главе. В коде часть chunk cout <<
означает «отправить следующий материал в стандартный вывод в отформатированном виде». Последний фрагмент << endl
означает «добавить символ возврата каретки (конец строки) в конец сообщения hello world». Наконец, в самом конце этой строки кода вы видите точку с запятой (;).
В C ++ точки с запятой похожи на точку или точку в конце предложения. Мы должны вводить точку с запятой после каждого оператора, и обычно она находится в конце строки кода. Если вы забудете ввести точку с запятой, компиляция завершится ошибкой. Точки с запятой полезны, потому что они позволяют нескольким операторам совместно использовать одну строку или одному оператору занимать несколько строк, что позволяет программисту быть гибким и выразительным с одним пробелом. Добавляя точку с запятой, вы гарантируете, что компилятор не запутается: вы помогаете ему и показываете, где заканчивается оператор. Когда вы впервые изучаете C или C ++, забывание точки с запятой может быть чрезвычайно распространенной ошибкой, и тем не менее это необходимо для компиляции кода. Будьте особенно внимательны и убедитесь, что операторы кода заканчиваются точкой с запятой.
Во время набора, возможно, вы заметили, что текст сам по себе стал разноцветным. Эта удобная функция, называемая раскраской синтаксиса, может подсознательно улучшить способность читать код, устранять неполадки в синтаксисе неправильного формата и помогать в поиске. У каждого инструмента будет своя собственная система раскраски синтаксиса, поэтому, если вы хотите изменить цвета, пожалуйста, ожидайте, что это не то же самое, что текстовый процессор, цвет которого вы сами добавляете в документ. Редактор кода не позволяет мне присвоить шрифт «TRON.TTF» светящегося бирюзового цвета только endl
(что означает конец строки). Вместо этого я могу выбрать особый стиль для целой категории синтаксиса и видеть все части моего кода, оформленные таким образом, если это такой тип кода. В этом случае и cout
, и endl
считаются ключевыми словами, поэтому инструмент окрашивает их в черный цвет. Если в другом месте эти вещи отображаются разными цветами, пожалуйста, поверьте, что это тот же код, что и раньше, поскольку разные редакторы кода предоставляют разную окраску синтаксиса. Теперь весь код должен выглядеть так:
#include <iostream.h> using namespace std; int main(){ cout << "Hello World" << endl; return 0; }
Теперь нажмите зеленую кнопку ideone it! в правом нижнем углу и посмотрите на консоль вывода, которая является нижней половиной редактора кода, прямо над этой зеленой кнопкой. Вы увидите оранжевые сообщения о состоянии с такими вещами, как «Ожидание компиляции», «Компиляция» и «Выполняется». Вскоре после этого программа запустится в облаке, и стандартный вывод должен появиться на этой веб-странице. Вы должны увидеть новое сообщение на рисунке 8.
Вы зашли так далеко. А теперь похлопайте себя по спине. Вы только что написали свою первую строчку кода на C ++; вы проанализировали его, скомпилировали, запустили и увидели результат.
За пределами Hello World
Теперь, когда мы промокли ноги, давайте вернемся и проанализируем другие части кода. Первая строка - это оператор включения:
#include <iostream>
Подобно import в Java и CSS, #include
напоминает компилятору вырезать и вставить другой полезный код из файла с именем iostream.h в эту позицию в файле, так что вы можете положиться на его код в своем новом коде. В этом случае iostream.h предоставляет cout
и endl
в качестве инструментов, которые я могу использовать в своем коде, просто вводя их имена. В C ++ имя файла, оканчивающееся на .h, называется файлом заголовка, и он содержит код, который вы должны включить в фактический файл реализации C ++, имя файла которого будет заканчиваться на .cpp. В C ++ встроено множество стандартных заголовков, которые предоставляют различные базовые услуги - на самом деле их слишком много, чтобы упоминать здесь. Если этого было недостаточно, также можно добавить в проект внешнюю библиотеку, включая ее заголовки. Вы также можете определить свои собственные файлы заголовков как часть написанного кода, но синтаксис немного отличается:
#include "MyCustomInclude.h"
В OpenFrameworks двойные кавычки используются для включения файлов заголовков, которые не являются частью установки системы.
Что с #?
Это целая история, но ее стоит понимать концептуально. Оператор include на самом деле не является кодом C ++ (обратите внимание на отсутствие точки с запятой). Это часть полностью отдельного этапа компиляции, называемого препроцессором. Это происходит до того, как будут обработаны ваши фактические программные инструкции. Они похожи на инструкции для компилятора кода, в отличие от инструкций для компьютера, запускаемых после компиляции. Используя символ решетки / решетки перед этими директивами препроцессора, можно четко заметить их в файле, и на то есть веские причины. Их следует рассматривать как другой язык, смешанный с реальным кодом C ++. Директив препроцессора C ++ не так уж и много - они в основном связаны с скоплением другого кода. Вот некоторые из них, которые вы можете увидеть.
#define
#elif
#else
#endif
#error
#if
#ifdef
#include
#line
#pragma
#undef
Проведем эксперимент. В редакторе кода закомментируйте директиву include в строке 1, а затем запустите код. Чтобы закомментировать строку кода, вставьте две смежные косые черты в начале строки.
//#include <iostream>
Цвет синтаксиса изменится на зеленый, то есть теперь это просто комментарий. Запустите код, нажав большую зеленую кнопку в правом нижнем углу, и вы увидите что-то новое на панели вывода.
prog.cpp: In function ‘int main()’:
prog.cpp:5:2: error: ‘cout’ was not declared in this scope
cout << "Hello World" << endl;
^
prog.cpp:5:27: error: ‘endl’ was not declared in this scope
cout << "Hello World" << endl;
^
Компилятор обнаружил ошибку и не запустил программу. Вместо этого, пытаясь помочь вам исправить это, компилятор показывает вам, где он запутался, пытаясь помочь вам исправить это. Первая часть, prog.cpp: сообщает вам файл, содержащий ошибку. В этом случае ideone.com сохранил ваш код под этим именем файла по умолчанию. Затем он говорит In function ‘int main()’
: файл, показывающий вам конкретный раздел кода, который содержит ошибку, в данном случае между {фигурной скобкой} функции с именем main. (О функциях и фигурных скобках мы поговорим позже). В следующей строке мы видим prog.cpp:5:2:
. 5 - это количество строк от начала файла, а 2 - количество символов вправо от начала строки. Далее видим error: ‘cout’ was not declared in this scope
. Это сообщение, описывающее, что, по его мнению, неправильно в коде. В данном случае это довольно правильно. iostream.h отсутствует, поэтому cout
нам не предоставляется, поэтому, когда мы пытаемся отправить «Hello World», компиляция завершается ошибкой. В следующих двух строках вы видите строку кода, содержащую ошибочный cout
, плюс дополнительный маленький символ вставки вверх в строке под ним, и это должна быть стрелка, указывающая на символ в коде. В этом случае стрелка должна находиться под буквой «c» в cout
. Система визуально показывает, какой токен неисправен. Отображается вторая ошибка, и на этот раз компилятор жалуется, что нет endl. Конечно, мы знаем, что для исправления ошибки нам нужно включить <iostream.h>
, так что давайте сделаем это сейчас. Не комментируйте строку 1 и повторно запустите код.
#include <iostream>
При использовании OpenFrameworks у вас есть выбор инструментов и платформ. Каждый показывает вам ошибку по-своему. Иногда редактор открывается и выделяет код за вас, помещая всплывающее сообщение об ошибке для получения дополнительной информации. В других случаях редактор ничего не покажет, но вывод компиляции покажет необработанную ошибку, отформатированную так же, как указанная выше. Иногда полезно получать несколько ошибок из-за компиляции, но это может сэкономить много времени, если вы сосредоточитесь на понимании и исправлении самой первой обнаруженной ошибки. После исправления основной ошибки вполне вероятно, что все последующие ошибки элегантно исчезнут, поскольку все они были покрыты вашим первым исправлением. Комментируя эту единственную строку кода вверху, мы вызвали две ошибки.
Пространства имен с первого взгляда
Переходя к строке 2, мы видим:
using namespace std;
Допустим, вы присоединились к социальному сайту, и вам предлагается выбрать имя пользователя. Мое имя при рождении начиналось с буквы J, поэтому я получаю J в JT. Меня зовут Джей Т. Нимой. Но веб-сайты могут предлагать JNIMOY. Я отправляю страницу, и она возвращает ошибку, сообщающую мне, что имя пользователя уже занято, и я должен выбрать другое, поскольку мой отец, Джозеф Нимой, зарегистрировался раньше меня, и у него есть JNIMOY. Поэтому я должен использовать начальную букву T в середине и создать уникальное имя пользователя JTNIMOY. Я только что создал и разрешил конфликт пространств имен. Пространство имен - это группа уникальных имен - ни одно из них не идентично. Могут быть идентичные имена, если они являются частью двух отдельных пространств имен. Пространства имен помогают программистам не наступать друг другу на пятки, перезаписывая символы друг друга или забивая хорошие имена. Пространства имен также обеспечивают аккуратную систему организации, которая помогает нам находить то, что мы ищем. В OpenFrameworks все начинается с of
. . . как ofSetBackground
и ofGraphics
. Это один из способов разделения пространства имен, поскольку маловероятно, что любые другие имена, созданные другими программистами, будут начинаться с of
. Тот же метод используется OpenGL. Каждое имя в OpenGL API (интерфейс прикладного программирования) начинается с gl
, например, glBlendFunc
и glPopMatrix
. Однако в C ++ нет необходимости иметь строго дисциплинированный символьный префикс для ваших имен, поскольку язык предоставляет свой собственный синтаксис пространств имен. В строке 2 using namespace std;
сообщает компилятору, что этот файл .cpp будет использовать все имена из пространства имен std
. Осторожно, спойлеры! эти два имени cout
и endl
. Давайте теперь проведем эксперимент и закомментируем строку 2, а затем запустим код. Как вы думаете, какую ошибку вернет компилятор?
/* using namespace std; */
Это очень похожая ошибка, когда не удается найти cout
или endl
, но на этот раз в список сообщений добавлены предлагаемые альтернативы.
prog.cpp:5:2: note: suggested alternative:
In file included from prog.cpp:1:0:
/usr/include/c++/4.8/iostream:61:18: note: ‘std::cout’
extern ostream cout; /// Linked to standard output
^
Компилятор говорит: «Привет, я искал cout
и нашел его в одном из пространств имен, включенных в файл. Вот. std::cout
'и в этом случае компилятор правильный. Он хочет, чтобы мы были более явными в том, как мы набираем cout
, поэтому мы выражаем его пространство имен std
(стандартное) с левой стороны, соединенное двойным двоеточием (: :). это все равно что называть себя Nimoy::JT
. Продолжая наш эксперимент, отредактируйте строку 5 так, чтобы в cout
и endl
были добавлены явные пространства имен.
std::cout << "Hello World" << std::endl;
Когда вы запустите код, вы увидите, что он компилируется нормально и успешно печатает «Hello World». Хотя строка с надписью using namespace std;
все еще закомментирована. Теперь представьте, что вы пишете программу для случайного создания текста песни. Очевидно, вы будете использовать cout
совсем немного. Необходимость набирать std::
перед всеми вашими cout
станет действительно утомительной, и одна из причин, по которой язык программирования добавляет эти функции, - это сокращение набора текста. Таким образом, хотя строка 2 using namespace std;
не была обязательной, ее наличие (вместе с другими using namespace
операторами) может упростить ввод и чтение кода C ++ с помощью подразумеваемого контекста.
Скажем, я на вечеринке по скрэбблу на Манхэттене, и я единственный Джей Ти. Люди могут просто звать меня JT, когда придет моя очередь играть. Однако, если JT Salizar присоединяется к нам после обеда, это немного сбивает с толку, и для ясности мы начинаем называть JT по имени и фамилии. То же верно и для C ++. Я был бы Nimoy::JT
, а он был бы Salizar::JT
. Допустимо иметь два разных cout
имени, одно из пространства имен std
, а другое из пространства имен improved
, если оба они выражены с явными пространствами имен; std::cout
и improved::cout
. Фактически, компилятор пожалуется, если вы этого не сделаете.
Когда я представлю классы, вы увидите больше синтаксиса с двойным двоеточием (: :).
Функции
Продолжая, давайте посмотрим на строку 4:
int main() {
Это первый фрагмент кода, у которого есть начало и конец, так что он «обертывает» другой фрагмент кода. Но что еще более важно, функция представляет заключенные в нее операторы. Закрывающим концом этой функции является закрывающая фигурная скобка в строке 7:
}
В C ++ мы заключаем группы операторов кода внутри функций, и каждую функцию можно рассматривать как небольшую программу внутри большой программы, как на упрощенной диаграмме на рисунке 9.
У каждой из этих функций есть имя, по которому мы можем ее назвать. Вызов функции означает выполнение операторов кода, содержащихся внутри этой функции. Основное удобство при этом заключается в меньшем количестве набора текста, а о других преимуществах мы поговорим позже. Как и в настольной игре, у программы есть стартовая позиция. Точнее, у программы есть точка входа, которую компилятор ожидает там. Эта точка входа - функция с именем main. Код, который вы пишете внутри функции main, является первым кодом, который выполняется в вашей программе, и поэтому он отвечает за вызов любых других функций в вашей программе. Кто вызывает вашу функцию main? Операционная система делает! Давайте разберем синтаксис основной функции в этой демонстрации. Опять же, для всех вас, программистов Processing, это старая новость.
При определении функции первым токеном является объявленный возвращаемый тип. Функции могут дополнительно возвращать значение, например ответ на вопрос, решение проблемы, результат задачи или продукт процесса. В этом случае main обещает вернуть тип int
или integer, который представляет собой целое число без дробной или десятичной составляющей. Следующий токен - это имя нашей функции. Система ожидает, что слово «основной» будет в нижнем регистре, но позже вы определите свои собственные функции, и мы перейдем к именованию. Далее следует открывающая и закрывающая круглые скобки. Да, это кажется странным, потому что внутри ничего нет. Позже мы увидим, что там происходит, но никогда не упускайте пару скобок с функциями, потому что в определенном смысле это главный намек для человека на то, что это функция. Фактически, с этого момента, когда я буду ссылаться на функцию по имени, я буду добавлять к ней суффикс (), например main()
.
Далее мы видим открывающуюся фигурную скобку. Иногда эта открывающая фигурная скобка находится на той же строке, что и предыдущая закрывающая скобка, а в других случаях вы увидите ее на отдельной новой строке. Это зависит от личного стиля кодера, проекта или группы - и то и другое подходит. Полную справку о различных стилях отступа см. В статье Википедии о стиле отступа (https://en.wikipedia.org/wiki/Indent_style).
Между этой открывающей фигурной скобкой и закрывающей мы помещаем операторы кода, которые фактически говорят компьютеру что-то сделать. В этом примере у меня есть только один оператор, и это обязательный return
. Если вы оставите это значение для функции, тип возврата которой int
, компилятор пожалуется, что вы нарушили свое обещание вернуть int. В этом случае операционная система интерпретирует 0 как «ничего не пошло не так». Просто для удовольствия посмотрим, что произойдет, если вы измените 0 на 1 и запустите код.
Пользовательские функции
Теперь мы определим нашу собственную функцию и будем использовать ее как шаблон слова. Введите пример кода в свой редактор и запустите его.
#include <iostream> using namespace std; void greet(string person){ cout << "Hi there " << person << "." << endl; } int main() { greet("moon"); greet("red balloon"); greet("comb"); greet("brush"); greet("bowl full of mush"); return 0; }
Результат показывает знакомую сказку на ночь.
Hi there moon.
Hi there red balloon.
Hi there comb.
Hi there brush.
Hi there bowl full of mush.
В этом новом коде обратите внимание на вторую функцию greet()
, которая выглядит так же, но отличается от main()
. Он имеет те же фигурные скобки для хранения блока кода, но тип возвращаемого значения отличается. В нем та же пара круглых скобок, но на этот раз что-то внутри. А что насчет этого обязательного оператора возврата? Ключевое слово void используется вместо типа возврата, когда функция ничего не возвращает. Итак, поскольку greet()
имеет тип возврата void, компилятор не будет жаловаться, если вы оставите return
. В скобках вы видите string person
. Это параметр, входное значение для использования функцией. В этом случае это немного похоже на поиск и замену. Внизу в main()
вы видите, что я вызываю greet()
пять раз, и каждый раз я помещаю другую строку в кавычки между круглыми скобками. Это аргументы.
Кроме того, чтобы помочь в технических аспектах различать, когда их вызывать аргументами и когда их вызывать параметрами, см. Этот пример кода:
void myFunction(int parameter1, int parameter2){ //todo: code } int main(){ int argument1 = 4; int argument2 = 5; myFunction(argument1,argument2); return 0; }
Возвращаясь к предыдущему примеру, все эти пять строк кода являются вызовами функций. Они говорят greet()
о выполнении и передают ему одностроковый аргумент, чтобы он мог выполнять свою работу. Этот единственный строковый аргумент доступен внутреннему коду greet()
через аргумент person
. Чтобы увидеть порядок вещей, взгляните на рисунок 11.
Цветная линия на рисунке 11 - это путь, нарисованный воображаемой головкой воспроизведения, которая переступает через код во время его выполнения. Мы начинаем с синей части и проходим через основную точку входа, затем встречаем greet()
, где происходит прыжок. Когда линия становится зеленой, она временно ускользает из main()
, чтобы можно было какое-то время следовать за greet()
. Когда строка становится желтой, вы видите, что она завершила выполнение содержащегося внутри кода greet()
и выполняет второй переход (возврат), на этот раз возвращаясь к предыдущему сохраненному месту, где он переходит к следующему оператору. Наиболее очевидное преимущество, которое мы видим в этом примере, - это сокращение сложности от длинного cout
оператора до простого вызова greet()
. Если мы должны вызвать greet()
пять раз, то наличие подпрограммы, инкапсулированной в функцию, дает ей удобство. Допустим, вы хотели изменить приветствие с «Спокойной ночи» на «Шоу окончено». Вместо того, чтобы обновлять все вырезанные и вставленные вами строки кода, вы можете просто отредактировать одну функцию, и все ее использования синхронно изменят свое поведение вместе с ней. Более того, код может стать довольно сложным. Это помогает разбить его на небольшие подпрограммы и использовать эти подпрограммы как свои собственные строительные блоки, когда вы думаете о том, как создать более качественное программное обеспечение. Используя функции, вы избавляетесь от необходимости скрупулезно представлять каждую деталь вашей системы; следовательно, функция - это один из видов абстракции, точно такой же, как абстракция в искусстве. Такой вид абстракции называется инкапсуляцией сложности, потому что это все равно, что взять большую сложную вещь и поместить ее в красивую маленькую капсулу, заставляя эту большую сложную вещь казаться меньше и проще. Это очень мощная идея - не только в коде.
Инкапсуляция сложности
Представьте себе актера Лоуренса Фишберна в тонированных очках пенсне, предлагающего вам два варианта, которые довольно сложно объяснить. С одной стороны, он готов помочь вам сбежать от злой Матрицы, чтобы вы могли исполнить свою судьбу как герой-хакер, но это включает в себя жизнь в условиях жизни, и это потенциально болезненно, но неважно. История должна продолжаться и, кстати, есть красивая девушка. С другой стороны, он также готов позволить вам забыть обо всем, что произошло, и таинственным образом посадить вас обратно в вашу крошечную квартирку, где вы можете продолжать жить во лжи, никто не станет мудрее. Эти два варианта описаны в фильме Матрица, а затем главному герою предлагается выбор в виде цветных таблеток, чтобы упростить многословный сценарий фильма. Два сложных выбора заключены в простую аналогию, которую кинозрителям гораздо легче проглотить. См. Рисунок 12.
Вместо того, чтобы повторять всю сложную ситуацию, Нео (главному герою) нужно было только проглотить одну из таблеток. Даже если бы это была настоящая медицина, идея инкапсуляции сложности все еще актуальна. У большинства из нас нет опыта, чтобы практиковать медицину наиболее эффективным способом, и поэтому мы доверяем врачам и фармакологам создать правильную смесь только правильных трав и химикатов. Когда вы глотаете таблетку, это похоже на вызов этой функции, потому что у вас есть преимущество в том, что вам не нужно понимать глубину таблетки. Вы просто верите, что таблетка приведет к исходу. То же самое и с кодом. В большинстве случаев функция была написана кем-то другим, и если этот человек - хороший разработчик, вы можете оставаться в блаженном неведении о внутренней работе его функции, пока вы понимаете, как правильно вызывать эту функцию. Таким образом, вы являетесь кодировщиком более высокого уровня, что означает, что вы просто вызываете функцию, но не писали ее. Кто-то, кто создает проект в OpenFrameworks, сидит на плечах слоя OpenFrameworks. OpenFrameworks лежит на плечах OpenGL Utility Toolkit, который находится на самом OpenGL, и так далее. Другими словами, проект OpenFrameworks - это высокоуровневое приложение C ++, языка с репутацией низкоуровневого программирования. Как показано на рисунке 13, я иногда сталкиваюсь с проблемой, когда говорю людям, что написал интерактивную статью на C ++.
Есть несколько преимуществ использования C ++ по сравнению с другими вариантами (в основном сценариями) для вашего нового медиа-проекта. Дискуссия может стать довольно религиозной (читай: горячей) среди тех, кто знает подробности. Если вы стремитесь изучить C ++, то обычно это происходит потому, что вам нужна более высокая производительность во время выполнения, потому что в C ++ есть больше библиотек, которые вы можете подключить к своему проекту, или потому, что ваш наставник работает на этом языке. Проект OF считается высокоуровневым, потому что он работает с большей инкапсуляцией сложности, и этим можно гордиться.
Краткая история
Я собираюсь сделать перерыв в изучении того, как использовать язык, и на мгновение загляну в его социальную историю, потому что знание того, кто и откуда пришел, может помочь понять, почему определенные вещи такие, какие они есть.
Существует множество языков программирования, каждый со своей уникальной историей. Как и человеческие языки, они связаны в запутанном генеалогическом древе, который в конечном итоге ведет к первому компьютерному языку Августы Ады Кинг, графини Лавлейс в 17 веке. Языки улучшаются по сравнению с предыдущими по разным причинам, таким как повышение эффективности / производительности, снижение инженерной сложности, добавление полезных функций для программиста, создание новых разделов труда и, в целом, повышение счастья программистов. C ++, его родительский язык C и прародительский язык B (BCPL) - все это разработано Bell Laboratories. Фактически, B означает Bell. На рисунке 14 показано изображение Денниса М. Ритчи и Кена Томпсона, создателей языка C, сидящих перед PDP-11, первым компьютером, работающим под управлением Unix. Поскольку C является следующим языком после B, в нем используется следующая буква латинского алфавита.
Если я вежливо попрошу отца, он расскажет истории о своих временах, проведенных в Bell Labs в Мюррей-Хилл, штат Нью-Джерси, в конце 1960-х годов, когда он занимался исследованиями жидких кристаллов. Некоторые из его коллег, которые выполняли более тяжелые вычисления, ходили с коробками из-под обуви, заполненными перфокартами. Эти перфокарты (рис. 15) было сложно создавать, публиковать и поддерживать (представьте, что уронили коробку из-под обуви). Смена рабочих мест на мэйнфреймах GE 600 также мешала совместной работе, поскольку системы перфокарт не были многопользовательскими. Возможно, это было немного похоже на ожидание использования самого большого станка с ЧПУ в TechShop.
Томпсон и Ричи инициировали разработку операционной системы Unix с целью улучшить эти исходные проблемы (и предоставить полезную приспособление, с помощью которой они могли бы создать свою игру «Космическое путешествие»). Частично привлекательность Unix заключалась в том, что ее суть была написана на языке C, а не на языке ассемблера нижнего уровня, и это сделало его более портативным. В то время каждая новая изобретенная машина должна была иметь определенный язык ассемблера - это всегда было первой задачей ее пользователей. Поскольку Томпсон и Ритче перенесли Unix на C, это сделало Unix очень портативным, что дало ему очень хорошие шансы на вирусное распространение. Рождение языка C произошло одновременно с рождением Unix. Следовательно, сегодняшняя повсеместность Unix-подобных операционных систем может объяснить важность C и производных от него языков.
Десять лет спустя Бьярн Страуструп создал новую разновидность языка C, чтобы обновить его новой парадигмой, называемой объектно-ориентированным программированием, которая вводит промежуточный контейнер между функцией и ее программой, названный класс. Система классов позволяет программе выполнять специальные трюки со скейтбордом, о которых вы можете прочитать в главе OOPS!. Поскольку C ++ достаточно близок к C, компилируясь с ним совместимо (если вы знаете, что делаете), C ++ пользуется популярностью у C и Unix. C ++ получил свое название, потому что добавление ++
после чего-либо в C означает добавление к нему 1 (или увеличение). C ++ - это язык C плюс одна парадигма. Многие считают C ++ самым популярным языком программирования всех времен.
Производные языки
(Приношу свои извинения The Nails)
С момента создания стандарта C ++ появилось множество прямых производных языков - некоторые с буквой C в названии, а некоторые без нее. Objective-C на десять лет новее C ++. Смешанный с операционной системой NeXT (на рисунке 16 показан логотип Пола Рэнда) и проектом OpenStep, Objective-C наиболее широко используется для разработки приложений для OS X и iOS. Java (на 20 лет новее C ++) считается прямым производным от C ++. Он пытается удалить «C» и оставить только «++». Если вы изучаете информатику в университете, этот язык может быть одним из первых, чему вас учат, тогда как исторически первыми языками были C и C ++. C # (произносится как C-Sharp) - альтернатива Java от Microsoft. Первоначально предназначенный для использования при разработке приложений .NET, вы также можете использовать его при разработке в Unity или на любой платформе, которая использует проект Mono. C ++ 11 - это более новая версия C ++ 2011 года выпуска, которая добавляет множество новых функций. Вы можете узнать больше о том, чем он отличается от классического C ++ в главе 15. Наконец, D - это язык программирования, который принимает следующую букву в алфавите после C, также пытаясь модернизировать язык так же, как и его современники. , и (банально) не из Мюррей-Хилл, штат Нью-Джерси. Эти разные языки легко освоить, если вы познакомитесь с C ++. Существуют обучающие программы, которые помогут программисту на C ++ понять другие языки и их среды. Если вы хотите создать свой собственный вариант языка C ++, я предлагаю пройти курс по построению компиляторов - например, тот, который предлагает на Coursera профессор Стэнфордского университета Алекс Айкен.
Хотя OS X продвигает Objective-C, а Windows продвигает C #, и XCode, и Visual Studio по-прежнему будут компилировать C и C ++ вместе с Linux, поскольку компиляторы языков C и C ++ пользуются большим спросом, чем их проприетарные разновидности. Может быть полезно объединить файлы кода, написанные на этих различных языках, чтобы создать гибкое решение, которое сокращает количество повторных изобретений, которые вам необходимо выполнить. По сути, OpenFrameworks основывает свою кроссплатформенную переносимость на переносимости языка C ++. Теоретически вы должны иметь возможность написать приложение OpenFrameworks один раз на одной платформе, а затем увидеть, как оно компилируется и запускается идентично в других средах.
Разговорный сленг
Иногда я говорю C, а иногда я говорю C ++. Поскольку они тесно связаны, возможно, вы сможете понять, насколько они почти синонимы. Я также опускаю ++, потому что говорю об обоих языках! C ++ - это почти надмножество C (см. Рисунок 17), поэтому, когда я говорю о свойствах C, я обычно также говорю о свойствах C ++. Что еще хуже, более широкий сленг C применяется ко всем языкам C-something. Допустим, вы известный художник кода, выступающий на фестивале EyeO, и один из зрителей просит вас рассказать о программировании вашего произведения. Возможно, вы использовали комбинацию C ++, C и Objective-C в Mac OS X, чтобы реализовать свой проект OpenFrameworks (не говоря уже о Java и C # на вашем сервере Linode), но из-за элегантности простоты и из-за того, что у вас есть только 2 До того, как вас вышвырнут со сцены, осталось несколько минут, ваш ответ сокращается - и вы говорите: «Я написал это на С». Возможно, позже, когда этот член аудитории поговорит с вами индивидуально, вы сможете более конкретно рассказать обо всех разновидностях языка Си, который вы действительно использовали. Как и в большинстве сленговых терминов, непринужденное использование буквы «C» в разговорной речи также зависит от контекста. Иногда такое превентивное упрощение речи неуместно, когда вы разговариваете с известными инженерами, разработчиками и хакерами. В этом случае, говоря C, когда вы имеете в виду C ++, можно было бы истолковать как фронтлинг!
Переменные (часть 1)
Пожалуйста, введите следующую программу в ideone и запустите ее.
#include <iostream> using namespace std; int main(){ cout << "My friend is " << 42 << " years old." << endl; cout << "The answer to the life the universe and everything is " << 42 << "." << endl; cout << "That number plus 1 is " << (42+1) << "." << endl; return 0; }
Результат выглядит так:
My friend is 42 years old.
The answer to the life the universe and everything is 42.
That number plus 1 is 43.
Из предыдущего урока мы понимаем, что все, что вы помещаете между операторами <<
, будет отформатировано в cout
object и волшебным образом попадет в консоль вывода. Обратите внимание, в последней строке я заключил небольшую арифметику (42 + 1) в скобки, и получилось 43. Это называется выражением в математическом смысле. Все эти три строки кода что-то говорят о числе 42, поэтому все они содержат буквальное целое число. Литеральное значение - это содержимое, вводимое непосредственно в код; некоторые сказали бы «зашита», потому что значение фиксируется после компиляции с остальными.
Если я хочу изменить это число, я могу сделать то, что знаю из текстового редактора, и «найти и заменить» 42 на новое значение. Что, если бы у меня было 100 000 частиц в трехмерном мире. У некоторых есть 42, которые нужно менять, а у других 42, которые не следует менять? Когда вы пишете код, все может стать как тяжелым, так и сложным. Наиболее очевидное применение переменных состоит в том, что они представляют собой очень мощный механизм поиска и замены, но вы увидите, что переменные полезны не только для этого. Итак, давайте объявим целое число в верхней части кода и будем использовать его вместо литерала 42.
#include <iostream> using namespace std; int main(){ int answer = 42; cout << "My friend is " << answer << " years old." << endl; cout << "The answer to the life the universe and everything is " << answer << "." << endl; cout << "That number plus 1 is " << (answer+1) << "." << endl; return 0; }
Теперь, когда я использую переменную answer
, мне нужно изменить только это одно число в моем коде, и во всех трех предложениях оно будет отображаться как 42. Это может быть более элегантно, чем поиск и замена. На рисунке 18 показано объяснение синтаксиса для объявления и инициализации переменной в одной строке.
Также возможно объявить переменную и инициализировать ее в двух отдельных строках. Это будет выглядеть так:
int answer; answer = 42;
В этом случае есть момент после объявления этой переменной, когда ее ответ может быть непредсказуемым и ошибочным, потому что в C (в отличие от Java) новые переменные не устанавливаются на ноль бесплатно - вам нужно это сделать. Если вы этого не сделаете, переменная может иметь непредсказуемые значения - компьютерная память - мусор из прошлого. Итак, если вы не собираетесь создавать глитч-арт, всегда инициализируйте свою переменную каким-то числом при ее объявлении, даже если это число равно нулю.
Именование вашей переменной
Обратите внимание на стрелку ниже, говорящую «должно быть действительное имя». Мы придумываем новые имена, чтобы дать нашим пространствам имен, функциям, переменным и другим конструкциям, которые мы определяем в коде (классы, структуры, перечисления и другие вещи, которым я вас не учил). Правила определения нового идентификатора в коде строги так же, как и при выборе пароля на веб-сайте.
- Ваш идентификатор должен содержать только буквы, цифры и символы подчеркивания.
- он не может начинаться с числа, но, безусловно, может начинаться с подчеркивания.
- Название не может совпадать с одним из ключевых слов языка (например, слово
void
).
Следующие идентификаторы допустимы.
a
A
counter1
_x_axis
perlin_noise_frequency
_ // a single underscore is fine
___ // several underscores are fine
Обратите внимание, что нижний регистр a - это другой идентификатор, чем верхний регистр A. Идентификаторы в C ++ чувствительны к регистру. Следующие идентификаторы недопустимы.
1infiniteloop // should not start with a number transient-mark-mode // dashes should be underscores @jtnimoy // should not contain an @ the locH of sprite 1 // should not contain spaces void // should not be a reserved word int // should not be a reserved word
присвоение имени вашей переменной void_int
, хотя и сбивает с толку, не вызовет ошибок компилятора, потому что подчеркивание объединяет два ключевых слова в новый идентификатор. Иногда вы можете столкнуться с unqualified id
ошибками. Вот список зарезервированных ключевых слов C ++, которых следует избегать при именовании переменных. Они нужны C ++, чтобы обеспечить полноценный язык программирования.
alignas alignof and and_eq asm auto bitand bitor bool break case catch
char char16_t char32_t class compl const constexpr const_cast continue
decltype default delete do double dynamic_cast else enum explicit
export extern false final float for friend goto if inline int long
mutable namespace new noexcept not not_eq nullptr operator or or_eq
override private protected public register reinterpret_cast return
short signed sizeof static static_assert static_cast struct switch
template this thread_local throw true try typedef typeid typename
union unsigned using virtual void volatile wchar_t while xor xor_eq
Соглашения об именах
Идентификаторы (включая переменные) записываются в разных стилях, чтобы указать их различные свойства, такие как тип конструкции (переменная, функция или класс?), Тип данных (целое число или строка?), Область действия (глобальная или локальная?), Уровень конфиденциальность и т. д. Вы можете увидеть некоторые идентификаторы, написанные с заглавной буквы в начале и использующие CamelCase
, в то время как другие остаются полностью lower_case_using_underscores_to_separate_the_words
. Обнаружено, что глобальные константы имеют имя ALL_CAPS_AND_UNDERSCORES
. Другой способ именования в нижнем регистре - начать с letterThenCamelCaseFromThere
в нижнем регистре. Вы также можете увидеть гибрид, например ClassName__functionName__variable_name
. Эти разные стили могут указывать на разные категории идентификаторов.
Более того, программисты могут иногда использовать то, что ласково называют венгерской нотацией, добавляя значки символов к идентификатору, чтобы сказать что-то об этом, но также уменьшать разборчивость, например dwLightYears
и szLastName
. Соглашения об именах не высечены на камне и, конечно же, не применяются компилятором. Сотрудникам обычно необходимо согласовать эти тонкие соглашения об именах, чтобы они не путали друг друга, и требуется дисциплина со стороны всех, чтобы оставаться в соответствии с любым принятым соглашением. Тема соглашения об именах в коде до сих пор вызывает комические жаркие споры среди разработчиков, точно так же, как решение о том, в какой строке поставить фигурную скобку и использовать ли табуляцию для отступа. Как и многие другие вещи в программировании, кто-то всегда скажет вам, что вы делаете что-то неправильно. Это не обязательно означает, что вы делаете это неправильно.
Изменение переменных
Мы называем их переменными, потому что их значения меняются во время выполнения. Они наиболее полезны в качестве ведра, в которое мы наливаем что-нибудь (скажем, воду) для безопасного хранения. Как это обычно бывает, мы возвращаемся к ведру и используем немного воды, или подмешиваем химикат в воду, или доливаем ведро большим количеством воды, и т. Д. Переменная похожа на пустое ведро, в которое вы можете положить ваши вещи. На рисунке 19 показано ведро из игры Minecraft.
Если компьютерная программа похожа на маленький мозг, то переменная похожа на базовую единицу памяти. Записать небольшую заметку в моем альбоме - это все равно, что сохранить значение в переменной для дальнейшего использования. Давайте посмотрим, как переменная меняет свое значение.
#include <iostream> using namespace std; int main(){ int counter = 0; cout << counter; counter = 1; cout << counter; counter = 2; cout << counter; counter = 3; cout << counter; counter = 4; cout << counter; counter = 5; cout << counter; return 0; }
Результат должен быть 012345
. Обратите внимание на использование знака равенства. Это отличается от того, к чему мы привыкли от арифметики. В традиционном контексте один знак равенства означает, что выражения с обеих сторон будут иметь одинаковое значение. В C это на самом деле двойное равенство (==), и мы поговорим об этом позже. Единственный знак равенства означает: «Решите выражение с правой стороны и сохраните ответ в переменной, названной с левой стороны». К этому нужно привыкнуть, если вы раньше не программировали. Если бы я был начинающим программистом (а мой внутренний ребенок постоянно), мне, возможно, понравился бы какой-нибудь альтернативный синтаксис, чтобы дать компьютеру команду сохранить значение в переменной. Что-то вроде: 3 => counter
as, найденного на языке ChucK Принстонской звуковой лабораторией, или, возможно, что-то более наглядное, как мое перепрофилирование верстака Minecraft на рисунке 20.
Полезность указания имени переменной слева, а не справа становится очевидной на практике, поскольку выражения становятся довольно длинными! Начинать строку с varname =
легче для глазного яблока, потому что это гарантированно будет двумя символами задолго до того, как вы начнете безумие, которое вы планируете печатать после знака равенства.
Анализируя предыдущий пример кода, мы видим, что число увеличивается на 1 каждый раз перед выводом. Я постоянно сохраняю в переменной буквальные целые числа. Поскольку язык программирования знает основы арифметики, давайте теперь попробуем следующую модификацию:
#include <iostream> using namespace std; int main(){ int counter = 0; cout << counter; counter = counter + 1; cout << counter; counter = counter + 1; cout << counter; counter = counter + 1; cout << counter; counter = counter + 1; cout << counter; counter = counter + 1; cout << counter; return 0; }
Результат должен быть 012345
. Говоря counter = counter + 1
, я увеличиваю counter
на 1. Точнее, я использую counter
в правом выражении «сложение», и результат этого (мгновение спустя) сохраняется в counter
. Это кажется немного забавным, потому что в нем говорится о counter
в два разных времени. Это напоминает мне сериал Назад в будущее, в котором Марти Макфлай сталкивается с прошлой и будущей версиями самого себя. См. Рисунок 21.
Великий Скотт, от этого может закружиться голова! Но после того, как вы проделаете это несколько раз, вы увидите, что это не намного сложнее, чем то, что вы там видите. Это очень практическое использование научной фантастики, и вы, вероятно, не пытаетесь бросить вызов структуре пространства-времени (если вы не Кайл Макдональд или, может быть, программист на Haskell). Дело здесь в том, чтобы изменить содержимое памяти компьютера, так что у нас есть counter
из одной инструкции назад, точно так же, как в нашем ведре уже может быть вода, когда мы собираемся добавить в него воды. На рисунке 22 показан bucket = bucket + water
.
Увеличение на единицу или добавление некоторого значения к переменной на самом деле настолько обычное дело во всем программировании, что для этого есть даже синтаксический сахар. Syntactic Sugar - это избыточная грамматика, добавленная в язык программирования для удобства. Это помогает сократить набор текста, может улучшить понимание или выразительность и (как сахар) делает программиста более счастливым. Все следующие утверждения добавляют 1 к counter
.
counter = counter + 1; // original form counter += 1; // "increment self by" useful because it's less typing. counter++; // "add 1 to self" useful because you don't need to type a 1. ++counter; // same as above, but with a subtle difference.
Давайте проверим это в программе.
#include <iostream> using namespace std; int main(){ int counter = 0; cout << counter; counter++; cout << counter; counter++; cout << counter; counter++; cout << counter; counter++; cout << counter; counter++; cout << counter; return 0; }
Да, здесь намного меньше набора текста, и есть много способов сделать его более лаконичным. Вот один способ.
#include <iostream> using namespace std; int main(){ int counter = 0; cout << counter++; cout << counter++; cout << counter++; cout << counter++; cout << counter++; cout << counter++; return 0; }
Ответ по-прежнему 012345
. Оператор увеличения постфикса будет увеличивать переменную, даже если она находится внутри выражения. Теперь попробуем префиксную версию.
#include <iostream> using namespace std; int main(){ int counter = 0; cout << ++counter; cout << ++counter; cout << ++counter; cout << ++counter; cout << ++counter; cout << ++counter; return 0; }
Если вы получили ответ 123456
, это не ошибка! Оператор увеличения префикса этим очень отличается от своего постфикса. Если counter
инициализирован как 0, ++counter
будет оцениваться как 1, а counter++
все равно будет оцениваться как 0 (но увеличенная версия counter
будет оставлена для дальнейшего использования). Результат для следующего примера - 1112
.
#include <iostream> using namespace std; int main(){ int counter = 0; cout << ++counter; // 1: increments before evaluating cout << counter; // 1: has NOT changed. cout << counter++; // 1: increments after evaluating cout << counter; // 2: evidence of change. return 0; }
Для арифметической полноты я должен упомянуть, что вычитающий оператор уменьшения (счетчик -) также существует. Кроме того, как вы уже могли догадаться, если можно сказать counter + 1
, то компилятор C также распознает другую классическую арифметику, такую как counter - 3
(вычитание), counter * 2
(звездочка - умножение), counter / 2
(деление) и переопределение порядка операции с использованием круглых скобок, например (counter + 1) / 2
результат, отличный от counter + 1 / 2
. Если поставить отрицательный знак перед переменной, это тоже будет правильным и отрицательным, как если бы она была вычтена из нуля. C расширяет эту базовую палитру математических операторов с помощью логической логики и побитовых манипуляций; Я представлю их во второй части "Переменные".
Есть еще несколько важных вещей, которые нужно знать о переменных, но мы собираемся взять то, что мы уже узнали, и использовать это во имя развлечения. А пока похлопайте себя по плечу за то, что зашли так далеко! Вы узнали, что такое переменные и как выполнять с ними основную арифметику. Вы также узнали, что делает оператор ++, помещенный до и после имени переменной.
Язык C ++ получил свое название от языка C плюс один.
Если-то
Представьте, что вы едете на велосипеде по улицам вашего города. Когда вы встречаетесь с перекрестками или развилками на дороге, вы должны решить, повернуть ли, ехать прямо или развернуться, в зависимости от того, куда вы собираетесь, вашей срочности при возвращении домой и других мест, в которых вы, возможно, захотите остановиться. На следующий день вы можете пойти по тому же маршруту, но в конечном итоге пойдете по совершенно другим улицам в зависимости от эффекта домино от ваших решений.
Ваша способность судить о ситуации и изменять свое поведение на основе вашего анализа является своего рода предсказуемым поведением, которое определяет интеллект. Компьютерная программа также может останавливать, анализировать и решать, как действовать, более простым способом. Условия или ветвление в коде - вот что обеспечивает такой простой, но полезный интеллект. С помощью всего лишь небольшого количества If-Then-Else в вашем коде вы можете автоматизировать принятие очень сложных решений.
На рисунке 23 показан генерирующий интерактивный кластер ветвящихся нейронов, закодированный в OpenFrameworks для интерактивного документального фильма CLOUDS.
Чтобы понять, как разветвляется код, давайте представим, что код представляет собой список покупок (код, который я запускаю на себе). Моя бабушка Сейл отправила меня в супермаркет за ингредиентами для Креплаха, (ашкеназского) еврейского вонтона. К нам приезжает семья из Клостера, штат Нью-Джерси, и, поскольку у них была наглость, чтобы шлепнуть весь путь до Брайтон-Бич, мы собираемся дать им работы - кугель, грудинку и даже цибеле кичель, пока она закончит готовить (вместо несвежий хаманташен из прошлого Пурима). Но единственная моя работа - покупать ингредиенты для Креплаха. Вот список покупок:
flour
kosher salt
dozen giant brown organic eggs
1 bottle corn oil
4 sweet onions
ground beef
parsley
pepper
garlic
potatoes
scallions
carrots
Опытный покупатель знает, что товары и предпочтительные бренды не всегда есть в наличии, поэтому мы помечаем список покупок с альтернативными покупками на случай, если основные предпочтения недоступны.
Heckers unbleached all purpose flour (pillsbury or gold medal would be fine)
Morton Kosher Salt
dozen giant brown organic eggs (white eggs ok too)
1 bottle Mazola corn oil
4 sweet onions (white onions would be acceptable)
ground beef (ground chicken also ok, but NO traif!)
parsley
pepper
garlic
potatoes (red rose, white rose, yukon gold) NOT Idaho or Russet, not too much shmutz
scallions (chives will do but call me)
carrots
Хотя приведенное выше является более реалистичным, как можно было бы выразить условные выражения в списке покупок (поместив их после основной темы продукта), позвольте нам реорганизовать список покупок в условные выражения в стиле C, используя отступы и «обернув» код внутри некоторого if- подтяжки.
if ( they have Heckers unbleached all purpose flour ){ get that; }else if( they only have Heckers BLEACHED flour){ i'll settle for that over the other brands; }else{ get the pillsbury or gold metal brands; }
get Morton Kosher Salt;
if ( there are giant brown organic eggs ){ get a dozen of those; }else{ get a dozen of whatever eggs; }
get 1 bottle Mazola corn oil;
if ( there are sweet onions ){ get 4 of those; } //otherwise do nothing, I have white onions in the apartment.
if(the butcher has chop meat AND that meat is not traif AND (it is chicken or it is beef) ){ get 2 lb of it; }else{ Boychik, call me and i'll direct you another kosher butcher in our hood; }
get parsley; get pepper; get garlic;
if(they have red rose, white rose, or yukon potatoes and they don't have a lot of shmutz){ get 8 of them; }
if(they don't have scallions){ get chives; }else{ get scallions; }
get carrots;
Возможно, вы заметили шаблон в приведенном выше псевдокоде, где я определяю структуру связей, которая похожа на функцию в том, что она имеет круглые и фигурные скобки. Это операторы if-then-else, и они представляют собой тип управления потоком. На рисунке 24 поясняются детали.
Часть, помеченная test
, является выражением логической логики, и это означает, что в конечном итоге она приводит к ответу ИСТИНА или ЛОЖЬ. Давайте посмотрим на реальный код, чтобы увидеть, как истинный или ложный тест выглядит в контексте.
#include <math.h> #include <iostream> using namespace std; int main(){ int counter = 0; counter++; cout << counter; if(counter > 1){ cout << " is greater than 1"; } cout << endl; counter++; cout << counter; if(counter > 1){ cout << " is greater than 1"; } cout << endl; counter++; cout << counter; if(counter > 1){ cout << " is greater than 1"; } cout << endl; return 0; }
Каждый раз я проверяю, больше ли counter
1, и если это так, то на консоль выводится немного дополнительного текста. Если вы запустите этот код (и я настоятельно рекомендую вам это сделать), вы увидите следующий результат.
1
2 is greater than 1
3 is greater than 1
В первый раз, когда он оценивает counter > 1
, результат ложный, и поэтому код с отступом не выполняется. Есть еще пара частей if-структуры, которые стоит представить. Один из важных - else
, который открывает второй блок кода для выполнения, если условие ложно.
//this code happens regardless if(true){ // this code happens if the condition is true }else{ // this code happens if it is false } //this code happens regardless
Это отличается от простого помещения строки кода без скобок под условием if, поскольку эта свободная строка кода будет выполняться независимо от того, было ли условие истинным. Так что else
оказывается довольно удобным. Другой похожий элемент - else if
, который позволяет каскадировать предложения if-then аналогично CSS.
if(condition1){ // this code happens if condition1 is TRUE, then the rest is skipped. }else if(condition2){ // this code happens if condition1 was FALSE, however condition2 is TRUE. }else if(condition3){ // this code happens if condition2 and condition1 were both FALSE, however condition3 is TRUE. }else{ // this code happens if none of the 3 conditions are true }
Используя else if
, легко создать гибкую систему фильтров для принятия решений.
Синтаксический сахар для условных выражений
Следующее обсуждается как нечто, чего стоит избегать такими людьми, как JSLint (которые также строго относятся к пробелам). Я представил его здесь потому, что вы увидите это в коде, связанном с OpenFrameworks, и я хочу, чтобы вы распознавали то, что видите. Операторы if можно выразить более кратко, используя однострочный синтаксис, который менее гибок.
if (counter > 1) cout << "yes, it's greater"; cout << "... this line will always execute regardless." endl;
Оператор if без фигурных скобок будет «ограничивать» только следующую строку кода. Даже с другими частями оснастки применяются те же правила «только следующая строка».
if (counter > 1) cout << "yes, it's greater"; else if (counter < 10) cout << "well, it's still smaller than 10"; else cout << "The counter was outside the 2-10 range."; cout << "... this line will always execute regardless." endl;
Приведенный выше код будет делать то же самое, что и:
if (counter > 1) { cout << "yes, it's greater"; } else if (counter < 10) { cout << "well, it's still smaller than 10"; } else { cout << "The counter was outside the 2-10 range."; }
Вы даже можете смешивать и сопоставлять, какие части предложения заключены в скобки, а какие однострочны. Если это вообще возможно, я рекомендую использовать полностью заключенный в скобки синтаксис для всего нового кода во имя быстрого сканирования глазного яблока. Также существует так называемый тернарный оператор, который выглядит как a > b ? x : y
. Я позволю вам погуглить это самостоятельно, если вам действительно любопытно, но я также не рекомендую использовать тройные if, потому что они эзотеричны, не очень портативны, их сложно масштабировать или вкладывать, и они могут быстро запутаться. С другой стороны, я рекомендую вам познакомиться с тернариями на случай, если вы столкнетесь с чужими.
Операторы неравенства
Пока что я замалчивал ту часть, которая идет counter > 1
. Возможно, вы помните момент в третьем классе, когда учитель научил вас «пакманскому» правилу неравенства, когда жадный пакман хочет съесть большее число, поэтому он смотрит в том же направлении. На этом основаны операторы сравнения в Си.
a > b // a is greater than b a < b // a is less than b a >= b // a is greater-than-or-equal-to b a <= b // a is less-than-or-equal-to b a == b // a is equal to b a != b // a is not equal to b
Вы заметите использование знака двойного равенства для обозначения исходной вещи, для которой, как вы думали, должен был использоваться знак равенства. Распространенной ошибкой начинающих программистов является смешение одинарных и двойных знаков равенства. Найдите время, чтобы узнать об этой разнице, потому что она избавит вас от некоторых загадочных ошибок в будущем.
Логическая логика
Неравенства можно объединить с помощью логических операторов AND, OR и NOT, которые делают то же самое, что и в естественном английском языке. И вводится как оператор &&
, а ИЛИ - как оператор ||
. НЕ является восклицательным знаком с префиксом, и он инвертирует истинное состояние выражения. Вот несколько выражений логической логики.
true || false // true false || false // false true || false || false // true false && true // false true && true // true false && false // false !true // false !false // true true && !(true || false) // false false || true && !false // true
«Boolean» назван в честь Джорджа Буля, одного из отцов информатики, который разработал такой вид математики истинного / ложного. Вы найдете логическую логику во всем отчасти потому, что она поддерживает предложения if-then. Ключевые слова true
и false
считаются глобальными константами. Для удобства любое ненулевое число квалифицируется как истинное при помещении в логический контекст, в то время как все нулевые значения квалифицируются как ложные. Таким образом, вы можете настроить свою арифметику на нулевое или ненулевое значение при формулировании тестового выражения для вашего предложения if-then. В следующем примере условного выражения вы увидите логические логические операторы, используемые для соединения числового сравнения.
if (counter > 10 && counter < 20) { cout << "it's within the 10-20 range"; } else if (counter <= 0) { cout << "it's zero or less"; } else { cout << "all other cases"; }
Гнездование
Предложения if-then также могут быть вложенными, что означает, что вы можете поместить целое предложение if-then внутри одного из блоков кода другого. Конечно, при отступе нужно соблюдать правильную рекурсию.
if( counter > 30 ){ if(counter > 50){ // todo: do something } }else{ if(counter < 0){ if(counter < -10){ //todo: do something } }else{ //todo: do something else } }
Термин вложенность - это то же понятие, что и матрешка, как показано на рисунке 25. Вы можете помещать полые русские куклы друг в друга до тех пор, пока это не будет выглядеть так, как будто есть только одна кукла. Это странно из-за концентрических модулей (рекурсия), но невероятно полезно при разработке алгоритма.
Рисунок 25: https://commons.wikimedia.org/wiki/File:Russian-Matroshka.jpg
Зацикливание
Возможно, вы заметили, что в недавнем примере кода C ++ было много вырезок и вставок. Этот метод грубой силы для создания повторяющегося поведения не так гибок и далеко не так краток, как простое указание вашему коду зацикливаться.
#include <math.h> #include <iostream> using namespace std; int main(){ int counter = 0; while(true){ counter ++; cout << counter << ", "; } return 0; }
Если вы запустите этот код, вывод начнется так. . .
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,
72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88,
89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104,
105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118,
119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132,
133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146,
147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160,
161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174,
175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188,
189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202,
203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214 . . .
. . . и продолжает выполняться до тех пор, пока ideone не решит преждевременно завершить процесс (принудительно завершить) программу. По правде говоря, если бы вы запускали то же приложение прямо на своем ноутбуке, как и в обычной ситуации OpenFrameworks, программа работала бы вечно - до тех пор, пока на компьютере не закончатся ресурсы, не перезагрузится или вы сами не завершите процесс. Причина, по которой он продолжает увеличиваться counter
, а затем бесконечно выводить его значение на консоль, заключается в том, что я поместил true
в круглые скобки. На рис. 26 объясняется каждая часть простого, но мощного предложения while
.
Обычно вы не хотите, чтобы ваша программа просто зацикливалась бесконечно. Давайте изменим код так, чтобы цикл знал, что нужно останавливаться после нажатия 100.
while(counter < 100){ counter ++; cout << counter << ", "; }
Я изменил содержимое условного выражения while
так, чтобы оно выполнялось только до 100. Результат:
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,
72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88,
89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100,
Это не единственный способ остановить цикл while. Давайте вложим несколько предложений if-then в цикл while, чтобы я мог продемонстрировать оператор break
, а позже я покажу вам оператор continue
.
while(true){ counter ++; cout << counter << " "; if(counter==10){ break; } }
На выходе получается 1 2 3 4 5 6 7 8 9 10
. Во время цикла я постоянно проверяю, равен ли счетчик 10, и если это так, я break
выхожу из цикла.
По модулю%
В следующем примере я хотел бы познакомить вас с оператором по модулю, который не так часто встречается в базовой арифметике, но оказывается полезным и интересным в программировании. Это оператор знака процента, помещенный между двумя числами, и он вычисляет остаток от деления левого числа на правое число. Вот несколько примеров:
- 4% 2 равно 0, потому что 4/2 оказывается равным 2 без остатка.
- 10 % 3 is 1
- 10 % 4 is 2
- 9 % 10 is 9
- 10 % 10 is 0
- 11 % 10 is 1
Все еще не понимаете? Ты не одинок.
Помните, как делить числа на бумаге? Остальное - это немного, оставшееся в конце, которое не подошло.
Оказывается, деление фиксированного числа на увеличивающееся значение даст остаток, который «оборачивается» от 0 до этого числа. Так, например, вот результат (i% 4), когда i идет от 0 до 16.
0 1 2 3 [wrap] 0 1 2 3 [wrap] 0 1 2 3 [wrap] 0 1 2 3
Благодаря такому легко управляемому пошаговому поведению Modulo отлично подходит для легкого создания шаблонов. В следующем примере всегда печатается чередующийся шаблон # и пробела.
while(true){ counter ++; if(counter % 2 == 0){ cout << '#'; }else{ cout << ' '; } }
Это выводит # # # # # #
. Следующая модификация распечатывает # ## ## ## ## ##
.
while(true){ counter ++; if(counter % 3 < 2){ cout << '#'; }else{ cout << ' '; } }
Возвращаясь к демонстрации оператора continue
, я воспользуюсь оператором%, чтобы «пропускать» только четные числа.
while(true){ counter ++; if(counter % 2 == 0){ continue; }else{ cout << counter << ' '; } }
Вывод начинается с 1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47
, только нечетные числа. Оператор continue
отличается от break
, потому что он прерывает только один экземпляр цикла и запускается непосредственно в следующем. break
полностью завершит цикл.
Следующая программа использует вложенный цикл while и дополнительную переменную счетчика для генерации пилообразного волнового паттерна в ASCII art.
#include <math.h> #include <iostream> using namespace std; int main(){ int counter = 0; int innerCounter = 0; while(true){ counter ++; innerCounter = 0; while(innerCounter < (counter % 32) ){ innerCounter++; cout << 'O'; } cout << endl; } return 0; }
На выходе получается вертикальная пилообразная волна:
O OO OOO OOOO OOOOO OOOOOO OOOOOOO OOOOOOOO OOOOOOOOO OOOOOOOOOO OOOOOOOOOOO OOOOOOOOOOOO OOOOOOOOOOOOO OOOOOOOOOOOOOO OOOOOOOOOOOOOOO OOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
O OO OOO OOOO OOOOO OOOOOO OOOOOOO OOOOOOOO OOOOOOOOO OOOOOOOOOO OOOOOOOOOOO OOOOOOOOOOOO OOOOOOOOOOOOO OOOOOOOOOOOOOO OOOOOOOOOOOOOOO OOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
O OO OOO OOOO OOOOO OOOOOO OOOOOOO OOOOOOOO OOOOOOOOO OOOOOOOOOO OOOOOOOOOOO OOOOOOOOOOOO OOOOOOOOOOOOO OOOOOOOOOOOOOO OOOOOOOOOOOOOOO OOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
For-Loop
Поскольку конечный цикл настолько распространен, для него есть синтаксический сахар. Этот сахар настолько распространен на всех языках, что перестает быть сахаром. Цикл for позволяет вам кратко перебирать диапазон подсчета. Циклы считаются более безопасными, потому что они предотвращают всю «бесконечность», и, с другой стороны, есть способы заставить цикл for работать бесконечно. Они невероятно практичны, и их можно встретить повсюду. На рисунке 27 показана структура синтаксиса цикла for, который повторяется 100 раз.
Поначалу весь этот кусок for( int i = 0 ; i < 100 ; i++ )
может сбивать с толку, но вы узнаете его довольно быстро. это просто означает «сделай это 100 раз», и стоит потренироваться печатать именно это несколько раз, чтобы вы могли закрепить это в мышечной памяти. Вот несколько циклов for.
for(int i = 100 ; i >= 0 ; i--){ //iterates with i from 100 to 0 } for( int i = 0 ; i < 100 ; i += 2){ //iterates with i from 0 to 100, skipping 2 each time. } for( int i = 0 ; i < 10 ; i++ ){ for( int j = 0 ; j < 10 ; j++ ){ for( int k = 0 ; k < 10 ; k++ ){ // a triple nested for-loop using variables i,j,k } } }
Переменная итерации в цикле for обычно называется i
, внутренняя - j
, а та, что внутри - k
. После этого появляющийся стандарт начинает меняться, но я видел _179 _, _ 180 _, _ 181_, а затем _182 _, _ 183 _, _ 184_. Программисты также просто ломаются и начинают использовать более выразительные имена переменных. Во вложенных циклах for, которые повторяются через пиксели, вы обычно видите x
, y
, а иногда и z
в качестве имен итераторов. Если вы видите переменную i
в чьем-то коде, скорее всего, это локальная переменная, объявленная в верхней части цикла for. Как и цикл while, цикл for будет отвечать на операторы break
и continue
. Вот пример двойного цикла for, который генерирует двумерный шаблон в консоли.
#include <iostream> using namespace std; int main(){ for( int y = 0; y < 20 ; y++ ){ for( int x = 0 ; x < 80 ; x++ ){ if(y%2==0){ if(x%2==0){ cout << ')'; }else{ cout << ' '; } }else{ if(x%2==0){ cout << ' '; }else{ cout << '('; } } } cout << endl; } return 0; }
И результат должен выглядеть так.
) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )
( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (
) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )
( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (
) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )
( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (
) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )
( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (
) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )
( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (
) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )
( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (
) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )
( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (
) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )
( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (
) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )
( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (
) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )
( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (
Ради интереса я собираюсь представить функцию расстояния. Вам не нужно понимать теорему Пифагора (если вы этого не хотите), а просто вставьте полезную функцию в свой код, чтобы вы могли измерить расстояние.
#include <iostream> #include <math.h> using namespace std; int distance(int x1,int y1,int x2, int y2){ int xDelta = x1-x2; int yDelta = y1-y2; return sqrt(xDelta * xDelta + yDelta * yDelta); } int main(){ for( int y = 0; y < 20 ; y++ ){ for( int x = 0 ; x < 80 ; x++ ){ if( distance(x,y,40,10) < 6 ){ // if this pixel is inside the hole cout << ' '; // render only spaces. }else if(y%2==0){ // otherwise do the usual wave thing. if(x%2==0){ cout << ')'; }else{ cout << ' '; } }else{ if(x%2==0){ cout << ' '; }else{ cout << '('; } } } cout << endl; } return 0; }
Результат выглядит так:
) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )
( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (
) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )
( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (
) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )
( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (
) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )
( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (
) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )
( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (
) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )
( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (
) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )
( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (
) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )
( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (
) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )
( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (
) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )
( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (
Новый оператор if, измеряющий расстояние от центра круга, позволяет нам отобразить внутреннюю часть круга с другой буквой. Причина, по которой он выглядит высоким и худым, заключается в том, что в искусстве ACSII персонажи вдвое выше, и мы не учли это в коде. Мы можем исправить это, умножив правильные значения на 2 в аргументах вызова distance()
.
if( distance(x,y*2,40,20) < 17 ){ // Y and circle-center-Y both multiplied by 2
Вывод должен быть более круглым, в зависимости от высоты строки средства визуализации шрифта.
) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )
( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (
) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )
( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (
) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )
( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (
) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )
( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (
) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )
( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (
) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )
( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (
) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )
( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (
) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )
( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (
) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )
( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (
) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )
( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (
Ранний возврат с работы
Когда я впервые представил функции, мы не выучили достаточно операторов кода, чтобы заполнить эти функции более чем одной строкой кода. Теперь, когда вы увидели немного больше, я хотел бы рассказать о том, что я упустил. Вы видели, как можно break
выйти из цикла. также можно прервать работу всей функции с помощью специального оператора return
, при этом остальная часть функции будет пропущена, и произойдет возврат непосредственно к вызывающему коду.
void doSomething(int a){ if ( a <= 0 ) { return; } else { for(int i=0;i<a;i++){ cout << '#'; } cout << endl; } }
Вышеупомянутая функция имеет тип возврата void
, поэтому возвращать какое-либо значение не требуется. Вы можете видеть вверху, что я проверяю значение a
, чтобы убедиться, что оно больше нуля, прежде чем я визуализирую такое количество #
в консоли. Я вызываю выражение return
раньше, и оно ускользает от функции.
Прыгающий мяч
Теперь мы объединим наши новые знания о переменных, условных операторах, циклах и функциях для визуализации анимации отскока мяча. Давайте начнем с пустого изображения в формате ASCII и добавим к нему мяч.
#include <iostream> using namespace std; int width = 20; int height = 6; void drawFrame(){ for(int y = 0 ; y < height; y++ ){ for(int x = 0 ; x < width; x++ ){ cout << '.'; // draw each column } cout << endl; // new row } cout << endl; // margin-bottom } int main(){ drawFrame(); return 0; }
Результат должен выглядеть так.
....................
....................
....................
....................
....................
....................
У нас есть функция drawFrame()
, задача которой - визуализировать 1 кадр анимации в консоли как искусство ASCII. Выше у нас есть пара переменных width
и height
для хранения размера холста. Теперь добавим мяч. Для этого мы добавляем еще пару глобальных переменных под width
и height
, чтобы удерживать позицию мяча, а затем мы добавляем if-оператор к рендереру, чтобы убедиться, что он создает визуальное исключение для положения мяча, которое составляет 1 символ.
#include <iostream> using namespace std; int width = 20; int height = 6; int ballX = 10; // new int ballY = 3; // new void drawFrame(){ for(int y = 0 ; y < height; y++ ){ for(int x = 0 ; x < width; x++ ){ if ( x == ballX && y == ballY ) { // new cout << 'O'; // new } else { // new cout << '.'; // draw each column } } cout << endl; // new row } cout << endl; // margin-bottom } int main(){ drawFrame(); return 0; }
Результат изменится, чтобы выглядеть так.
....................
....................
....................
..........O.........
....................
....................
Давайте заставим мяч двигаться влево и вправо, добавив глобальную переменную force
только для значения x шара, а затем начнем добавлять силу к положению мяча, отрисовывая кадр за кадром.
#include <iostream> using namespace std; int width = 20; int height = 6; int ballX = 10; int ballY = 3; int forceX = 1; // new void updatePhysics(){ // new ballX += forceX; // new } // new void drawFrame(){ for(int y = 0 ; y < height; y++ ){ for(int x = 0 ; x < width; x++ ){ if ( x == ballX && y == ballY ) { cout << 'O'; // draw the ball } else { cout << '.'; // draw each column } } cout << endl; // new row } cout << endl; // margin-bottom } int main(){ while(true){ // new updatePhysics(); // new drawFrame(); } // new return 0; }
Вывод должен начинаться с этого.
.................... .................... .................... ...........O........ .................... ....................
.................... .................... .................... ............O....... .................... ....................
.................... .................... .................... .............O...... .................... ....................
.................... .................... .................... ..............O..... .................... ....................
.................... .................... .................... ...............O.... .................... ....................
.................... .................... .................... ................O... .................... ....................
.................... .................... .................... .................O.. .................... ....................
.................... .................... .................... ..................O. .................... ....................
.................... .................... .................... ...................O .................... ....................
.................... .................... .................... .................... .................... ....................
.................... .................... .................... .................... .................... ....................
Как видите, позиция мяча постоянно увеличивается на единицу, и в конце концов он отваливается от холста. Чтобы заставить мяч отскакивать от стен, мы должны добавить в updatePhysics()
условия, которые обнаруживают столкновение мяча со стеной и изменяют направление его силы.
void updatePhysics(){ if( (ballX+1) ==width || ballX==0){ // new forceX = -forceX; // new } // new ballX += forceX; }
Результат покажет, что мяч правильно отскакивает от левой и правой стенок.
.................... .................... .................... ...........O........ .................... ....................
.................... .................... .................... ............O....... .................... ....................
.................... .................... .................... .............O...... .................... ....................
.................... .................... .................... ..............O..... .................... ....................
.................... .................... .................... ...............O.... .................... ....................
.................... .................... .................... ................O... .................... ....................
.................... .................... .................... .................O.. .................... ....................
.................... .................... .................... ..................O. .................... ....................
.................... .................... .................... ...................O .................... ....................
.................... .................... .................... ..................O. .................... ....................
.................... .................... .................... .................O.. .................... ....................
.................... .................... .................... ................O... .................... ....................
.................... .................... .................... ...............O.... .................... ....................
.................... .................... .................... ..............O..... .................... ....................
.................... .................... .................... .............O...... .................... ....................
.................... .................... .................... ............O....... .................... ....................
.................... .................... .................... ...........O........ .................... ....................
.................... .................... .................... ..........O......... .................... ....................
.................... .................... .................... .........O.......... .................... ....................
.................... .................... .................... ........O........... .................... ....................
.................... .................... .................... .......O............ .................... ....................
.................... .................... .................... ......O............. .................... ....................
.................... .................... .................... .....O.............. .................... ....................
.................... .................... .................... ....O............... .................... ....................
.................... .................... .................... ...O................ .................... ....................
.................... .................... .................... ..O................. .................... ....................
.................... .................... .................... .O.................. .................... ....................
.................... .................... .................... O................... .................... ....................
.................... .................... .................... .O.................. .................... ....................
.................... .................... .................... ..O................. .................... ....................
.................... .................... .................... ...O................ .................... ....................
.................... .................... .................... ....O............... .................... ....................
.................... .................... .................... .....O.............. .................... ....................
.................... .................... .................... ......O............. .................... ....................
.................... .................... .................... .......O............ .................... ....................
Чтобы мяч двигался не только влево и вправо, но и вверх и вниз, мы просто добавляем forceY
и повторяем рабочий алгоритм, начиная с поведения X.
int forceX = 1; int forceY = 1; // new void updatePhysics(){ if( (ballX+1)==width || ballX==0){ forceX = -forceX; } if( (ballY+1)==height || ballY==0){ // new forceY = -forceY; // new } // new ballX += forceX; ballY += forceY; // new }
Результат покажет, как мяч прыгает по диагонали.
.................... .................... .................... .................... ...........O........ ....................
.................... .................... .................... .................... .................... ............O.......
.................... .................... .................... .................... .............O...... ....................
.................... .................... .................... ..............O..... .................... ....................
.................... .................... ...............O.... .................... .................... ....................
.................... ................O... .................... .................... .................... ....................
.................O.. .................... .................... .................... .................... ....................
.................... ..................O. .................... .................... .................... ....................
.................... .................... ...................O .................... .................... ....................
.................... .................... .................... ..................O. .................... ....................
.................... .................... .................... .................... .................O.. ....................
.................... .................... .................... .................... .................... ................O...
.................... .................... .................... .................... ...............O.... ....................
.................... .................... .................... ..............O..... .................... ....................
.................... .................... .............O...... .................... .................... ....................
.................... ............O....... .................... .................... .................... ....................
...........O........ .................... .................... .................... .................... ....................
.................... ..........O......... .................... .................... .................... ....................
.................... .................... .........O.......... .................... .................... ....................
.................... .................... .................... ........O........... .................... ....................
.................... .................... .................... .................... .......O............ ....................
.................... .................... .................... .................... .................... ......O.............
.................... .................... .................... .................... .....O.............. ....................
.................... .................... .................... ....O............... .................... ....................
.................... .................... ...O................ .................... .................... ....................
.................... ..O................. .................... .................... .................... ....................
.O.................. .................... .................... .................... .................... ....................
.................... O................... .................... .................... .................... ....................
.................... .................... .O.................. .................... .................... ....................
.................... .................... .................... ..O................. .................... ....................
.................... .................... .................... .................... ...O................ ....................
.................... .................... .................... .................... .................... ....O...............
Если вы смогли продвинуться так далеко, тогда еще раз обнимите себя, потому что вы только что написали базовый симулятор физики и отрендерили его на консоли кадр за кадром.
Следующие шаги
То, что вы знаете, достаточно мощно, чтобы написать прыгающий мяч, но нам еще предстоит многое сделать. Что, если мы хотим, чтобы мяч двигался со скоростью менее 1 пикселя за кадр? Нам нужно как-то использовать дроби. Что, если мы хотим, чтобы мяч оставил следы? Нам понадобится какой-то способ запоминания значений пикселей. Эти и другие навыки будут рассмотрены до конца главы. Предыдущее упражнение с прыгающим мячом было для вас способом проверить себя и понять, понимаете ли вы каждую часть языка. Это также был способ собрать все эти части воедино и посмотреть, как они работают как одно целое. Если вас это пугает или сбивает с толку, вернитесь назад и попробуйте просмотреть те части этой главы, которые охватывают те темы, в которых вы не уверены, прежде чем продолжить. С другой стороны, если вы чувствуете, что получили это и хотите практиковаться, вот несколько дополнительных упражнений, которые помогут вам развить свои навыки C ++.
- Разверните холст и визуализируйте мяч, как в примере ниже, отрегулировав сцену так, чтобы ни одна часть мяча не выпадала из кадра.
.......
..._...
../ \..
..\_/..
.......
- Измените диагональный рисунок мяча, чтобы он не всегда двигался под углом 45 градусов.
- Попробуйте добавить в сцену второй шар и отрендерить его иначе, чем первый.
- Если два шара когда-нибудь столкнутся, выведите «ВЫ ВЫИГРЫВАЕТЕ» и остановите программу.
- Попробуйте визуализировать шары в виде больших кругов с помощью функции
distance()
из предыдущего примера.
Переменные (часть 2)
У переменных есть область действия.
Возможно, вы заметили нечто иное в примере с прыгающим мячом. Были объявлены переменные вне функций. В предыдущих примерах я объявил переменные внутри функций. Другие переменные были объявлены внутри скобок функций как аргументы, а внутри цикла for как итератор. Эти переменные объявлены в разных областях кода. Мы говорим об области видимости, говоря, что переменная является локальной (существующей только внутри моих непосредственных фигурных скобок) или глобальной (существующей для всей программы). Интуитивно мы можем думать о глобальных и локальных переменных в географическом смысле слов «локальный» и «глобальный». Применим эту идею к музыкальным направлениям.
Что-то местное (местный концерт местной группы) произойдет в меньшем, менее известном месте - и будет происходить менее известной (но не менее важной!) Местной группой. Путешественник может прийти посмотреть шоу, но, поскольку она ходит на так много концертов в очень многих местах, она забывает о нашей местной группе. Поскольку она забывает о местной группе, это дает группе возможность изменить свое название с «From First to Last» на «Skrillex» и выбрать более свежий музыкальный жанр. Когда наш путешественник возвращается в тот же город, чтобы увидеть ту же местную группу, это значит, что он начал с нуля. Это новое начало - вот почему мы используем локальные переменные. Их способность сбрасывать (их значения быстро забываются) невероятно полезна для нас. С другой стороны, глобальные переменные - это классические музыканты, такие как Людвиг ван Бетховен или Вольфганг Амадей Моцарт, чьи имена звучат повсюду на Земле (и, возможно, за ее пределами), чтобы их никогда нельзя было забыть. Их музыку давно ценили и будут ценить во многих странах. Их имена музыкантов известны во всем мире и сохраняются (остаются неизменными) во всем мире. Эта абсолютная стабильность глобальной переменной делает их такими полезными, в отличие от более локальных переменных. Нам нужны как глобальные, так и локальные переменные, чтобы вся система работала, точно так же, как нам нужны как глобальные, так и местные музыканты для здоровья музыки!
Извинения
Я знаю, что ненавидеть Skrillex - это модно, поэтому помните, что его карьера только началась. Он прошел путь от местного до глобального за небольшой промежуток времени, что впечатляет. Я уверен, что он превратится в еще более всемирно известного музыканта, чем он уже есть, и тогда мы все перестанем его ненавидеть. С другой стороны, люди ненавидели внешность и характер Моцарта в течение 256 лет (более глобальное неприятие).
Как и в случае с музыкой, кто-то всегда будет говорить вам, что вы не настоящий программист. Не позволяйте этому останавливать вас. Прочитав книгу «Кодеры за работой», в которой Питер Сейбел берет интервью у ведущих программистов и компьютерных ученых, я был приятно удивлен, узнав, что люди пришли из самых разных слоев общества, а некоторые считали себя архитекторами или писателями. Хороший программист отличается от хорошего компьютерного ученого или математика!
Переменные в более локальном масштабе невероятно эффективны и важны, потому что они имеют дело с тем, что ближе всего к нам, что немного похоже на то, чтобы быть ближе к нашим нервам. Если бы мы все время делали все, используя глобальные переменные, у нас были бы коллизии - и, возможно, мы запомнили бы слишком много мусора. Код и компьютерная память запутались бы, как растрепанные волосы электронного музыканта. Идея локальной переменной нова и была введена по организационным причинам. Программисты приложили немало усилий, чтобы избежать запутанного и запутанного кода, и один из способов сделать это - сохранить локальные переменные.
При работе с вложенными предложениями в коде global и local менее черно-белые, так как scope глубиной более двух матрешек. Переменная, объявленная в различных местах в саду фигурных скобок, может рассматриваться как «немного более глобальная» или «гораздо более локальная», как на рисунке 27.
Давайте посмотрим, как это выглядит в коде.
#include <iostream> using namespace std; int myGlobal; void addOne(){ myGlobal += 1; } int main() { for(int i=0;i<4;i++){ cout << myGlobal << ' '; addOne(); } cout << endl; return 0; }
Результат - 0 1 2 3
. Обратите внимание, что addOne()
и main()
совместно используют глобальную переменную. Его значение у них остается неизменным. К сожалению, myGlobal
забывается, когда программа завершает работу, что делает ее локальной в рамках этой программы. Чтобы сделать его значение еще более постоянным, нам нужно записать его в файл, базу данных или поделиться им по сети. Этот уровень глобальности объясняется в следующих главах другими авторами. Теперь давайте намеренно вызовем ошибку.
#include <iostream> using namespace std; void addOne(){ cout << i; } int main() { for(int i=0;i<4;i++){ addOne(); } cout << endl; return 0; }
Результатом является ошибка компилятора.
prog.cpp: In function ‘void addOne()’:
prog.cpp:5:10: error: ‘i’ was not declared in this scope
cout << i;
^
Компилятор не может понять, что вы имеете в виду под i
в рамках addOne()
, потому что i
не является глобальной переменной (она существует только внутри цикла for main()
) и потому что локальная переменная i
не была объявлена для личного использования addOne()
. Если бы мы хотели получить доступ к значению i
изнутри addOne()
, лучше всего было бы передать значение i
в качестве аргумента функции addOne()
.
#include <iostream> using namespace std; void addOne(int i){ i *= 2; cout << i; } int main() { for(int i=0;i<5;i++){ addOne(i); } cout << endl; return 0; }
Результатом этого кода будет 02468
, а не 026
, как если бы int i
был разделен между двумя функциями. Видите ли, я добавил в addOne()
параметр под названием int i
. Обратите внимание, что я манипулирую значением i
, находясь внутри addOne()
, умножая его на 2 перед выводом на печать. Каждый раз, когда вызывается addOne()
, переданное ему значение i
копируется из int i
в цикле for main()
, как если бы он забыл, что оно было умножено на 2 в addOne()
. Это потому, что существует две отдельные локальные версии int i
. См. Рисунок 28.
Когда я вызываю addOne()
, я передаю только значение первого i
, чтобы оно сохранялось в более временном int i
аргументе addOne()
. На рисунке 29 показано время жизни аргумента функции, который представляет собой локальную переменную определенного типа.
Точно так же, если бы я объявил какие-либо переменные внутри области действия этой функции, эти переменные также будут забыты, когда функция вернется. Они существуют только внутри этой области. Помимо передачи по значению, существует также такая вещь, как передача по ссылке, при которой вы не только передаете значение переменной, но и фактическое место в памяти компьютера - как будто переменную сделали глобальной. указатели памяти - это более сложная тема, которую мы рассмотрим позже, но синтаксис выглядит как void addOne(int& i){
с амперсандом, указанным перед именем переменной. если вы внесли это небольшое изменение в приведенный выше пример кода, вы увидите, что результат станет 026
.
Основные типы
Переменные имеют разные типы, что означает, что они содержат в себе разную информацию. Некоторые занимают больше памяти, чем другие. Возвращаясь к метафоре Minecraft, мы можем думать о том, чтобы налить нечто большее, чем просто воду, в нечто большее, чем просто железные ведра.
В Майнкрафте ведра будут содержать не только воду, но и молоко и лаву. Кроме того, есть еще несколько видов контейнеров, предназначенных для других жидкостей и прочего. Требование правильного контейнера для правильно соответствующих данных делает C строго типизированным (или строго типизированным) языком, потому что он строго определяет, какой тип переменной вы используете. Строго типизированные переменные, возможно, являются большим достоинством языка программирования, и их можно использовать по своему усмотрению, поскольку они уменьшают количество ошибок и значительно ускоряют работу приложения. Вопрос о том, следует ли использовать строгий или слабо типизированный язык для проекта, - очень важное решение, которое необходимо принять, но следует ли посвятить свою карьеру исключительно строгому или свободному типированию - это нездоровый вид ксенофобии. У обеих сфер жизни есть свои плюсы и минусы.
В начале этой главы мы объявили строки, и с тех пор мы объявили в основном целые числа с ключевым словом int
просто потому, что они были удобным типом данных общего назначения для немедленного удовлетворения, но, как я намекнул во время упражнения с прыгающим мячом, int
имеет его ограничения. Позвольте представить вам пару новых типов.
float myNumber = 340.1928;
char myLetter = 'E';
В отличие от int
, float
позволяет использовать десятичные точки, поэтому вы можете работать с более высоким разрешением. char
содержит один символ ASCII. Ему нужно всего лишь 8 бит памяти, в то время как float
и int
занимают больше (что позволяет им отображать более широкий диапазон значений). Я кратко коснусь битов и байтов памяти, когда мы перейдем к побитовым операторам, а затем Артуро расскажет вам гораздо больше в главе 16. Давайте посмотрим на float
в действии!
Плавать
#include <iostream> using namespace std; int main() { float myNumber = 0.0; for(int i=0;i<100;i++){ myNumber += 0.012; cout << myNumber << endl; } return 0; }
Результатом является поток чисел, которые увеличиваются на 0,12.
0.012
0.024
0.036
0.048
0.06
0.072
0.084
0.096
0.108
0.12
0.132
0.144
0.156
0.168
0.18
0.192
0.204
0.216
0.228
0.24
0.252
0.264
0.276
0.288
0.3
0.312
0.324
0.336
0.348
0.36
0.372
0.384
0.396
0.408
0.42
0.432
0.444
0.456
0.468
0.48
0.492
0.504
0.516
0.528
0.54
0.552
0.564
0.576
0.588
0.6
0.612
0.624
0.636
0.648
0.66
0.672
0.684
0.696
0.708
0.72
0.732
0.744
0.756
0.768
0.78
0.792
0.804
0.816
0.828
0.840001
0.852001
0.864001
0.876001
0.888001
0.900001
0.912001
0.924001
0.936001
0.948001
0.960001
0.972001
0.984001
0.996001
1.008
1.02
1.032
1.044
1.056
1.068
Значения с плавающей запятой полезны, потому что они позволяют нам попасть между клавишами фортепьяно целого числа, и поэтому я называю их скрипкой переменных. Но обратите внимание, что в этом списке значений с дробным приращением на некоторое время добавляется фантомное значение 0,000001, начиная с 0,840001. Иногда вы можете увидеть эти незначительные ошибки при работе с числами с плавающей запятой. Это странное явление называется ошибкой с плавающей запятой и является фундаментальной проблемой для технологии с плавающей запятой в целом, иногда доставляя головную боль дизайнерам, пытающимся визуализировать числа в разумном виде. Для целей этой главы я не буду указывать на ошибки с плавающей запятой, когда они возникают, но, пожалуйста, знайте, что это горячая тема в информатике и, безусловно, есть способы исправить ее позже.
Посмотрите, что произойдет, когда мы вернемся к этому примеру кода и изменим тип с float
на int
.
#include <iostream> using namespace std; int main() { int myNumber = 0.0; // changed from float to int! for(int i=0;i<100;i++){ myNumber += 0.012; cout << myNumber << endl; } return 0; }
Результатом становится длинный безмятежный список нулей, поскольку тип данных int
не может обрабатывать такие крошечные дроби, как 0,012. Таким образом, в тот момент, когда он пытается добавить 0,012 к myNumber (который является int), 0,012 обрезает все, что находится справа от десятичной точки, и каждый раз становится равным нулю. Теперь давайте попробуем противоположный пример, сохраняя целые числа в float.
#include <iostream> using namespace std; int main() { float myNumber = 0.0; for(int i=0;i<100;i++){ myNumber += 1; cout << myNumber << endl; } return 0; }
Тип переменной был изменен обратно на float
, и теперь в цикле for я добавляю целое число 1
. Оказывается, нет потери точности, поскольку числа с плавающей запятой более чем достаточно для хранения первых 100 целых чисел. Все идет нормально. А теперь вот что!
#include <iostream> using namespace std; int main() { float myNumber = 22 / 7; cout << myNumber << endl; return 0; }
На выходе получается просто 3
, а не 3,14286. Для новичка в типах переменных это может сбить с толку. С одной стороны, я объявил переменную как float
, поэтому она должна иметь возможность обрабатывать десятичные точки. Ответ заключается в синтаксисе, используемом для целых чисел 22 и 7. Как и переменные, буквальные числа (значения, набранные числовыми цифрами прямо в коде) в C имеют тип, выводимый компилятором на основе того, как вы отформатировали число . В этом случае 22 и 7 являются буквальными целыми числами, поскольку они не что иное, как число. Давайте попробуем изменить их на буквальные float, добавив к ним суффикс .0
.
float myNumber = 22.0 / 7.0;
Теперь на выходе должно быть 3.14286
, поскольку выражение теперь представляет собой деление с плавающей запятой, а не целочисленное деление, которое отсекает дробь. Чтобы пролить немного света на это важное различие между целыми числами и числами с плавающей запятой, давайте удалим .0
только из 7 и посмотрим, что произойдет.
float myNumber = 22.0 / 7;
На выходе по-прежнему будет 3.14286
, так как оператору деления необходимо, чтобы только одно из участвующих чисел было числом с плавающей запятой, чтобы действовать с большей точностью. Следовательно, не имеет значения, какая сторона оператора деления имеет тип с плавающей запятой. . .
float myNumber = 22 / 7.0;
На выходе остается 3.14286
.
Что в имени?
Я вырос в Южной Калифорнии. Когда кто-то произносит слово «поплавок», я вспоминаю Турнир роз в Пасадене, новогодний парад с покрытыми цветами поплавками - украшенными платформами на колесах. Вот почему я могу понять, трудно ли сначала вспомнить, что float - это тип переменной, допускающий десятичные точки. Часто, когда у меня возникают проблемы с ассоциированием такого имени в моей голове, это помогает мне глубже вникнуть в доводы, лежащие в основе номенклатуры. Причина, по которой они называются плавающими, имеет историческое значение. Точность с плавающей запятой означает, что десятичный разряд может сдвигаться влево и вправо (поскольку полезны степени 10), в отличие от точности с фиксированной запятой, например double
, типа, который мы узнаем позже. на. Если это все еще не имеет смысла, просто сделайте как язык сценариев и используйте числа с плавающей запятой для всего. В случае сомнений используйте тип с большей точностью.
Char
Char
- это еще один тип, который хранит один символ, значение которого традиционно составляет 0–255, диапазон, который умещается в 8-битном (1 байте). Часто по этой 8-битной причине символ называют байтом. Вы можете знать термин 8-bit
, если вы поклонник музыки chiptune или используете Nintendo Entertainment System (NES). Отдельный байт - это довольно классическая единица памяти, и поэтому он невероятно полезен для работы с данными. Вы узнаете больше о битах и байтах в главе 16, в которой Артуро расскажет об этом более подробно. А пока давайте начнем играть с типом char
и начнем понимать его основные способы использования.
#include <iostream> using namespace std; int main() { char myLetter = 'A'; myLetter += 1; cout << myLetter << endl; return 0; }
Эта программа выводит B
. Вы уже видели строки, заключенные в двойные кавычки. Буквальный char заключен в одинарные кавычки. К этой строгости может быть трудно привыкнуть, если вы привнесете привычки из языков сценариев, которые используют двойные и одинарные кавычки как взаимозаменяемые. В C символ всегда заключен в одинарные кавычки. Когда я добавляю 1 к myLetter
, он увеличивается от A до B. В следующей строке cout
знает, что делать с символом, и печатает его как букву.
Кастинг
Хорошо, а что, если мы хотим видеть числовое представление нашего символа вместо буквы? Явное преобразование типа (или приведение) позволяет вам заставить окружающее выражение обрабатывать переменную одного типа, как если бы она имела другой тип. Мы делаем это, помещая желаемый тип в круглые скобки и прикрепляя его слева от переменной. В следующем примере я выполняю цикл от «A» (65) до «z» (122) и распечатываю все буквы с соответствующими номерами.
#include <iostream> using namespace std; int main() { for(char i = 'A' ; i <= 'z' ; i++){ cout << i << ' ' << (int)i << endl; } return 0; }
Вот результат:
A 65
B 66
C 67
D 68
E 69
F 70
G 71
H 72
I 73
J 74
K 75
L 76
M 77
N 78
O 79
P 80
Q 81
R 82
S 83
T 84
U 85
V 86
W 87
X 88
Y 89
Z 90
[ 91
\ 92
] 93
^ 94
_ 95
` 96
a 97
b 98
c 99
d 100
e 101
f 102
g 103
h 104
i 105
j 106
k 107
l 108
m 109
n 110
o 111
p 112
q 113
r 114
s 115
t 116
u 117
v 118
w 119
x 120
y 121
z 122
Естественно, вы можете выбирать между любыми основными типами. Компьютер будет увеличивать и уменьшать точность по мере необходимости. Вот пример, в котором я выполняю преобразование из char в float, затем в int, а затем снова в char. Попробуйте угадать, какой будет результат.
#include <iostream> using namespace std; int main() { int a = (float)'D' + 0.7; cout << (char)a << endl; return 0; }
На выходе будет «D».
Возможно, вы догадались, что ответ будет «E», поскольку я добавил 0,7 перед сохранением в int a
. Вы были правы, предполагая, что компьютер преобразует float в int, но неверно сказать, что 68,7 'округлено' до 69, поэтому получается буква E. Вместо этого преобразование из float в int технически является функцией floor()
, то есть просто отрезает дробь и заменяет ее нулем. Таким образом, даже (int) 2.99999999999 все равно будет равно 2. Если вам нужно округлить число, доведя 0,5 и выше до потолка, используйте round()
#include <iostream> #include <math.h> using namespace std; int main() { float theNum = 3.449; int f = floor(theNum); int c = ceil(theNum); int r = round(theNum); cout << f << ' ' << c << ' ' << r << endl; return 0; }
Результатом будет 3 4 3
, и обратите внимание, что я включил <math.h>
, поэтому я могу использовать floor (), ceil () и round ().
Случайность
Я допускаю, что последняя тема о типах переменных была немного числовой, но она была важной, поэтому, если вы не уверены в чем-либо, вернитесь и перечитайте. А пока давайте повеселимся над тем, что мы только что узнали. math.h
поставляется со всевозможными полезными инструментами для искусства. Один из них генерирует поддельный поток данных с равномерно распределенными значениями. Программисты называют это случайным, и по иронии судьбы это совсем не случайное. Чтобы понять, что я имею в виду, запустите следующую программу три раза, каждый раз отмечая результат.
#include <iostream> #include <math.h> using namespace std; int main() { for(int i=0;i<3;i++){ cout << rand() << endl; } return 0; }
Каждый раз, когда вы запускаете программу, вы получаете один и тот же результат.
1804289383
846930886
1681692777
Это говорит нам о том, что последовательность начинается снова. Вы можете кое-что сделать с этим, и это будет рассмотрено в следующей главе. Поскольку это довольно большие числа, давайте нормализуем их, разделив на RAND_MAX
, константу, предоставленную math.h, которая является наивысшим возможным значением, которое случайная функция может вернуть.
#include <iostream> #include <math.h> using namespace std; int main() { for(int i=0;i<10;i++){ float r = rand() / (float)RAND_MAX; cout << r << endl; } return 0; }
Обратите внимание, что в выходных данных все числа находятся где-то между 0 и 1.
0.840188
0.394383
0.783099
0.79844
0.911647
0.197551
0.335223
0.76823
0.277775
0.55397
Давайте воспользуемся этим нормализованным случайным образом в генераторе изображений ascii. Умножая нормализованный случайный результат на 52, мы получаем случайные значения от 0 до 52.
#include <iostream> #include <math.h> using namespace std; int main() { for(int i=0;i<30;i++){ float r = rand() / (float)RAND_MAX; //normalize random cout << '8'; //snake tail for(int j = 0; j < r * 52;j++){ //from 0 to no more than 52... cout << '='; //print snake body } cout << "O~" << endl; // print snake head } return 0; }
В этих выходных данных каждая змея-погремушка имеет разную длину.
8============================================O~
8=====================O~
8=========================================O~
8==========================================O~
8================================================O~
8===========O~
8==================O~
8========================================O~
8===============O~
8=============================O~
8=========================O~
8=================================O~
8===================O~
8===========================O~
8==================================================O~
8================================================O~
8==================================O~
8======================================O~
8========O~
8================================O~
8=O~
8=============O~
8========O~
8==========================================O~
8=========O~
8=====================O~
8=======O~
8======O~
8====================================================O~
8============O~
Художникам нравится случайная функция, потому что она создает много органичной эстетики с минимальным набором текста. Опытный глаз может заметить случайную функцию даже на глубину нескольких октав перлина, точно так же, как вы можете посмотреть рекламу в торговом центре и назвать ее, как «это было так отфотошоплено». Использование случайной функции похоже на оставление «Lorem Ipsum». Возможно, я смогу выразить свое мнение более конкретно. Если вы находите привлекательным создавать визуальные элементы, имеющие фальшивый характер, просто представьте, какое влияние вы могли бы оказать, если бы вы использовали не фальшивый характер. Под этим я подразумеваю замену случайной функции данными из реального мира. Например, вы можете настроить Arduino или Raspberry Pi с фотоэлементом (датчиком освещенности), чтобы измерять, как автомобили, проезжающие мимо окна вашей квартиры, временно блокируют ближайший уличный фонарь, и использовать это в качестве входных данных для управления вашим программным обеспечением. Также попробуйте работать с большими данными из прошлого. Создание генеративного искусства, визуализация данных и работа с физическими вычислениями будут рассмотрены в следующих главах. Но не говорите, что я не предупреждал вас о политике.
В следующем примере я использую тот же случайный случай с масштабированием до 52, приводя его к char
и выводя псевдохаотическую стену букв.
#include <iostream> #include <math.h> using namespace std; int main() { for(int i=0;i<40;i++){ // 40 rows for(int j=0;j<100;j++){ // 100 columns float r = rand() / (float)RAND_MAX; // normalized random r *= 52; // scale to 52 r = 'A' + r; // offset by 'A' ascii value cout << (char)r; //output without a newline } cout << endl; //finally add the newline } return 0; }
Результат должен выглядеть так:
AGhX\LCddqT\lBC\cATDVd_qm\EcVephNCgRahtSMtfhbDaoOWhYMOSIZopDp[[QtZNErD[TOp\YqChilGAdnagftoMPS[_lVlOV
\YOJH^jB\Zrg]oalILfGEOAVBeqMJQobHdUUZH_l_r]HtVH^NZYrGKQaGbajMYUKBoWHrVGoEIDSNHiXSXkqbLdpNmY[`khYraWk
detrmP\[FV^nWfnfjegAo\YDfZcdKpno]HXtLXQ[nWYjSKtHaaAAifQVddKlelEEhaLLEUrrUNdOiiVOKAJtMkHU`JlItNMFLbej
ehcaC`LQeGh\]_ReHIZmk]gQH\Q_[WNTUXYUOETNcd[ffrXqQX[cU`tHcR]\lAOb]pNsMleXkBIeeW_hteOhIAZEIoigWA_[D]UL
Uace]OkTdQAF\iLCDIfPCYX\XHOle_dRCt]AmBTZ^gd\BGsJS``G]rSlsNNWkoMheXNcGKF]AJ`WEdTlGsBEZiqNShZgcdhBLL^b
aTLMbIgICcGrZapA_fZCW[iDXsYRXgKITqsNDG\I[FlTWRf^nhleUq`S]XUMEVYPFLj\cmMfNTEXMjZANcWJsGjrI`DSP[klfRja
CCHhD\qLKEeeZNfTKMWYmsXB\Bh_kRM\WUtsmnQHtbYqdk]^IrkICRFF[B^m[dgeBJcRDHpLT_IBdhfE_qqW[lp`f\ZIs^O\ecF]
`cZ^_WH[gBC\QBMFT]``qi[ZbeVZYe\O`Oe\qMV`ipdk\LHoNbAqOmOr[GVeWFhFWRPhRRC^ZKFsmZTjIIDHYTnWVbQDkopoiI^B
j\mOn_Uk\qV_gnRJgfnDenSEIVFd^knibonZFTTdGDHYeMUsadpSmjWDZZXZgqPHFEsVMslKGKR^W_WTT`WHcteUmkjTAYbbbjTS
UReHPcmQH`ApT[RNPlGbCXTIhcAj^PVQ[flMYTCDK^lVlPJSq_TYarVCX_gmGFcl_ctApKs\Of_llcfbS\cQnXpOrGVlsR\\kqcM
q`GMoKIl`alcUncGAdaX^BSmTc^DgOXJEot_BdZ^oWdKZOrMrYGrmLAiCrb^HGk[c[\GocOUUlMGfbKmBFJSifc_SIPILcVo_RWe
ljmONI\DthGIWiLWBqFNZjNLOTKqha^SpKFjeAgFocRTLRApiUflnGRqSSE^ilZkbWSSOpagIiN`mQ`GTGNGKhDXsMKdEmHGkrCD
lHjLU`cCtkVBFsnclMf_LLAXVqB[Tmj`RacbNBtKb^TGXH_]ZoESFPlplskjlXbekYKUqYT[LotHXdOVgBPKkOfVif_ajAaSYZ\Q
mHUUmCXhPsutgSjK`]NK^[g`tj]HF\M^RdDlnRHgcU_]Z`q`NCaRBI`W^fTTTE_VFQd^n\sDYFt]eGYDXpWmt]GmJccsXIHOs`mD
qrYOXWnKUhkcfdQIdpIAJJKcd[QYQEnXHbqncKIAIFS^sMBBoop]IZkq_]YhRI[TlsAPXolNglVeV\\EUiNQJhabRIJX\IqVBI^t
HRQSIQTb`BRE]ZONWcQAsFQrSItGCriLFNWMO]nmc]homhMtNTPgthWbYFJm[qoPas]lCJshnB[TsKL`o]EiieCnDtOZdgrdpIkO
ZpeUIoK]TNjMcR\X`cBkT]sgjhXVMeLcVORPdrioBsCVIjA`pF]D]TZUV^qe_AOUg^]NGISkNJVb\DYTFZGLWStoSWjtP^]H[ZfE
LIUTNImMojsNO]f[ZSmVBpr^tU_jVC]G^sdK^lPPst_R\Ekd_KKIOKjnjEIMh[UnR`itrkidlahU_VjYdk]P_YoWNYAcE^dcgbtO
iAEDqLiNOgPYppoNhdYYK[olrspHNUiiiXVbbglOiJGitEWlarWrjTMlCLEmCrkn[htVLUFSFKUkoskVkfXV_`ctgAaGodRMXdNH
e`dDnKLgirfgKLBlhLVOdlCaaXFfAV\FbWBMRaT]hEhCJYlRTmiOkeonVJ]\SfsMHMjkUn`ChZsSfInQdHOpB_rl^KB_bIDP^TKB
hDOiTPSsaeSI[^edjXgIRAWjq^^^tfF[XY]_Hf\R_Bph[VYIeBeINrgkPRclWWWDTb[Kgd[T[GVkB]Gga][e[fserTaJZPdemZjJ
XMB\nRXdJ`SkWClHqmkBsOgFliAj`A]aBQ_QbkggdGhQAF^nLaMnET_jUbjDt_sRmNYInfCDjJCMdKbROQBsLcLqDiPbTJgN\BOZ
qs_oRnAJQL[kL]JJboZOHSJeECSMP^TSnPpQgcjq[jkP_QFDdKW[Q]t`At\baJOIXqHUnKhjnkR^`EjFVgQc[[oJXNVXrrZJd[p`
UgsRTslFGFijiTSHMFBGefE_q__t^DOTNMktOGCb_c\SpnEImZrE`GWGUGcVdNY^iJMIa`ZFBRipVqNMVjH__kd[kc]ThKG^^e`N
iGTCGCIjROlLG[NPOF[HskpdQLcIKeFEBGJMmmPTYKcdChLAnMfJDnbC^qqoSQ]JsIftIbcqDiJXZGZRTTafqiAn[VUc\aTYqihN
iEc^pPDHYfAhaD`dZth_iBPYsVILTl_J_kCYiUWQPpP[aHNqt[oZAOtoBCDoI\qPPTErXcX_r[[Td]VCjFr_dbFkDa\ITgbcFg`q
LD_SsJkNp^CYteGkZAKDVAFsW`pQBj`LVNYKTbt_MWdLOrBSYGdLOH^dffiNUtIKGIlDOa_SrSZHO\_mPrAmLpNrMoGT[AVRO_kq
qnFp[mGHbHeannN`B\rTq]sPf_XJIi]]RBYhsZliHBTmSfbRRMHkSFpK\M[KJD[t[\SSQO`XtZnqkXCOeHrn`qkprBTIOOO]MAHB
F`KHI`]T_XdfPHoFas`GaGqoB`dC`MjIPDIZkdZH\pWLYMkLPbKLQETAnmKdZN\rFO^KFom`AlLBmobbAnMEMmX[_ZhANIn]ks[d
adBOJVtMHVJFRWdXQaUhYVb^DWY`aYsMH[WstWkc\KPiqKBHPgRZthXBccIFMi\X\Ib_Hlp`eKDZTelVNoMJhqk^]ZC\i_pIBf[V
DADDFsTMASVkqQ]Rob\TLHpCL`L^JVJaieCM]lYbsfpR^ss_bBZ]gPUZWPqAQhtWEAnXYWRcB]QViZ]P]mTRHntFOA_naEjpshKm
ItcSiM]]QE]KUqGZFS^hTdFXq]qfceBiNmGjGXh[A[`AOWJqqiWZDEarBeW[UENTMTnJZEDrNUK`tK^XITAhRFYdsLbJpkmMA^XN
[XoCTp\aBsDS]mXrXmAnmAFSAacgbN`ZCHhZoPnsF^HfXingeVkkl\U_WggOrgdWVlBFodVOnMecVA`ZQG[VWZjRmCAU]`kWSWjF
ZYY^AERTMbrKWD`_nojkCjEHVLEQi`Ms]o^hbLLVZFgWqiGilCrkJ^GD]NH_sbEpHhn[VrHijrH]NpiVEXWgbjcaP]tdMgA`TkJK
co`PfQiYGHKkmOindjgMi^iptspkemVpBaZlN^[iA\OakDkV]c[Sr[QaqrYW\CGfOGhimXdKRX^jpI^tEUbRWKVkbWCOLSP[JRMl
j\XUtEIFJNo^hoOoIMgkklGWqghLR__nKd_XtJe^jWOYXWjRIpcGiHRr]THpTfI]tnsKInULkpE]NOojOPCLUqiBL\R`dYY^Ki`X
p^NPhDj\Ilj]jNnnPlX`onn[pOiihJsSNehtl`]G[UULRttHXk^mrpOQtAEJpBmcfeBtXPEmEEoYPsEZRcfUJraDsH\iK\qMkDE[
^C[XXJE`_\TcT\De[nop[RFpC]OtKhtP`mMLhqErpIXbMXlf[NMYdltrV_Y_cFadPgemQgOOcSaBGZbQPTH^ikaVgNYc[DmYQm]S
LVt_anFDeHVjjJR`nF^C\oge[ElhXr[TGVoWrCNXGFZiJik\MbK^RAg`[pfmaYGfAAJF\LREqCZJZS_RZIPsBYmL\QmhShd]^UQt
WM\^fNOaJYrJpkpe`_PH^UMrmPbShtk`tiKdlhZMir`HD[\rE`qCLkLfUZsT[NVmGIlf[toeRtJOoNooJFJTUlAg^soeT`MqhOhF
Чтобы было интереснее, я сообщу палитру символов с индексом столбца, и я рандомизирую «маскирующую» часть кода, которая вставляет пробел в соответствии с настраиваемым условным условием.
#include <iostream> #include <math.h> using namespace std; int main() { for(int i=0;i<40;i++){ // 40 rows for(int j=0;j<80;j++){ // 100 columns float r1 = rand() / (float)RAND_MAX; // normalized random float r2 = rand() / (float)RAND_MAX; // normalized random r1 *= 3; // scale to 3 r1 = 30 + r1 + j; // offset by 'L' ascii value if(r2 > (i/40.0) || -i < -j+10){ // artistically tweaked inequalities r1 = ' '; //blank it out } cout << (char)r1; //output without a newline } cout << endl; //finally add the newline } return 0; }
В строке, отмеченной «художественно настроенные неравенства», мой процесс просто менял одну вещь в выражении, нажимал на компиляцию и смотрел, как я себя чувствую. Я сидел с кодом, пока не достиг золотого пятна. Вот результат.
= K Z
> C XZ
P Z fg
B B ] i n
> c l
= @ P T W Z h
<== C EH J L X \] m
@ B G MM PP W [ _ a cd h m
: AB EH IK M Y ^ b d n
= @ B E G N QP RU _` c g iij nm
A GH J N V `` a c gh j nn
< > A C EH L QP [ ^`a f h k
>?> G JJ KNMP P WYY ]^ a d fh lm
? A@ DDE K L N S WWXY \ ^] `` c l n
=>@ EFG QR TS W \ ] ` d g j l
;;= A BCD I IKML Q QRU VXX _ab gi o
;> BBB DE G J LO PQ UUWV Z \]_ _`b e fh k
< = @? D E HI N Q Q Y ZZ[ ^^ bb ddg h k
>@ @B D G N RR V [[\__ ` ddd g jj m n
? ? BD EFFHIKLK OOQQ RS WW []] cceg j km
JK LN PQ R VWW Z Z\] __`ab fhgh l
A@ D F GJK N ST VW Z[\ _ adcf i jm m
AA H KMNNPPQ TV WY ZZ ]` b dd g jjmnm
@C EEG JJ P RR W Y[[ \ ad de no
FG HIKJ NO QRU UX X [ _a`bcdfff jlk n
D EHG JK MO PRRUVU XYZ ^ ` acdcee klkm
CEE G J L ONOQ RTV V \ ]^ `aacc fhhjk lno
EGHH K LN QRRS UWWX \\]_ ccedffhh l no
JKKMNMOPRRT U XYYY ^ `aaa ef ih lln
GII KLLNPQP STUVY [\]^^` aadde hhjjmlo
GH MLNPQR SU WW XY[ ]^ _`bcddfhiij mno
HI KLM PQS SVV W Z ]\_``bbb gh ji ll
LLLNOQPQS TUWYZZZ\\_^_b bed ikkkmo
KNNPP QTUUVVX \^___abb eeghijkmmn
MMOOPQS UV WZZ ]^_abace efghilmmn
MNNOPRTTVWVWZZZ]]]^`abbefe gjkl nm
MO RQ T UWWX[[] ]```bcddefiijk ln
PPQSTSVVXYZYZ]\_^` ddffhhjikklo
PRRTTUUXYXZ\[^_`_bbbddefijillmo
Уже предупредив вас о его опасностях, я также придерживаюсь мнения, что компьютерная случайность, так же как Lorem Ipsum, поддельные пластиковые деревья и звук колокольчика Roland 808 - все они имеют свое место в качестве реальных элементов в искусстве, особенно когда художник создает реальное заявление об этих самых инструментах и их людях.
Алгоритм
Классический генератор случайных чисел алгоритм - это просто цикл обратной связи, в котором переменная возвращается сама на себя, каждый раз выполняя какие-либо манипуляции с ней. Нам возвращается снимок этого. Алгоритм - это просто процедура для выполнения чего-либо. Это причудливый термин для описания полезного фрагмента кода. На рисунке 31 показан алгоритм, представленный в виде блок-схемы, на которую многим легче смотреть, чем на синтаксис кода с отступом. Обратите внимание, что у него есть условные выражения, цикл и основная точка входа.
Если вы сможете понять random()
на алгоритмическом уровне, вы сможете глубоко ремиксировать его. Следующее - моя собственная быстрая случайная функция. Он берет символ (0–255) и добавляет к нему простое число, заставляя его переходить от 255 до 0 при выполнении итерации. Причина, по которой число переходит от 255 к 0 (вместо увеличения до 256), заключается в том, что char не может содержать больше этой суммы, поэтому это поведение переполнения.
#include <iostream> #include <math.h> using namespace std; int main() { unsigned char iterator = 0; for(int i=0;i<40;i++){ iterator += 67; float normalized = iterator / 256.0; cout << normalized << endl; } return 0; }
Отсюда вывод выглядит довольно органичным и хаотичным.
0.261719
0.523438
0.785156
0.046875
0.308594
0.570312
0.832031
0.09375
0.355469
0.617188
0.878906
0.140625
0.402344
0.664062
0.925781
0.1875
0.449219
0.710938
0.972656
0.234375
0.496094
0.757812
0.0195312
0.28125
0.542969
0.804688
0.0664062
0.328125
0.589844
0.851562
0.113281
0.375
0.636719
0.898438
0.160156
0.421875
0.683594
0.945312
0.207031
0.46875
Теперь давайте визуализируем ту же числовую последовательность, нарисовав полосы.
#include <iostream> #include <math.h> using namespace std; int main() { unsigned char iterator = 0; for(int i=0;i<40;i++){ iterator += 67; float normalized = iterator / 256.0; for(int j = 0 ; j < 100 * normalized ; j++){ // new cout << 'Z'; } cout << endl; } return 0; }
Визуализация выявляет закономерность, которая сильно отличается от классической случайности, но все же связана алгоритмически.
ZZZZZZZZZZZZZZZZZZZZZZZZZZZ
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
ZZZZZ
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
ZZZZZZZZZZ
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
ZZZZZZZZZZZZZZZ
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
ZZZZZZZZZZZZZZZZZZZ
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
ZZZZZZZZZZZZZZZZZZZZZZZZ
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
ZZ
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
ZZZZZZZ
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
ZZZZZZZZZZZZ
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
ZZZZZZZZZZZZZZZZZ
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
ZZZZZZZZZZZZZZZZZZZZZ
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
Иногда неожиданная петля обратной связи может быть всем, что вам нужно, вместо того, чтобы каждый раз тянуться к ванили random()
.
Подпись, Без подписи
Возможно, вы заметили использование специального ключевого слова в предыдущем примере unsigned
. В таких языках, как Java или Javascript, все числа имеют знаковые, что означает, что они могут быть отрицательными. (знак сообщает нам, отрицательное ли число). При работе на C мы извлекаем выгоду из удобства явного указания, является ли int или char подписанным или беззнаковым, путем добавления ключевого слова перед типом. unsigned char myByte = 128;
Чтобы увидеть разницу между знаковым и беззнаковым, давайте переберем все значения символа, распечатывая каждое из них.
#include <iostream> #include <math.h> using namespace std; int main() { for(int i=0;i<256;i++){ unsigned char c = i; //convert integer to char cout << (int)c << ' '; } return 0; }
Результатом является список целых чисел от 0 до 255, как и следовало ожидать.
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
250 251 252 253 254 255
По умолчанию символы подписаны. измените unsigned
на signed
и запустите его снова. Выход другой.
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
114 115 116 117 118 119 120 121 122 123 124 125 126 127 -128 -127 -126
-125 -124 -123 -122 -121 -120 -119 -118 -117 -116 -115 -114 -113 -112
-111 -110 -109 -108 -107 -106 -105 -104 -103 -102 -101 -100 -99 -98
-97 -96 -95 -94 -93 -92 -91 -90 -89 -88 -87 -86 -85 -84 -83 -82 -81
-80 -79 -78 -77 -76 -75 -74 -73 -72 -71 -70 -69 -68 -67 -66 -65 -64
-63 -62 -61 -60 -59 -58 -57 -56 -55 -54 -53 -52 -51 -50 -49 -48 -47
-46 -45 -44 -43 -42 -41 -40 -39 -38 -37 -36 -35 -34 -33 -32 -31 -30
-29 -28 -27 -26 -25 -24 -23 -22 -21 -20 -19 -18 -17 -16 -15 -14 -13
-12 -11 -10 -9 -8 -7 -6 -5 -4 -3 -2 -1
Вместо того, чтобы считать до 255, похоже, что последовательность останавливается после 127, переключается на -128, а затем отсчитывает оттуда, пока не достигнет -1. signed char
может считать только до 127 в обмен на возможность делать отрицательные значения.
Bool
Я уже упоминал Джорджа Буля, когда говорил о делах истинного и ложного. Хотя истина / ложь может быть сохранена в виде числа (ноль - ложь, а ненулевое значение - истина), C предоставляет удобный тип, называемый bool
, значение которого может быть только истинным или ложным. В следующем примере я объявляю логическую переменную и сохраняю в ней счетное целое число.
#include <iostream> #include <math.h> using namespace std; int main() { bool foo; for(int i=0;i<10;i++){ foo = i; cout << foo; } return 0; }
Результатом будет 0111111111
, поскольку bool
заботится только о том, является ли число нулем или отличным от нуля. конечно, поскольку bool имеет значение true или false, оно естественно сочетается с OR (||), AND (&&) и NOT (!), как я уже обсуждал в разделе Логическая логика. В следующем примере программы объявляются 4 логических значения и используются их как биты логики в 4-битном счетчике.
#include <iostream> #include <math.h> using namespace std; void render(bool v){ if(v){ cout << "democratic "; }else{ cout << "republican "; } } int main() { bool bit0 = false; bool bit1 = false; bool bit2 = false; bool bit3 = false; for(int i=0;i<32;i++){ render(bit3); render(bit2); render(bit1); render(bit0); cout << endl; bit0 = !bit0; if(!bit0){ bit1 = !bit1; if(!bit1){ bit2 = !bit2; if(!bit2){ bit3 = !bit3; } } } } return 0; }
На выходе - 4 бита, правильно считающих вверх в двоичном формате.
republican republican republican republican
republican republican republican democratic
republican republican democratic republican
republican republican democratic democratic
republican democratic republican republican
republican democratic republican democratic
republican democratic democratic republican
republican democratic democratic democratic
democratic republican republican republican
democratic republican republican democratic
democratic republican democratic republican
democratic republican democratic democratic
democratic democratic republican republican
democratic democratic republican democratic
democratic democratic democratic republican
democratic democratic democratic democratic
republican republican republican republican
republican republican republican democratic
republican republican democratic republican
republican republican democratic democratic
republican democratic republican republican
republican democratic republican democratic
republican democratic democratic republican
republican democratic democratic democratic
democratic republican republican republican
democratic republican republican democratic
democratic republican democratic republican
democratic republican democratic democratic
democratic democratic republican republican
democratic democratic republican democratic
democratic democratic democratic republican
democratic democratic democratic democratic
Корпус переключателя
Существует структура управления потоком, аналогичная if-then, которая иногда может быть более удобной, особенно если у вас есть длинная цепочка if-elseif-elseif-else. Он смотрит на одно целое число и переходит к маркеру (case
) в зависимости от того, какое значение имеет целое число.
#include <iostream> #include <math.h> using namespace std; int main(){ for(int i=0;i<200;i++){ float rnd = rand() / (float)RAND_MAX; int num = round(rnd * 5); switch(num){ case 0: cout << "////"; break; case 1: cout << "''''"; break; case 2: cout << "||||"; break; default: cout << " "; } if(i%16==15)cout << endl; } return 0; }
В этом примере вы видите, что за ключевым словом switch
следует (как если) (num)
, который является его критерием. Затем в фигурных скобках вы видите экземпляры case 0:
, и это закладки. Если бы num было 0 во время этой итерации цикла, то он перескочил бы к этой части дела и выполнил код. Результат будет выглядеть так:
////'''' |||| ''''//// |||| ////////
////||||////|||| //// ||||
''''//// |||| ||||'''' ////
''''|||| ||||''''''''||||''''|||| //// ||||
||||''''//// //// ||||'''' |||| ////
''''//// ''''||||||||
|||| ''''|||| ||||'''''''''''' //// ||||
'''''''' ''''////''''////||||//// ''''
''''|||| '''' ||||||||||||'''' ''''
||||'''' ''''|||||||| ''''''''|||| ''''
''''||||||||''''//// ||||'''' ||||'''' ////''''////||||
'''''''' |||||||||||| '''' '''' ||||
|||| ||||
Обратите внимание, что в выводе больше пробелов, чем в других фразах ascii. Это потому, что num устанавливается на случайное число от 0 до 5, но я предоставил варианты только для 0, 1 и 2. Внизу случая вы видите последний default
catching все остальные невысказанные случаи. Одна сложная вещь с кейсами заключается в том, что их блоки кода требуют в конце break
statement, если вы хотите пропустить весь путь до закрывающей фигурной скобки, когда закончите. В противном случае код фактически продолжит работу сразу в следующем случае!
#include <iostream> #include <math.h> using namespace std; float rnd(){ return rand() / (float)RAND_MAX; } int main(){ for(int i=0;i<20;i++){ switch(0){ case 0: cout << "You make me come"; if(rnd()>0.5)break; //break only half the time case 1: cout << "-plete"; if(rnd()>0.5)break; //break only half the time case 2: cout << "ly miserable"; if(rnd()>0.5)break; //break only half the time default: cout << " (guitar)"; } cout << '.' << endl; } return 0; }
При выборе варианта «разорвать» или «не разорвать» результаты могут быть каскадными. Выходные данные показывают, что каждая строка определенно начинается с варианта 0, а затем иногда продолжается до варианта 1. Среди тех, кто дошел до варианта 1, некоторые из них также выбрасывают случай 2 и так далее.
You make me come-pletely miserable.
You make me come-plete.
You make me come-pletely miserable.
You make me come.
You make me come.
You make me come-plete.
You make me come.
You make me come-pletely miserable.
You make me come.
You make me come-pletely miserable (guitar).
You make me come-plete.
You make me come.
You make me come.
You make me come.
You make me come.
You make me come-plete.
You make me come-plete.
You make me come.
You make me come.
You make me come-pletely miserable.
Последовательность побега
При работе с краской некоторые краски имеют особые инструкции по обращению. Один пигмент токсичен, и его нужно хранить в недоступном для детей месте. Другой пигмент требует охлаждения, а еще один нерастворим в воде и более требователен к среде. В C такие специальные символы обрабатываются с помощью escape-последовательности.
Давайте вернемся к предыдущему примеру кода, который отображал ////'''' ||||
, и попытаемся внести некоторые изменения. Предупреждение, это приведет к ошибке, и это не ваша вина. В case 1
замените четыре одинарные кавычки на три двойные.
#include <iostream> #include <math.h> using namespace std; int main(){ for(int i=0;i<200;i++){ float rnd = rand() / (float)RAND_MAX; int num = round(rnd * 5); switch(num){ case 0: cout << "////"; break; case 1: cout << """; // <--------------- changing this from '''' to """ break; case 2: cout << "||||"; break; default: cout << " "; } if(i%16==15)cout << endl; } return 0; }
Ошибка не так полезна, как мы видели, потому что препроцессор запутался.
/Users/jtnimoy/Downloads/tmp.cpp:15:90: error: expected ';' after expression
cout << """; // <--------------- changing this from single quote ' to double quote "
^
;
1 error generated.
Вы не можете поместить символ двойной кавычки в строковый литерал без использования escape-последовательности. В C escape-последовательность начинается с обратной косой черты, и это означает, что следующий символ должен интерпретироваться особым образом.
#include <iostream> using namespace std; int main(){ cout << "This is a quote ---> \" <---- Thank you." << endl; cout << "This is a backslash ---> \\ <---- Hooray." << endl; cout << "This is a tab ---> \t <---- Good Grief." << endl; cout << "This is a return ---> \n <---- What next?" << endl; cout << "This \ is a \ multiline \ string \ literal. Done." << endl; return 0; }
Вывод показывает, что обратная косая черта - это волшебство.
This is a quote ---> " <---- Thank you.
This is a backslash ---> \ <---- Hooray.
This is a tab ---> <---- Good Grief.
This is a return --->
<---- What next?
This is a multiline string literal. Done.
В строках, заключенных в двойные кавычки, нам нужна обратная косая черта, чтобы вставить двойные кавычки. То же самое верно и для символов, заключенных в одинарные кавычки. Вы можете заключить двойные кавычки между одинарными кавычками, но если вам нужен фактический символ одинарной кавычки, экранируйте его с помощью обратной косой черты. Последовательности побега сбивают с толку, потому что они саморефлексивны и выглядят странно, но к ним стоит привыкнуть, потому что вы будете видеть их все время.
Массивы
В некоторых из предыдущих примеров вы видели, что я пронумеровал похожие переменные, назвав их числами в конце. Они связаны по названию, и данные аналогичны.
#include <iostream> using namespace std; int main(){ int egg0 = 4; int egg1 = 5; int egg2 = 6; int egg3 = 7; cout << egg0 << egg1 << egg2 << egg3 << endl; egg0 += 1; egg1 += 1; egg2 += 1; egg3 += 1; cout << egg0 << egg1 << egg2 << egg3 << endl; egg0 += 1; egg1 += 1; egg2 += 1; egg3 += 1; cout << egg0 << egg1 << egg2 << egg3 << endl; return 0; }
В приведенном выше примере я должен обновить каждую из четырех egg
переменных, скопировав текст вокруг. Хотя этот метод грубой силы, вырезания и вставки прост для понимания и синтаксически более простой, он трудоемок, и компьютер не знает, что четыре яйца связаны между собой и имеют порядок. Цифры в конце имен переменных являются просто частью слова egg
. Мы упускаем возможность адресовать каждое яйцо по номеру, фактически контролируя, с каким яйцом мы работаем. Что, если бы я сказал вам, что так не должно быть?
Если переменная представляет собой ведро или кофейную чашку, то массив представляет собой коробку для яиц - одну коробку, в которой хранятся несколько одинаковых вещей. Массив - это одно имя переменной, но он ссылается на список значений. Все эти ценности имеют один тип. Давайте перепишем приведенный выше пример, используя массив, а не именованные переменные. Для этого нам нужно знать, как объявить массив.
При работе с отдельными яйцами в этой коробке используется аналогичный синтаксис квадратных скобок.
cout << eggs[2]; // output the value at index 2 eggs[0] = 45; // store 45 into the first index
При обращении к массиву квадратные скобки означают «посмотрите конкретно на этот», а при объявлении массива квадратные скобки означают «сделайте столько слотов». Новая программа «Яйцо», переведенная в основном (но не полностью), выглядит так.
#include <iostream> using namespace std; int main(){ int eggs[4]; // declare an integer array with 4 slots eggs[0] = 4; eggs[1] = 5; eggs[2] = 6; eggs[3] = 7; cout << eggs[0] << eggs[1] << eggs[2] << eggs[3] << endl; eggs[0] += 1; eggs[1] += 1; eggs[2] += 1; eggs[3] += 1; cout << eggs[0] << eggs[1] << eggs[2] << eggs[3] << endl; eggs[0] += 1; eggs[1] += 1; eggs[2] += 1; eggs[3] += 1; cout << eggs[0] << eggs[1] << eggs[2] << eggs[3] << endl; return 0; }
Вы можете видеть, что используется синтаксис массива, и что отдельные яйца могут обрабатываться одно за другим. Поскольку число больше не является частью идентификатора, а теперь является реальным целым числом, мы можем применить цикл for вместо вырезания и вставки, чтобы перебрать их все. Это делает код более лаконичным, более простым в обслуживании и дает нам возможность делать более интеллектуальные вещи в программе. Вот новая версия той же программы, в которой метод «один за другим» заменяется циклами for.
#include <iostream> using namespace std; int main(){ int eggs[4] = {4,5,6,7}; // declare an integer array with 4 slots, // and initialize it to a literal array of 4 values // print them out for(int i=0;i<4;i++){ cout << eggs[i]; } cout << endl; // increment them all for(int i=0;i<4;i++){ eggs[i]++; } // print them out for(int i=0;i<4;i++){ cout << eggs[i]; } cout << endl; // increment them all for(int i=0;i<4;i++){ eggs[i]++; } // print them out for(int i=0;i<4;i++){ cout << eggs[i]; } cout << endl; //report success return 0; }
В приведенной выше эволюции того же примера вы видите, что я мог бы сделать размер массива яиц сколь угодно большим, не замечая, что мой код в ответ становится длиннее.
Давайте продолжим эту идею увеличения, распечатав массив при его изменении.
#include <iostream> using namespace std; int main(){ char eggs[8] = { '|',' ','o',' ','x', ' ', '_' , ' ' }; for(int j=0;j<20;j++){ // print for(int i=0;i<8;i++){ cout << eggs[i]; } cout << endl; for(int i=8 ; i >= 1 ; i--){ //loop from max to min+1 eggs[i] = eggs[i-1]; // set current egg to previous egg's value } eggs[0] = eggs[7]; // wrap rightmost egg to leftmost egg } //report success return 0; }
В этом примере я сдвигаю содержимое массива вправо и заставляю его оборачиваться. На выходе получается конфета.
| o x _
_| o x _
_| o x
x _| o x
x _| o
o x _| o
o x _|
| o x _|
_| o x _
_| o x
x _| o x
x _| o
o x _| o
o x _|
| o x _|
_| o x _
_| o x
x _| o x
x _| o
o x _| o
Смахивая вниз и оставляя след, мы сделали одномерный массив двухмерным.
Многомерные массивы
Однако вам не обязательно делать массивы одномерными. Чтобы объявить дополнительное измерение, добавьте в конце дополнительные квадратные скобки.
#include <iostream> using namespace std; int main(){ //initialize a 2D array int tictactoe[3][3] = { {0,1,2}, {3,4,5}, {6,7,8} }; for(int y=0;y<3;y++){ // loop rows for(int x=0;x<3;x++){ // loop columns cout << tictactoe[y][x]; // print } cout << endl; } return 0; }
Обратите внимание, что синтаксис для его инициализации литеральными данными имеет внутренние вложенные фигурные скобки для каждой строки. 2D-массив может использоваться в качестве пиксельного буфера для запоминания ваших значений цвета.
#include <iostream> using namespace std; int main(){ //initialize a 2D array int smiley[5][5] = { {0,0,0,0,0}, {0,1,0,1,0}, {0,0,0,0,0}, {1,0,0,0,1}, {0,1,1,1,0} }; //render it right side up for(int y=0;y<5;y++){ // loop rows for(int x=0;x<5;x++){ // loop columns if(smiley[y][x]){ cout << '#'; }else{ cout << ' '; } } cout << endl; } cout << endl; //render it sideways for(int y=0;y<5;y++){ // loop rows for(int x=0;x<5;x++){ // loop columns if(smiley[x][y]){ // <----- X and Y flipped this time cout << '#'; }else{ cout << ' '; } } cout << endl; } return 0; }
В приведенном выше примере я визуализирую пиксели правой стороной вверх, а затем визуализирую второй раз, когда X и Y перевернуты внутри smiley[x][y]
. Результат должен выглядеть так:
# #
# #
###
#
# #
#
# #
#
Ошибки
В массивах есть несколько вещей, которые делают их опасными. Один из них связан с тем, что мы узнали из переменных, и это связано с оставшейся памятью, если вы не инициализируете ее значением. Давайте сделаем это в следующем примере.
#include <iostream> using namespace std; int main(){ //initialize a 2D array int smiley[5][5]; // <-------look, no initialization //render it right side up for(int y=0;y<5;y++){ // loop rows for(int x=0;x<5;x++){ // loop columns cout << smiley[y][x] << ' '; } cout << endl; } return 0; }
Мой результат (который, несомненно, отличается от вашего) выглядел так.
1479976160 32767 1608491643 32767 1479976184
32767 1479976184 32767 0 1
1479976208 32767 126439424 1 0
14 1479976224 32767 0 0
0 0 0 0 0
Когда-нибудь в вашем произведении произойдет сбой, и он вам понравится, но в конце концов вы задумаетесь, как это исправить. Когда наступит этот день, я надеюсь, вы вспомните, что массивы и переменные C должны быть инициализированы начальным значением, иначе вы обнаружите, что они заполнены визуализацией внутренней памяти вашего компьютера.
Другая ошибка возникает, когда вы пытаетесь получить доступ к индексу в вашем массиве, который находится за его пределами. В случае int eggs[4]
, это любое число ниже 0 или выше 3. Массивы имеют нулевой индекс, то есть первый находится в нулевом слоте, а последний - в (размер - 1). Это дает программисту большое математическое преимущество, и к нему стоит привыкнуть. Сделаем ошибку намеренно.
#include <iostream> using namespace std; int main(){ int eggs[4]; for( int i = 0 ; i < 10 ; i++ ){ cout << eggs[i] << endl; } return 0; }
Страшно то, что даже когда я зацикливаюсь от 4 до 10, он все еще обращается к таинственной памяти и выводит ее. Вот мой глючный вывод.
0
0
0
0
0
0
227347776
1
1906995960
32767
Если вы будете повторять достаточно долго, вы можете в конечном итоге вызвать ошибку Segmentation fault
. Программа остановлена операционной системой из-за попытки проникнуть в запрещенное место.
Передача массивов функциям
Когда вы передаете массив функции, он по умолчанию передается по ссылке, а это означает, что функция не получает локальную копию переменной и вместо этого начинает работать с той же переменной. Давайте посмотрим на это в действии.
#include <iostream> using namespace std; /** i will add 1 to each item in an array len is the length of the array a is the array */ void addOne(int a[], int len){ for( int i = 0 ; i < len ; i++ ){ a[i] ++; } } int main(){ // declare int eggs[10]; // initialize values for( int i = 0 ; i < 10 ; i++ ){ eggs[i] = 42; } // pass to function which adds one addOne( eggs, 10 ); // output the new values for( int i = 0 ; i < 10 ; i++ ){ cout << eggs[i] << ' '; } cout << endl; return 0; }
В этом примере я инициализирую все яйца со значением 42, а затем передаю его в качестве аргумента addOne()
. Массив не несет собственных метаданных «размер», поэтому необходимо передать размер в качестве второго аргумента. Затем addOne()
просматривает все элементы и добавляет по одному к каждому. Обратите внимание, что я не возвращаю массив, но, тем не менее, в main()
после того, как я вывожу значения, они равны 43. Массив передается по ссылке. Если вы хотите передать копию, вам нужно объявить вторую и скопировать в нее значения. Переключение между массивами может быть очень эффективным. В следующем примере я не совсем дословно копирую значения во второй массив. Вместо этого я использую предыдущее состояние как простые правила.
#include <iostream> using namespace std; int main(){ int width = 10; int height = 10; // setup int worlds[2][10][10] = { { {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,1,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0} } }; bool currentWorld = true; //------------------------------------------------ for(int i=0;i<9;i++){ // draw for(int y=0;y<10;y++){ for(int x=0;x<10;x++){ if(worlds[!currentWorld][y][x]==1){ cout << '#'; }else{ cout << '.'; } } cout << endl; } cout << endl; //------------------------------------------------ // update for(int y=0 ; y < height ; y++){ for(int x=0 ; x < width ; x++){ bool center = worlds[!currentWorld][y][x]; // collect which neighboring pixels are on // modulo by width or height for wrap-around access. int left = worlds[ !currentWorld ][ y ][ (x + width -1) % width ]; int right = worlds[ !currentWorld ][ y ][ (x+1) % width ]; int up = worlds[ !currentWorld ][ (y + height -1) % height ][ x ]; int down = worlds[ !currentWorld ][ (y+1) % height ][ x ]; if( left + right + up + down > 0){ worlds[currentWorld][y][x] = !center; // flip value }else{ worlds[currentWorld][y][x] = 0; // write a blank } } } //------------------------------------------------ // swap buffers currentWorld = !currentWorld; } return 0; }
Копируя значения из одного буфера в другой, я считаю, сколько соседних пикселей включено. Если есть, я меняю значение на обратное и пишу пробел. В результате получается эффект ряби, при котором точка превращается в клетчатый ромб.
.......... .......... .......... .......... .....#.... .......... .......... .......... .......... ..........
.......... .......... .......... .....#.... ....#.#... .....#.... .......... .......... .......... ..........
.......... .......... .....#.... ....#.#... ...#.#.#.. ....#.#... .....#.... .......... .......... ..........
.......... .....#.... ....#.#... ...#.#.#.. ..#.#.#.#. ...#.#.#.. ....#.#... .....#.... .......... ..........
.....#.... ....#.#... ...#.#.#.. ..#.#.#.#. .#.#.#.#.# ..#.#.#.#. ...#.#.#.. ....#.#... .....#.... ..........
....#.#... ...#.#.#.. ..#.#.#.#. .#.#.#.#.# #.#.#.#.#. .#.#.#.#.# ..#.#.#.#. ...#.#.#.. ....#.#... .....#....
...#.#.#.. ..#.#.#.#. .#.#.#.#.# #.#.#.#.#. .#.#.#.#.# #.#.#.#.#. .#.#.#.#.# ..#.#.#.#. ...#.#.#.. ....#.#...
..#.#.#.#. .#.#.#.#.# #.#.#.#.#. .#.#.#.#.# #.#.#.#.#. .#.#.#.#.# #.#.#.#.#. .#.#.#.#.# ..#.#.#.#. ...#.#.#..
.#.#.#.#.# #.#.#.#.#. .#.#.#.#.# #.#.#.#.#. .#.#.#.#.# #.#.#.#.#. .#.#.#.#.# #.#.#.#.#. .#.#.#.#.# ..#.#.#.#.
Развитие пиксельного буфера, использующего себя в качестве простых правил, часто называют клеточными автоматами, и это вдохновляющая модель природы. Если это похоже на то, в каком направлении вы идете, попробуйте Game of Life Конвея и Wolfram.
В приведенном выше примере кода вы видите, что у меня есть процедура setup, за которой следует цикл, который выполняет update, а затем что-то рисует. Если у сообщества веб-разработчиков есть шаблон проектирования Модель, Представление, Контроллер (MVC), у творческих программистов есть шаблон проектирования Настройка, Обновление, Рисование. Мы могли бы, по крайней мере, разделить фрагменты кода на их собственные функции. Вот тот же пример, отредактированный для разделения каждой из трех стандартных подпрограмм. Обратите внимание, что для разделения переменных между функциями мне пришлось сделать их глобальными.
#include <iostream> using namespace std; int width; int height; int worlds[2][10][10] = { { {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,1,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0} } }; bool currentWorld; // -------------------------------------- void setup(){ width = 10; height = 10; currentWorld = true; } // -------------------------------------- void update(){ for(int y=0 ; y < height ; y++){ for(int x=0 ; x < width ; x++){ bool center = worlds[!currentWorld][y][x]; // collect which neighboring pixels are on // modulo by width or height for wrap-around guard. int left = worlds[ !currentWorld ][ y ][ (x + width -1) % width ]; int right = worlds[ !currentWorld ][ y ][ (x+1) % width ]; int up = worlds[ !currentWorld ][ (y + height -1) % height ][ x ]; int down = worlds[ !currentWorld ][ (y+1) % height ][ x ]; if( left + right + up + down > 0){ worlds[currentWorld][y][x] = !center; // flip value }else{ worlds[currentWorld][y][x] = 0; // write a blank } } } // swap buffers currentWorld = !currentWorld; } // -------------------------------------- void draw(){ for(int y=0;y<10;y++){ for(int x=0;x<10;x++){ if(worlds[!currentWorld][y][x]==1){ cout << '#'; }else{ cout << '.'; } } cout << endl; } cout << endl; } // -------------------------------------- int main(){ setup(); for(int i=0;i<9;i++){ draw(); update(); } return 0; }
Я смог потерять комментарии, обозначающие раздел кода, так как содержащая функция называется этим. Самодокументирующийся код, который требует меньше комментариев, - хороший код. Некоторые говорят, что если вы напишете программу правильно, она не нуждается в комментариях. Также обратите внимание, что я добавил разделительные линии между функциями, чтобы их было легче увидеть. Это необязательно, но они отображаются повсюду в OpenFrameworks.
В следующем примере я использую ту же технику пиксельного буфера для рисования спирали (функции косинуса и синуса), но на этот раз я показываю только последний кадр.
#include <iostream> #include <math.h> using namespace std; int width, height; int world[10][40]; int spiralCounter; // -------------------------------------- void setup(){ width = 40; height = 10; spiralCounter = 0; //zero the memory for(int y=0;y<height;y++){ for(int x=0;x<width;x++){ world[y][x] = 0; } } } // -------------------------------------- float rnd(){ return (float)rand() / RAND_MAX; } // -------------------------------------- void update(){ int offsetX = 20; int offsetY = 5; float rScale = 0.0415; float thScale = 0.075; float theta = spiralCounter * thScale; float r = spiralCounter * rScale; //basic use of cos() and sin() int y = offsetY + round(sin( theta ) * r); int x = offsetX + round(cos( theta ) * r) * 2; // x 2 for ascii art aspect if(y < height && y >= 0 && x < width && x >= 0){ world[y][x] = 1; } spiralCounter++; } // -------------------------------------- void draw(){ for(int y=0;y<height;y++){ for(int x=0;x<width;x++){ if(world[y][x]==1){ cout << '@'; }else{ cout << '.'; } } cout << endl; } cout << endl; } // -------------------------------------- int main(){ setup(); for(int i=0;i<500;i++){ update(); } draw(); return 0; }
На выходе получается спиральный след.
....@.@.......@.@.........@.@.@.......@.
....@.......@.................@.@.....@.
....@.....@.........@.@.........@.......
..@.......@.......@...@.@.@.....@.@.....
..@.......@.....@.........@.......@.....
..@.......@.....@...@.....@.@.....@.....
..@.......@.......@.@.....@.@.....@.....
....@.....@.@.............@.......@.....
....@.......@.@.........@.@.....@.@.....
......@.......@.@.@.@.@.@.......@.......
Динамическое размещение и указатели
Помимо объявления новых переменных и массивов, как я показал вам, есть еще один способ, который использует другой синтаксис и немного сложнее, но его стоит знать. Динамическое размещение позволяет нам зарезервировать глобальную память с большей гибкостью, чем традиционные объявления на основе стека. Весь бизнес динамического распределения вращается вокруг ключевых слов new
и delete
, и, наоборот, вы несете ответственность за очистку после себя. Вот динамически выделяемое целое число.
#include <iostream> using namespace std; int main(){ int *myNumber = new int; (*myNumber) = 5; cout << myNumber << endl; delete myNumber; return 0; }
Префикс звездочки является частью синтаксиса указателя. Использование его в объявлении означает, что переменная будет не int, а указателем на int. Используя его в следующей строке, чтобы присвоить myNumber значение 5, звездочка отменяет ссылки, заставляя указатель myNumber
действовать как обычная переменная на тот момент, когда я присваиваю значение. Вы могли заметить странный вывод этой программы - что-то вроде 0x7feb6ac03980. Это большое число произошло потому, что мы отправили myNumber
на cout
в необработанном виде без отмены ссылки. Если вы добавите звездочку прямо перед myNumber
в код и повторно запустите, вы увидите удовлетворительный 5. Звучит непонятно? Ты не одинок. Это печально известно как одна из самых сложных частей C.
Что такое указатель?
Представьте себе переменную a
, целое число, содержащее значение 42.
Ну 42 где-то в памяти компа сидит. Вы можете думать о компьютерной памяти как о районе, а каждое место - как о доме с адресом. Эти адреса можно записать и обменять.
В C ++ мы используем оператор амперсанда ссылка перед именем переменной, чтобы обозначать 3
, адрес в памяти, где находится значение 42
.
При объявлении указателя мы ставим перед именем звездочку *, что означает, что он содержит адресную ссылку, а затем мы можем сохранить в нем ссылку.
Одна из причин, по которой это может сбивать с толку, заключается в том, что переменная может находиться сама по себе, без звездочки или амперсанда и выглядеть как обычная переменная, но все же это указатель.
Так что, если я пишу код, который использует B, и я хочу получить доступ к значению вместо адреса указателя? Я ставлю перед переменной звездочку другого типа, оператор разыменования, чтобы снова превратить 3 в 42.
В следующем примере я создам указатель на уже существующую переменную, а затем увеличу обе.
#include <iostream> using namespace std; int main(){ int a = 42; int *b = &a; // create a pointer to a cout << b << ' ' << a << endl; a++; (*b)++; cout << b << ' ' << a << endl; return 0; }
Обратите внимание, что последний результат - 44, поскольку при увеличении b и a к той же переменной добавляется 1.
Указатели также могут передаваться в качестве аргументов функциям с теми же результатами. В приведенном ниже примере (результат 45) я показываю пару приемов.
#include <iostream> using namespace std; void addOne(int *that){ (*that) ++; } void addTwo(int &that){ that += 2; } int main(){ int a = 42; addOne( &a ); addTwo( a ); cout << a << endl; return 0; }
В addOne()
я передаю указатель. Это может быть полезно, потому что не дает мне забыть, что я работаю с чем-то особенным. В addTwo()
я передаю по ссылке этот амперсанд, и он обеспечивает более чистый код, рискуя забыть, что локальная переменная связана с чем-то большим.
Пересылка декларации
Возможно, вы заметили, что все мои функции в предыдущих примерах были определены в определенном порядке. main()
всегда был самым последним. Что, если я поставлю main()
раньше?
#include <iostream> using namespace std; int main(){ doSomething(); return 0; } void doSomething(){ cout << "hi there" << endl; }
Ошибка показывает, что компилятору не удалось найти doSomething()
.
/Users/jtnimoy/Downloads/tmp.cpp:5:2: error: use of undeclared identifier 'doSomething'
doSomething();
^
1 error generated.
Если бы мне все время приходилось управлять всем в порядке зависимости, все довольно быстро вышло бы из-под контроля. К счастью, в C ++ есть форвардные объявления. Объявление функции в начале без определения полного фрагмента кода предоставляет компилятору легенду, поэтому он позволяет вам размещать ваши функции в любом порядке (после этого).
#include <iostream> using namespace std; //forward declaration void doSomething(); int main(){ doSomething(); return 0; } void doSomething(){ cout << "hi there" << endl; }
Фактически, форвардные объявления являются настолько важной частью написания C и C ++, что мы помещаем их в файл заголовка. Ниже приведен пример файла заголовка с разделением. #pragma once
означает, что этот файл должен быть включен только один раз. Заголовочный файл может взять на себя задачу включения библиотек с использованием пространств имен, и именно здесь вы должны выполнять прямое объявление функций.
// -------- filename: code.h ---------- #pragma once #include <iostream> using namespace std; void doSomething(); int main();
Между тем, в файле C ++ гораздо меньше работы, и вы можете размещать свои функции в любом порядке.
// -------- filename: code.cpp ---------- #include "code.h" int main(){ doSomething(); return 0; } void doSomething(){ cout << "hi there" << endl; }
Давайте изменим этот пример, чтобы использовать личное пространство имен.
// -------- filename: code.h ---------- #pragma once #include <iostream> using namespace std; namespace tutorial{ void doSomething(); }; int main(); // -------- filename: code.cpp ---------- #include "code.h" int main(){ tutorial::doSomething(); return 0; } void tutorial::doSomething(){ cout << "hi there" << endl; }
Обратите внимание, что в файле заголовка пространство имен tutorial
содержит предварительное объявление функции в этом пространстве имен, затем в файле реализации кода (.cpp) имя функции украшено tutorial::
перед ним. В C ++ двойное двоеточие означает, что объект справа организован внутри объекта слева, что немного похоже на биологическую классификацию. life::domain::kingdom::phylum::class::order::family::genus::species
. Пространство имен - не единственное, что вы можете использовать для организации ваших функций в красивые связки. В последнем разделе я проведу экскурсию по структурам и классам, и этого должно быть достаточно, чтобы вы познакомились с начальными темами OpenFrameworks. Классы и объектно-ориентированное программирование будут рассмотрены более подробно в следующей главе.
Классы
Приятно организовывать вещи в кластеры, создавать ассоциации между ними, сохранять порядок и оперировать ими как группой - еще один вид абстракции. class - это одна из конструкций, которая позволяет вам это делать. Сгруппируем целые числа x
и y
в класс Point
.
// -------- filename: code.h ---------- #pragma once #include <iostream> using namespace std; namespace tutorial{ class Point{ public: float x; float y; }; } // -------- filename: code.cpp ---------- #include "code.h" int main(){ tutorial::Point p; p.x = 5.8; p.y = 2.5; cout << p.x << ' ' << p.y << endl; return 0; }
В приведенном выше примере я определяю класс Point
, и вы видите внутри него метку public:
, которая позволяет мне использовать точечный синтаксис и позже сказать p.x
. Если бы я пропустил public:
, это было бы то же самое, что набрать private:
, и компилятор не позволил бы мне ссылаться на внутренние члены класса. Класс также может содержать функции! Давайте добавим функцию к классу Point
, которая будет инкапсулированно работать со своими значениями.
// -------- filename: code.h ---------- #pragma once #include <iostream> using namespace std; namespace tutorial{ class Point{ public: float x; float y; void move(int offsetX,int offsetY); }; } // -------- filename: code.cpp ---------- #include "code.h" int main(){ tutorial::Point p; p.x = 5.8; p.y = 2.5; p.move(4,4); cout << p.x << ' ' << p.y << endl; return 0; } void tutorial::Point::move(int offsetX,int offsetY){ x += offsetX; y += offsetY; }
Обратите внимание, что синтаксис пространства имен применяется при определении функции позже… tutorial::Point::move
. Внутри этой функции я имею в виду x
и y
как локальные переменные, и оказалось, что это работает, потому что функция находится в классе Point
, рядом с этими переменными-членами, так что это имеет смысл. Когда я вызываю p.move()
, один экземпляр этого класса добавляет 4 к своим членам x и y.
При динамическом размещении экземпляра класса используется тот же синтаксис указателя, но синтаксис точки меняется на стрелки.
int main(){ tutorial::Point *p = new tutorial::Point(); p->x = 5.8; p->y = 2.5; p->move(4,4); cout << p->x << ' ' << p->y << endl; delete p; return 0; }
Теперь, когда вы познакомились с синтаксисом стрелок для указателей экземпляров класса, вот более подробный способ ссылки на x и y в move()
:
void tutorial::Point::move(int offsetX,int offsetY){ this->x += offsetX; this->y += offsetY; }
Причина, по которой x и y работают, заключается в том, что они неявно являются членами this
, который является указателем на текущий экземпляр класса.
Когда вы видите классы в шаблонах кода OpenFrameworks, верхняя часть объявления класса может содержать больше, чем я вас учил. Что-то вроде:
class ofApp : public ofBaseApp{
Таинственная часть в конце, которая идет : public ofBaseApp
, означает, что класс ofApp
наследует некоторые заранее записанные переменные и функции от OpenFrameworks, поэтому он более эффективен, чем кажется на первый взгляд.
Струны STL
Теперь, когда вы немного разбираетесь в классах, давайте вернемся к некоторым классам, которые всегда были с нами. std::cout
- это экземпляр iostream, а оператор ‹ Хотя C традиционно использует c-строки (массивы символов с нулевыми байтами в конце) в качестве предпочтительного строкового формата, OpenFrameworks традиционно выбирает более надежную строку STL, поскольку она ближе к Java и имеет множество полезных функций.
#include <iostream> int main(){ std::string s = "hi"; s += " there"; std::cout << s << ' ' << s.size() << std::endl; return 0; }
В приведенном выше примере я добавляю дополнительный текст в свою строку с помощью оператора сложения + и вызываю s.size()
, чтобы получить количество байтов в тексте.
Поскольку объект STL String расширяет c-строку, в C ++ существует множество классов и пакетов, предназначенных для улучшения различных доменов. OpenFrameworks - одна из таких коллекций многократно используемого кода.
Заключение
Поздравляем, теперь вы должны знать достаточно C ++, чтобы начать работу с OpenFrameworks, набором функций и классов, которые облегчат вам жизнь вычислительного поэта. Конечно, я не просто научил вас всему языку, но если вы хотите пойти дальше в изучении C и C ++, вот несколько книг, которые я рекомендую.
Брайан В. Керниган; Деннис М. Ричи (март 1988 г.). Язык программирования C (2-е изд.). Энглвуд Клиффс, Нью-Джерси: Prentice Hall. ISBN 0–13–110362–8. (Amazon: https://www.amazon.com/Programming-Language-2nd-Brian-Kernighan/dp/0131103628)
Бьярн Страуструп (2013). Язык программирования C ++ (4-е изд.). Эддисон-Уэсли. ISBN 978–0–321–56384–2 (Amazon: https://www.amazon.com/The-Programming-Language-4th-Edition/dp/0321563840)
Для онлайн-справки я рекомендую https://en.cppreference.com и https://cplusplus.com.
Библиография
- Назад в будущее II. Реж. Роберт Земекис. Perf. Майкл Джей Фокс, Кристофер Ллойд, Леа Томпсон ,. Юниверсал Пикчерз, 1989. Фильм.
- Карвер, Сесили «То, что мне хотелось бы, чтобы кто-то сказал мне, когда я учился программировать». HACKER MONTHLY, выпуск 45, февраль 2014 г .: стр. 28–30. Распечатать.
- Кубби, Ключевые слова C ++. cppreference.com 29 ноября 2013 г. Интернет. 15 февраля 2014 г. https://en.cppreference.com/w/cpp/keyword
- Дэн Бенджамин, 10 лучших программных шрифтов. hivelogic.com 17 мая 2009 г. Интернет. 22 марта 2014 г. https://hivelogic.com/articles/top-10-programming-fonts
- С начала - https://snap.nlc.dcccd.edu/learn/selena/history.html
- Деннис М. Ричи - https://cm.bell-labs.com/who/dmr
- Маэда, Джон. Дизайн по номерам. Кембридж, Массачусетс: Массачусетский технологический институт, 1999. Печать.
- Матрица. Реж. Братья Вачовски. Perf. Киану Ривз, Лоуренс Фишберн, Кэрри-Энн Мосс. Warner Brothers, 1999. Фильм.
- Шахтерское ремесло. Версия 1.7.5. Mojang. 27 февраля 2014 года. Видеоигра.