Указатели в Cи

Указатели это чрезвычайно мощный инструмент в программировании. С помощью указателей некоторые вещи в программировании можно сделать намного проще и при этом эффективность работы вашей программы значительно повысится. Указатели даже позволяют обрабатывать неограниченное количество данных. Например, с помощью указателей можно изменять значения переменных внутри функции, при этом переменные передаются в функцию в качестве параметров. Кроме того, указатели можно использовать для динамического выделения памяти, что означает, что вы можете писать программы, которые могут обрабатывать практически неограниченные объемы данных на лету — вам не нужно знать, когда вы пишете программу, сколько памяти нужно выделить заранее. Пожалуй, это самая мощная функция указателей. Для начала давайте просто получим общее представление об указателях, научимся их объявлять и использовать.

Что такое указатели и зачем они нужны?

Указатели похожи на метки, которые ссылаются на места в памяти. Представьте сейф с депозитными ячейками различного размера в местном банке. Каждая ячейка имеет номер, уникальный номер, который связан только с этой ячейкой, таким образом можно быстро идентифицировать нужную ячейку. Эти цифры аналогичны адресам ячеек компьютерной памяти. К примеру, у вас есть богатый дядя, который хранит все свои ценности в своем сейфе. И чтобы обезопасить все свои сбережения, он решил завести меньший сейф, в который положит карту, на которой показано местоположение большого сейфа и указан 16-й пароль от большого сейфа, в котором и хранятся реальные драгоценности. По сути, сейф с картой будет хранить расположение другого сейфа. Вся эта организация сбережения драгоценностей эквивалентна указателям в языке Си. В компьютере, указатели просто переменные, которые хранят адреса памяти, как правило, адреса других переменных.

Идея в том, что зная адрес переменной, вы можете пойти по этому адресу и получить данные, хранящиеся в нем. Если вам нужно передать огромный кусок данных в функцию, намного проще передать адрес в памяти, по которому хранятся эти данные, чем скопировать каждый элемент данных! Более того, если программе понадобится больше памяти, вы можете запросить больше памяти из системы. Как же это работает? Система просто возвращает адрес ячейки памяти, и мы должны сохранить этот адрес в переменной-указателе. Так мы сможем взаимодействовать с данными из указанного участка памяти.

Синтаксис указателей

Если у нас есть указатель, значит мы можем получить его адрес в памяти и данные на которые он ссылается, по этой причине указатели имеют несколько необычный синтаксис, отличающийся от объявления простых переменных. Более того, поскольку указатели — это не обычные переменные, то,  необходимо сообщить компилятору, что переменная является указателем и сообщить компилятору тип данных, на которые ссылается указатель. Итак, указатель объявляется следующим образом:

data_type *pointerName;

где, data_type — тип данных, pointerName — имя указателя.

Например, объявим указатель, который хранит адрес ячейки памяти, в которой лежит целое число:

int *integerPointer;

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

// Объявление указателя и простой переменной в одной строке
int *pointer1,   // это указатель
    variable;    // это обычная переменная типа int

// Объявление двух указателей в одно строке
int *pointer1, // это указатель с именем pointer1
    *pointer2; // это указатель с именем pointer2

Как я и говорил, если имя переменной не предваряется символом *, то это обычная переменная, в противном случае — это указатель. Именно это и показывает пример объявления указателей, выше.

Есть два способа использования указателя:

  1. Использовать имя указателя без символа *, таким образом можно получить фактический адрес ячейки памяти, куда ссылается указатель.
  2. Использовать имя указателя с символом *, это позволит получить значение, хранящееся в памяти. В рамках указателей, у символа * есть техническое название — операция разыименования. По сути, мы принимаем ссылку на какой-то адрес памяти, чтобы получить фактическое значение. Это может быть сложно для понимания, но в дальнейшем постараемся разобраться во всем этом.

Объявление указателя, получение адреса переменной

Для того чтобы объявить указатель, который будет ссылаться на переменную, необходимо сначала получить адрес этой переменной. Чтобы получить адрес памяти переменной (её расположение в памяти), нужно использовать знак & перед именем переменной. Это позволяет узнать адрес ячейки памяти, в которой хранится значение переменной. Эта операция называется — операция взятия адреса и выглядит вот так:

int var = 5; // простое объявление переменной с предварительной инициализацией
int *ptrVar; // объявили указатель, однако он пока ни на что не указывает 
ptrVar = &var; // теперь наш указатель ссылается на адрес в памяти, где хранится число 5

В строке 3 использовалась операция взятия адреса, мы взяли адрес переменной var и присвоили его указателю ptrVar. Давайте рассмотрим программу, которая наглядно покажет всю мощь указателей. Итак, вот исходник:

#include <stdio.h>

int main()
{
    int var;     // обычная целочисленная переменная
    int *ptrVar; // целочисленный указатель (ptrVar должен быть типа int, так как он будет ссылаться на переменную типа int)

    ptrVar = &var;        // присвоили указателю адрес ячейки в памяти, где лежит значение переменной var
    scanf( "%d", &var );  // в переменную var положили значение, введенное с клавиатуры
    printf( "%d\n", *ptrVar ); // вывод значения через указатель
    getchar();
}

Результат работы программы:

CppStudio.com

6
6

В строке 10, printf() выводит значение, хранящееся в переменной var. Почему так происходит? Что ж, давайте посмотрим на код. В строке 5, мы объявили переменную var типа int. В строке 6 — указатель ptrVar на целое значение. Затем указателю ptrVar присвоили адрес переменной var, для этого мы воспользовались оператором присвоения адреса. Затем пользователь вводит номер, который сохраняется в переменную var, помните, что это то же самое место, на которое указывает ptrVar. В самом деле, так как мы используем амперсанд чтобы присвоить значение переменной var в функции scanf(), должно быть понятно, что scanf() инициализирует переменную var через адрес. На этот же адрес указывает указатель ptrVar.

Затем, в строке 10, выполняется операция «разыменования» — *ptrVar. Программа, через указатель ptrVar, считывает адрес, который хранится в указателе, по адресу попадает в нужную ячейку памяти, и возвращает значение, которое там хранится.

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

Если бы такие приемчики были позволены в ОС, вы могли бы получить доступ к любому участку памяти. А это значит, что для любой запущенной программы вы могли бы внести свои изменения, например, если у вас открыт документ в Word, вы могли бы изменить любой текст программно. К счастью, Windows и другие современные операционные системы остановит вас от доступа к этой памяти и преждевременно закроют вашу программу.

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

P.S.:Если у вас нет денег на телефоне, и нет возможности его пополнить, но при этом, вам срочно нужно позвонить, вы всегда можете использовать доверительный платеж билайн. Сумма доверительного платежа может быть самой разнообразной, от 50 до 300р.

Практика

К сожалению, для данной темы пока нет подходящих задач. Если у вас есть таковые на примете, отправте их по адресу: admin@cppstudio.com. Мы их опубликуем!

Автор: Marienko L.
Дата: 15.12.2013
Поделиться:

Оставить комментарий

Вы должны войти, чтобы оставить комментарий.