Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Указатели в C.docx
Скачиваний:
4
Добавлен:
08.08.2019
Размер:
34.26 Кб
Скачать

Указатели в C/C++ для начинающих

Что такое указатели?

Указатели — это те же переменные. Разница в том, что вместо того, чтобы хранить определенные данные, они хранят адрес (указатель), где данные могут быть найдены. Концептуально это очень важно. Многие программы и идеи зависят от указателей, как от основы их архитектуры, например, связанные списки (linked lists).

Введение

Как объявить указатель? Собственно, так же, как и любую другую переменную, но с добавлением звездочки перед именем переменной. Так, например, следующий код создает два указателя, которые указывают на целое число.

int *pNumberOne;

int *pNumberTwo;

Обратили внимание на префикс "p" в обоих именах переменных? Это принятый способ обозначить, что переменная является указателем. Так называемая венгерская нотация. Теперь давайте сделаем так, чтобы указатели на что-нибудь указывали:

pNumberOne = &some_number;

pNumberTwo = &some_other_number;

Знак & (амперсанд) следует читать как "адрес переменной ..." и означает адрес переменной в памяти, который будет возвращен вместо значения самой переменной. Итак, в этом примере pNumberOne установлен и содержит адрес переменной some_number, также pNumberOne указывает на some_number.

Таким образом, если мы хотим получить адрес переменной some_number, мы можем использовать pNumberOne. Если мы хотим получить значение переменной some_number через pNumberOne, нужно добавить звездочку (*) перед pNumberOne (*pNumberOne). Звездочка (*) разыменовывает (превращает в саму переменную) указатель и должна читаться как "место в памяти, которое указывается через ...", кроме объявлений, как в строке int *pNumber.

Чему мы научились: Пример

Фух! Многовато для начала, не правда ли? Я бы рекомендовал, если вы не понимаете то, что описано выше, перечитать ещё раз. Указатели - дело непростое и понимание их концепции может занять определенное время. Вот пример, который показывает идеи, изложенные выше. Пример написан на C, без дополнений C++.

#include <stdio.h>

void main()

{

// объявляем переменные:

int nNumber;

int *pPointer;

// инициализируем объявленные переменные:

nNumber = 15;

pPointer = &nNumber;

// выводим значение переменной nNumber:

printf("nNumber is equal to : %d\n", nNumber);

// теперь изменяем nNumber через pPointer:

*pPointer = 25;

// убедимся, что nNumber изменил свое значение в результате предыдущего действия,

// выведя значение переменной ещё раз

printf("nNumber is equal to : %d\n", nNumber);

}

Внимательно просмотрите код выше и скомпилируйте. Убедитесь в том, что вы понимаете, почему он работает. Потом, если вы готовы, читайте дальше!

Ловушка!

Попробуйте найти ошибку в этой программе:

#include <stdio.h>

int *pPointer;

void SomeFunction()

{

int nNumber;

nNumber = 25;

// делаем так, чтобы pPointer указывал на nNumber

pPointer = &nNumber;

}

Void main()

{

SomeFunction(); // сделайте чтобы pPointer указывал на что нибудь

// почему это не работает?

printf("Value of *pPointer: %d\n", *pPointer);

}

Программа сначала вызывает функцию SomeFunction, которая создает переменную nNumber, а потом инициализирует pPointer, чтобы он указывал на nNumber. Далее начинаются проблемы. Когда функция завершается, nNumber удаляется, поскольку это локальная переменная. Локальные переменные всегда удаляются, когда выполнение программы выходит из блока, где эти переменные были объявлены. Это означает, что когда функция SomeFunction завершается и выполнение возвращается в main(), переменная исчезает. Таким образом, pPointer указывает на ту область памяти, где была переменная, но которая уже не принадлежит программе. Если вы не совсем поняли, о чем идет речь, ещё раз прочтите о локальных и глобальных переменных, а также об областях определения (scope). Эта концепция также очень важна.

Итак, как проблема может быть решена? Ответ - использованием техники известной как динамическое выделение памяти. Обратите внимание, что в языке C такой техники нет, а пример ниже относится к C++.

Динамическое выделение памяти

Динамическое выделение памяти, возможно, является ключом к указателям. Она используется для того, чтобы выделить память без необходимости объявлять переменные, а затем создавать указатели, указывающие на эти переменные. Все же концепция может показаться сначала запутанной, но она очень проста. Код ниже демонстрирует, как выделить память для целого числа:

int *pNumber;

pNumber = new int;

Первая строчка объявляет указатель pNumber. Вторая строчка выделяет память для целого числа (int) и указывает pNumber на эту область памяти. Вот ещё один пример, на этот раз с числом с двойной точностью (double).

double *pDouble;

pDouble = new double;

Формула остается одной и той же, так что вам вряд ли удастся ошибиться. Но, что отличает динамическое выделение так это то, что память, которая была выделена, не удаляется после завершения работы функции или текущего блока кода. Так что если вы перепишете предыдущий пример, используя динамическое выделение - вы увидите, что теперь он работает:

#include <stdio.h>

int *pPointer;

void SomeFunction()

{

// pPointer указывает на целое число

pPointer = new int;

*pPointer = 25;

}

Void main()

{

SomeFunction(); //инициализируем указатель, чтобы он, на что то указывал

printf("Value of *pPointer: %d\n", *pPointer);

}

Прочитайте и скомпилируйте приведеный пример. Убедитесь, что вы понимаете, как и почему он работает. Когда вызывается SomeFunction, она выделяет память и инициализирует pPointer, чтобы он указывал на неё. В этот раз, когда функция завершается выделенная память остается нетронутой и pPointer всё ещё указывает на неё. На этом всё про динамическое выделение памяти!

Память приходит, память уходит

С памятью всегда существуют сложности и в данном случае довольно серьезные, но эти сложности можно с легкостью обойти. Проблема заключается в том, что, не смотря на то, что память, которая была динамически выделена, остается нетронутой она никогда не освобождается автоматически. Память будет оставаться выделенной до тех пор, пока вы не скажете компьютеру, что вам она больше не нужна. Проблема в том, что если вы не скажете системе, что память вам больше не нужна, она будет занимать место, которое возможно необходимо другим приложениям либо частям вашего приложения. В частности это может привести к сбою системы по причине использования всей доступной памяти, поэтому это очень важно. Освобождение памяти, когда она вам больше не нужна, делается очень просто:

delete pPointer;

С этим всё. Вы должны быть осторожны поскольку нужно передавать правильный указатель, указатель который указывает именно на ту область памяти которую вы выделяли а не на непонятный мусор. Попытка освободить память (при помощи delete) которая уже была освобождена опасно и может привести к сбою программы. Поэтому следующий обновленный пример показывает, как это делать правильно без траты памяти:

#include <stdio.h>

int *pPointer;

void SomeFunction()

{

// делаем так, чтобы pPointer указывал на целое число

pPointer = new int;

*pPointer = 25;

}