русс | укр

Мови програмуванняВідео уроки php mysqlПаскальСіАсемблерJavaMatlabPhpHtmlJavaScriptCSSC#DelphiТурбо Пролог

Компьютерные сетиСистемное программное обеспечениеИнформационные технологииПрограммирование


Linux Unix Алгоритмічні мови Архітектура мікроконтролерів Введення в розробку розподілених інформаційних систем Дискретна математика Інформаційне обслуговування користувачів Інформація та моделювання в управлінні виробництвом Комп'ютерна графіка Лекції


Перетворення типів


Дата додавання: 2014-11-28; переглядів: 1076.


Коли якийсь оператор має операнди різних типів, останні перетворюються до спільного типу, згідно невеличкого набору правил. Загалом, автоматичними перетвореннями вважаються ті, що обертають «вужчий» операнд на «ширший» без втрати інформації, як, наприклад, при перетворенні цілого в число з рухомою точкою у виразі на зразок f + i (де i містить ціле значення, a f — число з рухомою точкою). Вирази, які не мають змісту, як скажімо використання числа типу float в якості індексу — заборонені. Вирази, в яких можливо втратити інформацію, як от у випадку присвоєння довшого типу цілого коротшому, або присвоєння числа з рухомою точкою цілому, можуть викликати попередження, але не забороняються.

Тип char — це просто маленьке ціле, тож цей тип може вільно вживатися в арифметичних операціях. Це забезпечує суттєвою гнучкістю в деяких випадках перетворення символів. Прикладом може служити спрощена цим наївним втіленням, функція atoi, яка обертає ланцюжок цифр у їхній числовий еквівалент.

/* 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;

}

Як ми вже згадували це в Розділі 1, вираз

s[i] - '0'

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

Іншим прикладом перетворення char на int є функція lower, яка відображає знак у нижньому регістрі набору знаків ASCII. Якщо символ не є літерою верхнього регістру, lower повертає її без змін.

/* lower: переводить c у нижній регістр; тільки ASCII */

int lower(int c)

{

if (c >= 'A' && c <= 'Z')

return c + 'a' - 'A';

else

return c;

}

Це працює тільки з набором ASCII, оскільки відповідні літери верхнього і нижнього регістру знаходяться на сталій відстані як числові значення й алфавіт є неперервним — немає нічого окрім літер між A та Z. Це останнє правило не є дійсним у випадку набору символів EBCDIC, тож цей код перекладав би не тільки літери у випадку EBCDIC.

Стандартний файл заголовка <ctype.h>, описаний у Додатку Б, визначає сімейство функцій, що забезпечують можливістю перевірок і перетворень, незалежних від набору символів. Так, наприклад, функція tolower — це машинонезалежна заміна, наведеної вище, функції lower. Так само, перевірку

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

можна поміняти на

isdigit(c)

З цієї миті і надалі, ми послуговуватимемося функціями з <ctype.h>.

Існує один нюанс, що стосується перетворення знаків на ціле. Мова C не уточнює, чи змінні типуchar являються знаковими чи беззнаковими величинами. Коли char перетворено на int, чи не може це видати від'ємне ціле? Відповідь відрізняється на різних машинах, відображаючи відмінності в архітектурах. На деяких машинах, char із крайнім лівим бітом рівним 1 буде перетворено на від'ємне ціле («знакове розширення»). На інших, char зведено до int шляхом додання нулів із лівого боку, а отже — завжди додатній.

За визначенням, мова C гарантує, що будь-який знак у машинному стандартному друковному наборі символів ніколи не буде від'ємним, а отже завжди являтиметься додатньою величиною у виразах. Але довільні послідовності бітів, збережені в символьних змінних, можуть виявитися від'ємними на деяких машинах і, навпаки — додатніми на інших. Заради портабельності, вказуйтеsigned (знакове) або unsigned (беззнакове), якщо несимвольні дані треба зберегти в змінних типу char.

Реляційні вирази на кшталт i > j і логічні вирази, поєднані && або || мають за визначенням значення 1, якщо істинні і 0, якщо хибні. Таким чином, присвоєння на зразок

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

встановить d до 1, якщо c є цифрою, і 0 — якщо ні. Проте, такі функції, як isdigit, можуть повернути будь-яке ненульове значення у випадку істини. «Істина» в тестовій частині if, while,for тощо означає просто «ненульове значення», тож це не грає особливої ролі.

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

  • Якщо тип якогось з операндів дорівнює long double (довгому подвійному), інший також буде зведено до long double.
  • Інакше, якщо ти якогось з операндів дорівнює double (подвійному), інший також буде перетворено на double.
  • Інакше, якщо тип якогось з операндів дорівнює float (числу з рухомою точкою), інший також буде перетворено на float.
  • Інакше, перетворити char (знакове) та short (коротке) на int (ціле).
  • Якщо ж ти якогось з операндів дорівнює long (довгому), інший також буде перетворено на long.

Зауважте, що float у виразах не перетворюються автоматично на double; це відрізняється від оригінального визначення. Загалом, математичні функцій, як ті з <math.h>, використовуватимуть подвійну точність. Основною причиною використання float є збереження пам'яті у випадку великих масивів або, рідше, збереження часу на машинах, де арифметика з подвійною точністю — особливо ресурсоємка.

Правила перетворень ускладнюються, коли задіяні беззнакові операнди. Проблема полягає в тому, що порівнювання між знаковими і беззнаковими значеннями залежать від машини, оскільки вони покладаються на розміри різноманітних типів цілих. Для прикладу, скажімо, щоint дорівнює 16-и бітам, а long — 32-ом бітам. У такому випадку, -1L < 1U, оскільки 1U, яке є беззнаковим цілим (unsigned int) зведено до знакового довгого (signed long). Але -1L > 1UL, оскільки -1L зведено до беззнакового довгого (unsigned long) і, таким чином, здається великим додатнім числом.

Перетворення мають місце також під час присвоєнь; значення з правого боку зводиться до типу лівого, що й буде типом результату.

Символ перетворюється на ціле або шляхом додання знака, або ні, як буде описано нижче.

Довші цілі перетворюються на коротші або char шляхом відкидання зайвих бітів старшого розряду. Таким чином, при

int i;

char c;

 

i = c;

c = i;

значення c залишиться незмінним. Це залишатиметься істиною, незалежно від того, чи використовується знакове розширення. Однак, при протилежному напрямку присвоєння, можлива втрата інформації.

Якщо 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 є цілим, ми можемо використати

sqrt((double) n)

для перетворення значення n на тип double перед тим як передати його sqrt. Зауважте, що операція зведення просто видає значення відповідного типу, сама n залишиться незмінною. Оператор зведення має такий самий високий пріоритет як і решта унарних операторів, як буде вказано в таблиці в кінці цього розділу. Якщо аргументи оголошено через прототип функції, як і повинно відбуватися за звичайних обставин, оголошення спричинить до автоматичного зведення будь-яких аргументів під час виклику функції. Таким чином, маючи прототип функції srqt

double sqrt(double)

виклик

root2 = sqrt(2)

зводить ціле 2 у подвійне 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. Напишіть функцію htoi(s), яка би обертала ланцюжок з шістнадцяткових цифр (включаючи можливий 0x або 0X) на відповідне ціле значення (int). Дозволеними цифрами є 0до 9, a до f або A до F.


<== попередня лекція | наступна лекція ==>
Реляційні та логічні оператори | Оператори приросту та спаду


Онлайн система числення Калькулятор онлайн звичайний Науковий калькулятор онлайн