Краткий конспект семинарских занятий по языку C - Н.Д. Васюкова_ И.В. Машечкин_ В.В.Тюляева_ Е.М.Шляховая, страница 4
Описание файла
Документ из архива "Краткий конспект семинарских занятий по языку C - Н.Д. Васюкова_ И.В. Машечкин_ В.В.Тюляева_ Е.М.Шляховая", который расположен в категории "". Всё это находится в предмете "операционные системы" из 3 семестр, которые можно найти в файловом архиве МГУ им. Ломоносова. Не смотря на прямую связь этого архива с МГУ им. Ломоносова, его также можно найти и в других разделах. .
Онлайн просмотр документа "Краткий конспект семинарских занятий по языку C - Н.Д. Васюкова_ И.В. Машечкин_ В.В.Тюляева_ Е.М.Шляховая"
Текст 4 страницы из документа "Краткий конспект семинарских занятий по языку C - Н.Д. Васюкова_ И.В. Машечкин_ В.В.Тюляева_ Е.М.Шляховая"
main()
{
extern int y;
x = y = 10;
printf(“x=%d, y=%d\n”,x,y); /* x=10, y=10 */
}
…
int y;
В файле вне функций не может встречаться несколько определений переменных (возможно, разных типов) с одним и тем же именем:
int x;
main()
{
...
}
float x;/* ошибка: повторное определение x */
Указание static, примененное к переменной или функции, ограничивает область их видимости концом файла5.
Если используемая переменная определена в другом программном файле, она также должна быть описана со спецификатором extern. При таком описании переменных память не отводится, а только декларируется тип переменной.
Примечание. Переменные, определенные внутри блока, как и формальные параметры функций "перекрывают" переменные с теми же именами, видимые в пределах файла и в пределах программы.
Область существования переменной – это множество всех точек программы, при приходе управления на которые переменная существует, т.е. для нее выделена память. С этой точки зрения можно выделить две группы переменных:
1. Статические переменные
Переменные, являющиеся статическими, существуют на всем протяжении работы программы. Память под эти переменные выделяется на этапе редактирования внешних связей и загрузки программы, тогда же происходит и инициализация статических переменных (следует отметить, что статические переменные по умолчанию инициализируются нулем). Правила определения статических переменных различаются в зависимости от конкретного места программы, в котором это определение встретилось. Для определения статической переменной, локализованной в блоке, используется ключевое слово static, например:
int max; /*статическая переменная вне блока*/
int f(int param)
{
static int min; /* статическая переменная,
определенная внутри блока */
…
}
Основным свойством статических переменных, определенных внутри блока, является сохранение их значений при выходе из блока. Например:
Все переменные, определенные вне функций, являются статическими.
2. Автоматические переменные
Автоматическими переменными являются все переменные определенные внутри блока (функции) и не являющиеся статическими. Автоматические переменные существуют на протяжении работы блока, в котором они определены, включая блоки, вложенные в данный. Выделение памяти под автоматические переменные и их инициализация осуществляется каждый раз при входе в блок (начальное значение по умолчанию для автоматических переменных не определено).
При выходе из блока память, выделенная под автоматические переменные, освобождается. Таким образом, автоматические переменные являются более эффективным средством при работе с памятью, чем статические переменные. Однако, надо учитывать тот факт, что инициализация автоматических переменных, фактически, эквивалентна присваиванию значений и требует определенных временных затрат на исполнение этого кода. В качестве средств оптимизации Си программ предлагается возможность использования так называемых регистровых переменных. Это достигается за счет использования квалификатора register в определении переменных, что указывает компилятору, что данную переменную в целях ускорения программы имеет смысл разместить на регистрах, однако компилятор может проигнорировать это указание. Квалификатор register может применяться только к автоматическим переменным и формальным параметрам функций. Независимо от того, была ли переменная, описанная с квалификатором register, действительно размещена на регистрах или нет, для нее не определено понятие адреса (т.е. не определена операция &).
Рис.1
Обычно автоматические переменные, как и формальные параметры функций, реализуются с использованием стека. Параметрам функции отводится память в стеке, в которую копируются фактические значения. По окончании работы функции память освобождается. Рассмотрим состояние стека на примере рекурсивной функции (cм. Рис.1). В примере каждый раз при вызове функции f в стеке отводится место для параметра a и автоматической переменной i, а также для автоматической переменной r в случае, если ее не удалось разместить на регистрах. Заметим, что при рекурсивном вызове функции f еще раз выделяется память в стеке под автоматические переменные и параметры нового вызова. При выходе из функции выделенная в стеке память освобождается.
Препроцессор6. Стандартная схема трансляции Си-программы состоит из двух этапов: препроцессирование и собственно компиляция. Препроцессор выполняет макроподстановку, условную компиляцию, подключение файлов и тем самым формирует текст программы, поступающий на вход компилятору.
Программный файл может содержать директивы препроцессору. Директивам препроцессору предшествует знак #. Например:
#include <stdio.h> /* подключение файла */
#define a “max=%d\n” /* макроподстановка */
int x=15;
max(int);
main()
{ int y, u;
scanf(“%d”,&y);
u=max(y);
printf(a,u);
}
max(int f)
{ int k;
k=(x>f)?x:f;
return k;
}
Включение файлов. На место директивы #include препроцессор подставляет содержимое указанного в ней файла. Порядок поиска подключаемого файла зависит от реализации Си. Если имя файла заключено в < >, то в UNIX–системах он ищется в стандартном каталоге подключаемых файлов /usr/include, как например в случае:
#include <stdio.h>
Если же имя указано в “ ”, то препроцессор рассматривает его как относительный путь от местонахождения того файла, в котором встретился #include или, если имя начинается с “/” – как абсолютный путь от корня. Например:
#include “myfile.h”
#include “/usr/stuff/myfile.h”
Включаемые файлы, в свою очередь, могут содержать #include-директивы.
Макроподстановка.
#define имя подставляемый_текст
Начиная от места появления #define и до конца файла, везде, где встречается имя, указанное в #define, вместо него препроцессор подставляет заданный текст (кроме случаев, когда имя встречается внутри текста в кавычках).
Примеры:
#define NUMBER 10
#define DOUBLED_NUMBER NUMBER*2 /*в #define-определении можно использовать более ранние определения7 */
#define PrintHello printf(“Hello,\
world”); /* “\” используется для продолжения определения на следующей строке */
Можно определить макроподстановку с параметрами, что позволяет изменять подставляемый текст в зависимости от фактических параметров. Например:
#define SQR(x) ((x)*(x))
#define Print(x) printf(#x “ = %d\n”, x)
/* имя формального параметра, встретившееся в “”, не заменяется на фактический параметр, но если перед именем формального параметра стоит #, то в макрорасширении #имя_формального_параметра будет заменено на строку “фактический_параметр”, после чего в нашем примере строки конкатенируются */
main()
{
int n = 4;
Print(n); /* выводит на печать: n = 4 */
Print(SQR(n)); /* SQR(n) = 16 */
/* следующие два примера демонстрируют необходимость скобок в определении SQR(x) для обеспечения нужного порядка вычислений */
Print(SQR(n+2)); /* SQR(n+2) = 36 */
Print(256/SQR(n)); /* 256/SQR(n) = 16 */
}
Оператор ## позволяет «склеивать» аргументы в макроподстановке, например: #define bond(left, right) left##right
Таким образом для bond(name, 123)будет сгенерировано name123
Примечание. Повторный #define для того же имени является ошибкой, если подставляемые тексты не совпадают с точностью до разделяющих пробельных символов. Препроцессору можно дать указание «забыть» определенное имя с помощью директивы
#undef имя
(если имя не было ранее определено, ошибки не произойдет)
Условная компиляция. Другой пример использования препроцессора – управление выборочным включением того или иного текста в программу в зависимости от вычисляемого на этапе препроцессирования условия.
if-директива выражение_или_идентификатор
...
[{#elif выражение}
...
#else
... ]
#endif
где if-директива – это:
#if константное_выражение
(вычислить выражение и если получилось ненулевое значение, то включить весь текст до строки else-директивы или #endif)
#ifdef имя /* 1, если имя уже определено */
#ifndef имя /*1, если имя не было определено */
Например, во избежание повторного включения файла "myfile.h" его оформляют следующим образом:
ТЕМА 6. Инициализация одномерных массивов. Массивы указателей и их инициализация. Аргументы командной строки. Указатели на функции.
При инициализации массивов инициализирующие значения заключаются в фигурные скобки и, если их не хватает, то оставшимся элементам присваиваются нулевые значения. Например:
int m1[5]={0,1,2,3}, m2[10]={0};
Последний элемент массива m1 и все элементы массива m2 будут обнулены. Если размер массива не указан, то он определяется по количеству инициализирующих значений.
Массив типа char (строка) может быть проинициализирован двумя способами:
char str1[ ]={ ‘a’,’b’,’c’,’\0’};
char str2[ ]=”abc”;
Под строки str1 и str2 будет отведено по 4 байта. Строка str2 будет автоматически дополнена нулевым байтом.
При инициализации указателя на строку, в него записывается адрес константной строки, элементы которой изменять нельзя.
char *p=”abc”;
*p=’A’; /* Неправильно */
*str2=’A’; /* Правильно */
Язык Си позволяет инициализировать массив указателей, например:
char *mas[]={“for”,”while”,”do”,”return”,NULL};
Элементами массива mas являются адреса строковых констант.
mas[i] – адрес i-ой строки ( адрес её нулевого символа ).
mas[i]+j – адрес j-го символа i-ой строки.
Задача 1. Написать функцию, которой в качестве аргумента передается массив указателей на строки (признак конца - нулевой указатель). Распечатать последний символ каждой строки.
void fp(char *s[])/* void fp(char **s)
тождественно */
{ int i=0;
while(s[i]!=NULL) {
printf(“%c\n”,
*(s[i]+ strlen(s[i])-1));
i++;
}
}
Задача 2. Написать фрагмент программы, размещающий в динамической памяти вводимые из стандартного входного потока вещественные числа. Количество вводимых чисел вводится первым.
... int k,i;
double *p;
. . .
scanf(“%d”,&k);
p=(double*)malloc(k*sizeof(double));
for(i=0;i<k;i++) scanf(“%lf”,p+i);
Задача 3. Ввести строку из стандартного входного потока длиной не более ста символов и разместить ее в динамической памяти.
... char str[100],*p;
...
if(gets(str)!=NULL) {
p=(char*)malloc(strlen(str)+1);
strcpy(p,str);
}
В операционной системе имеется возможность передавать аргументы запускаемой программе при помощи командной строки. Стандарт ANSI C определяет только 2 аргумента функции main():
main(int argc, char *argv[])
В первом (argc) передается количество аргументов командной строки, во втором (argv) – адрес массива указателей на строки-аргументы. По соглашению argv[0] всегда содержит адрес строки, в которой записано имя вызванной программы. Поэтому если argc равен 1, то в командной строке после имени программы никаких аргументов нет. Кроме того, всегда выполняется соглашение argv[argc]=NULL.
Стандарт POSIX.1 определяет третий аргумент функции main():
main(int argc, char *argv[], char *envp[])
Массив envp[] содержит указатели на переменные окружения, передаваемые программе. Каждая переменная – строка вида
имя_переменной = значение_переменной
POSIX.1 также рекомендует доступ к окружению из программы через глобальную переменную environ:
еxtern char **environ;
В языке Си можно определять указатели на функции, которые ничем не отличаются от обычных указателей. Указатель на функцию можно присваивать, размещать в массиве, передавать в функцию в качестве параметра. Например:
double (*fp)(double);
fp – это указатель на функцию с одним параметром типа double, возвращающую значение типа double. Теперь указателю fp может быть присвоен адрес любой функции, имеющей такой же прототип, после чего его можно будет использовать наравне с именем функции.
double x=1.57,y;