Указатели и ссылки в С++

Указатели

Мощным средством С++ является возможность осуществления непосредственного доступа к памяти. Для этой цели предусматривается специальный тип переменных – указатели.  Понятие указатель связано с понятиями компьютерной архитектуры – это адрес, адресация, организация внутренней памяти.

Внутренняя память – упорядоченная последовательность байтов или машинных слов. Номер слова в памяти называется адресом.

Прямая адресация – машинное слово, которое содержит непосредственно адрес памяти.

Косвенная адресация – машинное слово, которое содержит адрес другого машинного слова.

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

Другой подход к указателям.

Переменная – это объект, имеющий тип, имя и значение.
Имя переменной – это адрес того участка памяти, который выделен для неё.

Значение переменной – это содержимое этого участка памяти. Для работы с адресами переменных используются указатели.

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

Основная операция для указателя – косвенное обращение к переменной (*). Переменная, адрес которой содержится в указателе называется указуемой переменной. Последовательность действий с указателем включает три шага.

Первый шаг - определение указуемых переменных и переменной указателя.

Синтаксис указателя:   тип_указателя   *имя указателя

Пример:   int a, x; // обычные переменные (указуемые)

         int *pа;  //  переменная – указатель на переменную а

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

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

pа=&a; // указатель содержит адрес переменной а

Объединив два шага можно записать  int *pа=&a;

Третий шаг - выполнение операций с указателями.

В любом выражении косвенное обращение по указателю интерпретируется кака переход от него к указуемой переменной с выполнением над ней всех далее перечисленных в выражении операций.

*pа = 100;  эта запись эквивалентна записи а=100

х = х + *pа эта запись эквивалентна записи х = х + а

(*pа)++  эта запись эквивалентна записи  а++

Адресная арифметика указателя

Указатель даёт «степень свободы» любому алгоритму обработки данных. Если некоторый фрагмент программы получает данные непосредственно в переменной, то он может обрабатывать её и только её.

Если же данные программа получает через указатель, то обработка данных (указуемых переменных) может производиться в любой области памяти компьютера (или программы). Любой указатель в С++ потенциально ссылается на неограниченную в обе стороны область памяти (массив), заполненную переменными указуемого типа с индексацией элементов массива относительно текущего положения указателя.

Особенности:

  • любой указатель потенциально ссылается на неограниченную в обе стороны область памяти, заполненную переменными указуемого типа;
  • переменные в области нумеруются от текущей указуемой переменной, которая получает относительный индекс нуль. Переменные в направлении возрастания адресов памяти нумеруются положительными значениями  1, 2, 3, и т.д., а в направлении убывания – отрицательными.

p+i    переместить указатель на i переменную после указуемой.

p-i     переместить указатель на i переменную перед указуемой.

p++   переместить указатель на следующую переменную.

p--     переместить указатель на предыдущую переменную.

В операциях адресной арифметики транслятором автоматически учитывается размер указуемых переменных, т.е. +i  понимается не как смещение на i байтов, слов и прочее, а как смещение на i указуемых переменных. При объявлении указателей необходимо указывать тип объекта, чтобы компилятору было известно, сколько байтов добавлять к адресу при увеличении указателя на единицу.

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

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

При объявлении массива ему выделяется память. Как только память для массива выделена, имя массива воспринимается, как указатель того типа к которому отнесены элементы массива, другими словами имя массива без индексов является адресом его первого элемента (с нулевым индексом).

Синтаксис: тип_указателя  имя_указателя = &имя_массива   или

                  тип_указателя  имя_указателя = &имя_массива[0]

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

int *px;  // объявлен указатель

px=&x или px=x  или  px= x[0] // инициализация указателя

Для организации доступа к элементам массива существует два доступа. Прямой доступ – это доступ через базовые переменные и доступ через указатели:
 *(х + i) или  *(pх + i).  Прибавление к указателю единицы обеспечивает переход к следующему элементу массива, независимо от типа его элементов.

При доступе к элементам массива через указатели выполняется два действия:

  • фиксируется базовый адрес массива, то есть первый элемент px=&x;
  • индекс i используется для вычисления смещения относительно базового адреса массива.

int a[4], *ptra, i;

….

ptra=a

for(i=0;i<4;i++)

cout<<”указатель =”<<i<<”:”<<(ptra + i)<<”\n”;

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

Ссылки

Ссылка – особый тип данных, являющийся скрытой формой указателя, который при использовании автоматически разыменовывается, т.е он может использоваться просто как другое имя или псевдоним объекта. При объявлении ссылки перед её именем ставится знак амперсанд &, а сама она должна быть проинициализирована именем того объекта, на который ссылается.

Ссылка – представляют собой видоизменённую форму указателя, которая используется в качестве псевдонима (другого имени) переменной.

Ссылки могут использоваться в следующих целях:

  • вместо передачи в функцию объекта по назначению;
  • для определения конструктора копий;
  • для перезагрузки унарных операций;
  • для предотвращения неявного вызова конструктора копий при передаче в функцию по значению объекта определённого пользователем класса.

Синтаксис:  тип &имя_ссылки = имя_переменной

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

Объявление неициализированной ссылки вызовет сообщение об ошибке.

char let = ‘a’;   // символьная переменная

char &ref = let;  // ссылка на символьную переменную

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

int I = 0;

int &rez = i;

rez +=10;   //  то же, что i +=10

Замечание:

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

Можно указывать операции над ссылкой, ни одна из них на саму ссылку не действует.

   int i = 0;

   int& rez = i;

   rez++;        // i увеличивается на 1

Здесь операция ++ допустима, но rez++ не увеличивает саму ссылку rez, вместо этого ++ применяется к целому, т.е. к переменной i.

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

#include<iostream.h>

int main()

  { int t = 13,

    &r = t; //инициализация ссылки на t

    //теперь r синоним имени t

     сout<<”Начальное значение t:“<< t;      //выводит 13

     r + = 10;                               //изменение значения t через ссылку

    cout<<“Конечное значение t: “<<t;       // выводит 23

    return 0;

  }

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

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

#include<iostream.h>

void fun_sqr(int &x); // прототип функции

int main()

{  int t = 3;

   сout<<”Начальное значение t:“<< t;        //выводит 3

   fun_sqr(t);

   cout<<“Конечное значение t: “<<t;        // выводит 9

   return 0;

}

void fun_sqr(int &x)

{x * = x;}

В приведённой программе при объявлении параметра – ссылки указывается, что в функцию будет передаваться адрес переменной. Это даёт возможность модифицировать аргумент (фактический параметр), не указывая в явном виде при вызове функции передачу в неё адреса. Другой особенностью является то, что внутри функции отпадает необходимость в использовании операции разыменования * при обращении к значению.

Нельзя возвращать ссылку (указатель) на локальную переменную, так как эта переменная перестаёт существовать в момент возврата из функции.

При применении переменных ссылочного типа накладывается ряд ограничителей:

  • нельзя взять адрес переменной ссылочного типа;
  • запрещается использовать массивы ссылок;
  • не допускается использовать ссылки на битовые поля;
  • нельзя создавать указатель на ссылку.
Суббота, 27.04.2024, 09:35
Приветствую Вас Гость