Указатели в СИ
Указатель – это переменная, значением которой является адрес, по которому располагаются данные. Адрес – это номер ячейки памяти, в которой или с которой располагаются данные.Классифицировать указатели в СИ можно:
•по типу данных;
•по области доступа.
По типу данных в СИ указатели делятся на:
Типизированный указатель – указатель, содержащий адрес данных определенного типа (системного или пользовательского).
Не типизированный указатель – указатель, содержащий адрес данных неопределенного типа (просто адрес).
По области доступа указатели в СИ делятся на:
Ближний указатель – указатель, содержащий только смещение, по которому располагаются данные.Сегмент в этом случае используется по умолчанию – текущий сегмент данных. Размер ближнего указателя в 16-разрядном реальном режиме работы процессора составляет 16 бит, а в 32-разрядном защищенном режиме – 32 бита.
Дальний указатель – указатель, содержащий и сегмент и смещение. Размер дальнего указателя в 16-разрядном реальном режиме работы процессора составляет 32 бита (16 бит – сегмент, 16 бит - смещение), а в 32-разрядном защищённом режиме – 48 бит (16 бит – селектор, 32 бита - смещение).
Работа с указателями в языке СИ включает три действия, осуществляемых в следующем порядке:
объявление указателя;
установка указателя;
обращение к значению, расположенному по указателю. Объявление (описание) указателя в языке СИ имеет следующий вид:
тип [near|far] *имя [=значение];
В современной реализации языка СИ (стандарт C99), ориентированной под разработку программ для ОС Windows, вследствие используемой в ОС Windows модели памяти, используются исключительно ближние (near) указатели, поэтому при объявлении указателя (например, в среде разработки Pelles C) модификатор области доступа указывать не надо.
Указатель в СИ при объявлении можно инициализировать, указав через знак присвоения соответствующее значение. Данное значение должно быть адресом, записанном в одном из следующих виде:
•нулевое значение (идентификатор NULL);
•другой указатель;
•адрес переменной (через операцию взятия адреса);
•выражение, представляющее собой арифметику указателей;
•адрес, являющийся результатом выделения динамической памяти.
Операция взятия адреса – операция языка СИ, возвращающая адрес переменной. Данная операция имеет следующий синтаксис:
&имя_переменной
Например, в программе описаны следующие переменные:
int a,b;
double c;
Описание указателей на эти переменные с инициализацией будет иметь вид:
int *ptr_a = &a, *ptr_b = &b;
double *ptr_c1 = &c, *ptr_c2 = ptr_c1;
Пример объявления не типизированного указателя с инициализацией нулевым значением:
void *ptr = NULL;
Установка указателя - присвоение его значению адреса, по которому располагаются или будут располагаться данные. Для установки указателя используется оператор присвоения, в левой части которого указывается имя указателя, а в правой – одно из значений отличных от NULL, используемых при инициализации указателя. Пример установки указателей:
int a = 10, *ptr = NULL;
ptr = &a;
Для обращения к значению, располагаемому по адресу, содержащемуся в указателе, используется операция разыменования указателя. Данная операция имеет следующий синтаксис:
*имя_указателя
Значение, полученное путём разыменования указателя, может рассматриваться в программе как LValue, так и RValue значение. Например:
double x = 0.0, *ptr = NULL;
ptr = &x;
scanf("%lf”,ptr);
*ptr += 1.5;
printf("%lf\n”,*ptr);
В языке СИ можно создавать константные указатели – значение, расположенное по этому указателю нельзя изменить. Создание такого указателя имеет следующий синтаксис:
const тип *имя = инициализирующее значение;
Например, следующий фрагмент программы на СИ приведет к ошибке компиляции:
int a = 10;
const int *ptr = &a;
(*ptr)++;
В СИ объявление указателя на массив имеет тот же синтаксис, что и объявление обычного указателя. Например, объявление указателя на вещественный массив типа double будет иметь вид:
double *arrptr = NULL;
Объявление целочисленного массива из десяти элементов с инициализацией нулевыми значениями, и объявление с инициализацией указателя на этот массив будут иметь вид:
int arr[10] = {0}, *arrptr = arr;
Фрагмент программы, в которой объявляется массив из 10 элементов целого типа, осуществляется ввод массива с вычислением суммы его элементов и вывод значения этой суммы с использованием указателей на массив и на переменную для хранения суммы:
int array[10] = {0}, summa = 0;
int *arrptr = array, *ptr = &summa;
for(int i=0;i<10;i++)
{
scanf("%d",&arrptr[i]);
*ptr += arrptr[i];
}
printf("Сумма: %d\n”,*ptr);
Довольно часто встречаются случаи, когда необходимо работать с массивами указателей. Синтаксис объявления массива указателей следующий:
тип *имя[размер];
Например: вычисление суммы набора целых чисел через обращение к ним посредством массива указателей на целые числа:
int arr[10], *ptrs[10], summa = 0;
for(int i=0;i<10;i++) ptrs[i] = &arr[i];
for(int i=0;i<10;i++)
{
scanf("%d”,ptrs[i]);
summa+= *ptrs[i];
}
printf("Сумма: %d\n”,summa);
Строки и указатели в СИ
Объявление указателя на строку имеет тот же синтаксис, что и объявление указателя на символьный тип данных языка СИ:
char *имя;
Так как в языке СИ строка является массивом символов, а имя массива есть указатель на этот массив, то установка указателя на строку осуществляется путём присвоения указателю имени этой строки.
Например:
char str[] = "Моя строка!", *ptr = str;
Работа со строкой как с массивом символов посредством указателя ничем не отличается от работы с массивом. Например, ниже приведён фрагмент программы вычисления длины строки str посредством обращения к ней через указатель ptr:
char *ptr = str;
int len = 0;
while(ptr[len]!=0) len++;
printf("Длина строки: %d\n”,len);
Интересной является возможность объявления указателей на строки и их установка на строковые
константы. Например, возможно следующее:
char *str = "Моя строка!";
puts(str);
Например, в следующем фрагменте программы на экран выводится сообщение "Положительное
значение", если значение целочисленной переменной a больше нуля, "Отрицательное значение" – если меньше и "Нулевое значение" – если ноль:
int a = 0;
printf("Введите число: "); scanf("%d",&a);
char *str = NULL;
if(a > 0) str = "Положительное значение”;
else if(a < 0) str = "Отрицательное значение”;
else str = "Нулевое значение”;
puts(str);
Перечисления и указатели в СИ
Работа с указателями на перечислимый тип данных (enum) ничем не отличается от работы с указателями на целочисленный тип данных, так как перечислимый тип данных является производным от целочисленного типа.
Структуры указатели в СИ
Объявление указателя на структуры или объединения, а также установка указателя на структуры и объединения синтаксически не отличается от соответствующих действий с указателями на скалярные
типы данных. Например:
typedef struct {double x,y;} Point2D;
Point 2D pnt = {0.0,0.0}, *ptr = &pnt;
Отличие заключается в обращении к полям структуры (объединения) через указатели на эти структуры (объединения). Возможны два варианта:
•(*имя_указателя).имя_поля
•имя_указателя->имя_поля
Вычислить расстояние между двумя точками в двумерном пространстве (структура Point2D):
Point2D pnt[2], *ptr1 = &pnt[0], *ptr2 = &pnt[1];
printf("Введите первую точку: ");
scanf("%lf %lf",&ptr1->x,&ptr1->y);
printf("Введите вторую точку: ”);
scanf("%lf %lf",&ptr2->x,&ptr2->y);
double len = sqrt(
pow(ptr1->x - ptr2->x, 2.0)+pow(ptr1->y - ptr2->y, 2.0));
printf("Расстояние: %lf\n",len);
Вычисление длины ломаной линии заданной массивом структур arr (структура Point2D) размера N через указатель ptr на этот массив:
Point2D arr[N] = {...}, *ptr = arr;
double len = 0.0;
for(int i=1;i<N;i++)
len += sqrt(pow(ptr[i].x - ptr[i-1].x, 2.0)+pow(ptr[i].y - ptr[i-1].y, 2.0));
printf("Длина ломаной линии: %lf\n",len);
Вычисление длины ломаной линии заданной массивом структур arr (структура Point2D) размера N через массив указателей ptr на на элементы исходного массива:
Point2D arr[N] = {...}, *ptrs[N];
for(int i=0;i<N;i++) ptrs[i] = &arr[i];
double len = 0.0;
for(int i=1;i<N;i++)
len += sqrt(pow(ptrs[i]->x - ptrs[i-1]->x, 2.0)+pow(ptrs[i]->y - ptrs[i-1]->y, 2.0));
printf("Длина ломаной линии: %lf\n",len);
Арифметика указателей В языке СИ доступны некоторые арифметические действия над типизированными указателями в СИ.
Доступны следующие виды выражений:
•указатель++; ++указатель;
•указатель--; --указатель;
•указатель = указатель + (целочисленное выражение);
•указатель += (целочисленное выражение);
•указатель = указатель - (целочисленное выражение);
•указатель -= (целочисленное выражение);
Технически сложение (или вычитание) типизированного указателя и целого числа означает «сдвиг» указателя на определённое число байт (в зависимости от размера типа указателя) «вправо» (или «влево»).
Примеры:
int *a, *b, *c; //Объявление указателей на int
double *x, *y;
... //Установка указателей
a++; //Сдвиг вправо на 4 байта (размер int)
b-=3; //Сдвиг влево на 12 байт ( (размер int)*3 )
c=a+2; //Смещение с относительно a на 8 байт (размер double)
y = x--; //Х смещается влево на 8 байт (размер double)
В языке СИ арифметика указателей наиболее часто применяется для доступа к элементам массивов. Например,вычисление суммы элементов целочисленного массива:
int arr[10] = {...}, *ptr = NULL, summa = 0;
ptr = arr; //или ptr = &arr[0];
for(int i=0;i<10;i++,ptr++) summa +=*ptr;
printf("Сумма: %d\n", summa);
Цикл в последнем фрагменте программы можно записать и несколько иначе:
for(int i=0;i<10;i++) summa += *(ptr+i);
Такой подход не рекомендуется так как снижает читаемость программы. Еще одним вариантом арифметики указателей является вычитание указателя из другого указателя, в виде: целочисленная переменная = указатель №1 – указатель №2;
Результатом вычитания указателей является целое значение равное расстоянию между адресами, содержащимися в указателях.
Например:
int arr[10], *ptr1 = arr, *ptr2 = &arr[1], *ptr3 = &arr[4]; //объявление массива и указателей
int dest1 = ptr2 - ptr1, dest2 = ptr2 - ptr3;
printf("%d\n%d\n",dest1,dest2);
На экране будет выведено:
1
-3
Далее статья о функциях в СИ.