• Авторизация


Программирование на Java: от ламера - чайнику. Пост №1. Странные типы. 01-11-2013 18:47 к комментариям - к полной версии - понравилось!


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



Что есть данные вообще?
"Есть только 0 и 1, остальное от лукавого..."(с)

Сей раздел полезен для понимания и к программированию имеет косвенное отношение в целом. Сия информация, как я помню, дается на уроках информатики ещё в школе, но вдруг кто забыл:)
Для компьютера любые данные, будь то цифры, символы или картинки - это просто набор электрических сигналов, составленных по определенным правилам. Возьмем цифири. Вот мы видим число 5, как "5", а для компьютера это 101. Почему так? Это называется двоичной системой счисления. Как правило, мы пользуемся десятичной, которая использует цифры от 0 до 9. И десятка записывается как 10. В двоичной системе всё несколько иначе. Она использует только 0 и 1. То есть, тут главное прочувствовать такую вещь: в десятичной системе десятка это 10, а в двоичной десяток это 2 в десятичной. Кажется, это называется переносом в старший разряд. Или это я так называю... Старший разряд - это крайняя левая цифра в числе, младший, соответственно, крайняя правая. Суть в том, что как только в самом старшем разряде хочет появиться 10 для десятичной системы, например, в результате переноса из разряда помладше при сложении, скажем 99+1=100, то в двоичной системе такое происходит когда хочет появиться 2. Например, сложим 1+1. В десятичной системе должно получиться 2. А в двоичной - получится 10. Потому что 10 в двоичной это 2 в десятичной:) А 3 десятичное это 11 в бинарном коде. Бинарный код - второе название двоичной системы счисления. А 4 уже 100. Вроде пояснил. У меня это больше уже на подкорковом понимании, а вот объяснить толково пока не получается:) Если не понятно - напишите в комментариях, я ещё попытаюсь как-нибудь:)

Компьютер использует именно двоичную систему счисления, потому что для него 0 - это нет электрического сигнала, а 1 - есть сигнал. И вот на такой простой вещи строится вся цифровая электроника.

Так же для понимания полезно знать, что 8-разрядное двоичное число, или оно же однобайтное, это число которое имеет нолики и единички в количестве 8 штук, например, 11110000, что равно 240 в десятичной системе, или 1111, что равно 15 в десятичной системе. Скажете 1111 - это 4 единицы, а я сказал про 8 цифр? А тут действует хитрость: можно дополнить до 8 цифр незначащими нулями в старших разрядах, то есть получится 00001111, а это одно и тоже. Кстати, каждый разряд бинарного числа называется битом.
Правило перевода из бинарного кода в десятичное число - очень простое. Берем двоичное число, например, 1101 и переводим:) Шучу. Теперь берем цифры, начиная справа, и умножаем их на 2 в степени номера разряда посчитанного с нуля и справа налево:) Так, не надо хвататься за голову и выпадать в нерастворимый осадок:) Вот у нас число 1101, раскладываем: 1*2^0 + 0*2^1 + 1*2^2 + 1*2^3. Знак ^ - это возведение в степень. Проще всего, чтобы не запутаться в степенях в которые надо возводить двойку, слева направо написать над разрядами двоичного числа цифры от нуля, получится примерно так:

3 2 1 0 - это степени двойки
1 1 0 1 - а это наше число.

Считаем, получаем 13.
Тут можно проследить некоторую аналогию с десятичной системой, для понимания. В десятичной системе любое число можно разложить по такому же принципу, только заместо 2 будет 10 в определенной степени. То есть получится число единиц, плюс число десятков, плюс число тысяч и так далее:) 2 в двоичной системе и 10 в десятичной системе называется основанием системы счисления, то есть количеством цифр, которые используются для записи числа. Так же, в программировании распространена шестнадцатиричная система исчисления. По аналогии, роль 10 в десятичной системе тут играет 16, а для записи используются цифры 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A(10), B(11), C(12), D(13), E(14), F(15). В скобках даны значения цифры в десятичной системе.

Основные типы данных в Java.
"Восемь бит - это байт, а 9 бит - толпа хулиганов."(с)

Этих типов данных не очень много, целых 8: 4 для целых чисел, 2 для дробных, 1 для символов, 1 для логических значений.
Переменная каждого из этих типов занимает строго определенное количество байт в памяти.

Соотвественно по группам:
для целых чисел это byte, int, short и long, которые занимают 1, 4, 2 и 8 байт в памяти соответственно;
для дробных чисел это float и double - 4 и 8 байт соотвественно;
для символов это char занимающий 2 байта;
для логических значений тип boolean, занимающий всего 1 бит.

В языке Си, о котором я писал в прошлом посте набор типов почти такой же. Но есть отличия - в нём нет отдельного типа данных для логических значений и количество байт, отведенных под переменную определенного типа, задано не строго, как в Java, а зависит от архитектуры машины. То есть можно поиметь много проблем, перенеся на очень мощный компьютер программу, написанную без предосторожностей на очень слабой машине.
Вы можете задаться вопросом: а на кой фиг для целых чисел аж 4 типа данных и для дробных 2? А я вам отвечу - у каждого из этих типов есть ограничения на число, которое он может вместить. Это ограничение накладывает количество байт используемых под переменную.
Например, тип byte, которому отводится всего 1 байтик в памяти, может хранить числа из диапазона 0...255 или -128...127. Почему так? Переведем число 255 в двоичную систему счисления. А, я ж не рассказал как:) Как правило, это делается делением уголком. Поскольку я не могу нарисовать здесь уголок, попробую объяснить так. Суть в том, что нужно делить число в десятичной системе на 2, то есть на основание новой системы счисления, до появления в остатке числа, которое при делении на 2 или основание новой системы счисления даст дробь меньше 1. На примере будет понятнее:

255/2=127, остаток 1; ^
127/2=63, остаток 1; |
63/2=31, остаток 1; |
31/2=15, остаток 1; ^
15/2=7, остаток 1; |
7/2=3, остаток 1; |
3/2=1, остаток 1. ^
----->-----------------|
Дальше делить бесполезно, получится 1/2, что меньше 1. А теперь суть метода. Берем последний результат деления и пишем его как старший разряд, остаток - следующий разряд, остаток от предыдущего деления - следующий разряд и так далее, то есть идем по стрелочке. Извиняюсь за кривость, рисовать никогда не умел:) То есть, результат последнего деления - старший разряд или крайняя левая цифра, а остатки пишутся в следующих разрядах в порядке получения этих самых остатков:) Кипит мозг? Охладите его кофейком, пивком или перекуром:)

В итоге мы получили 11111111 в двоичной системе. А теперь прибавим 1. Получим число 256 в десятичной или 100000000. Это уже 9 бит. Поскольку, у нас отведено всего 8 бит, старший разряд отсечется и останется число 00000000 или 0. Это называется переполнением и это может принести очень много проблем. Раньше когда особых вычислительных мощностей не было, программисты экономили память и там, где не могло быть числа больше 255, использовали тип byte(аналогично char в Си, он мог хранить символы, а мог числа). При обработке больших массивов данных это очень сильно снижало требования к компьютеру.
Кстати, такого вида диапазон определяется очень просто: берем количество значащих бит, отведенных под переменную, в нашем случае это 8, и возводим 2 в такую степень. Получаем 2 в 8 степени или 256. То есть, 8 бит данных могут дать 256 различных комбинаций. Поскольку одна комбинация "0" вычитаем одну и получаем предельное значение 255. Итого получаем формулку: 2 в степени количества значащих бит, отведенных под переменную. Всё просто:) И лишний раз напомню, что 1 байт это в бит.

Теперь относительно диапазона -128...127. Тут своя шутка юмора. Дело в том, что переменные в Java отводят старший бит под знак. Если старший бит 0 - значит число положительное, если 1 - значит отрицательное.
А теперь самое забавное: как говорил один препод: "Компьютер - это очень исполнительный, но крайне тупой работник"(с). К чему это я... Ах да, компьютер не умеет вычитать. То есть совсем не умеет. Вычитание заменили сложением с отрицательным числом. А делается это вот так...
Для примера посчитаем выражение 128-100.
Берем число 100 и переводим в двоичную систему счисления, получим 01100100. Это называется прямым кодом числа. Теперь инвертируем биты, то есть где был 0 - станет 1, где была 1 - станет 0. Получим 10011011. Это называется обратным кодом числа. А теперь прибавим 1. Получим 10011100. Это называется дополнительным кодом числа. Теперь переведём 128 в двоичную систему, получим 10000000. А теперь побитово сложим:
10000000
10011100
-----------
100011100
Произошло переполнение - получилось 9 бит. Не беда, отбрасываем старший бит, получаем 00011100, переводим в десятичную систему и получаем 28. Всё чин по чину.
Суть в том, что вычитание заменяется сложением, только вычитаемое переводится в дополнительный код.
Кстати, знаковый бит не является значащим. И если у нас тип byte(8 бит) и один бит отведен под знак, диапазон получается от -(2^7) до 2^7-1.
И наконец, зачем я всё это расписал. В языке Си можно указать какой диапазон значений переменная может принимать, используя специальное слово unsigned, в переводе "беззнаковый". Если оно есть - переменная принимает только положительные числа. Если нет, значит переменная знаковая и она принимает и положительные, и отрицательные значения.
А вот в Java такого нет. Здесь только знаковые переменные, то есть нельзя заставить переменную принимать только положительные значения. Не знаю, хорошо это или плохо - в моих задачах это пока что роли не играло. Хотя, многие это хаят...

Допустимые диапазоны значений переменных для целочисленных типов:
byte: -128...127
short: -32768...32767
int: -2147483648...2147483647
long: -9223372036854775808...9223372036854775807
Маленькие циферки, правда?:)

Теперь о типах float и double, которыми задаются дробные числы. Я не буду расписывать их формат, что там и как, я просто приведу пределы. Если кто-то сильно заинтересуется - напишите в комментах и я чуть позже допишу это. Итак,
float: −3.4·10^38..3.4·10^38
double: −1.8·10^308..1.8·10^308
Ещё меньше чиселки:) Завалиться можно:)

Тип char используется для хранения символов. В Си он занимает 1 байт, а в Java 2. Почему? Потому что используются разные кодировки для символов. В Си, если я не ошибаюсь, кодировка ASCII, символы которой занимают максимум 1 байт, а в Java - Unicode, символы которой кодируются двумя байтами каждый. Что такое кодировка? Всё просто. Кодировка - это таблица соотвествия символов и кодов. Каждому символу соответствует определенный код и при отображении текста читается 1 или 2 байта, находится код в таблице и по этому коду определяется, какой символ нужно вывести:)

Ну и наконец тип boolean. Он выдает всего два значения: true или труп false. Истина или ложь. Это из алгебры логики, её я коснусь в следующем посте.

Программка о типах данных
Итак, ниже я приведу код программки, которая показывает описанное выше. Открываем Eclipse, с прошлого раза у нас остался проект, так что не будем создавать новый. Просто удаляем лишнее и дописываем нужное:)


public class Hello {

public static void main(String[] args) {
// TODO Auto-generated method stub
int varInt;
byte varByte = Byte.MAX_VALUE;
short varShort = Short.MAX_VALUE;
long varLong;
boolean varBool = true;
float varFloat = 150.1F;
double varDouble = 150.1;
char varChar = 'X';


System.out.println("Число типа byte до переполнения " + varByte);
varByte++;
System.out.println("Число типа byte после переполнения " + varByte);

varInt = Integer.MAX_VALUE;
System.out.println("Число типа int в битовом и обычном видах до переполнения " + Integer.toBinaryString(varInt) + " " + varInt);
varInt = varInt + 1;
System.out.println("Число типа int в битовом и обычном видах посе переполнения " + Integer.toBinaryString(varInt) + " " + varInt);
varLong = Long.MIN_VALUE;
System.out.println("Диапазон для числа типа long от " + varLong + " до " + Long.MAX_VALUE);
System.out.println("Переменная типа float будет выглядеть так: " + varFloat);
System.out.println("Хотя обозначается она как 150.1F и если забыть F, программа не заработает и будет громко ругаться матом");
System.out.println("А типа double вот так " + varDouble);
varLong = 100000L;
System.out.println("Переменная типа long в коде программы будет обозначаться как 100000L");
System.out.println("А выведется как " + varLong);
System.out.println("L в конце числа типа long забыть можно, но это дурной тон, лучше ставить");
System.out.println("А вот так выведется символ char " + varChar);
System.out.println("Ну и наконец тип boolean, который при выводе выглядит так: " + varBool);
varBool = false;
System.out.println("Ну или так: " + varBool);
}

}


Написали? Умнички:) А теперь выбираем в меню Run-Run или нажимаем Ctrl+F11 и наблюдаем результат выполнения в окошке Console.

А теперь давайте разбираться, что мы тут понаписали. В принципе, выводимые строки говорят сами за себя, но остановлюсь на нескольких моментах. System.out.println() все помнят что такое?:)
Во-первых, вы наверняка заметили, что первую переменную varInt я не сразу приравнял какому-либо значению. Есть такая шутка юмора как объявление и инициализация переменной. Объявление переменной - это когда переменной выделяется какой-то адрес и нужное количество байт в памяти, а инициализация - когда в эту память что-то ещё и записывается. На ум приходит такая аналогия: объявили переменную - взяли бутылку с чем-то непонятным, инициализировали - вылили ненужное и налили хорошего виски:)
Объявляются переменные так: тип имя;
А инициализируются оператором присвоения "=": тип имя = выражение;
Выражение в данном случае может быть как просто числом, так и результатом какого-либо математического выражения с другими переменными.
Здесь надо быть осторожным, чтобы не допустить такого неприятного момента: по ошибке можно использовать неинициализированную переменную. Это чревато тем, что после объявления по этому адресу памяти как правило мусор и никто не знает, какое значение примет эта переменная. И если использовать это мусорное значение - может произойти большая печалька, бо программа может повести себя непредсказуемо.
Во-вторых, вы наверняка посмотрели на надписи типа Integer.toBinaryString(varInt) и выпали в осадок:) Тут всё просто: каждому примитивному типу данных сделан в соответствие класс, объекты которого по сути своей могут заменять переменные данного примитивного типа. В данном случае Integer - класс, объекты которого могут заменить переменные типа int. А toBinaryString() - всего лишь метод, который переводит указанное число в двоичный или бинарный вид и отдает его в виде строки, то есть объекта класса String. Кстати, метод этот статический, то есть его можно вызывать даже когда нет ни одного экземпляра класса, который мог бы его вызвать. Это можно пока запомнить, но не сильно забивать себе голову.
Так же можно посмотреть на фразочки типа Long.MAX_VALUE. Тут тоже ничего страшного. Это выражение выдает нам статическую константу MAX_VALUE, в которой содержится максимальное значение для типа Long. О статических и не статических методах, константах и прочем мы ещё поговорим, когда будем обсуждать объектно-ориентированное программирование как концепцию:) Не пугайтесь этих слов, они на самом деле добрые и аняняшные:)

Если кто-то что-то недопонял или нашёл в моих выкладках ошибку - напишите в комментариях, я попробую объяснить подробнее и понятнее или же в случае ошибки - исправлю оную:)

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

[400x130]
вверх^ к полной версии понравилось! в evernote
Комментарии (4):
Florentine 03-11-2013-18:08 удалить
Можно я тут глупые вопросы задавать буду, товарищ учитель? Мне вот что непонятно. "Если старший бит 0 - значит число положительное, если 1 - значит отрицательное."Выше ведь говорилось, что 0 - нет сигнала, 1 - есть сигнал. Почему тут всё как-то "наизнанку" получилось? Или я путаю тёплое с мягким, а эти обозначения между собой прямой связи не имеют?
Кстати, а обязательно всё время мучиться и все числа в бинар переводить? Помнится, в школьном учебнике по информатике (да, я понимаю, что это ужасно неавторитетный источник!) имелась волшебная табличка для цифр, чисел и букв. У нас даже было ужасное по исполнению задание - написать в тетради ФИО бинаром
И ещё, использовали мы вдруг неинициализированную переменную. И как примерно неадекватно начнёт вести себя программа? Превратится в 9 бит, то есть толпу хулиганов? Или что-то ещё? =)
Занятная гифка!
Yuri_Prime 03-11-2013-20:23 удалить
Ответ на комментарий Florentine # Первое: и то, и другое верно. На уровне электрических схем и транзисторов 0 - нет сигнала, 1 - есть сигнал. 8 линий - шина, передающая 1 байт:) А вот для процессора, который и выполняет все арифметические операции, придумано, что старший бит - знаковый, потому что для него все числа - те же нолики и единички или есть сигнал - нет сигнала, а отрицательное или положительное число - ему пофиг. В процессоре заложен именно такой механизм работы с цифирями.
Второе. Да, можно использовать таблицы, но это только чисел от 0 до 15. Связано это с тем, что такой перевод удобен для 16ричной системы счисления, там каждая цифра - это 4 цифры бинарного кода, там это удобно. А в школьном учебнике информатики, о котором ты говоришь, подозреваю, была таблица ASCII-символов или таблица ASCII-кодировки, где код для каждого символа был переведен в бинарный вид:)
Третье. Зависит от логики программы. Может поломать логику и увести программу в дебри, может вызвать какую-то исключительную ситуацию и заставить программу рухнуть.
Вот возьмем переменную и будем использовать её в качестве адреса памяти, то есть число, которое в ней - адрес памяти, по которому у нас лежит что-то нужное, которое берется и выпиваетсяобрабатывается. А теперь допустим, что мы её неинициализировали эту переменную, там лежит "мусор", случайное значение адреса. А мы берем это случайное значение адреса и по этому адресу в памяти читаем какое-то значение. В итоге мы имеем не то значение, которое будет правильным, а будем иметь "мусор". В некоторых случаях, может быть обращение по недоступному адресу или, например, обращение к несуществующему элементу массива, что так же может повлечь исключительную ситуацию.
Надеюсь, более-менее понятно объяснил:)
Florentine 06-11-2013-22:54 удалить
Ответ на комментарий Yuri_Prime # Первое стало понятно)) Я всё-таки путала тёплое с мягким))
*ф топку учебник* Покажешь мне как-нибудь как эти числа переводятся и посмотришь как я перевожу))) Чует моё сердце, информатичка икать будет))
Хм, ну с логикой программы ты мне погоды объяснять А если такая переменная вводится в давно работающую программу? Можно же поиздеваться над копией. В общем, я тут хотела знать это страшно только для этой конкретной программы или для системы в целом? Какие страшные мысли приходят мне в голову
Ага, то есть как если бы мы гостю предлагали пить из давно пустого бокала?
Я ещё про 9 бит думала, думала, но так и не поняла как спросить что делать, если они появились, а мы вот с ними прям не знаем куда бежать... Пока вроде всё, что было непонятно
Yuri_Prime 06-11-2013-23:33 удалить
Ответ на комментарий Florentine # Florentine,Показать-посмотреть - это можно:)
В давно работающей программе - это если она только каким-то редковызываемым методом объявится. Исход тот же, возможно, фатальный.
Для системы это не страшно, а вот программке будет очень грустно:)
Я тут аналогию придумал:) Вот представь, что мешаешь ты коктейль, скажем, "Хиросиму". И вот ты, значит, наливаешь самбуку из бутылки с надписью "Самбука", абсент из бутылки с надписью "Абсент" и водку из бутылки с надписью "Водка". Но! В бутылке с надписью "Водка" может быть абсолютно случайная жидкость, от воды до "царской водки" и цианистого калия. И вот ты смешала коктейль и даёшь выпить человеку:) Если там вода или нечто безвредное, то есть случайное "мусорное" значение оказалось в допустимых пределах, то всё ништяк, а если там тот же цианид или "царская водка", то есть наше "мусорное" значение вообще идёт в разрез с логикой? И человеку, и программе будет плохо:) Вот такая шляпа.
9 бит? Очень просто. Возьми число 255 и переведи в двоичную систему. А потом вспомни, что 8 бит задают только 256 комбинаций. А теперь возьми 256 и переведи:) Можешь калькулятором:) Это уже 257-е значение. А значит, что? Нужен ещё один бит. Вот в чём шутка.
У нас же получается, что мы заменили вычитание на сложение с дополнительным кодом числа. А вот теперь прибавь 99+3. 102 получается. Произошёл перенос в старший разряд(9+3 это 2 пишем, 1 в уме; 9+0 и ещё 1 в уме - это 10, 0 пишем, 1 в уме; и вот эта самая единичка и появляется).
В нашем случае такая же шляпа, только заместо 10 у нас 2. Если хочет появиться 2, значит 0 или 1 пишем, а 1 в уме:)
А зачем бежать? Отсекай его нафиг:) И дело с концом:) Компьютер так и делает. Для однобайтной переменной 9-ый бит, старший, будет нещадно отсечен и забыт, для двухбайтной - 17-ый бит будет срезан:) И так далее:)


Комментарии (4): вверх^

Вы сейчас не можете прокомментировать это сообщение.

Дневник Программирование на Java: от ламера - чайнику. Пост №1. Странные типы. | Yuri_Prime - Записки Yuri_Prime | Лента друзей Yuri_Prime / Полная версия Добавить в друзья Страницы: раньше»