3.6. Разница между указателями и ссылками - StriderAJR/StudentCpp GitHub Wiki

Рассматриваемые темы:

Разница между указателями и ссылками

Рассмотрим подробнее разницу применения указателей и ссылок на примере передачи параметров в функцию. Создадим 3 ф-ции.

   void funcWithValue(int a)
   {
     a++;
   }

   int var1 = 1;  
   funcWithValue(var1);
   void funcWithReference(int& a)
   {
     a++;
   }

   int var2 = 1;
   funcWithReference(var2);
   void funcWithPointer(int* a)
   {
     (*a)++;
   }

   int var3 = 1;  
   funcWithPointer(&var3);

   int var4 = 1;
   nt* ptr = &var4;
   funcWithPointer(ptr);

Ф-ция с передачей параметра по значению

void funcWithValue(int a)

При передаче параметра по значению создается копия переменной. Допустим, вызов ф-ции выглядит примерно так:

int var1 = 1;
  
funcWithValue(var1);

Тогда когда ф-ция вызовется состояние памяти будет следующее:

Tabl3

Изначальная переменная var1 была скопирована, а новой ячейке было присвоено имя параметра - a.

   a++;

Переменная а была увеличена на 1. А изначальная переменная осталась без изменений.

Tabl4

Ф-ция завершилась и все ее локальные переменные, которой и является параметр ф-ции уничтожаются.

Tabl5

Изначальная переменная никак не изменилась.

Ф-ция с передачей параметра по ссылке

void funcWithReference(int& a)

При передаче параметра по ссылке изначальной переменной дается альтернативное имя. Допустим, вызов ф-ции выглядит примерно так:

int var2 = 1;
   
funcWithReference(var2);

Тогда когда ф-ция вызовется состояние памяти будет следующее:

Tabl6

Изначальная переменная var2 получила второе имя - а, через которое к ней можно обращаться

      a++;

Переменная а была увеличена на 1.

Tabl7

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

Tabl8

Второе имя у ячейки пропало. Но значение-то уже было изменено.

Ф-ция с параметром-указателем

void funcWithPointer(int* a)

Как думаете, какая это передача параметра: по ссылке или по значению? Это просто. Ответьте на другой вопрос: Параметр является ссылкой? Ответ: нет. Значит, это передача параметра по значению! Теперь допустим, что вызов ф-ции выглядит следующий образом:

 int var3 = 1;
   
 funcWithPointer(&var3);

Чтобы вызов был корретным и совпали типы параметра и передаваемого значения, нам пришлось использовать оператор & - взятие адреса у переменной. Т.е. в памяти это выглядит следующим образом:

Tabl9

Где число 0xFFF1 - адрес переменной var3.

А теперь давайте посмотрим, если вызов ф-ции выглядел по-другому:

 int var4 = 1;
 
 int* ptr = &var4;
 
 funcWithPointer(ptr);

Тогда после вызова ф-ции состояние памяти следующее:

Tabl0

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

      (*a)++;

Значение ячейки, находящейся по адресу из указателя а, было увеличено на 1.

Tabl11

Ф-ция завершилась. Уничтожение локальных переменных.

Tabl12

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

void funcWithPointerReference(int*& a)

На первый взгляд заголовок этой ф-ции абсолютно такой же, как и в преедыдущем варианте funcWithPointer. Да, такое же. За искллюючением того, что указатель здесь - ссылка. Ага. Указателль и ссылка в одном флаконе. Допустим, вызов будет таким:

int var4 = 1;
    
int* ptr = &var4;
     
funcWithPointer(ptr);

Тогда в памяти получим:

Tabl13

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

     (*a)++;

Теперь надеюсь всем предельно ясно, что ссылки и указатели совершенно разные в применении механизмы языка С++. Поэтому не надо их путать.

void main()
   {
      int var1 = 1;
      funcWithValue(var1);

      int var2 = 1;
      funcWithReference(var2);

      int var3 = 1;
      funcWithPointer(&var3);

      int var4 = 1;
      int* ptr = &var4;
      funcWithPointer(ptr);
   }
}
⚠️ **GitHub.com Fallback** ⚠️