Указатели и массивы

advertisement
Лекция 4. Указатели и динамическая память. Выделение и освобождение
динамической памяти, размещение в ней переменных. Средства Windows
для работы с памятью. Передача параметров функций по ссылке и по указателю.
УКАЗАТЕЛИ И ДИНАМИЧЕСКАЯ ПАМЯТЬ
Динамическая память - это оперативная память ЭВМ, предоставляемая программе при её работе. Динамическое размещение данных означает использование динамической памяти непосредственно при работе программы. В отличие от этого статическое размещение данных осуществляется компилятором, то есть должны быть известны заранее количество и тип размещаемых данных. При динамическом размещении они могут быть заранее не известны.
Оперативная память ЭВМ представляет собой совокупность ячеек для хранения
информации – байтов, каждый из которых имеет собственный номер. Эти номера называются адресами, они позволяют обращаться к любому байту памяти. Язык С++ предоставляет в распоряжение программиста средство управления динамической памятью –
так называемые указатели (pointer). Указатель – это переменная, которая в качестве
своего значения содержит адрес байта памяти. С помощью указателей можно размещать в динамической памяти любой тип данных. Лишь некоторые из них занимают во
внутреннем представлении один байт, остальные – несколько смежных. Поэтому на самом деле указатель содержит адрес только первого байта данных.
В С++ указатели всегда связываются с некоторым типом данных (при этом говорят, что указатель ссылается на соответствующий тип). Хотя адрес – это по существу 32 битное целое число, определяющее положение объекта в оперативной памяти
ЭВМ, указатель как бы «помнит» на какого рода данные он ссылается. Объявление указателя выглядит так:
тип_объекта *имя_указателя;
Пример:
int *aa;
double *bb;
Чтобы получить доступ к объекту (переменной), на который указатель ссылается, применяют операцию разыменования указателя *. Например, *aa будет представлять собой значение переменной, на которую ссылается указатель aa. Чтобы, наоборот,
получить адрес переменной, нужно применить операцию взятия адреса &. Пример:
double pi=3.14;
double *aa;
aa=π // Здесь указателю аа присваивается адрес переменной pi.
printf(“%f”, *aa); // Здесь на печать выводится значение, содержащееся по адресу аа.
ВЫДЕЛЕНИЕ И ОСВОБОЖДЕНИЕ ДИНАМИЧЕСКОЙ ПАМЯТИ, РАЗМЕЩЕНИЕ В НЕЙ ПЕРЕМЕННЫХ
Основное назначение указателей - создание и обработка динамических структур данных. В С++ можно выделить память под некоторый объект не только с помощью оператора объявления, но и динамически, во время исполнения программы. Выделение
памяти для размещения объекта производится с помощью функции malloc() (для доступа к ней необходимо сослаться на заголовки <alloc.h> и <stdlib.h>). Пример:
//-----------------------------------------------------------double *aa; //Объявление указателя на тип double
aa=(double*)malloc(sizeof(double)); // Динамическое выделение памяти размера
// sizeof(double)
*aa=2.71;
// присвоение значения 2.71 динамической переменной, размещённой по
// адресу aa
printf(“%f”,*aa); // вывод на печать значения динамической переменной
free(aa); // Освобождение памяти, закреплённой за указателем аа.
//------------------------------------------------------------Несколько пояснений к примеру. Аргументом функции malloc() является размер
области памяти, которую нужно выделить; для этого можно применить операцию
sizeof(), которая возвращает размер переменной или типа, указанного в качестве операнда. Функция malloc() возвращает значение типа void* – то есть указатель на пустые
данные. Такой указатель нельзя разыменовывать, поскольку неизвестно, на данные какого типа он указывает, поэтому к нему применяется операция приведения типа
(double*). Память, выделенную malloc(), следует освободить функцией free(), если динамическую переменную больше не предполагается использовать.
Указатели и массивы
Между указателями и массивами в С++ существует тесная связь. Имя массива
без индекса эквивалентно указателю на его первый элемент. И наоборот, указатель
можно использовать подобно имени массива, то есть индексировать его. Например:
//--------------------------------int arr[5], i;
int *aa;
aa=arr; // указателю аа присваивается адрес первого элемента массива arr.
// это эквивалентно записи aa=&arr[0];
i=aa[2]; // обращение к элементу массива arr с индексом [2].
//--------------------------------Приведём пример динамического объявления одномерного массива длины n:
int *aa;
int n;
n=10;
aa=(int*)malloc(n*sizeof(int));
// Теперь к массиву aa можно обращаться обычным способом аа[0], aa[1] и т.п.
//После завершения с ним работы следует освободить занимаемую им память
free(aa);
//-----------------------------------Для организации многомерных динамических массивов применяется приём типа
«указатель на указатель». Пример динамического объявления двумерного массива размерностью m на n:
//-------------------------------------int **aa;
int m, n, i;
m=3; n=5;
aa=(int**)malloc(m*sizeof(int*)); //Выделение памяти под массив из m указателей на int
for(i=0;i<m;i++)
{
aa[i]=(int*)malloc(n*sizeof(int));// Выделение памяти под массивы из n целых чисел
}
// Теперь к массиву aa можно обращаться обычным способом аа[0][0], aa[1][0] и т.п.
//После завершения с ним работы следует освободить занимаемую им память
for(i=0;i<m;i++)
{
free(aa[i]);
}
free(aa);
//-----------------------------------При работе с динамической памятью кроме функции malloc() в С++ часто используется
также функция calloc(). Кроме того, существуют также специальные операции new и
delete. Они введены для предоставления возможности перегрузки этих операций для
придания им каких-либо дополнительных свойств. Вот пример создания и уничтожения
динамической переменной с помощью операций new и delete.
//--------------------------------int *aa, *bb;
int n;
n=5;
aa=new int; // Выделение памяти под переменную типа int
bb=new int[n]; // Выделение памяти под массив значений типа int длины n
delete aa;// Освобождение памяти под переменной
delete[] bb//; Освобождение памяти под массивом
//--------------------------------Использование процедур выделения и освобождения памяти, как и вообще вся
работа с динамической памятью требует особой осторожности и тщательного соблюдения следующего правила: освобождать нужно ровно столько памяти, сколько её было
зарезервировано и именно с того адреса, с которого она была зарезервирована.
СРЕДСТВА WINDOWS ДЛЯ РАБОТЫ С ПАМЯТЬЮ
Система Windows имеет собственные средства для работы с памятью. Это так называемые API-функции (application programming interface), которые становятся доступными
после ссылки на заголовочный файл <windows.h>. Так как почти все устройства ЭВМ
(в том числе оперативная память) в многозадачных ОС являются разделяемыми ресурсами, то есть программное адресное пространство не совпадает с физическим, APIфункции в своей работе широко используют понятие системного дескриптора (стандартный тип HANDLE). Дескриптор – это 32 разрядное число, хранящее условный адрес структуры с описанием той или иной программной сущности (переменной, объекта,
области памяти, файла, потока, окна и т.п.). Перечислим основные функции WinAPI,
используемые для работы с динамической памятью (полное их описание можно получить, обратившись к справочным файлам WIN32.HLP или WIN32S.HLP).
GlobalAlloc() – выделяет память требуемого размера;
GlobalFree() – освобождает блок памяти;
GlobalLock() – возвращает указатель на первый байт указанного блока памяти (как бы
«преобразуя» дескриптор HANDLE в указатель);
GlobalHandle() – возвращает дескриптор блока памяти, связанного с заданным указателем (как бы «преобразуя» указатель в дескриптор HANDLE);
(См. также пример программы к лекции 5).
ПЕРЕДАЧА ПАРАМЕТРОВ ФУНКЦИЙ ПО ССЫЛКЕ И ПО УКАЗАТЕЛЮ
В языке С++ в качестве параметров функций допускается передавать не только значения переменных, но и указатели на переменные. Например:
double sum(double *aa, int n)
{int i;
double j=0;
for(i=0;i<n;i++)
{j=j+aa[i];}
return j;
}
Данная функция sum выдаст сумму элементов массива aa (в данном случае память под
массив и его инициализация происходит вне тела функции sum; говорят, что массив aa
передается в функцию по указателю). Следует помнить, что в случае передачи параметров по указателю, изменение их значений внутри тела функции привёдёт к изменению их значений и вне тела функции.
В С++ имеется также модифицированная форма указателей, называемая ссылкой. Ссылка – это указатель, который автоматически разыменовывается при обращении
к нему. Ссылки оказываются удобны для передачи в функцию параметров по указателю, причём позволяют сделать это без использования явных указателей и адресов.
Пример передачи параметров функции по ссылке:
//-------------------------------------------------------------int i, j;
i=1; j=2;
void example(int &i, int j)
// переменная i передаётся в функцию как ссылка,
// j – как значение
{int k;
k=i+j;
i=k;
j=k;
}
// Вызов функции example
example(i, j);
//------------------------------------------------------------Результатом работы данной программы будет i=3, j=2. То есть переменная i, переданная по ссылке (int &i в списке параметров функции example), изменила своё значение, а переменная j, переданная по значению, нет.
По ссылке возможна не только передача параметров в функцию, но и передача
из неё возвращаемого значения. В том случае, когда функция возвращает ссылку, она
фактически возвращает адрес; это делает возможным написание функции слева от оператора присваивания в выражениях, соответствующее значение будет помещено по адресу, возвращённому функцией.
int& func(int n)
{
…
};
func(5) = 10;
Помимо случаев, когда функция должна возвращать значения в параметрах,
ссылки и указатели могут быть полезны при передаче переменных большого размера,
поскольку функции в этом случае передаётся не сама переменная, а её адрес.
ЗАДАНИЕ К ЛЕКЦИИ 4
Разместить в динамической памяти 2 массива – матрицу размерностью n на n и вектор
длины n, с элементами типа long double (значения n и элементы массивов ввести с консоли). Написать функцию, вычисляющую произведение данной матрицы на данный
вектор (их передать по указателю) и возвращающую указатель на массив-результат.
Выдать на консоль значения полученного вектора.
Download