1.6. Препроцессор
Препроцессор языка Си позволяет перед началом трансляции включать в программу фрагменты программ, написанных отдельно от основной.
Директива #define.
Директива #define может появляться в любом месте программы, а даваемое ею определение имеет силу от места до конца программы.
#include <iostream.h>
#include <stdio.h>
#define TRI 3
#define OTWET TRI*TRI
#define OT printf("ОТВЕТ равен %d.\n",OTWET)
#define jd cin >>C;
main( )
{
int C;
OT;
jd;
}
После выполнения программы получится:
ОТВЕТ равен 9
В первой строке программы TRI - это макроопределение и оно равно 3, где 3 - строка замещения.
Во второй строке макроопределение OTWET имеет строку замещения TRI*TRI и т.д. Каждая строка состоит из трех частей. Первой стоит директива #define, далее идет макроопределение. Макроопределение не должно содержать внутри себя пробелы. И, наконец, идет строка (называемая "строкой замещения"), которую представляет макроопределение. Когда препроцессор находит в программе одно из макроопределений, он заменяет его строкой замещения. Этот процесс прохождения от макроопределения до заключительной строки замещения называется "макрорасширением".
Директива #include.
Когда препроцессор "распознает" директиву #include, он ищет следующее за ней имя файла и включает его в текущую программу. Директива бывает в двух видах:
#include<stdio.h> имя файла в угловых скобках
#include "my.h" имя файла в двойных кавычках
Угловые скобки сообщают препроцессору, что файл следует искать в одном или нескольких стандартных системных каталогов. Кавычки говорят ему, что сначала нужно смотреть в рабочем каталоге, а затем искать в "стандартных" местах.
Директивы: #undef, #ifdef, #else, #endif
Эти директивы позволяют приостановить действие более ранних определений.
Директива #undef отменяет самое последнее определение поименованного макроопределения.
#define TRI 3
#define F 5
#undef TRI /* TRI теперь не определен */
#define F 10 /* F переопределен как 10 */
#undef F /* F снова равен 5 */
#undef F /* F теперь не определен */
Рассмотрим еще пример.
#ifdef OTW
#include "otw.h" /* выполнится, если OTW определен */
#define ST 10
#else
#include "w.h" /* выполнится, если OTW не определен */
#define ST 20
#endif
Директива ifdef сообщает, что если последующий идентификатор OTW определяется препроцессором, то выполняются все последующие директивы вплоть до первого появления #else или #endif. Когда в программе есть #else, то программа от #else до #endif будет выполняться, если идентификатор не определен.
1.7 Программы. Функции
Как мы рассматривали раньше, программа на Си имеет корневой сегмент, начинающийся с директив препроцессора и ключевого слова main.
Далее идет собственно программа, начинающаяся с открывающейся фигурной скобки { и заканчивающаяся закрывающейся фигурной скобкой }.
Часто используемые участки программы выделяются в функции. Каждая функция также начинается с директив препроцессора и имени и скобок { }.
Рассмотрим пример программы рисования лестницы.
#include <stdio.h>
main()
{
printf("|----|\n");
printf("|----|\n");
printf("|----|\n");
}
А теперь напишем эту программу с использованием функции Lestniza.
#include <stdio.h>
Lestniza(void)
{
printf("|----|\n");
}
main()
{
Lestniza();
Lestniza();
Lestniza();
}
Как видно из программы, обращение к функции осуществляется три раза. Для преодоления этого недостатка переработаем программу и введем формальные и фактические аргументы:
#include <stdio.h>
int C;
Lestniza(int B)/* B - формальный аргумент */
{
int A;
for (A = 1; A <= B; A++)
printf("|----|\n");
}
main()
{
Lestniza(3); /* 3 -фактический аргумент */
}
В данной функции B является формальным аргументом (конечная величина оператора for to). Для присвоение ей конкретного значения используется фактический аргумент, который передается функции при ее вызове в основной программе.
Если в функцию передается несколько параметров, то они должны передаваться в том порядке, в каком записаны в функции.
Рассмотрим функции, возвращающее свое значение на примере возведения числа в квадрат.
#include <stdio.h>
float Kwadrat(float A)
{
return A * A;
}
float B;
main()
{
printf("? \n");
scanf("%f",&B);
printf("Kwadrat = %8.2f\n",Kwadrat(B));
}
Как видно из примера - имя функции Kwadrat - она вычисляет квадрат числа. В строке printf("Kwadrat = %8.2f\n",Kwadrat(B)); эта функция вызывается - на вход подается значение (введенное число), а в результате получаем результат - квадрат числа, который возвращается в программу по команде return.
Рассмотрим еще один вариант работы с функцией, возвращающей значение без команды return.
#include <stdio.h>
Kwadrat(float A, float *B)
{
*B = A * A;
}
float C, D;
main()
{
printf("? \n");
scanf("%f",&C);
Kwadrat(C,&D);
printf("Kwadrat = %8.2f\n",D);
}
Указатель - это переменная, содержащая адрес данных, а не их значение. Указатель используется:
1.Для связи независимых структур друг с другом.
2.Для динамического распределения памяти.
3.Для доступа к различным элементам структуры.
Рассмотрим следующую программу:
#include <stdio.h>
main()
{
int Z,*Y;
Y =&Z;
Z = 100;
printf("Прямое значение Z: %d\n", Z);
printf("Значение Z, полученное через указатель: %d\n",*Y);
printf(" Адрес Z через получение адреса: %p\n",&Z);
printf("Адрес Z через указатель: %p\n", Y);
}
В данном примере Y указатель на целую переменную и содержит ее адрес. В свою очередь & позволяет получить адрес по которому размещено значение переменной Z. В этой программе:
- адрес переменной Z присваивается Y;
- целое значение 100 присваивается Z;
- оператор &, позволяет получить адрес,
по которому размещено значение Z.
Результат работы программы:
Прямое значение Z: 100
Значение Z, полученное через указатель: 100
Адрес Z через получение адреса: 85B3:0FDC
Адрес Z через указатель: 85B3:0FDC
Указатели также используются для оптимального распределения памяти.
Рассмотрим пример указателя на число типа char.
#include <stdio.h>
#include <alloc.h>
#include <string.h>
#include <stdlib.h>
#include <process.h>
int main(void)
{
char *str; /* указатель на символьную переменную */
str = (char *)malloc(10);
strcpy(str, "Hello");
printf("String is %s\n", str);
free(str);
return(0);
}
Вначале по команде char *str; создан тип str, который является указателем на переменную типа char(* обозначает "указатель"). По команде str = (char *)malloc(10); выделяем 10 байт памяти под переменную str(типа строка). По команде strcpy(str, "Hello"); осуществляется - "записать в область памяти, на которую указывает str, строку символов "Hello". По команде printf("String is %s\n", str); осуществляется "напечатать на экране то, на что указывает str. Команда free(str); освобождает память, на которую указывает str.
Рассмотрим более сложный пример получения доступа к записи, используя указатель.
#include <stdio.h>
#include <string.h>
#include <alloc.h>
#include <process.h>
struct Student { /* запись Student */
char Fio[30]; /* поле записи Fio */
int Gruppa; /* поле записи Gruppa */
}; /* конец записи */
struct Student *A;
main()
{
if ((A =(Student *) malloc(sizeof(Student))) == NULL)
{
printf("Нет памяти\n");
exit(1);
}
strcpy(A[1].Fio, "Ivanov");
A[1].Gruppa=385;
printf("Fio1 %s\n Gruppa %d\n", A[1].Fio, A[1].Gruppa);
strcpy(A[2].Fio, "Petrow");
A[2].Gruppa=386;
printf("Fio2 %s\n Gruppa %d\n", A[2].Fio, A[2].Gruppa);
free(A);
}
Указатель также может использоваться для получения косвенного указателя на структуру.
Пусть poit является указателем на структуру и что elem элемент, определенный структурным шаблоном. Тогда point->elem определяет элемент, на который выполняется ссылка. Рассмотрим предыдущий пример.
struct Student { /* запись Student */
char Fio[30]; /* поле записи Fio */
int Gruppa; /* поле записи Gruppa */
}; /* конец записи */
Student *poin;
Сейчас к полям структуры мы можем обращаться несколькими способами. Эквивалентные обращения:
Student.Gruppa=236;
poin->Gruppa=236;
Отметим одну важную особенность указателей в Си. Транслятор автоматически учитывает тип указателя в арифметических действиях над ним. Например если i есть указатель на целую (т.е. двухбайтную) переменную, то действие типа i++ означает, что указатель получает приращение не один, а два байта, т.е. будет указывать на следующую переменную или элемент массива. По этой причине указатель можно использовать вместо индексов массива. Например если A - указатель на массив целого типа, то вместо A[i] можно писать *(A+i). Более того, использование указателей вместо индексов позволяет компилятору создавать более компактный и быстрый код.
... . Имеет ли право на существование эта биологизаторская интерпретация экологии? Видимо, да. Она широко представлена, и с этим следует считаться. Но она не может служить концептуальной основой комплексного непрерывного экологического образования. В структуре научного знания при таком подходе не остаётся места для географической и социальной экологии, экологии человека, а сама биология превращается ...
... хотя бы стены, чтобы нас не унижали в собственном доме, до конца не растащили наше имущество, нам нужна, обладающая высоким моральным и воинским духом достойно обеспеченная армия. Однако, значение российской армии и в том, что она представляет собой, пожалуй, единственный институт в современной виртуальной России, лишенный симулякров, поскольку ней, по крайней мере, погибают реально - в бою. ...
... важные функции управления предприятием, такие как: определение задач; планирование ресурсов; оценка деятельности и мотивация персонала на основе оценки; контроль исполнения. В целом, бюджетирование решает тактические вопросы и, по существу, для стратегического управления не предназначено. Связь бюджетирования со стратегией Практика стратегического планирования западных компаний ...
... . Для этого достаточно измерить его на карте и знать масштаб карты. Компас. Научиться пользоваться компасом нетрудно. Но компас, как правило, наилучшим помощником в ориентировании становится вместе с картой. В спортивном ориентировании пользуются специальными жидкостными компасами. Они позволяют быстро и просто взять с карты нужное направление и двигаться по местности по выбранному азимуту. ...
0 комментариев