Жанр: Учеба
Язык Си
...OR (GAP = N/2; GAP " 0; GAP /= 2)
FOR (I = GAP; I " N; I++)
FOR (J=I-GAP; J"=0 && V[J]"V[J+GAP]; J-=GAP) {
TEMP = V[J];
V[J] = V[J+GAP];
V[J+GAP] = TEMP;
}
}
Здесь имеются три вложенных цикла. Самый внешний цикл управляет
интервалом между сравниваемыми элементами, уменьшая его
от N/2 вдвое при каждом проходе, пока он не станет равным
нулю. Средний цикл сравнивает каждую пару элементов, разделенных
на величину интервала; самый внутренний цикл переставляет
любую неупорядоченную пару. Так как интервал в конце
концов сводится к единице, все элементы в результате упорядочиваются
правильно. Отметим, что в силу общности конструкции
FOR внешний цикл укладывается в ту же самую форму, что и
остальные, хотя он и не является арифметической прогрессией.
Последней операцией языка "C" является запятая ",", которая
чаще всего используется в операторе FOR. Два выражения,
разделенные запятой, вычисляются слева направо, причем
типом и значением результата являются тип и значение правого
операнда. Таким образом, в различные части оператора FOR
можно включить несколько выражений, например, для параллельного
изменения двух индексов. Это иллюстрируется функцией
REVERSE(S), которая располагает строку S в обратном порядке
на том же месте.
REVERSE(S) /* REVERSE STRING S IN PLACE */
CHAR S[];
{
INT C, I, J;
FOR(I = 0, J = STRLEN(S) - 1; I " J; I++, J--) {
C = S[I];
S[I] = S[J];
S[J] = C;
}
}
Запятые, которые разделяют аргументы функций, переменные в
описаниях и т.д., не имеют отношения к операции запятая и не
обеспечивают вычислений слева направо.
Упражнение 3-2
---------------
Составьте программу для функции EXPAND(S1,S2), которая
расширяет сокращенные обозначения вида а-Z из строки S1 в
эквивалентный полный список авс...XYZ в S2. Допускаются сокращения
для строчных и прописных букв и цифр. Будьте готовы
иметь дело со случаями типа а-в-с, а-Z0-9 и -а-Z. (Полезное
соглашение состоит в том, что символ -, стоящий в начале или
конце, воспринимается буквально).
3.6. Цикл DO - WHILE
Как уже отмечалось в главе 1, циклы WHILE и FOR обладают
тем приятным свойством, что в них проверка окончания осуществляется
в начале, а не в конце цикла. Третий оператор цикла
языка "C", DO-WHILE, проверяет условие окончания в конце,
после каждого прохода через тело цикла; тело цикла всегда
выполняется по крайней мере один раз. Синтаксис этого оператора
имеет вид:
DO
оператор
WHILE (выражение)
Сначала выполняется оператор, затем вычисляется выражение.
Если оно истинно, то оператор выполняется снова и т.д. Если
выражение становится ложным, цикл заканчивается.
Как и можно было ожидать, цикл DO-WHILE используется
значительно реже, чем WHILE и FOR, составляя примерно пять
процентов от всех циклов. Тем не менее, иногда он оказывается
полезным, как, например, в следующей функции ITOA, которая
преобразует число в символьную строку (обратная функции
ATOI). Эта задача оказывается несколько более сложной, чем
может показаться сначала. Дело в том, что простые методы выделения
цифр генерируют их в неправильном порядке. Мы предпочли
получить строку в обратном порядке, а затем обратить
ее.
ITOA(N,S) /*CONVERT N TO CHARACTERS IN S */
CHAR S[];
INT N;
{
INT I, SIGN;
IF ((SIGN = N) " 0) /* RECORD SIGN */
N = -N; /* MAKE N POSITIVE */
I = 0;
DO { /* GENERATE DIGITS IN REVERSE ORDER */
S[I++] = N % 10 + '0';/* GET NEXT DIGIT */
} WHILE ((N /=10) " 0); /* DELETE IT */
IF (SIGN " 0)
S[I++] = '-'
S[I] = '\0';
REVERSE(S);
}
Цикл DO-WHILE здесь необходим, или по крайней мере удобен,
поскольку, каково бы ни было значение N, массив S должен содержать
хотя бы один символ. Мы заключили в фигурные скобки
один оператор, составляющий тело DO-WHILе, хотя это и не
обязательно, для того, чтобы торопливый читатель не принял
часть WHILE за начало оператора цикла WHILE.
Упражнение 3-3
--------------
При представлении чисел в двоичном дополнительном коде
наш вариант ITOA не справляется с наибольшим отрицательным
числом, т.е. Со значением N рАвным -2 в степени м-1, где м -
размер слова. объясните почему. Измените программу так, чтобы
она правильно печатала это значение на любой машине.
Упражнение 3-4
--------------
Напишите аналогичную функцию ITOB(N,S), которая преобразует
целое без знака N в его двоичное символьное представление
в S. Запрограммируйте функцию ITOH, которая преобразует
целое в шестнадцатеричное представление.
Упражнение 3-5
---------------
Напишите вариант Iтоа, который имеет три, а не два аргумента.
Третий аргумент - минимальная ширина поля; преобразованное
число должно, если это необходимо, дополняться слева
пробелами, так чтобы оно имело достаточную ширину.
3.7. Оператор BREAK
Иногда бывает удобным иметь возможность управлять выходом
из цикла иначе, чем проверкой условия в начале или в
конце. Оператор BRеак позволяет выйти из операторов FOR,
WHILE и DO до окончания цикла точно так же, как и из переключателя.
Оператор BRеак приводит к немедленному выходу из
самого внутреннего охватывающего его цикла (или переключателя).
Следующая программа удаляет хвостовые пробелы и табуляции
из конца каждой строки файла ввода. Она использует оператор
BRеак для выхода из цикла, когда найден крайний правый
отличный от пробела и табуляции символ.
#DEFINE MAXLINE 1000
MAIN() /* REMOVE TRAILING BLANKS AND TABS */
{
INT N;
CHAR LINE[MAXLINE];
WHILE ((N = GETLINE(LINE,MAXLINE)) " 0) {
WHILE (--N "= 0)
IF (LINE[N] != ' ' && LINE[N] != '\T'
&& LINE[N] != '\N')
BREAK;
LINE[N+1] = '\0';
PRINTF("%S\N",LINE);
}
}
Функция GETLINE возвращает длину строки. Внутренний цикл
начинается с последнего символа LINE (напомним, что --N
уменьшает N до использования его значения) и движется в обратном
направлении в поиске первого символа , который отличен
от пробела, табуляции или новой строки. Цикл прерывается,
когда либо найден такой символ, либо N становится отрицательным
(т.е., когда просмотрена вся строка). Советуем вам
убедиться, что такое поведение правильно и в том случае,
когда строка состоит только из символов пустых промежутков.
В качестве альтернативы к BRеак можно ввести проверку в
сам цикл:
WHILE ((N = GETLINE(LINE,MAXLINE)) " 0) {
WHILE (--N "= 0
&& (LINE[N] == ' ' \!\! LINE[N] == '\T'
\!\! LINE[N] == '\N'))
;
}
Это уступает предыдущему варианту, так как проверка становится
труднее для понимания. Проверок, которые требуют переплетения
&&, \!\!, ! И круглых скобок, по возможности следует
избегать.
3.8. Оператор CONTINUE
Оператор CONTINUE родственен оператору BRеак, но используется
реже; он приводит к началу следующей итерации охватывающего
цикла (FOR, WHILE, DO ). В циклах WHILE и DO это означает
непосредственный переход к выполнению проверочной
части; в цикле FOR управление передается на шаг реинициализации.
(Оператор CONTINUE применяется только в циклах, но не
в переключателях. Оператор CONTINUE внутри переключателя
внутри цикла вызывает выполнение следующей итерации цикла).
В качестве примера приведем фрагмент, который обрабатывает
только положительные элементы массива а; отрицательные
значения пропускаются.
FOR (I = 0; I " N; I++) {
IF (A[I] " 0) /* SKIP NEGATIVE ELEMENTS */
CONTINUE;
... /* DO POSITIVE ELEMENTS */
}
Оператор CONTINUE часто используется, когда последующая
часть цикла оказывается слишком сложной, так что рассмотрение
условия, обратного проверяемому, приводит к слишком глубокому
уровню вложенности программы.
Упражнение 3-6
---------------------------------------------------------------------------
Напишите программу копирования ввода на вывод, с тем исключением,
что из каждой группы последовательных одинаковых
строк выводится только одна. (Это простой вариант утилиты
UNIQ систем UNIX).
3.9. Оператор GOTO и метки
В языке "C" предусмотрен и оператор GOTO, которым бесконечно
злоупотребляют, и метки для ветвления. С формальной
точки зрения оператор GOTO никогда не является необходимым,
и на практике почти всегда можно обойтись без него. Мы не
использовали GOTO в этой книге.
Тем не менее, мы укажем несколько ситуаций, где оператор
GOTO может найти свое место. Наиболее характерным является
его использование тогда, когда нужно прервать выполнение в
некоторой глубоко вложенной структуре, например, выйти сразу
из двух циклов. Здесь нельзя непосредственно использовать
оператор BRеак, так как он прерывает только самый внутренний
цикл. Поэтому:
FOR ( ... )
FOR ( ... ) {
IF (DISASTER)
GOTO ERROR;
}
ERROR:
CLEAN UP THE MESS
Если программа обработки ошибок нетривиальна и ошибки могут
возникать в нескольких местах, то такая организация оказывается
удобной. Метка имеет такую же форму, что и имя переменной,
и за ней всегда следует двоеточие. Метка может быть
приписана к любому оператору той же функции, в которой находится
оператор GOTO.
В качестве другого примера рассмотрим задачу нахождения
первого отрицательного элемента в двумерном массиве. (Многомерные
массивы рассматриваются в главе 5). Вот одна из возможностей:
FOR (I = 0; I " N; I++)
FOR (J = 0; J " M; J++)
IF (V[I][J] " 0)
GOTO FOUND;
/* DIDN'T FIND */
FOUND:
/* FOUND ONE AT POSITION I, J */
Программа, использующая оператор GOTO, всегда может быть
написана без него, хотя, возможно, за счет повторения некоторых
проверок и введения дополнительных переменных. Например,
программа поиска в массиве примет вид:
FOUND = 0;
FOR (I = 0; I " N && !FOUND; I++)
FOR (J = 0; J " M && !FOUND; J++)
FOUND = V[I][J] " 0;
IF (FOUND)
/* IT WAS AT I-1, J-1 */
ELSE
/* NOT FOUND */
Хотя мы не являемся в этом вопросе догматиками, нам все
же кажется, что если и нужно использовать оператор GOTO, то
весьма умеренно.
* 4. Функции и структура программ *
Функции разбивают большие вычислительные задачи на маленькие
подзадачи и позволяют использовать в работе то, что
уже сделано другими, а не начинать каждый раз с пустого места.
Соответствующие функции часто могут скрывать в себе детали
проводимых в разных частях программы операций, знать
которые нет необходимости, проясняя тем самым всю программу,
как целое, и облегчая мучения при внесении изменений.
Язык "C" разрабатывался со стремлением сделать функции
эффективными и удобными для использования; "C"-программы
обычно состоят из большого числа маленьких функций, а не из
нескольких больших. Программа может размещаться в одном или
нескольких исходных файлах любым удобным образом; исходные
файлы могут компилироваться отдельно и загружаться вместе
наряду со скомпилированными ранее функциями из библиотек. Мы
здесь не будем вдаваться в детали этого процесса, поскольку
они зависят от используемой системы.
Большинство программистов хорошо знакомы с "библиотечными"
функциями для ввода и вывода /GETCHAR , PUTCHAR/ и для
численных расчетов /SIN, COS, SQRT/. В этой главе мы сообщим
больше о написании новых функций.
4.1. Основные сведения
Для начала давайте разработаем и составим программу печати
каждой строки ввода, которая содержит определенную комбинацию
символов. /Это - специальный случай утилиты GREP
системы "UNIX"/. Например, при поиске комбинации "THE" в наборе
строк
NOW IS THE TIME
FOR ALL GOOD
MEN TO COME TO THE AID
OF THEIR PARTY
в качестве выхода получим
NOW IS THE TIME
MEN TO COME TO THE AID
OF THEIR PARTY
основная схема выполнения задания четко разделяется на три
части:
WHILE (имеется еще строка)
IF (строка содержит нужную комбинацию)
вывод этой строки
Конечно, возможно запрограммировать все действия в виде
одной основной процедуры, но лучше использовать естественную
структуру задачи и представить каждую часть в виде отдельной
функции. С тремя маленькими кусками легче иметь дело, чем с
одним большим, потому что отдельные не относящиеся к существу
дела детали можно включить в функции и уменьшить возможность
нежелательных взаимодействий. Кроме того, эти куски
могут оказаться полезными сами по себе.
"Пока имеется еще строка" - это GETLINE, функция, которую
мы запрограммировали в главе 1, а "вывод этой строки" -
это функция PRINTF, которую уже кто-то подготовил для нас.
Это значит, что нам осталось только написать процедуру для
определения, содержит ли строка данную комбинацию символов
или нет. Мы можем решить эту проблему, позаимствовав разработку
из PL/1: функция INDEX(S,т) возвращает позицию, или
индекс, строки S, где начинается строка T, и -1, если S не
содержит т . В качестве начальной позиции мы используем 0, а
не 1, потому что в языке "C" массивы начинаются с позиции
нуль. Когда нам в дальнейшем понадобится проверять на совпадение
более сложные конструкции, нам придется заменить только
функцию INDEX; остальная часть программы останется той же
самой.
После того, как мы потратили столько усилий на разработку,
написание программы в деталях не представляет затруднений.
ниже приводится целиком вся программа, так что вы можете
видеть, как соединяются вместе отдельные части. Комбинация
символов, по которой производится поиск, выступает пока
в качестве символьной строки в аргументе функции INDEX, что
не является самым общим механизмом. Мы скоро вернемся к обсуждению
вопроса об инициализации символьных массивов и в
главе 5 покажем, как сделать комбинацию символов параметром,
которому присваивается значение в ходе выполнения программы.
Программа также содержит новый вариант функции GETLINE; вам
может оказаться полезным сравнить его с вариантом из главы
1.
#DEFINE MAXLINE 1000
MAIN() /* FIND ALL LINES MATCHING A PATTERN */
{
CHAR LINE[MAXLINE];
WHILE (GETLINE(LINE, MAXLINE) " 0)
IF (INDEX(LINE, "THE") "= 0)
PRINTF("%S", LINE);
}
GETLINE(S, LIM) /* GET LINE INTO S, RETURN LENGTH *
CHAR S[];
INT LIM;
{
INT C, I;
I = 0;
WHILE(--LIM"0 && (C=GETCHAR()) != EOF && C != '\N')
S[I++] = C;
IF (C == '\N')
S[I++] = C;
S[I] = '\0';
RETURN(I);
}
INDEX(S,T) /* RETURN INDEX OF T IN S,-1 IF NONE */
CHAR S[], T[];
{
INT I, J, K;
FOR (I = 0; S[I] != '\0'; I++) {
FOR(J=I, K=0; T[K] !='\0' && S[J] == T[K]; J++; K++)
;
IF (T[K] == '\0')
RETURN(I);
}
RETURN(-1);
}
Каждая функция имеет вид имя (список аргументов, если они
имеются) описания аргументов, если они имеются
{
описания и операторы , если они имеются
}
Как и указывается, некоторые части могут отсутствовать;
минимальной функцией является
DUMMY () { }
которая не совершает никаких действий.
/Такая ничего не делающая функция иногда оказывается
удобной для сохранения места для дальнейшего развития программы/.
если функция возвращает что-либо отличное от целого
значения, то перед ее именем может стоять указатель типа;
этот вопрос обсуждается в следующем разделе.
Программой является просто набор определений отдельных
функций. Связь между функциями осуществляется через аргументы
и возвращаемые функциями значения /в этом случае/; ее
можно также осуществлять через внешние переменные. Функции
могут располагаться в исходном файле в любом порядке, а сама
исходная программа может размещаться на нескольких файлах,
но так, чтобы ни одна функция не расщеплялась.
Оператор RETURN служит механизмом для возвращения значения
из вызванной функции в функцию, которая к ней обратилась.
За RETURN может следовать любое выражение:
RETURN (выражение)
Вызывающая функция может игнорировать возвращаемое
значение, если она этого пожелает. Более того, после RETURN
может не быть вообще никакого выражения; в этом случае в вызывающую
программу не передается никакого значения. Управление
также возвращется в вызывающую программу без передачи
какого-либо значения и в том случае, когда при выполнении мы
"проваливаемся" на конец функции, достигая закрывающейся
правой фигурной скобки. ЕСли функция возвращает значение из
одного места и не возвращает никакого значения из другого
места, это не является незаконным, но может быть признаком
каких-то неприятностей. В любом случае "значением" функции,
которая не возвращает значения, несомненно будет мусор. Отладочная
программа LINT проверяет такие ошибки.
Механика компиляции и загрузки "C"-программ, расположенных
в нескольких исходных файлах, меняется от системы к
системе. В системе "UNIX", например, эту работу выполняет
команда 'CC', упомянутая в главе 1. Предположим, что три
функции находятся в трех различных файлах с именами MAIN.с,
GETLINE.C и INDEX.с . Тогда команда
CC MAIN.C GETLINE.C INDEX.C
компилирует эти три файла, помещает полученный настраиваемый
объектный код в файлы MAIN.O, GETLINE.O и INDEX.O и загружает
их всех в выполняемый файл, называемый A.OUT .
Если имеется какая-то ошибка, скажем в MAIN.C, то этот
файл можно перекомпилировать отдельно и загрузить вместе с
предыдущими объектными файлами по команде
CC MAIN.C GETLIN.O INDEX.O
Команда 'CC' использует соглашение о наименовании с ".с"
и ".о" для того, чтобы отличить исходные файлы от объектных.
Упражнение 4-1
----------------
Составьте программу для функции RINDEX(S,T), которая
возвращает позицию самого правого вхождения т в S и -1, если
S не содержит T.
4.2. Функции, возвращающие нецелые значения
До сих пор ни одна из наших программ не содержала какого-либо
описания типа функции. Дело в том, что по умолчанию
функция неявно описывается своим появлением в выражении или
операторе, как, например, в
WHILE (GETLINE(LINE, MAXLINE) " 0)
Если некоторое имя, которое не было описано ранее, появляется
в выражении и за ним следует левая круглая скобка, то
оно по контексту считается именем некоторой функции. Кроме
того, по умолчанию предполагается, что эта функция возвращает
значение типа INT. Так как в выражениях CHAR преобразуется
в INT, то нет необходимости описывать функции, возвращающие
CHAR. Эти предположения покрывают большинство случаев,
включая все приведенные до сих пор примеры.
Но что происходит, если функция должна возвратить значение
какого-то другого типа ? Многие численные функции, такие
как SQRT, SIN и COS возвращают DOUBLE; другие специальные
функции возвращают значения других типов. Чтобы показать,
как поступать в этом случае, давайте напишем и используем
функцию AТоF(S), которая преобразует строку S в эквивалентное
ей плавающее число двойной точности. Функция AТоF является
расширением атоI, варианты которой мы написали в главах
2 и 3; она обрабатывает необязательно знак и десятичную точку,
а также целую и дробную часть, каждая из которых может
как присутствовать, так и отсутствовать./эта процедура преобразования
ввода не очень высокого качества; иначе она бы
заняла больше места, чем нам хотелось бы/.
Во-первых, сама AТоF должна описывать тип возвращаемого
ею значения, поскольку он отличен от INT. Так как в выражениях
тип FLOAT преобразуется в DOUBLE, то нет никакого смысла
в том, чтобы ATOF возвращала FLOAT; мы можем с равным успехом
воспользоваться дополнительной точностью, так что мы
полагаем, что возвращаемое значение типа DOUBLE. Имя типа
должно стоять перед именем функции, как показывается ниже:
DOUBLE ATOF(S) /* CONVERT STRING S TO DOUBLE */
CHAR S[];
{
DOUBLE VAL, POWER;
INT I, SIGN;
FOR(I=0; S[I]==' ' \!\! S[I]=='\N' \!\! S[I]=='\T'; I++)
; /* SKIP WHITE SPACE */
SIGN = 1;
IF (S[I] == '+' \!\! S[I] == '-') /* SIGN */
SIGN = (S[I++] == '+') ? 1 : -1;
FOR (VAL = 0; S[I] "= '0' && S[I] "= '9'; I++)
VAL = 10 * VAL + S[I] - '0';
IF (S[I] == '.')
I++;
FOR (POWER = 1; S[I] "= '0' && S[I] "= '9'; I++) {
VAL = 10 * VAL + S[I] - '0';
POWER *= 10;
}
RETURN(SIGN * VAL / POWER);
}
Вторым, но столь же важным, является то, что вызывающая
функция должна объявить о том, что ATOF возвращает значение,
отличное от INT типа. Такое объявление демонстрируется на
примере следующего примитивного настольного калькулятора
/едва пригодного для подведения баланса в чековой книжке/,
который считывает по одному числу на строку, причем это число
может иметь знак, и складывает все числа, печатая сумму
после каждого ввода.
#DEFINE MAXLINE 100
MAIN() /* RUDIMENTARY DESK CALKULATOR */
{
DOUBLE SUM, ATOF();
CHAR LINE[MAXLINE];
SUM = 0;
WHILE (GETLINE(LINE, MAXLINE) " 0)
PRINTF("\T%.2F\N",SUM+=ATOF(LINE));
Оисание
DOUBLE SUM, ATOF();
говорит, что SUM является переменной типа DOUBLE , и что
ATOF является функцией, возвращающей значение типа DOUBLE .
Эта мнемоника означает, что значениями как SUM, так и
ATOF(...) являются плавающие числа двойной точности.
Если функция ATOF не будет описана явно в обоих местах,
то в "C" предполагается, что она возвращает целое значение,
и вы получите бессмысленный ответ. Если сама ATOF и обращение
к ней в MAIN имеют несовместимые типы и находятся в одном
и том же файле, то это будет обнаружено компилятором. Но
если ATOF была скомпилирована отдельно /что более вероятно/,
то это несоответствие не будет зафиксировано, так что ATOF
будет возвращать значения типа DOUBLE, с которым MAIN будет
обращаться, как с INT , что приведет к бессмысленным результатам.
/Программа LINT вылавливает эту ошибку/.
Имея ATOF, мы, в принципе, могли бы с ее помощью написать
ATOI (преобразование строки в INT):
ATOI(S) /* CONVERT STRING S TO INTEGER */
CHAR S[];
{
DOUBLE ATOF();
RETURN(ATOF(S));
}
Обратите внимание на структуру описаний и оператор RETURN.
Значение выражения в
RETURN (выражение)
всегда преобразуется к типу функции перед выполнением самого
возвращения. Поэтому при появлении в операторе RETURN значение
функции атоF, имеющее тип DOUBLE, автоматически преобразуется
в INT, поскольку функция ATOI возвращает INT. (Как
обсуждалось в главе 2, преобразование значения с плавающей
точкой к типу INT осуществляется посредством отбрасывания
дробной части).
Упражнение 4-2
----------------
Расширьте ATOF таким образом, чтобы она могла работать с
числами вида
123.45е-6
где за числом с плавающей точкой может следовать 'E' и показатель
экспоненты, возможно со знаком.
4.3. Еще об аргументах функций
В главе 1 мы уже обсуждали тот факт , что аргументы функций
передаются по значению, т.е. вызванная функция получает
свою временную копию каждого аргумента, а не его адрес. это
означает, что вызванная функция не может воздействовать на
исходный аргумент в вызывающей функции. Внутри функции каждый
аргумент по существу является локальной переменной, которая
инициализируется тем значением, с которым к этой функции
обратились.
Если в качестве аргумента функции выступает имя массива,
то передается адрес начала этого массива; сами элементы не
копируются. Функция может изменять элементы массива, используя
индексацию и адрес начала. Таким образом, массив передается
по ссылке. В главе 5 мы обсудим, как использование указателей
позволяет функциям воздействовать на отличные от
массивов переменные в вызывающих функциях.
Между прочим, несуществует полностью удовлетворительного
способа написания переносимой функции с переменным числом
аргументов. Дело в том, что нет переносимого способа, с помощью
которого вызванная функция могла бы определить, сколько
аргументов было фактически передано ей в данном обращении.
Таким образом, вы, например, не можете написать действительно
переносимую функцию, которая будет вычислять максимум
от произвольного числа аргументов, как делают встроенные
функции MAX в фортране и PL/1.
Обычно со случаем переменного числа аргументов безопасно
иметь дело, если вызванная функция не использует аргументов,
которые ей на самом деле не были переданы, и если типы согласуются.
Самая распространенная в языке "C" функция с переменным
числом - PRINTF . Она получает из первого аргумента
информацию, позволяющую определить количество остальных аргументов
и их типы. Функция PRINTF работает совершенно неправильно,
если вызывающая функция передает ей недостаточное
количество аргументов, или если их типы не согласуются с типами,
указанными в первом аргументе. Эта функция не является
переносимой и должна модифицироваться при использовании в
различных условиях.
Если же типы аргументов известны, то конец списка аргументов
можно отметить, используя какое-то соглашение; например,
считая, что некоторое специальное значение аргумента
(часто нуль) является признаком конца аргументов.
4.4. Внешние переменные
Программа на языке "C" состоит из набора внешних объектов,
которые являются либо переменными, либо функциями. Термин
"внешний" используется главным образом в противопоставление
термину "внутренний", которым описываются аргументы и
автоматические переменные, определенные внурти функций.
Внешние переменные определены вне какой-либо функции и, таким
образом, потенциально доступны для многих функций. Сами
функции всегда являются внешними, потому что правила языка
"C" не разрешают определять одни функции внутри других. По
умолчанию внешние переменные являются также и "глобальными",
так что все ссылки на такую переменную, использующие одно и
то же имя (даже из функций, скомпилированных независимо),
будут ссылками на одно и то же. В этом смысле внешние переменные
аналогичны переменным COмМON в фортране и EXTERNAL в
PL/1. Позднее мы покажем, как определить внешние переменные
и функции таким образом, чтобы они были доступны не глобально,
а только в пределах одного исходного файла.
В силу своей глобальной доступности внешние переменные
предоставляют другую, отличную от аргументов и возвращаемых
значений, возможность для обмена данными между функциями.
Если имя внешней переменной каким-либо образом описано, то
любая функция имеет доступ к этой переменной, ссылаясь к ней
по этому имени.
В случаях, когда связь между функциями осуществляется с
помощью большого числа переменных, внешние переменные оказываются
более удобными и эффективными, чем использование
длинных списков аргументов. Как, однако, отмечалось в главе
1, это соображение следует использовать с определенной осторожностью,
так как оно может плохо отразиться на структуре
программ и приводить к программам с большим числом связей по
данным между функциями.
Втор
...Поділитися у соцмережах