Существуют важные различия между массивами и указателями.
|
|
|
|
· Указатель занимает одну ячейку памяти, предназначенную для хранения машинного адреса (в частности, адреса нулевого элемента массива). Массив занимает столько ячеек памяти, сколько элементов определено в нем при его объявлении. Только в выражении массив представляется своим адресом, который эквивалентен указателю.
· Адрес массива является постоянной величиной, поэтому, в отличие от идентификатора указателя, идентификатор массива не может составлять левую часть операции присваивания.
Для одномерного массива следующие 2 выражения эквивалентны, если а — массив или указатель, а b — целое:
а[b] *(а + b)
Аналогично, для матрицы a с целочисленными индексами i и j эквивалентны выраженияa[i][j] *(*(a+i)+j)Так, следующий оператор выводит элемент матрицы a[1][2]:int i=1,j=2;printf ("%d",*(*(a+i)+j));Специальное применениеимеют указатели на тип void. Указатель на void может указывать на значения любого типа. Однако для выполнения операций над указателем на void либо над указуемым объектом необходимо явно привести тип указателя к типу, отличному от void. Например, если объявлена переменная i типа int и указатель р на тип void
|
|
int i; void *p;
то можно присвоить указателю р адрес переменной i:
p = &i;
но изменить значение указателя без приведения типа нельзя:
р++; /* недопустимо */
(int *)р++; /* допустимо */
В стандартном включаемом файле stdio.h определена константа с именем NULL. Она предназначена специально для инициализации указателей. Гарантируется, что никакой программный объект никогда не будет иметь адрес NULL.
7.3. Указатели и символьные данные. Если описать указатель message в видеchar *message;то в результате оператораmessage = "Any string of text";message будет указывать на фактический массив символов. Это не копирование строки, так как в операции участвует только указатель. Также важно то, что в Си не предусмотрены какие-либо операции для обработки всей строки символов как целого. Как и в других контекстах, присваивание значения переменной можно объединить с ее определением:char *message = "Any string of text";Указатели в сочетании с операцией инкремента естественным образом используются для сканирования строк. В таком контексте динамически меняющийся указатель на строку часто называют просто строкой.Следующий пример реализует на Си функцию копирования строки с именем strcpy.#include <stdio.h>char *strcpy (char *s, char *t) { char *n=s; //запомнили, куда показывал s while (*t!='\0') { *s++=*t++; } return n; //s сдвинулся, вернули //его начальное значение}void main () { char *s1="none",*s2="test"; printf ("\n%s",strcpy(s1,s2));}Функция получает 2 указателя на строки s (строка назначения) и t (строка‑источник). Значением *t++ является символ, на который указывал t до увеличения; постфиксная операция ++ не изменяет t, пока этот символ не будет извлечен. Точно так же этот символ помещается в старую позицию s, до того как s будет увеличено. Конечный результат заключается в том, что все символы копируются из t в s, исключая завершающий символ нуля.Более кратко процесс копирования можно было бы описать в видеwhile ((*s++ = *t++)!= '\0');Здесь увеличение s и t вынесено в проверочную часть цикла. Также за счет того, что сначала выполняется присваивание, а затем сравнение с нулевым байтом, код копирует и завершающий символ '\0'. Наконец, сравнение с нулем также можно было опустить:while (*s++ = *t++);Напишем функцию сравнения строк с использованием указателей. Она вернет число меньше 0, если строка s лексикографически (по кодам символов) предшествует t, вернет 0, если строки одинаковы и положительное значение, если s "больше" t по кодам символов.int strcmp(char *s, char *t) { for (; *s == *t; s++, t++) if (*s == '\0') return(0); return(*s-*t);}Запись этой функции также можно сократить. 7.4. Указатели и динамическая память. Подробнее тема использования динамической памяти рассматривается в п. 8.5. Здесь мы ограничимся двумя стандартными функциями, имеющимися в библиотеке stdlib.h:· функция void *malloc(n) с целочисленным беззнаковым аргументом n возвращает в качестве своего значения нетипизированный указатель p, который указывает на первый из n выделенных байт памяти. Эта память может быть использована программой для хранения данных; перед использованием указатель должен быть типизирован операцией приведения типа. Если выделить память не удалось, функция возвращает NULL. Операция приведения типа имеет вид sizeof(тип) и позволяет узнать размер переменной этого типа в байтах.· функция void free(p) освобождает приобретенную таким образом память, так что ее в дальнейшем можно снова использовать. Обращения к free должны производиться в порядке, обратном тому, в котором производились обращения к malloc.В приведенном далее примере указателю p сопоставляется динамическая память под строку из n символов, значение n вводится пользователем.#include <stdlib.h>#include <stdio.h>//...unsigned char *p;unsigned n;printf ("\nN="); fflush (stdin); scanf ("%u",&n);p=(unsigned char *) malloc(n*sizeof(unsigned char));if (p==NULL) { //Здесь производится диагностика ошибки}В следующем примере функция strsave копирует свою строку‑аргумент в динамически выделенную область памяти.#include <stdlib.h>#include <stdio.h>#include <string.h> /*прототипы строковых функций*/char *strsave(char *s) { char *p=NULL; p=(char *) malloc(strlen(s)+1); if (p!= NULL) strcpy(p, s); return(p);}void main () { char *s1="hello", *s2; s2=strsave(s1); printf ("\n%s",s2);}
|
|