Как сделать неявное преобразование

Добавил пользователь Евгений Кузнецов
Обновлено: 05.10.2024

В программировании нередко значения переменных одного типа присваиваются переменным другого типа. Например, в приведенном ниже фрагменте кода целое значение типа int присваивается переменной с плавающей точкой типа float:

Автоматическое преобразование типов

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

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

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

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

Обратите внимание на то, что метод Sum() ожидает поступления двух параметров типа int. Тем не менее, в методе Main() ему на самом деле передаются две переменных типа short. Хотя это может показаться несоответствием типов, программа будет компилироваться и выполняться без ошибок и возвращать в результате, как и ожидалось, значение 25.

Причина, по которой компилятор будет считать данный код синтаксически корректным, связана с тем, что потеря данных здесь невозможна. Поскольку максимальное значение (32767), которое может содержать тип short, вполне вписывается в рамки диапазона типа int (максимальное значение которого составляет 2147483647), компилятор будет неявным образом расширять каждую переменную типа short до типа int. Формально термин "расширение" применяется для обозначения неявного восходящего приведения (upward cast), которое не приводит к потере данных.

Приведение несовместимых типов

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

Здесь целевой_тип обозначает тот тип, в который желательно преобразовать указанное выражение.

Если приведение типов приводит к сужающему преобразованию, то часть информации может быть потеряна. Например, в результате приведения типа long к типу int часть информации потеряется, если значение типа long окажется больше диапазона представления чисел для типа int, поскольку старшие разряды этого числового значения отбрасываются. Когда же значение с плавающей точкой приводится к целочисленному, то в результате усечения теряется дробная часть этого числового значения. Так, если присвоить значение 1,23 целочисленной переменной, то в результате в ней останется лишь целая часть исходного числа (1), а дробная его часть (0,23) будет потеряна. Давайте рассмотрим пример:

Результатом работы данной программы будет:

Обратите внимание, что переменная i1 корректно преобразовалась в тип short, т.к. ее значение входит в диапазон этого типа данных. Преобразование переменной dec в тип int вернуло целую часть этого числа. Преобразование переменной i2 вернуло значение переполнения 18964 (т.е. 84500 - 2*32768).

Перехват сужающих преобразований данных

По умолчанию, в случае, когда не предпринимается никаких соответствующих исправительных мер, условия переполнения (overflow) и потери значимости (underflow) происходят без выдачи ошибки. Обрабатывать условия переполнения и потери значимости в приложении можно двумя способами. Это можно делать вручную, полагаясь на свои знания и навыки в области программирования.

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

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

Результат работы данной программы:

Ключевое слово checked вынуждает CLR-среду генерировать исключения в случае потери данных

Настройка проверки на предмет возникновения условий переполнения в масштабах проекта

Для активизации этого флага в Visual Studio 2010 необходимо открыть страницу свойств проекта, перейти на вкладку Build (Построение), щелкнуть на кнопке Advanced (Дополнительно) и в открывшемся диалоговом окне отметить флажок Check for arithmetic overflow/underflow (Проверять арифметические переполнения и потери точности):

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

Роль класса System.Convert

В завершении темы преобразования типов данных стоит отметить, что в пространстве имен System имеется класс Convert, который тоже может применяться для расширения и сужения данных:

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

char - int , short int , long int (Преобразование к значению с большим числом двоичных разрядов может включать, а может не включать расширение знакового разряда - это зависит от реализации языка. Для элементов заданного набора знаков гарантируется преобразование в неотрицательные целые значения).

int - char , short int , long int ( преобразование к целому большей длины включает расширение знакового разряда. Преобразование к целому меньшей длины вызывает отбрасывание лишних старших разрядов). float , double , unsigned int ( интерпретация комбинации битов в виде беззнакового целого значения).

short int - аналогично типу int .

long int - аналогично типу int .

float - double , int , short int , long int (машинно-зависимое преобразование, если преобразуемое значение слишком велико, то результат неопределен).

double - float (преобразование с округлением и последующим отбрасыванием лишних разрядов), int , short int , long int .

Арифметические преобразования

Арифметические операторы языка Си преобразуют операнды к соответствующим типам автоматически, если операнды не имели таких типов с самого начала. Схема преобразования, используемая этими операторами, называется обычно арифметические преобразования ; эта схема может быть описана следующими правилами:

  1. Преобразовать операнды типов char и short int к типу int ; преобразовать операнды типа float к типу double .
  2. Если хотя бы один из операндов имеет тип double , то и другой операнд преобразуется к типу double (если он другого типа); результат имеет тип double .
  3. Если хотя бы один операнд имеет тип long , то и другой операнд преобразуется к типу long (если он другого типа); результат имеет тип long .
  4. Если хотя бы один из операндов имеет тип unsigned , то и другой операнд преобразуется к типу unsigned (если его тип не unsigned); результат имеет тип unsigned .
  5. Если ни один из случаев 1-4 не имеет места, то оба операнда должны иметь тип int ; такой же тип будет и у результата.

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

Выражения могут быть преобразованы из одного типа в другой явным указанием. Выражение E может быть явно преобразовано к типу имя-типа с помощью записи вида

где имя типа представляется в форме

Абстрактный описатель аналогичен описателю, за исключением того, что он не содержит определяемого или описываемого идентификатора. Смысл слов имя-типа, представляемого в форме

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

  • форма абстрактного описателя - смысл слов " Т абстрактный описатель ";
  • пустой ( абстрактный описатель ) - абстрактный описатель типа Т ;
  • * ( абстрактный описатель ) - указатель на тип Т ;
  • абстрактный описатель ( ) - функция, возвращающая значение типа Т ;
  • абстрактный описатель [ n ] - массив с n элементами типа Т , n - выражение с постоянным значением;

Приведем примеры явного преобразования. Предположим, что даны следующие определения и описания:

тогда можно привести следующие примеры явных преобразований типов:

(char) i - преобразует значение типа int в значение типа char .

pc=(char *) 0777 - преобразует восьмеричный литер 0777 в значение указателя на знак таким образом, что оно может быть присвоено переменной "pc" .

(emp *) calloc(1,sizeof(emp)) - преобразует значение "знакового" указателя, возвращаемого функцией calloc , в значение указателя emp .

(void) strcpy(name,"gehani") - опускает значение , возвращенное функцией strcpy .

Синтаксис типов

Можно отметить, что синтаксис типов в языке Си нерегулярен и беспорядочен, о чем свидетельствуют:


Преобразование типов это процесс конвертации значения из одного типа в другой (как например, строки в число, объекта к булевому значению и т. д.). Любой тип, будь то примитив или объект, может быть преобразован в другой тип. Для справки, примитивы это: number , string , boolean , null , undefined + Symbol (добавлен в ES6).

В качестве примера преобразования типов, можно ознакомиться с JavaScript Comparison Table, где продемонстрировано как ведёт себя оператор нестрогого сравнения == для разных типов a и b .

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

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

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

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

Преобразование типов может происходить явно и неявно.

Когда разработчик хочет намеренно произвести преобразование типов, написав, к примеру Number(value) , это называется явным преобразованием типов (или type casting).

Так как JavaScript это слабо типизированный язык, преобразование между разными типами может происходить автоматически, и это называется неявным преобразованием типов. Чаще всего это происходит когда вы применяете операторы к значениям разных типов, таких как 1 == null , 2 / `5` , null + new Date() , может происходить в зависимости от контекста, как например, в случае с if (value) , где value будет приведено к булевому значению.

Оператор строгого равенства === не приводит к неявному преобразованию типов. Оператор нестрогого равенства == , в свою очередь, производит сравнение операндов и, если требуется, неявное преобразование типов.

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

Три типа конвертации

Во-первых, следует знать, что в JavaScript существует всего 3 типа преобразования:

Во-вторых, логика преобразования для примитивов и объектов работает по-разному, но, и примитивы и объекты могут быть преобразованы только этими тремя способами.

Давайте сначала разберёмся с примитивами.

Приведение к строке

Для явного приведения значения к строке необходимо применить к нему функцию String() . Неявное преобразование будет вызвано бинарным оператором + , кода один из операндов является строкой:

Все примитивы будут приведены к строке вполне естественно, как вы могли и ожидать:

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

Булевое преобразование

Для явного преобразования к булевому значению, нужно применить функцию Boolean() . Неявное преобразование происходит в логическом контексте if (val) < … >или при применении логических операторов ( || && ! ).

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

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

Любое значение, которое не вошло в этот список, будет преобразовано в true , включая объекты, функции, Array , Date и так далее. Символы, пустые объекты и массивы так же будут иметь значение true .

Численное преобразование

Для явного преобразования к числу нужно применить функцию Number() , точно так же, как мы делали с Boolean() и String() .

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

  • операторы сравнения ( > , , , >= )
  • бинарные операторы ( | & ^ ~ )
  • арифметические операторы ( - + * / % ). Обратите внимание, что бинарный оператор + не вызывает численного преобразования, если один из операндов является строкой
  • унарный оператор +
  • оператор нестрогого равенства == (включая != ). Обратите внимание, что данный оператор не вызывает численное преобразование, если оба операнда являются строками

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

При преобразовании строки в число, движок сначала отсекает все пробельные символы, символы \n , и \t в начале и в конце строки, и возвращает NaN если обрезанная строка не представляет из себя корректное число. Если строка окажется пустой, то результатом будет 0 .

null и undefined обрабатываются по разному: null станет 0 , в то время как undefined станет NaN .

Численное преобразование, как явное так и неявное, не работает для символов. Более того, движок бросает ошибку TypeError , вместо того, чтобы по-тихому преобразовать Symbol в NaN , как это происходит с undefined . Подробнее о правилах преобразования символов можно посмотреть на MDN.

Существует два специальных правила которые следует запомнить:

  1. При применении == к null или undefined , численное преобразование не происходит, так как null может равняться только null или undefined , и ничему другому.

2. NaN не равен ничему, даже самому себе.

Преобразование типов для объектов

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

Когда дело доходит до объектов, и движок встречает выражение вроде [1] + [2, 3] , ему сначала необходимо привести объекты к примитивным значениям, а уже потом выполнить финальное преобразование. Как и в случае с примитивами, объект может быть преобразован всего тремя способами: численным, строковым, булевым.

Самый простой пример это булевое преобразование — любое не примитивное значение всегда приводится к true , включая пустые объекты и массивы.

Объекты приводятся к примитивам посредством вызова внутреннего метода [[ToPrimitive]] , который отвечает как за численное, так и за строковое преобразование.

Вот псевдокод реализации метода [[ToPrimitive ]] :

Методу [[ToPrimitive]] передаётся два аргумента: входящее значение и предпочтительный тип для преобразования: Number или String . Второй аргумент является опциональным.

Как для строкового так и для численного преобразования используются два метода объекта: valueOf и toString . Оба метода объявлены в Object.prototype , а значит доступны для всех производных типов, таких как Date , Array и т.д.

В общих чертах алгоритм выглядит следующим образом:

  1. Если входящее значение уже является примитивом, ничего не делать и просто вернуть его.
  2. Вызвать input.toString() , если результат примитив — вернуть его.
  3. Вызвать input.valueOf() , если результат примитив — вернуть его.
  4. Если ни один из методов не вернул примитив — бросить ошибку TypeError .

При численном преобразовании сначала вызывается метод valueOf() , а уже затем toString() . При строковом преобразовании наоборот — сначала происходит вызов toString() , а уже потом valueOf() .

Большинство встроенных типов не имеют метода valueOf или же имеют valueOf , который возвращает свой собственный объект this , который игнорируется, так как this не является примитивом. Вот почему численное и строковое преобразование в большинстве случаев работает одинаково — оба в конечном итоге вызывают метод toString() .

Разные операторы могут вызывают строковое или численное преобразование при помощи параметра preferredType . Но существует два исключения: нестрогое равенство == и бинарный оператор + , которые вызывают режим преобразования по умолчанию ( preferredType не указан или равен default ). В таком случае, большинство встроенных типов подразумевают численное преобразование, за исключением Date , который предпочитает строковое преобразование.

Вот пример преобразования Date :

Вы можете переопределить методы toString() и valueOf() для того, чтобы повлиять на логику преобразования объектов в примитив.

Обратите внимание, как obj + ‘’ возвращает строку 101 . Оператор + вызывает преобразование в режиме по умолчанию, и как упоминалось выше, Object подразумевает численное преобразование в таком случае, используя сначала метод valueOf() , а затем уже toString() .

Метод ES6 Symbol.toPrimitive

В ES5 вы можете повлиять на логику преобразования объекта в примитив, переопределив методы toString() и valueOf() .

В ES6 вы можете пойти дальше и полностью заменить внутреннюю процедуру метода [[ToPrimitive]] , реализовав метод [Symbol.toPrimitive]() у объекта.

Примеры

Вооружившись теорией, давайте вернёмся к нашим примерам:

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

Бинарный оператор + вызывает численное преобразование для true и false :

Оператор деления / вызывает численное преобразование строки 6 :

Оператор + выполняется слева направо, поэтому сначала выполнится выражение “number” + 15 . Поскольку один из операндов это строка, оператор + вызовет строковое преобразование числа 15 и последующую конкатенацию двух строк. На следующем этапе выражение “number15” + 3 выполнится таким же образом.

Сначала выполняется сложение чисел 15 + 3 . На данном этапе никакого преобразования не нужно, так как оба операнда являются числами. Затем выполняется выражение 18 + ‘number’ , и так как один из операндов является строкой, то вызывается строковое преобразование для числа 18 , и последующая конкатенация двух строк.

Оператор сравнения > вызывает численное преобразование для [1] и null

Унарный оператор + имеет более высокий приоритет чем бинарный оператор + . Поэтому выражение + 'bar' выполняется первым. Унарный плюс вызывает численное преобразования строки bar . Так как эта строка не представляет собой корректное число, результатом будет NaN . Следующим шагом выполнится выражение 'foo' + NaN .

Оператор сравнения == вызывает численное преобразование, поэтому строка true конвертируется в NaN , а правый операнд true станет 1 .

Оператор == обычно вызывает численное преобразование, но не в случае с null . Действует исключение из правил: null равен только null или undefined и ничему другому.

Оператор !! конвертирует строки true и false в булевое значение true , так как это не пустые строки. А дальше оператор == просто сравнивает два булевых значения безо всяких преобразований.

Оператор == вызывает численное преобразование для массива. Метод массива valueOf() возвращает сам массив, а значит результат игнорируется, так как не является примитивом. Далее, вызывается метод массива toString() , который конвертирует [‘x’] в строку ‘x’ .

Оператор + вызывает численное преобразование массива. Метод массива valueOf() вернёт сам массив, поэтому результат игнорируется, поскольку не является примитивом.

Далее выполняется выражение ’’ + null + 1 .

Логические операторы || и && преобразовывают операнды к булевому значению, но всегда возвращают оригинальное значение операнда (не булевое). 0 станет false , а поскольку 0 является не пустой строкой, то конвертируется в true . <> пустой объект тоже становится true .

В данном примере никакого преобразования не требуется, потому что оба операнда одного типа. Так как оператор == сравнивает объекты по ссылке, а не по значению, а данные массивы являются двумя разными экземплярами, результатом будет false .

Все операнды являются не примитивами, поэтому + вызывает численное преобразование. Методы Object.valueOf() и Array.valueOf() возвращают самих себя, соответственно будут проигнорированы. В качестве запасного варианта, вызывается метод toString() . Трюк в том, что первый <> воспринимается движком не как создание объекта, а как объявление пустого блока кода и поэтому игнорируется. Выполнение начинается с выражения +[] , которое преобразуется в пустую строку посредством метода toString() , и далее в 0 .

Данный пример лучше объяснить пошагово с точки зрения приоритета выполнения операторов:

Оператор - вызывает численное преобразование для объекта Date . Date.valueOf() возвращает количество миллисекунд прошедших с начала Unix эпохи (в данном случае 0 ).

Оператор + вызывает преобразование по умолчанию. Date , как исключение, подразумевает строковое преобразование, поэтому используется метод toString() , а не valueOf() .

Источники

Я бы хотел порекомендовать отличную книгу “Understanding ES6” написанную Nicholas C. Zakascholas. Книга является отличным ресурсом для изучения ES6 с достаточным уровнем освещения: не слишком поверхностно, и в тоже время не погружается в механику работы движка черезчур.

Ещё одна отличная книга, на этот раз по ES5 — SpeakingJS написанная Axel Rauschmayer.

С++ позволяет нам преобразовывать данные одного типа в данные другого – это известно как преобразование типов.

В C++ существует два типа преобразования:

  1. Неявное преобразование.
  2. Явное преобразование (также известное, как приведение типов).

Неявное преобразование типов

Преобразование типа, которое автоматически выполняется компилятором, известно как неявное преобразование. Этот тип также известен, как автоматическое преобразование.

Давайте посмотрим на два примера неявного преобразования типов.

Пример 1: из int в double

В программе мы присвоили данные типа int переменной типа double.

Здесь значение int автоматически преобразуется компилятором в double, прежде чем оно будет присвоено переменной num_double .

Пример 2: из double в int

В программе мы присвоили данные типа double переменной типа int.

Здесь двойное значение автоматически преобразуется компилятором в int, прежде чем оно будет присвоено переменной num_int .

Примечание. Поскольку int не может иметь десятичную часть, цифры после десятичной точки в приведенном выше примере усекаются.

Потеря данных (сужение преобразования)

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

Потеря данных (сужение преобразования)

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

Когда пользователь в C++ вручную меняет данные с одного типа на другой, это называется явным преобразованием. Этот тип преобразования также известен, как приведение типов.

Есть три основных способа использования явного преобразования в C++:

  1. Приведение типов в C-style (также известное, как обозначение приведения).
  2. Обозначение функций (также известное, как приведение типов в старом стиле С++)
  3. Операторы преобразования типов.

C-style

Как следует из названия, этому типу приведения предпочитает язык программирования C. Это также известно как обозначение приведения.

Синтаксис этого стиля:

Использование функционального стиля

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

Пример 3: приведение типов

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

Операторы преобразования типов

Помимо этих двух приведений типов, C++ также имеет четыре оператора преобразования типов:

Читайте также: