- •Указатели
- •Объявление указателей
- •Доступ к объекту через указатель
- •Инициализация указателей
- •Указатель на неопределенный тип
- •Выражения с указателями
- •Операции с указателями
- •Указатели и массивы
- •Нехватка памяти
- •Указатели и многомерные массивы
- •Использование указателей вместо двумерного массива
- •Свободные массивы и указатели
Операции с указателями
С указателями можно использовать только следующие операции:
++ инкремента
- - декремента
+, - сложения и вычитания
Если к указателю применяются операции ++ или --, то указатель увеличивается или уменьшается на размер объекта, который он адресует:
тип *ptr;
ptr ++ = значение ptr + sizeof (тип)
ptr -- = значение ptr - sizeof (тип)
Например:
int i, *pi = &i;
float a, *pa =&a;
pi++; //значение указателя pi увеличивается на 2 байта, так как он адресует объект
//типа int, указатель сдвигается вправо на 2 байта
pa++; // значение указателя pa увеличивается на 4 байта, так как он адресует объект
//типа float, указатель сдвигается вправо на 4 байта
Одним из операндов операции сложения может быть указатель, другим должно быть целое число. Целое число складывается с указателем следующим образом:
тип *ptr;
int n;
ptr + n = значение ptr + n*размер типа
Например,
float a, *pa = &a;
pa = pa+3; // значение указателя pa будет увеличено на 12 байт (сдвиг указателя вправо)
Левым операндом операции вычитания может быть указатель, а правым операндом должно быть целое число:
тип *ptr;
int n;
ptr - n = значение ptr - n*размер типа
Например:
int i, *pi = &i;
pi=pi-5; // уменьшение значения указателя на 10 байт (сдвиг указателя влево)
Пример 3.Использование указателей в выражениях
#include <stdio.h>
void main (void)
{
int x=5,y,*px=&x; //указатель px инициализирован адресом переменной x
y=*px+5;
printf("y=%d значение указателя=%p\n",y,px);
y=*px++; //изменение значения указателя после его использования
printf("y=%d значение указателя=%p\n",y,px);
px=&x;
y=(*px)++; //изменение содержимого по адресу, хранящемуся в px
printf("y=%d значение указателя=%p значение, адресуемое указателем *px= %d\n",y,px,*px);
y=++*px; //изменение содержимого по адресу px на единицу
printf("y=%d значение указателя=%p\n",y,px);
}
Результаты работы этой программы имеют вид:
y=10 значение указателя=1561:1000
y=5 значение указателя=1561:1002
y=5 значение указателя=1561:1000 значение, адресуемое указателем*px= 6
y=7 значение указателя=1561:1000
Указатели и массивы
Между указателями и массивами существует прямая связь. Когда объявляется массив, например:
int arr [5];
то идентификатор массива arr определяется как константный указатель на первый (с индексом 0) элемент массива. Это означает, что имя массива содержит адрес элемента массива с индексом 0:
arr = &arr[0];
Так как идентификатор массива содержит адрес, то можно, например, записать:
int arr[ 5]; //объявление массива целых
int *parr; //объявление указателя на данное целого типа
parr = arr; // присваивавание указателю адреса первого элемента массива, то же, что
// и parr = &arr[0];
Связь между именем массива arr и указателем parr можно изобразить следующим образом:
parr
-
arr[0]
arr[1]
arr[2]
arr[3]
arr[4]
Адрес элемента массива с индексом i может быть записан как
&arr[i], что эквивалентно записи parr+i
Значение элемента массива с индексом i может быть записано как
arr[i], что эквивалентно записи *(parr+i) или *(arr+i)
Все индексные выражения вида arr[i] компилятор преобразует к адресному представлению в соответствии с правилами выполнения операции сложения указателя с целой величиной:
arr[i]=*(arr+i) =* (arr + i* sizeof(int))
ВАЖНО.
Указатель – это переменная, и ее значение можно изменять в программе, например, можно записать:
parr++;
Операция parr++ означает сдвиг указателя на следующий элемент массива arr++, то есть на 2 байта. Если, например, до выполнения операции parr++ указатель был установлен на первый элемент массива, то в результате выполнения этой операции он окажется установленным на второй элемент:
parr
-
arr[0]
arr[1]
arr[2]
arr[3]
arr[4]
Имя массива – константа. Значение адреса, которое хранит имя массива, изменять нельзя, поэтому ошибочной является следующая запись:
arr++;
Пример 4. Программа, в которой демонстрируется использование указателей на массив.
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <time.h>
#define N 100 //максимально допустимое количество элементов в массиве
int main(void)
{
int a[N]; /* резервирование памяти под N элементов массива */
int i; //переменная, управляющая циклом
int *pa=a; //объявление указателя на данное целого типа
//и инициализация указателя адресом массива а
clrscr();
randomize();
//цикл заполнения массива случайными числами
for(i=0; i<N; i++)
*(pa+i) = random(101)-50; // присваивание элементу массива значения случайного
// числа из диапазона от -50 до 50
//цикл вывода элементов массива
printf("\n\n Массив выведен по 10 элементов в строке \n\n");
for(i=0; i<N; i++)
{
printf("%6d ", *(pa+i)); // вывод массива
if(i%10==9)
printf("\n");
}
getch();
return 0;
}
Пример 4. Программа, в которой организован ввод элементов массива в диалоговом режиме и вывод полученного массива. Используется указатель на массив.
#include <stdio.h>
#include <conio.h>
void main(void)
{
int i;
int a[10]; //объявление массива целых из десяти элементов
int *pa = a; //объявление указателя на данное целого типа и инициализация указателя
// адресом массива
clrscr();
//цикл ввода элементов массива в диалоговом режиме
for(i=0; i<10; i++)
{
printf("\nВведите элемент массива с индексом %d ",i);
scanf("%d", pa+i); //функция scanf получает адрес элемента массива
}
printf("\n\n Вы ввели массив \n\n");
for(i=0; i<10; i++)
printf("%4d ", *(pa+i)); // вывод массива
getch();
}
Пример 5. Программа, в которой используются указатели на строки.
#include <stdio.h>
void main(void)
{
char s[]="строка как массив";
char *ps=s; //указатель ps инициализирован значением адреса строки s
char *ps1,*ps2,*ps3; //объявление указателей на строки
//присвоение указателю ps1 адреса строковой константы
ps1="В языке си в одну\n"
"строку можно записать\n"
"целое стихотворение\n";
//присвоение указателю ps2 адреса строковой константы
ps2="предложение не будет\
перенесено на следующую строку";
//присвоение указателю ps3 адреса строковой константы
ps3="одна"
" строка";
//цикл вычисления длины строки s, адрес которой хранит указатель ps
// выполнять, пока не найден конец строки
while(*ps!='\0')
ps++;
printf("длина строки= %d\n",ps-s);
puts(ps1);
puts(ps2);
puts(ps3);
}
УКАЗАТЕЛИ И ДИНАМИЧЕСКИЕ ПЕРЕМЕННЫЕ
Память под переменные можно распределять в процессе исполнения программы.
Переменные, память под которые выделяется в процессе исполнения программы, называются динамическими. Они запоминаются в блоках памяти переменного размера, известных как “куча”. Принятое распределение памяти в показано на рисунке 1.
Старшие адреса
Младшие адреса
Рис. 1.Распределение памяти в С
Динамические переменные удобно использовать, если в программе нужно работать с массивом, размер которого заранее не известен.
Для динамического выделения свободной памяти (в куче) можно использовать библиотечные функции malloc() и calloc(). Для их применения следует включить в текст программы заголовочный файл malloc.h.
Чтобы воспользоваться функцией malloc(), необходимо объявить указатель, например:
float *fp;
Затем вызовите функцию malloc(), задавая в круглых скобках число байтов для выделения из кучи, например:
fp = (float *) malloc(16);
или:
fp = (float *) malloc( 4*sizeof (float));
В этом примере выделено 16 байт под размещение данных типа float. Функция malloc() возвращает указатель на тип void. Выражение (float *) позволяет выполнить приведение типа указателя к требуемому – float, так как перед применением указателя на тип void в программе требуется явное приведение его типа к определенному. Возвращаемое функцией malloc() значение адреса присваивается укзателю fр.
Функция calloc() требует передачи ей двух аргументов – количества объектов, под которые запрашивается память и размер одного объекта, например:
long *p;
p= (long *) calloc(10, sizeof (long)) ;
то же, что и:
p= (long *) calloc(10, 4)) ;
В приведенных примерах распределяется 40 байт памяти для размещения объектов типа long.
Кроме выделения памяти, функция calloc() устанавливает каждый байт выделенной памяти равным нулю. Функцию calloc() можно рекомендовать использовать для инициализации блоков памяти.
