Вказівники. Основні поняття

Уявіть собі ситуацію, ви йдете до когось у гості (друзі, родичі, колеги, тощо). Ви знаєте адресу, але коли прийшли на місце, то побачили багатоквартирний будинок. Вам не вдається зв’язатися з людиною щоб уточнити точну адресу. Тоді, залишається лише ходити та запитувати всіх де можна знайти тих людей.

Це приблизний опис як працюють змінні. Значення яке зберігає змінна це людина яку ви шукаєте, а знаходиться вона за якимось адресом в пам’яті комп’ютера. Щоб використати значення змінної комп’ютеру необхідно звернутися за її адресом.

Ви можете побачити адресу змінної, якщо при виведені інформації перед ім’ям змінної поставите символ

Якщо перед ім’ям змінної поставити комбінацію символів то ви отримаєте значення яке зберігається за адресом змінної.

    
            #include <iostream>
            int main()
            {
                int a = 5;

                //виводимо значення змінної
                std::cout << a << std::endl;

                //виводимо адресу змінної
                std::cout << &a << std::endl;

                //виводимо значення за адресом змінної
                std::cout << *&a << std::endl;

                return 0;
            }
        
    

Отже, тут варто узагальнити:

Якщо у вас є змінна і ви перед нею поставите символ то отримаєте її адресу.

Якщо у вас є адреса і перед нею поставити символ то ви отримаєте значення яке зберігається за даним адресом.

Відповідно, коли ви ставите перед ім’ям змінної, то програма сприймає це приблизно так: Тому, надає нам і після цього у нас виходить А вже як відомо, коли перед «адресом» стоїть то ми отримаємо «значення».

Маючи адресу якоїсь змінної ви отримуєте прямий доступ до її значення.

Щоб було простіше зрозуміти як це працює, то уявіть, що у вас є холодильник до вмісту якого ви має доступ з точки Землі. Для прикладу ви на а у холодильнику на вас чекають смаколики які ви можете взяти не встаючи з місця.

Отже, маючи адресу змінної ми можемо отримати доступ до її значення з точки програми.

Мова програмування надає можливість зберігати в якості змінної.

Змінні які зберігають в якості значень адреси інших змінних називаються «вказівниками».

Для того щоб створити вказівник вам потрібно:

1. Вказати тип вказівника. Цей тип має бути таким же як і тип змінної на яку він вказує.

2. Поставити символ

3. Вказати ім’я вказівника.

4. Ви можете ініціалізувати значенням вказівник. Для цього потрібно поставити Далі символ та вказати ім’я змінної на яку вказуємо. Або присвоїти значення пізніше за таким же принципом (тільки без типу вказівника та символа

Наприклад:


        Тип_вказівника *ім’я_вказівника = &ім’я_змінної_на_яку_вказуємо;
        

Або

    
            Тип_вказівника *ім’я_вказівника;
            ім’я_вказівника = &ім’я_змінної_на_яку_вказуємо;
        
    

При створені вказівників ви можете використовувати різні записи розміщення

    
            Тип_вказівника* ім’я_вказівника;
            Тип_вказівника * ім’я_вказівника;
            Тип_вказівника *ім’я_вказівника;
        
    

Розглянемо наступний приклад:

    
            #include <iostream>
            int main()
            {
                int a = 5;
                int *b; //створюємо вказівник
                b = &a; //присвоюємо йому адресу змінної"а"

                //значення змінної "а"
                std::cout << a << std::endl;

                //адреса змінної "а"
                std::cout << &a << std::endl;

                //виводимо значення яке зберігається у змінній "b"
                //тобто адресу змінної "а"
                std::cout << b << std::endl;

                //виводимо значення яке зберігається за адресою
                //яка є значенням (адреса змінної "а")
                std::cout << *b << std::endl;

                //виводимо адресу змінної "b"
                std::cout << &b << std::endl;

                return 0;
            }
        
    

Зверніть свою увагу на два рядки:


        std::cout << b << std::endl;
        

та


        std::cout << &b << std::endl;
        

Тут є вказівником, але виконуємо дії з ним як зі звичайною змінною. Тому, в першому рядку виводимо значення змінної (значення яке в собі зберігає вказівник а у другому адресу змінної (адресу ВКАЗІВНИКА).

    
            #include <iostream>
            int main()
            {
                int a = 5, b = 7;

                //створюємо константний вказівник
                //значення змінної на яку він вказує
                //змінити не можна
                const int *c = &a;

                //аналогічний запис
                //int const *c = &a;

                std::cout << "c = " << *c << std::endl;

                //будемо мати помилку, бо змінювати значення
                //змінної не можна
                //*c = 12;
                //std::cout << "c = " << *c << std::endl;

                //але можна змінити змінну на яку вказуватимемо
                c = &b;
                std::cout << "c = " << *c << std::endl;

                return 0;
            }
        
    

Завдяки вказівникам ви можете змінювати значення змінної на які вони вказують. Для цього потрібно вказати символ ім’я вказівника та присвоїти йому нове значення. Тоді, ми змінимо значення змінної на яку вказував вказівник.

    
            #include <iostream>
            int main()
            {
                int a = 5;
                int *c = &a;

                std::cout << "a = " << a << std::endl;

                //міняємо значення змінної "а" за
                //допомогою вказівника "с"
                *c = 8;
                std::cout << "a = " << a << std::endl;

                return 0;
            }
        
    

Тобто, вказівник є як другим ім’ям змінної. При цьому він дозволяє працювати зі значенням на пряму.

Ви можете створювати безліч вказівників на одну і туж змінну.

    
            #include <iostream>
            int main()
            {
                int a = 5;
                int *c = &a;
                int *e = &a;

                std::cout << "a = " << a << std::endl;
                std::cout << "*c = " << *c << std::endl;
                std::cout << "*e = " << *e << std::endl;

                return 0;
            }
        
    

Ви можете змінювати змінну на яку вказує вказівник (звісно, якщо він є не константним).

    
            #include <iostream>
            int main()
            {
                int a = 5;
                int c = 7;
                int *e = &a;

                std::cout << "*e = a = " << *e << std::endl;

                //змінюємо змінну на яку вказуватиме вказівник
                e = &c;
                std::cout << "*e = c = " << *e << std::endl;

                return 0;
            }
        
    

Також ви можете робити вказівник на вказівник. Але в такій ситуації потрібно збільшувати кількість символів Враховуючи, що це вже буде подвійний вказівник то потрібно ставити два символа при створенні та використанні.

    
            #include <iostream>
            int main()
            {
                int a = 5;
                int *c = &a;

                //робимо вказівник на вказівник
                //оскільки, "е" є подвійним вказівником,
                //то маємо поставити два символа "*"
                int **e = &c;

                std::cout << "a = " << a << std::endl;

                //міняємо значення змінної "а" за
                //допомогою вказівника "с"
                *c = 8;
                std::cout << "a = " << a << std::endl;

                //"е" є подвійним вказівником, тому міняючи
                //значення змінної "а" потрібно використовувати
                //два символа "*"
                **e = 11;
                std::cout << "a = " << a << std::endl;

                return 0;
            }
        
    

Якщо ви не плануєте більше використовувати свій вказівник, то його краще зробити нульовим. Тобто, щоб він більше не вказував ні на яку змінну. Це гарантуватиме, що ви більше не зміните значення змінної на яке вказував вказівник.

Для цього вам варто написати таке:


        Ім’я_вказівника = 0;
        

Або


        Ім’я_вказівника = nullptr;
        

Після цього ви не зможете його використовувати поки не присвоїте йому нове значення.

    
            #include <iostream>
            int main()
            {
                int a = 5;
                int c = 7;
                int *e = &a;

                std::cout << "*e = a = " << *e << std::endl;

                //робимо вказівник нульовим
                e = nullptr;

                //намагаємося скористатися нульовим вказівником
                //програма "зламається"
                //std::cout << "*e = a = " << *e << std::endl;

                //змінюємо змінну на яку вказуватиме вказівник
                //тому зможемо його використовувати
                e = &c;
                std::cout << "*e = c = " << *e << std::endl;

                return 0;
            }
        
    

Працюючи із вказівниками потрібно завжди бути обережним. Оскільки, деякі речі, що є нормальними для звичайних змінних з вказівниками можуть працювати не адекватно. Наприклад, скорочений запис одиниці з вказівниками працює доволі погано.

Наприклад:

    
            #include <iostream>
            int main()
            {
                int a = 5;
                int *b = &a;
                std::cout << "a = " << a << "\n*b = " << *b << std::endl; *b++;
                std::cout << "a = " << a << "\n*b = " << *b << std::endl;

                return 0;
            }
        
    

При цьому інші скорочені записи такі як працюють добре.

Наприклад:

    
            #include <iostream>
            int main()
            {
                int a = 6;
                int *b = &a;
                std::cout << "a = " << a << "\n*b = " << *b << std::endl;
                *b /= 2;
                std::cout << "a = " << a << "\n*b = " << *b << std::endl;

                return 0;
            }
        
    

Для гарантії ви можете використовувати довгий запис. Тобто, рядок можна замінити Аналогічно, можна замінювати й інші скорочені записи.