ToxicSpider
Бритуля - Богиня
Доброго времени суток!


Условие:
Перепишите домашнее задание с предыдущей лекции с дополнительным требованием: в вашем коде обязан присутствовать хотя бы один блок switch-case.

Условие домашнего задания, о котором идет речь:
Напишите программу, которая получает от пользователя символ и выполняет следущие действия:

Если символ это буква - программа пишет в консоль "символ является буквой",
а также: если эта буква заглавная, то программа добавляет: "символ является заглавной буквой"

Если символ это цифра - программа пишет: "символ является цифрой",
а также, если это 5 - добавляет: "это 5",
а также, если это 7 - добавляет: "это 7",
а также, если это 9 - добавляет: "это 9"

В любом другом случае программа пишет: "символ не является ни буквой, ни цифрой"


Запустите программу со следущими символами:
a, Z, 2, 7, [ , @ (кому не видно: пятый символ - квадратная скобка, находится на той же клавише, что и русское 'х')


Подсказка: желательно вспомнить, что существует таблица ASCII и внимательно ее изучить!







Я долго раздумывал как мне продолжить, дело в том, что есть 3 темы, примерно подходящие нашему уровню знаний, и поэтому тяжело было выбрать, с какой начать. В итоге, после долгих размышлений, все-таки "перевесили" указатели. Мы забираемся еще на одну ступеньку, поэтому - пристегните ремни, я начинаю.....

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


Область видимости переменной


Я не стал делать из этого отдельную тему, по причине, что много тут не расскажешь, НО! То, что я расскажу - вы должны знать как "отче наш"! Если ночью вас разбудить - от зубов чтобы отлетало!

Каждая переменная занимает место в памяти, это мы уже знаем. Это место занимается, как только код доходит до строки, в которой переменная объявляется. Осталось только понять, когда место освобождается? Как я уже упоминал, в том виде, в котором мы пока умеем объявлять переменную - память выделяется в стеке (помните? "стек", "куча"...). Так же я говорил, что операционная система сама отвечает за распределение памяти в стеке.

Мы объявляем переменную int num и с этого момента можем ею пользоваться до тех пор, пока она "жива". "Срок жизни" каждой переменной, обычно называют "область видимости", как только алгоритм выходит за область видимости конкретной переменной - она "умирает" и память, которую она занимала - освобождается.

Объявлять переменную, в языке С++ можно в любом месте в коде. Дело в том, что в языках, существовавших до С++ переменные можно было объявлять только в начале блока, до выполнения каких либо действий, однако в С++ это ограничение было снято, что позволило программисту объявлять переменную там, где она ему необходима, без надобности "перематывать" текст к началу блока, однако, переменная умрет, как только выйдет за границы блока, в котором ее объявили. Примеры:



Разберем:
переменная num существует в границах блока main, переменная symbol - только в блоке if. Немного не понятна одна деталь: что такое переменная global_var и чем она отличается от num? Вроде я говорил, что вся программа расположена в границах блока main, то есть по завершению блока - программа закончится....

Ответ: и да и нет))) а именно:
"по завершению блока main - программа закончится" - это правда
"программа расположена в границах блока main" - а вот это не совсем....

Можно открывать другие, вспомогательные блоки, на подобии main, но мы пока еще не умеем (скоро научимся). Такие блоки будут называться "функции" и они являются основой "процедурного программирования", к изучению которого мы вот-вот приступим))) пока мы учим только азбуку.

Чтобы сильно не подвесить это дело в воздухе, я лишь коснусь: global_var будет "жить" и в main и в любом другом месте кода, ВЕЗДЕ, а вот num в других функциях "виден" не будет и обращаться к нему там будет невозможно.

Теперь поговорим немного о "хорошем тоне":

Некоторые, прочитав статью, зададутся справедливым вопросом: зачем объявляют переменные в блоках и усложняют себе этим жизнь? "Тут видно, тут не видно"... объявил все переменные глобальными и не паришься....

Я понимаю этот вопрос, так как в свое время тоже его задавал. Однако, переменные должны существовать только там, где они нужны! В вашем коде не должно быть ничего лишнего! Если глобальная переменная не оправдана (а в ПОДАВЛЯЮЩЕМ большинстве случаев это так), то она не имеет права на существование!

Оправдано такое ограничение многими вещами, например, во время отлова багов, гораздо проще "проследить" где происходит ошибка, если точно знать границы видимости тех или иных переменных. Это только один из массы аргументов в эту пользу. Поэтому, глобальные переменные объявляют только тогда, когда без нее либо очень глупо, либо - невозможно, а такие случаи - очень редки. Как вывод - глобальные переменные в коде не упрощают жизнь, а круто ее усложняют)))

В принципе - я закончил эту тему. Это - ОЧЕНЬ важные знания и их не тяжело запомнить, так что ОЧЕНЬ советую постараться)))




Указатели


Вспомним картинку, которую мы уже видели раньше:



На этой лекции мы будем говорить про адреса, они на картинке обозначены синим цветом. Дело в том, что существуют типы переменных, которые способны хранить эти адреса. Эти типы имеют общее название "указатель" (англ - pointer). Слово "указатель" не случайно, так как он держит "в себе" адрес той или иной переменной, по которому к этой переменной можно обратиться, чтобы, например, изменить ее содержимое.

Объявляется указатель просто: пишется тип переменной, на которую он указывает, после этого "звездочка", после звездочки - имя. К примеру:

int * p_num;

Сразу "стравлю" вам кусочек кода + картинки, посмотрите, а потом мы подискутируем)))

уже известный нам кусок кода создает переменные, изображенные на картинке выше:

int num = 5;
float result = -7.21;
char letter = 'a', symbol = '#';


Теперь указатели:

int * p_num;
float * p_result;
char * p_letter, * p_symbol;


Обратим внимание! Во время объявления указателей на char - я использовал трюк, позволяющий объявить одинаковый тип в строчку, через запятую, НО!!! На этот раз я обязан уточнить, что ОБЕ переменные - указатели! Делаю я это звездочкой! Если бы я написал: char * p_letter, p_symbol; - то я бы объявил указатель p_letter и "обычную" переменную, содержащую символ!

Примечание: я специально дал именно такие имена указателям, чтобы было понятно, на что они указывают.

Присваиваем указателям адреса переменных:

p_num = & num;
p_result = & result;
p_letter = & letter;
p_symbol = & symbol;


Примечание: Символ & говорит компилятору: "дай мне адрес этой переменной"

Примечание: я мог объявить указатель и сразу присвоить ему адрес: int * p_num = & num;

Итак, "заглянем в память", что произошло?



Как мы видим, указатели содержат в памяти адреса присвоенных переменных, НО! Они - тоже переменные и поэтому - тоже имеют адреса))) Вывод, который напрашивается сам собой: а можно ли создать указатель, содержащий адрес другого указателя? Ответ очевиден: МОЖНО! Делается это так:

int ** p_num2; // с двумя звездочками!!!!
p_num2 = & p_num;

ИЛИ

int ** p_num2 = & p_num;

Нужно ли это? Да, бывает.... подробнее об этом далее))) За свою практику я не использовал ни разу более 3-х звездочек))) и то - 3 - огромная редкость! Мы дойдем до причин, пока просто знайте: можно.

Примечание: внимательные читатели спросят: а что будет, если объявить указатель, не присвоив ему адрес и по ошибке к нему обратиться?
Если у вас возник такой вопрос - то дайте я вас расцелую! Потому что это означает, что вы - прекрасные ученики!

Как я объяснял ранее, при объявлении переменной, операционная система не чистит и не обнуляет память, то есть, программа полезет в переменную, прочтет случайный набор единиц и нулей, "переведет" это как адрес и попытается по этому адресу обратиться. Что произойдет? Программа ТУТ ЖЕ рухнет!!! Потому что скорее всего, она обратится по адресу, ей не принадлежащему, а эту наглость операционная система простить не сможет! Программа будет убита, а система отрапортует: "segmentation fault" - и это очень печальный результат! Если вы видите такую ошибку - где-то вы нажгли с памятью непростительных вещей)))

Как же выходят с ситуации? Если нельзя сразу присвоить адрес, то его обнуляют, то есть адрес будет указывать на ячейку памяти по адресу 0. Этот нулевой адрес обозначается словом NULL (большими буквами), то есть:

int * p_num = NULL;

Примечание: В нулевой адрес ТОЖЕ нельзя обращаться! Если это сделать, то программа НЕИЗБЕЖНО рухнет! НО!!! Ведь всегда, ДО обращения можно спросить: а не указывает ли наш указатель на NULL? И если это так - не обращаться к нему! То есть:

if(p_num != NULL)

Примечание: в чем разница между случаем, когда указатель указывает на ноль и когда указатель указывает на "мусор", потому что мы не присвоили ему адрес? ИМЕННО в возможности такой проверки! Если мы напишем так:

int * p_num; // объявим, ничего не присвоив
// тут какой либо код, в котором присвоение не происходит
if(p_num != NULL) // не указывает ли указатель на ноль? Нет! Не указывает...
// отлично, скажем мы и полезем к нему.... и это будет последнее, что сделает программа, перед тем, как с грохотом рухнуть)))

Отлично, подводные камни мы прощупали, поэтому приступим к рассуждениям как пользоваться указателями. К указателю есть 2 типа обращений. В первом случае мы можем обратиться к его содержимому - то есть к адресу той переменной, на которую он указывает, а во втором - к содержимому той переменной, на которую он указывает. Как обращатся к содержимому самого указателя (к адресу переменной) мы уже поняли - точно так же, как к содержимому любой переменной, а вот как обратиться к содержимому той переменной, на которую указывает наш указатель, чтобы, допустим поменять ее содержимое? Это делают, ставя звездочку, перед указателем. Нагляднее это продемострирует следущий код:




Результат выполнения программы:




Разберем:

Я объявил 3 переменных: int a, int b и указатель на int. В начале а содержит 1, b содержит 2. Я распечатываю это, чтобы наглядно в этом убедиться. После этого, я присваиваю указателю адрес а и через указатель меняю содержимое переменной а. Я распечатываю обе переменные снова, чтобы было видно, что изменения произошли. После этого, я присваиваю указателю адрес переменной b, меняю значение b и снова распечатываю. Вглядитесь в код и в результат, чтобы убедиться, что все понятно.

Итак, на этой лекции мы выучили 2 вещи, во-первых: область видимости переменных, а во-вторых - указатели. На данном этапе, еще пока не понятно, для чего указатели нужны. У указателей ОГРОМНОЕ значение! Просто пока мы не видим где именно их применять. Наберитесь терпения!


Домашнее задание:


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

Программа должна:
1. Распечатать содержимое всех переменных, включая указатели (ВАЖНО! кроме содержимого указателя на char!)
2. Распечатать адреса всех переменных, включая указатели (ВАЖНО! кроме адреса переменной char!)
3. Только для указателей: распечатать содержимое переменных, на которые указывают указатели (через указатели)
4. Только для указателей: изменить содержимое переменных, на которые они указывают (через указатели)
5. Распечатать все заново, то есть повторить с 1 по 3 пункт.

Примечание: Адреса переменных, на самом деле, записываются в шестнадцатиричной системе. Цифры в этой системе: 0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f (всего 16-ть). Чтобы зрительно отличить шестнадцатиричную систему от "обычной" - десятичной, перед числом добавляют ноль и букву 'x'. То есть, распечатав адрес, вы увидите примерно следующее: 0x28ff0e

Удачи!

@темы: C++