
- •Лабораторная работа №11 Тема работы «Работа с динамическими структурами данных»
- •Цель работы
- •Использование указателей
- •Указатели и массивы
- •Массивы указателей
- •Динамическое распределение памяти, динамические структуры данных
- •Динамическое выделение памяти для массивов
- •Использование массива указателей на структуру
- •Создание сложных структур данных
- •Индивидуальные задания
Лабораторная работа №11 Тема работы «Работа с динамическими структурами данных»
Цель работы
Приобретение навыков работы с указателями. Изучение методов представления списков в памяти.
Использование указателей
Указатель на ячейку памяти используется для организации работы с динамическими структурами данных. Для объявления указателя нужно поставить перед именем переменной оператор разыменования *.
int *pointer;
Инициализировать указатель можно:
используя адрес уже объявленной переменной с помощью оператора взятия адреса &.;
выделяя под него память функцией malloc().
В первом случае создается как бы синоним переменной, это удобно, когда требуется ссылаться на переменные, последовательность использования которых будет меняться.
Пример:
#include "stdafx.h"
#include "conio.h"
void main()
{
int someVariable = 4,k; //объявляем и инициализируем переменную someVariable
int *pointer = &someVariable; //объявляем указатель
pointer = &someVariable; //инициализируем его адресом переменной someVariable
k=*pointer+1; // изменяем значение, находящееся по адресу, на который ссылается
// указатель pointer
printf("Текущее значение переменной someVariable = %d\n", someVariable);
printf("Текущее значение переменной *pointer = %d", k);
getch();
}
Выведет строки:
Текущее значение переменной someVariable = 4
Текущее значение переменной*pointer = 5
Второй случай рассмотрен в п.5 данных методических указаний.
Указатели и массивы
Понятия указателей и массивов тесно связаны. Рассмотрим следующий фрагмент программы:
char str[80], *p1;
p1 = str;
Напомним, что имя массива без индекса возвращает адрес первого элемента массива. Поэтому здесь p1 указывает на первый элемент массива str. Обратиться к пятому элементу массива str можно с помощью любого из двух выражений:
str[4]
или
* (p1+4)
Массив начинается с нуля. Поэтому для пятого элемента массива str нужно использовать индекс 4. Можно также увеличить p1 на 4, тогда он будет указывать на пятый элемент.
Рассмотрим следующий фрагмент программы:
int *p, i[10];
p = i;
p[5] = 100; /* в присвоении используется индекс */
*(p+5) = 100; /* в присвоении используется адресная арифметика */
Оба оператора присваивания заносят число 100 в 6-й элемент массива i. Первый из них индексирует указатель p, во втором применяются правила адресной арифметики. В обоих случаях получается один и тот же результат.
В языке С существуют два метода обращения к элементу массива: адресная арифметика и индексация массива. Стандартная запись массивов с индексами наглядна и удобна в использовании, однако с помощью адресной арифметики иногда удается сократить время доступа к элементам массива. Поэтому адресная арифметика часто используется в программах, где существенную роль играет быстродействие.
В следующем фрагменте программы приведены две версии функции putstr(), выводящей строку на экран. В первой версии используется индексация массива, а во второй – адресная арифметика:
/* Индексация указателя s как массива. */
void putstr(char *s)
{
register int t;
for(t=0; s[t]; ++t) putchar(s[t]);
}
/* Использование адресной арифметики. */
void putstr(char *s)
{
while(*s) putchar(*s++);
}
Большинство профессиональных программистов сочтут вторую версию более наглядной и удобной. Для большинства компиляторов она также более быстродействующая. Поэтому в процедурах такого типа приемы адресной арифметики используются довольно часто.
Можно также индексировать указатели на многомерные массивы. Например, если а — это указатель на двухмерный массив целых размерностью 10×10, то следующие два выражения эквивалентны:
a
&a[0][0]
Более того, к элементу (0,4) можно обратиться двумя способами:
либо указав индексы массива: а[0][4],
либо с помощью указателя: *((int*)а+4).
Аналогично для элемента (1,2): а[1][2] или *((int*)а+12).
В общем виде для двухмерного массива справедлива следующая формула:
a[j][k] эквивалентно *((базовый_тип*)а+(j*длина_строки)+k)
Правила адресной арифметики требуют явного преобразования указателя на массив в указатель на базовый тип. Указатели используются для обращения к элементам массива потому, что часто операции адресной арифметики выполняются быстрее, чем индексация массива.
Двухмерный массив может быть представлен как указатель на массив одномерных массивов. Добавив еще один указатель, можно с его помощью обращаться к элементам отдельной строки массива. Этот прием демонстрируется в функции pr_row(), которая печатает содержимое конкретной строки двухмерного глобального массива num:
int num[10][10];
/* ... */
void pr_row(int j)
{
int *p, t;
p = (int *) &num[j][0]; /* вычисление адреса 1-го
элемента строки номер j */
for(t=0; t<10; ++t) printf("%d ", *(p+t));
}
Эту функцию можно обобщить, включив в список аргументов номер строки, длину строки и указатель на 1-й элемент:
void pr_row(int j, int row_dimension, int *p)
{
int t;
p = p + (j * row_dimension);
for(t=0; t<row_dimension; ++t)
printf("%d ", *(p+t));
}
/* ... */
void f(void)
{
int num[10][10];
pr_row(0, 10, (int *) num); /* печать 1-й строки */
}
Такой прием "понижения размерности" годится не только для двухмерных массивов, но и для любых многомерных. Например, вместо того, чтобы работать с трехмерным массивом, можно использовать указатель на двухмерный массив, причем вместо него в свою очередь можно использовать указатель на одномерный массив. В общем случае вместо того, чтобы обращаться к n-мерному массиву, можно работать с указателем на (n-1)-мерный массив. Причем этот процесс понижения размерности кончается на одномерном массиве.