Я только недавно начал изучать C ++, и, как и большинство людей (согласно тому, что я читал), я борюсь с указателями.
Не в традиционном смысле, я понимаю, что это такое, и почему они используются, и как они могут быть полезны, однако я не могу понять, насколько полезны инкрементные указатели, может ли кто-нибудь дать объяснение того, как инкрементный указатель является полезная концепция и идиоматический C ++?
Этот вопрос возник после того, как я начал читать книгу Bjarne Stroustrup « Путешествие по C ++ », мне порекомендовали эту книгу, потому что я хорошо знаком с Java, и ребята из Reddit сказали мне, что это будет хорошая книга «переключения» ,
Ответы:
Когда у вас есть массив, вы можете установить указатель, указывающий на элемент массива:
Здесь
p
указывает на первый элементa
, который являетсяa[0]
. Теперь вы можете увеличить указатель, чтобы он указывал на следующий элемент:Теперь
p
указывает на второй элементa[1]
. Вы можете получить доступ к элементу здесь, используя*p
. Это отличается от Java, где вы должны использовать целочисленную индексную переменную для доступа к элементам массива.Увеличение указателя в C ++, где этот указатель не указывает на элемент массива, является неопределенным поведением .
источник
Инкрементные указатели - это идиоматический C ++, потому что семантика указателей отражает фундаментальный аспект философии проектирования, лежащей в основе стандартной библиотеки C ++ (на основе STL Александра Степанова )
Важной концепцией здесь является то, что STL разработан вокруг контейнеров, алгоритмов и итераторов. Указатели - это просто итераторы .
Конечно, способность увеличивать (или добавлять / вычитать) указатели восходит к C. Многие алгоритмы манипуляции с C-строками могут быть написаны просто с использованием арифметики указателей. Рассмотрим следующий код:
Этот код использует арифметику указателей для копирования C-строки с нулевым символом в конце. Цикл автоматически завершается, когда встречается с нулем.
В C ++ семантика указателей обобщается на концепцию итераторов . Большинство стандартных контейнеров C ++ предоставляют итераторы, к которым можно получить доступ через функции-члены
begin
иend
. Итераторы ведут себя как указатели в том смысле, что их можно увеличивать, разыменовывать, а иногда уменьшать или расширять.Чтобы перебрать
std::string
, мы бы сказали:Мы увеличиваем итератор так же, как мы увеличиваем указатель на обычную C-строку. Причина, по которой эта концепция является мощной, заключается в том, что вы можете использовать шаблоны для написания функций, которые будут работать для любого типа итератора, который соответствует необходимым требованиям концепции. И это сила STL:
Этот код копирует строку в вектор.
copy
Функция представляет собой шаблон , который будет работать с любым итератора , который поддерживает увеличивающимся (который включает в себя простые указатели). Мы могли бы использовать ту жеcopy
функцию на простой C-строке:Мы могли бы использовать
copy
вstd::map
илиstd::set
или любом другом контейнере, который поддерживает итераторы.Обратите внимание, что указатели представляют собой особый тип итератора: итератор с произвольным доступом , что означает, что они поддерживают увеличение, уменьшение и продвижение с помощью оператора
+
and-
. Другие типы итераторов поддерживают только подмножество семантики указателей: двунаправленный итератор поддерживает как минимум увеличение и уменьшение; а вперед итераторы поддерживает , по меньшей мере , приращение. (Все типы итераторов поддерживают разыменование.) Дляcopy
функции требуется итератор, который, по крайней мере, поддерживает инкремент.Вы можете прочитать о различных концепциях итераторов здесь .
Таким образом, инкрементные указатели являются идиоматическим способом C ++ для итерации по C-массиву или доступа к элементам / смещениям в C-массиве.
источник
Арифметика указателей в C ++, потому что она была в C. Арифметика указателей в C, потому что это нормальная идиома в ассемблере .
Существует множество систем, в которых «регистр приращения» быстрее, чем «загрузить постоянное значение 1 и добавить в регистр». Более того, довольно много систем позволяют вам «загрузить DWORD в A с адреса, указанного в регистре B, а затем добавить sizeof (DWORD) к B» в одной инструкции. В наши дни вы можете ожидать, что оптимизирующий компилятор решит эту проблему за вас, но в 1973 году такой возможности не было.
По сути, это та же самая причина, по которой массивы C не проверяются по границам, а строки C не имеют встроенного размера: язык разрабатывался в системе, где учитывались каждый байт и каждая инструкция.
источник