Язык Си - руководство для начинающих
Шрифт:
int *u, *v; /* u и v являются указателями */
{
int temp;
temp = *u; /* temp присваивается значение, на которое указывает u */
*u = *v;
*v = temp;
}
После всех встретившихся трудностей, проверим, работает ли этот вариант 1
Вначале x = 5 и y = 10.
Теперь x = 10 и y = 5.
Да программа работает. Посмотрим, как она работает.
interchange(&x, &y);
Вместо передачи значений х и у мы передаем их адреса. Это означает, что формальные аргументы u и v, имеющиеся в спецификации:
interchange(u,v)
при обращении будут заменены адресами и, следовательно, они должны быть описаны как указатели. Поскольку х и у– целого типа, u и v являются указателями на переменные целого типа, и мы вводим следующее описание:
int *u, *v;
Далее в теле функции оператор описания:
int temp;
используется с целью резервирования памяти. Мы хотим поместить значение переменной х в переменную temp, поэтому пишем:
temp = *u;
Вспомните, что значение переменной u– это &х, поэтому переменная u ссылается на х. Это означает, что операция *u дает значение x, которое как раз нам и требуется. Мы не должны писать, например, так:
temp = u; /* неправильно */
поскольку при этом происходит запоминание адреса переменной х, а не ее значения; мы же пытаемся осуществить обмен значениями, а не адресами.
Точно так же, желая присвоить переменной у значение переменной х, мы пользуемся оператором:
*u = *v;
который соответствует оператору
x = y;
Подведем итоги. Нам требовалась функция, которая могла бы изменять значения переменных х и у. Путем передачи функции адресов переменных х и у мы предоставили ей возможность доступа к ним. Используя указатели и операцию *, функция смогла извлечь величины, помещенные в соответствующие ячейки памяти, и поменять их местами.
Вообще говоря, при вызове функции информация о переменной может передаваться функции в двух видах. Если мы используем форму обращения:
function1(х);
происходит передача значения переменной х. Если же мы используем форму обращения:
function2(&x);
происходит передача адреса переменной х. Первая форма обращения требует, чтобы определение функции включало в себя формальный аргумент того же типа, что и х:
functionl(num)
int num;
Вторая форма обращения требует, чтобы определение функции включало в себя формальный аргумент, являющийся указателем на объект соответствующего типа:
function2(ptr)
int *ptr;
Пользуйтесь первой формой, если входное значение необходимо функции для некоторых
вычислений или действий, и второй формой, если функция должна будет изменять значения переменных в вызывающей программе. Вторая форма вызова уже применялась при обращении к функции scanf. Когда мы хотим ввести некоторое значение в переменную num, мы пишем scanf("%d, &num). Данная функция читает величину, затем, используя адрес, который ей дается, помещает эту величину в память.Указатели позволяют обойти тот факт, что переменные функции interchange являются локальными. Они дают возможность нашей функции "добраться" до функции main и изменить величины описанных в ней объектов.
Программисты, работающие на языке Паскаль, могут заметить, что первая форма вызова аналогична обращению с параметром-значением, а вторая - с параметром-переменной. У программистов, пишущих на языке Бейсик, понимание всей этой методики может вызвать некоторые затруднения. В этом случае если материал данного раздела покажется вам поначалу весьма не обычным, не сомневайтесь, что благодаря некоторой практике, все обсуждаемые средства станут простыми, естественными и удобными.
Переменные: имена, адреса и значения
Наше обсуждение указателей строится на рассмотрении связей между именами, aдреcaми и значениями переменных; дальше мы продолжим обсуждение этих вопросов.
При написании программы мы представляем себе переменную как объект, имеющий два атрибута: имя и значение. (Кроме указанных, существуют еще и другие атрибуты, например тип, но это уже другой вопрос). После компиляции программы и загрузки в память "с точки зрения машины" данная переменная имеет тоже два атрибута: адрес и значение. Адрес - это машинный вариант имени.
Во многих языках программирования адрес объекта скрыт от программиста и считается относящимся к уровню машины. В языке Си благодаря операции & мы имеем возможность узнать и использовать адрес переменной:
&bаrn– это адрес переменной bаrn.
Мы можем получить значение переменной, соответствующее данному имени, используя только само имя:
printf(" %d\n", barn) печатает значение переменной barn
Мы можем также получить значение переменной, исходя из ее адреса, при помощи операции *:
РИС. 9.6. Имена, адреса и величнны в системе с "байтовой адресацией" тина IBM PC.
Дано pbarn = &bаrn; тогда *pbarn– это величина, помещенная по адресу &bаrn. Хотя мы и можем напечатать адрес переменной для удовлетворения своего любопытства, это не основное применение операции &. Более важным является то, что наличие операций &, * и указателей позволяет обрабатывать адреса и их содержимое в символическом виде, чем мы и занимались в программе обмен3.
ИСПОЛЬЗОВАНИЕ НАШИХ ЗНАНИЙ О ФУНКЦИЯХ
Теперь, когда мы знаем о функциях немного больше, соберем вместе несколько поучительных примеров, но сначала решим, чем мы будет заниматься.
Что вы скажете насчет функции возведения в степень, которая дает возможность возводить 2 в 5-ю степень или 3 в 3-ю и т. д.? Во-первых, необходимо решить, что будет служить входом программы. Это понятно: Cи требуется знать число, возводимое в степень, и показатель степени. Достичь этого можно путем введения двух аргументов: