В этой серии статей мы рассмотрим новички в динамически типизированных (или слабо типизированных) языках и то, как их отсутствие строгой типизации может как положительно, так и отрицательно повлиять на наше программирование.
Как упоминалось в первом посте , эта серия специально предназначена для начинающих или тех, кто не имеет большого опыта работы со слабо типизированными языками. То есть, если вы программировали как на строго типизированных, так и на слабо типизированных языках и знакомы с приведением типов и подводными камнями, которые могут возникнуть при выполнении определенных операций, то эта серия может не представлять для вас особого интереса.
С другой стороны, если вы только начинаете писать код или переходите на динамически типизированный язык с другого языка, эта серия предназначена специально для вас. В конечном итоге цель состоит в том, чтобы определить приведение типов, показать, как оно работает, а затем изучить его недостатки.
Принуждение определено
Согласно Википедии, принуждение определяется следующим образом:
В информатике преобразование типов, приведение типов и принуждение — это различные способы явного или неявного изменения сущности одного типа данных в другой.
Или, возможно, более простым способом, вы можете определить это как то, как вы берете один тип данных и конвертируете его в другой. Дело в том, что между обращением и принуждением есть тонкая грань.
Как общее практическое правило, я склонен думать о принуждении как о том, как интерпретатор или компилятор работает для определения типа сравнения, тогда как преобразование — это явное изменение типа, которое мы, как программист, пишем в нашем коде.
Давайте посмотрим на это более подробно.
Преобразование типов
Допустим, например, что у вас есть строка с именем example
и ее значение равно '5'
. В статически типизированных языках вы можете набрать приведение этого захвата к значению строки и преобразовать его в int
помощью ряда различных методов.
Предположим, что у нас есть объект Integer
с методом parseInt
. Метод принимает строку и возвращает значение строки в целочисленном типе данных. Код для этого может выглядеть примерно так:
1
2
3
4
5
6
7
8
|
string example = ‘5’;
Integer myInt = new Integer();
int intExample = myInt.parseInt( example );
/* intExample now has the value of 5 (not ‘5’)
* and example still refers to the string ‘5’
*/
|
Конечно, синтаксис будет варьироваться от языка к языку, и существуют другие способы приведения значения, но это дает вам представление о том, как явно преобразовать один тип в другой.
Другой способ сделать это — использовать оператор приведения типов. Хотя реализация операции варьируется от языка к языку, большинство программистов, которые работали с языками в стиле C, вероятно, признают это как нечто похожее на это:
1
|
int myInt = (int)example;
|
Вообще говоря, приведение типов обычно выполняется путем помещения типа, в который вы хотите преобразовать переменную, в скобки перед самой переменной. В приведенном выше примере myInt
теперь будет содержать 5
, а не '5'
а в example
прежнему будет '5'
.
Как уже говорилось, это обычно делается в контексте скомпилированных языков.
Тип принуждения
Это все еще оставляет вопрос о том, как приведение типов отличается от преобразования типов. Хотя принуждение может происходить в скомпилированных языках, более вероятно, что вы увидите, что это происходит в интерпретируемых языках или в динамически типизированных языках.
Более того, вы, скорее всего, увидите, что приведение типов происходит всякий раз, когда выполняется сравнение между объектами разных типов, или когда выполняется операция или оценка с переменными разных типов.
В качестве простого примера скажем, что в JavaScript у нас есть две переменные — sName
, iAge
— где sName
относится к имени человека, а iAge
— к возрасту человека. Переменные, например, используют венгерскую нотацию просто для обозначения того, что один хранит строку, а другой хранит целое число.
Обратите внимание, что это не аргумент за или против венгерской нотации — это тема для другого поста. Он используется здесь, чтобы прояснить, какой тип значения хранится в каждой переменной, чтобы легче было следовать коду.
Итак, мы продолжим и определим наши переменные и их значения:
1
2
|
var sName = ‘John Doe’;
var iAge = 32;
|
Теперь мы можем взглянуть на несколько примеров того, как приведение типов работает в контексте интерпретируемого языка. Вот два примера того, как работает приведение типов:
- Сравнивая число с логическим
- Конкатенация строки и числа
Давайте посмотрим на пример каждого:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
/**
* Comparing a number to a boolean
* will result in the boolean value
* of ‘false’.
*/
var result = iAge == true;
/**
* Concatenation strings and numbers will
* coerce the number to a string.
*
* «John Doe is 32 years old.»
*/
var bio = sName + ‘ is ‘ + iAge + ‘ years old.’;
|
Эти примеры относительно просты. Первый имеет смысл, поскольку нет никакого способа, которым число можно сравнить с логическим значением.
Во втором примере обратите внимание, что мы берем строку, объединяем ее с другим набором строк, а также используем число в операции конкатенации. В этом случае число преобразуется в строку, а затем объединяется вместе с остальными словами.
Это приведение типа: когда вы берете переменную одного типа и конвертируете ее значение в другой тип при выполнении операции или оценки.
Дело в том, что оба эти примера очень упрощены. Давайте рассмотрим еще несколько, чтобы продемонстрировать, как работает принуждение, по крайней мере в JavaScript, при выполнении операций конкатенации:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
var one, two, result;
// one and two refer to string values of ‘1’ and ‘2’
one = ‘1’;
two = ‘2’;
// result will contain the string ’12’;
result = one + two;
// redefine two to equal the number ‘2’
two = 2;
// concatenating a string and a number results in a string
// result will contain ’12’;
result = one + two;
// redefine one as a number
one = 1;
// then concatenate (or sum) the two values
// result will be 3
result = one + two;
|
Следует отметить две важные вещи:
- Оператор
+
перегружен. Это означает, что когда он работает со строками, он объединяет их вместе, но когда он работает с числами, он складывает их вместе. - Один тип всегда приведен к другому, и обычно существует иерархия того, как это происходит. Хотя каждый язык отличается, обратите внимание, что во втором примере, когда мы объединяем строку и число, результатом является строка. Это потому, что число приведено в строку.
Чтобы продвинуть пример еще на шаг, давайте добавим еще одну переменную, приоритетный набор операций, а затем рассмотрим результат:
01
02
03
04
05
06
07
08
09
10
|
var one, two, tree, result;
one = ‘1’;
two = 2;
three = 3;
// result is ‘123’
result = one + two + three;
// result is ’15’
result = one + (two + three);
|
Обратите внимание, что во втором примере two
и three
добавляются вместе, потому что они оба числа, а затем результат объединяется с one
потому что это строка.
Ранее мы упоминали, что существует один особый случай для чисел и логических значений, по крайней мере, в JavaScript. И так как это язык, который мы использовали для изучения приведения типов, и так как этот язык часто используется в современной веб-разработке, давайте взглянем.
В случае JavaScript обратите внимание, что 1
считается «истинным» значением, а 0
— «ложным». Эти слова выбраны как таковые, потому что значения могут служить числами, но также будут оцениваться как true
или false
при выполнении сравнения.
Давайте посмотрим на некоторые основные примеры:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
var bTrue, bFalse, iZero, iOne, result;
bTrue = true;
bFalse = false;
iZero = 0;
iOne = 1;
// result holds the boolean value of false
result = bTrue == iZero;
// result holds the boolean value of true
result = bTrue == iOne;
// result holds the boolean value of false
result = bFalse == iOne;
// result holds the boolean value of true
result = bFalse == iZero;
|
Обратите внимание, что в приведенных выше примерах числовые значения приводятся к целочисленным значениям по характеру выполняемого сравнения.
Но что произойдет, если мы сравним логическое значение true
или false
со строковым значением one
или zero
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
var bTrue, bFalse, sTrue, sFalse, result;
bTrue = true;
bFalse = false;
sTrue = ‘1’;
sFalse = ‘0’;
// result holds true
result = bTrue == sTrue;
// result holds false
result = bTrue == sFalse;
// result holds false;
result = bFalse == sTrue;
// result holds true
result = bFalse == sFalse;
|
На этом этапе все может начать сбивать с толку, потому что мы сравниваем строковое значение числа, равное 1
с логическим значением true
и мы получаем логический результат, а логическое значение true
.
Есть смысл? Мы рассмотрим это более подробно в следующей статье, но я хотел бы вначале представить основы этого.
Далее…
Это когда динамически типизированные языки могут стать причиной головной боли для разработчиков. К счастью, есть способы, с помощью которых мы можем написать код, который является более строгим, чем у нас выше, и который дает точные результаты.
Кроме того, некоторые динамически типизированные языки также содержат значения для undefined
и для null
. Они также поддерживают «истинные» и «ложные» значения, которые, в свою очередь, влияют на то, как мы имеем дело с сравнениями.
В заключительной статье серии мы рассмотрим, как такие значения, как undefined
и null
сравниваются с другими значениями, а также друг с другом, и рассмотрим некоторые стратегии, которые мы можем реализовать и которые позволят код более устойчив к неправильному приведению типов, и это делает его более читаемым.
Если это ваше первое знакомство с языками с динамической типизацией или принуждением к типу, и у вас есть вопросы, комментарии или отзывы, пожалуйста, не стесняйтесь оставлять комментарии в ленте ниже!