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

C Урок 14. Преобразование типов

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

На данном уроке мы поговорим о преобразовании типов данных в процессе работы программы из одного типа в другой.

Конечно, в идеальном случае, желательно, чтобы программа была построена таким образом чтобы лишний раз избегать всякого рода преобразований и использовать везде данные нужного типа. Но не всегда так получается и преобразование типа в ряде случаев просто необходимо.

Например, мы складываем значения двух переменных типа unsigned short и, по идее у нас результат тоже должен присваиваться переменной типа unsigned short. Но мы не уверены в том, что этот результат уместится в такой тип, например, мы захотим сложить числа 65535 и 65534.

Поэтому здесь без преобразования не обойтись. И таких ситуаций огромное множество, поэтому мы должны знать как происходит преобразование типов автоматически, а также как мы можем этим процессом управлять.

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

Для начала давайте начнём с автоматического преобразования типов, или, если это правильно назвать, с неявного приведения типов. В данном случае преобразованием типов будет управлять компилятор и мы никаких специальных операций для данного преобразования не применяем.

Если операнды некоторой операции принадлежат различным типам, то они автоматически приводятся к определённому общему типу. А к какому именно, существует ряд правил.

Также надо учесть, что автоматические преобразования типов имеют место лишь в тех случаях, когда они не влекут потери данных и при этом превращают операнды с меньшим диапазоном значений в операнды с большим диапазоном.

Например, если мы складываем операнд целого типа с операндом с плавающей точкой, то первый автоматически приведётся к типу с плавающей точкой.

Или, если мы складываем операнд типа int с оператором типа short, то второй автоматически превратится в число типа int, так как у него больший диапазон значений. Тем самым достигается принцип целостности информации.

Если же результат наших операций мы попытаемся присвоить переменной такого типа, у которого меньший диапазон значений, то мы рискуем потерять часть информации, хотя при этом код все равно соберётся и мы получим лишь предупреждение.

Также неявное преобразование операндов какой-либо операции происходит в том случае, когда от этой операции есть смысл. А если мы, к примеру, захотим использовать тип с плавающей точкой в качестве номера элемента массива, то в таком случае мы уже получим ошибку.

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

  • long double
  • double;
  • float;
  • unsigned long long;
  • long long;
  • unsigned long;
  • long;
  • unsigned int;
  • int

Если в операции присутствуют операнды различных типов то компилятор вычисляет сначала операнд наивысшим приоритетом и неявно преобразовывает тип другого операнда, у которого приоритет ниже, к типу первого.

В операциях присваивания также может происходить неявное преобразование типов данных.

Например, если мы хотим значение 8-байтового типа присвоить переменной 4-байтового типа, неважно, целого типа эти данные или нет, то при этом произойдёт неявное преобразование к 4-байтовому типу, при этом старшие 4 байта отбрасываются. Это считается небезопасным приведением типов, когда возможна потеря информации.

Может быть и наоборот, если мы хотим значение 4-байтового типа присвоить переменной 8-байтового типа. Также произойдёт неявное преобразование и, наоборот, 4 старших байта добавятся и заполнятся нулями. Это будет уже безопасное приведение типа и при этом целостность информации не страдает.

Так как мы с указателями ещё не работали, то в данном уроке мы их преобразование рассматривать не будем, скажу лишь, что там всё происходит аналогично, разница в поведении указателей типа void.

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

Явное преобразование типов происходит тогда, когда мы применяем специальные механизмы для приведения одного типа к строго определённому другому типу. В языке C в для явного приведения типа перед переменной или выражением, значения которых мы преобразуем к другому типу, ставится в круглых скобках тип, к которому мы данное значение преобразуем, например

int i;

char c = 45;

i = (int)c;

В данном случае значение переменной c явно преобразовывается к типу данных int, а затем уже в преобразованном виде присваивается переменной i.

Вот ещё несколько примеров явного преобразования типов данных

sqrt((double) n)

float f_res = (float) i / (float) j;

double f_res = (double)((float) i / (float) j);

Давайте теперь поэкспериментируем с преобразованием типов данных на практике в реальном коде.

Проект сделаем из проекта MYPROG13прошлого занятия и имя ему было присвоено MYPROG14.

Откроем файл main.c и в функции main(), как обычно, удалим весь код тела кроме возврата нуля, останется от него вот это

int main()

{

  return 0; //Return an integer from a function

}

Удалим также вот эти константы

#define VAR_CONST1 12345

#define HELLO_CONST «\»Hello, world!!!\»»

Добавим в тело функции main() следующий код

int main() { char c = 54; int i = c; printf(«Value is %d», i);

int main(){  char c = 54;  int i = c;  printf(«Value is %d», i);

В данном коже мы выведем значение переменной i, которая в свою очередь сначала получит значение переменной c, у которой тип имеет меньший диапазон.

В этом случае должно будет до присвоения произойти неявное преобразование значения переменной c в тип int и уж затем преобразованное значение присвоится нашей переменной c.

Как мы знаем, преобразование значения типа, имеющего меньший диапазон в тип, имеющий больший диапазон, является безопасным и потери данных за собой не влечёт.

Проверим работу нашего кода

Всё сработало и значение не пострадало.

Теперь закомментируем наш предыдущий код и напишем вот такой

int i = 1000000; char c = i; printf(«Value is %d», c);

  int i = 1000000;  char c = i;  printf(«Value is %d», c);

Думаю, что тут и без теории понятно, что миллион не влезет в диапазон типа данных char. Тем не менее соберём код и посмотрим результат

Мы видим, что результат сильно изменился. Почему же именно 64?

Попробуем наш миллион перевести в шестнадцатеричный вид, получится 0x000F4240. Каждые два разряда данного числа — это один байт. Такие разряды байта также называют тетрадами.

Тип char — это у нас однобайтовое знаковое целое, поэтому при преобразовании у нас от числа отбросятся 3 старших байта, а останется лишь один младший байт, а это 0x40, что в десятичном выражении и является нашим числом 64.

Данное преобразование, само собой является небезопасным, так как при его применении теряется часть информации.

Закомментируем данный код и добавим вот такой

int i = 8; int j = 3; float f_res = i / j; printf(«Result is %.10f», f_res);

  int i = 8;  int j = 3;  float f_res = i / j;  printf(«Result is %.10f», f_res);

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

Но не тут-то было. Сначала вычисляется выражение не смотря на то, какой переменной будет присваиваться его значение. операнды оба у нас целого типа, поэтому при делении у нас остаток отбросится, а уж потом при присваивании результата переменной вещественного типа произойдёт неявное приведение к данному типу. Но остаток уже отброшен и его не вернуть.

Проверим это

Так и есть, остаток потерян.

Чтобы нам наверняка его не потерять, мы должны сначала произвести явное приведение типов наших операндов, а уж потом их делить друг на друга.

Поэтому, закомментировав сначала предыдущий код, добавим вот такой вариант кода

int i = 8; int j = 3; float f_res = (float) i / (float) j; printf(«Result is %.10f», f_res);

  int i = 8;  int j = 3;  float f_res = (float) i / (float) j;  printf(«Result is %.10f», f_res);

Теперь другое дело

Причём, исходя из правил неявного приведения типов и представленного выше списка приоритетов, нам вовсе не обязательно приводить к типу с плавающей точкой оба операнда, достаточно привести только один, а второй тогда окажется приоритетом ниже и сам преобразуется к типу float.

Поэтому мы можем сделать либо так

int i = 8; int j = 3; float f_res = (float) i / j; printf(«Result is %.10f», f_res);

  int i = 8;  int j = 3;  float f_res = (float) i / j;  printf(«Result is %.10f», f_res);

либо так

int i = 8; int j = 3; float f_res = i / (float) j; printf(«Result is %.10f», f_res);

  int i = 8;  int j = 3;  float f_res = i / (float) j;  printf(«Result is %.10f», f_res);

Результат получится тот же самый

Ну думаю, в коде с явным и неявным преобразованием у нас что-то прояснилось. Интересно ещё поработать с функциями и как происходит преобразование их аргументов.

В случае использования функций все аргументы преобразовывают свой тип по-отдельности и друг от друга никак не зависят по сравнению с использованием обычных операторов.

Добавим вот такую функцию, которая будет делить значение оного аргумента с плавающей точкой на аналогичное значение другого и возвращать результат деления

float my_div(float a, float b) { return a/b; }

float my_div(float a, float b){  return a/b;}

Теперь закомментируем предыдущий код в функции main() и добавим вот такой

int i = 8; int j = 3; float f_res = my_div (i, j); printf(«Result is %.10f», f_res);

  int i = 8;  int j = 3;  float f_res = my_div (i, j);  printf(«Result is %.10f», f_res);

Мы можем испугаться потери информации, так как мы с этим встретились применяя обычный оператор деления, но если у нас функция, то этого не произойдёт.

Проверим это

Происходит это потому, что каждый аргумент преобразовывается из int в float отдельно. Поэтому в случае функций можно потери информации не бояться.

Для более полного закрепления материала давайте добавим ещё пару вариантов кода с преобразованиями.

Первый такой

//небезопасное преобразование типов — от большего типа к меньшему unsigned int i = 65535; unsigned char c = i; unsigned int j = c; printf(«Value is %u, %u, %u», i, c, j);

  //небезопасное преобразование типов — от большего типа к меньшему  unsigned int i = 65535;  unsigned char c = i;  unsigned int j = c;  printf(«Value is %u, %u, %u», i, c, j);

Источник: http://narodstream.ru/c-urok-14-preobrazovanie-tipov/

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

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

Определение 1

Преобразование типов в Си — это преобразование любого типа данных в любой другой тип. При таком преобразовании не имеет значения безопасность этой процедуры.

Если требуется поменять тип данных на иной, например, больший по размеру, то возможно использовать для этой цели неявный метод преобразования. Некоторые неопытные программисты иногда применяют такой вариант: float x = 11 / 3;.

Но поскольку и одиннадцать и три, это целочисленные величины, то никакие преобразования типов чисел выполняться не будет. Будет выполнена операция деления без остатка одиннадцать на три, что даст в итоге три. Тройка пройдёт преобразование в вид 3.

0 и это значение получит переменная х. То есть, если применяются числовые символы (например, одиннадцать или три), то подмена первого или пары целых чисел на значения с плавающей точкой (11.0 или 3.

0) ведёт к преобразованию их в значении вида с плавающей точкой и выполнению деления типа с плавающей точкой. Оптимально в таком случае использовать переменные.

Ничего непонятно?

Попробуй обратиться за помощью к преподавателям

К примеру, так:

1 int i1 = 11;

2 int i2 = 3;

3 float x = i1 / i2;

Переменная х примет в итоге значение три. Чтобы программа компиляции понимала, что программисту надо заменить деление целых чисел без остатка на деление типа с плавающей точкой, следует применять какой-либо оператор, преобразующий типы данных в явной форме.

Явное преобразование типов

В программном языке С++ имеется следующие типы операторов casts, выполняющих в явной форме изменение типов данных:

C-stylet;

static;

const;

C-style cast.

В программах на языке С изменение в явной форме типа данных осуществляется посредством специального обозначения (). Внутрь скобок заносится тип, в который необходимо преобразовать данные. Такой способ перемены типа данных носит название C-style cast. Применять можно, например, так:

1 int i1 = 11;

2 int i2 = 3;

3 float x = (float)i1 / i2;

В этом примере содержимое внутри скобок сообщает программе компиляции, что надо выполнить изменение переменной i1 (типа int) в тип float.

Так как i1 теперь имеет тип float, то i2 тоже будет автоматом преобразовано в такой же тип, и операция деления уже будет выполнена для типа данных с плавающей точкой.

Следует заметить, не стоит применять данный оператор для изменения данных типов const, так как это может привести к ошибкам.

Оператор static_cast

Этот оператор может применяться для конвертирования переменной типа char в тип int, при этом выводится целое число, а не символ:

Рисунок 1. Код. Автор24 — интернет-биржа студенческих работ

Этот оператор оптимально применять для преобразования фундаментальных типов(один в другой):

1 int i1 = 11;

2 int i2 = 3;

3 float x = static_cast< float > (i1) / i2;

Главным достоинством static_cast может считаться наличие проверки программой компиляции на ошибки, что предотвращает появление непредсказуемых проблем. Кроме того, оператор static_cast обладает меньшим влиянием, чем оператор C-style cast. Это исключает возможность случайного изменения типа const и другие бессмысленные операции.

Использование операторов явного преобразования в неявном преобразовании

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

1 int i = 49;

2 char ch = i; // неявное преобразование

Преобразование переменной типа int (4 байта) в тип char (1 байт) несёт потенциальную опасность, и компилятор об этом предупредит. Чтобы компилятор понял, что программист понимает потенциальную опасность операции, но её надо всё равно выполнить, следует применять оператор static_cast:

Рисунок 2. Код. Автор24 — интернет-биржа студенческих работ

В примере ниже, программа компиляции может выдать сообщение, что преобразование из double в int способно спровоцировать потерю данных:

1 int i = 90;

2 i = i / 3.6;

Для сообщения компилятору о сознательном намерении выполнить операцию, следует записать такие команды:

Рисунок 3. Код. Автор24 — интернет-биржа студенческих работ

Не следует использовать без крайней необходимости конвертацию типов данных. Почти всегда при выполнении этих преобразований существует вероятность появления различных проблем. Но если это всё-таки необходимо сделать, лучше применять static_cast вместо C-style cast.

Конвертирование типов в арифметических операциях

При использовании в арифметических операциях разных типы значений, программа компиляции будет пытаться в неявной форме преобразовать их в один тип. А так же, если программа задаёт переменной конкретное значение, оно всегда должно быть приведено к типу, присущему этой переменной. К примеру, так:

1 char c = 6;

2 int d = c;

Переменная d, представляющая тип int, получает значение типа char. По этой причине компилятор приведёт её значение от типа char к типу int.

Как отмечалось выше, такие преобразования могут нести потенциальную опасность, так как различные типы обладают различным внутренним представлением. И без потерь данных изменение типа представления не всегда можно осуществить.

Программа компиляции при арифметических операциях делает следующие преобразования:

  1. Когда одна из переменных обладает типом long double, то и вторая переменная должна быть приведена к этому типу.
  2. Когда пункт один не исполнен и одна из переменных обладает типом double, то вторая переменная так же должна быть приведена к типу double.
  3. Если не удалось выполнить пункт выше, и какая-либо переменная обладает типом long, то вторая переменная так же преобразуется в тип long.
  4. Если снова не произошло выполнение предыдущего пункта и одна из переменных является типа unsigned, то другая переменная так же преобразуется в тип unsigned.
  5. Когда не выполнен предыдущий пункт, обе переменные преобразуются к типу int.

Приведём пример:

1 int a = 10;

2 double b = 4;

3 double c = a + b; // 14.000000

В формуле a + b переменная b имеет тип double, по этой причине переменная a так же в автомате преобразуется к типу double. Итог арифметической операции сложения также станет типа double.

Источник: https://spravochnick.ru/informatika/preobrazovanie_tipov_v_si/

Booksm
Добавить комментарий