Функции и структура программ☛Язык программирования C ✎ |
Регистровые переменные
Объявление регистровой переменной указывает компилятору, что данная переменная будет интенсивно использоваться. Идея заключается в том, что регистровые переменные помещено в регистры процессора, что приводит к меньшим и более быстрых программ. Но компиляторы свободные игнорировать этот совет. Объявление регистровых переменных может выглядеть как
register int x;
register char c;
и т.д. Регистровые объявления могут применяться к автоматическим переменных и в формальных параметров функции. В последнем случае, это может иметь вид
f (register unsigned m, register long n)
{
register int i;
...
}
На практике, на регистровые переменные наложены ограничения, отражающие возможности базовых оборудования. Только несколько переменных с каждой функции могут быть сохраненными в регистрах и только определенные типы разрешаются. Избыток регистровых объявлений не является вредоносным, тем не менее, поскольку слово register игнорировано для лишних или запрещенных объявлений. Адрес регистровой переменной невозможно получить (получение адресов переменных рассмотрен в Главе 5), независимо от того, переменную действительно размещен в регистре или нет. Ограничение количества и типов регистровых переменных отличается на разных машинах.
Структура блоков
C не является блок-структурированной языке вроде Pascal или других языков, поскольку функцию нельзя обозначить внутри другой функции. Кроме того, переменные могут быть описаны в блок-структурированном виде внутри функции. Объявление переменных (включая инициализацию) может следовать за левой фигурной скобкой, которая начинает любое составное утверждение, а не только начинает функцию. Переменные, объявленные таким образом, скрыто от одинаково названных переменных во внешних блоках и остаются существовать в соответствующую правой фигурной скобки. Например, в
if (n> 0) {
int i; / * объявления новой i * /
for (i = 0; i
}
зоны действия переменной i является "истинное" ответвление if; не будет иметь никакого отношения ни к одному i вне этого блока. Автоматическую переменную, объявленную и инициированную в блоке, инициировано каждый раз при вхождении в этот блок.
Автоматические переменные, включая формальные параметры функций, также скрытые от внешних переменных и функций с тем же названием. Имея объявления
int x;
int y;
f (double x)
{
double y;
}
переменная x, как параметр функции f типа double, нет ничего общего с внешней x типа int. То же касается переменной y. Но, в общем, лучше избегать названий переменных, которые совпадают с названиями из других зон действия, слишком велика вероятность путаницы и ошибок.
Инициализация
О инициализации мимоходом уже упоминалось много раз, но дважды периферийно, как часть другой темы. Этот раздел подводит итог правил, после того как мы оговорили различные типы хранения. В случае отсутствия явной инициализации, внешние и статические переменные будет гарантировано придано значение ноль. Автоматические и регистровые переменные будут иметь неопределенное начальное значение.
Скалярные переменные могут быть инициированными при их определении путем придачи к их названию знака равенства и выражения:
int x = 1;
char squota ='/ь';
long day = 1000L * 60L * 60L * 24L; / * миллисекунд в день * /
Для внешних и статических переменных, инициализатор должен быть устойчивым выражением; инициализация бидбуваеться один раз, концептуально тому как программа начинает выполняться. Для автоматических и регистровых переменных, инициализатор не обязательно должен быть константой: это может быть любое выражение, включая попередьно-определении значения, даже вызовы функций. Так, например, инициализацию в программе бинарного поиска может быть написано как
int binsearch (int x, int v [], int n)
{
int low = 0;
int high = n - 1;
int mid;
...
}
вместо
int low, high, mid;
low = 0;
high = n - 1;
Эффективно, инициализация автоматических знинних, это просто сокращение для выражений присвоении значения. Какой форме предоставлять преимущество, это в значительной степени является вопросом змаку. Мы вообще использовали явные присвоения, поскольку инициализатор в объявлениях труднее увидеть и находятся дальше от места использования.
Массив может быть инициировано через придание до объявления списка инициализаторов, включенных в фигурные скобки и разделенных запятыми. Как пример - инициализация массива days с количеством дней каждого месяца:
int days [] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
Когда размер массива не указан, компилятор вычислит его, перечислив инициализатор, 12 в этом случае.
Если предоставлено меньше инициализаторов, чем указанный размер массива, остальным будет присвоено значение ноль в случае внешних, статических и автоматических переменных. Ошибка возникнет в случае лишних инициализаторов. Не существует способа указать повторные инициализатор, равно как элемент посередине массива, без указания предыдущих значений также.
Символьные массивы - это специальный случай инициализаторов: можно использовать цепочку вместо нотации из фигурных скобок и запятых:
char pattern [] = "ould";
что является сокращением для более длинного, но равнозначного
char pattern [] = {'o', 'u', 'l', 'd', '\ 0'};
В обоих случаях, массив будет состоять из пяти элементов: четыре буквы и конечный '\ 0'.
Рекурсия
Функции в C могут использоваться рекурсивно, то есть, функция может вызвать сама себя непосредственно или опосередково. Рассмотрим вывод числа как символьный цепочку. Как мы отмечали ранее, цифры генерируются в неправильной последовательности: младшие числа доступны раньше старшие числа, хотя их нужно выводить наоборот.
Существует два способа решения этой проблемы. Одним будет сохранение цифр в массив при их генерации, затем напечатать их в обратной последовательности, как мы это осуществили с itoa в Разделе 3.6. Альтернативой является рекурсивное решение, в котором printd сначала вызывает себя чтобы справиться с начальными цифрами, после чего выводит конечные цифры. Имейте в виду, что эта версия может потерпеть неудачу на крупнейшем отрицательном числе.
# Include
/ * Printd: вывести n как десятичное * /
void printd (int n)
{
if (n <0) {
putchar ('-');
n =-n;
}
if (n / 10)
printd (n / 10);
putchar (n% 10 + '0 ');
}
При вызове функции самого себя рекурсивно, каждое обращение получает свежий набор всех автоматических переменных, независимых от попередьного набора. Таким образом, в printd (123), первая printd получает аргумент n = 123. Она передает 12 вторых printd, которая в свою очередь передает 1 третьем. printd третьего уровня выводит 1 после чего возвращается к printd второго уровня, которая выводит 2, затем возвращается к первому уровню. Он выводит 3 и завершается.
Другой хороший пример рекурсии, это quicksort, алгоритм сортировки, разработанный CAR Hoare в 1962 году. Имея массив, выбирается один элемент и остальные розбивается на две группы - те, которые меньше выбранного элемента и те, которые больше или равны ему. То же самое затем применена рекурсивно к этим двум группам. Когда подгруппа осталась с мешне чем двумя элементами, она не требует никакого сортировки; это останавливает рекурсию.
Наша версия quicksort не является самой быстрой из возможных, но это одна из самых простых. Мы используем средний элемент каждой части массива для дальнейшего разделения.
/ * Qsort: сортирует v [left] ... v [right] в возрастающей последовательности * /
void qsort (int v [], int left, int right)
{
int i, last;
void swap (int v [], int i, int j);
if (left> = right) / * ничего не делать, если массив * /
return; / * содержит менее двух элементы * /
swap (v, left, (left + right) / 2); / * переместить элемент разделения * /
last = left; / * к v [0] * /
for (i = left + 1; i <= right; i + +) / * поделить * /
if (v [i]
swap (v, left, last) / * восстановить элемент разделения * /
qsort (v, left, last-1);
qsort (v, last +1, right);
}
Мы переместили операцию перестановка в отдельную функцию swap, поскольку она происходит три раза в qsort.
/ * Swap: поменять местами v [i] и v [j] * /
void swap (int v [], int i, int j)
{
int temp;
temp = v [i];
v [i] = v [j];
v [j] = temp;
}
Стандартная библиотека включает собственную версию qsort, способную сортировать объекты любого типа. Рекурсия может занимать место, так где-то нужно удерживать стек значений для обработки. Также она не является быстрой. Но рекурсивный код компактнее и часто намного проще в написании и понимании, чем не-рекурсивный эквивалент. Рекурсия особенно удобна для рекурсивно-определенных данных, например древовидных структур.
Продолжение статьи: ч.1 Продолжение статьи: ч.2 Продолжение статьи: ч.3 Продолжение статьи: ч.4 Продолжение статьи: ч.5 Продолжение статьи: ч.6 Продолжение статьи: ч.7 Продолжение статьи: ч.8 Продолжение статьи: ч.9 Продолжение статьи: ч.10 Продолжение статьи: ч.11 Продолжение статьи: ч.12 Продолжение статьи: ч.13 Продолжение статьи: ч.14 Продолжение статьи: ч.15 Продолжение статьи: ч.16 Продолжение статьи: ч.17 Продолжение статьи: ч.18
Другие материалы по теме:
- Символьные массивы- Язык программирования c в игровой индустрии: движки и производительность
- Функции и структура программ
- Типы, операторы и выражения
- Внешние переменные и область действия
