Некоторые неграфические символы могут быть представлены как символьные константы с помощью эскайп-последовательностей, как, например, \n (новая строка). Хотя они выглядят как два символа, на самом деле являются одним. Кроме того, можно сгенерировать произвольную последовательность двоичных знаков размером в байт, если написать '\ooo', где ooo – от одной до трех восьмеричных цифр (0, 1,…,7), или '\xhh', где hhh – одна, две или более шестнадцатиричных цифр (0, 1, …, F).
Например, в коде ASCII:
#define vtab '013' // Вертикальная табуляция
#define bell '\017' // Звоночек
или в шестнадцатиричном виде:
#define vtab '\xb' // Вертикальная табуляция
#define bell '\x7' // Звоночек
Полный набор эскайп-последовательностей таков:
Символ | Значение | Символ | |
\a | Сигнал “звоночек” | \ | Обратная косая черта |
\v | Вертикальная табуляция | /? | Знак вопроса |
\n | Новая строка | \' | Ковычка |
\f | Перевод страницы | \" | Двойная ковычка |
\r | Возврат каретки | \ooo | Восьмеричный код |
\t | Горизонтальная табуляция | \xhh | Шестнадцатиричный код |
Символьная константа '\0', изображающая символ со значением 0, часто записывается вместо целой константы 0 , чтобы подчеркнуть символьную природу некоторого выражения.
3.3.2. Константное выражение. Константное выражение – это выражение, состоящее из одних констант. Такие выражения обрабатываются во время компиляции, а не при прогоне программы, и соответственно могут быть использованы в любом месте, где можно использовать константу, как, например в
#define maxline 1000
char line[maxline+1];
или
seconds = 60 * 60 * hours;
3.3.3. Строчная константа. Строчная константа – это последовательность, состоящая из нуля или более символов, заключенных в двойные кавычки, как, например,
"I am a string" // Я - строка
или
"" // Null string – нулевая строка
Кавычки не являются частью строки, а служат только для ее ограничения. Те же самые условные последовательности, которые использовались в символьных константах, применяются и в строках; символ двойной кавычки изображается как \".
С технической точки зрения строка представляет собой массив, элементами которого являются отдельные символы. Чтобы программам было удобно определять конец строки, компилятор автоматически помещает в конец каждой строки нуль-символ \0. Такое представление означает, что не накладывается конкретного ограничения на то, какую длину может иметь строка, и чтобы определить эту длину, программы должны просматривать строку полностью. При этом для физического хранения строки требуется на одну ячейку памяти больше, чем число заключенных в кавычки символов.
Пример 3-1. Следующая функция strlen(s) вычисляет длину символьной строки s не считая конечный символ \0.
strlen(char s[]) // Возвращает длину строки
{
int i;
i = 0;
while (s[i] != '\0')
++i;
return(i);
}
Будьте внимательны и не путайте символьную константу со строкой, содержащей один символ: 'x' - это не то же самое, что "x". Первое – это отдельный символ, использованный с целью получения численного значения, соответствующего букве х в машинном наборе символов. Второе – символьная строка, состоящая из одного символа (буква х) и \0.
Все переменные должны быть описаны до их использования, хотя некоторые описания делаются неявно, по контексту. Описание состоит из спецификатора типа и следующего за ним списка переменных, имеющих этот тип, как, например,
int lower, upper, step;
char c, line[1000];
Переменные можно распределять по описаниям любым образом; приведенные выше списки можно с тем же успехом записать в виде:
int lower;
int upper;
int step;
char c;
char line[1000];
Такая форма занимает больше места, но она удобна для добавления комментария к каждому описанию и для последующих модификаций.
Переменным могут быть присвоены начальные значения внутри их описания, хотя здесь имеются некоторые ограничения. Если за именем переменной следуют знак равенства и константа, то эта константа служит в качестве инициализатора, как, например, в
char backslash = '\';
int i = 0;
float eps = 1.0e–5;
Если рассматриваемая переменная является внешней или статической, то инициализация проводится только один раз, согласно концепции до начала выполнения программы. Инициализируемым явно автоматическим переменным начальные значения присваиваются при каждом обращении к функции, в которой они описаны. Автоматические переменные, не инициализируемые явно, имеют неопределенные значения, (т.е. мусор). Внешние и статические переменные по умолчанию инициализируются нулем, но, тем не менее, их явная инициализация является признаком хорошего стиля.
Мы продолжим обсуждение вопросов инициализации, когда будем описывать новые типы данных.
Бинарными арифметическими операциями являются +, –, *, / и операция деления по модулю %. Имеется унарная операция – , но не существует унарной операции +.
При делении целых дробная часть отбрасывается. Выражение:
x % y
дает остаток от деления x на y и, следовательно, равно нулю, когда х делится на y точно. Например, год является високосным, если он делится на 4, но не делится на 100, исключая то, что делящиеся на 400 годы тоже являются високосными. Поэтому
if(year%4 == 0 && year%100 != 0 || year%400 == 0)
год високосный;
else
год невисокосный;
Операцию % нельзя использовать с типами float или double. Операции + и – имеют одинаковое старшинство, которое младше одинакового уровня старшинства операций *, / и %, которые в свою очередь младше унарного минуса. Арифметические операции группируются слева направо. (Сведения о старшинстве и ассоциативности всех операций собраны в таблице в конце этой главы). Порядок выполнения ассоциативных и коммутативных операций типа + и – не фиксируется; компилятор может перегруппировывать даже заключенные в круглые скобки выражения, связанные такими операциями.
Таким образом, а+(b+c) может быть вычислено как (a+b)+c. Это редко приводит к какому-либо расхождению, но если необходимо обеспечить строго определенный порядок, то нужно использовать явные промежуточные переменные.
Действия, предпринимаемые при переполнении и антипереполнении (т.е. при получении слишком маленького по абсолютной величине числа), зависят от используемой машины.
3.6. Операции отношения и логические операции
Операциями отношения являются
=> > =< < .
Все они имеют одинаковое старшинство. Непосредственно за ними по уровню старшинства следуют операции равенства и неравенства:
== != ,
которые тоже имеют одинаковое старшинство. Операции отношения младше арифметических операций, так что выражения типа i<lim-1 понимаются как i<(lim-1), как и предполагается.
Логические связки && и || более интересны. Выражения, связанные операциями && и ||, вычисляются слева направо, причем их рассмотрение прекращается сразу же как только становится ясно, будет ли результат истиной или ложью. Учет этих свойств очень существенен для написания правильно работающих программ. Рассмотрим, например, оператор цикла в считывающей строку функции getline, которую мы написали в главе 2:
for(i=0;i<lim-1 && (c=getchar())!='\n' && c!=eof; ++i)
s[i]=c;
Ясно, что перед считыванием нового символа необходимо проверить, имеется ли еще место в массиве s, так что условие i<lim-1 должно проверяться первым. И если это условие не выполняется, мы не должны считывать следующий символ.
Так же неудачным было бы сравнение символа в переменной c с eof до обращения к функции getchar: прежде чем проверять символ, его нужно считать.
Старшинство операции && выше, чем у ||, и обе они младше операций отношения и равенства. Поэтому такие выражения, как
i<lim-1 && (c = getchar()) != '\n' && c != eof
не нуждаются в дополнительных круглых скобках.
Но так как операция != старше операции присваивания, то для достижения правильного результата в выражении
(c = getchar()) != '\n'
скобки необходимы.
Унарная операция отрицания ! преобразует ненулевой или истинный операнд в 0, а нулевой или ложный операнд в 1. Обычное использование операции ! заключается в записи
if( ! inword ) вместо if( inword == 0 )
Tрудно сказать, какая форма лучше. Конструкции типа ! inword читаются довольно удобно («если не в слове»). Но в более сложных случаях они могут оказаться трудными для понимания.
Упражнение 3-1. Напишите оператор цикла, эквивалентный приведенному выше оператору for, не используя операции &&.
Если в выражениях встречаются операнды различных типов, то они преобразуются к общему типу в соответствии с небольшим набором правил. В общем, автоматически производятся только преобразования, имеющие смысл, такие как, например, преобразование целого в плавающее в выражениях типа f+i. Выражения же, лишенные смысла, такие как использование переменной типа float в качестве индекса, запрещены.