Типы данных с плавающей точкой хранятся в памяти компьютера в виде мантиссы со знаком и порядка со знаком. В 32-разрядных персональных компьютерах величины типа float занимают 4 байта, из которых один двоичный разряд отводится под знак мантиссы, 24 разряда под мантиссу и 8 разрядов под порядок. При этом обеспечивается точность представления данных, равная 7 десятичным цифрам. Для величин типа double, занимающих 8 байт, под мантиссу и порядок отводится 53 и 11 разрядов соответственно. При этом обеспечивается точность представления, равная 15 десятичным цифрам. Спецификатор long перед именем типа double указывает, что под величину типа long double отводится 10 байт. Обеспечиваемая точность – 19 десятичных цифр.
Диапазоны значений вещественных типов данных для 32-разрядного компьютера
Тип | Диапазон значений | Размер (байт) | Точность |
Float | 3.4e-38... 3.4e+38 | ||
Double | 1.7e-308... 1.7e+308 | ||
long double | 3.4e-4932... 3.4e+4932 |
Тип void
Кроме перечисленных к простым типам данных относится тип void, но множество значений этого типа является пустым. Он используется для описания функций, которые не возвращают значений, а также для указания пустого списка аргументов функции, например:
void main(void) {}
Тема 3. Выражения
В любой программе требуется производить какие-то вычисления. Для вычисления значений используются выражения, которые состоят из операндов, объединенных знаками операций. Операнды задают данные для вычислений и чаще всего являются переменными или константами. Операции выполняются в соответствии с их приоритетами. Для изменения порядка выполнения операций используются круглые скобки. Примеры выражений:
a + 2
(a + b)*c
В первом выражении + является знаком операции, а а и 2 – операндами. Во втором выражении знаками операций являются + и *, а операндами a, b и c. При этом скобки необходимы, иначе первой выполнялась бы операция умножения.
Рассмотрим составные части выражений и правила их вычислений.
Переменные
Переменная – это именованная область оперативной памяти, предназначенная для хранения данных определенного типа. Во время выполнения программы значение переменной может изменяться. Все переменные, используемые в программе, должны быть описаны явным образом. При описании каждой переменной задаются ее имя и тип. Например:
int a;
float x;
Здесь описаны целая переменная с именем a и вещественная переменная с именем x.
Имя переменной служит для обращения к области памяти, в которой хранится значение переменной. Имя дает программист, с учетом, что оно должно соответствовать правилам именования идентификаторов языка С, отражать смысл хранимой величины и быть легко распознаваемым.
Тип переменной выбирается исходя из диапазона и требуемой точности представления данных. Например, нет необходимости описывать переменную вещественного типа для хранения величины, которая может принимать только целые значения, - хотя бы потому, что целочисленные операции выполняются гораздо быстрее.
В одном операторе можно описать несколько переменных одного типа, разделяя их запятыми:
int a, b, с;
При описании переменной можно присвоить ей некоторое начальное значение, т.е. инициализировать ее, например:
int a, b = 100, с;
float x = 0.05, y = 2.37e4;
char ch1 = 65, ch2 = ‘A’;
Здесь описаны:
- переменные a, b и с типа int, при этом переменная b имеет начальное значение 100;
- переменные x и y типа float, которым присвоены начальные значения 0.05 и 23700 соответственно;
- переменные ch1 и ch2 типа char, которым присвоено одно и то же начальное значение 65 (прописная латинская буква A кодируется числом 65).
Существует другая форма записи инициализирующих значений, использующая круглые скобки вместо знака равенства. В соответствии с ней предыдущие операторы описания переменных будут выглядеть так:
int a, b(100), с;
float x(0.05), y(2.37e4);
char ch1(65), ch2(‘A’);
При инициализации можно использовать не только константу, но и выражение – главное, чтобы на момент описания оно было вычисляемым, например:
int a = 5, b = 100;
int с = a * b + 25;
Инициализировать переменную прямо при объявлении не обязательно, но перед тем, как ее использовать в вычислениях, это сделать все равно придется.
Именованные константы
Можно запретить изменять значение переменной, задав при ее описании ключевое слово const, например:
const int A = 5, B = 100, MIN = 100, MAX = 800;
const int C = A * B + 25, STEP = (MAX – MIN)/100;
const float X = 0.05, Y = 2.37e4, PI = 3.14;
Такие величины называются именованными константами или просто константами. Именованные константы должны обязательно инициализироваться при описании. Они применяются в программах для того, чтобы вместо значений констант можно было использовать их имена. Это делает программу более понятной и облегчает внесение в нее изменений, поскольку изменить значение достаточно только в одном месте программы (там, где константа описана и инициализирована). Например, число p = 3.14 может быть впоследствии заменено более точным значением 3.14159 и т.д.
В хорошо написанной программе вообще не должно встречаться иных чисел, кроме 0 и 1, все остальные числа должны задаваться именованными константами с именами, отражающими их назначение. Имена именованных констант принято записывать заглавными буквами, чтобы они отличались от обычных переменных.
Операции
В соответствии с количеством операндов, которые используются в операциях, они делятся на унарные (один операнд), бинарные (два операнда) и тернарную (три операнда).
Операции языка С в порядке убывания их приоритетов
№ | Операции | Выполнение |
() []. -> ! ~ ++ -- + - * & (тип) sizeof | слева направо справа налево | |
* / % + - << >> < <= > >= ==!= & ^ | && || ?: = += -= *= /= %= &= ^= |= <<= >>= , | слева направо слева направо слева направо слева направо слева направо слева направо слева направо слева направо слева направо слева направо справа налево справа налево слева направо |
В таблице операций первые две строки задают унарные операции, а все последующие – бинарные и одну тернарную (строка 13). Строки упорядочены по убыванию приоритетов операций, при этом операции, записанные в одной строке, имеют одинаковый приоритет. Например, операции *, / и % (строка 3) имеют одинаковый приоритет, который выше, чем приоритет бинарных операций + и - (строка 4).
Операции присваивания
Операции присваивания (строка 14) задают новое значение переменной и подразделяются на простые и сложные. Формат операции простого присваивания:
переменная = выражение
Механизм выполнения операции присваивания такой: вычисляется выражение и его результат заносится в память по адресу, который определяется именем переменной, находящейся слева от знака операции. То, что ранее хранилось в этой области памяти, теряется. Примеры с операцией присваивания:
int a, b = 5, c = 5;
float x = 1.0, y = 33.0;
a = b + c/2;
b = a;
x = x + 0.5;
Рассмотрим последний оператор. Сначала из ячейки памяти, в которой хранится значение переменной х, выбирается это значение. Затем к нему прибавляется 0.5, после чего полученный результат записывается в ту же самую ячейку, а то, что там хранилось ранее, теряется безвозвратно.
Необходимо отметить, что результат вычисления выражения характеризуется значением и типом, например, для b и с равных 5 выражение b + c/2 имеет значение 7 и тип int, т.к. при делении целых чисел дробная часть отбрасывается.
Если операнды, входящие в выражение, разного типа, то перед вычислениями выполняются преобразования более коротких типов в более длинные для сохранения значимости и точности. Такие преобразования называются неявным приведением типов и выполняются автоматически. При этом целочисленные константы имеют по умолчанию тип int, а вещественные – тип double. Например, выражение b + c/2.0 имеет значение 7.5 и тип double. Если же рассмотреть операторы:
a = b + c/2.0;
х = b + c/2.0;
то переменная а, поскольку она целого типа, получит значение 7, а переменная х, поскольку она вещественная, получит значение 7.5. Другие примеры:
х = 10.0 + 9/10; //х = 10.0
х = 10 + 9.0/10; //х = 10.9
х = 5/9*(у – 32.0); //х = 0 для любого значения у
х = 5*(у – 32)/9; //х = 0.555556 для у = 33.0
x = 5.0;
x = 5; //присваивание с неявным приведением типа int к типу float
unsigned char ch1 = 255, ch2;
a = ch1 + 1; //a = 256
ch2 = ch1 + 1; // ch2 = 0
Операция присваивания в отличие от большинства других операций является правоассоциативной, т.е. выполняется справа налево:
a = b = c означает a = (b = c)
Это отличается, например, от операции сложения, для которой a + b + c означает (a + b) + c. Поэтому можно выполнить такое присваивание: a = b = c = 10; (вместо цепочки: a = 10; b = 10; c = 10;).
В сложных операциях присваивания (+=, -=, *=, /= и др.) при вычислении выражения, стоящего в правой части, используется значение из левой части. Например, выражение a += b является более компактной записью выражения a = a + b, выражение a *= b можно использовать вместо выражения a = a * b и т.д.
Инкремент и декремент
Унарные операции инкремента (++) и декремента (--), называемые также операциями увеличения и уменьшения на единицу, имеют две формы записи: префиксную, когда знак операции записывается перед операндом, и постфиксную – после операнда. В префиксной форме сначала изменяется операнд, а затем его значение используется при вычислении выражения, а в постфиксной форме сначала вычисляется выражение с использованием исходного значения операнда, после чего операнд изменяется. Операции инкремента и декремента можно применить только к операндам, которые хранят свои значения в оперативной памяти. Примеры:
int a = b = 2, c, d;
c = ++a; //a = 3, c = 3
d = b++; //d = 2, b = 3
c = a + b; //c = 6
d = a-- * ++c / (--b + 1); //c = 7, b = 2, d = 7, a = 2
a = 10++; //неверно
a = (--b + 5)++; //неверно
Унарный плюс и унарный минус (строка 2)
Унарный плюс (+) перед операндом можно не ставить, т.к. он подразумевается по умолчанию. Унарный минус (-) меняет знак операнда на противоположный.
Явное преобразование типа
Эта унарная операция используется для явного преобразования величины из одного типа в другой, который указывается в круглых скобках перед преобразуемой величиной. При преобразовании из более длинного типа в более короткий возможна потеря информации, если исходное значение выходит за пределы диапазона результирующего типа (эта потеря никак не диагностируется, т.е. остается на совести программиста). Примеры:
int a = 9, b = 10;
float x;
x = a/b; //x = 0
x = (float)a/b; //x = 0.9
x = a/(float)b; //x = 0.9
x = (float)(a/b); //x = 0
a = 256;
b = a; //b = 256
b = (char)a; //b = 0 (потеря информации)