Хотя мы этого сразу прямо не сказали, существуют некоторые ограничения
на имена
Играют роль только первые восемь символов внутреннего имени, хотя
использовать можно и больше. Для внешних имен, таких как имена if, else, int, float и т.д.,
зарезервированы: вы не можете использовать их в качестве имен
Конечно, разумно выбирать имена
В языке C имеется только несколько основных char один
байт, в котором может находиться один символ из внутреннего набора символов. int
Целое, обычно соответствующее естественному размеру целых в используемой
машине. float С плавающей точкой одинарной точности. double С плавающей точкой
двойной точности.
Кроме того имеется ряд квалификаторов, которые можно использовать с int: short (короткое), long (длинное) и unsigned (без знака).
Квалификаторы short и long указывают на различные размеры целых. Числа без знака
подчиняются законам арифметики по модулю 2 в степени n, где n - число битов
в int ; числа без знаков всегда неотрицательны.
short int x; long int y; unsigned int z;
Cлово int в таких ситуациях может быть опущено, что обычно и делается.
Количество битов, отводимых под эти объекты зависит от имеющейся машины; в таблице ниже приведены некоторые характерные значения.
| DEC |
HONEYWELL 6000 | IBM 370 | INTERDATA 8/32 | |
|---|---|---|---|---|
char |
8-BITS | 9-BITS | 8-BITS | 8-BITS |
int |
16 | 32 | 32 | 32 |
short |
16 | 36 | 16 | 16 |
long |
32 | 36 | 32 | 32 |
float |
32 | 36 | 32 | 32 |
double |
64 | 72 | 64 | 64 |
Цель состоит в том, чтобы short и long давали возможность в зависимости
от практических нужд использовать различные длины целых; int отражает
наиболее "естественный" размер конкретной машины. Как вы видите, каждый short и long в соответствии со своими
аппаратными средствами. Все, на что вы можете твердо полагаться, это то, что short не длиннее, чем long.
int и float мы уже рассмотрели. Отметим еще только, что как
обычная
123.456е-7,
так и "научная" запись
0.12е3
для float является законной.
Каждая double, так что
обозначение " e " служит как для float, так и для double.
Длинные 123L. Обычная целая int, рассматривается как long.
Существует система обозначений для восьмеричных и шестнадцатеричных 0 (нуль) в int
указывает на восьмеричную 0x
соответствуют шестнадцатеричной 31
можно записать как 037 в восьмеричной форме и как 0x1f в шестнадцатеричной. Шестнадцатеричные и восьмеричные l, что делает их относящимися
к long.
'х' '0', имеет значение 48, а в коде - 240, и оба эти значения совершенно отличны от числа 0.
Написание '0' вместо численного значения, такого как 48 или 240, делает
Некоторые неграфические символы могут быть представлены как
\n (новая строка),\t (табуляция),\0 ( \\ (обратная косая черта),\' (одинарная кавычка) и т.д.Хотя они выглядят как два символа, на самом деле
являются одним. Кроме того, можно сгенерировать произвольную
'\ddd'
где - от одной до трех восьмеричных цифр, как в
#define formfeed '\014' /* form feed */
'\0', изображающая символ со значением 0, часто записывается вместо целой 0, чтобы подчеркнуть символьную природу
некоторого выражения.
#define maxline 1000 char line[maxline+1];
или
seconds = 60 * 60 * hours;
"i am a string" /* я - строка */ или "" /* null string */ /* нуль-строка */
Кавычки не являются частью строки, а служат только для ее ограничения. те
же самые \".
С технической точки зрения строка представляет собой \0. Такое представление означает, что не накладывается конкретного
ограничения на то, какую длину может иметь строка, и чтобы определить эту длину, strlen(s) вычисляет длину s не считая конечный символ \0.
strlen(s) /* return length of s */
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 и, следовательно, равно нулю,
когда x делится на y точно. Например, год является високосным, если он делится на 4, но не
делится на 100, исключая то, что делящиеся на 400 годы тоже являются
високосными. Поэтому
if(year % 4 == 0 year % 100 != 0 || year % 400 == 0)
год високосный
else
год невисокосный
Операцию % нельзя использовать с float
или double.
Операции + и - имеют одинаковое старшинство,
которое младше одинакового уровня старшинства
операций *, / и %, которые в свою очередь младше унарного
минуса. Арифметические операции группируются слева направо. (Сведения о
старшинстве и ассоциативности всех операций собраны в таблице в конце этой
лекции). Порядок выполнения ассоциативных и коммутативных операций + и -
не фиксируется; а+(b+c) может
быть вычислено как (a+b)+c. Это редко приводит к какому-либо расхождению, но
если необходимо обеспечить строго определенный порядок, то нужно использовать
явные промежуточные
Действия, предпринимаемые при переполнении и антипереполнении (т.е. при
получении слишком маленького по
Операциями отношения являются
>= > =< <
все они имеют одинаковое старшинство. Непосредственно за ними по уровню старшинства следуют операции равенства и неравенства:
== !=
которые тоже имеют одинаковое старшинство. операции отношения младше
арифметических операций, так что выражения i< понимаются как i<( ), как и предполагается.
Логические связки и || более интересны.
Выражения, связанные операциями и ||, вычисляются
слева направо, причем их рассмотрение
прекращается сразу же как только становится ясно, будет ли результат истиной
или ложью. Учет этих свойств очень существенен для написания правильно работающих getline, которую мы написали в лекции №1.
for(i=0;i<lim-1 (c=getchar())
!= '\n' c != EOF; ++i)
s[i]=c;
Ясно, что перед считыванием нового символа необходимо проверить, имеется
ли еще место в s, так что условие i< должно проверяться первым. И
если это условие не выполняется, мы не должны считывать следующий символ.
Так же неудачным было бы сравнение 'c' с EOF до getchar: прежде чем проверять символ, его нужно считать.
Старшинство операции выше, чем у ||, и обе они младше операций
отношения и равенства. Поэтому такие выражения, как
i<lim-1 (c = getchar()) != '\n' c != EOF
не нуждаются в дополнительных != старше
операции присваивания, то для достижения правильного результата в выражении
(c = getchar()) != '\n'
скобки необходимы.
Унарная
if( ! inword )
Вместо
if( inword == 0 )
Tрудно сказать, какая форма лучше. ! inword Читаются довольно
удобно ("если не в слове"). Но в более сложных случаях они могут оказаться
трудными для понимания.
Упражнение 2-1
Напишите for, не
используя операции .
Если в выражениях встречаются операнды различных f+i. Выражения
же, лишенные смысла, такие как использование float в
качестве индекса, запрещены.
Во-первых, char и int могут свободно смешиваться в арифметических
выражениях: каждая char автоматически преобразуется в int. Это
обеспечивает значительную гибкость при проведении atoi, которая ставит в соответствие
строке цифр ее численный эквивалент.
atoi(s) /* convert s to integer */
char s[];
{
int i, n;
n = 0;
for ( i = 0; s[i]>='0' s[i]<='9'; ++i)
n = 10 * n + s[i] - '0';
return(n);
}
Kак уже обсуждалось в лекции №1, выражение
s[i] - '0'
имеет численное значение находящегося в s[i] символа, потому что значение
символов '0', '1' и т.д. образуют возрастающую
Другой пример преобразования char
в int дает lower, преобразующая
данную прописную букву в строчную. Если выступающий в качестве lower возвращает его неизменным. Приводимая
ниже
lower(c) /* convert c to lower case; ascii only */
int c;
{
if ( c >= 'a' c <= 'z' )
return( c + '@' - 'a');
else /*@ Записано вместо 'a' строчного*/
return(c);
}
Эта а и z нет
ничего, кроме букв. Это последнее замечание для набора символов систем
IBM 360/370 оказывается несправедливым, в силу чего эта
При преобразовании символьных char
соответствовать численные значения со знаком или без знака. Может ли при
преобразовании char в int получиться отрицательное целое?
К сожалению, ответ на этот вопрос меняется от машины к машине, отражая расхождения в их
архитектуре. На некоторых машинах (char,
крайний левый бит которой содержит 1, преобразуется в отрицательное целое
("знаковое расширение"). На других машинах такое преобразование сопровождается
добавлением нулей с левого края, в результате чего всегда получается
положительное число.
Наиболее типичным примером возникновения такой ситуации является случай,
когда значение -1 используется в качестве EOF. Рассмотрим
char c; c = getchar(); if ( c == EOF) ...
На машине, которая не осуществляет знакового расширения, 'с'
всегда положительна, поскольку она описана как char,
а так как EOF отрицательно,
то условие никогда не выполняется. Чтобы избежать такой ситуации,
мы всегда предусмотрительно
использовали int вместо char для любой getchar.
Основная же причина использования int вместо char не связана с каким-либо
вопросом о возможном знаковом расширении. просто getchar должна
передавать все возможные символы (чтобы ее можно было использовать для
произвольного EOF. Следовательно
значение EOF не может быть представлено как char,
а должно храниться как int.
Другой полезной формой автоматического преобразования i>j, и логические выражения, связанные
операциями и ||, по 1, если они истинны, и 0,
если они ложны. Таким образом, присваивание
isdigit = c >= '0' c <= '9';
полагает isdigit равным 1, если с - цифра,
и равным 0 в противном случае. (В проверочной части if, while, for и т.д.
"Истинно" просто означает "не нуль").
Неявные + или *, которая связывает два
операнда (бинарная операция), имеет операнды разных
char и short преобразуются в int,
а float в double.double,
то другой преобразуется
в double, и результат имеет double.long, то другой
преобразуется в long, и результат имеет long.unsigned, то другой
преобразуется в unsigned и результат имеет unsigned.int, и результат имеет int. Подчеркнем, что все float в выражениях преобразуются в double ; в "C" вся плавающая арифметика выполняется с двойной точностью.Преобразования возникают и при присваиваниях; значение правой части
преобразуется к int
в char ведет себя хорошо - лишние
биты высокого порядка просто отбрасываются. Таким образом
int i; char c; i = c; c = i;
значение 'с' не изменяется. Это верно независимо от того, вовлекается ли
знаковое расширение или нет.
Если х float, а i int, то как
х = i; так и
i = х;
приводят к преобразованиям; при этом float преобразуется в int
отбрасыванием дробной части. double
преобразуется во float округлением. Длинные целые
преобразуются в более короткие целые и в char посредством
отбрасывания лишних битов высокого порядка.
Так как char и short
становятся int, а float становится double.
Именно поэтому мы описывали int и double даже тогда, когда обращались к ним с float.
Наконец, в любом выражении может быть осуществлено ("принуждено") явное
преобразование ). В этой
(имя типа) выражение
Выражение преобразуется к указанному sqrt ожидает double и выдаст бессмысленный
ответ, если к ней по небрежности обратятся с чем-нибудь иным. таким образом,
если n - целое, то выражение
sqrt((double) n)
до передачи sqrt преобразует n к double. (Отметим,
что операция перевод преобразует значение n в надлежащий n при этом не изменяется). Операция перевода имеет тот же уровень старшинства, что и другие унарные операции, как указывается в таблице в конце этой лекции.
Упражнение 2-2
Составьте htoi(s), которая преобразует строку
шестнадцатеричных цифр в эквивалентное ей целое значение. При этом допустимыми
цифрами являются цифры от 1 до 9 и буквы от а до F.
В языке "C" предусмотрены две необычные операции для увеличения и
уменьшения ++ добавляет 1 к своему
операнду, а операция уменьшения -- вычитает 1. Мы часто использовали операцию ++ для увеличения
if(c == '\n') ++i;
Необычный аспект заключается в том, что ++ и -- можно использовать либо
как ++n ), либо как постфиксные
(после n++ ). Эффект в обоих случаях состоит в увеличении n. Но
выражение ++n увеличивает n до использования ее значения, в то время
как n++ увеличивает n после того, как ее значение было использовано.
Это означает, что в контексте, где используется ++n и n++ приводит к разным результатам. Если n = 5, то
х = n++;
устанавливает х равным 5, а
х = ++n;
полагает х равным 6. В обоих случаях n
становится равным 6. Операции увеличения и уменьшения
можно применять только к х=(i+j)++
являются незаконными.
В случаях, где нужен только эффект увеличения, а само значение не используется, как, например, в
if ( c == '\n' )
nl++;
выбор , которая удаляет символ 'с' из строки s, каждый
раз, как он встречается.
squeeze(s,c) /* delete all c from s */
char s[];
int c;
{
int i, j;
for ( i = j = 0; s[i] != '\0'; i++)
if ( s[i] != c )
s[j++] = s[i];
s[j] = '\0';
}
Каждый раз, как встречается символ, отличный от 'с', он копируется в текущую
позицию j, и только после этого j увеличивается на 1,
чтобы быть готовым для поступления следующего символа.
Это в точности эквивалентно записи
if ( s[i] != c ) {
s[j] = s[i];
j++;
}
Другой пример подобной getline, которую мы
запрограммировали в лекции №1, где можно заменить
if ( c == '\n' ) {
s[i] = c;
++i;
}
более компактной записью
if ( c == '\n' )
s[i++] = c;
В качестве третьего примера рассмотрим , которая
приписывает строку t в конец строки s, образуя конкатенацию
строк s и t. При этом предполагается, что в s достаточно места для хранения полученной комбинации.
strcat(s,t) /* concatenate t to end of s */
char s[], t[]; /* s must be big enough */
{
int i, j;
i = j = 0;
while (s[i] != '\0') / *find end of s */
i++;
while((s[i++] = t[j++]) != '\0') /*copy t*/
;
}
Tак как из t в s копируется каждый символ, то для подготовки к следующему
прохождению цикла постфиксная операция ++ применяется к обеим i и j.
Упражнение 2-3
Напишите другой вариант , который удаляет из строки s1 каждый символ, совпадающий с каким-либо символом строки s2.
Упражнение 2-4
Напишите any(s1,s2), которая находит место первого
появления в строке s1 какого-либо символа из строки s2 и, если
строка s1 не
содержит символов строки s2, возвращает значение -1.
В языке предусмотрен ряд операций для работы с битами; эти операции нельзя
применять к float или double.
| Побитовое AND | |
| | | Побитовое включающее OR |
| ^ | побитовое исключающее OR |
| << | сдвиг влево |
| >> | сдвиг вправо |
| ~ | дополнение (унарная операция) |
" | " имитирует вертикальную черту.
Побитовая операция and часто используется для маскирования некоторого множества
битов; например,
c = n 007f
передает в 'с' семь младших битов n, полагая остальные равными
нулю. Операция '|' побитового or используется для включения битов:
c = x | mask
устанавливает на единицу те биты в х, которые равны единице в mask.
Следует быть внимательным и отличать побитовые операции и | от
логических связок и ||, которые подразумевают вычисление значения
истинности слева направо. Например, если х=1, а y=2, то значение хy равно нулю, в то время как значение xy равно единице.
Операции сдвига << и >> осуществляют
соответственно сдвиг влево и вправо
своего левого операнда на число битовых позиций, задаваемых правым операндом.
Таким образом, х<<2 сдвигает х влево на две позиции,
заполняя освобождающиеся
биты нулями, что эквивалентно умножению на 4. Сдвиг вправо величины без знака
заполняет освобождающиеся биты на некоторых машинах, таких как
Унарная операция ~ дает дополнение к целому; это означает, что каждый
бит со значением 1 получает значение 0 и наоборот. Эта операция обычно
оказывается полезной в выражениях
x ~077
где последние шесть битов х маскируются нулем. Подчеркнем, что выражение x!077 не зависит от длины слова и поэтому предпочтительнее,
чем, например, x0177700,
где предполагается, что х занимает 16 битов. Такая переносимая форма не
требует никаких дополнительных затрат, поскольку ~077 является
Чтобы проиллюстрировать использование некоторых операций с битами,
рассмотрим getbits(x,p,n), которая возвращает /сдвинутыми к правому
краю/ начинающиеся с позиции р поле х длиной n битов. Мы предполагаем, что крайний правый бит имеет номер 0, и
что n и р - разумно заданные
положительные числа. Например, getbits(х,4,3) возвращает сдвинутыми к правому
краю биты, занимающие позиции 4, 3 и 2.
getbits(x,p,n) /* get n bits from position p */
unsigned x, p, n;
{
return((x >> (p+1-n)) ~(~0 << n));
}
Операция x >> (p+1-n ) сдвигает желаемое поле в правый конец слова. x как unsigned гарантирует, что при сдвиге вправо освобождающиеся биты
будут заполняться нулями, а не содержимым ~0
равны 1 ; сдвиг его на n позиций влево с помощью операции ~0<<n создает маску с нулями в n крайних
правых битах и единицами в остальных; дополнение ~ создает
маску с единицами в n крайних правых битах.
Упражнение 2-5
Переделайте getbits таким образом, чтобы биты отсчитывались слева
направо.
Упражнение 2-6
Напишите wordlength(), вычисляющей длину слова
используемой машины, т.е. Число битов в int.
Упражнение 2-7
Напишите rightrot(n,b), сдвигающей циклически целое n вправо на b битовых позиций.
Упражнение 2-8
Напишите invert(x,p,n), которая инвертирует (т.е.
заменяет 1 на 0 и наоборот) n битов x,
начинающихся с позиции p, оставляя другие биты неизмененными.
Такие выражения, как
i = i + 2
в которых левая часть повторяется в правой части могут быть записаны в сжатой форме
i += 2
используя операцию присваивания вида +=.
Большинству бинарных операций (операций подобных +, которые имеют левый
и правый операнд) соответствует операция присваивания вида оп=,
где оп - одна из операций
+ - * / % << >> ^ |!
Если е1 и е2 - выражения, то е1 оп= е2
эквивалентно
е1 = (е1) оп (е2)
за исключением того, что выражение е1 вычисляется только один раз. Обратите
внимание на е2:
x *= y + 1
то
x = x * (y + 1)
не
x = x * y + 1
В качестве примера приведем bitcount, которая подсчитывает число
равных 1 битов у целого
bitcount(n) /* count 1 bits in n */
unsigned n;
(
int b;
for (b = 0; n != 0; n >>= 1)
if (n 01)
b++;
return(b);
)
Не говоря уже о краткости, такие i " или "увеличить i на 2",
но не "взять i, прибавить 2 и
поместить результат опять в i ". Итак, i += 2.
Кроме того, в громоздких выражениях, подобных
yyval[yypv[p3+p4] + yypv[p1+p2]] += 2
Tакая операция присваивания облегчает понимание
Мы уже использовали тот факт, что операция присваивания имеет некоторое значение и может входить в выражения; самый типичный пример
while ((c = getchar()) != EOF)
присваивания, использующие другие операции присваивания ( +=, -= и т.д.) также
могут входить в выражения, хотя это случается реже.
Упражнение 2-9
В двоичной системе счисления операция x(x-1)
обнуляет самый правый равный 1 бит x.(почему?)
используйте это замечание для написания более
быстрой версии bitcount.
if (a > b)
z = a;
else
z = b;
конечно вычисляют в z максимум из а и в.
Условное выражение, записанное с помощью "?:", предоставляет другую возможность для записи
этой и аналогичных
е1 ? Е2 : е3
сначала вычисляется выражение е1. Если оно отлично от нуля (истинно), то
вычисляется выражение е2, которое и становится значением условного выражения. В
противном случае вычисляется е3, и оно становится значением условного
выражения. Каждый раз вычисляется только одно из выражения е2 и е3.
Таким образом, чтобы положить z равным максимуму из а и в,
можно написать
z = (a > b) ? a : b; /* z = max(a,b) */
Следует подчеркнуть, что условное выражение действительно является
выражением и может использоваться точно так же, как любое другое выражение. Если е2 и е3 имеют разные f имеет float, а n - int, то выражение
(n > 0) ? f : n
Имеет double независимо от того, положительно ли n или нет.
Так как уровень старшинства операции ?: очень низок, прямо над
присваиванием, то первое выражение в условном выражении можно не заключать в
Использование условных выражений часто приводит к коротким n элементов
for (i = 0; i < n; i++)
printf("%6d%c",a[i],(i%10==9 || i==n-1) ? '\n' : ' ')
Символ перевода строки записывается после каждого десятого элемента и после n -го
элемента. За всеми остальными элементами следует один пробел. Хотя, возможно,
это выглядит мудреным, было бы поучительным попытаться записать это, не используя
условного выражения.
Упражнение 2-10
Перепишите lower, которая переводит прописные буквы в
строчные, используя вместо if - else условное выражение.
В приводимой ниже таблице сведены правила старшинства и ассоциативности всех
операций, включая и те, которые мы еще не обсуждали. Операции, расположенные в
одной строке, имеют один и тот же уровень старшинства; строки расположены в
порядке убывания старшинства. Так, например, операции *, /
и % имеют одинаковый
уровень старшинства, который выше, чем уровень операций + и -.
| OPERATOR | ASSOCIATIVITY |
|---|---|
| () [] -> . | LEFT TO RIGHT |
| ! ^ ++ -- - (TYPE) * SIZEOF | RIGHT TO LEFT |
| * / % | LEFT TO RIGHT |
| + - | LEFT TO RIGHT |
| << >> | LEFT TO RIGHT |
| < <= > >= | LEFT TO RIGHT |
| == != | LEFT TO RIGHT |
| LEFT TO RIGHT | |
| ^ | LEFT TO RIGHT |
| | | LEFT TO RIGHT |
| LEFT TO RIGHT | |
| || | LEFT TO RIGHT |
| ?: | RIGHT TO LEFT |
| = += -= ETC. | RIGHT TO LEFT |
| , (CHAPTER 3) | LEFT TO RIGHT |
Операции . Используются для доступа к элементам sizeof (размер объекта).
В лекции №5 обсуждаются
операции * (косвенная
( , ^ и '
ниже уровня операций == и !=. Это
приводит к тому, что осуществляющие побитовую проверку выражения, подобные
if ((x mask) == 0) ...
Для получения правильных результатов должны заключаться в
Как уже отмечалось ранее, выражения, в которые входит одна из
ассоциативных и коммутативных операций ( *, +, , ^, ' ), могут
перегруппировываться, даже если они заключены в
В языке "C", как и в большинстве языков, не фиксируется порядок вычисления
операндов в
x = f() + g();
сначала может быть вычислено f, а потом g,
и наоборот; поэтому, если либо f,
либо g изменяют x может зависеть от порядка вычислений. Для обеспечения нужной
Подобным же образом не фиксируется порядок вычисления
printf("%d %d\n",++n,power(2,n));
может давать (и действительно дает) на разных машинах разные результаты в
зависимости от того, увеличивается ли n до или после power.
Правильным решением, конечно, является запись
++n; printf("%d %d\n",n,power(2,n));
a[i] = i++;
Возникает вопрос, старое или новое значение i служит в качестве индекса.
Из этих рассуждений вытекает такая мораль: написание lint укажет большинство мест,
зависящих от порядка вычислений.
Хотя мы этого сразу прямо не сказали, существуют некоторые ограничения
на имена
Играют роль только первые восемь символов внутреннего имени, хотя
использовать можно и больше. Для внешних имен, таких как имена if, else, int, float и т.д.,
зарезервированы: вы не можете использовать их в качестве имен
Конечно, разумно выбирать имена
В языке C имеется только несколько основных char один
байт, в котором может находиться один символ из внутреннего набора символов. int
Целое, обычно соответствующее естественному размеру целых в используемой
машине. float С плавающей точкой одинарной точности. double С плавающей точкой
двойной точности.
Кроме того имеется ряд квалификаторов, которые можно использовать с int: short (короткое), long (длинное) и unsigned (без знака).
Квалификаторы short и long указывают на различные размеры целых. Числа без знака
подчиняются законам арифметики по модулю 2 в степени n, где n - число битов
в int ; числа без знаков всегда неотрицательны.
short int x; long int y; unsigned int z;
Cлово int в таких ситуациях может быть опущено, что обычно и делается.
Количество битов, отводимых под эти объекты зависит от имеющейся машины; в таблице ниже приведены некоторые характерные значения.
| DEC |
HONEYWELL 6000 | IBM 370 | INTERDATA 8/32 | |
|---|---|---|---|---|
char |
8-BITS | 9-BITS | 8-BITS | 8-BITS |
int |
16 | 32 | 32 | 32 |
short |
16 | 36 | 16 | 16 |
long |
32 | 36 | 32 | 32 |
float |
32 | 36 | 32 | 32 |
double |
64 | 72 | 64 | 64 |
Цель состоит в том, чтобы short и long давали возможность в зависимости
от практических нужд использовать различные длины целых; int отражает
наиболее "естественный" размер конкретной машины. Как вы видите, каждый short и long в соответствии со своими
аппаратными средствами. Все, на что вы можете твердо полагаться, это то, что short не длиннее, чем long.
int и float мы уже рассмотрели. Отметим еще только, что как
обычная
123.456е-7,
так и "научная" запись
0.12е3
для float является законной.
Каждая double, так что
обозначение " e " служит как для float, так и для double.
Длинные 123L. Обычная целая int, рассматривается как long.
Существует система обозначений для восьмеричных и шестнадцатеричных 0 (нуль) в int
указывает на восьмеричную 0x
соответствуют шестнадцатеричной 31
можно записать как 037 в восьмеричной форме и как 0x1f в шестнадцатеричной. Шестнадцатеричные и восьмеричные l, что делает их относящимися
к long.
'х' '0', имеет значение 48, а в коде - 240, и оба эти значения совершенно отличны от числа 0.
Написание '0' вместо численного значения, такого как 48 или 240, делает
Некоторые неграфические символы могут быть представлены как
\n (новая строка),\t (табуляция),\0 ( \\ (обратная косая черта),\' (одинарная кавычка) и т.д.Хотя они выглядят как два символа, на самом деле
являются одним. Кроме того, можно сгенерировать произвольную
'\ddd'
где - от одной до трех восьмеричных цифр, как в
#define formfeed '\014' /* form feed */
'\0', изображающая символ со значением 0, часто записывается вместо целой 0, чтобы подчеркнуть символьную природу
некоторого выражения.
#define maxline 1000 char line[maxline+1];
или
seconds = 60 * 60 * hours;
"i am a string" /* я - строка */ или "" /* null string */ /* нуль-строка */
Кавычки не являются частью строки, а служат только для ее ограничения. те
же самые \".
С технической точки зрения строка представляет собой \0. Такое представление означает, что не накладывается конкретного
ограничения на то, какую длину может иметь строка, и чтобы определить эту длину, strlen(s) вычисляет длину s не считая конечный символ \0.
strlen(s) /* return length of s */
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 и, следовательно, равно нулю,
когда x делится на y точно. Например, год является високосным, если он делится на 4, но не
делится на 100, исключая то, что делящиеся на 400 годы тоже являются
високосными. Поэтому
if(year % 4 == 0 year % 100 != 0 || year % 400 == 0)
год високосный
else
год невисокосный
Операцию % нельзя использовать с float
или double.
Операции + и - имеют одинаковое старшинство,
которое младше одинакового уровня старшинства
операций *, / и %, которые в свою очередь младше унарного
минуса. Арифметические операции группируются слева направо. (Сведения о
старшинстве и ассоциативности всех операций собраны в таблице в конце этой
лекции). Порядок выполнения ассоциативных и коммутативных операций + и -
не фиксируется; а+(b+c) может
быть вычислено как (a+b)+c. Это редко приводит к какому-либо расхождению, но
если необходимо обеспечить строго определенный порядок, то нужно использовать
явные промежуточные
Действия, предпринимаемые при переполнении и антипереполнении (т.е. при
получении слишком маленького по
Операциями отношения являются
>= > =< <
все они имеют одинаковое старшинство. Непосредственно за ними по уровню старшинства следуют операции равенства и неравенства:
== !=
которые тоже имеют одинаковое старшинство. операции отношения младше
арифметических операций, так что выражения i< понимаются как i<( ), как и предполагается.
Логические связки и || более интересны.
Выражения, связанные операциями и ||, вычисляются
слева направо, причем их рассмотрение
прекращается сразу же как только становится ясно, будет ли результат истиной
или ложью. Учет этих свойств очень существенен для написания правильно работающих getline, которую мы написали в лекции №1.
for(i=0;i<lim-1 (c=getchar())
!= '\n' c != EOF; ++i)
s[i]=c;
Ясно, что перед считыванием нового символа необходимо проверить, имеется
ли еще место в s, так что условие i< должно проверяться первым. И
если это условие не выполняется, мы не должны считывать следующий символ.
Так же неудачным было бы сравнение 'c' с EOF до getchar: прежде чем проверять символ, его нужно считать.
Старшинство операции выше, чем у ||, и обе они младше операций
отношения и равенства. Поэтому такие выражения, как
i<lim-1 (c = getchar()) != '\n' c != EOF
не нуждаются в дополнительных != старше
операции присваивания, то для достижения правильного результата в выражении
(c = getchar()) != '\n'
скобки необходимы.
Унарная
if( ! inword )
Вместо
if( inword == 0 )
Tрудно сказать, какая форма лучше. ! inword Читаются довольно
удобно ("если не в слове"). Но в более сложных случаях они могут оказаться
трудными для понимания.
Упражнение 2-1
Напишите for, не
используя операции .
Если в выражениях встречаются операнды различных f+i. Выражения
же, лишенные смысла, такие как использование float в
качестве индекса, запрещены.
Во-первых, char и int могут свободно смешиваться в арифметических
выражениях: каждая char автоматически преобразуется в int. Это
обеспечивает значительную гибкость при проведении atoi, которая ставит в соответствие
строке цифр ее численный эквивалент.
atoi(s) /* convert s to integer */
char s[];
{
int i, n;
n = 0;
for ( i = 0; s[i]>='0' s[i]<='9'; ++i)
n = 10 * n + s[i] - '0';
return(n);
}
Kак уже обсуждалось в лекции №1, выражение
s[i] - '0'
имеет численное значение находящегося в s[i] символа, потому что значение
символов '0', '1' и т.д. образуют возрастающую
Другой пример преобразования char
в int дает lower, преобразующая
данную прописную букву в строчную. Если выступающий в качестве lower возвращает его неизменным. Приводимая
ниже
lower(c) /* convert c to lower case; ascii only */
int c;
{
if ( c >= 'a' c <= 'z' )
return( c + '@' - 'a');
else /*@ Записано вместо 'a' строчного*/
return(c);
}
Эта а и z нет
ничего, кроме букв. Это последнее замечание для набора символов систем
IBM 360/370 оказывается несправедливым, в силу чего эта
При преобразовании символьных char
соответствовать численные значения со знаком или без знака. Может ли при
преобразовании char в int получиться отрицательное целое?
К сожалению, ответ на этот вопрос меняется от машины к машине, отражая расхождения в их
архитектуре. На некоторых машинах (char,
крайний левый бит которой содержит 1, преобразуется в отрицательное целое
("знаковое расширение"). На других машинах такое преобразование сопровождается
добавлением нулей с левого края, в результате чего всегда получается
положительное число.
Наиболее типичным примером возникновения такой ситуации является случай,
когда значение -1 используется в качестве EOF. Рассмотрим
char c; c = getchar(); if ( c == EOF) ...
На машине, которая не осуществляет знакового расширения, 'с'
всегда положительна, поскольку она описана как char,
а так как EOF отрицательно,
то условие никогда не выполняется. Чтобы избежать такой ситуации,
мы всегда предусмотрительно
использовали int вместо char для любой getchar.
Основная же причина использования int вместо char не связана с каким-либо
вопросом о возможном знаковом расширении. просто getchar должна
передавать все возможные символы (чтобы ее можно было использовать для
произвольного EOF. Следовательно
значение EOF не может быть представлено как char,
а должно храниться как int.
Другой полезной формой автоматического преобразования i>j, и логические выражения, связанные
операциями и ||, по 1, если они истинны, и 0,
если они ложны. Таким образом, присваивание
isdigit = c >= '0' c <= '9';
полагает isdigit равным 1, если с - цифра,
и равным 0 в противном случае. (В проверочной части if, while, for и т.д.
"Истинно" просто означает "не нуль").
Неявные + или *, которая связывает два
операнда (бинарная операция), имеет операнды разных
char и short преобразуются в int,
а float в double.double,
то другой преобразуется
в double, и результат имеет double.long, то другой
преобразуется в long, и результат имеет long.unsigned, то другой
преобразуется в unsigned и результат имеет unsigned.int, и результат имеет int. Подчеркнем, что все float в выражениях преобразуются в double ; в "C" вся плавающая арифметика выполняется с двойной точностью.Преобразования возникают и при присваиваниях; значение правой части
преобразуется к int
в char ведет себя хорошо - лишние
биты высокого порядка просто отбрасываются. Таким образом
int i; char c; i = c; c = i;
значение 'с' не изменяется. Это верно независимо от того, вовлекается ли
знаковое расширение или нет.
Если х float, а i int, то как
х = i; так и
i = х;
приводят к преобразованиям; при этом float преобразуется в int
отбрасыванием дробной части. double
преобразуется во float округлением. Длинные целые
преобразуются в более короткие целые и в char посредством
отбрасывания лишних битов высокого порядка.
Так как char и short
становятся int, а float становится double.
Именно поэтому мы описывали int и double даже тогда, когда обращались к ним с float.
Наконец, в любом выражении может быть осуществлено ("принуждено") явное
преобразование ). В этой
(имя типа) выражение
Выражение преобразуется к указанному sqrt ожидает double и выдаст бессмысленный
ответ, если к ней по небрежности обратятся с чем-нибудь иным. таким образом,
если n - целое, то выражение
sqrt((double) n)
до передачи sqrt преобразует n к double. (Отметим,
что операция перевод преобразует значение n в надлежащий n при этом не изменяется). Операция перевода имеет тот же уровень старшинства, что и другие унарные операции, как указывается в таблице в конце этой лекции.
Упражнение 2-2
Составьте htoi(s), которая преобразует строку
шестнадцатеричных цифр в эквивалентное ей целое значение. При этом допустимыми
цифрами являются цифры от 1 до 9 и буквы от а до F.
В языке "C" предусмотрены две необычные операции для увеличения и
уменьшения ++ добавляет 1 к своему
операнду, а операция уменьшения -- вычитает 1. Мы часто использовали операцию ++ для увеличения
if(c == '\n') ++i;
Необычный аспект заключается в том, что ++ и -- можно использовать либо
как ++n ), либо как постфиксные
(после n++ ). Эффект в обоих случаях состоит в увеличении n. Но
выражение ++n увеличивает n до использования ее значения, в то время
как n++ увеличивает n после того, как ее значение было использовано.
Это означает, что в контексте, где используется ++n и n++ приводит к разным результатам. Если n = 5, то
х = n++;
устанавливает х равным 5, а
х = ++n;
полагает х равным 6. В обоих случаях n
становится равным 6. Операции увеличения и уменьшения
можно применять только к х=(i+j)++
являются незаконными.
В случаях, где нужен только эффект увеличения, а само значение не используется, как, например, в
if ( c == '\n' )
nl++;
выбор , которая удаляет символ 'с' из строки s, каждый
раз, как он встречается.
squeeze(s,c) /* delete all c from s */
char s[];
int c;
{
int i, j;
for ( i = j = 0; s[i] != '\0'; i++)
if ( s[i] != c )
s[j++] = s[i];
s[j] = '\0';
}
Каждый раз, как встречается символ, отличный от 'с', он копируется в текущую
позицию j, и только после этого j увеличивается на 1,
чтобы быть готовым для поступления следующего символа.
Это в точности эквивалентно записи
if ( s[i] != c ) {
s[j] = s[i];
j++;
}
Другой пример подобной getline, которую мы
запрограммировали в лекции №1, где можно заменить
if ( c == '\n' ) {
s[i] = c;
++i;
}
более компактной записью
if ( c == '\n' )
s[i++] = c;
В качестве третьего примера рассмотрим , которая
приписывает строку t в конец строки s, образуя конкатенацию
строк s и t. При этом предполагается, что в s достаточно места для хранения полученной комбинации.
strcat(s,t) /* concatenate t to end of s */
char s[], t[]; /* s must be big enough */
{
int i, j;
i = j = 0;
while (s[i] != '\0') / *find end of s */
i++;
while((s[i++] = t[j++]) != '\0') /*copy t*/
;
}
Tак как из t в s копируется каждый символ, то для подготовки к следующему
прохождению цикла постфиксная операция ++ применяется к обеим i и j.
Упражнение 2-3
Напишите другой вариант , который удаляет из строки s1 каждый символ, совпадающий с каким-либо символом строки s2.
Упражнение 2-4
Напишите any(s1,s2), которая находит место первого
появления в строке s1 какого-либо символа из строки s2 и, если
строка s1 не
содержит символов строки s2, возвращает значение -1.
В языке предусмотрен ряд операций для работы с битами; эти операции нельзя
применять к float или double.
| Побитовое AND | |
| | | Побитовое включающее OR |
| ^ | побитовое исключающее OR |
| << | сдвиг влево |
| >> | сдвиг вправо |
| ~ | дополнение (унарная операция) |
" | " имитирует вертикальную черту.
Побитовая операция and часто используется для маскирования некоторого множества
битов; например,
c = n 007f
передает в 'с' семь младших битов n, полагая остальные равными
нулю. Операция '|' побитового or используется для включения битов:
c = x | mask
устанавливает на единицу те биты в х, которые равны единице в mask.
Следует быть внимательным и отличать побитовые операции и | от
логических связок и ||, которые подразумевают вычисление значения
истинности слева направо. Например, если х=1, а y=2, то значение хy равно нулю, в то время как значение xy равно единице.
Операции сдвига << и >> осуществляют
соответственно сдвиг влево и вправо
своего левого операнда на число битовых позиций, задаваемых правым операндом.
Таким образом, х<<2 сдвигает х влево на две позиции,
заполняя освобождающиеся
биты нулями, что эквивалентно умножению на 4. Сдвиг вправо величины без знака
заполняет освобождающиеся биты на некоторых машинах, таких как
Унарная операция ~ дает дополнение к целому; это означает, что каждый
бит со значением 1 получает значение 0 и наоборот. Эта операция обычно
оказывается полезной в выражениях
x ~077
где последние шесть битов х маскируются нулем. Подчеркнем, что выражение x!077 не зависит от длины слова и поэтому предпочтительнее,
чем, например, x0177700,
где предполагается, что х занимает 16 битов. Такая переносимая форма не
требует никаких дополнительных затрат, поскольку ~077 является
Чтобы проиллюстрировать использование некоторых операций с битами,
рассмотрим getbits(x,p,n), которая возвращает /сдвинутыми к правому
краю/ начинающиеся с позиции р поле х длиной n битов. Мы предполагаем, что крайний правый бит имеет номер 0, и
что n и р - разумно заданные
положительные числа. Например, getbits(х,4,3) возвращает сдвинутыми к правому
краю биты, занимающие позиции 4, 3 и 2.
getbits(x,p,n) /* get n bits from position p */
unsigned x, p, n;
{
return((x >> (p+1-n)) ~(~0 << n));
}
Операция x >> (p+1-n ) сдвигает желаемое поле в правый конец слова. x как unsigned гарантирует, что при сдвиге вправо освобождающиеся биты
будут заполняться нулями, а не содержимым ~0
равны 1 ; сдвиг его на n позиций влево с помощью операции ~0<<n создает маску с нулями в n крайних
правых битах и единицами в остальных; дополнение ~ создает
маску с единицами в n крайних правых битах.
Упражнение 2-5
Переделайте getbits таким образом, чтобы биты отсчитывались слева
направо.
Упражнение 2-6
Напишите wordlength(), вычисляющей длину слова
используемой машины, т.е. Число битов в int.
Упражнение 2-7
Напишите rightrot(n,b), сдвигающей циклически целое n вправо на b битовых позиций.
Упражнение 2-8
Напишите invert(x,p,n), которая инвертирует (т.е.
заменяет 1 на 0 и наоборот) n битов x,
начинающихся с позиции p, оставляя другие биты неизмененными.
Такие выражения, как
i = i + 2
в которых левая часть повторяется в правой части могут быть записаны в сжатой форме
i += 2
используя операцию присваивания вида +=.
Большинству бинарных операций (операций подобных +, которые имеют левый
и правый операнд) соответствует операция присваивания вида оп=,
где оп - одна из операций
+ - * / % << >> ^ |!
Если е1 и е2 - выражения, то е1 оп= е2
эквивалентно
е1 = (е1) оп (е2)
за исключением того, что выражение е1 вычисляется только один раз. Обратите
внимание на е2:
x *= y + 1
то
x = x * (y + 1)
не
x = x * y + 1
В качестве примера приведем bitcount, которая подсчитывает число
равных 1 битов у целого
bitcount(n) /* count 1 bits in n */
unsigned n;
(
int b;
for (b = 0; n != 0; n >>= 1)
if (n 01)
b++;
return(b);
)
Не говоря уже о краткости, такие i " или "увеличить i на 2",
но не "взять i, прибавить 2 и
поместить результат опять в i ". Итак, i += 2.
Кроме того, в громоздких выражениях, подобных
yyval[yypv[p3+p4] + yypv[p1+p2]] += 2
Tакая операция присваивания облегчает понимание
Мы уже использовали тот факт, что операция присваивания имеет некоторое значение и может входить в выражения; самый типичный пример
while ((c = getchar()) != EOF)
присваивания, использующие другие операции присваивания ( +=, -= и т.д.) также
могут входить в выражения, хотя это случается реже.
Упражнение 2-9
В двоичной системе счисления операция x(x-1)
обнуляет самый правый равный 1 бит x.(почему?)
используйте это замечание для написания более
быстрой версии bitcount.
if (a > b)
z = a;
else
z = b;
конечно вычисляют в z максимум из а и в.
Условное выражение, записанное с помощью "?:", предоставляет другую возможность для записи
этой и аналогичных
е1 ? Е2 : е3
сначала вычисляется выражение е1. Если оно отлично от нуля (истинно), то
вычисляется выражение е2, которое и становится значением условного выражения. В
противном случае вычисляется е3, и оно становится значением условного
выражения. Каждый раз вычисляется только одно из выражения е2 и е3.
Таким образом, чтобы положить z равным максимуму из а и в,
можно написать
z = (a > b) ? a : b; /* z = max(a,b) */
Следует подчеркнуть, что условное выражение действительно является
выражением и может использоваться точно так же, как любое другое выражение. Если е2 и е3 имеют разные f имеет float, а n - int, то выражение
(n > 0) ? f : n
Имеет double независимо от того, положительно ли n или нет.
Так как уровень старшинства операции ?: очень низок, прямо над
присваиванием, то первое выражение в условном выражении можно не заключать в
Использование условных выражений часто приводит к коротким n элементов
for (i = 0; i < n; i++)
printf("%6d%c",a[i],(i%10==9 || i==n-1) ? '\n' : ' ')
Символ перевода строки записывается после каждого десятого элемента и после n -го
элемента. За всеми остальными элементами следует один пробел. Хотя, возможно,
это выглядит мудреным, было бы поучительным попытаться записать это, не используя
условного выражения.
Упражнение 2-10
Перепишите lower, которая переводит прописные буквы в
строчные, используя вместо if - else условное выражение.
В приводимой ниже таблице сведены правила старшинства и ассоциативности всех
операций, включая и те, которые мы еще не обсуждали. Операции, расположенные в
одной строке, имеют один и тот же уровень старшинства; строки расположены в
порядке убывания старшинства. Так, например, операции *, /
и % имеют одинаковый
уровень старшинства, который выше, чем уровень операций + и -.
| OPERATOR | ASSOCIATIVITY |
|---|---|
| () [] -> . | LEFT TO RIGHT |
| ! ^ ++ -- - (TYPE) * SIZEOF | RIGHT TO LEFT |
| * / % | LEFT TO RIGHT |
| + - | LEFT TO RIGHT |
| << >> | LEFT TO RIGHT |
| < <= > >= | LEFT TO RIGHT |
| == != | LEFT TO RIGHT |
| LEFT TO RIGHT | |
| ^ | LEFT TO RIGHT |
| | | LEFT TO RIGHT |
| LEFT TO RIGHT | |
| || | LEFT TO RIGHT |
| ?: | RIGHT TO LEFT |
| = += -= ETC. | RIGHT TO LEFT |
| , (CHAPTER 3) | LEFT TO RIGHT |
Операции . Используются для доступа к элементам sizeof (размер объекта).
В лекции №5 обсуждаются
операции * (косвенная
( , ^ и '
ниже уровня операций == и !=. Это
приводит к тому, что осуществляющие побитовую проверку выражения, подобные
if ((x mask) == 0) ...
Для получения правильных результатов должны заключаться в
Как уже отмечалось ранее, выражения, в которые входит одна из
ассоциативных и коммутативных операций ( *, +, , ^, ' ), могут
перегруппировываться, даже если они заключены в
В языке "C", как и в большинстве языков, не фиксируется порядок вычисления
операндов в
x = f() + g();
сначала может быть вычислено f, а потом g,
и наоборот; поэтому, если либо f,
либо g изменяют x может зависеть от порядка вычислений. Для обеспечения нужной
Подобным же образом не фиксируется порядок вычисления
printf("%d %d\n",++n,power(2,n));
может давать (и действительно дает) на разных машинах разные результаты в
зависимости от того, увеличивается ли n до или после power.
Правильным решением, конечно, является запись
++n; printf("%d %d\n",n,power(2,n));
a[i] = i++;
Возникает вопрос, старое или новое значение i служит в качестве индекса.
Из этих рассуждений вытекает такая мораль: написание lint укажет большинство мест,
зависящих от порядка вычислений.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.