Текст книги "Основы программирования с Java"
Автор книги: Тимур Машнин
сообщить о нарушении
Текущая страница: 4 (всего у книги 6 страниц)
Типы данных
Тип данных является очень важным понятием в языке программирования высокого уровня.
Java является строго типизированным языком программирования.
Это означает, что должны быть определены все типы имен, упомянутых в программе Java, прежде чем они могут быть использованы.
Мы только что видели, что способом объявить переменную является указание типа.
Мы использовали тип int для целого числа в предыдущих примерах.
Другие типы данных также поддерживаются Java.
Каждый тип данных имеет свои свойства и требования к пространству памяти.
Это позволяет компьютеру сделать выделение памяти, когда программа выполняется, так как различные типы данных имеют разные требования к размеру памяти.
Свойства типа данных включают в себя набор значений, которые он может взять на себя и набор операторов, которые могут применяться к этим значениям.
Например, значение целого числа может отрицательным, положительным или нулевым, и операции, которые могут быть выполнены для целого числа включают + сложение, – вычитание, * умножение и / деление.
Набор значений известен как домен для этого типа.
Java поддерживает восемь простых типов данных в рамках 4-х основных категорий, а именно целые числа, числа с плавающей точкой, символы и логические значения.
byte, short, int и long являются различными целочисленными типами, которые занимают разное количество памяти.
float и double представляют числа с плавающей точкой, то есть, числа с дробной частью.
Значения, сохраненные в float и double типах, являются только приблизительными.
Существует также тип char для символов, представленных 16-битным стандартом Unicode.
boolean это тип данных, которые могут взять на себя только два возможных значения, истина и ложь.
Сначала мы сконцентрируемся на целых числах и числах с плавающей точкой и вернемся к char и логическим типам позже.
Среди 4 целых типов:
byte это 8-разрядные целые числа, со значениями в диапазоне от -128 до 127,
short составляет 16 бит,
int 32 бита и
long 64 бита, соответствующие диапазоны значений приведены в этой таблице.
Основное преимущество целочисленного представления в том, что оно представляет собой точное значение без приближения, но оно не может представлять значения с плавающей запятой и его область значений ограничена.
Хотя long может составлять до 2 в 63-й степени, его диапазон по-прежнему намного меньше, чем float или double.
В таблице здесь показан диапазон значений, которые могут быть представлены float, который использует 32 бита и double, использующий 64 бита.
Как вы можете видеть, это астрономические цифры.
Выражения
При написании программ, выражения часто используются как строительные блоки для определения действий, которые программа предполагает выполнить.
Существует два типа выражений – арифметические выражения и логические выражения.
Примером арифметического выражения может служить вычисление окончательной оценки как взвешенной суммы оценок, как мы видели в программе СourseGrade.
Примером логического выражения может служить действие проверки студента на получение оценки.
Сначала остановимся на арифметических выражениях, а затем рассмотрим логические выражения, когда будем говорить о разветвленных выражениях.
Арифметическое выражение это последовательность операндов, включающих переменные и константы или литералы, соединенные арифметическими операторами, такими как +, -, * & / .
Мы уже обсуждали переменные и константы.
И первым примером здесь будет использование литералов.
Арифметическое выражение 2+3 соединяет два литерала 2 и 3, используя оператор сложения.
Второй пример, это арифметическое выражение для преобразования температуры из Цельсия в Форенгейт:
Цельсий*9/5+32
Цельсий, – это переменная, 9, 5 и 32 – литералы, и литералы соединены тремя операторами *, / и +.
Позже, вы обнаружите, что, если это будет использовано как Java выражение, возникнет ошибка вычисления.
Вы узнаете, что я имею в виду, когда мы будем говорить о делении целых чисел.
Так что вы видите, что литерал, – это константное значение, которое появляется непосредственно в Java программе.
И существует два типа численных литералов в Java – это целочисленные литералы и литералы с плавающей запятой.
Здесь показаны некоторые примеры литералов.
Заметьте, что для переменной aFloat значение установлено 10.0f с суффиксом f после 10.0 – это потому, что литерал с плавающей запятой 10.0 представлен как double – с размером 64 bit, в то время как float имеет размер 32 bit.
Присвоение double в float ведет потенциально к потере информации, и Java компилятор будет жаловаться, если суффикс f отсутствует.
Я поговорю о преобразовании типов позже.
Также присвоение 1000 в byte, который может содержать только целое значение до 128, приведет к ошибке компиляции, потому что литерал слишком большой, чтобы поместиться в переменную.
Здесь показаны общераспространенные арифметические операторы.
Операторы +, – и * довольно простые, однако оператор деления целых чисел слегка хитрый.
Например, если 2 разделить на 3, результат будет 0.
Оператор модуля, который представлен знаком процентов, дает остаток от деления первого операнда на второй операнд.
Например, 2 модуль 3 возвращает 2 как результат.
Оператор модуля в Java работает также для чисел с плавающей запятой.
Рассмотрим другой пример использования оператора модуля.
Когда переменная со значением 104 делится на другую переменную b со значением 3, результат будет 34 с остатком 2.
Числовые значения хранятся как целые числа или числа с плавающей запятой.
Для целочисленного деления результат, – это целая часть деления, десятичная часть результата игнорируется.
Другими словами, целочисленное деление всегда возвращает целое число путем усечения без округления.
При этом может быть потеряна информация, когда десятичная часть отсекается.
Например, при делении 2 на 3 результат должен быть 0.66, но мы получаем 0 из-за отсечения.
Или 3 делим на 2 и получаем 1 вместо 1.5.
Если вы делите double на double, результат будет double, как и ожидалось.
Теперь вопрос, как Java оперирует со смешанными делениями, включающими целые числа и числа с плавающей запятой?
В общем, деление double дает double.
Когда целое делится на double или double делится на целое, результатом будет double.
Это позволяет программе максимально сохранить информацию.
Например, если 2 делится на 3.0, результатом будет 0.6666 вместо 0.
Также, деление 3.0 на 2 даст результат 1.5.
Деление двух double 10.0 и 2.0 даст результат double 5.0.
Когда выражение вычисляется, мы должны определить порядок для выполнения операций, если в выражении больше одного оператора.
Когда мы изучали алгебру, мы узнали, что операции * умножения и / деления выполняются перед операциями + сложения и – вычитания, и такое же правило действует и в Java.
Например, в выражении m*x + b, m умножается на x перед прибавлением b к результату умножения.
Приоритет операторов задает порядок, в котором различные операторы выражения вычисляются.
Здесь показан стандартный порядок, которому следует Java:
( )
* / %
– +
Выражение, заключенное в круглые скобки, вычисляется первым.
Для вложенных скобок внутреннее выражение вычисляется первым.
Операторы * умножения, / деления и % остатка вычисляются вторыми, и, если их несколько, вычисление идет слева направо.
Операторы сложения и вычитания вычисляются после остальных операторов, и, если их несколько, вычисление идет слева направо.
Другая важная вещь в вычислении выражений, это концепция ассоциативности.
Ассоциативность используется для определения порядка, в котором операторы с одинаковым приоритетом вычисляются в выражении.
Правило ассоциации в этом примере, – это вычисление слева направо, и называется левой ассоциативностью.
При этом круглые скобки могут быть вставлены для усиления порядка вычисления.
Вы можете подумать, что все операции должны следовать левой ассоциации.
Однако это не всегда случается в Java, и мы уже видели оператор, который следует правой ассоциации, – это оператор присваивания =.
Вопросы
Задача
Что является результатом каждого из следующих выражений?
Expression X: 3 % 4 – 10 * 5
Expression Y: 5 + 11 / 2 * 2.0
Expression Z: 100 / 0
Варианты:
1.
X: -47
Y: 10.0
Z: 0
2.
X: 1
Y: 10.0
Z: 0
3.
X: -47
Y: 15.0
Z: ERROR
4.
X: -47
Y: 10.0
Z: ERROR
Ответ: 3.
Присваивание
Мы видели много выражений со знаком равенства в предыдущих примерах.
Все они использовали оператор присваивания.
Синтаксис оператора присваивания представляет собой размещение переменной на левой стороне знака равенства, выражения на его правой стороне и точки с запятой в конце.
Смысл или семантика оператора присваивания – это присвоить значение, вычисленное выражением на правой стороне, переменной на левой стороне, и исходное значение, хранимое в переменной, будет заменено.
Это обозначение может быть немного запутанным, поскольку в большинстве утверждений присваивания, левая сторона может быть не равна правой стороне в математическом смысле.
Например, вы можете иметь что-то вроде, а = а + 1;
Это не корректно в качестве математического выражения, но это верное утверждение присваивания.
Переменная здесь имеет начальное значение 1, и ее значение будет изменено на 2 после присвоения.
Для определенного типа существует набор действительных операторов, которые могут быть применены к этому типу.
Если кто-то хочет применить некоторый оператор, который действителен только для другого типа, необходимо преобразование типов, чтобы преобразовать тип данных из одного типа в другой.
Преобразование типа может быть сделано двумя способами: это явные и неявные преобразования.
Неявное преобразование изменяет значение одного типа в другой без специальной инструкции от программиста.
Например, целый тип int разрешено присваивать типу float, хотя при этом может быть потеряна точность.
Основное правило заключается в том, что неявное преобразование разрешено, если диапазон значений первого типа является подмножеством второго.
Его часто называют расширяющим преобразованием.
Обратное это сужающее преобразование, которое, как правило, не допускается без явного преобразования.
Явное преобразование выполняется приведением типов.
Приведение типов делается путем размещения имени целевого типа в скобках перед типом данных, которые будут преобразованы.
Например, если dValue является double переменной, компилятор не допустит присвоения int или float без преобразования явного типа, указав int или float в скобках.
Далее, мы вернемся к примеру CourseGrade и используем то, что мы только что узнали о переменных, объявлениях, типах данных и арифметических выражениях, чтобы получить более глубокое понимание того, как эта программа выполняется.
Выделение памяти
Давайте теперь используем пример СourseGrade для того, чтобы проиллюстрировать эффект объявления и выполнения программы.
Как уже говорилось, объявление идентификатора, в дополнение к определению его типа данных, также позволяет компьютеру делать выделение памяти, когда программа выполняется.
В этой программе, объявляются 7 идентификаторов, а именно examweight, labWeight, hwWeight, examScore, labScore, hwScore и finalGrade.
Я буду использовать схему, которая показывает набор из ячеек, чтобы проиллюстрировать выделение памяти, когда переменная объявлена.
Это только для иллюстрации и объем памяти, конечно отличается от ячейки.
Если объявление int examWeight сделано, пространство памяти выделяется в соответствии с размером типа данных, в данном случае int.
Присвоение значения 70 для examWeight приведет к инициализации значения, хранимого в памяти, до 70. В компьютере, значение 70 на самом деле представлено как строка битов 0 и 1.
Объявление и инициализация для labWeight и hwWeight проходит через аналогичный процесс.
Объявление для examScore выделяет достаточно памяти для хранения числа с плавающей точкой типа double. Большинство реализаций для double требует 8 байт, поэтому размер будет отличаться от int, который требует 4 байта.
Подобное выделение памяти будет сделано для labScore, hwScore и finalGrade.
Я буду использовать здесь другой цвет, чтобы проиллюстрировать, что int и double имеют разные требования к памяти.
Поскольку значения не были присвоены для этих переменных, их значения не известны.
Фактически, сначала им должны быть присвоены значения до того, как может быть сделана на них ссылка.
После того, как объявления выполнены, программа предложит пользователю ввести значения для examScore, labScore и hwScore.
Предположим, что пользователь ввел 90,0 для examscore.
Обратите внимание, что даже если пользователь ввел 90, без десятичной точки, значение будет преобразовано в число с плавающей точкой.
Опять же, я использую здесь другой цвет, чтобы отличить double тип от int типа, который находится в синих ячейках.
Аналогично, значение 85,0 вводится для labScore, и 80,5 вводится для hwScore.
Чтобы продолжить выполнение кода, будет выполнен другой оператор присваивания, который изменяет значение examScore.
Вычисление выражения справа от оператора присваивания сначала вычисляет выражение внутри круглых скобок.
Значение examWeight извлекается из памяти, а затем делится на 100,0.
Следует отметить, что examWeight представляет собой целое число, и если оно делилось бы на другое целое число 100, то результатом был бы ноль.
Но так как мы используем 100.0, которое является числом с плавающей точкой, результатом деления будет число с плавающей точкой 0.7.
Значение examScore затем будет извлечено из памяти и умножится на 0,7.
Полученное значение 63,0 затем будет присвоено переменной на левой стороне оператора присваивания.
Результат выражения заменит исходное значение в памяти для examscore новым значением 63.0.
Аналогично, значения для labScore и hwScore обновятся и, наконец, значение finalGrade будет рассчитано путем добавления обновленных значений для examScore, labScore и hwScore.
Полученное значение 88,05 затем будет присвоено участку памяти для finalGrade.
Во время стадии анализа задачи при проектировании исходной задачи, было определено, что веса экзаменов, лабораторных и домашних заданий должны быть предварительно определены, и их значения должны быть одинаковыми для всех студентов в том же курсе.
Если мы хотим предотвратить случайное изменение весов, мы объявим эти идентификаторы как константы, поставив final в качестве ключевого слова в начале объявления.
В некотором смысле, вы можете думать об этом как запереть ячейку памяти и запретить любую попытку изменить ее значение в другой части программы.
Можно сказать, что, если студент сдал плохо экзамены, но сделал хорошо лабораторные работы, попытка уменьшить вес для экзамена и увеличить вес для лабораторных будет блокирована.
Демонстрация примера
Давайте теперь посмотрим на программу в среде IntelliJ IDEA. Мы откроем проект под названием CourseGrade, который является программой, которую мы только что обсуждали.
Откроем файл CourseGrade в редакторе исходного кода.
И вы можете видеть, что эта программа та, которую мы только что обсуждали.
И эта программа еще не скомпилирована.
Попробуем скомпилировать программу с помощью меню Build Project.
Вы можете видеть, что программа успешно компилируется без ошибок синтаксиса.
Давайте попробуем запустить программу с помощью кнопки Run.
Теперь вы можете видеть, что приглашение ввести ваши оценки экзаменов отображается в окне терминала.
Скажем, что это очень хороший ученик и получил отличные оценки на экзаменах, лабораторных, а также домашних заданиях.
Обратите внимание, что мы вводим с десятичной запятой, 100,0, для каждой из оценок.
И в результате, по какой-то причине, итоговая оценка вернулась программой как 0.0.
Я уверен, что студент будет очень недоволен. Давайте попробуем выяснить, что вызывает эту проблему.
В IntelliJ IDEA, есть очень полезный инструмент отладки, который позволит нам проследить выполнение программы.
Инструмент очень полезен для отслеживания ошибок.
Давайте попробуем выяснить, есть ли какие-либо проблемы после того, как оценки были введены в программу, установив точку останова после объявлений IO.
Для этого просто нажмем на соответствующее место вдоль правого столбца в окне редактора.
Вы можете нажимать в этих местах, чтобы установить или очистить точки останова.
После того, как точка останова устанавливается, IntelliJ IDEA выполняет программу до инструкции, где точка останова была установлена.
Давайте начнем выполнение снова, нажав кнопку Debug.
И введем ту же оценку, 100,0, для экзаменационной оценки, 100,0 для лабораторной.
Давайте попробуем для домашних работ введем просто 100 без 0,0, чтобы сделать преобразование неявного типа, так как переменная hwScore имеет double тип и диапазон значений для int представляет собой подмножество для double.
Когда вы нажмете return, вы увидете, что окно редактора появляется с выражением, выделенным в точке останова.
Что еще более важно, при этом выскочило окно отладчика.
Это позволит вам просматривать текущее состояние программы, когда выполнение останавливается в точке останова.
Вы можете увидеть окно локальных переменных и изучить их значения.
Вы можете видеть, что значения весов определены при инициализации, и examScore, labScore и hwScore, все получили значение 100,0.
Вы можете продолжить выполнение программы, нажав на кнопку 'step', которая будет выполнять следующую инструкцию в программе.
Есть и другие кнопки здесь и лучший способ узнать, что они делают, это попробовать их самостоятельно.
Давайте просто использовать кнопку step на данный момент.
Вы можете видеть, что следующее утверждение подсвечено в окне редактора.
Это означает, что программа только что закончила выполнение инструкции для вычисления examScore.
Если изучить значения переменных снова, вы найдете, что все остальные значения остаются такими же, за исключением того, что для examScore теперь дается значение 0.0.
Поэтому здесь должна быть некоторая проблема с выполнением этого выражения.
Давайте продолжим выполнение чтобы увидеть, если у нас аналогичная проблема с вычислением и других показателей.
Опять же, мы получаем 0 для labScore а затем 0 для hwScore, так что должны быть некоторые общие проблемы с расчетом и обновлением оценок, использующих эти выражения присваивания.
Можете ли вы найти эту проблему? Намек, что это связано с делением целых, которое я обсуждал ранее.
Обратите внимание, что вес экзаменов со значением 70 имеет тип int. Когда он делится на 100, что также является целым числом, результат деления 0,7 будет урезан и возвращает 0 в результате.
Далее 0 умножается на examScore, результирующее значение равно 0, который затем присваивается examScore.
Та же проблема возникает в labScore и hwScore.
И эту проблему легко исправить. Для этого нужно заменить 100 на 100,0, и, если вы помните правило преобразования типов, которое мы только что обсудили, когда целое делится на число с плавающей точкой, результат будет преобразован в число с плавающей точкой.
Давайте теперь прекратим предыдущее выполнение программы и скомпилируем программу снова.
Теперь также нет ошибки синтаксиса.
В предыдущем выполнении программы было показано, что, хотя программа не имеет синтаксических ошибок, это не означает, что она будет работать так как надо, или семантически правильно.
Давайте попробуем выполнить программу, нажав кнопку Run.
Если ввести те же баллы 100, 100 и 100, вычисляется окончательная оценка 100 и все сейчас должны быть счастливы.
Вопросы
Задача
Каким вы думаете будет значение переменной result после выполнения следующего сегмента Java кода?
int i = 1234567890;
float f = i;
int result = i – (int)f;
1. 0
2. nonzero
3. an error
Ответ: 2.
Оба int и float 32-битные типы.
Все 32-битные int используются для представления целой части числового значения.
Тем не менее, для float, некоторые биты используются для представления целой части и некоторые для десятичной части.
При присвоении большого целого значения переменной с плавающей точкой, уменьшенное количество битов для его целой части, возможно, не в состоянии точно представить большое значение.
Таким образом, вычисление разности между int и float переменных одного и того же большого численного значения, не всегда может дать результат 0.
Вы можете проверить код, указанный выше, в программе Java.
Обсуждение отладки
Достаточно часто случается, что мы не можем написать без ошибок программу с первой попытки.
Нам часто нужно отлаживать нашу программу несколько раз, чтобы сделать безупречную версию.
Ниже пример программы содержит ряд ошибок. Попробуйте определить все эти ошибки.
Ответ: