• Уважаемые гости и новички, приветствуем Вас на нашем форуме
    Здесь вы можете найти ответы практически на все свои вопросы о серии игр «Готика» (в том числе различных модах на нее), «Ведьмак», «Ризен», «Древние свитки», «Эра дракона» и о многих других играх. Можете также узнать свежие новости о разработке новых проектов, восхититься творчеством наших форумчан, либо самим показать, что вы умеете. Ну и наконец, можете обсудить общие увлечения или просто весело пообщаться с посетителями «Таверны».

    Чтобы получить возможность писать на форуме, оставьте сообщение в этой теме.
    Удачи!
  • Друзья, доброго времени суток! Спешите принять участие в конкурсе "Таинственные миры" 2024!
    Ждем именно вас!

    Ссылка на конкурсную тему - тык

8.1 Указатели. Введение в основы.

Gratt


Модостроитель
Регистрация
14 Ноя 2014
Сообщения
3.276
Благодарности
4.579
Баллы
625
Вообще решил написать этот тред в попытке облегчить понимание указателей на начальном этапе знакомства с плюсами. По началу будет не очень просто, зато потом жить без указателей не сможете :D.

Поэтому попробую объяснить максимально примитивным языком. Освоение указателей на C/C++ равносильно взятию художником кисточки и красок. Только с этого момента программист начинает творить :). Не совру, если скажу, что весь движок состоит из указателей. И от их понимания зависит, сможет ли программист реализовать сложную программу под платформу ZenGin, да и вообще в целом.

Структура памяти
Для начала смоделируем, как будут выглядеть наши данные в памяти. Определим несколько переменных:
C++:
int a = 5;
int b = 10;
int c = 15;

В памяти у нас создадутся 3 ячейки, которые будут хранить значения этих переменных - a, b и c соответственно.
Из рисунка ниже видно, что идущие друг за другом значения имеют адреса (формата 0xXXXXXXXX). Каждый объект лежит строго в своем адресе.
1576174045171.png


Почему же адреса идут не [00, 01, 02], а [00, 04 08]? Все очень просто. Каждая из этих ячеек имеет размер хранения данных.
Размер ячейки определяется размером типа данных. Например любое число int имеет размер 4 байта, отсюда и объясняется такой интервал между ячейками.
Известно, что чем больше тип, тем больше информации он может вместить.


Формат указателя
И так, что такое указатель... Еще раз вернемся к переменным a, b, и c. Их имена - это по сути метки, которые скрывают за собой адрес ячейки с данными.
А указатель - это ячейка, хранящая адрес другой ячейки.
Добавим в наш код указатель на переменную b:
C++:
int a = 5;
int b = 10;
int c = 15;
// Указатель определяется символом '*' после типа.
// Символ'&' перез объектом берет адрес, в котором он лежит. Или, говоря грубо, строит на него указатель.
// Этот указатель мы записываем в p.
int* p = &b;

И тогда структура программы будет выглядеть так:
1576175869543.png


Заметим, что значение ячейки указателя повторяет адрес переменной b. Это значит, что указатель на переменную b построен корректно.
Также важно, что тип указателя должен совпадать с типом данных в ячейке памяти + '*', на которую он ссылается. В нашем случае это указатель int* на переменную int.


Построение указателей
Теперь о том, как обращаться к данным по казателю. Для этого представим, что символ '*' - это некий уровень адресации (как иерархия папочек в проводнике).
Чем больше '*' в указателе, тем выше его уровень адресации. Например создадим указатель на указатель (далее двойной указатель):

C++:
int a = 5;
int b = 10;
int c = 15;
int* p = &b;
int** pp = &p;

Структура такой программы:
1576176504242.png


Как я уже сказал выше, тип указателя строится как тип данных на который он ссылается + '*', для p это читается как int + *, а для pp - int* + *.
Благодаря указателю pp мы ссылаемся одновременно на p и b и можем в любой момент времени читать/изменять их данные.

Теперь непосредственно к обращению к данным. Для того, чтобы извлечь информацию, на которую ссылается указатель, необходимо провести обратную операцию, поставив звездочку с другой стороны от объекта.
C++:
// pp -> возвращает адрес p - 0x0000000C
// *pp -> возвращает адрес b - 0x00000004
// **pp -> возвращает значение b - 10

// &pp -> возвращает адрес на pp, то бишь указатель int***,
// например:
int* p = &b;
int** pp = &p;
int*** ppp = &pp;

Визуализация:
example1.gif



Примеры операций
Ну и как это работает на практике. Определим переменную и указатель на нее. Попытаемся изменить значение переменной через указатель.
C++:
int a = 100;
// Выводим на экран значение переменной a
cmd << "1. " << a << endl;

int* p = &a;
// Выводим на экран значение в заданном адресе
cmd << "2. " << *p << endl;

*p = 200;
// Выводим на экран значение переменной a !!!
cmd << "3. " << a << endl;

// Результаты выводов
// 1. 100
// 2. 100
// 3. 200

Видим, что через указатель мы можем изменять значение переменной a, а также в любой момент времени считывать ее значения.
Попробуем еще раз. Создадим два указателя на один и тот же объект.
C++:
int a = 100;
int* p1 = &a;
int* p2 = &a;
cmd << "1. " << a << endl;

*p1 += 20;
cmd << "2. " << a << endl;

*p2 += 30;
cmd << "3. " << a << endl;

// Результаты вывода:
// 1. 100
// 2. 120
// 3. 150

Результат эквивалентен предыдущему. Оба указателя способствуют изменению значений переменной a.
И напоследок реализуем изменение значения адреса указателя через двойной указатель.
C++:
int a = 100;
int b = 50;

// Указатель на 'а'
int* p = &a;

// Указатель на 'p'
int** pp = &p;

// Выводим значение переменной 'а' через указатель 'p'
cmd << "1. " << *p << endl;

// Изменяем значение адреса указателя p через указатель на него.
// Другими словами теперь 'p' будет ссылаться не на 'а', а на 'b'
*pp = &b;

// Выводим значение переменной 'b' через указатель 'p'
cmd << "2. " << *p << endl;

// Проверяем целостность переменных 'a' и 'b'
cmd << "3. a = " << a << ", b = " << b << endl;

// Результаты вывода:
// 1. 100
// 2. 50
// 3. a = 100, b = 50
 

neromont


Модостроитель
Регистрация
12 Мар 2011
Сообщения
674
Благодарности
655
Баллы
245
Это хорошо, а то у многих (у меня в частности) в свое время появлялись вопросы с этими указателями.
 
Сверху Снизу