Данные в языке c
Программы работают с данными. Вы вводите числа, буквы и слова в компьютер и ожидаете, что он выполнит над этими данными какие-то действия. Например, вам может потребоваться, чтобы компьютер рассчитал прибыль и отобразил на экране отсортированный список виноторговцев.
В настоящей главе рассматриваются два больших семейства типов данных: целые числа и числа с плавающей запятой (или плавающей точкой, что является синонимом). В языке С имеется несколько разновидностей этих типов.
Демонстрационная программа
Как и прежде, вы столкнетесь с несколькими новыми и незнакомыми деталями, которые мы вскоре проясним. Общий замысел программы должен быть очевиден, поэтому попробуйте скомпилировать и выполнить исходный код, показанный в листинге 3.1. В целях экономии времени можете не вводить комментарии.
Листинг 3.1. Программа platinum.с
/* platinum, с -- ваш вес в платиновом эквиваленте */
#include <stdio.h>
int main(void)
{
float weight; /* вес пользователя */
float value; /* платиновый эквивалент */
printf("Хотите узнать свой вес в платиновом эквиваленте?\n");
printf("Давайте подсчитаем.\n");
printf("Пожалуйста, введите свой вес, выраженный в фунтах: ");
/* получить входные данные от пользователя */
scanf("%f", &weight);
/* считаем, что цена родия равна $1700 за тройскую унцию */
/* 14.5833 коэффициент для перевода веса, выраженного в фунтах, в тройские унции */
value = 1700.0 * weight * 14.5833;
printf("Ваш вес в платиновом эквиваленте составляет $%.2f.\n", value); printf("Вы легко можете стать достойным этого! Если цена платины падает,\n"); printf("ешьте больше для поддержания своей стоимости.\n" );
return 0;
}
СОВЕТ. Сообщения об ошибках и предупреждения
Если вы введете код программы некорректно, скажем, пропустив точку с запятой, компилятор выдаст сообщение о синтаксической ошибке. Однако даже при правильном вводе программы компилятор может выдать предупреждение, подобное следующему: “Преобразование из double в float может привести к потере данных". Сообщение об ошибке означает, что вы сделали что-то неправильно, и программа компилироваться не будет. С другой стороны, предупреждение означает, что введенный код является допустимым, но может привести не к тому результату, который ожидался. Предупреждение не вызывает прекращение компиляции. Это конкретное предупреждение связано с тем, как в языке С обрабатываются числа, подобные 1700.0.
При вводе этой программы, возможно, потребуется заменить число 1700.0 текущей ценой платины. Однако не следует каким-либо образом изменять значение 14.5833, представляющее число тройских унций в одном фунте. (В качестве меры веса для драгоценных металлов используются тройские унции; для измерения веса всего остального применяются фунты.)
Обратите внимание, что ввод веса означает набор на клавиатуре числа, представляющего значение веса, и затем нажатие клавиши <Errter> или <Return>. Нажатие клавиши <Enter> информирует компьютер о завершении ввода. В программе предполагается, что для указания веса будет введено некоторое число, например, 156, а не слова вроде очень большой. Ввод букв вместо цифр вызывает проблемы, поэтому вводите подходящее число. Ниже приведен пример вывода программы:
Хотите узнать свой вес в платиновом эквиваленте?
Давайте подсчитаем.
Пожалуйста, введите свой вес, выраженный в фунтах: 156 Ваш вес в платиновом эквиваленте составляет $3867 491.25.
Вы легко можете стать достойным этого! Если цена платины падает, ешьте больше для поддержания своей стоимости.
Настройка программы
Если вывод программы быстро мелькает на экране, а затем исчезает даже после добавления строки getchar ();, как было описано раньше, вызов этой функции нужно использовать дважды:
getchar(); getchar();
Функция getchar () считывает следующий введенный символ, поэтому программа вынуждена дожидаться ввода. В данном случае мы предоставили ввод, набрав число 156 и затем нажав клавишу <Enter> (или <Return>), что приводит к передаче символа новой строки. Таким образом, функция scanf () считывает число, первая функция getchar () считывает символ новой строки, а вторая функция getchar () вынуждает программу приостановить выполнение, дожидаясь дальнейшего ввода.
Что нового в этой программе?
В этой программе появилось несколько новых элементов языка С.
Обратите внимание, что в программе используется новый вид объявления переменных. В предыдущих примерах применялся только целочисленный тип переменных (int), а здесь добавился тип с плавающей запятой (float), что позволяет поддерживать более широкий спектр данных. Тип float может хранить числа с плавающей запятой.
В программе демонстрируются новые способы записи констант. Теперь в роли констант выступают числа с десятичной точкой.
Для вывода значения переменной нового типа в функции printf () должен использоваться спецификатор %f. Модификатор .2 в спецификаторе %f служит для настройки внешнего вида вывода, так что после десятичной точки будут отображаться два знака.
Для ввода данных в программу с клавиатуры применяется функция scanf (). Спецификатор %f в scanf () означает, что с клавиатуры должно считываться число с плавающей запятой, a &weight — что введенное число будет присвоено переменной по имени weight. В функции scanf () используется амперсанд (&) для указания на то, где можно найти переменную weight.
Вероятно, главной новой характеристикой этой программы является то, что она интерактивна. Компьютер запрашивает у вас информацию и затем задействует введенное вами число. Работать с интерактивными программами намного интереснее, чем с их неинтерактивными разновидностями. Но важнее то, что интерактивный подход делает программы более гибкими. Например, показанная выше демонстрационная программа может применяться для пересчета любого разумного веса, а не только 156 фунтов. Такую программу не придется переписывать каждый раз, когда она потребуется новому пользователю. Эта интерактивность обеспечивается функциями scanf () и printf (). Функция scanf () читает данные с клавиатуры и делает их доступными в программе, а функция printf () принимает данные от программы и выводит их на экран. Вместе эти две функции позволяют установить двухсторонний обмен данными с компьютером (рис. 3.1), что делает работу с компьютером гораздо более увлекательной.
Переменные и константы
Под руководством программы компьютер может выполнять множество действий. Он может суммировать числа, сортировать имена, воспроизводить аудио- и видеоклипы, вычислять орбиты комет, составлять списки адресатов почтовых отправлений, набирать телефонные номера, рисовать картинки, делать логические выводы и решать множество других задач, какие только можно себе вообразить. Для их решения программа должна работать с данными, т.е. числами и символами, несущими в себе необходимую информацию. Некоторые виды данных устанавливаются до начала выполнения программы и их значения сохраняются неизменными в течение всего времени ее работы. Такие данные называются константами. Другие виды данных могут изменяться в ходе выполнения программы. Они называются переменными. В приведенной выше демонстрационной программе weight является переменной, а 14.5833 — константой. А что можно сказать о числе 1770.0? Конечно, в реальности цена на платину не является постоянной величиной, но в этой программе она считается константой. Различие между переменной и константой состоит в том, что переменной можно присваивать значение либо изменять его во время выполнения, а с константой так поступать нельзя.
Ключевые слова для типов данных
Помимо отличий между переменными и константами, существует также разница между разных типами данных. Одни данные являются числами. Другие данные представляют собой буквы или, в общем случае, символы. Компьютеру необходим способ идентификации и использования этих разных видов данных. В языке С для этого предусмотрено несколько базовых типов данных. Если данные представляют собой константы, то обычно компилятор может выяснить их тип по внешнему виду: 42 — это целое число, а 42.100 — число с плавающей запятой. Тем не менее, тип переменной должен быть указан в операторе объявления. В стандарте K&R С существовало семь ключевых слов, относящихся к типам. В стандарте С90 к этому списку были добавлены два ключевых слова. В стандарте С99 список пополнился еще тремя ключевыми словами (табл. 3.1).
Таблица 3.1. Ключевые слова для типов данных в языке С
Ключевые слова в исходном стандарте С |
Ключевые слова, добавленные стандартом С90 |
Ключевые слова, добавленные стандартом С99 |
int |
signed |
Bool |
long |
void |
Complex |
short |
|
Imaginary |
unsigned |
|
|
char |
|
|
float |
|
|
double |
|
|
Ключевым словом int обозначается основной класс целых чисел, применяемых в С. Следующие три ключевых слова (long, short и unsigned) и добавленное стандартом С90 ключевое слово signed используются для указания вариаций этого базового типа, например, unsigned short int и long long int. С помощью ключевого слова char определяются символьные данные, к которым относятся буквы алфавита и другие символы, такие как #, $, % и *. Тип данных char можно также применять для представления небольших целых чисел. Типы float, double и long double служат для представления чисел с плавающей запятой. Тип данных Bool используется для булевских значений (true и false), а типы данных Complex и Imaginary представляют, соответственно, комплексные и мнимые числа.
Типы данных, создаваемые с помощью указанных ключевых слов, могут быть разделены на два семейства на основе того, как они хранятся в памяти компьютера: целочисленные типы и типы с плавающей запятой.
Биты, байты и слова
Для описания элементов компьютерных данных или элементов компьютерной памяти могут применяться термины бит, байт и слово. Второму термину уделяется основное внимание.
Минимальная единица памяти называется битом, который может хранить одно из двух значений: 0 или 1. (Иногда говорят, что бит “включен” или “выключен”.) Конечно, в одном бите много информации сохранить не получится, но в компьютере их имеется огромное количество. Бит является базовым строительным блоком для памяти компьютера.
Байт — это наиболее часто используемая единица памяти компьютера. Практически на всех машинах байт состоит из 8 битов, и это является стандартным определением байта, по крайней мере, когда речь идет об измерении объема памяти. Поскольку бит может принимать значение 0 или 1, байт обеспечивает 256 (т.е. 28) возможных комбинаций нулей и единиц. Эти комбинации могут использоваться, например, для представления целых чисел от 0 до 255 или набора символов. Числа можно записывать посредством двоичного кода, в котором для представления чисел применяются только нули и единицы.
Слово — это естественная единица памяти для компьютера конкретного типа. В 8-разрядных микрокомпьютерах, таких как первые машины Apple, слово состояло из 8 битов. С тех пор персональные компьютеры перешли на 16-битные, 32-битные, а в настоящее время и 64-битные слова. Большие размеры слова позволяют быстрее передавать данные и делают доступным больший объем памяти.
Сравнение целочисленных типов и типов с плавающей запятой
Для человека различие между целыми числами и числами с плавающей запятой отражено в способе их написания. Для компьютера это различие проявляется в способе, которым они хранятся в памяти. Давайте взглянем по очереди на оба класса данных.
Целые числа
Целое число — это число без дробной части. В языке С целое число никогда не записывается с десятичной точкой, например, 2, -23 и 2456. Числа наподобие 3.14, 0.22 и 2.000 целыми не являются. Целые числа хранятся в двоичной форме. Например, целое число 7 записывается в двоичной форме как 111. Следовательно, чтобы сохранить это число в 8-битном байте, нужно просто установить первые 5 битов в 0, а последние три бита — в 1 (рис. 3.2).
Числа с плавающей запятой
Число с плавающей запятой более или менее соответствует тому, что математики называют вещественным числом. К вещественным числам относятся числа, находящиеся в промежутках между целыми числами. Примерами чисел с плавающей запятой могут служить 2.75, 3.16Е7, 7.00 и 2е-8. Обратите внимание, что добавление десятичной точки превращает целое число в число с плавающей запятой. Таким образом, 7 имеет целочисленный тип, но 7.00 — тип с плавающей запятой. Очевидно, что существует более одной формы записи числа с плавающей запятой. Более подробно экспоненциальную форму записи чисел мы обсудим позже, но если кратко, то запись 3.16Е7 означает, что число 3.16 необходимо умножить на 107, т.е. на число, состоящее из единицы с последующими семью нулями. Число 7 называется порядком числа 10.
Ключевым моментом здесь является то, что схема, используемая для хранения числа с плавающей запятой, отличается от схемы, которая применяется для хранения целого числа. Число с плавающей запятой разделяется на дробную часть и порядок, которые хранятся отдельно. Таким образом, 7.00 будет храниться в памяти не в том виде, в каком хранится целое число 7, хотя оба они имеют одно и то же значение. Десятичным аналогом записи 7.00 может быть 0.7Е1. Здесь 0.7 — дробная часть числа, а 1 — порядок. На рис. 3.3 показан еще один пример хранения числа с плавающей запятой. Разумеется, для хранения компьютер использует двоичные числа и степени 2, а не степени 10. А теперь сосредоточим внимание на практических различиях.
Целое число не имеет дробной части; число с плавающей запятой может иметь дробную часть.
Диапазон допустимых значений у чисел с плавающей запятой гораздо шире диапазона допустимых значений у целых чисел.
При выполнении некоторых арифметических операций с плавающей запятой, таких как вычитание одного большого числа из другого, возможна значительная потеря точности.
Поскольку в любом диапазоне чисел имеется бесконечное количество вещественных чисел, например, в диапазоне между 1.0 и 2.0, применяемые в компьютере числа с плавающей запятой не могут представить все числа этого диапазона. Числа с плавающей запятой часто являются приближениями настоящих значений. Например, 7.0 может быть сохранено как значение с плавающей запятой 6.99999.
Раньше операции над числами с плавающей запятой выполнялись значительно медленнее операций над целыми числами. Однако многие современные ЦП содержат процессоры с плавающей запятой, которые сводят на нет эту проблему.
Базовые типы данных языка с
Давайте взглянем на особенности базовых типов данных, используемых в языке С. Для каждого типа мы покажем, как объявлять переменные и представлять константы с фиксированными значениями наподобие 5 или 2.78, а также продемонстрируем типичные случаи их применения. Некоторые старые компиляторы С поддерживают не все эти типы данных, поэтому выясните в документации, какие типы данных доступны в компиляторе.
Тип int
Язык С предлагает множество целочисленных типов, и вы, скорее всего, хотите знать, почему одного типа оказалось не достаточно. Дело в том, что язык С предоставляет программисту возможность сопоставления типа с конкретным случаем использования. В частности, целочисленные типы С варьируются в диапазонах допустимых значений и в возможности применения отрицательных чисел. Тип int является базовым выбором, но если вам потребуются другие варианты, удовлетворяющие требованиям определенной задачи или компьютера, то они также доступны.
Тип int представляет целое число со знаком. Это значит, что оно должно быть целым и может иметь положительную, отрицательную или нулевую величину. Диапазон возможных значений зависит от компьютерной системы. Обычно для хранения данных типа int используется одно машинное слово. Поэтому в компьютерах, совместимых со старыми моделями IBM PC с 16-битными словами, для хранения данных типа int выделялось 16 битов. Это позволяло иметь диапазон значений от -32 7 68 до 327 67. Современные персональные компьютеры обычно оперируют 32-битными целыми числами и данные типа int соответствуют такому размеру. В настоящее время индустрия персональных компьютеров сориентировалась на выпуск 64-разрядных процессоров, которые могут свободно манипулировать еще большими целыми числами. В стандарте ISO С указано, что минимальным диапазоном для типа int должен быть от -32767 до 32767. Обычно системы представляют целые числа со знаком за счет использования значения определенного бита.
Объявление переменной типа int
Для объявления целочисленных переменных применяется ключевое слово int. Сначала указывается ключевое слово int, затем выбранное имя для переменной и, наконец, точка с запятой. Объявление более одной переменной можно делать либо по отдельности, либо поместить после ключевого слова int список имен, отделяя их друг от друга запятыми. Ниже показаны примеры допустимых объявлений:
int erns;
int hogs, cows, goats;
Для каждой переменной можно было бы предусмотреть отдельное объявление или же объявить все четыре переменных в одном операторе. Результат будет таким же: связывание имен с выделенными областями памяти для четырех переменных типа int.
Эти объявления создают переменные, но не присваивают им значения. Каким образом переменные получают значения? Вы уже видели два способа, с помощью которых переменные могут получать значения в программе. Первый способ — оператор присваивания:
cows = 112;
Второй способ предусматривает получение переменной значения из функции, например, из scanf (). А теперь рассмотрим третий способ.
Инициализация переменных
Инициализация переменной означает присваивание ей начального значения. В языке С это можно делать в виде части объявления. Достаточно после имени переменной поместить операцию присваивания (=) и указать значение, которое переменная должна получить. Вот несколько примеров:
int hogs = 21;
int cows = 32, goats = 14;
int dogs, cats = 94; /* допустимая, но неудачная форма */
В последней строке инициализируется только переменная cats. Однако по невнимательности может показаться, что переменная dogs также инициализируется значением 94, поэтому лучше избегать использования в одном операторе объявления инициализированных и неинициализированных переменных.
Выражаясь кратко, эти объявления выделяют и помечают для переменных области хранения, а также присваивают им начальные значения (рис. 3.4).
Вывод значений типа int
Для вывода значений типа int можно применять функцию printf (). Конструкция %d служит для указания в строке места, где будет выводиться целое число. Конструкция %d называется спецификатором формата, поскольку она определяет формат, используемый функцией printf () для отображения конкретного значения. Каждому спецификатору %d в строке формата должно соответствовать значение int в списке выводимых элементов. Таким значением может быть переменная int, константа int или любое другое выражение int. Программист должен следить за тем, чтобы количество спецификаторов формата соответствовало числу значений, потому что компилятор не обнаруживает ошибки подобного рода. В листинге 3.2 представлена простая программа, которая инициализирует переменную, а затем выводит значение этой переменной, значение константы и значение простого выражения. Вдобавок она демонстрирует, что происходит в случае невнимательности.
Листинг 3.2. Программа printl.с
/* printl.с — демонстрирует некоторые свойства функции printfO */
#include <stdio.h>
int main (void)
{
int ten = 10;
int two = 2;
printf("Выполняется правильно: ");
printf("%d минус %d равно %d\n", ten, 2, ten - two );
printf("Выполняется неправильно: ") ; ") ;
printf("%d минус %d равно %d\n", ten ) ; // пропущены 2 аргумента
return 0;
}
Компиляция и запуск этой программы ведет к получению следующего вывода:
Выполняется правильно: 10 минус 2 равно 8
Выполняется неправильно: 10 минус 16 равно 1650287143
В первой строке вывода первый спецификатор %d представляет переменную ten типа int, второй — константу 2 типа int и третий — значение выражения ten - two типа int. Однако во второй строке переменная ten применяется для предоставления значения только первому спецификатору %d, а для последующих двух спецификаторов %d значений не предусмотрено, поэтому программа использует для них случайные значения, находящиеся в памяти! (На своем компьютере вы можете получить результат, сильно отличающийся от полученного в этом примере. Может отличаться не только содержимое памяти, но также разные компиляторы будут управлять ячейками памяти по-разному.)
Вас может раздражать тот факт, что компилятор не выявляет настолько очевидную ошибку. Причина связана с необычным поведением функции printf (). Большинство функций принимают заданное количество аргументов, и компилятор может проверить правильность указанного числа аргументов. Однако функция printf () может принимать один, два, три и большее количество аргументов, и это препятствует применению компилятором обычных методов обнаружения ошибок такого рода. Однако некоторые компиляторы будут использовать нестандартные методы проверки, и предупреждать о возможных неправильных действиях. Тем не менее, лучше всего всегда проверять в функции printf () соответствие количества спецификаторов формата количеству значений, которые подлежат отображению на экране.
Восьмеричные и шестнадцатеричные числа
Обычно в языке С предполагается, что целочисленные константы являются десятичными числами (по основанию 10). Однако у многих программистов пользуются популярностью восьмеричные (по основанию 8) и шестнадцатеричные (по основанию 16) числа. Поскольку 8 и 16 представляют собой степени числа 2, а 10 — нет, восьмеричная и шестнадцатеричная системы счисления более удобны для представления чисел, связанных с компьютерами. Например, число 65536, которое часто встречается в 16-разрядных машинах, в шестнадцатеричной форме записывается как 10000. Кроме того, каждая цифра шестнадцатеричного числа соответствует в точности 4 битам. Например, шестнадцатеричная цифра 3 — это 0011, а шестнадцатеричная цифра 5 — это 0101. Таким образом, шестнадцатеричному значению 35 соответствует битовая комбинация 0011 0101, а шестнадцатеричному значению 53 — 010 1 0011. Такое соответствие позволяет облегчить переход от шестнадцатеричного представления числа к двоичному представлению (по основанию 2) и обратно. Но каким образом компьютер может определить, в какой форме записано число 10000 — в десятичной, шестнадцатеричной или восьмеричной? В языке С система счисления задается с помощью специального префикса. Префикс Ох или ОХ означает, что вы указываете шестнадцатеричное число, поэтому 16 в шестнадцатеричном виде записывается как 0x10 или 0X10. Аналогично, префикс 0 (ноль) означает, что задается восьмеричное число. Например, в С десятичное число 16 в восьмеричном виде записывается как 020. Вы должны понимать, что возможность применения разных систем счисления является лишь удобством для программистов. Это не влияет на способ хранения числа в памяти. Другими словами, вы можете написать 16, 020 или 0x10, и это число в каждом случае будет храниться в памяти одинаковым образом — в двоичном коде, используемом внутри компьютера.
Отображение восьмеричных и шестнадцатеричных чисел
Язык С позволяет не только записывать число в любой из трех систем счисления, но и отображать его во всех них. Чтобы вывести на экран целое число в восьмеричном, а не десятичном виде, вместо М применяйте спецификатор %о. Для отображения целого числа в шестнадцатеричном виде служит спецификатор %х. Если вы хотите вывести на экран префиксы языка С, воспользуйтесь спецификаторами %#о, %#х и %#Х, которые позволяют отображать префиксы 0, Ох и ОХ. В листинге 3.3 приведен небольшой пример. (Вспомните, что в некоторых интегрированных средах разработки может потребоваться вставить в программу оператор getchar (); , чтобы предотвратить немедленное закрытие окна выполнения программы.)
Листинг 3.3. Программа bases.с
/* bases.с -- выводит число 100 в десятичной, восьмеричной и шестнадцатеричной форме */
#include <stdio.h>
