вторник, 27 мая 2014 г.

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

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

#include <stdio.h>
int main(void)
{
char line[10];
int i = 0;
float f = 5;

for (i = 0; i < 10; ++i)
line[i] = i;

printf("%d\n", line[f]);

return 0;

}

При компиляции получаем ошибку: subscript is not of integral type

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

#include <stdio.h>
#define MAXLINE 1000    /* максимальный размер вводимой строки */

int getstr(char line[], int maxline);
int atoi (char line[]);

int main(void)
{
    int len = 0;
    int number = 0;
    char line[MAXLINE]; /* текущая строка */
    
    while ((len = getstr(line, MAXLINE)) > 0)
    {
number = atoi(line);
printf("Преобразует в число %d\n", number);
number = 0;
    }
    
    return 0;
}

/* getline: читает строку в s, возвращает длину */
int getstr(char s[], int lim)
{
    int c, i;

    for (i = 0; i < lim - 1 && (c = getchar()) != EOF && c != '\n'; ++i)
s[i] = c;
    if (c == '\n')
    {
s[i] = c; 
++i;
    }
    s[i] = '\0';        /* в конец строки дописывам "0" */
    return i;   /* функция возвращает длину строки */
}

/* atoi: преобразует s в целое */
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;

}

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


#include <stdio.h>
#define MAXLINE 1000

int getstr(char line[], int maxline);
int lower(int c);

int main(void)
{
int len = 0;
char line[MAXLINE];

while ((len = getstr(line, MAXLINE)) > 0)
{
printf("%s", line);
}

return 0;
}


int getstr(char s[], int lim)
{
int c, i;

for (i = 0; i < lim - 1 && (c = getchar()) != EOF && c != '\n'; ++i)
s[i] = lower(c);
if (c == '\n')
{
s[i] = c;
++i;
}
s[i] = '\0';        
return i;   
}

int lower(int c)
{
if (c >= 'А' && c <= 'Z')
return c + 'а' - 'А';
else
return c;

}

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

#include <stdio.h>
#include <ctype.h>
#define MAXLINE 1000

int getstr(char line[], int maxline);

int main(void)
{
int len = 0;
char line[MAXLINE];

while ((len = getstr(line, MAXLINE)) > 0)
{
printf("%s", line);
}

return 0;
}


int getstr(char s[], int lim)
{
int c, i;

for (i = 0; i < lim - 1 && (c = getchar()) != EOF && c != '\n'; ++i)
s[i] = tolower(c);
if (c == '\n')
{
s[i] = c;
++i;
}
s[i] = '\0';        
return i;   
}
Язык не определяет, являются ли переменные типа char знаковыми или беззнаковыми. При преобразовании char в int может ли когда-нибудь получиться отрицательное целое? На машинах с разной архитектурой ответы могут отличаться. На некоторых машинах значение типа char с единичным старшим битом будет превращено в отрицательное целое (посредством "распространения знака"). На других преобразование char в int осуществляется добавлением нулей слева, и, таким образом, получаемое значение всегда положительно.

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

Отношения вроде i > j и логические выражения, перемежаемые операторами && и ||, определяют выражение-условие, которое имеет значение 1, если оно истинно, и 0, если ложно. Так, присваивание
d = с >= '0' && с <= '9'
установит d в значение 1, если с есть цифра, и 0 в противном случае. Однако функции, подобные isdigit, в качестве истины могут выдавать любое ненулевое значение. В местах проверок внутри ifwhilefor и пр. "истина" просто означает "не нуль".

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

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


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

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

Тип long int преобразуются в short int или в значения типа char путем отбрасывания старших разрядов. Так, в
int i;
char с;
i = с;
с = i;
значение с не изменится. Это справедливо независимо от того, распространяется знак при переводе char в int или нет. Однако, если изменить очередность присваиваний, возможна потеря информации. 

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

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

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

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

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

sqrt((double) n)

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

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

double sqrt(double);

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

root2 = sqrt(2);

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

Операцию приведения проиллюстрируем на переносимой версии генератора псевдослучайных чисел и функции — my_rand(), инициализирующей "семя" — my_srand().
И генератор rand() , и функция srand() входят в стандартную библиотеку.

#include <stdio.h>

#define MAX 1000

unsigned long int next = 1;
void my_srand(unsigned int seed);
int my_rand(void);

int main(void)
{
int mass[MAX];

my_srand(5);

for (int i = 0; i < MAX; i++)
{
mass[i] = my_rand();
}

for (int i = 0; i < MAX; i++)
{
printf("%d:\t%d\n", i, mass[i]);
}

return 0;
}

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

/* my_srand: устанавливает "семя" для my_rand() */
void my_srand(unsigned int seed)
{
next = seed;
}

2 комментария:

  1. for (i = 0; s[i] >= '0' && s[i] <= '9'; ++i)
    n = 10 * n + (s[i] - '0');
    Плохо влияют на карму такие примеры. Не понятно же как это работает. Причем здесь умножение на n? Мы же хотим порядок разряда посчитать в 10*n, как же мы это сделаем?

    ОтветитьУдалить
    Ответы
    1. все, понял. Эта функция просто умножает предыдущее значение, записанное в переменную, таким образом у нас всегда будет записано значение в правильном разряде.

      Удалить