Типизация — Введение в программирование
В одном из прошлых уроков мы говорили об ошибках и как с ними справляться. Есть несколько видов ошибок, и я хочу напомнить об одном конкретном виде. Вот небольшой фрагмент того урока:
Взгляните на этот код:
const length = 12; const num = length(54);
Сначала мы создали константу. Помните, что это как давать чему-то название: в нашем случае — числу 12 даётся название length . В следующей строке мы вызываем функцию length и передаём ей аргумент — число 54. Но подождите! length — это не функция! Это всего лишь число. Числа — это не функции, не ящики, которые производят какие-то действия. И JavaScript пожалуется именно на это:
= length(-54); ^ TypeError: length is not a function at Object. (/Users/rakhim/test.js:2:13) at Module._compile (module.js:571:32) at Object.Module._extensions..js (module.js:580:10) at Module.load (module.js:488:32) at tryModuleLoad (module.js:447:12) at Function.Module._load (module.js:439:3) at Module.runMain (module.js:605:10) at run (bootstrap_node.js:420:7) at startup (bootstrap_node.js:139:9) at bootstrap_node.js:535:3
Это Ошибка типизации: тип объекта, который вы использовали, неверный. Интерпретатор JavaScript не скажет чем что-то является, но точно скажет чем оно не является. length — это не функция.
Ошибка типизации — это как просить кошку постирать бельё. Возможно, вы хотели попросить об этом вашего друга.
В программировании «типизация» — это классификация информации. Это общий термин и разные языки программирования справляются с типизацией по-разному. Как вы уже знаете, JavaScript умеет отличать типы. Функция — это один тип, Число — другой, и вы не можете просто использовать число как функцию.
typeof — это специальный оператор, который возвращает строку, в которой написан тип.
typeof 42; // 'number' typeof 3.14; // 'number' typeof NaN; // 'number' typeof 'Berry'; // 'string' typeof true; // 'boolean' typeof false; // 'boolean'
42 и 3.14, очевидно, числа, несколько комбинаций букв в кавычках — строка, а true и false — булево значение. Всё это — типы в JavaScript — число, строка и булево значение.
NaN означает — «не число», но тип NaN — это «число». Да, я знаю. Еще одна странность JavaScript. Такие правила в этом языке.
Типизация полезна. Когда мы попытаемся запустить число, как будто это функция, JavaScript начнёт жаловаться и мы увидим ошибку и починим её. Если бы никакого обозначения типов в JavaScript не было, мы бы сталкивались либо с каким-нибудь аномальным поведением, либо с мистической ошибкой. Вместо чёткого «length — это не функция», мы бы видели что-то вроде «I’m sorry Dave, I’m afraid I can’t do that».
А что, если создать переменную, но не задать ей никакого значения? Какой в этом случае будет тип? Это ни число, ни строка, ничто. Потому что нет значения, правильно?
JavaScript в этом случае кое-что делает в тайне от вас. Переменная без значения на самом деле имеет специальное значение — «undefined». И тип такой переменной называется «undefined».
let a; console.log(a); // undefined typeof a; // 'undefined'
Например, тип number имеет множество потенциальных значений: 1, 2, -10, 69000 и другие числа. А тип undefined только одно — undefined .
Когда дело касается типизации в программировании, важно различать две концепции: динамическая против статической и слабая против сильной.
Чтобы понимать разницу между динамической и статической типизацией, нам сначала нужно посмотреть как написанные программы становятся запущенными программами.
Код, который вы пишете, обычно конвертируется в понятную для запуска компьютером форму. Этот процесс называется компиляцией, а промежуток времени, за который это происходит — «стадией компиляции» или compile time.
После того, как компиляция закончена и программа запущена, начинается отсчёт времени, который называется «стадией исполнения» или run time.
Некоторые языки проверяют типы и ищут ошибки типизации на стадии компиляции. У них статическая типизация.
Другие языки проверяют типы и ищут ошибки типизации на стадии исполнения. Такая типизация — динамическая.
Иными словами: статическая типизация означает проверку типов перед запуском программы, динамическая — проверку типов, когда программа запущена.
C#, C++, Java, Go — статически типизированные языки. Если в одном из этих языков вы создадите число и попытаетесь проводить с ним операции, как с функцией, вы получите ошибку во время компиляции, а программа не станет запускаться — она даже не дойдёт до этой стадии, потому что ошибка типизации будет обнаружена перед исполнением, в период компиляции.
JavaScript, Ruby, PHP — динамически типизированные языки. Как вы видели раньше, если использовать неверную типизацию, ваша программа запустится, а ошибка обнаружится только когда будет исполняться конкретная строчка кода. Здесь типы проверяются в период исполнения.
Вообще-то, в JavaScript обычно нет никакой компиляции, но это тема другого урока.
Динамическая типизация не хуже и не лучше статической. Оба способа имеют свои преимущества и недостатки. Динамически типизированные языки обычно проще изучать и писать на них программы, но, как вы можете представить, это потенциально увеличивает ошибки.
Теперь давайте поговорим о слабой и сильной типизации. Посмотрите на этот JavaScript код:
4 + '7'; // '47' 4 * '7'; // 28 2 + true; // 3 false - 3; // -3
М-да… Это… Ок, что тут происходит? Сложение числа 4 со строкой «7» даёт нам строку «47». JavaScript конвертирует число 4 в строку «4» и конкатенирует две строки — склеивает их друг с другом. JavaScript просто берёт на себя ответственность предположить, что это то, что мы хотели. Глупо обвинять его — чего мы действительно хотели? Складывать число со строкой не имеет никакого смысла. Какой-нибудь другой язык, вроде Ruby или Python просто бы пожаловался и ничего не сделал.
Произведение числа 4 со строкой «7», это, как видите, 28, по мнению JavaScript. В этом случае он сконвертировал строку «7» в число 7 и произвёл обычное умножение.
JavaScript постоянно так делает. Он знает о типах разных значений, но когда типы не соответствуют, он пытается предположить и сконвертировать один тип в другой, не предупреждая вас. Иногда это полезно, иногда мозгодробяще. Такое происходит потому что JavaScript — язык со слабой типизацией. У него есть представление о типах, но он типа «это всего лишь игра, чего ты злишься?»
У этой концепции нет ничего общего с динамической и статической типизацией, смысл которых — КОГДА проверять типы. Сильная против слабой — это НАСКОЛЬКО СЕРЬЁЗНО проверять типы.
Вы можете считать, что слабая — это нестрогая типизация, а сильная — это требовательная.
В отличие от динамичности-статичности, сила типизации это спектр. У PHP типизация немного сильнее. У Python ещё сильнее. И все они динамически типизированные языки.
JavaScript делает множество неявных конвертаций, но он так же даёт нам инструменты, чтобы мы могли делать явные конвертации сами. Мы можем конвертировать строки в числа, числа в строки, булевы значения в строки и так далее:
// Конвертация числа в строку String(44843); // '44843' // Конвертация строки в число Number('590'); // 590 Number('aaa!!'); // NaN // Конвертация числа в булево значение Boolean(1); // true Boolean(0); // false // Конвертация булева значения в строку String(true); // 'true' String(false); // 'false'
Можно предположить, что неявная конверсия из типа в тип — не самая лучшая идея. Неявный, значит скрытый, а скрытый — значит трудно понимаемый и предрасположенный к ошибкам. Поведение программы становится менее очевидным. Вы пишете меньше кода, да, но код более хрупкий и менее понятный.
Дополнение к уроку
null
В JavaScript кроме undefined существует null . Оно означает, что «значение отсутствует». Например, если создать переменную, но не задавать ей значения, то у нее будет значение undefined:
let a; console.log(a); // undefined
Тут значения не оказалось ненамеренно. Видимо, просто еще не пришло время дать этой переменной значение.
null нужен для явного, намеренного указания, что значения нет. Можно сказать let a = null; . Например, вы попросили пользователя ввести информацию, но он ничего не ввел. В таком случае уместно записать в результат null .
null , в отличие от undefined , можно задавать вручную, передавать как аргумент в функцию и в целом использовать как любое другое явное значение.
( undefined тоже можно задавать вручную, но никогда не нужно этого делать: это значение семантически создано только для того, чтобы его генерировал компьютер, а не программист).
При сравнении null и undefined нужно быть осторожным:
typeof null; // "object" (не "null" по историческим причинам) typeof undefined; // "undefined" null === undefined; // false null == undefined; // true null === null; // true null == null; // true !null; // true isNaN(1 + null); //false isNaN(1 + undefined); //true
Сравнение
В этом курсе мы сравниваем данные, используя три знака равенства:
a === b; 12 === 12;
Это сравнение прямое: являются ли эти данные абсолютно идентичными?
В JavaScript есть расслабленное сравнение, с двумя знаками равенства. Оно показывает, что происходит внутри JavaScript, при сравнении значений разных типов:
1 === '1'; // false 1 == '1'; // true true === 1; // false true == 1; // true
Выводы
Типизация в JavaScript
JavaScript имеет представление о типах: числах, строках, функциях, логических значениях и так далее. typeof возвращает строку, в которой записан тип:
typeof 42; // 'number' typeof 3.14; // 'number' typeof NaN; // 'number' typeof 'Berry'; // 'string' typeof true; // 'boolean' typeof false; // 'boolean'
NaN означает «не число», но тип этого значения — number .
Переменная без значения имеет специальное значение undefined . Тип такой переменной — undefined :
let a; console.log(a); // undefined typeof a; // 'undefined'
Динамическая и статическая типизация
Код конвертируется в другую форму, которую компьютер может запустить. Этот процесс называется компиляцией, а период времени, за который этот процесс происходит — стадией компиляции (compile time).
После того, как компиляция закончена, запускается программа и период, пока она запущена, называется стадией исполнения (run time).
Статически типизированные языки проверяют типы и ищут ошибки типизации на стадии компиляции.
Динамически типизированные языки проверяют типы и ищут ошибки типизации на стадии исполнения.
Иными словами: статическое типизирование означает проверку типов перед запуском программы; динамическое — проверку типов пока программа запущена.
Слабая и сильная типизация
JavaScript часто конвертирует типы автоматически:
4 + '7'; // '47' 4 * '7'; // 28 2 + true; // 3 false - 3; // -3
JavaScript — это язык со слабой типизацией. У него есть представление о типах, но он расслаблено к ним относится и может оперировать значениями, можно сказать, произвольно. Чем сильнее система типизации, тем строже правила.
Явные конверсии в JavaScript
Number('590'); // 590 Number('aaa!!'); // NaN Boolean(1); // true Boolean(0); // false String(true); // 'true' String(false); // 'false' String(44843); // '44843'
Дополнительные материалы
- Wat by Gary Bernhardt (video)
- typeof
- Data type / Wikipedia
- Dynamic typing (WikiWikiWeb)
- Weak And Strong Typing (WikiWikiWeb)
![]()
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Об обучении на Хекслете
- Статья «Как учиться и справляться с негативными мыслями»
- Статья «Ловушки обучения»
- Статья «Сложные простые задачи по программированию»
- Урок «Как эффективно учиться на Хекслете»
- Вебинар « Как самостоятельно учиться »
Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
- 130 курсов, 2000+ часов теории
- 1000 практических заданий в браузере
- 360 000 студентов
Наши выпускники работают в компаниях:
Сильная и слабая типизация¶
Типизированные языки программирования также разделяются на сильно и слабо типизированные. Очень часто можно услышать их неверные синонимы строгая и нестрогая типизация. Эти синонимы считаются некорректными, так как произошли в результате неверного перевода англоязычных терминов strongly typed и weakly typed, что дословно переводится как сильно типизированные и слабо типизированные.
Сильная типизация¶
Языки с сильной типизацией (strongly typed) не разрешают выполнение выражений с несовместимыми типами и не выполняют неявное преобразование типов в ситуациях, когда нужно выполнять преобразование явно.
Сильно типизированный язык не позволит такие операции, как умножение числа на массив и не выполнит неявного преобразования объекта к строке.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
var multy = (a, b) => a * b; var number = 5; var array = [0, 1, 2]; // Ок, вернёт 10 var result = multy(number, array[2]); // Ошибка var result = multy(number, array); var welcome = (name) => 'Hello' + ' ' + name; var user = name: 'Bob', >; // Ок, вернёт 'Hello Bob' var result = welcome(user.name); var result = welcome(user); // Ошибка
В языках с сильной типизацией при операциях, способных привести к потере или порче значения, возникает ошибка. Благодаря этому сильно типизированные языки в меньшей степени подвержены багам.
К языкам с сильной типизацией можно отнести такие языки, как Java, C# и другие.
Слабая типизация¶
Языки со слабой типизацией (weakly typed) разрешают выполнение выражений с любыми типами и самостоятельно выполняют неявное преобразование.
Например, в JavaScript при операции сложения строки и числа второй операнд может быть неявно приведён к строке, а при сложении строки и объекта объект будет неявно приведён к значению, возвращаемому объектом из метода toString() , то есть строке.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
var add = (a, b) => a + b; // Ок, вернёт 10 var results = add(5, 5); // Ошибки не будет, но результат будет Hello5 var result = add('Hello', 5); var welcome = (name) => 'Hello' + ' ' + name; var user = name: 'Bob', >; // Ок, вернёт 'Hello Bob' var result = welcome(user.name); // Ошибки не будет, но в результате вернёт // 'Hello [object Object]' var result = welcome(user);
Из-за того, что в языках со слабой типизацией допускаются неявные преобразования, существует высокая вероятность возникновения трудно выявляемых багов. Причиной появления подобных багов является отсутствие возникновения ошибок в некоторых операциях неявного преобразования. Программа получает данные ожидаемого типа, но непредсказуемого значения.
1 2 3 4 5
// Вернёт 55 (string) вместо 10 (number) var valueA = 5 + '5'; // Вернет 110 (number) вместо 20 (number) var valueB = valueA * 2;
К языкам со слабой типизацией относятся JavaScript или PHP*.
Динамическая типизация C
Эта статья была написана и опубликована мной на своем сайте более десяти лет назад, сам сайт с тех пор канул в лету, а я так и не начал писать что-то более вразумительное в плане статей. Все ниже описанное является результатом исследования C как языка двадцатилетним парнем, а, следовательно, не претендует на звание учебного пособия, несмотря на стиль изложения. Тем не менее, я искренне надеюсь, что она побудит молодых разработчиков погрузиться в эксперименты с C также, как когда-то делал это я.
Предупреждение
Эта короткая статья, окажется абсолютно бесполезной для опытных программистов C/C++, но кому-то из начинающих, возможно, позволит сэкономить время. Хочу подчеркнуть, что в большинстве хороших книг по C/C++ данная тема рассмотрена в достаточной степени.
Динамическая и статическая типизация
Во многих интерпретируемых языках используется динамическая типизация. Такой подход позволяет хранить в переменной с одним именем значения разных типов. В языке C используется статическая типизация, что, на мой взгляд более, чем правильно. Однако бывают случаи (хоть и не так часто), когда гораздо удобней было бы использовать динамическую типизацию. Зачастую, такая потребность напрямую связана с некачественным проектированием, но не всегда. Не зря же в Qt присутствует тип QVariant .
Здесь мы поговорим про язык C, хотя все, что описано ниже, применимо и к C++.
Магия указателя пустоты
На самом деле, никакой динамической типизации в C нет и быть не может, однако существует универсальный указатель, тип которому void * . Объявление переменной такого типа, скажем, в качестве аргумента функции, позволяет передавать в нее указатель на переменную любого типа, что может быть крайне полезно. И вот он — первый пример:
#include int main()
Вывод:
i_ptr: 22 d_ptr: 22.500000
Здесь мы одному и тому же указателю присвоили указатели (простите за тавтологию) как на тип int , так и на double .
Первый пример не нес никакой полезной нагрузки. Попробуем ее поискать во втором примере:
#include #include int lilround(const void *arg, const char type) < if(type == 0) // если передан int return *((int *)arg); // просто возвращаем значение целого аргумента // если передан double return round(*(double *)arg); // округляем >int main() < int i = 12; double j = 12.5; printf("round int: %d\n", lilround(&i, 0)); // пытаемся округлить целое число printf("round double: %d\n", lilround(&j, 1)); // пытаемся округлить число двойной точности return 0; >
Вывод:
round int: 12 round double: 13
Здесь мы создали, можно сказать, универсальную функцию для округления как целых чисел (которым оно не требуется, конечно), так и для чисел двойной точности. Следует понимать, что функция может выполнять и что-то более полезное, в зависимости от типа аргумента.
Альтернативная реализация функции lilround() :
int lilround(const void *arg, const char type)
Но для того, чтобы функция знала — с чем имеет дело — мы передаем в нее второй аргумент. Если он равен 0 , то первый интерпретируется как указатель на int , если нет — как указатель на double . Такой подход может во многих случаях сгодиться, но, в основном, смысл использования универсального указателя как раз-таки в том, чтобы не указывать тип передаваемого параметра.
Предположим, что у нас две или более структур ( struct ), которые содержат различный набор полей. Но так уж получилось, что нужно передать их одной и той же функции. Почему так вышло рассуждать не будем.
Что же делать? Ответ почти очевиден: передавать их в виде указателя неопределенного типа. И, все ничего, но как же тогда наша функция узнает об их типе? Все просто: в самое начало структуры добавим поле type , в которое будем записывать идентификатор структуры, по которому наша функция и будет определять ее тип, предварительно приведя неопределенный указатель к любой из структур. Идентификатором может быть поле любого типа, хоть еще одна структура, но оно должно стоять первым в каждой из структур и иметь один и тот же тип. Такое условие следует из способа расположения структур в памяти компьютера. Если написать так:
typedef struct < char type; int value; >iStruct; typedef struct < char type; double value; >dStruct;
То все сработает корректно. Но если написать так:
typedef struct < char type; int value; >iStruct; typedef struct < double value; char type; >dStruct;
То программа соберется, но во время работы выдаст неверный вариант, так как, в зависимости от того — к какой структуре приведем указатель, в случае обращения программа попытается считать первый байт из double value или, вообще, неизвестно откуда.
А вот и пример использования такого подхода:
#include #include #pragma pack(push, 1) typedef struct < char type; // идентификатор типа структуры int value; // целочисленное значение >iStruct; #pragma pack(pop) #pragma pack(push, 1) typedef struct < char type; // идентификатор типа структуры double value; // значение двойной точности >dStruct; #pragma pack(pop) int lilround(const void *arg) < iStruct *s = (iStruct *)arg; if(s->type == 0) // если передан int return s->value; // просто возвращаем значение целого аргумента // если передан double double a = ((dStruct *)arg)->value; return round(a); // округляем > int main() < iStruct i; i.type = 0; i.value = 12; dStruct j; j.type = 1; j.value = 12.5; printf("round int: %d\n", lilround(&i)); // пытаемся округлить целое число printf("round double: %d\n", lilround(&j)); // пытаемся округлить число двойной точности return 0; >
Примечание: директивы компилятора #pragma pack(push, 1) и #pragma pack(pop) необходимо помещать до и после каждой специфической структуры, соответственно. Данная директива используется для выравнивания структуры в памяти, что обеспечит корректность метода. Однако не стоит также забывать о порядке полей.
В теле функции аргумент приводится к структуре iStruct и проверяется значение поля type. Дальше уже аргумент приводится к другому типу структуры, если нужно.
Перед тем, как перейти к последней части, стоить пояснить работу с простыми void-указателями. Сложение, вычитание, инкремент, декремент и т.д. запрещены для типа void , поэтому необходимо сперва привести аргумент к нужному типу, а уж затем совершать операцию:
#include int main() < int i = 22; void *var = &i; // объявляем void-указатель и инициализируем его адресом переменной i (*(int *)var)++; // приводим void-указатель к int-указателю, разыменовываем его и производим операцию инкремента printf("result: %d\n", i); // выводим измененное значение i return 0; >
Исходя из кода: для совершения операции необходимо записать (*(int *)var) и уже к данной записи применить требуемый оператор.
Подобие интерфейсов в C
Вернемся к структурам. Если структура «засылается» далеко и глубоко в код, возможно даже чужой, то имеет смысл передать вместе с ней и методы, которые будут обрабатывать ее значения. Для этого создадим дополнительную структуру, которая заменит поле type :
typedef struct < void (*printType)(); // указатель на функцию, выводящую тип int (*round)(const void *); // указатель на функцию, округляющую значение >uMethods;
Опишем реализации указанных выше функций для разных типов структур, а также — функции инициализации. Результат ниже:
#include #include typedef struct < void (*printType)(); // указатель на функцию, выводящую тип int (*round)(const void *); // указатель на функцию, округляющую значение >uMethods; #pragma pack(push, 1) typedef struct < uMethods m; // структура с указателями на функции int value; // целочисленное значение >iStruct; #pragma pack(pop) #pragma pack(push, 1) typedef struct < uMethods m; // структура с указателями на функции double value; // значение двойной точности >dStruct; #pragma pack(pop) void intPrintType() // вывод типа для iStruct < printf("integer\n"); >int intRound(const void *arg) // округление для iStruct < return ((iStruct *)arg)->value; // приводим аргумент к указателю на iStruct и возвращаем значение > void intInit(iStruct *s) // инициализация iStruct < s->m.printType = intPrintType; // задаем полю printType указатель на функцию вывода для iStruct s->m.round = intRound; // задаем полю round указатель на функцию округления для iStruct s->value = 0; > void doublePrintType() // вывод типа для dStruct < printf("double\n"); >int doubleRound(const void *arg) // округление для dStruct < return round(((dStruct *)arg)->value); > void doubleInit(dStruct *s) < s->m.printType = doublePrintType; // задаем полю printType указатель на функцию вывода для dStruct s->m.round = doubleRound; // задаем полю round указатель на функцию округления для dStruct s->value = 0; > int lilround(const void *arg) < ((iStruct *)arg)->m.printType(); // приводим к любой структуре, в данном случае iStruct, и выводим тип return ((iStruct *)arg)->m.round(arg); // возвращаем округленное значение > int main() < iStruct i; intInit(&i); // инициализируем целочисленную структуру i.value = 12; dStruct j; doubleInit(&j); // инициализируем структуру с данными двойной точности j.value = 12.5; printf("round int: %d\n", lilround(&i)); // пытаемся округлить целое число printf("round double: %d\n", lilround(&j)); // пытаемся округлить число двойной точности return 0; >
Вывод:
integer round int: 12 double round double: 13
Примечание: директивами компилятора следует обрамлять только те структуры, которые необходимо использовать в качестве аргумента для void-указателя.
Заключение
В последнем примере можно заметить сходство с ООП, что, в общем-то, правда. Здесь мы создаем структуру, инициализируем ее, задаем ее ключевым полям значения и вызываем функцию округления, которая, кстати говоря, крайне упростилась, хотя мы сюда же добавили вывод типа аргумента. На этом все. И помните, что применять подобные конструкции нужно размумно, ведь, в подавляющем большинстве задач их наличие не требуется.
UPD.: Спасибо модераторам хабра за указания на опечатки и досадные ошибки исходной версии текста.
UPD.: Спасибо mastan за то замечание по поводу усложненного некорректного округления, хоть это и не относится к теме статьи, все же, лучше исправить на использование round() из math.h . Также была перефразирована часть про арифметику с void (не void * ) благодаря замечанию eanmos. При помощи разбора кода со стороны sergio_nsk код в статье стал более читаем и лишился спорной части с неподтвержденным примечанием.
- указатели
- динамическая типизация
- void
Типизация языков программирования: разбираемся в основах
Типизация языков программирования определяет, как вы будете работать с типами переменных. Рассказываем о ней подробнее.
Типизация языков программирования — это то, как различные языки распознают типы переменных. Она определяет, как вы будете работать с типами переменных: нужно ли их задавать изначально, можно ли изменять и так далее.
Виды типизации
Языки программирования бывают типизированными и нетипизированными (бестиповыми).
Бестиповая типизация в основном присуща старым и низкоуровневым языкам программирования, например Forth. Все данные в таких языках считаются цепочками бит произвольной длины и, как следует из названия, не делятся на типы. Работа с ними труднее, и при чтении кода не всегда ясно, о каком типе переменной идет речь. Это можно исправить, написав комментарии к коду.
При этом у нетипизированных языков немало плюсов. В них можно совершать операции с любыми данными, и код получается более эффективным.
Перейдём к типизированным языкам.
Статическая и динамическая типизация
Особенность языков программирования со статической типизацией в том, что проверка типов начинается на стадии компиляции. Компиляторы ищут ошибки ещё до запуска программы, и вам не нужно раз за разом запускать её, чтобы выяснить, что пошло не так. Благодаря этому статически типизированные языки программирования зачастую быстрее. Кроме того, тип для переменной можно назначить только один раз. Например в Java такая запись вызовет ошибку на этапе компиляции:
int data; data = 20; data = "Hello";
В свою очередь, языки с динамической типизацией ищут ошибки на стадии исполнения. В них можно задать разные типы для одной и той же переменной, и они более гибкие. Например, в Python возможна такая запись, и ошибки не будет:
data = 8 data = "Hello"
Сильная и слабая типизация
В слабо типизированных языках программирования можно смешивать разные типы данных. Так код получается короче — язык «старается» сам выполнять операции преобразования с разными типами. Впрочем, в таком случае не всегда ясно, как поведёт себя программа. Например, в JavaScript возможна такая запись:
5 + '6'; // '56' 4 * '3'; // 12 2 + true;// 3 false - 4;// -4
При сильной или строгой типизации, как в Python, язык не позволяет смешивать разные типы — то есть, если вы обозначили переменную как число, то добавить к ней строку уже не получится:
foo = "x" foo = foo + 5 Traceback (most recent call last): File "", line 1, in ? foo = foo + 5 TypeError: cannot concatenate 'str' and 'int' objects
Языки с сильной типизацией надёжнее. Да и программист, прописывая все преобразования вручную, лучше понимает, как работает его код.
Явная и неявная типизация
В языках программирования с явной типизацией типы переменных и возвращаемых значений функций нужно задавать. Это дольше, но так проще определять, что значат все данные, а программисту не придётся запоминать или записывать отдельно каждое значение. В языке С переменную нужно записывать так:
При неявной типизации тип переменной определяется интерпретатором или компилятором, поэтому записи в таких языках короче. Иногда они позволяют вручную указывать типы значений, как в Haskell или Python. В Python возможна такая запись, ведь язык сам определит, что это целое число:
Типизация в разных языках программирования
Разные категории могут пересекаться. Обычно языку программирования присуще одно значение из каждой группы: он может быть сильным или слабым, явным или неявным. Но есть несколько исключений. Так, Python с аннотациями может поддерживать явную типизацию, а язык D, наоборот, — неявную. С# поддерживает динамическую типизацию благодаря ключевым словам dynamic и var, Delphi — благодаря типу Variant, а С++ — с помощью библиотеки Boost и имеет одновременно черты языка с сильной и
слабой типизацией.

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