C++. Поняття виразу. Операція присвоєння. Перетворення та приведення типів




Поняття виразу. Операція присвоєння =. Особливості перетворення та приведення типів. Ініціалізація. Уніфікована ініціалізація


Зміст


Пошук на інших ресурсах:

1. Що називається виразом у мовах програмування?

Вирази – це поєднання операторів, літералів та змінних. Вирази використовуються для проведення обчислень, деяких дій тощо. У виразах можуть використовуватись імена функцій.

Приклади виразів.

a*8+5
Math::Sqrt(x)+Math::Sin(x+2)
sigma + gamma/20.0

 

2. Який загальний вигляд операції присвоєння? Приклади

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

У мові C/C++ операція присвоєння позначається символом = (дорівнює).

Загальний вигляд операції присвоєння у поєднанні з виразом:

змінна = вираз;

де

  • змінна – ім‘я змінної, якій присвоюється значення виразу;
  • вираз – деякий вираз, що може бути використаний згідно синтаксису мови C/C++.

Приклади операції присвоєння.

a = 8;
b = a + Math::Sqrt(x)+Math::Sin(x+2);
sigma = (double) (2.0 / b);

 

3. Як використовується операція присвоєння при ініціалізації змінних? Приклади

Загальний вигляд операції присвоєння при ініціалізації змінних:

тип ім‘я_змінної = вираз_або_значення;

де

  • тип – тип змінної, що ініціалізується;
  • вираз_або_значення – значення виразу або константна величина. Якщо використовується значення виразу, то змінні, що входять у цей вираз мають бути вже оголошені на даний момент (див. приклад).

Приклади ініціалізації змінних.

...
// Ініціалізація змінних
int a = 8, b = 25;
double c = 3.550093;
bool f = false;
char sym = 'A';

// Ініціалізація змінних з виразом
float x = 3.5;
float y = x + 2.8;
float z = x*x + y*y*y;
float zz = Math::Sqrt(z+5.0);

...

 

4. Поняття перетворення типу. Класифікації перетворень типів

Часто у виразах фігурують змінні різних типів. У таких випадках компілятор виконує так зване приведення або перетворення одного типу в інший. У C++ існують наступні класифікації перетворень типів:

  • класифікація за діапазоном значень;
  • класифікація за способом здійснення перетворення.

 

5. Класифікація за діапазоном значень при перетворенні типів

При класифікації за діапазоном значень розрізняють:

  • звужуюче перетворення типів. У цьому випадку більший тип даних звужується до меншого (звужується діапазон значень). Тут може виникнути втрата даних;
  • розширювальне перетворення типів. При такому перетворенні менший тип даних розширюється до більшого типу даних (розширюється діапазон значень).

Нижченаведені приклади демонструють ці два види перетворень.

Приклад 1. Демонструється звужуюче перетворення даних.

// 1. Звужуюче перетворення даних, що приводить до втрати даних
// При ініціалізації змінної
int d = 5.85; // тип double звужується до типу int, d = 5
cout << "d = " << d << endl; // d = 5 - відбувається втрата даних

// При присвоєнні у виразі
int k;
k = 2.3 + 0.5 + 0.1; // тип double звужується до типу int
cout << "k = " << k << endl; // k = 2 - відбувається втрата даних

// 2. Звужуюче перетворення без втрати даних
short f = 2000; // тип int звужується до типу short
cout << "f = " << f << endl; // f = 2000

Як видно з прикладу, при звужуючому перетворенні типів можлива втрата даних.

Приклад 2. Демонструється розширювальне перетворення типів.

// Розширювальне перетворення даних
unsigned int t = 4000000000; // int розширюється до unsigned int без помилок
cout << "t = " << t << endl;

У прикладі значення 4000000000 має тип int. Це значення коректно розширюється до типу unsigned int.

Якщо тип змінної t вказати як int

int t = 4000000000; // число 4000000000 не поміститься в тип int, t = -294967296 - втрата даних

то значення t буде рівне -294967296. Це означає, що число 4000000000 не поміститься в тип int.

 

6. Класифікація за способом здійснення перетворення. Неявне і явне перетворення типів

В залежності від напрямку перетворення, це перетворення може бути реалізоване одним з двох способів:

  • неявне перетворення. Це перетворення ще називають автоматичним. Неявне перетворення компілятор виконує самостійно без втручання програміста;
  • явне перетворення або приведення типів. У даному випадку в дужках вписується тип перетворення.

 Приклад.

// 1. Неявне перетворення типів
int a = 25;
double x;
x = a; // неявне перетворення int => double

float y = 78.3; // неявне перетворення double => float

// 2. Явне перетворення типів
int b;
b = (int)8.6; // b = 8, явне перетворення double => int

float z = (float)12.55; // явне перетворення double => float

 

7. Правила перетворення типів у виразах

Якщо у виразі зустрічаються два операнди різних типів, то діють наступні правила:

  • усі операнди перетворюються до типу самого найбільшого операнду. Процес такого перетворення називається розширенням типу (integral promotion);
  • усі типи char та short int перетворюються до типу int. Процес такого перетворення називається цілочисельним розширенням (integer promotion);
  • якщо один з операндів має тип double, тоді будь-який інший операнд приводиться до типу double. Навіть, у випадку з типом char, відбувається приведення до типу double;
  • після перетворення обидва операнди мають однаковий тип, який є типом результату операції.





Нижче наведено приклади автоматичного перетворення типів.

Перетворення між типами char та int:

char c;
int d;

c = 'A';
d = c; // d = 65

d = 67;
c = d; // c = 'C'

Перетворення між типами int та float:

int d = 28;
float x;

x = d;       // x = 28.0 - тип float
d = 5.0 + 5; // d = 10 - тип int

Перетворення між типами float і double

float f;
double d;
int size;

f = 2.54f;
d = f; // d = 2.54 - типу double
d = 2.0f + 8.5; // результат типу double

 

8. Як здійснюються перетворення, що зв‘язані з типом bool?

Якщо вираз містить цілочисельний тип, то значення типу bool автоматично перетворюються в цілі числа 0 та 1. Значенню 0 відповідає значення false. Значенню 1 або ненульовому значенню відповідає значення true.

Приклад. Фрагмент коду, що демонструє перетворення для типу bool

bool b;
int a;

a = 0;
b = a; // b = False

a = 1;
b = a; // b = True

a = 50;
b = a; // b = True

 

9. Який загальний вигляд операції явного приведення типу (type)?

Загальний вигляд операції явного приведення типу:

(тип) вираз

де

тип – тип, до якого потрібно привести результат обчислення виразу.

Приклади використання операції приведення типів.

int a;
float x;

a = 5;
x = a/2; // x = 2.0
x = (float)(a/2); // x = 2.0
x = (float)a/2; // x = 2.5 - типу float

x = a/2.0; // x = 2.5 - типу float

x = (int) (8/3.0);   // x = 2
x = (float) (8/3.0); // x = 2.666667

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

 

10. Що таке уніфікована (спискова) ініціалізація? Приклад

Уніфікована ініціалізація ще називається списковою ініціалізацією. Цей вид ініціалізації змінної був доданий починаючи з версії C++ 11. З допомогою уніфікованої ініціалізації можна задати значення змінним, масивам, об’єктам єдиноподібним способом.

Синтаксично уніфікована ініціалізація може бути реалізована одним з двох способів:

1. З використанням оператора присвоювання (=). У цьому випадку ініціалізація має вигляд:

type varName = { value };

2. Без використання оператора присвоювання. Загальний вигляд такої ініціалізації наступний:

type varName { value };

У вищенаведених формах:

  • type – тип даних, яким може бути базовий тип (int, double, …), клас, структура, зчислення тощо;
  • varName – ім’я змінної, яка ініціалізується;
  • value – значення, що присвоюється змінній varName.

Приклад уніфікованої ініціалізації змінних різних видів.

#include <iostream>
using namespace std;

// Структура Point
struct Point
{
  int x;
  int y;
};

// Клас Book
class Book
{
public:
  string title;
  float price;
};

void main()
{
  // Уніфікована (спискова) ініціалізація
  // 1. Для одиночної змінної різних типів
  int i = { 25 };
  double x { 7.88 };
  float f = { -13.23 };
  char c { '+' };
  string s { "Hello world" };
  bool b = { true };

  // 2. Для масиву
  int A[5] { 33 }; // A = { 33, 0, 0, 0, 0 }

  // 3. Для структури Point
  Point pt{ 2, -3 }; // pt.x = 2; pt.y = -3

  // 4. Для об'єкту класу Book
  Book B{ "The title of book", 99.99f };
}

 

11. Як працює уніфікована (спискова) ініціалізація у випадку перетворення ширшого типу до вужчого? Для чого потрібна уніфікована ініціалізація?

Уніфікована ініціалізація корисна, коли потрібно реалізувати перевірку коректного приведення типів під час ініціалізації. Уніфікована ініціалізація виявляє помилки на етапі компіляції у випадку, якщо відбувається втрата даних. Втрата даних може виникнути у випадку неявного звужуючого перетворення типів.

При уніфікованій ініціалізації компілятор генерує помилку:

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

При звичайній ініціалізації (не уніфікованій) компілятор допускає втрату даних і не генерує помилку.

Нижченаведені приклади демонструють призначення уніфікованої ініціалізації.

Приклад 1. Нехай задано змінну типу int, яку потрібно ініціалізувати значенням типу double звичайним способом ініціалізації

int d = 7.88; // d = 7
cout << "d = " << d << endl; // d = 7

У цьому випадку відбудеться неявне звужуюче перетворення, оскільки змінній типу int присвоюється значення типу double. У результаті, значення змінної d буде рівне 7.

Якщо ініціалізувати змінну d уніфікованим способом ініціалізації

int d = { 7.88 }; // помилка!!!

то компілятор видасть помилку на етапі компіляції

conversion from double to int requires a narrowing conversion

Приклад 2. У цьому прикладі показано, що уніфікована ініціалізація дозволяє виявляти помилки втрати даних для типів, що є сумісними. Нехай задано змінну типу short, яка ініціалізується значенням типу int, яке виходить за допустимий діапазон типу short

// Звичайна ініціалізація
short t = 200000000; // переповнення, помилки компіляції немає, t = -15872

Тут компілятор помилки не генерує, відбувається звичайне переповнення і значення 200000000 урізається до типу short.

Якщо ініціалізувати змінну t уніфікованим способом

// Уніфікована ініціалізація
short t{ 200000000 }; // переповнення, помилка компіляції

то виникне помилка на етапі компіляції з повідомленням

conversion from int to short requires a narrowing conversion

 

12. Які особливості застосування круглих дужок та символів “пробіл” у виразах?

Щоб покращити читабельність, у програмах використовуються:

  • символи “пробіл”;
  • символи табуляції (клавіша Tab);
  • круглі дужки ( ). Круглі дужки дозволяють також підвищити пріоритет операцій, що містяться в них. Кількість круглих дужок не впливають на швидкість обчислення виразу.

 


Зв’язані теми