01:56 

Учимся программировать на С++. Лекция 6. Память

ToxicSpider
Бритуля - Богиня
Доброго всем здравичка))))

Напоминаю условие:
Напишите программу, которая получает от пользователя 2 положительных числа и высчитывает среднее арифметическое между ними.
Запустите программу со следующими числами:

1. 4 и 2
2. 5 и 2

Проверьте результат!

Напоминаю: среднее арифметическое высчитывается по формуле: сумма чисел, разделенная на количество чисел.

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


Код:





Результат запуска с цифрами 4 и 2:





Результат запуска с цифрами 5 и 2:




Примечания:
1. Я выбрал тип float потому что переменная int НЕ способна хранить дробные числа, а результат моего деления может быть дробным. Вы спросите, почему я не объявил как float ТОЛЬКО переменную average?
Объясняю: результат деления int на int - это int. Дробная часть у int - просто "выкидывается", то есть, поделив int на int мы потеряем дробную часть результата еще ДО того, как запишем в переменную, типа float. ЕСЛИ бы мы объявили хотя бы одну переменную как float, то код отработал бы правильно, то есть:

float first_num = 0;
int second_num = 0;
float average = 0;


ТОЖЕ отработает правильно, так как результат деления float на int ИЛИ int на float - это float.

Существует способ объявить переменные first_num и second_num как int и НЕ потерять дробную часть при делении, но пока вы его не знаете. Узнаете, обещаю))))

2. Обратите внимание на мой cout, он получился сложнее, чем привычный. Проверьте, что вы все понимаете в нем)))

3. Чтобы программа выполнила деление ПОСЛЕ сложения - я взял операцию сложения в скобки





Лекция, которую я хочу написать - необходима. Да, именно так... Дело в том, что программист С++ не может существовать без понимания того, как работает память. В отличии от языков Python, C#, Java, PHP и прочих, в которых программист очень ограничен возможностями языка (в этих языках все готово и преподнесено "на блюдечке"), язык С++ дает ОГРОМНУЮ свободу программисту.

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

Учить на память то, что я напишу - не нужно. Зубрежка - это ВСЕГДА неправильный подход. То, что я напишу - нужно ПОНЯТЬ. Если вы не поймете это - вас всегда будут преследовать "белые пятна" в ваших знаниях, НО поняв это - вы будете жонглером, способным элегантно, красиво и просто решать сложные задачи.

Для начала - немного отвлечемся от С++ и попробуем решить легкую, коротенькую задачку:

У вас есть ящик и шарик. Если разобраться, то у вас есть 2 варианта действий с этими вещами: положить шарик в ящик ИЛИ не положить. Это - тривиально. Идем дальше:
у вас есть 2 ящика и 2 шарика. Варианты:

1. не положить шарик ни в один из них
2. положить только в первый
3. положить только во второй
4. положить в оба.

Теперь, попробуем заметить "закономерность" количества вариантов:

Так как в каждый ящик можно положить ИЛИ не положить шарик, то с каждым ящиком есть 2 варианта действий. Это элементарно. Теперь, можно заметить, что выбор "положить или не положить шарик во второй ящик" НЕ зависит от того, положили ли мы шарик в первый. То есть, по законам математики, количество возможностей перемножается. В случае с двумя ящичками есть: 2 * 2 варианта, что равно четырем (как мы можем заметить выше - это так и есть)

Таким образом, мы можем просчитать, сколько возможностей будет с тремя ящичками: 2 * 2 * 2 = 8 (можете проверить).

Количество разных вариантов высчитывается по формуле: (количество вариантов с одним ящиком В СТЕПЕНИ количества ящиков). То есть, если есть 3 ящика - то количество вариантов 2^3 (значком ^ я обозначаю действие "в степени").

Напоминаю для тех, кто забыл/не знал:
Что такое умножить 2 на 3? Это все равно как написать 2 + 2 + 2. То есть - прибавить цифру 2 три раза. Что такое поднять 2 в степень три? Это все равно как написать 2 * 2 * 2 (три раза). Если мы хотим поднять 2 в пятую степень, то это: 2*2*2*2*2 (пять раз). У этого есть сокращенная запись: 2 ^ 5.

Если то, что я написал не совсем понятно или выглядит спорно, то лучше остановиться на этом подумать, проверить..... хороший программист это тот, кто все проверяет сам, не полагаясь на других)))

Если все понятно - пойдем дальше.

Память компьютера - это много-много ящичков. Каждый такой ящичек называется "бит". Бит способен хранить заряд электричества. Если в бит "положить" электричество, то это условно обозначают "1". Если в бит НЕ положить электричество - это "0". Теперь мы понимаем, почему в компьютере все хранится в единицах и нулях)))

Если условно сгруппировать биты по 8 штук в группу, то такую группу битов называют "байт". Почему именно по 8-мь? Так сложилось, не будем сейчас об этом, это не интересно. Если "байт" это 8 "ящичков", то сколько есть вариантов положить туда "шарики" (электричество)? Как мы уже знаем - это 2^8 = 256 вариантов.

Теперь, давайте представим, что у нас нет цифр 4, 7 и так далее, а есть только две цифры - 0 и 1. Если с помощью такого исчисления мы хотим выразить 0 - то мы так и пишем - 0. 1 - так и пишем - 1. А как записать 2? Для этого не хватит одного ящичка, нужно уже минимум два. И тогда мы сможем выразить цифру 2 вот так: 10.

Если у нас есть 3 бита, то мы можем выразить 8 чисел:
0 - 000
1 - 001
2 - 010
3 - 011
4 - 100
5 - 101
6 - 110
7 - 111

То есть, как мы видим, мы можем выразить ЛЮБУЮ цифру, оперируя исключительно единицами и нулями. Но тут же возникает вопрос: с положительными числами - все прекрасно, а как быть с отрицательными? Как записать число -5, например?

Это хороший вопрос! И тут на помощь приходит самый "высокий" бит, то есть - самый левый. Если там 0 - число положительное, если там 1 - отрицательное. Например, уже знакомый нам тип int хранится в 2-х байтах (в Windows, Linux хранит этот тип в 4-х) То есть, давайте посчитаем вместе: 2 байта - это 16 битов (ящичков). 2^16 = 65536 вариантов!

Что это значит? Это значит, что в двух байтах можно хранить 65536 разных чисел. Из них - половина имеет 0 как самый высокий бит, половина - 1. То есть, если относится к этому биту как И к знаку, то числа которые можно хранить в int - ОТ -32768 ДО +32767, если НЕ относится к этому биту как к знаку, то можно хранить ОТ 0 ДО +65536.

А-А-А-А! Скажут некоторые! Теперь-то нам понятны 2 вещи с предыдущей лекции!
1. Почему существует ограничение MIN_INT и MAX_INT
2. Почему, как только мы приписываем к типу переменной слово unsigned, то оно становится способным хранить от 0 до MAX_INT * 2.

Если вы это поняли - я вас поздравляю! Вы - молодцы! Если нет - перечитайте абзац сначала, пока не дойдет)))

Примечание: по моему объяснению можно сделать такой вывод:
0001 - это цифра 1
1001 - это цифра -1

так вот: первое утверждение - правильно, а второе - нет. -1 записывается как 1111. Почему это так и как осуществляется переход на отрицательные числа - я рассказывать не буду. Это не нужно. Кому интересно - может почитать в википедии про способ перехода "Two's complement".

Отлично, скажете вы. А как хранить буквы, символы и т.д.? Решение напрашивается само-собой. Мы просто "присвоим" каждой букве порядковый номер. И такие номера пропишем в табличку. Такая таблица существует! Она называется ASCII. Если набрать в гугле "ascii table", то мы ее увидим:



Давайте немного задержимся на ней и разберем. Мы видим такие колонки:

Dec - это номер в десятичном исчислении (10 цифр), то есть, как мы привыкли 0-9
Hx - это "шестнадцатиричная" система (16 цифр) такая система тоже существует, но она нам не нужна
Oct - это "восьмиричная" (8 цифр) - тоже не нужна
Html - это код, на котором пишут интернет страницы - нам не нужен
Char - это сама буква

То есть, как мы видим - буква 'a' имеет номер 97, например.

Обратим внимание: цифры тоже имеют свои номера! То есть:

int a = 1;
char b = '1';


a и b - РАЗНЫЕ вещи!!!!!! ЗАПОМНИТЕ этот факт! Мы к нему вернемся!

Теперь становится понятным почему, во время объявления переменной мы пишем ее тип! По двум причинам:
1. Во-первых, char хранится в одном байте, int - в двух... объявляя тип мы говорим компилятору: на сколько байт "смотреть"? Если int - то нужно смотреть на 2 байта.
2. Компилятор видит число 97... это число 97 ИЛИ буква 'a'?

Примечание: можно узнать в скольких байтах компьютер хранит то или иное число, если воспользоваться оператором sizeof. То есть код:
cout << sizeof(int) << endl; // выводит на экран количество байт, в которых хранят int

можно написать похожий код:
int a;
cout << sizeof(a) << endl;
// тоже самое

Примечание: Прежде чем я перейду к финалу лекции, хочу напомнить, что есть еще типы, которые хранят дробные числа. Мы не будем разбирать этот тип по битам по 2 причинам:
1. Это сложно (там не совсем тривиально, но если кто хочет - может почитать об этом в инете)
2. Для программера С++ это, как правило, не нужно.


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

Когда операционная система запускает программу, она выделяет под нужды программы определенное количество памяти. Память, которой располагает программа, делится на 2 части. Первая часть - маленькая, но там всегда идеальный порядок и доступ к ней - быстрый. Эта часть называется "стек" (от англ "stack"). За стеком "следит" операционная система. Вторая - значительно бОльшая часть памяти, называется "куча" ("heap"). За памятью в куче, в С++, следит программист.

Когда программист в С++ объявляет переменную - именно он решает, где ее выделить - в куче или в стеке. Как именно это происходит и почему так происходит - мы поговорим позже. Сегодня мы немного заглянем в стек. Ниже на рисунке я очень схематично нарисовал этот стек. На рисунке я сделал СПЕЦИАЛЬНО 2 вещи:
1. я нарисовал там "пробелы". По многим соображениям, не будем об этом....
2. я нарисовал разные типы, но клетки одинакового размера. Как мы уже знаем - это не так, но данный рисунок - всего лишь схема.

Допустим, мы написали код, в котором объявили 4 переменных:

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


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



Давайте разберем:
черным, внутри клетки, я обозначил содержимое переменной. Красным - ее имя. А что я обозначил синим? Я обозначил синим ее адрес.

У каждой переменной есть адрес! Именно так компьютер и "запоминает" наши переменные! Имя создано лишь для удобства программиста! Имя переменной, для компьютера, НЕ ЗНАЧИТ НИЧЕГО!

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

int num; // без инициализации, то есть НЕ int num=0;
cout << "the number is: " << num << endl;

что произойдет, когда отработает строка cout? Программа обратится по адресу, в котором лежит переменная num, найдет там СЛУЧАЙНЫЕ единицы и нули и "переведет" это соответственно заявленному типу - целое число. То есть на экран будет выведено: "the number is: " и после двоеточия - ЛЮБОЕ число, которое зависит ТОЛЬКО от вашего личного мусора)))) Можете написать и запустить эту программу, чтобы ЛИЧНО убедиться в моей правоте.

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




Тем, что я рассказал вам сейчас - я "закинул удочку" на последующие свои лекции. Мы поговорим про эти адреса, что они дают и как этим пользоваться. Но все это будет уже позже. Эта лекция и так была сложной и длинной, но она была ОЧЕНЬ важной! Не идите дальше, пока эти знания не будут хорошо и уверено сидеть в голове!

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

Эта лекция была нетривиальной, поэтому, я позволю себе закончить програмисткой шуткой: "В мире есть 10 типов людей, те, которые знают двоичное исчисление и те, которые не знают". Если у вас возник вопрос: "А еще 8?" - мигом перечитывать лекцию)))



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


Напишите программу, которая выдает на экран размеры всех, пройденых на предыдущих лекциях переменных, в байтах. Напоминаю, для этой цели вам необходим оператор sizeof. Удачи!

@темы: C++

URL
Комментарии
2012-01-03 в 07:54 

Juliya_Luthor
I believe in Moire. Twice. 3-й-Невеста-4-й сезоны "Шерлок ВВС"? Нет, не видел.
:horror: :facepalm3: :night:
Лучше Шерлока Холмса глянь... :pity:

2012-01-03 в 11:19 

ToxicSpider
Бритуля - Богиня
Juliya_Luthor, Вышел???? Гая Ричи????


Все, понял))) BBC)))

Смотрю))))
ААААА!!!!!!
"Мертвые не улетают на небо, их свозят в особую комнату, а потом сжигают"
Он - лучший!!!!

URL
2012-01-04 в 05:34 

Juliya_Luthor
I believe in Moire. Twice. 3-й-Невеста-4-й сезоны "Шерлок ВВС"? Нет, не видел.
ToxicSpider, второй Шерлок Гая Риччи кстати тоже вышел!

   

Godney

главная