Керниган и Ритчи - Язык программирования Си (793773), страница 25
Текст из файла (страница 25)
Кроме того, взамен char * в качестве типа обобщенногоуказателя предлагается тип void * (указатель на void).5.1. Указатели и адресаНачнем с того, что рассмотрим упрощенную схему организации памяти. Память типичной машиныпредставляет собой массив последовательно пронумерованных или проадресованных ячеек, с которымиможно работать по отдельности или связными кусками. Применительно к любой машине верны следующиеутверждения: один байт может хранить значение типа char, двухбайтовые ячейки могут рассматриваться какцелое типа short, а четырехбайтовые — как целые типа long.
Указатель — это группа ячеек (как правило,две или четыре), в которых может храниться адрес. Так, если с имеет тип char, а р — указатель на с, тоситуация выглядит следующим образом:Унарный оператор & выдает адрес объекта, так что инструкцияр = &с;присваивает переменной p адрес ячейки c (говорят, что р указывает на с). Оператор & применяется только кобъектам, расположенным в памяти: к переменным и элементам массивов. Его операндом не может быть нивыражение, ни константа, ни регистровая переменная.Унарный оператор * есть оператор косвенного доступа.
Примененный к указателю, он выдает объект, накоторый данный указатель указывает. Предположим, что x и y имеют тип int, a ip — указатель на int.Следующие несколько строк придуманы специально для того, чтобы показать, каким образом объявляютсяуказатели и как используются операторы & и *.int х = 1, у = 2, z[10];int *ip; /* ip - указатель на int */ip = &x; /* теперь ip указывает на х */y = *ip; /* у теперь равен 1 */*ip =0; /* х теперь равен 0 */ip = &z[0]; /* ip теперь указывает на z[0] */Объявления х, y и z нам уже знакомы. Объявление указателя ipint *ip;мы стремились сделать мнемоничным — оно гласит: "выражение *ip имеет тип int".
Синтаксис объявленияпеременной "подстраивается" под синтаксис выражений, в которых эта переменная может встретиться.Указанный принцип применим и в объявлениях функций. Например, записьdouble *dp, atof (char *);означает, что выражения *dp и atof(s) имеют тип double, а аргумент функции atof есть указатель наchar.Вы, наверное, заметили, что указателю разрешено указывать только на объекты определенного типа.(Существует одно исключение: "указатель на void" может указывать на объекты любого типа, но к такомууказателю нельзя применять оператор косвенного доступа. Мы вернемся к этому в параграфе 5.
11.)Если ip указывает на x целочисленного типа, то *ip можно использовать в любом месте, где допустимоприменение х; например,*ip = *ip+ 10;увеличивает *ip на 10.Унарные операторы * и & имеют более высокий приоритет, чем арифметические операторы, так чтоприсваиваниеу = *ip + 1берет то, на что указывает ip, и добавляет к нему 1, а результат присваивает переменной y. Аналогично*ip += 1увеличивает на единицу то, на что указывает ip; те же действия выполняют++*ipи(*ip)++В последней записи скобки необходимы, поскольку если их не будет, увеличится значение самого указателя,а не то, на что он указывает. Это обусловлено тем, что унарные операторы * и ++ имеют одинаковыйприоритет и порядок выполнения — справа налево.И наконец, так как указатели сами являются переменными, в тексте они могут встречаться и без операторакосвенного доступа.
Например, если iq есть другой указатель на int, тоiq = ipкопирует содержимое ip в iq, чтобы ip и iq указывали на один и тот же объект.5.2. Указатели и аргументы функцийПоскольку в Си функции в качестве своих аргументов получают значения параметров, нет прямойвозможности, находясь в вызванной функции, изменить переменную вызывающей функции. В программесортировки нам понадобилась функция swap, меняющая местами два неупорядоченных элемента. Однаконедостаточно написатьswap(a, b);где функция swap определена следующим образом:void swap(int x, int y) /* НЕВЕРНО */{int temp;temp = x;x = y;y = temp;}Поскольку swap получает лишь копии переменных a и b, она не может повлиять на переменные а и b тойпрограммы, которая к ней обратилась.Чтобы получить желаемый эффект, вызывающей программе надо передать указатели на те значения, которыедолжны быть изменены:swap(&a, &b);Так как оператор & получает адрес переменной, &a есть указатель на a. В самой же функции swap параметрыдолжны быть объявлены как указатели, при этом доступ к значениям параметров будет осуществлятьсякосвенно.void swap(int *px, int *py) /* перестановка *рх и *рy */{int temp;temp = *рх;*рх = *py;*рy = temp;}Графически это выглядит следующим образом:в вызывающей программе:Аргументы-указатели позволяют функции осуществлять доступ к объектам вызвавшей ее программы и даютвозможность изменить эти объекты.
Рассмотрим, например, функцию getint, которая осуществляет ввод всвободном формате одного целого числа и его перевод из текстового представления в значение типа int.Функция getint должна возвращать значение полученного числа или сигнализировать значением EOF оконце файла, если входной поток исчерпан. Эти значения должны возвращаться по разным каналам, так какнельзя рассчитывать на то, что полученное в результате перевода число никогда не совпадет с EOF.Одно из решений состоит в том, чтобы getint выдавала характеристику состояния файла (исчерпан или неисчерпан) в качестве результата, а значение самого числа помещала согласно указателю, переданному ей ввиде аргумента. Похожая схема действует и в программе scanf, которую мы рассмотрим в параграфе 7.4.Показанный ниже цикл заполняет некоторый массив целыми числами, полученными с помощью getint.int n, array[SIZE], getint (int *);for (n = 0; n < SIZE && getint (&array[n]) != EOF; n++);Результат каждого очередного обращения к getint посылается в array[n], и n увеличивается на единицу.Заметим, и это существенно, что функции getint передается адрес элемента array[n] .
Если этого несделать, у getint не будет способа вернуть в вызывающую программу переведенное целое число.В предлагаемом нами варианте функция getint возвращает EOF по концу файла; нуль, если следующиевводимые символы не представляют собою числа; и положительное значение, если введенные символыпредставляют собой число.#include <ctype.h>int getch (void);void ungetch (int);/* getint: читает следующее целое из ввода в *pn */int getint(int *pn){int c, sign;while (isspace(c = getch())); /* пропуск символов-разделителей */if (!isdigit(c) && с != EOF && c ! = '+' && с != '-' ) {ungetch (с); /* не число */return 0;}sign = (с = = ' - ' ) ? -1 : 1;if (с == '+' || с == '-')с = getch();for (*pn = 0; isdigit(c); c = getch())*pn = 10 * *pn + (c - '0' ) ;*pn *= sign;if (c != EOF)ungetch(c);return c;}Везде в getint под *pn подразумевается обычная переменная типа int.
Функция ungetch вместе сgetch (параграф 4.3) включена в программу, чтобы обеспечить возможность отослать назад лишнийпрочитанный символ.Упражнение 5.1. Функция getint написана так, что знаки - или +, за которыми не следует цифра, онапонимает как "правильное" представление нуля. Скорректируйте программу таким образом, чтобы вподобных случаях она возвращала прочитанный знак назад во ввод.Упражнение 5.2. Напишите функцию getfloat — аналог getint для чисел с плавающей точкой.
Какой типбудет иметь результирующее значение, выдаваемое функцией getfloat?5.3. Указатели и массивыВ Си существует связь между указателями и массивами, и связь эта настолько тесная, что эти средства лучшерассматривать вместе. Любой доступ к элементу массива, осуществляемый операцией индексирования,может быть выполнен с помощью указателя. Вариант с указателями в общем случае работает быстрее, норазобраться в нем, особенно непосвященному, довольно трудно.Объявлениеint a[10];определяет массив а размера 10, т. е.
блок из 10 последовательных объектов с именами а[0], а[1],...,а[9].Запись а[i] отсылает нас к i-му элементу массива. Если pa есть указатель на int, т. е. объявлен какint *pa;то в результате присваиванияра = &а[0];pa будет указывать на нулевой элемент а, иначе говоря, pa будет содержать адрес элемента а[0].Теперь присваиваниех = *ра;будет копировать содержимое а[0] в х.Если ра указывает на некоторый элемент массива, то ра+1 по определению указывает на следующийэлемент, pa+i — на i-й элемент после ра, a pa-i — на i-й элемент перед ра. Таким образом, если рауказывает на а[0], то*(pa+1)есть содержимое а[1], а+i -адрес a[i], а *(pa+i) — содержимое a[i].Сделанные замечания верны безотносительно к типу и размеру элементов массива а.
Смысл слов "добавить 1к указателю", как и смысл любой арифметики с указателями, состоит в том, чтобы ра+1 указывал наследующий объект, а ра+1 — на 1-й после ра.Между индексированием и арифметикой с указателями существует очень тесная связь. По определениюзначение переменной или выражения типа массив есть адрес нулевого элемента массива. Послеприсваиванияра = &а[0];ра и а имеют одно и то же значение.















