Курс на Си. Подбельский. Курс программирования на Си. В., Фомин С. С. Курс программирования на языке Си Учебник
Скачать 1.57 Mb.
|
Строки, или строковые константы. Формально строки (в соответствии со стандартом) не относятся к константам языка Си, а представляют собой отдельный тип его лексем. Для них в литературе используется еще одно название - «строковые литералы». Строковая константа определяется как последовательность символов (см. выше символьные константы), заключенная в двойные кавычки (не в апострофы): "Образец строки" Среди символов строки могут быть эскейп-последовательности, то есть сочетания знаков, соответствующие неизображаемым символам, или символам, задаваемым их внутренними кодами. В этом случае, как и в представлениях отдельных символьных констант, их изображения начинаются с обратной косой черты ' \' : "\n Текст \n разместится \n в 3-х строках дисплея" Представления строковых констант в памяти ЭВМ подчиняются следующим правилам. Все символы строки размещаются подряд, и каждый символ (в том числе представленный эскейп-последова- тельностью) занимает ровно 1 байт. В конце записи строковой константы компилятор помещает символ '\0'. Таким образом, количество байтов, выделяемое в памяти ЭВМ для представления значения строки, ровно на 1 больше, чем число символов в записи этой строковой константы: "Эта строка занимает в памяти ЭВМ 43 байта." "Строка в 18 байт." При работе с символьной информацией нужно помнить, что длина константы 'F' равна 1 байту, а длина строки "F" равна 2 байтам. При записи строковых констант возможно размещение одной константы в нескольких строках текстового файла с программой. Для этого используется следующее правило. Если в последовательности символов (литер) константы встречается литера '\', за которой до признака '\n' конца строки текстового файла размещены только пробелы, то эти пробелы вместе с символом '\' и окончанием '\n' удаляются, и продолжением строковой константы считается следующая строка текста. Например, следующий текст представляет одну строковую константу: "Шалтай-Болтай \ сидел на стене." В программе эта константа будет эквивалентна такой: "Шалтай-Болтай сидел на стене." Начальные (левые) пробелы в продолжении константы на новой строке не удаляются, а считаются входящими в строковую константу. Две строковые константы, между которыми нет других разделителей, кроме обобщенных пробельных символов (пробел, табуляция, конец строки и т. д.), воспринимаются как одна строковая константа. Таким образом, "Шалтай-Болтай" " свалился во сне." Воспринимается как одна константа: "Шалтай-Болтай свалился во сне." Тем же правилам подчиняются и строковые константы, размещенные на разных строках. Как одна строка будет воспринята последовательность "Вся королевская " "конница, "вся королевская " "рать" Эти четыре строковые константы эквивалентны одной: "Вся королевская конница, вся королевская рать" Обратите внимание, что в результирующую строку здесь не включаются начальные пробелы перед каждой константой-продолжением. Переменные и именованные константы Переменная как объект. Одним из основных понятий языка Си является объект - именованная область памяти. Частный случай объекта - переменная. Отличительная особенность переменной состоит в возможности связывать с ее именем различные значения, совокупность которых определяется типом переменной. При задании значения переменной в соответствующую ей область памяти помещается код этого значения. Доступ к значению переменной наиболее естественно обеспечивает ее имя, а доступ к участку памяти возможен только по его адресу. О взаимосвязях имен и адресов будет подробно говориться в главе, посвященной указателям и работе с памятью ЭВМ. Для целей первых глав будет вполне достаточно интерпретировать понятие переменной как пару «имя - значение». Определение переменных. Каждая переменная перед ее использованием в программе должна быть определена, то есть для переменной должна быть выделена память. Размер участка памяти, выделяемой для переменной, и интерпретация содержимого зависят от типа, указанного в определении переменной. В соответствии с типами значений, допустимых в языке Си, рассмотрим символьные, целые и вещественные переменные автоматической памяти. О классах памяти (один из которых - класс автоматической памяти) будем подробно говорить позже. Сейчас достаточно ввести только переменные автоматической памяти, которые существуют в том блоке, где они определены. В наиболее распространенном случае таким блоком является текст основной (main) функции программы. Простейшая форма определения переменных: тип список_имен_переменных, где имена переменных - это выбранные программистом идентификаторы, которые в списке разделяются запятыми; тип - один из уже упоминаемых (в связи с константами) типов. Определены целочисленные типы (перечислены в порядке неубывания длины внутреннего представления): char - целый длиной не менее 8 бит; short int - короткий целый (допустима аббревиатура short); int - целый; long - длинный целый. Каждый из целочисленных типов может быть определен либо как знаковый signed, либо как беззнаковый unsigned (по умолчанию signed). Различие между этими двумя типами - в правилах интерпретации старшего бита внутреннего представления. Спецификатор signed требует, чтобы старший бит внутреннего представления воспринимался как знаковый; unsigned означает, что старший бит внутреннего представления входит в код представляемого числового значения, которое считается в этом случае беззнаковым. Выбор знакового или беззнакового представления определяет предельные значения, которые можно представить с помощью описанной переменной. Например, на современном ПК переменная типа unsigned int позволяет представить числа от 0 до 65535, а переменной типа signed int (или просто int) соответствуют значения в диапазоне от -32768 до +32767. Чтобы глубже понять различие между целой величиной и целой величиной без знака, следует обратить внимание на результат выполнения унарной операции "-" (минус) над целой величиной и целой величиной без знака. Для целой величины результат очевиден и тривиален. Результатом при использовании целой величины без знака является 2n - (значение_величины_без_знака), где n - количество разрядов, отведенное для представления величины без знака. По умолчанию при отсутствии в качестве префикса ключевого слова unsigned любой целый тип считается знаковым (signed). Таким образом, употребление совместно со служебными словами char, short, int, long префикса signed излишне. Допустимо отдельное использование обозначений (спецификаторов) «знаковости». При этом signed эквивалентно signed int; unsigned эквивалентно unsigned int. Примеры определений целочисленных переменных: char symbol, cc; unsigned char code; int number, row; unsigned long long_number; Обратите внимание на необходимость символа «точка с запятой» в конце каждого определения. Стандартом языка введены следующие вещественные типы: float - вещественный одинарной точности; double - вещественный удвоенной точности; long double - вещественный максимальной точности. Значения всех вещественных типов в ЭВМ представляются с «плавающей точкой», то есть с мантиссой и порядком, как было рассмотрено при определении констант (§1.2). Примеры определений вещественных переменных: float x, X, cc3, pot_8; double e, Stop, B4; Предельные значения переменных. Предельные значения констант (и соответствующих переменных) разработчики компиляторов вправе выбирать самостоятельно, исходя из аппаратных возможностей компьютера. Однако при такой свободе выбора стандарт языка требует, чтобы для значений типа short и int было отведено не менее 16 бит, для long - не менее 32 бит. При этом размер long должен быть не менее размера int, а int - не менее short. Предельные значения арифметических констант и переменных для большинства компиляторов, реализованных на современных ПК, приведены в табл. 1.3. Таблица 1.3. Основные типы данных
Предельные значения вещественных переменных совпадают с предельными значениями соответствующих констант (см., например, табл. 1.2). Предельные значения целочисленных переменных совпадают с предельными значениями соответствующих констант (см. табл. 1.1). Таблица 1.3 содержит и предельные значения для тех типов, которые не включены в табл. 1.1. Требования стандарта отображают таблицы приложения 2. Инициализация переменных. В соответствии с синтаксисом языка переменные автоматической памяти после определения по умолчанию имеют неопределенные значения. Надеяться на то, что они равны, например, 0, нельзя. Однако переменным можно присваивать начальные значения, явно указывая их в определениях: тип имя_ переменной=начальное_значение; Этот прием назван инициализацией. В отличие от присваивания, которое осуществляется в процессе выполнения программы, инициализация выполняется при выделении для переменной участка памяти. Примеры определений с инициализацией: float pi=3.1415, cc=1.23; unsigned int year=1997; Именованные константы. В языке Си, кроме переменных, могут быть определены константы, имеющие фиксированные названия (имена). В качестве имен констант используются произвольно выбираемые программистом идентификаторы, не совпадающие с ключевыми словами и с другими именами объектов. Традиционно принято, что для обозначений констант выбирают идентификаторы из больших букв латинского алфавита и символов подчеркивания. Такое соглашение позволяет при просмотре большого текста программы на языке Си легко отличать имена переменных от названий констант. Первая возможность определения именованных констант была проиллюстрирована в §1.2, посвященном константам. Это перечисляемые константы, вводимые с использованием служебного слова enum. Вторую возможность вводить именованные константы обеспечивают определения такого вида: const тип имя_константы=значение_константы; Здесь const - квалификатор типа, указывающий, что определяемый объект имеет постоянное значение, то есть доступен только для чтения; тип - один из типов объектов; имя_константы - идентификатор; значение_константы должно соответствовать ее типу. Примеры: const double E=2.718282; const long M=99999999; const F=765; В последнем определении тип константы не указан, по умолчанию ей приписывается тип int. Третью возможность вводить именованные константы обеспечивает препроцессорная директива #define имя_константы значение_константы Обратите внимание на отсутствие символа «точка с запятой» в конце директивы. Подробному рассмотрению директивы #define будут посвящены два параграфа главы 3. Здесь мы только упоминаем о возможности с ее помощью определять именованные константы. Кроме того, отметим, что в конкретные реализации компиляторов с помощью директив #define включают целый набор именованных констант с фиксированными именами (см. главу 3 и приложение 2). Отличие определения именованной константы const double E=2.718282; от определения препроцессорной константы с таким же значением #define EULER 2.718282 состоит внешне в том, что в определении константы E явно задается ее тип, а при препроцессорном определении константы EULER ее тип определяется «внешним видом» значения константы. Например, следующее определение #define NEXT 'Z' вводит обозначение NEXT для символьной константы 'Z'. Это соответствует такому определению: const char NEXT = 'Z'; Однако различия между обычной именованной константой и пре- процессорной константой, вводимой директивой #define, гораздо глубже и принципиальнее. До начала компиляции текст программы на языке Си обрабатывается специальным компонентом транслятора - препроцессором. Если в тексте встречается директива #define EULER 2.718282 а ниже ее в тексте используется имя константы EULER, например в таком виде: double mix = EULER; d = alfa*EULER; то препроцессор заменит каждое обозначение EULER на ее значение и сформирует такой текст: double mix = 2.718282; d = alfa*2.718282; Далее текст от препроцессора поступает к компилятору, который уже «и не вспомнит» о существовании имени EULER, использованного в препроцессорной директиве #define. Константы, определяемые на препроцессорном уровне с помощью директивы #define, очень часто используются для задания размеров массивов, что будет продемонстрировано позже. Итак, основное отличие констант, определяемых препроцессор- ными директивами #define, состоит в том, что эти константы вводятся в текст программы до этапа ее компиляции. Специальный компонент транслятора - препроцессор - обрабатывает исходный текст программы, подготовленный программистом, и делает в этом тексте замены и подстановки. Пусть в исходном тексте встречается директива: #define ZERO 0.0 Это означает, что каждое последующее использование в тексте программы имени ZERO будет заменяться на 0.0. Рисунок 1.1 иллюстрирует некоторые принципы работы препроцессора. Его основное отличие от других компонентов транслятора - обработка программы выполняется только на уровне ее текста. На входе препроцессора - текст с препроцессорными директивами, на выходе препроцессора - модифицированный текст без препро- цессорных директив. Этот выходной модифицированный текст изменен по сравнению с входным текстом за счет выполнения препро- цессорных директив, но сами препроцессорные директивы в выходном тексте отсутствуют. Полностью все препроцессорные директивы будут рассмотрены позже в главе 3. В связи с именованными конс- Текст до препроцессора (исходный текст программы): //define PI 3.141593 //define ZERO 0.0 if (r>ZERO)/* Сравнение с константой ZERO */ /* Длина окружности радиуса г:*/ D=2*PI*r; Текст после препроцессора: if (г>0.0)/* Сравнение с константой ZERO */ /*Длина окружности радиуса г:*/ D=2*3.141593*r; Рис. 1.1. Обработка текста программы препроцессором тантами здесь рассматривается только одна из возможностей директивы #define - простая подстановка. Имена PI и ZERO (см. рис. 1.1) после работы препроцессора заменены в тексте программы на определенные в двух директивах #define значения (3.141593 и 0.0). Обратите внимание, что подстановка не выполняется в комментариях и в строковых константах. В примере на рис. 1.1 идентификатор ZERO остался без изменений в комментарии (/* Сравнение с константой ZERO */). Именно с помощью набора именованных препроцессорных констант стандарт языка Си рекомендует авторам компиляторов определять предельные значения всех основных типов данных. Для этого в языке определен набор фиксированных имен, каждое из которых является именем одной из констант, определяющих то или иное предельное значение. Например: |