Использование указателей в выражениях



 

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

 

Арифметические операции над указателями

 

С указателями можно использовать только четыре арифметических оператора: ++ , ‑‑ , + и . Чтобы лучше понять, что происходит при выполнении арифметических действий с указателями, начнем с примера. Пусть p1 – указатель на int‑переменную с текущим значением 2 ООО (т.е. p1 содержит адрес 2 ООО). После выполнения (в 32‑разрядной среде) выражения

 

 

содержимое переменной‑указателя p1 станет равным 2 004 , а не 2 001 ! Дело в том, что при каждом инкрементировании указатель p1 будет указывать на следующее int‑значение. Для операции декрементирования справедливо обратное утверждение, т.е. при каждом декрементировании значение p1 будет уменьшаться на 4 . Например, после выполнения инструкции

 

 

указатель p1 будет иметь значение 1 996 , если до этого оно было равно 2 000 . Итак, каждый раз, когда указатель инкрементируется, он будет указывать на область памяти, содержащую следующий элемент базового типа этого указателя. А при каждом декрементировании он будет указывать на область памяти, содержащую предыдущий элемент базового типа этого указателя.

Для указателей на символьные значения результат операций инкрементирования и декрементирования будет таким же, как при "нормальной" арифметике, поскольку символы занимают только один байт. Но при использовании любого другого типа указателя при инкрементировании или декрементировании значение переменной‑указателя будет увеличиваться или уменьшаться на величину, равную размеру его базового типа.

Арифметические операции над указателями не ограничиваются использованием операторов инкремента и декремента. Со значениями указателей можно выполнять операции сложения и вычитания, используя в качестве второго операнда целочисленные значения. Выражение

 

 

заставляет p1 ссылаться на девятый элемент базового типа указателя p1 относительно элемента, на который p1 ссылался до выполнения этой инструкций.

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

Помимо сложения указателя с целочисленным значением и вычитания его из указателя, а также вычисления разности двух указателей, над указателями никакие другие арифметические операции не выполняются. Например, с указателями нельзя складывать float‑ или double‑значения.

Чтобы понять, как формируется результат выполнения арифметических операций над указателями, выполним следующую короткую программу. Она выводит реальные физические адреса, которые содержат указатель на int ‑значение (i ) и указатель на float ‑значение (f ). Обратите внимание на каждое изменение адреса (зависящее от базового типа указателя), которое происходит при повторении цикла. (Для большинства 32‑разрядных компиляторов значение i будет увеличиваться на 4, а значение f – на 8.) Отметьте также, что при использовании указателя в cout‑инструкции его адрес автоматически отображается в формате адресации, применяемом для текущего процессора и среды выполнения.

 

 

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

 

 

Узелок на память. Все арифметические операции над указателями выполняются относительно базового типа указателя.

 

Сравнение указателей

 

Указатели можно сравнивать, используя операторы отношения == , < и > . Однако для того, чтобы результат сравнения указателей поддавался интерпретации, сравниваемые указатели должны быть каким‑то образом связаны. Например, если p1 и р2 – указатели, которые ссылаются на две отдельные и никак не связанные переменные, то любое сравнение p1 и р2 в общем случае не имеет смысла. Но если p1 и р2 указывают на переменные, между которыми существует некоторая связь (как, например, между элементами одного и того же массива), то результат сравнения указателей p1 и р2 может иметь определенный смысл. Ниже в этой главе мы рассмотрим пример программы, в которой используется сравнение указателей.

 

Указатели и массивы

 

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

 

 

Здесь str представляет собой имя массива, содержащего 80 символов, a p1 – указатель на тип char . Особый интерес представляет третья строка, при выполнении которой переменной p1 присваивается адрес первого элемента массива str . (Другими словами, после этого присваивания p1 будет указывать на элемент str[0] .) Дело в том, что в C++ использование имени массива без индекса генерирует указатель на первый элемент этого массива. Таким образом, при выполнении присваивания p1 = str адрес stг[0] присваивается указателю p1 . Это и есть ключевой момент, который необходимо четко понимать: неиндексированное имя массива, использованное в выражении, означает указатель на начало этого массива.

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

Поскольку после рассмотренного выше присваивания p1 будет указывать на начало массива str , указатель p1 можно использовать для доступа к элементам этого массива. Например, если нужно получить доступ к пятому элементу массива str , используйте одно из следующих выражений:

 

 

В обоих случаях будет выполнено обращение к пятому элементу. Помните, что индексирование массива начинается с нуля, поэтому при индексе, равном четырем, обеспечивается доступ к пятому элементу. Точно такой же эффект производит суммирование значения исходного указателя (p1 ) с числом 4 , поскольку p1 указывает на первый элемент массива str .

Необходимость использования круглых скобок, в которые заключено выражение p1+4 , обусловлена тем, что оператор "*" имеет более высокий приоритет, чем оператор "+" . Без этих круглых скобок выражение бы свелось к получению значения, адресуемого указателем p1 , т.е. значения первого элемента массива, которое затем было бы увеличено на 4 .

Важно! Убедитесь лишний раз в правильности использования круглых скобок в выражении с указателями. В противном случае ошибку будет трудно отыскать, поскольку внешне программа может выглядеть вполне корректной. Если у вас есть сомнения в необходимости их использования, примите решение в их пользу – вреда от этого не будет.

В действительности в C++ предусмотрено два способа доступа к элементам массивов: с помощью индексирования массивов и арифметики указателей. Дело в том, что арифметические операции над указателями иногда выполняются быстрее, чем индексирование массивов, особенно при доступе к элементам, расположение которых отличается строгой упорядоченностью. Поскольку быстродействие часто является определяющим фактором при выборе тех или иных решений в программировании, то использование указателей для доступа к элементам массива– характерная особенность многих С++‑программ. Кроме того, иногда указатели позволяют написать более компактный код по сравнению с использованием индексирования массивов.

Чтобы лучше понять различие между использованием индексирования массивов и арифметических операций над указателями, рассмотрим две версии одной и той же программы. В этой программе из строки текста выделяются слова, разделенные пробелами. Например, из строки "Привет дружище" программа должна выделить слова "Привет" и "дружище" . Программисты обычно называют такие разграниченные символьные последовательности лексемами (token). При выполнении программы входная строка посимвольно копируется в другой массив (с именем token) до тех пор, пока не встретится пробел. После этого выделенная лексема выводится на экран, и процесс продолжается до тех пор, пока не будет достигнут конец строки. Например, если в качестве входной строки использовать строку Это лишь простой тест. , программа отобразит следующее.

 

 

Вот как выглядит версия программы разбиения строки на слова с использованием арифметики указателей.

 

 

А вот как выглядит версия той же программы с использованием индексирования массивов.

 

 

У этих программ может быть различное быстродействие, что обусловлено особенностями генерирования кода С++‑компиляторами. Как правило, при использовании индексирования массивов генерируется более длинный код (с большим количеством машинных команд), чем при выполнении арифметических действий над указателями. Поэтому неудивительно, что в профессионально написанном С++‑коде чаще встречаются версии, ориентированные на обработку указателей. Но если вы– начинающий программист, смело используйте индексирование массивов, пока не научитесь свободно обращаться с указателями.

 

Индексирование указателя

 

Как было показано выше, можно получить доступ к массиву, используя арифметические действия над указателями. Интересно то, что в C++ указатель, Который ссылается на массив, можно индексировать так, как если бы это было имя массива (это говорит о тесной связи между указателями и массивами). Соответствующий такому подходу синтаксис обеспечивает альтернативу арифметическим операциям над указателями, поскольку он более удобен в некоторых ситуациях. Рассмотрим пример.

 

 

При выполнении программа отобразит на экране следующее.

 

 

Вот как работает эта программа. Сначала в массив str вводится строка "I love you" . Затем адрес начала этой строки присваивается указателю р . После этого каждый символ строки str с помощью функции toupper() преобразуется в его прописной эквивалент посредством индексирования указателя р . Помните, что выражение р[i] по своему действию идентично выражению *(p+i) .

 


Дата добавления: 2018-09-22; просмотров: 341; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!