Представление целочисленных и вещественных значений в памяти компьютера в большинстве случаев реализуется аппаратным способом с учетом возможностей конкретного процессора. Также аппаратно реализуется примитивный набор операций над этими значениями. Применение операций, реализуемых аппаратно, значительно более эффективно, чем использование программно реализуемых операций. Поэтому компиляторы по возможности формируют код, в котором применяется аппаратная реализация примитивных операций (таких, как сложение, вычитание, умножение и деление).
Целочисленное значение типа integer, записанное как "signed 32-bit", может иметь в памяти компьютера следующее представление:
Рис. 6.1. Битовая структура типа integer
Целочисленное значение типа shortint, записанное как " signed 8-bit ", может иметь в памяти компьютера следующее представление:
Рис. 6.2. Битовая структура типа shortint
Максимально допустимое значение, которое можно записать в 7 бит - это восьмеричное число 177, которое в десятичном исчислении равно 127 (1778=1*82+7*81+7*80=12710).
Таким образом, диапазон допустимых значений для каждого целочисленного типа данных определяется как стандартом языка, так и возможностями аппаратуры.
В некоторых языках программирования представление целочисленного значения включает еще и хранение дескриптора этого значения. При этом дескриптор может храниться как в одной ячейке со значением, так и в различных ячейках. В первом случае наличие дескриптора значительно уменьшает диапазон допустимых значений, а также, как правило, замедляет процесс выполнения арифметических операций (операции над частью ячейки памяти не поддерживаются аппаратно).
При хранении дескриптора и значения в разных ячейках (этот способ представления используется для языка LISP) одновременно с дескриптором хранится и указатель физического расположения значения.
Вещественные типы аппаратно могут иметь два представления: вещественные числа с фиксированной точкой и вещественные числа с плавающей точкой. Как правило, по умолчанию компиляторы преобразуют вещественные значения в экспоненциальный формат (формат с плавающей точкой), если синтаксис языка явно не указывает применение формата с фиксированной точкой.
Вещественное значение с плавающей точкой может иметь в памяти компьютера следующее представление:
Рис. 6.3. Битовая структура типа float
Для представления вещественных чисел с плавающей точкой и вещественных чисел двойной точности с плавающей точкой стандартом IEEE 754 определены 32- и 64-битовые представления соответственно.
В разряды мантиссы записываются значащие цифры, а в разряды экспоненты заносится показатель степени. Положительные значения содержат в знаковом бите числа - 0, а отрицательные значения - 1.
При обозначении чисел с плавающей точкой приняты следующие соглашения: знаковый разряд обозначается буквой s, экспонента - e, а мантисса - m.
В языке Java значения с плавающей точкой могут объявляться следующими базовыми типами:
float диапазон значений от +3.40282347E+28 до +1.40239846E-45 (32 бита)
double диапазон значений от +1.79769313486231579E+308 до +4.9406545841246544E-324 (64 бита).
Полной ненулевой формой значения типа float является форма s·m·2e, где s это +1 или -1 (знаковый бит числа), m является положительным значением, меньшим, чем 224 (мантисса), и e является целым в пределах от -149 до 104 включительно (экспонента).
Полной ненулевой формой значения типа double является форма s·m·2e, где s это +1 или -1, m является положительным значением, меньшим, чем 253, и e является целым в пределах от -1045 до 1000 включительно.
Положительным нулевым значением и отрицательным нулевым значением являются 0.0 и -0.0 (0.0 == -0.0 дает в результате true), но некоторые операторы различают эти "эквивалентные" значения, например, 1.0/0.0 равно положительной бесконечности, а 1.01/-0.0 - отрицательной бесконечности.
В языке Java приняты следующие правила приведения вещественных типов:
любые значения любого типа с плавающей точкой могут быть приведены к любому другому значению числового типа;
любые значения любого типа с плавающей точкой могут быть приведены к значению типа char и наоборот;
приведение невозможно между значениями типа boolean и значениями с плавающей точкой.
Если оба операнда имеют тип float, или один имеет тип float, а другой - целый тип, то результат будет иметь тип float.
Если же хотя бы один из операндов имеет тип double (устанавливаемый по умолчанию), то другой операнд также приводится к типу double и результат будет иметь тип double.
Операции со значениями с плавающей точкой не выдают исключений.
При вычислениях значений с плавающей точкой следует учитывать правила округления. Так например, для значений с плавающей точкой выражение1.0*10 не равно значению 10. Это иллюстрирует следующий пример на языке Java:
public class MyFloatSumma { public static void main(String[] args) { float f1 = (float) 1.0; // Число с плавающей точкой float fSum = 0; // Переменная для записи результата final static float myDeltaFloat = (float) 0.00001; // Дельта для "усечения" // мусора мантиссы // Способ вычисления - простое // суммирование и сравнение for (byte i=0; i<10; i++) fSum = fSum + (float) 0.1; // Цикл от 0 до 10 if (f1 == fSum) // Сравнение (float) 1.0 и // полученной суммы System.out.println("Числа равны"); else System.out.println("Числа различны");// Правильный способ вычисления - // с "усечением" мусора мантиссы fSum = 0; for (byte i=0; i<10; i++) fSum = fSum + (float) 0.1; if (Math.abs(fSum - f2) < myDeltaFloat) System.out. println("Числа одинаковы"); else System.out. println("Числа различны"); }}
Лекция #7: Производные типы данных языка С++. Массивы и указатели Описывается синтаксис и семантика объявления массивов, рассматривается их размещение в памяти, определяется доступ к элементам массива. Освещается применение символьных массивов и строк. Рассматривается работа с указателями, определяются указатели на переменные, константные указатели, указатели на указатели.
Производные типы
Производные типы можно условно подразделить на две группы:
Непосредственно производные типы. Эти типы являются производными от некоторых существующих типов, реализуя типы указателей, ссылки, функции преобразования типов. В группу непосредственно производных типов входят:
массивы
указатели
ссылки
перечисления
указатели на члены класса.
Составные производные типы. В группу составных производных типов входят:
классы
структуры
объединения
Массивы
Объявление массивов
Массив переменных или объектов состоит из определенного числа однотипных данных, называемых элементами массива. Все элементы массива индексируются последовательно, начиная с нуля.
Размещение элементов массива в памяти выполняется последовательно.
Количество элементов в массиве определяет размер массива и является константным выражением. Для создания динамических массивов стандартная библиотека C++ предусматривает тип vector.
Имя массива определяет адрес первого элемента массива.
Имя массива в отличие от имени вектора нельзя указывать в левой части оператора присваивания.
Объявление массива может иметь следующее формальное описание:
// Объявление одномерного массиватип имя_массива[размерность_N];// Объявление одномерного массива с // одновременной инициализациейтип имя_массива[размерность_N]= {значение0, значение1, ..., значение_N-1};// Объявление безразмерного массива с // одновременной инициализацией:// размерность определяется количеством // значений, указанных в списке инициализациитип имя_массива[]={значение0, значение1, ..., значение_N-1};// Объявление многомерного массиватип имя_массива[размерность_1N]... [размерность_MN];// Объявление многомерного массива с // одновременной инициализациейтип имя_массива [размерность_N]... [размерность_M] = { {значение0, значение1, ..., значение_M-1}, ... {значение0N, значение1N, ..., значение_NM-1}}; Размерность массива может:
автоматически определяться компилятором по списку значений инициализации массива (если размерность не указана явно).
Например:
int arrayOfInteger[17]; // Массив из 17 переменных типа intchar arrayOfChar_3[3]={'L','M','N'}; // Объявление и инициализация // символьного массиваchar arrayOfChar_0[]={"Array of char. \n"};int arrayOfInteger_6[2][3]= { {1,2,3}, {11,12,13}}; // Объявление и инициализация // двумерного массива Если ни размерность массива, ни список значений инициализации не указаны, то будет создан массив нулевой длины.
Объявление многомерных массивов выполняется по следующим правилам:
список значений инициализации можно указывать как в единых фигурных скобках, так и в общих фигурных скобках, включающих через запятую списки по каждому левому индексу в отдельных фигурных скобках;
при использовании общих фигурных скобок сначала следует указывать значения для неизменяемого самого левого индекса;
можно не указывать только самую левую размерность массива, получая ее значение из списка значений инициализации.
Инициализацию массива можно выполнить одним из следующих способов:
указать во время объявления массива в фигурных скобках значения инициализации (начиная с нулевого элемента и первого столбца);
присвоить значение элементам массива во время выполнения программы;
объявить массив как глобальный или статический, инициализируемый по умолчанию (для числовых значений выполняется инициализация нулями, а для указателей - значением null). Глобальный массив объявляется вне всех функций, а статический массив объявляется с модификатором static.
Если количество значений инициализации больше, чем объявленная размерность массива, то компилятор Visual C++ инициирует ошибку.
Если количество значений инициализации меньше, чем объявленная размерность массива, то оставшиеся значения автоматически инициализируются нулями.
Размещение массива в памяти
При создании массива память под все его элементы выделяется последовательно для каждого элемента в зависимости от типа массива. Для многомерных массивов в первую очередь изменяются значения самого правого индекса.
Например, для массива char aCh[2][4] будет выделено восемь байтов памяти, в которых в следующем порядке будут размещены элементы массива:
элемент
aCh[0][0]
aCh[0][1]
aCh[0][2]
aCh[0][3]
aCh[1][0]
aCh[1][1]
aCh[1][2]
aCh[1][3]
N байта
Двухмерные массивы можно рассматривать как матрицу, в которой первый индекс опре-деляет строку, а второй индекс - столбец. Порядок расположения элементов матрицы в памяти - по строкам.
При размещении трехмерного массива char aCh[3][2][5] память под элементы этого массива будет выделяться последовательно в соответствии со следующими значениями индексов:
Рис. 7.1.
Общий объем выделяемой под массив памяти определяется как произведение всех размерностей массива (общее число элементов), умноженное на длину типа данных массива.