4390

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

Контрольная

Информатика, кибернетика и программирование

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

Русский

2012-11-18

57.5 KB

13 чел.

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

  1.  Указатели

Обычно программисту не нужно знать реальный адрес каждой переменной, поскольку компилятор способен сам позаботиться о таких подробностях. Но если необходимость в этой информации все же возникает, то получить ее можно с помощью оператора обращения к адресу: &. Например, если в программе используется переменная Var типа unsigned short, то набрав команду:

cout<<&Var;

мы получим результат: 0012FF7C – адрес этой переменной в шестнадцатеричном коде. На другом компьютере результат может получиться другим.

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

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

Предположим, что существует целочисленная переменная howOld. Чтобы объявить указатель на нее по имени pAge, применяется следующая форма записи:

int *pAge=0;

В результате будет получена переменная pAge, предназначенная для хранения адреса значения типа int. В этом примере переменная pAge инициализируется нулевым значением. Указатели, значения которых равны нулю, называются пустыми (null pointer). После объявления указателю обязательно нужно присвоить какое-либо значение. Если предназначенный для хранения в указателе адрес заранее не известен, ему следует присвоить нулевое значение. Неинициализированные указатели называются дикими (wild pointer). Они очень опасны. Не шутите с этим, всегда инициализируйте указатели!

Присвоить адрес переменной howOld указателю pAge можно следующим образом:

int howOld=50;

int *pAge=0;

pAge=&howOld;

 Две последние строки можно объединить в одну:

int howOld=50;

int *pAge=&howOld;

В указателях звездочка (*) используется двумя различными способами: при объявлении указателя и в операторе косвенного доступа. При объявлении указателя звездочка (*) является частью синтаксиса и располагается после указания типа объекта. Например:

// объявить указатель на тип int

int *pAge = 0;

При косвенном доступе звездочка означает, что речь идет о значении в памяти, находящемся по адресу в данной переменной, а не о самом адресе.

// разместить значение 5 по адресу, находящемуся в pAge

*pAge = 5;

Заметьте, что тот же символ (*) используется как оператор умножения. Что именно имел в виду программист, поставив звездочку, компилятор определяет, исходя из контекста.

Наиболее часто указатели применяются для манипулирования данными в динамически распределяемой памяти. Оперативная память компьютера состоит из следующих областей:

  •  область глобальных переменных;
  •  динамически распределяемая память;
  •  регистры;
  •  сегменты программного кода;
  •  стек.

Стек представляет собой память, организованную по принципу LIFO (“Last In, First Out” – «Последним вошел, первым вышел»). Локальные переменные размещаются в стеке. Код программы размещается в сегментах. Регистры используются для внутренних целей процессора, например, для контроля вершины стека и указателя команд. Остальная часть памяти составляет так называемую динамически распределяемую память, или heap (кучу).

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

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

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

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

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

int *pPointer = new int;

Теперь мы можем записать

  *pPointer=72;

что можно прочитать так: «Разместить число 72 в той области динамически распределяемой памяти, на которую указывает pPointer».

По завершении работы с выделенной областью памяти ее нужно освободить. Для этого применяется оператор delete, после которого следует имя указателя. Например:

delete pPointer;

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

Поэтому каждый раз, когда в программе используется оператор new, за ним должен следовать оператор delete. Наилучшим способом нажить себе неприятностей является переприсвоение указателя без предварительного освобождения участка памяти, на который он указывает. Это приводит к утечке памяти. Рассмотрим следующий фрагмент кода:

int *pPointer = new int;

*pPointer = 72;

pPointer = new int;

*pPointer = 84;

В первой строке объявляется указатель pPointer и выделяется память для хранения переменной типа int. В следующей строке в выделенную область записывается значение 72. Затем в третьей строке указателю присваивается адрес другой области памяти, в которую записывается число 84 (строка 4). Теперь исходный участок памяти, содержащий значение 72, оказывается недоступен, поскольку указателю на эту область было присвоено новое значение. В результате невозможно ни использовать, ни освободить зарезервированную память до завершения программы. Правильно этот фрагмент выглядел бы так:

int *pPointer = new int;

*pPointer = 72;

delete pPointer;

pPointer = new int;

*pPointer = 84;

  1.  Ссылки

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

При объявлении ссылки вначале указывается тип объекта адресата, за которым следуют оператор ссылки (&) и имя ссылки.

int &rSomeRef=someInt;

Это можно прочитать как «rSomeRef  является ссылкой на целочисленное значение, инициализированное адресом переменной someInt».

Оператор ссылки (&) выглядит так же, как и оператор возвращения адреса, который используется при работе с указателями. Но это не один и тот же оператор, хотя они очень похожи. Пробел перед оператором ссылки обязателен, а пробел между оператором ссылки и именем ссылки необязателен.

  1.  Передача аргументов функции по ссылке

Функции имеют два ограничения: аргументы передаются как значения и теряют связь с исходными данными, а возвращать функция может только одно значение. Передавая функции аргументы по ссылке, можно преодолеть оба ограничения. В языке С++ передача данных по ссылке осуществляется двумя способами: либо с помощью указателей, либо с помощью ссылок. Ниже приведены примеры реализации функции swap(), обменивающей значения двух переменных, с помощью указателей и с помощью ссылок.

Листинг 6.1. Реализация функции swap() для работы с указателями

void swap (int *px, int *py)

{

int temp;

temp = *px;

*px = *py;

 *py = temp;

}

Для вызова функции swap() из основной программы используется команда swap(&x, &y).

Листинг 6.2. Реализация функции swap() для работы со ссылками

void swap (int &rx, int &ry)

{

int temp;

temp = rx;

rx = ry;

ry = temp;

}

Для вызова функции swap() из основной программы используется команда swap(x, y).

Как можно видеть, при работе со ссылками функции swap() передаются именно значения x и y, а не их адреса. Таким образом, благодаря использованию ссылок функция приобретает новую возможность изменять исходные данные в вызывающей функции, хотя при этом сам вызов функции ничем не отличается от обычного.

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

  1.  Возвращение нескольких значений

Как уже говорилось, функции могут возвращать только одно значение. Если необходимо вернуть несколько, то решением этой проблемы является передача функции нескольких объектов по ссылкам. В этом случае можно обойтись без главного возвращаемого значения, которое (зачем же добру пропадать?) можно использовать для сообщений об ошибках.

Листинг 6.3. Возвращение из функций нескольких значений при помощи ссылок

#include <iostream.h>

enum ERR_CODE {SUCCESS, ERROR};

ERR_CODE Factor(int, int&, int&); //Объявление функции

int main() {

int number, squared, cubed;

ERR_CODE result;

cout << ”Enter a number (0 – 20); “;

cin >> number;

result = Factor (number, squared, cubed);

if (result == SUCCESS)

{

cout << “number: “ << number << endl;

cout << “squared: “ << squared << endl;

cout << “cubed: “ << cubed << endl;

}

else

cout << “Error encountered!!” << endl;

char res;

cin>>res;

}

// Реализация функции

ERR_CODE Factor(int n, int &rSquared, int &rCubed) {

if (n > 20)

return ERROR;

else

{

rSquared = n*n;

rCubed = n*n*n;

return SUCCESS;

}

}

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


 

А также другие работы, которые могут Вас заинтересовать

65510. СИСТЕМА МОДЕЛЮВАННЯ ПРОГНОЗУ РОСТУ ШТУЧНИХ СОСНОВИХ ЛІСОСТАНІВ ПОЛІССЯ УКРАЇНИ 365 KB
  Поглиблене вивчення закономірностей росту модальних деревостанів має особливе значення при проведенні безперервного лісовпорядкування оскільки воно враховує не лише сучасний стан насаджень а й їх зміну в динаміці.
65511. Індукція генних мутацій зразками копчених ковбасних виробів різних технологій виробництва 6.29 MB
  Мета дослідження –встановити закономірності мутагенної дії зразків копчених ковбасних виробів трьох технологій виробництва. Вивчити сумарну мутагенну дію зразків неорганічної і органічної фракцій варенокопчених ковбас ВК.
65512. ЧЕРВОНИХ СТОЛОВИХ ВИН НА ОСНОВІ ВИКОРИСТАННЯ ВУГЛЕКИСЛОТНОЇ МАЦЕРАЦІЇ 932 KB
  Найважливішою тенденцією розвитку сучасної виноробної галузі є підвищення якості та розширення асортименту виноградних вин. Основний об'єм столових вин України складають вина, які випускають без витримки і реалізують не раніше 1 січня наступного за врожаєм року.
65513. СПЕЦИФІКА ЛЮДСЬКОГО ІСНУВАННЯ У РЕЛІГІЙНІЙ ФІЛОСОФІЇ В. РОЗАНОВА 211 KB
  Розанова провидця що у своїх численних замальовках зумів передбачити багато епохальних у тому числі й філософських подій ХХ ст. Розанова актуально й для сучасної культури. Розанова як ідеолога національного і особистого самовизначення який шукав зміст народної душі...
65514. ДЕРЖАВНИЙ КОНТРОЛЬ ЗА ДІЯЛЬНІСТЮ МІЛІЦІЇ 184.5 KB
  Держава керуючись невідкладними завданнями які вона ставить перед собою в певний період розвитку має контролювати їхнє виконання консолідувати зусилля на подолання перешкод а також визначати і завдання контролю та механізм його здійснення.
65515. МЕТОД РОЗРАХУНКУ ТЕМПЕРАТУРНОГО НАПРУЖЕНО-ДЕФОРМОВАНОГО СТАНУ КОМПОЗИТНИХ СТРИНГЕРІВ ПАНЕЛЕЙ ОБШИВКИ 1.26 MB
  Наукова новизна одержаних результатів полягає у такому: уперше виявлено і обґрунтовано механізм виникнення згинально-крутильних деформацій композитних стержнів з неоднорідним перерізом при зміні температури та або внаслідок усадки...
65516. ФОРМУВАННЯ ДУХОВНО-ТВОРЧОГО ПОТЕНЦІАЛУ СТУДЕНТСЬКОЇ МОЛОДІ В ТРАНСФОРМАЦІЙНОМУ СУСПІЛЬСТВІ 154 KB
  Зрозуміло кожен з нас зацікавлений щоб цей вигляд був гуманістичним культурним людським і людяним і оскільки це так кожен зацікавлений у формуванні відповідних норм в свідомості та поведінці молоді у більш широкому розумінні...
65517. Методи та алгоритми диспетчеризації завдань у розподілених комп’ютерних системах 572.5 KB
  Сьогодні участь у міжнародних програмах стає стратегічним питанням інформаційного розвитку держав. Україна також стоїть на шляху науково-технічної інтеграції у європейський та світовий простір, про що говорить низка проектів, в яких Україна є повноправним учасником.