Введение в языки программирования C и C++

Системные данные числового типа

Показывать лекцию целиком

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

3.1. Типы числовых данных и их представление в памяти ЭВМ

Числовые данные условно можно разбить на три категории - положительные целочисленные данные (их значения в компьютере представлены целыми двоичными числами без знака), произвольные целочисленные данные (один из двоичных разрядов их представления играет роль знакового разряда) и числовые данные вещественного типа.

3.1.1. Внутреннее представление целочисленных данных

Для хранения целочисленных данных со знаком в IBM PC используется дополнительный двоичный код. Эта особенность распространяется только на отрицательные числа. Для получения дополнительного кода отрицательного числа нужно перевернуть все двоичные разряды соответствующего положительного числа и прибавить единицу в старший разряд. Например:

Дополнительный код позволяет примерно на 25% ускорить выполнение таких операций как сложение и вычитание.

3.1.2. Однобайтовые целочисленные данные

Самые короткие целочисленные данные со знаком представлены в памяти IBM-совместимых ПК одним байтом, в котором может разместиться любое число из диапазона от -128 до 127, записанное в дополнительном коде. В языках C, C++ для описания переменных такого типа используется спецификатор char. В одном же байте может быть расположено и самое короткое целое число без знака. По терминологии C таким числам соответствует спецификатор unsigned char. Диапазон допустимых данных при этом смещается вправо и равен [0, 255].

3.1.3. Двухбайтовые целочисленные данные

Вторая категория целочисленных данных в системах программирования, эксплуатируемых на IBM PC, представлена двухбайтовыми целыми числами. В варианте со знаком они поддерживают диапазон от -32768 до +32767, в варианте без знака - от 0 до 65535.

Язык системы BC 3.1 использует для описания двухбайтовых целочисленных данных спецификаторы int (короткое целое со знаком) и unsigned int (короткое целое без знака). При их использовании арифметические операции над короткими операндами выполняются корректно при условии, что результат не выходит за пределы разрешенного диапазона. Однако если к максимальному целому числу прибавить 1, то вместо положительного числа +32768 в компьютере окажется отрицательное число -32768. И никакого предупреждения о нарушении допустимого интервала система не выдаст. Считается, что программист сам должен позаботиться о соответствующих проверках. В версии Borland C++ Builder для объявления двухбайтовых целочисленных данных используются спецификаторы short и unsigned short.

3.1.4. Четырехбайтовые целочисленные данные

Третья категория целых чисел в IBM PC представлена четырехбайтовыми данными. В варианте со знаком они перекрывают диапазон от -2147483648 до +2147483647, в варианте без знака - от 0 до 4294967295.

Для описания четырехбайтовых данных целого типа в языках C, C++ используются спецификаторы long (эквивалент long int ) и unsigned long. В среде визуального программирования C++ Builder спецификаторы int и long эквивалентны.

3.1.5. Восьмибайтовые целочисленные данные

Несмотря на то, что микропроцессоры IBM PC уже давно поддерживают восьмибайтовый целочисленный формат, обеспечивающий диапазон от -263 до 263-1, системы программирования довольно долго обходили этот формат или использовали его особым образом. Так, например, системы Turbo Pascal на базе этого формата предложили тип данных comp, который был причислен к разряду данных вещественного типа. В современных визуальных средах этот тип данных в своем естественном виде представляет числовые объекты типа int64. В недалеком будущем системы программирования воспользуются и сверхдлинными целочисленными данными типа int128.

3.2. Внутреннее представление данных вещественного типа

Для внутреннего представления данных вещественного типа характерно то, что в соответствующей области оперативной памяти хранятся две компоненты числа - мантисса m и порядок p. Само число x при этом равно произведению m*2p. Таким образом, мантисса определяет значащие цифры числа и его знак, а порядок - положение запятой, которая благодаря этому как бы "плавает" между значащими цифрами (отсюда и термин - формат с плавающей запятой ). Такой способ представления числовых данных позволяет при одинаковом количестве двоичных разрядов, отведенных для хранения чисел существенно расширить диапазон допустимых данных.

Попробуем оценить тот выигрыш в диапазоне допустимых чисел, который обеспечивает формат вещественных данных по сравнению с целочисленным форматом. Рассмотрим 32-битные двоичные числа. Целочисленные значения со знаком в этом формате позволяют работать с числами, принадлежащими по модулю интервалу [0, 2*109]. Предположим, что для числа с плавающей запятой в 32-битном слове отведены 1 двоичный разряд под знак числа, 8 двоичных разрядов под порядок и оставшиеся 23 разряда - под мантиссу. Тогда минимально представимое число равно произведению минимальной мантиссы ( 2-1 ) на минимальный порядок ( 2-128 ), т.е. 2-129, что примерно соответствует 10-39. Самое большое по модулю число представляет произведение (1-2-23)*2+127, что примерно соответствует 10+38. Таким образом, если целые числа перекрывали диапазон в 9 десятичных порядков, то формат с плавающей запятой при той же разрядности слова перекрывает диапазон в 77 порядков. Однако нельзя не заметить и проигрыш в количестве значащих цифр. Целочисленный 32- битный формат поддерживает 10 значащих цифр, тогда как 23-битные мантиссы вещественных данных позволяют работать с 7-8 десятичными знаками.

Наиболее часто применяемые типы вещественных чисел представлены короткими (4 байта) и длинными (8 байт) данными. Короткий вещественный формат по модулю обеспечивает представление чисел в диапазоне приблизительно от 10-38 до 10+38 с 7-8 значащими десятичными цифрами. Для 8-байтового формата диапазон существенно расширяется - от 10-308 до 10+308, а количество значащих цифр увеличивается до 15-16. Сопроцессор IBM PC предлагает расширенный десятибайтовый формат вещественных данных, перекрывающий диапазон (по модулю) от 10-4932 до 10+4932 и сохраняющий 19-20 значащих цифр.

В системах программирования Borland C++ для объявления данных вещественного типа используют спецификаторы float (короткое вещественное, 4 байта), double (вещественное с удвоенной точностью, 8 байт) и long double (длинное вещественное с удвоенной точностью, 10 байт).

В машинном представлении вещественных данных разного типа на IBM PC не выдержана какая-то общая идеология. Объясняется это, по всей вероятности, разными наслоениями на прежние аппаратные решения, которые принимались при разработке процессоров в разных отделениях фирмы Intel. Поэтому здесь имеют место такие нюансы, как сохранение в оперативной памяти или не сохранение старшего бита мантиссы, представление мантиссы в виде чисто дробного ( 0.5 <= m < 1 ) или смешанного ( 1 <= m < 2 ) числа и т.п. Прикладных программистов эти детали мало интересуют, однако при создании специальных системных компонент с точным представлением данных приходится считаться.

3.3. Внешнее представление числовых констант

В программах на языках C, C++ встречаются литеральные и именованные числовые константы целого или вещественного типа. Числовые константы, употребляемые в тексте программ в арифметических или логических выражениях, называют литеральными. Они представлены числовыми литерами - цифрами, знаками + или -, точками, отделяющими целую часть числа от дробной, показателями десятичного порядка. Например:

x=-25;         //целочисленная константа -25
  y=y+2.4;       //вещественная константа в форме с фиксированной запятой
  z=2.1e-6;      //вещественная константа в форме с плавающей запятой

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

#define Nmax 100
  const double eps=1e-6;
..................
  int a[Nmax]
..................
  for(i=0; i<Nmax; i++)
....................
    if(abs(z)<eps)...

Удобство именованных констант заключается в минимальных переделках программы, связанных с изменением размерности массивов и точности других управляющих констант. Достаточно изменить одну строку программы с объявлением той или иной константы и не менять другие операторы, использующие эту константу.

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

Наличие в естественной записи числа точки ( 3.1415 ) или указателя десятичного порядка ( 314.159265e-02, 314.159265E-02 ) означает, что соответствующее значение представлено в ЭВМ в виде вещественного числа с плавающей запятой.

Кроме естественного представления числовых констант в виде целого или вещественного числа языки программирования допускают различные добавки в начале (" префиксы ") или конце (" суффиксы ") числа, определяющие способы преобразования и хранения данных в памяти компьютера.

В алгоритмическом языке C активно используются как префиксы, так и суффиксы:

  • 0x5,0X5 - шестнадцатеричное целое число (префикс - 0x или 0X );
  • 05 - восьмеричное целое число (префикс - незначащий нуль в начале);
  • 5H,5h - короткое целое число (суффикс - h или H от sHort )
  • 5U,5u - целое число без знака (суффикс - u или U, от Unsigned );
  • 5HU,5hu,5Hu,5hU - короткое целое число без знака;
  • 5L,5l - длинное целое число (суффикс - l или L, от Long );
  • 5LU,5lu,5Lu,5lU - длинное целое число без знака;
  • 5f,5F - короткое вещественное число (суффикс - f или F, от Float );
  • 5LF,5FL,5fl,5lf,5Lf,5lF,5Fl,5fL - длинное вещественное число.
  • Входной язык системы визуального программирования BCB дополнительно позволяет использовать следующие суффиксы:

  • i8 - для однобайтовых целых чисел со знаком (например, 127i8 )
  • i16 - для двухбайтовых целых чисел со знаком;
  • i32 - для четырехбайтовых целых чисел со знаком;
  • i64 - для восьмибайтовых целых чисел со знаком;
  • ui64 - для восьмибайтовых целых чисел без знака.
  • 3.4. Объявление и инициализация числовых переменных

    Переменные числового типа, используемые в программе, обязательно должны быть объявлены до их использования в тех или иных исполняемых операторах. В отличие от языка Pascal алгоритмические языки C, C++ позволяют вводить такие описания не только в начале программных единиц (функций), но и по ходу формирования программы:

    int main(void)
    { int i,j;
    ...........
      s=0;
      for(int k=0; k<10; k++)
    ........................

    В приведенном примере переменные i и j объявлены в начале функции, а объявление переменной k встретилось в операторе цикла. По стандарту языка C++ место объявления переменной определяет сферу ее действия. Переменные, описанные в начале функции (такие как i и j в приведенном выше примере), считаются локализованными в данной функции и могут быть использованы в любой части тела этой функции. Повторное объявление переменных с такими же именами считается ошибкой (дублирование имен переменных). В отличие от этого действие переменных, объявленных в некотором внутреннем блоке функции (в нашем примере переменная k объявлены в цикле for ), распространяется только на время работы этого блока. Т.е после выхода из цикла память, выделенная под переменную k, возвращается системе, и без повторного переобъявления этой переменной пользоваться уже нельзя. Следует отметить, что это положение стандарта языка C++ в системе BC 3.1 реализовано не так. Повторное объявление такой переменной в следующем внутреннем блоке в системе BC 3.1 приводит к сообщению об ошибке, т.к. при выходе из цикла переменная k продолжает свою жизнь. В средах визуального программирования Borland C++ Builder этот пассаж исправлен.

    Объявлению любой переменной предшествует служебное слово, определяющее диапазон допустимых значений - тип переменной. Имена базовых типов числовых данных и соответствующие им характеристики, приведены в табл. 3.1 (BC 3.1) и 3.2 (BCB). Для вещественных чисел указаны минимальные по модулю значения.

    Тип Длина Минимальное значение Максимальное значение
    char, signed char 1 байт -128 127
    unsigned char 1 байт 0 255
    short int, short 2 байта -32768 32767
    unsigned short 2 байта 0 65535
    int, signed 2 байта -32768 32767
    unsigned int, unsigned 2 байта 0 65535
    long, long int 4 байта -2147483648 2147483647
    unsigned long 4 байта 0 4294967265
    float 4 байта 3.4*e-38 3.4*e38
    double 8 байт 1.7e-308 1.7e308
    long double 10 байт 3.4e-4932 1.1e4932
    Тип Длина Минимальное значение Максимальное значение
    char, __int8 1 байт -128 127
    unsigned char 1 байт 0 255
    short int, short, __int16 2 байта -32768 32767
    unsigned short 2 байта 0 65535
    int, signed, __int32 4 байта -2147483648 2147483647
    unsigned int, unsigned 4 байта 0 4294967265
    long, long int 4 байта -2147483648 2147483647
    unsigned long 4 байта 0 4294967265
    int64, __int64 8 байт -4611686018427387904 4611686018427387903
    __uint64 8 байт 0 9223372036854775807
    float 4 байта 3.4*e-38 3.4*e38
    double 8 байт 1.7e-308 1.7e308
    long double 10 байт 3.4e-4932 1.1e4932

    Объявление переменной можно совместить с присвоением ей начального значения ( инициализацией ):

    int x=18,y=-5;
      float a=5F;

    В отличие от языка Pascal инициализация локальных переменных в функциях на языках C, C++ происходит при каждом вызове функции.

    3.5. Ввод числовых данных по запросу программы

    3.5.1. Потоковый ввод данных числового типа

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

    cin >> d;
    ...........
      cin >> x1 >> x2 >> x3;

    Первая строка соответствует запросу на ввод значения единственной переменной d. Следующая строка программы представляет запрос на ввод трех числовых значений, первое из которых будет присвоено переменной x1, второе значение предназначается для переменной x2, третье значение - для переменной x3. В ответ на запрос программы пользователь должен набрать на клавиатуре затребованные числа, разделяя их, по крайней мере, одним пробелом. Набор строки завершается нажатием клавиши Enter. Количество числовых значений, набираемых пользователем в пределах строки, может оказаться как меньше, так и больше, чем требуется программе. В первом случае продолжение программы задержится до тех пор, пока пользователь не введет дополнительные строки с недостающими значениями. Если пользователь наберет слишком много значений в вводимой строке, то лишние числовые данные сохранятся в буфере ввода и будут переданы программе при выполнении следующего оператора cin.

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

    Для обеспечения потокового ввода к программе следует подключить заголовочный файл iostream.h:

    #include <iostream.h>
      int main()
    { int i;
      float f;
      double d;
    ..........
      cin >> i >> f >> d;

    В переменные типа char или unsigned char из потока числовые значения ввести невозможно, т.к. они в данном случае воспринимаются системой не как числовые данные, а как символьные. В такие переменные попадет только первый символ набранного значения.

    3.5.2. Форматный ввод

    Гораздо более сложным для начинающих программистов является ввод числовых данных, организуемый с помощью функции scanf, использующей так называемую форматную строку:

    #include <stdio.h>
      int main()
    { int i;
      float f;
      double d;
    ..........
      scanf("%d %f %lf",i,f,d);

    Строка вводимых данных поступает со стандартного устройства ввода ( stdin ), которым по умолчанию считается клавиатура. Завершение набора строки ввода - нажатие клавиши Enter.

    Первый аргумент функции scanf представляет форматную строку, управляющую процессом преобразования числовых данных, набранных пользователем в строке ввода, в машинный формат, соответствующий типам переменных, адреса которых указаны вслед за форматной строкой. Числовые значения в строке ввода рекомендуется разделять одним или несколькими пробелами.

    В приведенном примере переменной i (в списке ввода указан ее адрес - i ), объявленной с помощью спецификатора типа int, соответствует форматный указатель %d. Это означает, что первым числовым значением в строке ввода может быть только целое десятичное число со знаком ( d - от decimal, десятичный). Вещественной переменной f типа float в форматной строке соответствует указатель %f. Это означает, что второе числовое значение в строке ввода должно принадлежать диапазону, предусмотренному для коротких вещественных данных. Для переменной d типа double использован форматный указатель %lf ( l - от long ).

    Как правило, количество форматных указателей, перечисленных в первом аргументе функции scanf, должно совпадать с количеством адресов переменных, следующих за форматной строкой. Исключение составляет случай, когда форматный указатель предписывает программе пропустить очередное значение из введенной строки. В этом случае количество адресов в списке ввода уменьшается соответствующим образом. Например:

    scanf("%d %*l %lf",i,d);

    При выполнении такого оператора ввода программа проигнорирует второе числовое значение, набранное пользователем. Конечно, при ручном наборе вводимых значений, нелепо заставлять пользователя набирать данные, которые программе не понадобятся. Но такая возможность может оказаться полезной, когда строка ввода поступает не с клавиатуры, а из других источников (считана с диска, сформирована другой программой в оперативной памяти).

    Вообще говоря, функция scanf возвращает числовое значение, равное количеству правильно обработанных полей из строки ввода. Это полезно помнить при организации проверки правильности ввода, т.к. сообщения об ошибках ввода функция scanf не выдает, но после первой же ошибки прерывает свою работу.

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

    %[*][ширина][{l|h|L}]{d|i|u|o|x|X|f|e|E|g|G}

    Квадратные скобки здесь означают, что соответствующий элемент форматного указателя может отсутствовать. В фигурных скобках указаны символы, один из которых может быть выбран. Обязательными элементами любого форматного указателя являются начальный символ % и последний символ, определяющий тип вводимого значения.

    Символ * после начального символа является указанием о пропуске соответствующего значения из строки ввода. Необязательное, и, как правило, не используемое при вводе, поле ширина задает количество символов во вводимом значении. Дополнительные признаки l, h и L уточняют длину машинного формата соответствующей переменной ( l, L - long; h - short ). Значение последнего обязательного символа форматного указателя расшифровано в табл. 3.3.

    Символ формата Допустимое значение в строке ввода
    d целое десятичное число со знаком
    i целое число
    u целое число без знака
    o целое восьмеричное число без знака
    x,X целое шестнадцатеричное число без знака
    f вещественное число
    e,E вещественное число
    g,G вещественное число

    Форматный ввод также как и потоковый не позволяет вводить числовые значения в переменные типа char. Дело в том, что минимальная длина числового значения вводимого с помощью функции scanf - 2 байта. И значение введенного старшего байта затирает в памяти еще один байт вслед за переменной типа char. Заметить такую ошибку удается не каждому, но на работу программы такой ввод может повлиять довольно серьезно. Поэтому возьмите за правило - в однобайтовые переменные типа char числовую информацию вводить нельзя.

    В системе BC 3.1 для ввода значений переменных типа short или int можно использовать один из следующих форматных указателей - %d, %i, %u, %o, %x или %X. Ввод целочисленных данных типа long требует одного из следующих форматных указателей - %ld, %li, %lu, %lo, %lx или %lX.

    В системе BCB для двухбайтовых числовых переменных следует использовать форматные указатели в сочетании с символом h - %hd, %hi, %hu, %ho, %hx или %hX. Форматные указатели %d и %ld здесь эквивалентны.

    Такое обилие целочисленных форматных указателей объясняется тем, что при наборе целых чисел можно добавлять префиксы - 0x или 0X для шестнадцатеричных значений, лидирующий 0 для восьмеричных значений. При использовании форматного указателя %d вводимые данные не могут содержать указанные префиксы, все значения интерпретируются только как целые десятичные числа со знаком. При использовании форматного указателя %i ( i - от integer ) можно вводить числа в любой нотации, но обязательно с соответствующим префиксом. Форматные указатели %o, %x или %X позволяют вводить восьмеричные или шестнадцатеричные числа как с соответствующими префиксами, так и без них.

    Ввод коротких вещественных данных типа float может быть осуществлен с использованием любого из следующих форматных указателей - %f, %e, %E, %g или %G. На вводе все они эквивалентны, их специфика проявляется только при выводе числовых результатов. Для восьмибайтовых данных типа double к каждому из этих указателей добавляется буква l - %lf, %le, %lE, %lg или %lG. Десятибайтовым числовым данным типа long double соответствуют форматные указатели %Lf, %Le, %LE, %Lg или %LG.

    3.6. Вывод числовых результатов

    3.6.1. Форматный вывод

    Форматный вывод числовых результатов на стандартное устройство вывода ( stdout ), которым по умолчанию является экран дисплея, осуществляется с помощью функции printf. Например:

    #include <stdio.h>
      int main()
    { int i;
      float f;
      double d;
    ..........
      printf("%d %f %lf", i+1, f, f*d);

    Ее аргументы напоминают по форме обращение к функции scanf с той лишь разницей, что список вывода составляют не адреса переменных, а арифметические выражения, значения которых предварительно будут подсчитаны, а потом выведены в соответствии с использованными форматными указателями.

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

    %[флажки][ширина][.точность][{l|h|L}]{d|i|u|o|x|X|f|e|E|g|G}

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

    Поле флажков может содержать до четырех управляющих символов из набора [ -,+,пробел,0,# ]. Символ " минус " устанавливает прижим выводимого значения к левой границе выделенного поля (по умолчанию действует правый прижим). Символ " плюс " устанавливает обязательный режим вывода знака числа (даже если оно положительно). Символ " пробел " устанавливает такой режим, при котором вместо знака " + " выводится пробел. Символ " нуль " устанавливает режим вывода чисел, при котором старшие незначащие позиции поля вывода заполняются нулями.

    Символ # влияет на формат вывода восьмеричных, шестнадцатеричных и вещественных чисел. При его использовании перед восьмеричными числами выводится лидирующий нуль, перед шестнадцатеричными числами - префикс 0x или 0X. Для форматов f, e, и E обязательно отображается десятичная точка после целой части (по умолчанию, числа, у которых дробная часть равна 0, выводятся без десятичной точки). Для форматов g и G не удаляются лидирующие нули и всегда выводится десятичная точка.

    В табл. 3.4 и 3.5, заимствованных из файла помощи, показаны способы вывода целочисленного значения I=555 и вещественного значения R=5.5 при различных комбинациях флажков.

    Необязательное поле точность задает для целочисленных значений обязательное количество цифр (если число меньше, то перед его старшим разрядом добавляется необходимое количество нулей). Например, вывод целочисленного значения -5 по формату %8.4d выделяет в строке вывода поле из восьми символов, на котором первые три позиции заполнены пробелами, а следующие пять символами -0005.

    Для данных вещественного типа поле точность определяет количество цифр в дробной части числа. Не забывайте о количестве значащих цифр, хранение которых обеспечивает тот или иной тип данных. Например, если переменной типа float присвоить значение 3.14159265, то на выводе по формату %10.8f мы увидим результат 3.14159274. Двум последним цифрам результата доверять нельзя, т.к. формат float обеспечивает хранение 7-8 десятичных цифр.

    Флажки Формат %6d Формат %6o Формат %8x
    %-+#0 +555 01053 0x22b
    %-+# +555 01053 0x22b
    %-+0 +555 01053 22b
    %-+ +555 01053 22b
    %-#0 555 01053 0x22b
    %-# 555 01053 0x22b
    %-0 555 1053 22b
    %- 555 1053 22b
    %+#0 +00555 001053 0x00022b
    %+# +555 01053 0x22b
    %+0 +00555 001053 0000022b
    %+ +555 1053 22b
    %#0 000555 001053 0x00022b
    %# 555 01053 0x22b
    %0 000555 001053 0000022b
    % 555 1053 22b
    Флажки Формат %10.2e Формат %10.2f
    %-+#0 +5.50e+00 +5.50
    %-+# +5.50e+00 +5.50
    %-+0 +5.50e+00 +5.50
    %-+ +5.50e+00 +5.50
    %-#0 5.50e+00 5.50
    %-# 5.50e+00 5.50
    %-0 5.50e+00 5.50
    %- 5.50e+00 5.50
    %+#0 +005.50e+00 +0000005.50
    %+# +5.50e+00 +5.50
    %+0 +005.50e+00 +0000005.50
    %+ +5.50e+00 +5.50
    %#0 005.50e+00 0000005.50
    %# 5.50e+00 5.50
    %0 005.50e+00 0000005.50
    % 5.50e+00 5.50

    Разница между форматами %0x и %0X заключается в том, что в первом случае шестнадцатеричная запись числа формируется из малых букв [ a,b,c,d,e,f ], а во втором случае - из больших букв [ A,B,C,D,E,F ].

    Для вывода однобайтовых целочисленных данных со знаком (типа char ) можно пользоваться одним из следующих форматов - %o, %0x, %0X, %i, %d, %ho, %hx, %hX, %hi, %hd. Для вывода однобайтовых целых без знака наряду с перечисленными указателями допустим формат %u. Однако следует иметь ввиду, что однобайтовые значения расширяются до двухбайтовых, сохраняя знак числа. Поэтому попытка вывести однобайтовое значение 127 по формату %u приведет к правильному результату. Но если мы по такому же формату выдадим число -5, то результатом будет число 65531 (дополнение до максимального двухбайтового числа).

    Вывод числовых результатов вещественного типа предусматривает две формы отображения - с фиксированной запятой (форматные указатели %f и %lf ) или с плавающей запятой (форматные указатели %e и %E ). Форматные указатели %g и %G предлагают системе самой выбрать один из этих форматов, который окажется более компактным для выводимого значения. Большая или маленькая буква в форматных указателях с плавающей запятой приводит к тому, что порядку числа предшествует большая или маленькая буква " e ".

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

    x1=127;  x2=-350;
      printf("x1=%d x2=%d",x1,x2);

    на экране дисплея появится строка:

    x1=127 x2=-350

    Если мы хотим расположить выводимую информацию на экране дисплея с начала следующей строки, то в начале форматных указателей обычно вставляют управляющий символ " \n ":

    printf("\nx1=%d x2=%d",x1,x2);

    Такой же управляющий символ может быть включен в середину или в конец форматной строки. В языках C, C++ с помощью символа " \ " в форматную строку могут быть включены и другие управляющие символы (так называемые Escape-последовательности), список которых приведен в табл. 3.6.

    Символ Назначение Символ Назначение
    \a Звуковой сигнал (код 0x07, BEL) \\ Отображение символа \
    \b Забой предыдущего символа (код 0x08, Backspace) \' Отображение символа '
    \f Переход на новую страницу (код 0x0C, FF) \" Отображение символа "
    \n Переход на следующую строку (код 0x0A, LF) \? Отображение символа ?
    \r Возврат в начало текущей строки (код 0x0D, CR) \0xxx Отображение символа с восьмеричным кодом xxx
    \t Горизонтальная табуляция (код 0x09, HT) \xhh Отображение символа с шестнадцатеричным кодом hh
    \v Вертикальная табуляция (код 0x0B, VT) \xHH Отображение символа с шестнадцатеричным кодом HH

    В строковых значениях такие символы как апостроф и вопросительный знак могут набираться и без предшествующего обратного слэша.

    3.6.2. Потоковый вывод

    Для начинающих программистов организация потокового вывода числовой информации на стандартное устройство вывода ( stdout ) представляется более простой:

    #include <iostream.h>
      int main()
    { int i;
      float f;
      double d;
    ..........
      cout << i+1 << f << f*d;

    Здесь на первых порах не надо заботиться о форматах выводимых результатов. Для каждого типа данных существуют соответствующие системные соглашения. Переход в начало следующей строки здесь осуществляется путем включения в список вывода либо упомянутого выше управляющего символа ' \n ', либо аналогичного ему признака конца строки endl:

    cout << i+1 << f << f*d << endl;

    В список выводимых данных могут быть вкраплены и пояснительные подписи:

    cout << "x1="<< x1 << "x2=" << x2 << '\n';

    В потоковом выводе тоже имеются средства управления форматом вывода числовых результатов. Однако научиться пользоваться ими почти так же сложно, как и овладеть нюансами работы с форматными указателями. Дело в том, что потоки вывода в языке C++ организованы двумя способами - старым и новым. Поэтому в управлении потоком вывода можно встретить различные синтаксические конструкции - традиционные функции ( hex - установка режима вывода шестнадцатеричного числа, setw - установка ширины поля вывода, setprecision - установка количества отображаемых цифр и т.п.) и более современные обращения к методам класса ( cout.fill(:), cout.width(...) ). Некоторые из установленных режимов действуют только на вывод следующего значения, другие сохраняют свое действие до следующей установки. С некоторыми деталями управления форматами числовых данных в потоковом выводе можно познакомиться на примере 4 из следующего параграфа.

    3.7. Примеры программ вывода числовых данных

    Пример 1. Вывод однобайтовых числовых значений

    #include <iostream.h>
    #include <stdio.h>
    #include <conio.h>
    
    int main()
    { char ch1 = 69;
      char ch2 = -128;
      unsigned char uch1 = 70;
      unsigned char uch2 = 129;
      __int8 i8_1 = 71;
      __int8 i8_2 = -127;
      printf("\nch1=%d  ch2=%d",ch1,ch2);
      printf("\nuch1=%u uch2=%u",uch1,uch2);
      printf("\ni8_1=%i i8_2=%i\n",i8_1,i8_2);
      cout << "ch1=" << ch1 << endl;
      cout << "ch1=" << (int)ch1 << endl;
      cout << "ch2=" << ch2 << endl;
      cout << "ch2=" << (int)ch2 << endl;
      cout << "i8_1=" << i8_1 << endl;
      cout << "i8_1=" << (int)i8_1 << endl;
      cout << "i8_2=" << i8_2 << endl;
      cout << "i8_2=" << (int)i8_2 << endl;
      getch();
      return 0;
    }
    //=== Результаты работы ===
    ch1=69  ch2=-128
    uch1=70 uch2=129
    i8_1=71 i8_2=-127
    ch1=E
    ch1=69
    ch2=А
    ch2=-128
    i8_1=G
    i8_1=71
    i8_2=Б
    i8_2=-127

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

    Пример 2. Вывод однобайтовых числовых значений

    #include <stdio.h>
    #include <conio.h>
    
    int main()
    {
      char ch1 = -5, ch2 = -127;
      unsigned char uch1 = 25, uch2 = 255;
      printf("With sign :\n");
      printf("-5=%3d(10) -127=%3d(10)",ch1,ch2);
      printf("\nOhne sign :\n");
      printf("25=%#3o(8) %#3x(16) %3d(10)",uch1,uch1,uch1);
      printf("\n255=%#3o(8) %#3x(16) %3d(10)",uch2,uch2,uch2);
      getch();
      return 0;
    }
    //=== Результаты работы ===
    With sign :
    -5= -5(10) -127=-127(10)
    Ohne sign :
    25=031(8) 0x19(16)  25(10)
    255=0377(8) 0xff(16) 255(10)

    В примере 2 однобайтовые числовые значения выданы по формату в виде десятичных, восьмеричных и шестнадцатеричных данных (основание системы записано в круглых скобках после самого значения).

    Пример 3. Вывод коротких и длинных вещественных данных

    #include <stdio.h>
    #include <conio.h>
    
    int main()
    {
      float f1=3.14159265, f2=20000000;
      double d1=3.14159265, d2=20E+125;
      printf("\nf1=%10.8f f2=%f", f1,f2);
      printf("\nf1=%e f2=%e", f1,f2);
      printf("\nf1=%g f2=%g", f1,f2);
      printf("\nd1=%12.9f d2=%g", d1,d2);
      printf("\nd1=%12.9e d2=%G", d1,d2);
      getch();
      return 0;
    }
    //=== Результаты работы ===
    f1=3.14159274 f2=20000000.000000
    f1=3.141593e+00 f2=2.000000e+07
    f1=3.14159 f2=2e+07
    d1= 3.141592650 d2=2e+126
    d1=3.141592650e+00 d2=2E+126

    Пример 4. Управление форматом при выводе в поток

    #include <iostream.h>
    #include <iomanip.h>
    #include <conio.h>
    
    int main()
    { int i=15, j=6, k;
      float v=1.23456, x=3.149;
      cout << hex;
      cout << "i=" << i << " j=" << j << " x=" << x << " v=" << v;
      cout.fill('*');
      cout << "\ni=" << setw(6) << i << " j=" << j << endl;
      cout << setprecision(3) << x << " v=" << v << endl;
      cout.width(10);
      cout << x << ' ' << j << endl;
      for(k=0; k<8; k++)
        cout << setprecision(k) << v << endl;
      getch();
      return 0;
    }
    //=== Результаты работы ===
    i=f j=6 x=3.149 v=1.23456
    i=*****f j=6
    3.15 v=1.23
    ******3.15 6
    1.23456
    1
    1.2
    1.23
    1.235
    1.2346
    1.23456
    1.23456

    3.8. Операции над числовыми данными целого типа

    Над целочисленными данными (константами и переменными) в языках C, C++ можно выполнять обычные арифметические операции - сложение ( x+y ), вычитание ( z-5 ), умножение ( x1*x3 ) и деление ( y/w ). В отличие от языка Pascal здесь деление целочисленных операндов дает целочисленный результат. Например, 5/2=2. Для получения остатка от деления в C, C++ используется операция %, например, 5%2=1.

    Целочисленные данные можно подвергать операции сдвига как влево (знак операции - <<), так и вправо (знак операции - >>) на заданное количество двоичных разрядов:

    y=x<<3; // сдвиг влево на три двоичные разряда
    z=y>>5; // сдвиг вправо на пять двоичных разрядов

    Операция сдвига вправо работает по-разному для целых чисел со знаком и целых чисел без знака. Внимательно посмотрите на результат работы следующей программы:

    #include <stdio.h>
    #include <conio.h>
    int main()
    {
      int x=5, y=-5;
      unsigned z=0xFFFFFFFB;
      printf("x=%x y=%x z=%x",x,y,z);
      printf("\nx<<2=%x x>>2=%x",x<<2,x>>2);
      printf("\ny<<2=%x y>>2=%x",y<<2,y>>2);
      printf("\nz<<2=%x z>>2=%x",z<<2,z>>2);
      getch();
      return 0;
    }
    //=== Результат работы ===
    x=5 y=fffffffb z=fffffffb
    x<<2=20 x>>2=1
    y<<2=ffffffec y>>2=fffffffe
    z<<2=ffffffec z>>2=3ffffffe

    Дело в том, что для чисел со знаком операция сдвига на n разрядов эквивалентна умножению на 2n при сдвиге влево или делению на 2n при сдвиге вправо. Поэтому для отрицательного операнда результат сдвига должен остаться отрицательным. С этой целью при сдвиге вправо производится размножение знакового разряда.

    Над одноименными двоичными разрядами целочисленных операндов могут выполняться логические операции - логическое сложение (символ операции ' | '), логическое умножение (символ операции ' '), исключающее ИЛИ (символ операции ' ^ ') и инвертирование (символ операции ' ~ '). Приведенная ниже программа иллюстрирует выполнение указанных операций над переменными x=5 (двоичный код 00...0101 ) и y=7 (двоичный код 00...0111 ).

    #include <stdio.h>
    #include <conio.h>
    int main()
    {
      int x=5, y=7;
      printf("x=%x y=%x",x,y);
      printf("\nx|y=%x xy=%x",x|y,xy);
      printf("\nx^y=%x ~x=%x",x^y,~x);
      getch();
      return 0;
    }
    //=== Результат работы ===
    x=5 y=7
    x|y=7 xy=5
    x^y=2 ~x=fffffffa

    Определенную помощь при обработке целочисленных данных могут оказать системные функции математической библиотеки. Прототипы этих функций описаны в заголовочных файлах math.h и stdlib.h. Мы упомянем лишь некоторые из них.

    Функция abs(x) возвращает модуль своего аргумента. Аргументом функций atoi(s) и atol(s) является строка, представляющая запись целого числа. Каждая из этих функций преобразует символьную запись числа в соответствующий машинный формат (результат atoi имеет тип int, результат atol - тип long ) и возвращает полученный результат. Довольно полезное преобразование выполняют функции itoa и ltoa. Первый их аргумент - числовое значение типа int или long. Вторым аргументом является строковый массив (или указатель на строку), куда записывается результат преобразования. А третий аргумент, значение которого находится в диапазоне от 2 до 36, определяет основание системы счисления, в которую преобразуется значение первого аргумента.

    В ряде математических алгоритмов, использующих вероятностные методы (методы Монте-Карло), а чаще - в игровых программах активно используются различные датчики случайных чисел. Функция random(N) при каждом повторном обращении к ней выдает очередное случайное число из диапазона от 0 до N-1. Эти числа имеют равномерное распределение вероятностей, что можно трактовать следующим образом. Допустим, мы обратились к функции random(1000) 1000 раз. Можно утверждать, что среди полученных чисел почти все будут разными. Конечно, на практике это не совсем так, но вероятность того, что среди полученных чисел встретится много одинаковых, близка к нулю. Для игровых программ очень важно, чтобы при обращении к датчику случайных чисел каждый раз возникала непредсказуемая последовательность. С этой целью можно обратиться к функции randomize(), которая случайным образом возмущает начальное состояние программы, генерирующей случайные числа.

    Продемонстрируем использование датчика случайных чисел на примере программы перемешивания колоды карт. Предположим, что для идентификации карт использованы целые числа в диапазоне от 0 до 35 (или до 51). Идея алгоритма перемешивания состоит в многократной генерации случайных чисел от 0 до 35 и перекладывания первой карты с k-той ( k - очередное случайное число). Организуем пятикратное перемешивание, чтобы убедиться в том, что каждый раз колода оказывается в новом непредсказуемом состоянии.

    #include <stdio.h>
    #include <stdlib.h>
    #include <conio.h>
    
    void mixer(char *b)
    { int j;
      char i,tmp;
      randomize();
      for(j=0; j<10000; j++)
      { i=random(36);
        tmp=b[0]; b[0]=b[i]; b[i]=tmp;
      }
    }
    
    int main()
    { char j,k,a[36];
      printf("\nPlaying-cards before:\n");
      for(k=0; k<36; k++)
      { a[k]=k; printf("%4d",a[k]); }
      for(j=0; j<5; j++)
      { mixer(a);
        printf("\nPlaying-cards after %d:\n",j);
        for(k=0; k<36; k++)
          printf("%4d",a[k]);
      }
      getch();
      return 0;
    }
    //=== Результат работы ===
    Playing-cards before:
       0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18
      19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35
    Playing-cards after 0:
       9   0  13  32  18  30  14  24  34  28   7  26  35  16   3  27   5  11   6
      23  21   8  31  17  29   2  15  22  33  12  19  25   1   4  10  20
    Playing-cards after 1:
      28   9  16   1   6  19   3  29  10  33  24  15  20   5  32  22  30  26  14
      17   8  34  25  11  12  13  27  31   4  35  23   2   0  18   7  21
    Playing-cards after 2:
      33  28   5   0  14  23  32  12   7   4  29  27  21  30   1  31  19  15   3
      11  34  10   2  26  35  16  22  25  18  20  17  13   9   6  24   8
    Playing-cards after 3:
       4  33  30   9   3  17   1  35  24  18  12  22   8  19   0  25  23  27  32
      26  10   7  13  15  20   5  31   2   6  21  11  16  28  14  29  34
    Playing-cards after 4:
      18   4  19  28  32  11   0  20  29   6  35  31  34  23   9   2  17  22   1
      15   7  24  16  27  21  30  25  13  14   8  26   5  33   3  12  10

    3.9. Операции над числовыми данными вещественного типа

    При вычислениях с вещественными данными разрешается использовать только четыре арифметические операции - сложение (+), вычитание (-), умножение (*) и деление (/). В арифметическом выражении могут встречаться операнды разных типов, поэтому важно знать, как компилятор определяет тип результирующего значения. Правило это довольно простое, но оно содержит подводные камни. Арифметические действия выполняются в соответствии общепринятыми правилами:

  • сначала выполняются действия в самых внутренних скобках;
  • внутри скобок порядок действий определяется приоритетом операций - сначала одноместные операции типа смены знака, инвертирования и вычисления функций, затем умножения и деления и, в самую последнюю очередь, сложения и вычитания;
  • тип результата функции описан либо в системных заголовочных файлах, либо в программе пользователя;
  • тип операнда, представленного сомножителями, определяется типом самого "продвинутого" сомножителя (т.е. допускающего самый широкий диапазон представления данных);
  • тип выражения, состоящего из слагаемых, определяется типом самого "продвинутого" слагаемого.
  • Несмотря на простоту и естественность описанных выше правил, результаты вычисления некоторых выражений могут поставить в тупик не очень внимательного программиста. Например, не вызывает сомнения, что тип выражения 5/2+1.0 должен быть вещественным. Однако к вещественным данным относятся и значения типа float, и значения типа double, и значения типа long double. В данном случае, компилятор ориентируется на тип числовой константы 1.0, которая по правилам системы программирования преобразуется в машинный формат длинного вещественного числа (т.е. имеет тип double ). А вот результат вычисления данного выражения равен 3.000000, и это может вызвать недоумение. На самом деле, тип каждого слагаемого формулы определяется независимо от типов других слагаемых. Первое слагаемое представлено частным от деления двух целых констант, поэтому его тип тоже целый, т.е. результат деления равен 2, а не 2.5. Затем значения всех слагаемых приводятся к типу double, и итоговый результат равен 3.0.

    Существенную помощь в вычислениях с вещественными данными оказывают многочисленные математические функции из раздела math.h. Список некоторых из них приведен в табл. 3.7.

    Прототип функции Возвращаемое значение
    double acos(double x) arccos x
    double asin(double x) arcsin x
    double atan(double x) arctg x
    double atan2(double x, double y) arctg (y/x)
    double atof(const char *s) машинный формат числа из строки s
    double ceil(double x) округление "сверху"
    double cos(double x) cos x
    double cosh(double x) ch x
    double exp(double x) ex
    double fabs(double x) |x|
    double floor(double x) округление "снизу"
    double log(double x) ln x
    double log10(double x) lg x
    max(a,b) максимум (a,b), тип совпадает с типом максимального аргумента
    min(a,b) минимум (a,b) , тип совпадает с типом минимального аргумента
    double pow(double x, double y) xy
    double pow10(int p) 10p
    double sin(double x) sin x
    double sinh(double x) sh x
    double sqrt(double x) квадратный корень из x
    double tan(double x) tg x
    double tanh(double x) th x
    double hypot((double x, double y) квадратный корень из x2+y2
    double poly(double x, int n, double *a) значение полинома
    double ldexp(double x, int n) x*2n

    Прототипы функций max и min, которые, на самом деле представлены не настоящими функциями, а соответствующими макроопределениями, содержатся в файле stdlib.h.

    Для большинства функций типа double с аргументом типа double имеются их аналоги с данными типа long double. Названия этих функций отличаются от приведенных в табл. 3.7 добавкой окончания l - (fabs, fabsl), (acos, acosl), ...:

    Особо следует остановиться на функциях округления ceil и floor. Первая из них возвращает наименьшее целое значение, которое больше или равно x. Вторая возвращает наибольшее целое значение, не превосходящее x. Обратите внимание на то, что значение, возвращаемое обеими функциями, представлено в формате double.

    ceil(0.1) =1.0			floor(0.1) = 0.0
    ceil(0.5) =1.0			floor(0.5) = 0.0
    ceil(0.9) =1.0			floor(0.9) = 0.0
    ceil(-0.9)=0.0			floor(-0.9)=-1.0
    ceil(-0.5)=0.0			floor(-0.5)=-1.0
    ceil(-0.1)=0.0			floor(-0.1)=-1.0

    Довольно часто программисты используют для округления функцию floor(x+0.5). Однако иногда она выдает результат, не совпадающий с общепринятым в математике, например floor(-0.5+0.5)=0. Конечно, жаль, что в языках C, C++ нет прямого аналога функции round из Паскаля, но построить такую функцию совсем не сложно:

    int round(double x)
    { int res;
      res=(x<0)? x-0.5 : x+0.5;
      return res;
    }

    Если нужно произвести округление в том или ином знаке, то число можно предварительно разделить или умножить на 10k, округлить, а затем результат округления умножить или разделить на 10k.

    Замечание 1. При выводе числовых результатов вещественного типа необходимые округления производят системные программы вывода.

    Замечание 2. Если целочисленной переменной присваивается вещественное значение, то округление не производится. Дробная часть, какой бы она ни была, просто отбрасывается.

    В заголовочном файле math.h приводятся определения именованных констант, которыми полезно воспользоваться в своих программах (см. табл. 3.8).

    Константа Значение Константа Значение
    M_PI $$\pi$$ M_E e=2.718...
    M_PI_2 $$\pi /2$$ M_LOG2E log2e
    M_PI_4 $$\pi /4$$ M_LOG10E log e
    M_2_PI $$2/\pi$$ M_LN2 ln 2
    M_1_SQRTPI $$1/\sqrt \pi$$ M_LN10 ln 10
    M_2_SQRTPI $$2/ \sqrt \pi$$ M_SQRT2 $$\sqrt 2=1.414..$$.
    M_SQRT_2 $$\sqrt 2/2$$
    Вернуться к учебному плану