ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 01.03.2019

Просмотров: 1199

Скачиваний: 2

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.


Функция объявляется следующим образом:

тип имя_функции(тип имя_параметра_1, тип имя_параметра_2, ...);

Тип функции определяет тип значения, которое возвращает функция. Если тип не указан, то предполагается, что функция возвращает целое значение (int).

При объявлении функции для каждого ее параметра можно указать только его тип (например: тип функция (int, float, ...), а можно дать и его имя (например: тип функция (int а, float b, ...) ).

В языке Си разрешается создавать функции с переменным числом параметров. Тогда при задании прототипа вместо последнего из них указывается многоточие.

Определение функции имеет следующий вид:

тип имя_функции(тип имя_параметра_1, тип имя_параметра_2,...)

{

тело функции

}

Передача значения из вызванной функции в вызвавшую происходит с помощью оператора возврата return, который записывается следующим образом:

return выражение;

Таких операторов в подпрограмме может быть несколько, и тогда они фиксируют соответствующие точки выхода. Например:

int f(int a, int b)

{

if (a > b) { printf("max = %d\n", a); return a; }

printf("max = %d\n", b); return b;

}

Вызвать эту функцию можно следующим образом:

c = f(15, 5);

c = f(d, g);

f(d, g);

Вызвавшая функция может, при необходимости, игнорировать возвращаемое значение. После слова return можно ничего не записывать; в этом случае вызвавшей функции никакого значения не передается. Управление передается вызвавшей функции и в случае выхода "по концу" (последняя закрывающая фигурная скобка).

В языке Си аргументы функции передаются по значению, т.е. вызванная функция получает свою временную копию каждого аргумента, а не его адрес. Это означает, что вызванная функция не может изменить значение переменной вызвавшей ее программы. Однако это легко сделать, если передавать в функцию не переменные, а их адреса. Например:

void swap(int *a, int *b)

{

int *tmp = *a;

*a = *b;

*b = *tmp;

}

Вызов swap(&b, &c) (здесь подпрограмме передаются адреса переменных b и с) приведет к тому, что значения переменных b и c поменяются местами.

Если же в качестве аргумента функции используется имя массива, то передается только адрес начала массива, а сами элементы не копируются. Функция может изменять элементы массива, сдвигаясь (индексированием) от его начала.

Рассмотрим, как функции можно передать массив в виде параметра. Здесь возможны три варианта:

Параметр задается как массив (например: int m[100];).

Параметр задается как массив без указания его размерности (например: int m[];).

Параметр задается как указатель (например: int *m;). Этот вариант используется наиболее часто.

Независимо от выбранного варианта вызванной функции передается указатель на начало массива. Сами же элементы массива не копируются.

Если некоторые переменные, константы, массивы, структуры объявлены как глобальные, то их не надо включать в список параметров вызванной функции.


25 классы переменных


В языке Си различают четыре основных класса памяти: внешнюю (глобальную), автоматическую (локальную), статическую и регистровую память.

Внешние (глобальные) переменные определены вне функций и, следовательно, доступны для любой из них. Они могут быть определены только один раз. Выше уже говорилось, что сами функции всегда глобальные. Язык не позволяет определять одни функции внутри других. Область действия внешней переменной простирается от точки во входном файле, где она объявлена, до конца файла. Если на внешнюю переменную нужно ссылаться до ее определения или она определена в другом входном файле, то в подпрограмме или файле она должна быть объявлена как extern.

Например:

extern int a; /* Объявление a; память под переменную не

резервируется */

Автоматические переменные по отношению к функциям являются внутренними или локальными. Они начинают существовать при входе в функцию и уничтожаются при выходе из нее (для них можно использовать ключевое слово auto). Однако оно практически не используется, так как при отсутствии ключевого слова переменные по умолчанию принадлежат к классу auto.


Статические переменные объявляются с помощью ключевого слова static. Они могут быть внутренними (локальными) или внешними (глобальными). Внутренние статические переменные, как и автоматические, локальны по отношению к отдельной функции. Однако они продолжают существовать, а не возникают и не уничтожаются при каждом ее вызове. Другими словами, они являются собственной постоянной памятью для функции. Внешние статические переменные доступны внутри оставшейся части файла после того, как они в нем объявлены, однако в других файлах они неизвестны. Это, в частности, позволяет скрыть данные одного файла от другого файла.

Регистровые переменные относятся к последнему классу. Ключевое слово register говорит о том, что переменная, о которой идет речь, будет интенсивно использоваться. Если возможно, значения таких переменных помещаются во внутренние регистры микропроцессора, что может привести к более быстрой и короткой программе (разработчики компиляторов фирмы Borland утверждают, что оптимизация компиляторов данной фирмы по использованию регистровых переменных сделана так хорошо, что указание использовать переменную как регистровую может только снизить эффективность создаваемого машинного кода). Для регистровых переменных нельзя взять адрес; они могут быть только автоматическими с допустимыми типами int или char.

Таким образом, можно выделить четыре модификатора класса памяти: extern, auto, static, register. Они используются в следующей общей форме:

модификатор_класса_памяти тип список_переменных;

Выше уже говорилось об инициализации, т.е. о присвоении различным объектам начальных значений. Если явная инициализация отсутствует, гарантируется, что внешние и статические переменные будут иметь значение нуль, а автоматические и регистровые - неопределенное значение.


26 – 27 Препроцессор


Препроцессор Си - это программа, которая обрабатывает входные данные для компилятора. Препроцессор просматривает исходную программу и выполняет следующие действия: подключает к ней заданные файлы, осуществляет подстановки, а также управляет условиями компиляции. Для препроцессора предназначены строки программы, начинающиеся с символа #. В одной строке разрешается записывать только одну команду (директиву препроцессора).

Директива #define идентификатор подстановка

вызывает замену в последующем тексте программы названного идентификатора на текст подстановки (обратите внимание на отсутствие точки с запятой в конце этой команды). По существу, эта директива вводит макроопределение (макрос), где "идентификатор" - это имя макроопределения, а "подстановка" - последовательность символов, на которые препроцессор заменяет указанное имя, когда находит его в тексте программы. Имя макроопределения принято набирать прописными буквами.

Рассмотрим примеры:

#define MAX 25

#define BEGIN {

Первая строка вызывает замену в программе идентификатора MAX на константу 25. Вторая позволяет использовать в тексте вместо открывающей фигурной скобки ( { ) слово BEGIN.

Отметим, что поскольку препроцессор не проверяет совместимость между символическими именами макроопределений и контекстом, в котором они используются, то рекомендуется такого рода идентификаторы определять не директивой #define, а с помощью ключевого слова const с явным указанием типа (это в большей степени относится к Си++):

const int MAX = 25;

(тип int можно не указывать, так как он устанавливается по умолчанию).

Если директива #define имеет вид:

#define идентификатор(идентификатор, ..., идентификатор) подстановка

причем между первым идентификатором и открывающей круглой скобкой нет пробела, то это определение макроподстановки с аргументами. Например, после появления строки вида:

#define READ(val) scanf("%d", &val)

оператор READ(y); воспринимается так же, как scanf("%d",&y);. Здесь val - аргумент и выполнена макроподстановка с аргументом.

При наличии длинных определений в подстановке, продолжающихся в следующей строке, в конце очередной строки с продолжением ставится символ \.

В макроопределение можно помещать объекты, разделенные знаками ##, например:

#define PR(x, у) x##y

После этого PR(а, 3) вызовет подстановку а3. Или, например, макроопределение

#define z(a, b, c, d) a(b##c##d)

приведет к замене z(sin, x, +, y) на sin(x+y).

Символ #, помещаемый перед макроаргументом, указывает на преобразование его в строку. Например, после директивы

#define PRIM(var) printf(#var"= %d", var)

следующий фрагмент текста программы

year = 2006;

PRIM(year);

преобразуется так:

year = 2006;

printf("year""= %d", year);

Опишем другие директивы препроцессора.

Директива #include уже встречалась ранее. Ее можно использовать в двух формах:

#include "имя файла"

#include <имя файла>

Действие обеих команд сводится к включению в программу файлов с указанным именем. Первая из них загружает файл из текущего или заданного в качестве префикса каталога. Вторая команда осуществляет поиск файла в стандартных местах, определенных в системе программирования. Если файл, имя которого записано в двойных кавычках, не найден в указанном каталоге, то поиск будет продолжен в подкаталогах, заданных для команды #include <...>. Директивы #include могут вкладываться одна в другую.


Следующая группа директив позволяет избирательно компилировать части программы. Этот процесс называется условной компиляцией. В эту группу входят директивы #if, #else, #elif, #endif, #ifdef, #ifndef. Основная форма записи директивы #if имеет вид:

#if константное_выражение последовательность_операторов

#endif

Здесь проверяется значение константного выражения. Если оно истинно, то выполняется заданная последовательность операторов, а если ложно, то эта последовательность операторов пропускается.

Действие директивы #else подобно действию команды else в языке Си, например:

#if константное_выражение

последовательность_операторов_1

#else

последовательность_операторов_2

#endif

Здесь если константное выражение истинно, то выполняется последовательность_операторов_1, а если ложно - последовательность_операторов_2.

Директива #elif означает действие типа "else if". Основная форма ее использования имеет вид:

#if константное_выражение

последовательность_операторов

#elif константное_выражение_1

последовательность_операторов_1

#elif константное_выражение_n

последовательность_операторов_n

#endif

Эта форма подобна конструкции языка Си вида: if...else if...else if...

Директива #ifdef идентификатор

устанавливает определен ли в данный момент указанный идентификатор, т.е. входил ли он в директивы вида #define. Строка вида

#ifndef идентификатор

проверяет является ли неопределенным в данный момент указанный идентификатор. За любой из этих директив может следовать произвольное число строк текста, возможно, содержащих инструкцию #else (#elif использовать нельзя) и заканчивающихся строкой #endif. Если проверяемое условие истинно, то игнорируются все строки между #else и #endif, а если ложно, то строки между проверкой и #else (если слова #else нет, то #endif). Директивы #if и #ifndef могут "вкладываться" одна в другую.

Директива вида

#undef идентификатор

приводит к тому, что указанный идентификатор начинает считаться неопределенным, т.е. не подлежащим замене.

Рассмотрим примеры. Три следующие директивы:

#ifdef WRITE

#undef WRITE

#endif

проверяют определен ли идентификатор WRITE (т.е. была ли команда вида #define WRITE...), и если это так, то имя WRITE начинает считаться неопределенным, т.е. не подлежащим замене.

Директивы

#ifndef WRITE

#define WRITE fprintf

#endif

проверяют является ли идентификатор WRITE неопределенным, и если это так, то определятся идентификатор WRITE вместо имени fprintf.

Директива #error записывается в следующей форме:

#error сообщение_об_ошибке

Если она встречается в тексте программы, то компиляция прекращается и на экран дисплея выводится сообщение об ошибке. Эта команда в основном применяется на этапе отладки. Заметим, что сообщение об ошибке не надо заключать в двойные кавычки.

Директива #line предназначена для изменения значений переменных _LINE_ и _FILE_, определенных в системе программирования Си. Переменная _LINE_ содержит номер строки программы, выполняемой в текущий момент времени. Идентификатор _FILE_ является указателем на строку с именем компилируемой программы. Директива #line записывается следующим образом:


#line номер "имя_файла"

Здесь номер - это любое положительное целое число, которое будет назначено переменной _LINE_, имя_файла - это необязательный параметр, который переопределяет значение _FILE_.

Директива #pragma позволяет передать компилятору некоторые указания. Например, строка

#pragma inline

говорит о том, что в программе на языке Си имеются строки на языке ассемблера. Например:

asm mov ax, 5

asm {

inc dx

sub bl, al

}

и т.д.

Рассмотрим некоторые глобальные идентификаторы или макроимена (имена макроопределений). Определены пять таких имен: _LINE_, _FILE_, _DATE_, _TIME_, _STDC_. Два из них (_LINE_ и _FILE_) уже описывались выше. Идентификатор _DATE_ определяет строку, в которой сохраняется дата трансляции исходного файла в объектный код. Идентификатор _TIME_ задает строку, сохраняющую время трансляции исходного файла в объектный код. Макрос _STDC_ имеет значение 1, если используются стандартно - определенные макроимена. В противном случае эта переменная не будет определена.

28 Форматированный ввод данных


Функция scanf( ) (прототип содержится в файле stdio.h) обеспечивает форматированный ввод. Ее можно записать в следующем формальном виде:

scanf("управляющая строка", аргумент_1, аргумент_2,...);

Аргументы scanf( ) должны быть указателями на соответствующие значения. Для этого перед именем переменной записывается символ &. Назначение указателей будет рассмотрено далее.

Управляющая строка содержит спецификации преобразования и используется для установления количества и типов аргументов. В нее могут включаться:

пробелы, символы табуляции и перехода на новую строку (все они игнорируются);

спецификации преобразования, состоящие из знака %, возможно, символа * (запрещение присваивания), возможно, числа, задающего максимальный размер поля, и самого символа преобразования;

обычные символы, кроме % (считается, что они должны совпадать с очередными неизвестными символами во входном потоке).

Рассмотрим символы преобразования функции scanf( ) (указываются после символа %):

с - на входе ожидается появление одиночного символа;

d или i - на входе ожидается десятичное целое число и аргумент является указателем на переменную типа int;

D или l - на входе ожидается десятичное целое число и аргумент является указателем на переменную типа long;

е или Е - на входе ожидается вещественное число с плавающей точкой;

f - на входе ожидается вещественное число с плавающей точкой;

g или G - на входе ожидается вещественное число с плавающей точкой;

о - на входе ожидается восьмеричное целое число и аргумент является указателем на переменную типа int;

О - на входе ожидается восьмеричное целое число и аргумент является указателем на переменную типа long;

s - на входе ожидается появление строки символов;

х - на входе ожидается шестнадцатеричное целое число и аргумент является указателем на переменную типа int;

Х - на входе ожидается шестнадцатеричное целое число и аргумент является указателем на переменную типа long;