Если операнды оператора принадлежат к разным типам, то они приводятся к некоторому общему типу. Приведение выполняется в соответствии с небольшим числом правил. Обычно автоматически производятся лишь те преобразования, которые без какой-либо потери информации превращают операнды с меньшим диапазоном значений в операнды с большим диапазоном, как, например, преобразование целого в число с плавающей точкой в выражении вроде 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 к 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, в качестве истины могут выдавать любое ненулевое значение. В местах проверок внутри if, while, for и пр. "истина" просто означает "не нуль".
Неявные арифметические преобразования, как правило, осуществляются естественным образом. В общем случае, когда оператор вроде + или * с двумя операндами (бинарный оператор) имеет разнотипные операнды, прежде чем операция начнет выполняться, "низший" тип повышается до "высшего". Результат будет иметь высший тип.
Если же в выражении нет беззнаковых операндов, можно удовлетвориться следующим набором неформальных правил:
- Если какой-либо из операндов принадлежит типу 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;
}
Операцию приведения проиллюстрируем на переносимой версии генератора псевдослучайных чисел и функции — 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;
}
for (i = 0; s[i] >= '0' && s[i] <= '9'; ++i)
ОтветитьУдалитьn = 10 * n + (s[i] - '0');
Плохо влияют на карму такие примеры. Не понятно же как это работает. Причем здесь умножение на n? Мы же хотим порядок разряда посчитать в 10*n, как же мы это сделаем?
все, понял. Эта функция просто умножает предыдущее значение, записанное в переменную, таким образом у нас всегда будет записано значение в правильном разряде.
Удалить