9. Операции явного преобразования типа
Применение этой операции имеет следующий формат:
(имя_типа) операнд
Операндом могут быть константа, переменная, выражение. В результате значение операнда преобразуется к указанному типу.
Примеры использования преобразования типа:
(long)8 , (float)1 , (int)x%2
По поводу последнего выражения заметим, что приоритет операции «тип» выше деления (и других бинарных арифметических операций), поэтому сначала значение переменной х приведется к целому типу (отбросится дробная часть), а затем выполнится деление по модулю.
Следующий фрагмент программы иллюстрирует одну из практических ситуаций, в которой потребовалось использовать преобразование типа:
floa t с;
in t a=l , b=2;
с=(float)a/b ;
В результате переменная с получит значение 0,5. Без преобразования типа ее значение стало бы равно 0.
10. Операции определения размера sizeof
Эта операция имеет две формы записи:
sizeof(тип) и sizeof(выражение)
Результатом операции является целое число, равное количеству байтов, которое занимает в памяти величина явно указанного типа или величина, полученная в результате вычисления выражения. Последняя определяется также по типу результата выражения. Хотя по форме записи это похоже на функцию, однако sizeof является именно операцией. Ее приоритет выше, чем у бинарных арифметических операций, логических операций и отношений.
11.Операция «запятая» и «?»
Операция «запятая». Эта необычная операция используется для связывания нескольких выражений в одно. Несколько выражений, разделенных запятыми, вычисляются последовательно слева направо. В качестве результата такого совмещенного выражения принимается значение самого правого выражения. Например, если переменная х имеет тип int , то значение выражения (х=3, 5*х) будет равно 15, а переменная х примет значение 3.
Операция «условие ?:». Это единственная операция, которая имеет три операнда. Формат операции:
выражение1 ? выражение2 : вьгражениеЗ
Данная операция реализует алгоритмическую структуру ветвления. Алгоритм ее выполнения следующий: первым вычисляется значение выражения 1, которое обычно представляет собой некоторое условие. Если оно истинно, т. е. не равно 0, то вычисляется выражение 2 и полученный результат становится результатом операции. В противном случае в качестве результата берется значение выражения 3.
12. Операция получения адреса (&) и раскрытия ссылки(*)
Указатель — это адрес объекта в памяти. Переменная типа "указатель" (или просто переменная-указатель) — это специально объявленная переменная, в которой хранится указатель на переменную определенного типа. В языке С указатели служат мощнейшим средством создания программ и широко используются для самых разных целей. Например, с их помощью можно быстро обратиться к элементам массива или дать функции возможность модифицировать свои аргументы. Указатели широко используются для связи элементов в списках, в двоичных деревьях и в других динамических структурах данных.
Первый из них — оператор &, это унарный оператор, возвращающий адрес операнда в памяти[12]. (Унарной операцией называется операция, имеющая только один операнд.) Например, оператор
m = &count;
записывает в переменную m адрес переменной count. Этот адрес представляет собой адрес ячейки памяти компьютера, в которой размещена переменная. Адрес и значение переменной — совершенно разные понятия. Выражение "&переменная" означает "адрес переменной". Следовательно, инструкция m = &scount; означает: "Переменной m присвоить адрес, по которому расположена переменная count;".
Допустим, переменная count расположена в памяти в ячейке с адресом 2000, а ее значение равно 100. Тогда в предыдущем примере переменной m будет присвоено значение 2000.
Второй рассматриваемый оператор * является двойственным (дополняющим) по отношению к &[13]. Оператор * является унарным оператором, он возвращает значение объекта, расположенного по указанному адресу. Операндом для * служит адрес объекта (переменной). Например, если переменная m содержит адрес переменной count, то оператор
q = *m;
записывает значение переменной count в переменную q. В нашем примере переменная q получит значение 100, потому что по адресу 2000 записано число 100, причем этот адрес записан в переменной m. Выражение "* адрес" означает "по адресу". Наш фрагмент программы можно прочесть как "q получает значение, расположенное по адресу m".
К сожалению, символ операции раскрытия ссылки совпадает с символом операции умножения, а символ операции получения адреса — с символом операции поразрядного И. Необходимо помнить, что эти операторы не имеют никакого отношения друг к другу. Операторы * и & имеют более высокий приоритет, чем любая арифметическая операция, кроме унарного минуса, имеющего такой же приоритет.
Если переменная является указателем, то в объявлении перед ее именем нужно поставить символ *, он сообщит компилятору о том, что это указатель на переменную данного типа. Например, объявление указателя на переменную типа char записывается так:
char *ch;
Необходимо понимать, что ch — это не переменная типа char, а указатель на переменную данного типа, это совершенно разные вещи. Тип данных, на который указывает указатель (в данном случае это char), называется базовым типом указателя[14]. Сам указатель является переменной, содержащей адрес объекта базового типа. Компилятор учтет размер указателя в архитектуре компьютера и выделит для него необходимое количество байтов, чтобы в указатель поместился адрес. Базовый тип указателя определяет тип объекта, хранящегося по этому адресу.
В одном операторе объявления можно одновременно объявить и указатель, и переменную, не являющуюся указателем. Например, оператор
int x, *y, count;
объявляет х и count как переменные целого типа, а у — как указатель на переменную целого типа.
В следующей программе операторы * и & используются для записи значения 10 в переменную target. Программа выведет значение 10 на экран.
#include<stdio.h>
intmain(void)
{
inttarget, source;
int *m;
source = 10;
m = &source;
target = *m;
printf("%d", target);
return 0;
}
13. Знаки операций () и []
Круглые скобки являются оператором, повышающим приоритет выполнения операций, которые в них заключены. Квадратные скобки служат для индексации массива). Если в программе определен массив, то выражение в квадратных скобках представляет собой индекс массива. Например, в программе
#include<stdio.h>
char s[80];
intmain(void)
{
s[3] = 'X';
printf("%c", s[3]);
return 0;
}
значение 'Х' сначала присваивается четвертому элементу массива (в С элементы массива нумеруются с нуля), затем этот элемент выводится на экран.
14. Директива #define
Директива #define определяет идентификатор и последовательность символов, которая будет подставляться вместо идентификатора каждый раз, когда он встретится в исходном файле. Идентификатор называется именем макроса, а сам процесс замены — макрозаменой[1]. В общем виде директива выглядит таким образом:
#defineимя_макросапоследовательность_символов
Обратите внимание, что в этом выражении нет точки с запятой. Между идентификатором и последовательностью символов последовательность_символов может быть любое количество пробелов, но признаком конца последовательности символов может быть только разделитель строк.
Предположим, например, что вместо значения 1 нужно использовать слово LEFT (левый), а вместо значения 0 — слово RIGHT (правый). Тогда можно сделать следующие объявления с помощью директивы #define:
#define LEFT 1
#define RIGHT 0
В результате компилятор будет подставлять 1 или 0 каждый раз, когда в вашем файле исходного кода встречается идентификатор соответственно LEFT или RIGHT. Например, следующий код выводит на экран 0 1 2:
printf("%d %d %d", RIGHT, LEFT, LEFT+1);
После определения имя макроса можно использовать в определениях других имен макросов. Вот, например, код, определяющий значения ONE (один), TWO (два) и three (три):
#define ONE 1
#define TWO ONE+ONE
#define THREE ONE+TWO
Макроподстановка — это просто замена какого-либо идентификатора связанной с ним последовательностью символов. Поэтому если требуется определить стандартное сообщение об ошибке, то можно написать примерно следующее:
#define E_MS "стандартная ошибка при вводе\n"
/* ... */
printf(E_MS);
Теперь каждый раз, когда встретится идентификатор E_MS, компилятор будет его заменять строкой "стандартная ошибка при вводе\n". Для компилятора выражение printf() на самом деле будет выглядеть таким образом:
printf("стандартная ошибка при вводе\n");
Если идентификатор находится внутри строки, заключенной в кавычки, то замены не будет. Например, при выполнении кода
#define XYZ это проверка
printf("XYZ");
вместо сообщения это проверка будет выводиться последовательность символов XYZ.
Если последовательность_символов не помещается в одной строке, то эту последовательность можно продолжить на следующей строке, поместив в конце предыдущей, как показано ниже, обратную косую черту:
#define LONG_STRING "это очень длинная \
строка, используемая в качестве примера"
Программисты, пишущие программы на языке С, в именах определяемых идентификаторов часто используют буквы верхнего регистра. Если разработчики программ следуют этому правилу, то тот, кто будет читать их программу, с первого взгляда поймет, что будет происходить макрозамена. Кроме того, все директивы #define обычно лучше всего помещать в самом начале файла или в отдельном заголовочном файле, а не разбрасывать по всей программе.
Имена макросов часто используются для определения имен так называемых "магических чисел" (встречающихся в программе). Например, имеется программа, в которой определяется массив и несколько процедур, получающих доступ к этому массиву. Вместо того чтобы размер массива "зашивать в код" в виде константы, этот размер можно определить с помощью оператора #define, а затем использовать это имя макроса везде, где требуется размер массива. Таким образом, если требуется изменить этот размер, то потребуется изменить только соответствующий оператор#define, a затем перекомпилировать программу. Рассмотрим, например, фрагмент программы
#define MAX_SIZE 100
/* ... */
floatbalance[MAX_SIZE];
/* ... */
for(i=0; i<MAX_SIZE; i++) printf("%f", balance[i]);
/* ... */
for(i=0; i<MAX_SIZE; i++) x =+ balance[i];
Размер массива balance определяется именем макроса MAX_SIZE, и поэтому если этот размер потребуется в будущем изменить, то надо будет изменить только определение MAX_SIZE. В результате при перекомпиляции программы все обращения к этому имени макроса, находящиеся после измененного определения, будут автоматически изменены.