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;
}
/////////////////////////////////////////////////////////////////////////////
|