Обычно в C мы должны сообщать компьютеру тип данных в объявлении переменной. Например, в следующей программе я хочу вывести сумму двух чисел с плавающей запятой X и Y.
#include<stdio.h>
main()
{
float X=5.2;
float Y=5.1;
float Z;
Z=Y+X;
printf("%f",Z);
}
Мне пришлось сообщить компилятору тип переменной X.
- Разве компилятор не может определить тип
X
самостоятельно?
Да, может, если я сделаю это:
#define X 5.2
Теперь я могу написать свою программу, не сообщая компилятору тип X
:
#include<stdio.h>
#define X 5.2
main()
{
float Y=5.1;
float Z;
Z=Y+X;
printf("%f",Z);
}
Итак, мы видим, что язык Си имеет какую-то особенность, используя которую он может самостоятельно определять тип данных. В моем случае было установлено, что X
это тип float.
- Почему мы должны упоминать тип данных, когда мы объявляем что-то в main ()? Почему компилятор не может определить тип данных переменной самостоятельно,
main()
как это делается в#define
.
c
variables
data-types
io
declarations
user106313
источник
источник
5.2
это adouble
, поэтому первая программа округляет двойные литералы доfloat
точности, затем добавляет их как числа с плавающей точкой, в то время как вторая округляет двойное представление 5.1 доdouble
и добавляет его кdouble
значению 5.2, используяdouble
сложение, затем округляет результат этого вычисления доfloat
точности , Поскольку округление происходит в разных местах, результат может зависеть. Это всего лишь один пример для типов переменных, влияющих на поведение другой идентичной программы.#define X 5.2
,X
это не переменная, а константа, поэтому она буквально заменяется препроцессором на5.2
то, что вы упомянулиX
. Вы не можете переназначитьX
.auto
фактически делает то, что вы хотите). С другой стороны, если вы думаете, что знаете, что делает ваш код, и вы действительно ввели что-то еще, статическая типизация, подобная этой, обнаружит ошибку раньше, прежде чем станет огромной проблемой. Каждый язык соблюдает баланс: статическая типизация, определение типа, динамическая типизация. Для некоторых задач дополнительная печать действительно стоит того. Для других это пустая трата времени.Ответы:
Вы сравниваете объявления переменных с
#define
s, что неверно. С помощью#define
вы создаете отображение между идентификатором и фрагментом исходного кода. Затем препроцессор C будет буквально заменять любые вхождения этого идентификатора предоставленным фрагментом. Письмов конечном итоге это то же самое, что и компилятор
Думайте об этом как об автоматическом копировании и вставке.
Кроме того, обычные переменные могут быть переназначены, а макрос, созданный с
#define
помощью, не может (хотя вы можете#define
это сделать). ВыражениеFOO = 7
будет ошибкой компилятора, так как мы не можем присвоить «rvalues»:40 + 2 = 7
это недопустимо.Итак, зачем нам вообще нужны типы? Некоторые языки, очевидно, избавляются от типов, это особенно распространено в скриптовых языках. Однако у них обычно есть нечто, называемое «динамическая типизация», когда переменные не имеют фиксированных типов, а значения имеют. Хотя это гораздо более гибко, но и менее эффективно. C любит производительность, поэтому у него очень простая и эффективная концепция переменных:
Существует участок памяти, называемый «стеком». Каждая локальная переменная соответствует области в стеке. Теперь вопрос в том, сколько байтов должно быть в этой области? В C каждый тип имеет четко определенный размер, который вы можете запросить через
sizeof(type)
. Компилятору необходимо знать тип каждой переменной, чтобы он мог зарезервировать правильный объем пространства в стеке.Почему константы, созданные с помощью, не
#define
нуждаются в аннотации типа? Они не хранятся в стеке. Вместо этого#define
создает многократно используемые фрагменты исходного кода в несколько более удобной форме, чем копирование и вставка. Литералы в исходном коде, такие как"foo"
или42.87
хранятся компилятором, либо в виде встроенных инструкций, либо в отдельном разделе данных результирующего двоичного файла.Однако у литералов есть типы. Строковый литерал - это
char *
.42
это ,int
но также может быть использована для более коротких типов (сужение преобразования).42.8
будетdouble
. Если у вас есть литерал, и вы хотите, чтобы он имел другой тип (например, чтобы сделать42.8
afloat
или42
anunsigned long int
), то вы можете использовать суффиксы - букву после литерала, которая изменяет способ обработки компилятором этого литерала. В нашем случае мы могли бы сказать42.8f
или42ul
.Некоторые языки имеют статическую типизацию, как в C, но аннотации типов являются необязательными. Примерами являются ML, Haskell, Scala, C #, C ++ 11 и Go. Как это работает? Магия? Нет, это называется «вывод типа». В C # и Go компилятор просматривает правую часть присваивания и определяет его тип. Это довольно просто, если правая часть является литералом типа
42ul
. Тогда очевидно, каким должен быть тип переменной. Другие языки также имеют более сложные алгоритмы, которые учитывают, как используется переменная. Например, если вы делаетеx/2
, тоx
не может быть строкой, но должен иметь некоторый числовой тип.источник
#define
нас есть константа, которая напрямую преобразуется в двоичный код - каким бы длинным он ни был - и сохраняется в памяти как есть.#define X 5.2
?printf
вы вызываете неопределенное поведение. На моей машине этот фрагмент каждый раз печатает разные значения, на Ideone происходит сбой после нулевой печати.X*Y
недействителен, еслиX
иY
являются указателями, но это нормально, если ониint
s;*X
не допустимо, еслиX
естьint
, но это нормально, если это указатель.X во втором примере никогда не является плавающей точкой. Он называется макросом, он заменяет определенное значение макроса 'X' в источнике значением. Читаемая статья о #define находится здесь .
В случае предоставленного кода, перед компиляцией препроцессор меняет код
в
и это то, что компилируется.
Это означает, что вы также можете заменить эти «значения» кодом
или даже
источник
#define FOO(...) { __VA_ARGS__ }
.Короткий ответ - C нуждается в типах из-за истории / представления оборудования.
История: C был разработан в начале 1970-х годов и предназначался как язык для системного программирования. Код в идеале быстрый и наилучшим образом использует возможности оборудования.
Вывод типов во время компиляции был бы возможен, но и без того медленное время компиляции увеличилось бы (см. Карикатуру «компиляции» в XKCD. Раньше она применялась к «hello world» как минимум в течение 10 лет после публикации C ). Вывод типов во время выполнения не соответствовал бы целям системного программирования. Вывод времени выполнения требует дополнительной библиотеки времени выполнения. С пришел задолго до первого ПК. Который имел 256 RAM. Не гигабайты или мегабайты, а килобайты.
В вашем примере, если вы опустите типы
Тогда компилятор мог бы успешно решить, что X & Y являются float, и сделать Z таким же. Фактически, современный компилятор также решит, что X & Y не нужны, и просто установит Z на 10,3.
Предположим, что расчет встроен в функцию. Автор функций может захотеть использовать свои знания об оборудовании или о решаемой проблеме.
Дабл был бы более подходящим чем поплавок? Занимает больше памяти и медленнее, но точность результата будет выше.
Возможно, возвращаемое значение функции может быть int (или long), потому что десятичные дроби не важны, хотя преобразование из float в int не обходится без затрат.
Возвращаемое значение также можно сделать двойным, гарантируя, что float + float не переполняется.
Все эти вопросы кажутся бессмысленными для подавляющего большинства кода, написанного сегодня, но они были жизненно важны, когда создавался Си.
источник
C не имеет логического вывода типа (так он называется, когда компилятор угадывает тип переменной для вас), потому что он старый. Он был разработан в начале 1970-х годов
Многие новые языки имеют системы, которые позволяют вам использовать переменные без указания их типа (ruby, javascript, python и т. Д.)
источник
true
isboolean
), а не переменные (например,var x
могут содержать значения любого типа). Кроме того, вывод типа для таких простых случаев, как те, о которых идет речь, был, вероятно, известен за десять лет до выпуска Си.implicit none
в этом случае вы должны объявить тип.