p =&c;
присваивает переменной p адрес ячейки c (говорят, что p указывает на c ). Оператор &применяется только к объектам, расположенным в памяти: к переменным и элементам массивов. Его операндом не может быть ни выражение, ни константа, ни регистровая переменная.
Унарный оператор *есть оператор косвенного доступа . Примененный к указателю он выдает объект, на который данный указатель указывает. Предположим, что x и y имеют тип int , а ip - укаэатель на int . Следующие несколько строк придуманы специально для того, чтобы показать, каким образом объявляются указатели и как используются операторы &и *.
int х = 1, у = 2, z[10];
int *ip; /* ip - указатель на int */
ip = &x; /* теперь ip указывает на x */
y = *ip; /* y теперь равен 1 */
*ip = 0; /* x теперь равен 0 */
ip = &z[0]; /* ip теперь указывает на z[0] */
Объявления x , y и z нам уже знакомы. Объявление указателя ip
int *ip;
мы стремились сделать мнемоничным - оно гласит: "выражение *ip имеет тип int ". Синтаксис объявления переменной "подстраивается" под синтаксис выражений, в которых эта переменная может встретиться. Указанный принцип применим и в объявлениях функций. Например, запись
double *dp, atof (char *);
означает, что выражения *dp и atof(s) имеют тип double , а аргумент функции atof есть указатель на char .
Вы, наверное, заметили, что указателю разрешено указывать только на объекты определенного типа. (Существует одно исключение: "указатель на void " может указывать на объекты любого типа, но к такому указателю нельзя применять оператор косвенного доступа. Мы вернемся к этому в параграфе 5.11.)
Если ip указывает на x целочисленного типа, то *ip можно использовать в любом месте, где допустимо применение x ; например,
*ip = *ip + 10;
увеличивает *ip на 10.
Унарные операторы *и &имеют более высокий приоритет, чем арифметические операторы, так что присваивание
y = *ip + 1;
берет то, на что указывает ip , и добавляет к нему 1, а результат присваивает переменной y . Аналогично
*ip += 1;
увеличивает на единицу то, на что указывает ip ; те же действия выполняют
++*ip;
и
(*iр)++;
В последней записи скобки необходимы, поскольку если их не будет, увеличится значение самого указателя, а не то, на что он указывает. Это обусловлено тем, что унарные операторы *и ++имеют одинаковый приоритет и порядок выполнения - справа налево.
И наконец, так как указатели сами являются переменными, в тексте они могут встречаться и без оператора косвенного доступа. Например, если iq есть другой указатель на int , то
iq = ip;
копирует содержимое ip в iq , чтобы ip и iq указывали на один и тот же объект.
5.2 Указатели и аргументы функций
Поскольку в Си функции в качестве своих аргументов получают значения параметров, нет прямой возможности, находясь в вызванной функции, изменить переменную вызывающей функции. В программе сортировки нам понадобилась функция swap , меняющая местами два неупорядоченных элемента. Однако недостаточно написать
swap(a, b);
где функция swap определена следующим образом:
void swap(int х, int у) /* НЕВЕРНО */
{
int temp;
temp = х;
x = y;
у = temp;
}
Поскольку swap получает лишь копии переменных a и b , она не может повлиять на переменные a и b той программы, которая к ней обратилась. Чтобы получить желаемый эффект, вызывающей программе надо передать указатели на те значения, которые должны быть изменены:
swap(&a, &b);
Так как оператор &получает адрес переменной, &a есть указатель на a . В самой же функции swap параметры должны быть объявлены как указатели, при этом доступ к значениям параметров будет осуществляться косвенно.
void swap(int *px, int *py) /* перестановка *px и *py */
{
int temp;
temp = *рх;
*рх = *py;
*ру = temp;
}
Графически это выглядит следующим образом: в вызывающей программе:
Аргументы-указатели позволяют функции осуществлять доступ к объектам вызвавшей ее программы и дают возможность изменить эти объекты. Рассмотрим, например, функцию getint , которая осуществляет ввод в свободном формате одного целого числа и его перевод из текстового представления в значение типа int . Функция getint должна возвращать значение полученного числа или сигнализировать значением EOF о конце файла, если входной поток исчерпан. Эти значения должны возвращаться по разным каналам, так как нельзя рассчитывать на то, что полученное в результате перевода число никогда не совпадет с EOF.
Читать дальше