netlib.narod.ru< Назад | Оглавление | Далее >

2.7. Преобразования типов

Если операнды одного оператора принадлежат к разным типам, они преобразуются в общий тип в соответствии с небольшим набором правил. Обычно автоматически производятся лишь те преобразования, которые превращают операнды с меньшим диапазоном значений в операнды с большим диапазоном без какой-либо потери информации, как, например, преобразование целого в число с плавающей точкой в выражении вроде float + int. Выражения, не имеющие смысла, например число с плавающей точкой в роли индекса массива, не допускаются. Выражения, в которых могла бы теряться информация (скажем, при присваивании длинных целых переменным более коротких типов или при присваивании значений с плавающей точкой целым переменным), могут повлечь за собой предупреждение, но являются допустимыми.

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

/* atoi: преобразование строки в целое число */
int atoi(char s[])
{
    int i,n;

    n = 0;
    for(i = 0; s[i] >='0' && s[i] <='9'; ++i)
        n = 10 * n + (s[i] - '0');
    return n;
}

Как мы уже говорили в главе 1, выражение

    s[i] - '0'

дает числовое значение символа, хранящегося в s[i], так как значения '0', '1' и т.д. образуют непрерывную возрастающую последовательность.

Другим примером преобразования значений char в int является функция lower, которая получает одиночный символ из набора ASCII и, если он является заглавной буквой, превращает его в строчную. Если символ не является заглавной буквой, функция lower возвращает его неизменным.

/* lower: преобразование символа из заглавного в строчный.
          Только для кодировки ASCII */
int lower(int c)
{
  if (c >= 'A' && c <='Z')
    return c + 'a' - 'A';
  else
    return c;
}

В случае кодировки ASCII эта программа будет работать правильно, потому что между одинаковыми буквами верхнего и нижнего регистров всегда одинаковое расстояние (если их рассматривать как числовые значения). Кроме того, в этой кодировке латинский алфавит непрерывный, т.е. между буквами A и Z расположены только буквы. Для кодировки EBCDIC последнее условие не выполняется, и в этом случае функция будет преобразовывать не только буквы.

Стандартный заголовочный файл <ctype.h>, описанный в приложении В, определяет семейство функций, которые позволяют проверять и преобразовывать символы независимо от символьного набора. Например, функция tolower(c) преобразует символ c из верхнего регистра в нижний, если с это буква. Поэтому функция tolower является универсальной заменой для рассмотренной выше функции lower. Аналогично проверку

    c >='0' && c <='9'

можно заменить на

    isdigit(c)

Далее мы будем пользоваться функциями из <ctype.h>.

Существует одна тонкость, касающаяся преобразования символов в целые числа: язык не определяет, являются ли переменные типа char знаковыми или беззнаковыми. Может ли при преобразовании char в int когда-нибудь получиться отрицательное число? На машинах с разной архитектурой ответы могут отличаться. На некоторых машинах значение типа char у которого старший бит равен единице будет преобразовано в отрицательное число (так называемое «распространение знакового разряда»). На других — преобразование char в int осуществляется путем добавления нулей слева, и, таким образом, получаемое значение всегда положительно.

Гарантируется, что любой символ из стандартного набора печатаемых символов никогда не будет отрицательным числом, поэтому в выражениях такие символы всегда являются положительными операндами. Но произвольный восьмибитовый код в переменной типа char на одних машинах может быть отрицательным числом, а на других — положительным. Для совместимости переменные типа char, в которых хранятся данные не являющиеся символами, следует явно определять как signed или unsigned.

Сравнения вроде i > j и логические выражения, использующие операторы && и ||, образуют выражение, значение которого равно 1, если оно истинно, и 0, если ложно. Так, присваивание

    d = c >= '0' && c <= '9'

установит в переменной d значение 1, если символ c — цифра, и 0 в противном случае. Однако функции, подобные isdigit, в качестве истины могут возвращать любое ненулевое значение. В местах проверок в инструкциях if, while, for и т.д. «истина» означает просто «не нуль».

Неявные арифметические преобразования, как правило, осуществляются естественным образом. В общем случае, когда оператор вроде + или * с двумя операндами (бинарный оператор) имеет разнотипные операнды, прежде чем операция начнет выполняться, «низший» тип (тип с меньшим диапазоном значений) повышается до «высшего». Результат будет иметь высший тип. В параграфе 6 приложения А правила преобразования сформулированы более точно. Если же в выражении нет беззнаковых операндов, можно руководствоваться следующим набором неформальных правил:

Заметим, что операнды типа float не преобразуются автоматически в тип double, этим данная версия языка отличается от первоначальной. Вообще говоря, математические функции, аналогичные собранным в библиотеке <math.h>, используют вычисления с двойной точностью. В основном тип float используется для экономии памяти при работе с большими массивами и, не так часто, для ускорения счета на тех машинах, где арифметика с двойной точностью слишком дорога с точки зрения расхода времени и памяти.

Правила преобразования усложняются с появлением операндов типа unsigned. Проблема в том, что сравнения знаковых и беззнаковых значений зависят от размеров целочисленных типов, которые на разных машинах могут отличаться. Предположим, что значение типа int занимает 16 битов, а значение типа long — 32 бита. Тогда -1L < 1U, поскольку 1U принадлежит к типу unsigned int и преобразуется в тип signed long. Но -1L > 1UL, так как -1L преобразуется в тип unsigned long и воспринимается как большое положительное число.

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

Тип char преобразуется в int либо путем распространения знакового разряда, либо путем добавления нулей.

Тип long int преобразуется в int, short int или в значение типа char путем отбрасывания старших разрядов. Так, при исполнении последовательности операторов

    int  i;
    char c;

    i = c;
    c = i;

значение с не изменится. Это справедливо независимо от того, распространяется знак при преобразовании char в int или нет. Однако, если изменить очередность присваиваний, возможна потеря информации.

Если x принадлежит типу float, а i — к типу int, то и присваивание x = i, и присваивание i = x вызовут преобразования типов, причем перевод float в int сопровождается отбрасыванием дробной части. Если значение double преобразуется в тип float, то значение либо округляется, либо обрезается — это зависит от реализации.

Так как аргумент в вызове функции является выражением, при передаче его функции также возможно преобразование типа. При отсутствии прототипа функции аргументы типа char и short переводятся в int, а float — в double. Вот почему мы объявляли аргументы типа int или double даже тогда, когда в вызове функции использовали аргументы типа char или float.

И наконец, для любого выражения можно явно указать необходимость преобразования типа, используя оператор, называемый приведением. Конструкция типа

    (имя типа) выражение

преобразует результат выражения в указанный в скобках тип по перечисленным выше правилам. Смысл приведения типа можно представить себе так: выражение как бы присваивается некоторой переменной указанного типа, и эта переменная используется вместо всей конструкции. Например, библиотечная функция sqrt требует аргумент типа double и выдает бессмысленный результат, если ей передать аргумент другого типа (sqrt описана в <math.h>). Поэтому, если переменная n относится к типу int, мы можем написать

    sqrt((double) n)

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

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

    double sqrt(double);

перед обращением к sqrt в присваивании

    root2 = sqrt(2);

целое число 2 будет преобразовано в значение типа double 2.0 автоматически без явного указания оператора приведения.

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

unsigned long int next = 1;

/* rand: возвращает псевдослучайное целое 0 ... 32767 */
int rand(void)
{
  next = next * 1103515245 + 12345;
  return (unsigned int)(next / 65536) % 32768;
}

/* srand: инициализация генератора rand() */
void srand(unsigned int seed)
{
  next = seed;
}

Упражнение 2-3


Напишите функцию htol(s), которая преобразует последовательность шестнадцатиричных цифр, начинающуюся с 0x или 0X, в соответствуюшее целое. Шестнадцатиричными цифрами являются символы 0...9, a...f, A...F.



netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz