Указатели

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

  • переменных
  • констант
  • функций
  • других указателей

 

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

<тип> *<имя_переменной>[,*<имя_переменной>]...

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

 

Инициализация указателей

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

Указателю нельзя присвоить адрес переменной другого типа. То есть нельзя указателю типа int* присвоить адрес переменной типа double.

Также указателю можно присвоить значение другого указателя.

Указатель также может быть проинициализирован пустым значением. Это можно сделать несколькими способами:

  • использовать значение 0 или макроопределение NULL
  • использовать значение nullptr
  • использовать значение std::nullptr_t (C++ 11)

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

Тип std::nullptr_t может иметь только одно значение - nullptr. Использование этого типа поможет в тех редких случаях, когда существуют перегруженные функции и требуется передать нулевой указатель. В этом случае непонятно какую именно функцию нужно будет вызвать. Поэтому в таком случае в функции можно задать аргумент с типом std::nullptr_t.

Напрямую записать адрес в указатель можно только с помощью операций преобразования типов, либо операции reinterpret_cast

Пример кода:

int a = 0;
int *p = &a;
double v = 0.1;
double *pv = &v;
char *pc = nullptr;

 

Разыменование указателей

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

Пример кода:

int a = 123;
int *p = &a;
int b = *p;   // b присваивается значение 123

 

Арифметические действия с указателями

С указателем можно производить следующие арифметические действия:

  • сложение и вычитание с целым числом
  • операции инкремента/декремента

При использовании арифметических операций, указатель изменяется на величину кратную размеру типа указателя. Например, если указатель имеет тип 32-разрядного int, то увеличение указателя на 1 приведет к увеличению значения адреса в указателе на 4.

 

Указатель на указатель

В языке C++ можно объявить указатель, который будет указывать на другой указатель.

Синтаксис объявления такой же, как и у объявления указателя, за исключением того, что ставится два символа * (звездочка).

<тип> **<имя_переменной>[,**<имя_переменной>]...

Указатель на указатель работает подобно обычному указателю: его можно разыменовать для получения значения, на которое он указывает. И, поскольку этим значением является другой указатель, для получения исходного значения потребуется выполнить разыменование еще раз. Разыменования можно выполнять последовательно:

int value = 1234;
int *p = &value;
int **pp = &p;
int val = **pp;     // 1234

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

Язык C++ также позволяет работать с указателями на указатели на указатели, или сделать еще большую вложенность. Их можно объявлять просто увеличивая количество символов * (звездочек). Однако на практике такие указатели используются крайне редко.

 

Неконстантный указатель на неконстантное значение

int val1 = 10;
int val2 = 20;
int* ptr = &val1;
std::cout << *ptr << std::endl;    // 10
ptr = &val2;
std::cout << *ptr << std::endl;    // 20
*ptr = 30;
std::cout << *ptr << std::endl;    // 30

В этом случае можно изменять как сам указатель, так и значение, на которое он указывает.

 

Неконстантный указатель на константное значение

const int val1 = 10;
const int val2 = 20;
const int* ptr = &val1;
std::cout << *ptr << std::endl;    // 10
ptr = &val2;
std::cout << *ptr << std::endl;    // 20
*ptr = 30;    // Ошибка! Нельзя изменять константное значение
std::cout << *ptr << std::endl;

В этом случае указатель можно изменять. Но само значение, на которое он указывает изменять нельзя.

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

int val1 = 10;
int val2 = 20;
const int* ptr = &val1;
std::cout << *ptr << std::endl;    // 10
ptr = &val2;
std::cout << *ptr << std::endl;    // 20
*ptr = 30;    // Ошибка! Нельзя изменять константное значение
std::cout << *ptr << std::endl;

 

Константный указатель на неконстантное значение

int val1 = 10;
int val2 = 20;
int* const ptr = &val1;
std::cout << *ptr << std::endl;    // 10
*ptr = 30;
std::cout << *ptr << std::endl;    // 30
ptr = &val2;    // Ошибка! Нельзя изменять константный указатель
std::cout << *ptr << std::endl;

В этом случае можно изменять значение, на которое указывает указатель. Но нельзя изменять сам указатель.

Кроме того указатель при объявлении нужно сразу инициализировать.

 

Константный указатель на константное значение

int val1 = 10;
int val2 = 20;
const int* const ptr = &val1;
std::cout << *ptr << std::endl;
ptr = &val2;    // Ошибка! Нельзя изменять константный указатель
std::cout << *ptr << std::endl;
*ptr = 30;      // Ошибка! Нельзя изменять константное значение
std::cout << *ptr << std::endl;

В этом случае нельзя менять ни указатель, ни значение, на которое он указывает.

 

Ссылки

Ссылка - это тип переменной в языке C++, который работает как псевдоним другого объекта или значения. При объявлении ссылки перед её именем ставится символ амперсанда &. Сама же ссылка не может быть пустой, и должна быть обязательно проинициализирована именем переменной, на которую она ссылается. Изменить значение ссылки после инициализации невозможно.

<тип> &<имя_ссылки> = <имя_переменной>[, &<имя_ссылки> = <имя_переменной>]...

При создании ссылки на константную переменную, ссылка тоже должна быть создана как константная. Можно также создать константную ссылку на обычную переменную: в этом случае изменить переменную через ссылку не получится.

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

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

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

Пример кода:

int value = 123;
int &refval = value;
refval = 12345;
std::cout << value;     // 12345
const int a = 10;
const int &ref = a;

 

Ссылки r-value

В стандарте C++11 ввели новый тип ссылок - ссылки r-value. Ссылки r-value - это ссылки, которые инициализируются только значениями r-values. Объявляются такие ссылки, в отличие от обычных, с помощью двух символов амперсанда &&

<тип> &&<имя_ссылки> = <выражение r-value>[, &&<имя_ссылки> = <выражение r-value>]...

Ссылки r-value, в отличие от обычных ссылок, ссылаются не на постоянный, а на временный объект, созданный при инициализации ссылки r-value.

Такие ссылки обладают двумя важными свойствами:

  • продолжительность жизни объекта, на который ссылается ссылка увеличивается до продолжительности жизни самой ссылки
  • неконстантные ссылки r-value позволяют менять значение r-values, на который они ссылаются

Пример кода:

int &&ref = 10;
ref = ref + 20;
std::cout << ref;   // будет выведено 30

auto &&rref = std::string("test string");
rref.append(" append");
std::cout << rref << std::endl;   // будет выведено "test string append"

Ссылки r-value - позволяют избегать логически ненужного копирования и обеспечивать возможность идеальной передачи (perfect forwarding). Прежде всего они предназначены для использования в высокопроизводительных проектах и библиотеках.