Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Конспект лекций -=Вычислительная техника и прог...docx
Скачиваний:
1
Добавлен:
01.05.2025
Размер:
1.84 Mб
Скачать

3.4. Лекция 7. Указатели и массивы

3.4.1. Объявление указателей

При обработке оператора определения переменной, например, int i = 10 ; компилятор выделяет память в соответствие с типом (int) и инициализирует её с указанным значением (10). Все обращения в программе к переменной по её имени (i) заменяются компилятором на адрес области памяти, в которой хранится значение переменной.

Программист может определить собственные переменные для хранения адресов областей памяти. Такие переменные называются указателями.

Указатели — это переменные, предназначенные для хранения адресов областей памяти.

Различают три вида указателей:

— указатели на функцию;

— указатели на объект;

— указатели на void.

Указатель на функцию содержит адрес области памяти в сегменте кода, по которому располагается исполняемый код функции.

Используется для:

— косвенного вызова функции (не через имя, а через обращение к переменной, хранящей ее адрес);

— передачи имени функции в другую функцию в качестве параметра.

Формат записи указателя на функцию имеет вид:

тип (*имя) (список_типов_аргументов);

Например, объявление вида:

int (*cooler) (double, double);

задаёт указатель cooler на функцию, возвращающую значение типа int и имеющую два аргумента типа double.

Указатель на объект содержит адрес области памяти, в которой хранятся данные основного или составного типа.

Формат указателя на объект в простейшем варианте имеет вид:

тип *имя;

где: тип — может быть любым типом, кроме ссылки и битового поля;

* — относится к имени.

Например:

int *a, b, *c ;

В данном примере описываются два указателя на целое с именами a и c, а также целая переменная b. Размер указателя зависит от модели памяти. Можно определить указатель на указатель и т.д.

Указатель на void применяется в тех случаях, когда конкретный тип объекта, адрес которого требуется хранить, не определен (например, если в одной и той же переменной в разные моменты времени требуется хранить адреса объектов различных типов).

Форматы указателя на void имеют вид:

тип (*имя) (список_типов_аргументов);

тип *имя;

Примечание (!):

Указателю на void можно присвоить значение указателя любого типа, сравнивать его с любыми указателями, но перед выполнением действий с областью памяти, на которую он ссылается, требуется преобразовать его к конкретному типу явным образом.

Примеры объявления указателей:

1) int i ; // целая переменная

2) const int ci=1 ; // целая константа

3) int *pi ; // указатель на целую переменную

4) const int *pci ; // указатель на целую константу

5) int *const cp=&i ; // указатель-константа на целую

// переменную

6) const int *const cpc=&ci ; // указатель константа на

// целую константу

В представленных примерах:

1) символ & — команда взятия адреса переменной;

2) модификатор const — запрещает изменение значения указателя;

const — указывает на то, что в ячейке памяти хранится

константа.

3.4.2. Инициализация указателей

Указатели чаще всего используют при работе с динамической памятью (кучей).

Динамическая память — свободная память, в которой можно во время выполнения программы выделять место под данные в соответствии с потребностями.

Доступ к выделенным участкам динамической памяти, называемым динамическими переменными, производится только через указатели.

Время «жизни» динамических переменных — от момента объявления, до конца выполнения программы или до явного освобождения динамической памяти.

В С/С++ используют два способа работы с динамической памятью:

— используя семейство функций malloc;

— используя операции new и delete.

Инициализатор записывается после имени указателя либо в круглых скобках, либо после знака равенства.

Способы инициализации указателей:

— присваивание указателю адреса уже существующего объекта;

— присваивание указателю адреса области памяти в явном виде;

— присваивание пустого значения;

— выделение (и освобождение) участка динамической памяти и присваивание её адреса указателя.

1. Присвоение указателю адреса существующего объекта:

— с помощью операции получения адреса:

int a=5 ; // целая переменная

int *p=&a ; // в указатель записывается адрес

// переменной “а”

int *p (&a) ; // в указатель записывается адрес

// переменной “а” другим способом

— с помощью значения другого инициализированного указателя:

int *r=p ; //p — указатель, унициализированный ранее

— с помощью имени массива или функции, которые трактуются как адрес:

int b[10] ; // объявление массив

int *t=b ; // присваивание адреса начала массива

void f(int a) { … } // определение функции

void (*pf)(int) ; // объявление указателя на функцию

pf=f // присваивание указателю адреса

// функции

2. Присваивание указателю адреса области памяти в явном виде:

char *vp=(char*) 0xB8000000 ;

Здесь 0xB8000000 — шестнадцатеричная константа (адрес области памяти);

(char*) — операция приведения типа: константа преобразуется к типу «указатель на char».

3. Присваивание пустого значения:

int *a=NULL ;

int *b=0 ; // рекомендуется использовать этот вариант

Здесь NULL — нулевая константа.

Так как объектов с нулевым адресом нет, то пустой указатель можно использовать для проверки, ссылается ли проверяемый указатель на какой-либо объект или нет.

Рекомендуется использовать второй вариант инициализации.

4. Выделение участка динамической памяти и присваивание её адреса указателю:

Пример:

— с помощью new:

int *n=new int ; // 1

int *m=new int (10) ; // 2

int *q=new int [10] ; // 3

— с помощью функции malloc (библиотека <malloc.h>):

int *u=(int*) malloc(sizeof(int)) ; // 4 .

В представленных примерах инструкции могут быть описаны следующим образом:

//1 — операция new выполняет выделение достаточного для размещения величины типа int участка динамической памяти и записывает адрес начала этого участка в переменную n.

Память под саму переменную n (размера, достаточного для размещения указателя) выделяется на этапе компиляции.

//2 — выполняются действия, описанные выше, а также производится инициализация выделенной динамической памяти значением 10.

//3 — операция new выполняет выделение памяти под 10 величин типа int (т.е. под массив из 10-ти элементов) и записывает адрес начала этого участка в переменную q , которая может трактоваться как имя массива, через которое можно обращаться к любому элементу массива.

//4 — выполняется тоже самое, что в //1, но с помощью функции malloc . В функцию передаётся один параметр — количество выделяемой памяти в байтах.

(int*) — приведение типа указателя, возвращаемого функцией, к требуемому типу. Функция возвращает 0, если выделить память не удалось.

Примечание (!):

Предпочтительнее использовать операцию new, чем функцию malloc, особенно при работе с объектами.

5. Освобождение памяти:

— выделение new — освобождение delete;

— выделение malloc — освобождение free.

Пример освобождения памяти (соответствует примеру выделения памяти): delete n ; // 1

delete m ; // 2

delete []q ; // 3

free(u) ; // 4

Примечания:

Если выделение осуществлялось для массива, то необходимо указывать, что освобождается массив, поставив перед именем указателя пустые квадратные скобки (для освобождения требуется []delete). Размерность массива при этом не указывается.

Если нет скобок [], то освобождается только первый элемент массива, а остальные окажутся недоступными для дальнейших операций. Т.е. появляется «мусор» в памяти.

«Мусор» также может появиться, когда переменная-указатель выходит из области своего действия (и становиться недоступной), или когда инициализированному указателю присваивается значение другого указателя (старое значение теряется).