Вы находитесь на странице: 1из 6

ОСНОВЫ ПРОГРАММИРОВАНИЯ

НА ЯЗЫКЕ C++
Лапшун Татьяна Степановна
Lapshun_T@itstep.org

Тема 26
ПОНЯТИЕ ССЫЛКИ. ПЕРЕДАЧА АРГУМЕНТОВ
ВНУТРЬ ФУНКЦИИ.
ПОНЯТИЕ СТЕКА И ДИНАМИЧЕСКОЙ ПАМЯТИ

Понятие ссылки
Во многих языках программирования указатель присутствует, но в
завуалированном виде в форме ссылки. Под ссылкой понимается переменная,
которая не имеет самостоятельного значения, а отображается на другую
переменную, т.е. является ее синонимом. Во всем остальном она не
отличается от обычной переменной. В отличие от явного указателя
обращение по ссылке к объекту-прототипу имеет тот же самый синтаксис,
что и обращение к объекту-прототипу.
Ссылка – неявный указатель, имеющий синтаксис указуемого
объекта (синоним).  В Си имеется возможность определения такого
синонима с использованием символа &:
int a=5;             // Переменная – прототип
int &b=a;           // Переменная b – ссылка на переменную a
b++;                 // Операция над b есть операция над прототипом a

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


работаем напрямую с переменной. Можно свободно получать значение
переменой и менять его. Определив ссылку однажды ее значение нельзя
заменить в процессе работы. все операции со ссылками производятся над
объектом на который она ссылается. константная ссылка может ссылаться на
объект другого типа или любую другую величину.
int x=10;
int &rx=x;
int &ry=10; //не правильно
int &rz; //не правильно)
const int &rt=10;

Наиболее употребительным в Си, а в других языках – единственно


возможным является использование ссылки как формального параметра
функции. Это означает, что при вызове функции формальный параметр
создается как переменная-ссылка, который отображается на
соответствующий фактический параметр. Формальный параметр –ссылка
аналогично отмечается в списке параметров значком & перед именем. В
остальном синтаксис работы с параметром-ссылкой и параметром-значением
идентичны. Различие заключается в способе взаимодействия с фактически
параметром:
 при передаче по значению формальный параметр является копией
фактического, он может быть изменен независимо от значения
оригинала –  фактического параметра. Такой параметр является
исключительно входным;
 при передаче по ссылке формальный параметр отображается на
фактический, и его изменение сопровождается изменением
фактического параметра-прототипа. Такой параметр может быть
как входным, так и выходным.

Таким образом, в Си возможны целых три способа передачи


параметра:
 по значению
 по ссылке
 с использованием явного указателя.

Формальный параметр-ссылка совпадает с формальным параметром-


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

Передача параметров по значению, указателю и ссылке

//------------------------------------------------------------------------------------------
// Формальный параметр - значение
void inc(int vv){ vv++; }                 // Передается значение - копия nn
void main(){ int nn=5; inc(nn); }    // nn=5
//------------------------------------------------------------------------------------------
// Формальный параметр - указатель
void inc(int *pv) { (*pv)++; }          // Передается указатель - адрес nn
void main(){ int nn=5; inc(&nn); }  // nn=6
//------------------------------------------------------------------------------------------
// Формальный параметр - ссылка
void inc (int &vv) { vv++; }             // Передается указатель - синоним nn
void main(){ int nn=5; inc(nn); }    // nn=6
 //------------------------------------------------------------------------------------------

Передача аргументов внутрь функции

Обычно функция возвращает одно значение. А как вернуть больше


одного? Рассмотрим код функции, которая меняет местами две переменные.
int swap(double a, double b) {
double temp = a;
a = b;
b = temp;
}
Пусть есть переменные x и y с некоторыми значениями. Если выполнить
функцию, передав в нее x и y, окажется, что никакого обмена не произошло.
И это правильно.
При вызове этой функции в стеке будут сохранены значения x и y. Далее
a и b получат значения x и y. Будет выполнена перестановка. Затем функция
завершится и значения x и y будут восстановлены из стека. Все по-честному.
Чтобы заставить функцию работать так, как нужно, следует передавать в
нее не значения переменных x и y, а их адреса. Но и саму функцию тогда
нужно адаптировать для работы с адресами.
void swap(double* a, double* b) {
double temp = *a;
*a = *b;
*b = temp;
}
Не стоить забывать о том, что и вызов функции теперь должен
выглядеть иначе.
swap(&x, &y);
Теперь в функцию передаются адреса. И работа ведется относительно
переданных адресов.
Если функция должна вернуть несколько значений, необходимо
передавать в нее адреса.
Если функция должна менять значение переменной, нужно передавать
ей адрес этой переменной.
 Функция swap() была хороша, только не слишком удобно применять
разыменование. С помощью ссылок функция swap() может выглядеть
аккуратнее.
#include <stdio.h>
void swap(double& a, double& b) {
double temp = a;
a = b;
b = temp;
}
А вызов функции тогда будет уже без взятия адреса переменных.
swap(x, y);
Конструкция double& объявляет ссылку на переменную типа double.
При таком объявлении функции в стек будут положены не значения
переменных, а их адреса.
Ссылка — это указатель, с которым можно работать, как с обычной
переменной. Ссылка не может быть равна NULL. Указатель может. Ссылка
не может быть непроинициализирована. Указатель может.

Статическая память
Место в статической памяти — резервируется в момент запуска
программы и освобождается только после того, как программа завершит
свою работу.
Туда попадают глобальные переменные (объявленные вне любых
блоков, в частности, до начала метода main) и статические переменные
(перед типом которых указывается ключевое слово static, при этом
переменная может находится где угодно, в том числе и в теле какой-то
функции, но вести статическая переменная внутри функции себя будет
специфически: сохранять своё значения между повторными вызовами
функции). Разница между статической и глобальной переменной
проявляется, когда программа состоит из нескольких файлов: глобальные
переменные доступны в смежных файлах, а статические — только в том
файле, где были объявлены.
В статической памяти не рекомендуется держать длинные объекты
(например, массивы).

Локальная память (стековая память)


Выделяется при входе в блок программы, освобождается — при выходе
из блока.
Туда попадают переменные объявленные внутри блоков (в частности, в
теле функций).

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

Выделяется память с помощью оператораnew, а освобождается — с


помощью оператораdelete.
В момент, когда динамическая память выделена, она должна быть
связана с некоторым указателем, подходящего типа (при выделении
указывается тип и количество необходимых ячеек данного типа).
int* p;
p =newint;
*p=10;
cout<<*p;// 10
deletep;// памятьосвобождена

Если не освобождать динамическую память, то она будет занята до


завершения программы, что неприемлемо.
При выделении одной динамической переменной (одной ячейки
памяти), можно сразу инициализировать её значение:
int* p;
p =newint(10);
cout<<*p;// 10
deletep;// памятьосвобождена

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


динамический массив. Для этого его размер указывается в квадратных
скобках после типа. Чтобы удалить динамический массив и освободить
память используется операторdelete[].
int* p;
p =newint[13];
for(int i=0; i<13; i++)
{
*(p+i)= i +1;
cout<<*(p+i)<<' ';// 1 2 3 ... 13
}
delete[] p;// память освобождена, из неё удалены все элементы

Cразу после создания динамический массив автоматически заполняется


нулями (в отличии от обычного массива в статической или стековой памяти).
Если в указатель, уже хранящий адрес какого-то фрагмента
динамической памяти, записать новый адрес, то фрагмент динамической
памяти будет потерян, т. е. он не будет освобождён, но к нему никак нельзя
будет обратиться (например, чтобы освободить этот фрагмент).
int* p;
p =newint(13);
int a =666;
p =&a;// теперь до 13 никак не добраться

Проблема становится особенно острой, когда в памяти теряются целые


массивы (они занимают больше места, чем отдельные переменные).
int* p;
for(int i=1; i<=10;i++)
{
p =newint[100];
}
delete[] p;

На каждом шаге цикла создаётся динамический массив из 100


элементов. Всего таких массивов будет создано 10, но только от последнего
из них память будет освобождена после выхода из цикла. 9 массивов
продолжат занимать место в памяти до конца программы. 9 массивов * 100
элементов * 4 байта = 3600 байт потерянной памяти, которую никак нельзя
использовать (ни в этой программе, не в других запущенных).
Очень важно после использования динамической памяти не забывать
освобождать её в нужный момент!

Вам также может понравиться