Переключатель
Указатель - это
переменная, значением которой является адресс
другой переменной. Так как указатель может
ссылаться на переменные разных типов, с
указателем в языке Си связывается тип того
объекта, на который он ссылается. Для описания
указателей используется операция косвенной
адресации *. Например, указатель целого
типа uk описывается так : int *uk. Унарная
операция &, примененная к некоторой
переменной, показывает, что нам нужен адресс этой
переменной, а не ее текущее значение. Если
переменная uk объявлена как указатель,
то оператор присваивания uk=&x означает:
"взять адресс переменной x и присвоить его
значение переменной-указателю uk".
Унарная операция *. примененная
к указателю, обеспечивает доступ к содержимому
ячейки памяти, на которую ссылается указатель.
Например, *uk можно описать словами как
"то, что содержится по адресу, на который
указывает uk". Указатели могут
использоваться в выражениях. Если. например,
переменная uk указывает на целое x,
то *uk может во всех случаях
использоваться вместо x; так, *uk+1 увеличивает
x на единицу, а *uk=0 равносильно x=0.
Два оператора присваивания uk=&x;y=*uk; выполняет
то же самое, что и один оператор y=x. Польза
от применения указателей в таких ситуациях,
мягко выражаясь, невелика.
Наиболее полно их преимущества
при обработке массивов и, в частности, символьных
строк. Указатели и массивы тесно связаны друг с
другом. Прежде чем рассмотреть эту связь
подробно, заметим, что если uk - некоторый
указатель, то uk++ увеличивает его
значение и он теперь указывает на следющий,
соседний адресуемый объект. Значение uk используется
в выражении, а затем увеличивается. Аналогично
определяются операции uk--, ++uk, --uk. В
общем случае указатель uk можно
скаладывать с целым числом i. Оператор uk+=i
передвигает ссылку на i элементов
относительно текущего значения. Эти конструкции
подчиняются правилам адресной арифметики.
А теперь вернемся к массивам. Пусть
имеется описание int a[5]; Оно определяет
массив размером 5 элементов, т.е. пять
последовательно расположенных ячеек памяти a[0],
a[1], a[2], a[3], a[4]. Адресс i-го элемента
массива равен сумме адреса начального елемента
массива и смещения этого элемента на i единиц
от начала массива. Это достигается
индексированием: a[i]-i -й элемент массива.
Но доступ к любому элементу массива может быть
выполнен и с помощью указателей, причем, более
эффективно. Если uk -указатель на целое,
описанный как int *uk, то uk после
выполнения операции uk=&a[0] содержит
адресс a[0], а uk+i указывает на
i -й элемент массива. Таким образом, uk+i
является адрессом a[i], а *(uk=1)
- содержимым i- го элемента(операции
* и & более приоритетны, чем
арифметические операции). Так как имя массива в
программе отождествляется с адресом его первого
элемента, то выражение uk=&a[0] эквивалентно
такому: uk=a. Поэтому значение a[i] можно
записать как *(a+i). Применив к этим двум
элементам операцию взятия адреса, получим, что &a[i]
и a+i идеитичны.
Раньше, в
связи с использованием функции scanf, мы
говорили, что применение указателей в качестве
аргументов функции дает способ обхода защиты
значений вызывающей функции от действий
вызванной функции. На примере 5.4 приведен текст
программы с функцией obmen(x,y), которая
меняет местами значения двух целых величин. Так
как x,y - адреса переменных a и
b, то *x и *y обеспечивают
косвенный доступ значениям a и b. К
сказанному добавим, что использование
указателей позволяет нам обходить ограничения
языка Си, согласно которым функциям может
возращать только одно значение.
Если в качестве функции
передается имя массива, то оно фактически
определяет адрес начала массива, т.е. является
указателем. В качестве илюстрации напишем
очередную версию функции length, вычисляющей
длину строки. Если имя массива передается
функции, то в самой функции можно исходить из
того, что она работает с массивом, а можно
исходить из того, что она работает с указателем.
Пример 5.4
/*обмен a и b */
obmen(int *x,int *y)
{
int t;
t=*x;
*x=*y;
*y=t;
}
#include <stdio.h>
main()
{
int a,b;
a=3;b=7;
obmen(a,b);
printf("a=%d b=%d",a,b);
}
|
В определении функции
формальные параметры char s[] и char *s совершенно
идеитичны. Операция s++ (пример 5.5)
увеличение на единицу текущее значение
указателя, первоначально указывающее на первый
символ строки, а операция *s!='\0' сравнивает
очередной символс признаком конца строки.
Пример 5.5
/*длина строки*/
length(s)
char *s;
{
int i;
for(i=0; *s!='\0';s++)
i+++;
return(i);
} |
Кроме ранее расмотренных
операций адресной арифметики, к указателям можно
применить операции сравнения == и !=. Даже
операции отношения Б <,>= и т.п. работают
правильно, если указатели ссылаются на элементы
одного и того же массива. В последнем случае
возможно даже вычитание ссылок: если u и s
ссылаются на элементы одного массива, то u-s
есть число элементов между u и s.
Используем этот факт для составления еще
одной версии функции length (пример 5.6).
Cначала u указывает на первый
символ строки (char *u =s). Затем в
цикле по очереди проверяется каждый символ, пока
в конце концов не будет обнаружен "\0". Разность
u-s дает как раз длину строки.
Пример 5.6
/*длина строки*/
length(s)
char *s;
{
char *u=s;
while(*u!='\0')
u++;
return(u-s);
} |
Для илюстрации основных
аспектов применения указателей в СИ рассмотрим
функцию копирования строки s1 в строку s2.
Сначала приведем версию, основанную на работе с
массивами(пример 5.7). Для сравнения рядом
помещена версия с использованием
указателей(пример 5.8).
Пример 5.7
/*копия строки*/
copy(s1,s2)
char s1[],s2[];
{
int i=0;
while((s2[i]=s1[i])!='\0')
i++;
} |
Пример 5.8
/*копия строки*/
copy(s1,s2)
char *s1,*s2;
{
while((*s2=*s1)!='\0')
{
s2++;s1++;
}
} |
Здесь операция
копирования помещена непосредственно в условие,
определяющее момент цикла: while((*s2=*s1)!='\0'). Продвижение
вдоль массивов вплоть до тех пор, пока не
встретится "\0", обеспечивают
операторы s2++ и s1++. Их, однако,
тоже можно поместить в проверку (пример 5.9).
Пример 5.9
/*копия строки*/
copy(s1,s2)
char *s1,*s2;
{
while((*s2++=*s1++)!='\0');
} |
Еще раз напомним, что унарные
операции типа * и ++ выполняются
справа налево. Значение *s++ cесть символ,
на который указывает s до его
увеличения. Так как значение "\0" есть нуль, а
цикл while проверет, не нуль ли выражение в
скобках, то это позволяет опустить явное
сравнение с нулем(пример 6.0) . Так постепенно
функция копирования становится все более
компактной и ... все менее понятной. Но в
системном программировании предпостение чаще
отдают именно компактным и, следовательно, более
эффективным по быстродействиб программам.
Пример 6.0
/*копия строки*/
copy(s1,s2)
char *s1,*s2;
{
while(*s2++=*s1++);
} |
В языке Си, что некоторая
литерная строка, выраженная как "строка" ,
фактически рассматривается как указатель на
нулевой элемент массива " строка".
Допускается, например, такая интересная запись:
char *uk; uk="ИНФОРМАТИКА";
Последний оператор присваивает
указателю адрес нулевого элемента строки, т.е.
символа "И". Возникает вопрос, где
находится массив, содержащий символы "ИНФОРМАТИКА"?
Мы его нигде не описывали. Ответ такой: эта строка
- константа; она является частью функции, в
которой встречается, точно также, как целая
константа 4 или символьная константа "А"
в операторах i=4; c="A";. Более
детально пояснит сказанное программа на пример
6.1, которая печатает строку символов в обратном
порядке.
Пример 6.1
#include <stdio.h>
main()
{
char *uk1,*uk2;
uk1=uk2="ИНФОРМАТИКА";
while(*uk2!='\0')
putchar(*uk2++);
putchar('\n');
while(--uk2 >= uk1)
putchar(*uk2);
putchar('\n');
} |
В самом начале указателям uk1
и uk2 присваивается начальный
адресс строки "ИНФОРМАТИКА". Затем
строка посимвольно печатается и одновременно
указатель uk2 смещается вдоль строки. В
конце вывода uk2 указывает на последний
символ исходной строки. Во втором цикле while все
тот же указатель uk2 начинает изменяться
в обратном направлении, уменьнаясь до тех пор,
пока он не будет указывать на нулевой элемент
массива, обеспечивая выдачу строки в обратном
порядке.
< Дальше >
|