ТИПЫ ДАННЫХ И ОПЕРАЦИИ В ЯЗЫКЕ СИ. ВЫРАЖЕНИЯ
Типы данных.
Программа на процедурных языках, к которым относится Си, представляет собой описание операций над величинами различных типов. Тип определяет множество значений, которые может принимать величина, и множество операций, в которых она может участвовать.
В языке Си типы связаны с именами (идентификаторами) величин, т. е. с переменными. С переменной в языке Си связывается ячейка памяти. Тип переменной задает размер ячейки, способ кодирования ее содержимого, допустимые преобразования над значением данной переменной. Все переменные должны быть описаны до их использования. Каждая переменная должна быть описана только один раз.
Описание состоит из спецификатора типа и следующего за ним списка переменных. Переменные в списке разделяются запятыми. В конце описания ставится точка с запятой.
Примеры описаний:
char a,b; /* Переменные а и b имеют тип
char */ int х; /* Переменная х - типа int
*/ char sym; /" Описаны переменные sym типа char;
*/ int count.num; /* num и count типа int */
Переменным могут быть присвоены начальные значения внутри их описаний. Если за именем переменной следует знак равенства и константа, то эта константа служит в качестве инициализатора.
Примеры: char backch = '\0';
int i = 0;
Рассмотрим основные типы в языке Си.
int - целый ("integer"). Значения этого типа - целые числа из некоторого ограниченного диапазона (обычно от- 32768 до 32767). Диапазон определяется размером ячейки для типа и зависит от конкретного компьютера. Кроме того, имеются служебные слова, которые можно использовать с типом int: short int («short integer» - «короткое целое»), unsigned int («unsigned integer» - «целое без знака»), long int («длинное целое»), которые сокращают или, наоборот, расширяют диапазон представления чисел.
char - символьный («character»). Допустимое значение для этого типа — один символ (не путать с текстом!). Символ записывается в апострофах.
Примеры:
'х"2"?'
В памяти компьютера символ занимает один байт.
Фактически хранится не символ, а число - код символа (от 0 до 255). В специальных таблицах кодировки указываются все допустимые символы и соответствующие им коды.
В языке Си разрешается использовать тип char как числовой, т. е. производить операции с кодом символа, применяя при этом спецификатор целого типа в скобках - (int).
float - вещественный (с плавающей точкой). Значения этого типа - числа, но, в отличии от char и int, не обязательно целые.
Примеры:
12.87 -316.12 -3.345е5 12.345e-15
double - вещественные числа двойной точности. Этот тип аналогичен типу float, но имеет значительно больший диапазон значений (например, для системы программирования Borland-C от 1.7Е-308 до 1.7Е+308 вместо диапазона от 3.4Е-38 до 3.4Е+38 для типа float). Однако увеличение диапазона и точности представления чисел ведет к снижению скорости выполнения программ и неэкономному использованию оперативной памяти компьютера.
Обратите внимание на отсутствие в этом списке строкового типа. В языке Си нет специального типа, который можно было бы использовать для описания строк. Вместо этого строки представляются в виде массива элементов типа char. Это означает, что символы в строке будут располагаться в соседних ячейках памяти.
Необходимо отметить, что последним элементом массива является символ \0. Это «нуль-символ», и в языке Си он используется для того, чтобы отмечать конец строки. Нуль-символ не цифра 0; он не выводится на печать и в таблице кодов ASCII имеет номер 0. Наличие нуль-символа означает, что количество ячеек массива должно быть. по крайней мере, на одну больше, чем число символов, которые необходимо размещать в памяти.
Приведем пример использования строк.
Программа 84
# include<stdio.h> main()
{
char string[31] ;
scanf("%s",string) ;
printf("%s",string);
}
В этом примере описан массив из 31 ячейки памяти, в 30 из которых можно поместить один элемент типа char. Он вводится при вызове функции scanf("%s",string); "&"отсутствует при указании массива символов.
Указатели. Указатель - некоторое символическое представление адреса ячейки памяти, отведенной для переменной.
Например, &name - указатель на переменную name;
Здесь & - операция получения адреса. Фактический адрес - это число, а символическое представление адреса &name является константой типа «указатель».
В языке Си имеются и переменные типа указатель. Точно так же, как значением переменной типа char является символ, а значением переменной типа int - целое число, значением переменной типа указатель служит адрес некоторой величины.
Если мы дадим указателю имя ptr, то сможем написать такой оператор:
ptr = &name;/* присваивает адрес name переменной ptr */
Мы говорим в этом случае, что prt «указатель на» name. Различие между двумя формами записи: ptr и &name - в том, что prt - это переменная, в то время как &name - константа. В случае необходимости можно сделать так, чтобы переменная ptr указывала на какой-нибудь другой объект:
ptr = &bah; /* ptr указывает на bah, а не на name */
Теперь значением переменной prt является адрес переменной bah. Предположим, мы знаем, что в переменной ptr содержится ссылка на переменную bah. Тогда для доступа к значению этой переменной можно воспользоваться операцией «косвенной адресации» * :
val = *ptr; /* определение значения, на которое указывает ptr */ Последние два оператора, взятые вместе, эквивалентны следующему:
val = bah;
Итак, когда за знаком &
следует имя переменной, результатом операции является адрес указанной переменной; &nurse дает адрес переменной nurse; когда за знаком * следует указатель на переменную, результатом операции является величина, помещенная в ячейку памяти с указанным адресом.
Пример: nurse = 22;
ptr = &nuse; /* указатель на nurse */
val = *ptr;
Результат- присваивание значения 22 переменной val.
Недостаточно сказать, что некоторая переменная является указателем. Кроме этого необходимо сообщить, на переменную какого типа ссылается данный указатель. Причина заключается в том, что переменные разных типов занимают различное число ячеек памяти, в то время как для некоторых операций, связанных с указателями, требуется знать объем отведенной памяти.
Примеры
правильного описания указателей: int *pi; char *pc;
Спецификация типа задает тип переменной, на которую ссылается указатель, а символ * определяет саму переменную как указатель. Описание вида int *pi; говорит, что pi - это указатель и что *pi - величина типа int.
В языке Си предусмотрена возможность определения имен типов данных. Любому типу данных с помощью определения typedef можно присвоить имя и использовать это имя в дальнейшем при описании объектов.
Формат: typedef <старый тип> <новый тип> Пример: typedef long LARGE; /* определяется тип large, эквивалентный типу long */
Имена производного типа рекомендуется записывать прописными буквами, чтобы они выделялись в тексте программы.
Определение typedef не вводит каких-либо новых типов, а только добавляет новое имя для уже существующего типа. Описанные таким способом переменные обладают точно теми же свойствами, что и переменные, описанные явно. Переименование типов используется для введения осмысленных или сокращенных имен типов, что повышает понятность программ, и для улучшения переносимости программ (имена одного типа данных могут различаться на разных компьютерах).
Операции. Язык Си отличается большим разнообразием операций (более 40). Здесь мы рассмотрим лишь основные из них, табл. 3.3.
Арифметические операции. К ним относят
• сложение(+),
• вычитание (бинарное) (-),
• умножение (*),
• деление (/),
• остаток от деления нацело (%),
• вычитание (унарное) (-) .
В языке Си принято правило: если делимое и делитель имеют тип int, то деление производится нацело, т е. дробная часть результата отбрасывается.
Как обычно, в выражениях операции умножения, деления и нахождения остатка выполняются раньше сложения и вычитания. Для изменения порядка действий используют скобки.
Программа 85
#include<stdio.h>
main()
(
int s;
5 = -3 + 4 * 5 - 6; printf("%d\n",s);
s = -3 + 4%5 - 6; printf("%d\n",s);
s = -3 * 4% - 6/5; printf("%d\n",s);
s= (7 + 6)%5/2; printf("%d\n",s);
}
Результат выполнения программы: 11 1 0 1
Таблица 3.3 Старшинство и порядок выполнения операций
Приоритет |
Операция |
Название |
Порядок выполнения |
||
Высший |
() [] ++ -- (тип) * |
Круглые скобки Квадратные скобки Увеличение Уменьшение Приведение Содержимое |
Слева направо Слева направо Справа налево Справа налево Справа налево Справа налево |
||
1 |
& - ! /~ sizeof * |
Адрес Унарный минус Логическое «НЕ» Инверсия битов Размер объекта Умножение |
Справа налево Справа налево Справа налево Справа налево Справа налево Слева направо |
||
2 |
\ % |
Деление Остаток |
Слева направо Слева направо |
||
3 |
+ - |
Сложение Вычитание |
Слева направо Слева направо |
||
4 |
» « > |
Сдвиг вправо Сдвиг влево Больше |
Слева направо Слева направо Слева направо |
||
5 |
>= < <= |
Больше или равно Меньше Меньше или равно |
Слева направо Слева направо Слева направо |
||
6 |
= = != |
Равно Не равно |
Слева направо Слева направо |
||
7 |
& |
Битовое «И» |
Слева направо |
||
8 |
~ |
Битовое исключающее «ИЛИ» |
Слева направо |
||
9 |
| |
Битовое «ИЛИ» |
Слева направо |
||
10 |
&& |
Логическое «И» |
Слева направо |
||
11 |
|| = += |
Логическое «ИЛИ» Операция присвания Справа налево |
Слева направо Справа налево |
||
12 |
- = *= /= %= |
Специальная форма операции присваивания |
Справа налево Справа налево Справа налево Справа налево |
||
Для этого обычно выполняются оператор присваивания вида: s= s + 1;
В языке Си для этих действий существуют специальные операции:
• увеличение (+ +),
• уменьшение (--).
Следующие записи на языке Си являются эквивалентными:
i=i+l и i++; j=j-1 и j--;.
Символы "++" или "- -" записывается после имени переменной или перед ним.
Пример:
s + +; /* s увеличить на единицу
*/ t - -; /* t уменьшить на единицу
*/ + + а; /* а увеличить на единицу
*/ --b; /* b уменьшить на единицу */
Как и обычные присваивания, увеличение и уменьшение можно использовать в выражениях. При этом существенно, с какой стороны от имени стоит знак "+ +"или "- -". Если знак стоит перед переменной (в этом случае говорят о префиксной форме операции), то сначала выполняется увеличение (уменьшение) значения переменной, а лишь затем полученный результат используется в выражении. Если же знак стоит после переменной (постфиксная форма операции), то в выражении используется старое значение переменной, которое затем изменяется.
Пример:
inti,j,s;
i = j = 2; /* i и j получают значение 2 */
s = (i++) + (++J);
После выполнения этих действий переменные имеют такие значения:
i=3;j=3;s=5.
Операции увеличения ++ и уменьшения - - можно применять топько к переменным, выражения типа s=(i+j)++ являются незаконными. Кроме того, не рекомендуется:
1) применять операции увеличения или уменьшения к переменной, присутствующей в более чем одном аргументе функции,
2) применять операции увеличения или уменьшения к переменной, которая входит в выражение более одного раза.
Операции отношения и логические операции
Больше или равно |
>= |
Больше |
> |
Меньше или равно |
<= |
Меньше |
< |
Равно |
== |
Неравно |
!= |
Логическое «и» |
&& |
Логическое «или» |
|| |
Отрицание «не» |
! |
Логическое значение «ложь» представляется целым нулевым значением, а значение «истина» представляется любым ненулевым значением
Выражения, связанные логическими операциями && и ||, вычисляются слева направо, причем вычисление значения выражения прекращается сразу же, как только становится ясно, будет ли результат истинен или ложен.
Старшинство операции && выше, чем у операции ||.
Программа 86
#include<stdio.h>
main()
(
int x, у, z;
x=l; y=l; z=0; x=x&&y||z; printf("%d\n",x);
x=x|| !y&&z; printf("%d\n",x) ;
x=y=l; z=x++-l; printf("%d\n",x);printf("%d\n",z) ;
z+=-x++ + ++y; printf("%d\n",x) ; printf("%d\n",z);
z=x/++x; printf("%d\n",x); printf("%d\n",z) ;
}
Результат выполнения программы: 1 1 2 0 3 0 4 1
Битовые операции
Битовое «и» Битовое «или» Битовое исключающее «или» Сдвиг влево Сдвиг вправо Инверсия битов (унарная операция) |
& | ~ « » \ ~ |
#include<stdio.h>
main()
(
int у, х, z, k;
x=03; y=02; z=01; k=x|y&z; printf("%d\n",k) ;
k=x|y&~z; printf("%d\n",k) ;
k=x^y&~z; printf("%d\n",k) ;
k=x&y&&z; printf("%d\n",k) ;
x=l; y=-l;
k=!x|x; printf("%d\n",k) ;
k=-x|x; printf("%d\n",k) ;
k=x^x; printf("%d\n",k) ;
x<<=3; printf("%d\n",x);
y<<=3; printf("%d\n",y);
y>>=3; printf("%d\n",y);
}
После выполнения программы получаем следующие результаты:
3 3 1 1 1 -1 0 8 -8 8 1 9 1
Выражения. Конструкции, включающие константы (литералы), переменные, знаки операции, скобки для управления порядком выполнения операций, обращения к функциям, называют выражениями.
Если в выражениях встречаются операнды различных типов, то они преобразуются к общему типу в соответствии с определенными правилами:
• переменные типа char интерпретируются как целые без знака (unsigned);
• переменные типа short автоматически преобразуются в int; если один из операндов имеет тип unsigned, то другой (другие) также преобразуется к типу unsigned и результат имеет тип unsigned;
• если один из операндов имеет тип int, то другой (другие) также преобразуется к типу int и результат имеет тип int;
• если один из операндов имеет тип char, то другой (другие) также преобразуется к типу char и результат имеет тип char;
• во время операции присваивания значение правой части преобразуется к типу левой части, который и становится типом результата;
• в процессе преобразования int в char лишние старшие 8 бит просто отбрасываются. Кроме того, существует возможность точно указывать требуемый тип данных, к которому необходимо привести некоторую величину (в скобках перед этой величиной). Скобки и имя типа вместе образуют операцию, называемую приведением типов.
Например: z=(int)x+(int)y;