Вирази і операції
Комбінація знаків операцій і операндів, результатом якої є певне значення, називається виразом. Знаки операцій визначають дії, які повинні бути виконані над операндами. Кожен операнд у виразі може бути виразом. Значення виразу залежить від розташування знаків операцій і круглих дужок у виразі, а також від пріоритету виконання операцій.
У мові Cі присвоєння також є виразом, і значенням такого виразу є величина, яка присвоюється.
При обчисленні виразів тип кожного операнда може бути перетворений до іншого типу. Перетворення типів можуть бути неявними, при виконанні операцій і викликів функцій, або явними, при виконанні операцій приведення типів.
Операнд - це константа, літерал, ідентифікатор, виклик функції, індексний вираз, вираз вибору елементу або складніший вираз, сформований комбінацією операндів, знаків операцій і круглих дужок. Будь-який операнд, який має константне значення, називається константним виразом. Кожен операнд має тип.
Якщо як операнд використовується константа, то йому відповідає значення і тип представляючої його константи. Ціла константа може бути типу int, long, unsigned int, unsigned long, залежно від її значення і від форми запису. Символьна константа має тип int. Константа з плаваючою крапкою завжди має тип double.
Рядковий літерал складається з послідовності символів, укладених в лапки, і представляється в пам’яті як масив елементів типу char, ініціалізований вказаною послідовністю символів. Значенням рядкового літерала є адреса першого елементу рядка і синтаксично рядковий літерал є покажчиком, що не модифікується, на тип char. Рядкові літерали можуть бути використані як операнди у виразах, що допускають величини типу покажчиків. Проте оскільки рядки не є змінними, їх не можна використовувати в лівій частині операції присвоєння.
Слід пам’ятати, що останнім символом рядка завжди є нульовий символ, який автоматично додається при зберіганні рядка в пам’яті.
Ідентифікатори змінних і функцій. Кожен ідентифікатор має тип, який встановлюється при його оголошенні. Значення ідентифікатора залежить від типу таким чином:
- ідентифікатори об’єктів цілих і плаваючих типів представляють значення відповідного типу;
- ідентифікатор об’єкту типу enum представлений значенням однієї константи з множини значень констант в переліку. Значенням ідентифікатора є константне значення. Тип значення є int, що виходить з визначення переліку;
- ідентифікатор об’єкту типу struct або union представляє значення, визначене структурою або об’єднанням;
- ідентифікатор, оголошуваний як покажчик, представляє покажчик на значення, задане в оголошенні типу;
- ідентифікатор, оголошуваний як масив, представляє покажчик, значення якого є адресою першого елементу масиву. Тип величин, що адресуються покажчиком, - це тип елементів масиву. Відзначимо, що адреса масиву не може бути зміненою під час виконання програми, хоча значення окремих елементів може змінюватися. Значення покажчика, що представляється ідентифікатором масиву, не є змінною і тому ідентифікатор масиву не може з’являтися в лівій частині оператора присвоєння.
- ідентифікатор, оголошуваний як функція, представляє покажчик, значення якого є адресою функції, що повертає значення певного типу. Адреса функції не змінюється під час виконання програми, міняється значення, що тільки повертається. Таким чином, ідентифікатори функцій не можуть з’являтися в лівій частині операції присвоєння.
Виклик функцій складається з виразу, за яким слідує необов’язковий список виразів в круглих дужках:
1вираз-1 ([ список виразів ])
Значенням виразу-1
повинна бути адреса функції (наприклад, ідентифікатор функції). Значення кожного виразу із списку виразів передається у функцію як фактичний аргумент. Операнд, що є викликом функції, має тип і значення значення, що повертається функцією.
Індексний вираз задає елемент масиву і має вигляд:
1вираз-1 [ вираз-2 ]
Тип індексного виразу є типом елементів масиву, а значення представляє величину, адреса якої обчислюється за допомогою значень вираз-1
і вираз-2
.
Звичний вираз-1
- це покажчик, наприклад, ідентифікатор масиву, а вираз-2
- це ціла величина. Проте потрібен тільки, щоб один з виразів був покажчиком, а другий цілочисельною величиною. Тому вираз-1
може бути цілочисельною величиною, а вираз-2
покажчиком. У будь-якому випадку вираз-2
повинен бути укладений в квадратні дужки. Хоча індексний вираз звичайно використовується для посилань на елементи масиву, проте індекс може з’являтися з будь-яким покажчиком.
Індексні вирази для посилання на елементи одновимірного масиву обчислюються шляхом складання цілої величини із значеннями покажчика з подальшим застосуванням до результату операції розадресації *
.
Оскільки один з виразів, вказаних в індексному виразі, є покажчиком, то при складанні використовуються правила адресної арифметики, згідно яким ціла величина перетвориться до адресного уявлення, шляхом множення її на розмір типу, що адресується покажчиком. Хай, наприклад, ідентифікатор arr оголошений як масив елементів типу double.
1double arr[10];
Так, щоб дістати доступ до i-тому елементу масиву arr можна написати аrr[i]
, що, через сказаний вище, еквівалентне i[a]
. При цьому величина i
домножується на розмір типу double
і є адресою i-го елементу масиву arr від його початку. Потім це значення складається із значенням покажчика arr, що в свою чергу дає адресу i-го елементу масиву. До одержаної адреси застосовується операція розадресації тобто здійснюється вибірка елементу масиву arr за сформованою адресою.
Таким чином, результатом індексного виразу arr[i]
(або i[arr]
) є значення i-го елементу масиву.
Вираз з декількома індексами посилається на елементи багатовимірних масивів. Багатовимірний масив - це масив, елементами якого є масиви. Наприклад, першим елементом тривимірного масиву є масив з двома вимірюваннями.
Для посилання на елемент багатовимірного масиву індексний вираз повинен мати декілька індекCів ув’язнених в квадратні дужки:
1вираз-1 [ вираз-2 ][ вираз-3 ] ...
Такий індексний вираз інтерпретується зліва направо, тобто спочатку розглядається перший індексний вираз:
1вираз-1 [ вираз-2 ]
Результат цього виразу є адресний вираз, з яким складається вираз-3 і т.д. Операція розадресації здійснюється після обчислення останнього індексного виразу. Відзначимо, що операція розадресації не застосовується, якщо значення останнього покажчика адресує величину типу масиву.
Приклад:
1int mass [2][5][3];
Розглянемо процес обчислення індексного виразу mass[1][2][2]
.
Обчислюється вираз mass[1]
. Посилання індекс 1 множиться на розмір елементу цього масиву, елементом же цього масиву є двомірний масив містить 5х3 елементів, що мають тип int. Набуваюче значення складається із значенням покажчика mass
. Результат є покажчик на другий двомірний масив розміром (5х3) в тривимірному масиві mass
.
- Другий індекс 2 указує на розмір масиву з трьох елементів типу
int
, і складається з адресою, відповідноюmass[1]
. - Оскільки кожен елемент тривимірного масиву - це величина типу
int
, то індекс 2 збільшується на розмір типуint
перед складанням з адресоюmass[1][2]
. - Нарешті, виконується розадресація одержаного покажчика. Результуючим виразом буде елемент типу
int
.
Якщо було б вказане mass [1][2]
, то результатом був би покажчик на масив з трьох елементів типу int. Відповідно значенням індексного виразу mass[1]
є покажчик на двомірний масив.
Вираз вибору елементу застосовується, якщо як операнд треба використовувати елемент структури або об’єднання. Такий вираз має значення і тип вибраного елементу. Розглянемо дві форми виразу вибору елементу:
вираз.ідентификатор,
вираз->ідентификатор .
У першій формі вираз представляє величину типу struct
або union
, а ідентифікатор – це ім’я елементу структури або об’єднання. У другій формі вираз повинен мати значення адреси структури або об’єднання, а ідентифікатор – ім’я вибираного елементу структури або об’єднання.
Обидві форми виразу вибору елементу дають однаковий результат. Дійсно, запис, що включає знак операції вибору ->
, є скороченою верcією запису з крапкою для випадку, коли вираз стоїть перед крапкою передує операція розадресації *
, застосована до покажчика, тобто запис
вираз -> ідентифікатор
еквівалентний запису
(* вираз).ідентифікатор
у випадку, якщо вираз є покажчиком.
Приклад:
1 struct tree {
2 float num;
3 int data[5];
4 struct tree *left;
5 } tr[5], elem;
6
7 elem.left = &elem;
У приведеному прикладі використовується операція вибору .
для доступу до елементу left структурної змінної elem. Таким чином елементу left структурної змінної elem присвоюється адреса самої змінної elem, тобто змінна elem зберігає посилання на себе саму.
Приведення типів ця зміна (перетворення) типу об’єкту. Для виконання перетворення необхідно перед об’єктом записати в дужках потрібний тип:
( ім’я-типу ) операнд.
Приведення типів використовуються для перетворення об’єктів одного скалярного типу в інший скалярний тип. Проте виразу з приведенням типу не може бути надане інше значення.
Приклад:
1 int i;
2 double x;
3 b = (double)i+2.0;
В даному прикладі ціла змінна за допомогою операції приведення типів приводиться до плаваючого типу, а потім вже бере участь в обчисленні виразу.
Константний вираз - це вираз, результатом якого є константа. Операндом константного виразу можуть бути цілі константи, символьні константи, константи з плаваючою крапкою, константи переліку, вирази приведення типів, вирази з операцією sizeof і інші константні вирази. Проте на використання знаків операцій в константних виразах накладаються наступні обмеження:
У константних виразах не можна використовувати операції присвоєння і послідовного обчислення (,) .
Операція “адреса” &
може бути використана тільки при деяких ініціалізаціях.
Вирази із знаками операцій можуть брати участь у виразах як операнди. Вирази із знаками операцій можуть бути унарними (з одним операндом), бінарними (з двома операндами) і тернарними (з трьома операндами).
Унарний вираз складається з операнда і передування йому знаку унарної операції і має наступний формат:
1 знак-унарної-операції операнд .
Бінарні вирази складаються з двох операндів, розділених знаком бінарної операції:
1 операнд1 знак-бінарної-операції операнд2 .
Тернарний вираз складається з трьох операндів, розділених знаками тернарної операції (?) і (:), і має формат:
1 операнд1 ? операнд2 : операнд3
Операції. По кількості операндів, що беруть участь в операції, операції підрозділяються на унарні, бінарні і тернарні.
У мові Cі є наступні унарні операції:
Операція | Опис |
---|---|
- (мінус) | арифметичне заперечення (заперечення і доповнення); |
~ (тильда) | побітове логічне заперечення (доповнення); |
! | логічне заперечення; |
* | розадресація (непряма адресація); |
& | обчислення адреси; |
+ | унарний плюс; |
++ | збільшення (інкремент); |
– | зменшення (декремент); |
sizeof | розмір. |
Унарні операції виконуються справа наліво.
Операції збільшення і зменшення збільшують або зменшують значення операнда на одиницю і можуть бути записані як справа так і зліва від операнда. Якщо знак операції записаний перед операндом (префіксна форма), то зміна операнда відбувається до його використання у виразі. Якщо знак операції записаний після операнда (постфіксна форма), то операнд спочатку використовується у виразі, а потім відбувається його зміна.
На відміну від унарних, бінарні операції, список яких наведений в табл.7, виконуються зліва направо.
Таблиця 7.
Знак операції | Операція | Група операцій |
---|---|---|
* | Множення | Мультиплікативні |
/ | Ділення | |
% | Остача віділення | |
+ | Додавання | Адитивні |
- | Віднімання | |
<< | Зсув вліво | Операції зсуву |
>> | Зсув вправо | |
< | Менше | Операції відношення |
<= | Менше або рівно | |
>= | Більше або рівно | |
== | Рівно | |
!= | Не рівно | |
& | Порозрядне І | Порозрядні операції |
| | Порозрядне АБО | |
^ | Що порозрядне виключає АБО | Логічні операції |
&& | Логічне І | |
|| | Логічне АБО | |
, | Послідовне обчислення | Послідовного обчислення |
= | Присвоєння | Операції присвоєння |
*= | Множення з присвоєнням | |
/= | Розподіл з присвоєнням | |
%= | остача від розподілу з присвоєнням | |
-= | Віднімання з присвоєнням | |
+= | Складання з присвоєнням | |
<<= | Зсув вліво з присвоєнням | |
>>= | Зсув вправо присвоєнням | |
&= | Порозрядне І з присвоєнням | |
|= | Порозрядне АБО з присвоєнням | |
^= | Що порозрядне виключає АБО з присвоєнням |
Лівий операнд операції присвоєння повинен бути виразом, що посилається на область пам’яті (але не об’єктом оголошеним з ключовим словом const), такі вирази називаються ліводопустимими до них відносяться:
- ідентифікатори даних цілого і плаваючого типів, типів покажчика, структури, об’єднання;
- індексні вирази, виключаючи вирази мають тип масиву або функції;
- вирази вибору елементу (->) і (.), якщо вибраний елемент є ліводопустимим;
- вирази унарної операції розадресації
*
, за винятком виразів, що посилаються на масив або функцію; - вираз приведення типу якщо результуючий тип не перевищує розміру первинного типу.
При запиcі виразів слід пям’ятати, що символи *
, &
, !
, +
можуть позначати унарну або бінарну операцію.
Перетворення при обчисленні виразів
При виконанні операцій виробляється автоматичне перетворення типів, щоб привести операнди виразів до загального типу або щоб розширити короткі величини до розміру цілих величин, використовуваних в машинних командах. Виконання перетворення залежить від специфіки операцій і від типу операнда або операндів.
Розглянемо загальні арифметичні перетворення.
Операнди типу float перетворяться до типу double.
- Якщо один операнд long double, то другий перетвориться до цього ж типу.
- Якщо один операнд double, то другий також перетвориться до типу double.
- Будь-які операнди типу char і short перетворяться до типу int.
- Будь-які операнди unsigned char або unsigned short перетворяться до типу unsigned int.
- Якщо один операнд типу unsigned long, то другий перетвориться до типу unsigned long.
- Якщо один операнд типу long, то другий перетвориться до типу long.
- Якщо один операнд типу unsigned int, то другий операнд перетвориться до цього ж типу.
Таким чином, можна відзначити, що при обчисленні виразів операнди перетворяться до типу того операнда, який має найбільший розмір.
Приклад:
1 double ft,sd;
2 unsigned char ch;
3 unsigned long in;
4 int i;
5 ...
6 sd=ft*(i+ch/in);
При виконанні оператора присвоєння правила перетворення використовуватимуться таким чином. Операнд ch перетвориться до unsigned int (правило 5). Потім він перетвориться до типу unsigned long (правило 6). За цим же правилом i перетвориться до unsigned long і результат операції, укладеної в круглі дужки матиме тип unsigned long. Потім він перетвориться до типу double (правило 3) і результат всього виразу матиме тип double
.
Операції заперечення і доповнення
Операція арифметичного заперечення -
виробляє заперечення свого операнда. Операнд повинен бути цілою або плаваючою величиною. При виконанні здійснюються звичні арифметичні перетворення.
Приклад:
1double u = 5;
2u = -u; /* змінній u присвоюється її заперечення,
3 тобто u приймає значення -5 */
Операція логічного заперечення (!) не виробляє значення 0, якщо операнд є істина (не нуль), і значення 1, якщо операнд рівний нулю (0). Результат має тип int
. Операнд повинен бути цілого або плаваючого типу або типу покажчик.
Приклад:
1int t, z=0;
2t=!z;
Змінна t набуде значення рівне 1, оскільки змінна z мала значення рівне 0 (помилково).
Операція двійкового доповнення (~) виробляє двійкове доповнення свого операнда. Операнд повинен бути цілого типу. Здійснюється звичне арифметичне перетворення, результат має тип операнда після перетворення.
Приклад:
1 char b = '9';
2 unsigned char f;
3 b = ~f;
Шістнадцяткове значення символу ‘9’ рівне 39. В результаті операції ~f буде набуте шістнадцяткове значення С6, що відповідає символу ‘ц’.
Операції розадресації і адреси
Ці операції використовуються для роботи із змінними типу покажчик.
Операція розадресації *
здійснює непрямий доступ до величини, що адресується, через покажчик. Операнд повинен бути покажчиком. Результатом операції є величина, на яку вказує операнд. Типом результату є тип величини, що адресується покажчиком. Результат не визначений, якщо покажчик містить неприпустиму адресу.
Розглянемо типові ситуації, коли покажчик містить неприпустиму адресу:
- покажчик є нульовим;
- покажчик визначає адресу такого об’єкту, який не є активним у момент посилання;
- покажчик визначає адресу, яка не вирівняна до типу об’єкту, на який він вказує;
- покажчик визначає адресу, не використовувану програмою, що виконується.
Операція адресу &
дає адресу свого операнда. Операндом може бути будь-який іменований вираз. Ім’я функції або масиву також може бути операндом операції “адреса”, хоча в цьому випадку знак операції є зайвим, оскільки імена масивів і функцій є адресами. Результатом операції адреси є покажчик на операнд. Тип, що адресується покажчиком, є типом операнда.
Операція адреси не може застосуються до елементів структури.
Приклади:
1int t, f=0, *addresss;
2addresss = &t;
3/* змінній addresss, оголошуваної як покажчик,
4 присвоюється адреса змінної t */
5
6*addresss = f; /* змінній знаходиться за адресою, що міститься
7 у змінній addresss, присвоюється значення
8 змінною f, тобто 0, що еквівалентне
9 t=f; тобто t=0;
10 */
Операція sizeof
За допомогою операції sizeof можна визначити розмір пам’яті яка відповідає ідентифікатору або типу. Операція sizeof має наступний формат:
1sizeof(вираз).
Як вираз може бути використаний будь-який ідентифікатор, або ім’я типу, укладене в дужки. Відзначимо, що не може бути використане ім’я типу void
, а ідентифікатор не може відноситься до поля бітів або бути ім’ям функції.
Якщо як вираз вказано ім’я масиву, то результатом є розмір всього масиву (тобто твір числа елементів на довжину типу), а не розмір покажчика, відповідного ідентифікатору масиву.
Коли sizeof
застосовується до імені типу структури або об’єднання або до ідентифікатора, який має тип структури або об’єднання, то результатом є фактичний розмір структури або об’єднання, який може включати ділянки пам’яті, використовувані для вирівнювання елементів структури або об’єднання. Таким чином, цей результат може не відповідати розміру, одержуваному шляхом складання розмірів елементів структури.
Приклад:
1 struct {
2 char h;
3 int b;
4 double f;
5 } str;
6 int a1;
7 a1 = sizeof(str);
Змінна а1 набуде значення, рівне 12, в той же час якщо скласти довжини всіх використовуваних в структурі типів, то одержимо, що довжина структури str рівна 7.
Невідповідність має місце на увазі того, що після розміщення в пам’яті першої змінної h довжиною 1 байт, додається 1 байт для вирівнювання адреси змінної b на границю слова (слово має довжину 2 байти для машин серії IBM PC AT /286/287), далі здійснюється вирівнювання адреси змінної f на границю подвійного слова (4 байти), таким чином в результаті операцій вирівнювання для розміщення структури в оперативній пам’яті потрібно на 5 байт більше.
У зв’язку з цим доцільно рекомендувати при оголошенні структур і об’єднання розташовувати їх елементи у порядку зменшення довжини типів, тобто приведену вище структуру слід записати в наступному вигляді:
1struct {
2 double f;
3 int b;
4 char h;
5} str;
Мультиплікативні операції
До цього класу операцій відносяться операції множення *
, ділення /
і отримання остачі від ділення %
. Операндами операції %
повинні бути цілі числа. Відзначимо, що типи операндів операцій множення і ділення можуть відрізнятися, і для них справедливі правила перетворення типів. Типом результату є тип операндів після перетворення.
Операція множення *
виконує множення операндів.
1 int i=5;
2 float f=0.2;
3 double g,z;
4 g=f*i;
Тип добутку i
і f
перетвориться до типу double
, потім результат присвоюється змінній g
.
Операція ділення (/) виконує ділення першого операнда на другій. Якщо дві цілі величини не діляться без остачі, то результат округляється у бік нуля.
При спробі ділення на нуль видається повідомлення під час виконання.
1 int i=49, j=10, n, m;
2 n = i/j; /* результат 4 */
3 m = i/(-j); /* результат -4 */
Операція остача від ділення %
дає остача від ділення першого операнда на другий.
Знак результату залежить від конкретної реалізації. У даній реалізації знак результату співпадає із знаком діленого. Якщо другий операнд рівний нулю, то видається повідомлення.
1 int n = 49, m = 10, i, j, k, l;
2 i = n % m; /* 9 */
3 j = n % (-m); /* 9 */
4 k = (-n) % m; /* -9 */
5 l = (-n) % (-m); /* -9 */
Адитивні операції
До адитивних операцій відносяться додавання +
і віднімання -
. Операнди можуть бути цілого або плаваючого типів. В деяких випадках над операндами адитивних операцій виконуються загальні арифметичні перетворення. Проте перетворення, виконувані при адитивних операціях, не забезпечують обробку ситуацій переповнювання і втрати значення. Інформація втрачається, якщо результат адитивної операції не може бути представлений типом операндів після перетворення. При цьому повідомлення про помилку не видається.
Приклад:
1 int i=30000, j=30000, k;
2 k=i+j;
В результаті складання до набуде значення рівне -5536.
Результатом виконання операції складання є сума двох операндів. Операнди можуть бути цілого або плаваючого типу або один операнд може бути покажчиком, а другий - цілою величиною.
Коли ціла величина складається з покажчиком, то ціла величина перетвориться шляхом множення її на розмір пам’яті, займаної величиною, що адресується покажчиком.
Коли перетворена ціла величина складається з величиною покажчика, то результатом є покажчик, що адресує елемент пам’яті, розташовану на цілу величину далі від початкової адреси. Нове значення покажчика адресує той же самий тип даних, що і початковий покажчик.
Операція віднімання -
віднімає другий операнд з першого. Можлива наступна комбінація операндів:
Обидва операнди цілого або плаваючого типу.
- Обидва операнди є покажчиками на один і той же тип.
- Перший операнд є покажчиком, а другий - цілим.
Відзначимо, що операції складання і віднімання над адресами в одиницях, відмінних від довжини типу, можуть привести до непередбачуваних результатів.
Приклад:
1 double d[10],* u;
2 int i;
3 u = d+2; /* u вказує на третій елемент масиву */
4 i = u-d; /* i приймає значення рівне 2 */
Операції зсуву
Операції зсуву здійснюють зсув операнда вліво <<
або вправо >>
на число бітів, що задається другим операндом. Обидва операнди повинні бути цілими величинами. Виконуються звичні арифметичні перетворення. При зсуві вліво праві біти, що звільняються, встановлюються в нуль. При зсуві вправо метод заповнення лівих бітів, що звільняються, залежить від типу першого операнда. Якщо тип unsigned, то вільні ліві біти встановлюються в нуль. Інакше вони заповнюються копією знакового біта. Результат операції зсуву не визначений, якщо другий операнд негативний.
Перетворення, виконані операціями зсуву, не забезпечують обробку ситуацій переповнювання і втрати значущості. Інформація втрачається, якщо результат операції зсуву не може бути представлений типом першого операнда, після перетворення.
Відзначимо, що зсув вліво відповідає множенню першого операнда на степінь числа 2, рівну другому операнду, а зсув вправо відповідає розподілу першого операнда на 2 в степені, рівному другому операнду.
Приклади:
1 int i=0x1234, j, k ;
2 k = i << 4 ; /* k = 0x0234 */
3 j = i << 8 ; /* j = 0x3400 */
4 i = j >> 8 ; /* i = 0x0034 */
Порозрядні операції
До порозрядних операцій відносяться: операція порозрядного логічного “І” &
, операція порозрядного логічного “АБО” |
, операція порозрядного “виключаюче АБО” ^
.
Операнди порозрядних операцій можуть бути будь-якого цілого типу. При необхідності над операндами виконуються перетворення за замовчуванням, тип результату - це тип операндів після перетворення.
Операція порозрядного логічного І &
порівнює кожен біт першого операнда з відповідним бітом другого операнда. Якщо обидва порівнювані біти одиниці, то відповідний біт результату встановлюється в 1, інакше в 0.
Операція порозрядного логічного АБО |
порівнює кожен біт першого операнда з відповідним бітом другого операнда. Якщо будь-який (або обидва) з порівнюваних бітів рівний 1, то відповідний біт результату встановлюється в 1, інакше результуючий біт рівний 0.
Операція порозрядного виключаюче АБО ^
порівнює кожен біт першого операнда з відповідними бітами другого операнда. Якщо один з порівнюваних бітів рівний 0, а другий біт рівний 1, то відповідний біт результату встановлюється в 1, інакше, тобто коли обидва біти рівні 1 або 0, біт результату встановлюється в 0.
Приклад.
1 int i=0x45FF, /* i= 0100 0101 1111 1111 */
2 j=0x00FF; /* j= 0000 0000 1111 1111 */
3 char r;
4 r = i^j; /* r=0x4500 = 0100 0101 0000 0000 */
5 r = i|j; /* r=0x45FF = 0100 0101 0000 0000 */
6 r = i&j /* r=0x00FF = 0000 0000 1111 1111 */
Логічні операції
До логічних операцій відносяться операція логічного І &&
і операція логічного АБО ||
. Операнди логічних операцій можуть бути цілого типу, плаваючого типу або типу покажчика, при цьому в кожній операції можуть брати участь операнди різних типів.
Операнди логічних виразів обчислюються зліва направо. Якщо значення першого операнда достатньо, щоб визначити результат операції, то другий операнд не обчислюється.
Логічні операції не викликають стандартних арифметичних перетворень. Вони оцінюють кожен операнд з погляду його еквівалентності нулю. Результатом логічної операції є 0 або 1, тип результату int.
Операція логічного І &&
повертає значення 1, якщо обидва операнди мають нульові значення. Якщо один з операндів рівний 0, то результат також рівний 0. Якщо значення першого операнда рівне 0, то другий операнд не обчислюється.
Операція логічного АБО ||
виконує над операндами операцію включаючого АБО. Вона виробляє значення 0, якщо обидва операнди мають значення 0, якщо який-небудь з операндів має ненульове значення, то результат операції рівний 1. Якщо перший операнд має ненульове значення, то другий операнд не обчислюється.
Операція послідовного обчислення
Операція послідовного обчислення позначається комою ,
і використовується для обчислення двох і більш виразів там, де по синтаксису допустимий тільки один вираз. Ця операція обчислює два операнди зліва направо. При виконанні операції послідовного обчислення, перетворення типів не виробляється. Операнди можуть бути будь-яких типів. Результат операції має значення і тип другого операнда. Відзначимо, що кома може використовуватися також як символ роздільник, тому необхідно по контексту розрізняти, кому, використовувану як роздільник або знак операції.
Умовна операція
У мові Cі є одна тернарна операція - умовна операція, яка має наступний формат:
1операнд-1 ? операнд-2 : операнд-3**
Операнд-1
повинен бути цілого або плаваючого типу або бути покажчиком. Він оцінюється з погляду його еквівалентності 0. Якщо операнд-1 не рівний 0, то обчислюється операнд-2
і його значення є результатом операції. Якщо операнд-1
рівний 0, то обчислюється операнд-3
і його значення є результатом операції. Слід зазначити, що обчислюється або операнд-2
, або операнд-3
, але не обидва. Тип результату залежить від типів операнда-2
і операнда-3
, таким чином.
Якщо операнд-2
або операнд-3
має цілий або плаваючий тип (відзначимо, що їх типи можуть відрізнятися), то виконуються звичні арифметичні перетворення. Типом результату є тип операнда після перетворення.
- Якщо
операнд-2
іоперанд-3
мають один і той же тип структури, об’єднання або покажчика, то тип результату буде тим же самим типом структури, об’єднання або покажчика. - Якщо обидва операнди мають тип
void
, то результат має типvoid
. - Якщо один операнд є покажчиком на об’єкт будь-якого типу, а інший операнд є покажчиком на
vold
, то покажчик на об’єкт перетвориться до покажчика наvold
, який і буде типом результату. - Якщо один з операндів є покажчиком, а інший константним виразом із значенням 0, то типом результату буде тип покажчика.
Приклад:
1 max = (d<=b) ? b : d;
Змінній max присвоюється максимальне значення змінних d і b.
Операції збільшення і зменшення
Операції збільшення ++
і зменшення --
є унарними операціями присвоєння. Вони відповідно збільшують або зменшують значення операнда на одиницю. Операнд може бути цілого або плаваючого типу або типу покажчик і повинен бути тим, що модифікується. Операнд цілого або плаваючого типу збільшуються (зменшуються) на одиницю. Тип результату відповідає типу операнда. Операнд адресного типу збільшується або зменшується на розмір об’єкту, який він адресує. У мові допускається префіксна або постфіксна форми операцій збільшення (зменшення), тому значення виразу, що використовує операції збільшення (зменшення) залежить від того, яка з форм вказаних операцій використовується.
Якщо знак операції стоїть перед операндом (префіксна форма запису), то зміна операнда відбувається до його використання у виразі і результатом операції є збільшене або зменшене значення операнда.
В тому разі якщо знак операції стоїть після операнда (постфіксна форма запису), то операнд спочатку використовується для обчислення виразу, а потім відбувається зміна операнда.
Приклади:
1 int t=1, s=2, z, f;
2 z= t++ * 5;
Спочатку відбувається множення t*5
, а потім збільшення t
. В результаті вийде z=5
, t=2
.
1 f= ++s / 3;
Спочатку значення s збільшується, а потім використовується в операції розподілу. В результаті одержимо s=3
, f=1
.
У випадку, якщо операції збільшення і зменшення використовуються як самостійні оператори, префіксна і постфіксна форми запису стають еквівалентними.
1z++; /* еквівалентне \*/ ++z;
Просте присвоєння
Операція простого присвоєння використовується для заміни значення лівого операнда, значенням правого операнда. При присвоєнні виробляється перетворення типу правого операнда до типу лівого операнда за правилами, згаданими раніше. Лівий операнд повинен бути тим, що модифікується.
Приклад:
1int t;
2char f;
3long z;
4t = f + z;
Значення змінної f перетвориться до типу long, обчислюється f+z, результат перетвориться до типу int і потім присвоюється змінній t.
Складне присвоєння
Окрім простого присвоєння, є ціла група операцій присвоєння, які об’єднують просте присвоєння з однією з бінарних операцій. Такі операції називаються складовими операціями присвоєння і мають вигляд:
1(операнд-1) (бінарна операція) = (операнд-2) .
Складове присвоєння по результату еквівалентно наступному простому присвоєнню:
1(операнд-1) = (операнд-1) (бінарне операція) (операнд-2) .
Відзначимо, що вираз складового присвоєння з погляду реалізації не еквівалентний простому присвоєнню, оскільки в останньому операнд-1 обчислюється двічі.
Кожна операція складового присвоєння виконує перетворення, які здійснюються відповідною бінарною операцією. Лівим операндом операцій (+=) (-=) може бути покажчик, тоді як правий операнд повинен бути цілим числом.
Приклади:
1 double arr[4] = { 2.0, 3.3, 5.2, 7.5 };
2 double b=3.0;
3 b += arr[2]; /* еквівалентне b=b+arr[2] */
4 arr[3] /= b+1; /* еквівалентне arr[3]=arr[3]/(b+1) */
Помітимо, що при другому присвоєнні використання складового присвоєння дає помітніший виграш в чаCі виконання, оскільки лівий операнд є індексним виразом.
Пріоритети операцій і порядок обчислень
У мові Cі операції з вищими пріоритетами обчислюються першими. Щонайвищим пріоритетом є пріоритет рівний 1. Пріоритети і порядок операцій приведені в табл. 8.
Таблиця 8
Пріоритет | Знак операції | Типи операції | Порядок виконання |
---|---|---|---|
2 | () [] . -> | Вираз | Зліва направо |
1 | - ~ ! * & ++ -- sizeof приведення типів | Унарні | Справа наліво |
3 | * / % | Мультиплікативні | Зліва направо |
4 | + - | Адитивні | |
5 | << >> | Зсув | |
6 | < > <= >= | Відношення | |
7 | == != | Відношення (рівність) | |
8 | & | Порозрядне І | |
9 | ^ | Порозрядне виключаюче АБО | |
10 | | | Порозрядне АБО | |
11 | && | Логічне І | |
12 | || | Логічне АБО | |
13 | ? : | Умовна | |
14 | = *= /= %= += -= &= |= >>= <<= ^= | Просте і складове присвоєння | Справа наліво |
15 | , | Послідовне обчислення | Зліва направо |
Побічні ефекти
Операції присвоєння в складних виразах можуть викликати побічні ефекти, оскільки вони змінюють значення змінної. Побічний ефект може виникати і при виклику функції, якщо він містить пряме або непряме присвоєння (через покажчик). Це пов’язано з тим, що аргументи функції можуть обчислюватися у будь-якому порядку. Наприклад, побічний ефект має місце в наступному виклику функції:
1prog (а, a = k*2);
Залежно від того, який аргумент обчислюється першим, у функцію можуть бути передані різні значення.
Порядок обчислення операндів деяких операцій залежить від реалізації і тому можуть виникати різні побічні ефекти, якщо в одному з операндів використовується операції збільшення або зменшення, а також інші операції присвоєння.
Наприклад, вираз i*j+(j++)+(--i)
може приймати різні значення при обробці різними компіляторами. Щоб уникнути непорозумінь при виконанні побічних ефектів необхідно дотримуватися наступних правил.
- Не використовувати операції присвоєння змінної у виклику функції, якщо ця змінна бере участь у формуванні інших аргументів функції.
- Не використовувати операції присвоєння змінної у виразі, якщо ця змінна використовується у виразі більше одного разу.
Перетворення типів
При виконанні операцій відбуваються неявні перетворення типів в наступних випадках:
- при виконанні операцій здійснюються звичні арифметичні перетворення (які були розглянуті вище);
- при виконанні операцій присвоєння, якщо значення одного типу присвоюється змінній іншого типу;
- при передачі аргументів функції.
Крім того, в Cі є можливість явного приведення значення одного типу до іншого.
У операціях присвоєння тип значення, яке присвоюється, перетвориться до типу змінної, одержуючої це значення. Допускається перетворення цілих і плаваючих типів, навіть якщо таке перетворення веде до втрати інформації.
Перетворення цілих типів із знаком. Ціле із знаком перетвориться до коротшого цілого із знаком, за допомогою уCікання старших бітів. Ціле із знаком перетвориться до довшого цілого із знаком, шляхом розмноження знаку. При перетворенні цілого із знаком до цілого без знаку, ціле із знаком перетвориться до розміру цілого без знаку і результат розглядається як значення без знаку.
Перетворення цілого із знаком до плаваючого типу відбувається без втрати інформації, за винятком випадку перетворення значення типу long int або unsigned long int до типу float, коли точність часто може бути втрачена.
Перетворення цілих типів без знаку. Ціле без знаку перетвориться до коротшого цілого без знаку або із знаком шляхом уCікання старших бітів. Ціле без знаку перетвориться до довшого цілого без знаку або із знаком шляхом доповнення нулів зліва.
Коли ціле без знаку перетвориться до цілого із знаком того ж розміру, бітове уявлення не змінюється. Тому значення, яке воно представляє, змінюється, якщо знаковий біт встановлений (рівний 1), тобто коли початкове ціле без знаку більш ніж максимальне позитивне ціле із знаком, такої ж довжини.
Цілі значення без знаку перетворяться до плаваючого типу, шляхом перетворення цілого без знаку до значення типу signed long, а потім значення signed long перетвориться в плаваючий тип. Перетворення з unsigned long до типу float, double або long double виробляються з втратою інформації, якщо перетворюване значення більше, ніж максимальне позитивне значення, яке може бути представлене для типу long.
Перетворення плаваючих типів. Величини типу float перетворяться до типу double без зміни значення. Величини double і long double перетворяться до float з деякою втратою точності. Якщо значення дуже велике для float, то відбувається втрата значущості, про що повідомляється під час виконання.
При перетворенні величини з плаваючою крапкою до цілих типів вона спочатку перетвориться до типу long (дробова частина плаваючої величини при цьому відкидається), а потім величина типу long перетвориться до необхідного цілого типу. Якщо значення дуже велике для long, то результат перетворення не визначений.
Перетворення з float, double або long double до типу unsigned long виробляється з втратою точності, якщо перетворюване значення більше, ніж максимально можливе позитивне значення, представлене типом long.
Перетворення типів покажчика. Покажчик на величину одного типу може бути перетворений до покажчика на величину іншого типу. Проте результат може бути не визначений через відмінності у вимогах до вирівнювання і розмірах для різних типів.
Покажчик на тип void може бути перетворений до покажчика на будь-який тип, і покажчик на будь-який тип може бути перетворений до покажчика на тип void без обмежень. Значення покажчика може бути перетворене до цілої величини. Метод перетворення залежить від розміру покажчика і розміру цілого типу таким чином:
-
якщо розмір покажчика менший розміру цілого типу або рівний йому, то покажчик перетвориться точно так, як і ціле без знаку;
-
якщо покажчик більший, ніж розмір цілого типу, то покажчик спочатку перетвориться до покажчика з тим же розміром, що і цілий тип, і потім перетвориться до цілого типу.
Цілий тип може бути перетворений до адресного типу за наступними правилами:
-
якщо цілий тип того ж розміру, що і покажчик, то ціла величина просто розглядається як покажчик (ціле без знаку);
-
якщо розмір цілого типу відмінний від розміру покажчика, то цілий тип спочатку перетвориться до розміру покажчика (використовуються способи перетворення, описані вище), а потім набуте значення трактується як покажчик.
Перетворення при виклику функції. Перетворення, виконувані над аргументами при виклику функції, залежать від того, чи був заданий прототип функції (оголошення “вперед”) із списком оголошень типів аргументів.
Якщо заданий прототип функції і він включає оголошення типів аргументів, то над аргументами у виклику функції виконуються тільки звичні арифметичні перетворення.
Ці перетворення виконуються незалежно для кожного аргументу. Величини типу float перетворяться до double, величини типу char і short перетворяться до int, величини типів unsigned char і unsigned short перетворяться до unsigned int. Можуть бути також виконані неявні перетворення змінних типу покажчик. Задаючи прототипи функцій, можна перевизначити ці неявні перетворення і дозволити компілятору виконати контроль типів.
Перетворення при приведенні типів. Явне перетворення типів може бути здійснене за допомогою операції приведення типів, яка має формат:
1 ( ім'я-типу ) операнд .
У приведеному запиcі ім'я-типу
задає тип, до якого повинен бути перетворений операнд.
Приклад:
1int i=2;
2long l=2;
3double d;
4float f;
5d=(double) i * (double)l;
6f=(float) d;
У даному прикладі величини i,l,d явно перетворюватимуться до вказаних в круглих дужках типів.
Коментарі