вернуться в оглавление предыдущая глава предыдущий параграф следующий параграф следующая глава


1.11. Ссылки и указатели

1.11.1. Ссылки

Ссылка (referenсe) - это переменная особого вида, которая представляет собой альтернативное имя (псевдоним, alias) другой переменной:

int &a = b; 

В данном примере a - новое альтернативное имя для переменной b. a и b ссылаются на одно и то же место в памяти.

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

/////////////////////////////////////////////////////////////////////////////
// Прикладное программирование
// Пример 14. Ссылки
// 
// Кафедра Прикладной и компьютерной оптики, http://aco.ifmo.ru
// Университет ИТМО
/////////////////////////////////////////////////////////////////////////////
// подключение библиотеки ввода-вывода
#include <iostream>
// подключение стандартного пространства имен для использования библиотек
using namespace std;

/////////////////////////////////////////////////////////////////////////////
// функция main начинает исполнение программы 
void main()
{
    //----------------------
    int i=1;   // объявление переменной
    int &r=i;  // объявление и инициализация ссылки на переменную

    // вывод на экран значений переменной и ссылки на нее
    cout<<i<<endl; // 1
    cout<<r<<endl; // 1
    cout<<endl;

    // вывод на экран адресов переменной и ссылки на нее
    cout<<&i<<endl; // 0x0012ff6c
    cout<<&r<<endl; // 0x0012ff6c
    cout<<endl;
    //----------------------
    i+=5;  // изменение переменной

    // вывод на экран значений переменной и ссылки на нее
    cout<<i<<endl; // 6
    cout<<r<<endl; // 6
    cout<<endl;
    //----------------------
    i-=5;  // изменение переменной

    // вывод на экран значений переменной и ссылки на нее
    cout<<i<<endl; // 1
    cout<<r<<endl; // 1
    cout<<endl;
    //----------------------
    r=0;  // изменение ссылки

    // вывод на экран значений переменной и ссылки на нее
    cout<<i<<endl; // 0
    cout<<r<<endl; // 0
    cout<<endl;
}
/////////////////////////////////////////////////////////////////////////////

1.11.2. Указатели

На хранение адреса в современных вычислительных системах отводится 32 бита (4 байта) или 64 бита (8 байт). Это значит, что теоретически программы могут управлять памятью объемом около 4096 Мб (а для 64-разрядных систем - около 16777215 Тб). Для работы с памятью каждая из её ячеек нумеруется.

Пусть для хранения переменной i типа int отводится 4 байта. Тогда структура хранения этой переменной в оперативной памяти может иметь следующий вид:

адрес ячейки

байт памяти

......

.........

0x00A0

00000000

0x00A1

00000000

0x00A2

00000000

0x00A3

00000001

0x00A4

.........

.......

..........

Указатель (pointer) - это переменная особого вида, предназначенная для хранения адреса объекта (адреса ячейки памяти, в которых хранится значение переменной). Обычно переменная непосредственно содержит некоторое значение. Указатель же содержит адрес переменной, которая, в свою очередь, содержит значение. Диапазон значений для любого указателя включает специальный адрес 0 (null) и диапазон положительных целых чисел, который интерпретируется как машинные адреса.

Для получения значения переменной хранящейся в ячейке, на которую указывает указатель, используется оператор разыменования (*).

Объявление указателя

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

 int *p; 

что означает, что переменная p имеет тип int * (т.е. указатель на int) и указывает на объект типа int.

Если в списке объявляется несколько переменных, перед каждой переменной, объявляемой как указатель, должна предшествовать звездочка (*):

 double *xPtr, *yPtr; 

В данном случае переменные xPtr, и yPtr являются указателями на значения типа double. Когда звездочка появляется в объявлении, она не является операцией разименования, она только сообщает, что переменная объявляется как указатель.

Присваивание указателя

Для получения адреса ячейки памяти, в которой хранится переменная, и последующей инициализации переменной типа указатель используется операция получения адреса (&).

int i=1; // объявление целой переменной i 
int *p;  // объявление переменной типа указатель, т.е. значение по адресу p является целым числом 
p=&i;    // p=0x00A0 (оператор получения адреса) 

В данном примере, значение переменной p представляет собой адрес ячейки (указатель на то место в памяти), в которой хранится значение переменной i.

Операция разименования

Для получения значения переменной хранящейся в ячейке, на которую указывает указатель, используется оператор разыменования (*). Если нужно использовать значение переменной, хранящейся по адресу p, перед p нужно ставить оператор разыменования:

int i=1;    // объявление целой переменной i 
int *p ;    // объявление переменной типа указатель, т.е. значение по адресу p является целым числом 
p=&i;       // p=0x00A0 (оператор получения адреса) 
int t=*p+1; // эквивалентно t=i+1; 
p++;        // прибавляем 1 к указателю – переходим к следующей ячейке памяти с другой переменной

Рассмотрим пример работы с указателем.

/////////////////////////////////////////////////////////////////////////////
// Прикладное программирование
// Пример 15. Указатели
// 
// Кафедра Прикладной и компьютерной оптики, http://aco.ifmo.ru
// Университет ИТМО
/////////////////////////////////////////////////////////////////////////////
// подключение библиотеки ввода-вывода
#include <iostream>
// подключение стандартного пространства имен для использования библиотек
using namespace std;

/////////////////////////////////////////////////////////////////////////////
// функция main начинает исполнение программы 
void main()
{
    //----------------------
    int i=1;   // объявление переменной
    int* p;    // объявление указателя на переменную
    p=&i;      // инициализация указателя на переменную

    // вывод на экран значения переменной
    cout<<i<<endl;   // 1
    // вывод на экран ссылки
    cout<<p<<endl;   // 0x0012ff6c
    // вывод на экран значения переменной, на которую указывает указатель
    cout<<*p<<endl;  // 1
    cout<<endl;
    //----------------------
    i+=5;    // изменение переменной

    cout<<i<<endl;   // 6
    cout<<*p<<endl;  // 6
    cout<<endl;
    //----------------------
    *p-=5;   // изменение переменной, хранящейся по указателю

    cout<<i<<endl;   // 1
    cout<<*p<<endl;  // 1
    cout<<endl;
    //----------------------
    p++;     // изменение указателя (смещение в памяти на следующюу переменную)

    cout<<i<<endl;   // 1     (значение переменной не изменилось)
    cout<<p<<endl;   // 0x0012ff70    (адрес ячейки памяти изменился)
    cout<<*p<<endl;  // ????  (что хранится по новому адресу - неизвестно)
    cout<<endl;
    //----------------------
    p=0;    // изменение указателя (указатель никуда не указывает)

    cout<<p<<endl;   // 0x00000000
    cout<<*p<<endl;  // ошибка !!!  по этому адресу нет никаких значений
    cout<<endl;
}

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

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

  • Передача по значению (call by value). Когда аргумент передается по значению, в вызываемой функции создается копия аргумента, и изменения копии внутри функции не влияют на значение исходной переменной вызывающей функции.
  • Передача по указателю (call by pointer). Не значения копируются в параметры, а передаются указатели на переменные, в которых хранятся значения переменных.
  • Передача по ссылке (call by reference). Самый удобный способ, когда передаются ссылки (псевдонимы) на переменные. Этот способ позволяет обходиться без оператора разыменования.
/////////////////////////////////////////////////////////////////////////////
// Прикладное программирование
// Пример 16. Передача аргументов в функцию по значению, по ссылке, по указателю 
// 
// Кафедра Прикладной и компьютерной оптики, http://aco.ifmo.ru
// Университет ИТМО
/////////////////////////////////////////////////////////////////////////////
// подключение библиотеки ввода-вывода
#include <iostream>
// подключение стандартного пространства имен для использования библиотек
using namespace std;

// прототипы функций 
void swap_by_value(int i, int j);
void swap_by_reference(int &i, int &j);
void swap_by_pointer(int *i, int *j);
/////////////////////////////////////////////////////////////////////////////
// функция main начинает исполнение программы 
void main()
{
    int i1=5, i2=5, i3=5;
    int j1=7, j2=7, j3=7;
    swap_by_value(i1, j1);      // передача параметров в функцию по значению
    cout<<"main - swap_by_value: "<<i1<<" "<<j1<<endl;  

    swap_by_reference(i2, j2);  // передача параметров в функцию по ссылке
    cout<<"main - swap_by_reference: "<<i2<<" "<<j2<<endl; 

    swap_by_pointer(&i3, &j3);  // передача параметров в функцию по указателю
    cout<<"main - swap_by_pointer: "<<i3<<" "<<j3<<endl; 
} 
/////////////////////////////////////////////////////////////////////////////
// параметры i,j передаются в функцию по значению
void swap_by_value(int i, int j)
{
    int t=i;
    i=j;
    j=t;
    cout<<"swap_by_value: "<<i<<" "<<j<<endl; 
}
/////////////////////////////////////////////////////////////////////////////
// параметры i,j передаются в функцию по ссылке
void swap_by_reference(int &i, int &j)
{
    int t=i;
    i=j;
    j=t;
    cout<<"swap_by_reference: "<<i<<" "<<j<<endl; 
} 
/////////////////////////////////////////////////////////////////////////////
// в функцию передаются указатели на параметры i,j
void swap_by_pointer(int *i, int *j)
{
    int t=*i;
    *i=*j;
    *j=t;
    cout<<"swap_by_pointer: "<<*i<<" "<<*j<<endl;
} 
/////////////////////////////////////////////////////////////////////////////