Программирование >>  Унарные и бинарные операторы 

1 ... 7 8 9 [ 10 ] 11 12 13 ... 37


После объявления указатель еще пе связан с объектом и указывает пальцем в небо . Чтобы привести указатель в рабочее состояние, необходимо присвоить ему адрес объекта. Делается это оператором &.

Листинг 5.4 демонстрирует программу, где объявляется целочисленная переменная а с Ha4ajHjHbiM значением 2. Затем объявляется указатель р, и в него засылается адрес переменной а, то есть р=&а. Как только в указателе р оказывается адрес, возникает тайная тропа к переменной, и после выполнения инструкции *р=5 неременная а становится равной пяти!

Листинг 5.4

#inc1ude <iostream> using namespace std; int mainC){ int a=2: int *p=&a;

cout *p= p endl; *p=2 *p=5; a-5:

cout a= a endl; }

Теперь самое время вспомнить о неудачной функции exchngO из предыдущего раздела и подумать, что случится, если передать ей пе переменные, а их адреса. Конечно, сами адреса переменных функция изменить пе сможет, потому что получит только их когти. Но что мешает применить к этим адресам оператор * п тем са.-ыыммег1ять неременные из внешнего мира?

Программа, показанная в листин1-е 5.5, пользуется т-мснешюй функцией exclingC), припимающс!! указатели на две переменные. В ее заголовке void exchngCint *р1. int *р2) имеется два параметра - указатель на целочисленную переменную р1 и указатель па такую же пче.менную р2.

Листинг 5.5

#1nclude <iostream>

using namespace std:

void exchngCint *pl. int *p2){

int tmp=*pl; *pl=*p2: *p2=tmp; )

int mainOf int a=2.b=5: exchngC&a.&b);

cout a= a endl: a=5 cout b- b endl; b=2 return 0; }

При вызове функции exchng(&a.&b) ей передаются два адреса: &а - адрес неременной а, первоначально равной двум и адрес b - &b, первоначально равной пяти. Внутри функц1П1 адрес переменной а оказывается в napaMcqie р1, а адрес переменной b - в параметре р2. №1струкция tnp=*pl пересылает содержимое переменной а во временную переменную tmp, то есть tmp становится равной двум. Далее переменной а присваивается значение b (*р1=*р2). В этот момент а-5. И наконец, последняя инструкция *p2=tmp пересылает в переменную b содержимое а, сохраненное в переменной tmp. После возврата из функции а=5, Ь=2.

Занимаясь указателями, нельзя не вспомнить итераторы - объекты, созданные для передвижения внутри контейнеров (см. раздел Контейнеры в главе 4). Для итераторов тоже существует оператор *, с номопи ю ко горого МОЖ1 ю получить значс! гие элеме! 1та контейнера. В целом, итераторы и указатели очень похожи. Указатели также можно использовать для передвижения, по не внутри контейнера, а внутри обычного массива. Но об этом поговорим в следуюпюм разделе.

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

Мы уже знаем, что указатели инициализируются оператором &. Инструкция int *р=&а отправляет в указатель р адрес уже существуюп1сй нереметпюй а.



Но в указателе может храниться не только адрес ранее объявленной переменной, но и начальный адрес области памяти, выделяемой специальным оператором new. Следующие инструкции создают указатель р (int *р:), выделяют область памяти, куда умещается целочисленная переменная (new int), и записывают в указатель адрес этой области:

int *р;

р = new int;

Теперь оператор *, примененный к указателю, позволит пользоваться этой областью памяти как обычной переменной. Например, инструкция *р=2 засылает в память, на которую указывает р, число 2.

Оператор new, с которым мы только что познакомились, способен вьщелить память и для нескольких расположенных рядом переменных. Их число указывается в квадратных скобках вслед за типом переменной. Например, следующая инструкция выделяет память для десяти расположенных рядом переменных типа int и помещает адрес ее начала в указатель р:

int *р:

р = new int[10];

К нулевой переменной в этом ряду можно, как обычно, обратиться с помощью оператора *. Инструкция *р=0 засылает туда число ноль. Но как получить доступ к первой, второй и т. д. переменным?

Для этого в С++ существует правило: адрес следующей переменной получается прибавлением единицы к указателю на предыдущую. Если р указывает на нулевую переменную, выделащую операторам new, то pl - на первую, р+2 - на вторую и т. д. Инструкция *(р+4)=5 засылает пятерку в четвертую из десяти расположенных одна за другой переменных, выделенных с помощью оператора new (рис. 5.1). Естественно, вместо цифр можно использовать целочисленные переменные, то есть *{р+1) равно четвертой переменной в последовательности, если i-4.

р+0 р+1 р+2 р+4

Рис 5.1. Адресация переменных с помощью указателей

Иными словами, увеличение на единицу продвигает указатель к следующей переменной, соответствующей его типу. Если это указатель на переменную i nt, занимающую 4 байта, то после прибавления единицы указатель продвинется вперед на 4 байта и будет указывать на следующую переменную. Если компилятор хранит целочисленные переменные в 2 байтах, то и указатель на int от прибавления единицы продвинется только на 2 байта. В любом случае переход к следующей переменной происходит автоматически, и программисту не нужно думать, сколько байтов она занимает.

Естественно, правило прибавления единицы справедливо для всех объектов С++. Если р - указатель на переменную типа doubl е, то от прибавления единицы он продвинется к следующей переменной типа double, а на сколько конкретно байтов - зависит от компилятора. Для нашего компилятора это число равно 8.

А теперь попробуем взглянуть на память, вьщеляемую оператором new, по-другому. Что такое несколько последовательно расположенных переменных, начинающихся с адреса, хранимого в указателе р? Очевидно, это массив, и разумно обращаться с такой памятью как с массивом. Поэтому в С++ разрешено писать вместо *{p+i )=2, где i - целочисленная переменная, просто p[i]=2. То есть получается, что указател ь р становится как бы именем массива.

Но что такое тогда настоящий массив? Раз имена массива и указателя равноправны и практически не-



ОТЛИЧИМЫ, то разумно и с именем массива обращаться как с указателем на его нулевой элемент. Иными словами, если а - имя настоящего массива, то загнать тройку во второй его элемент можно инструкцией *(а+2)=3, а не только инструкцией а[2]=3. И это действительно так. Имя настоящего массива компилятор рассматривает как указатель на его нулевой элемент и, встретив инструкцию а[2]-3, превращает ее в *(а+2)=3.

Если имя массива указывает на его пулевой элемент, то можно настроить указатель па начало массива, просто приравняв имя указателя и имя массива. Вместо записи p=&z[0], где р - имя указателя, а z - имя массива, можно просто записать p=z, после чего р cTanoBin-ся как бы вторым именем массива, а инструкции p[i ]-2 и z[i]=2 оказываются эквивалентными.

Следует ли из всего этого, что имя массива эквивалентно имени указателя? Пи в коем случае. Ведь указатель -это некая область памяти, где х1)анится ад1тес другой области памяти. Указателю можно придать новое значение, тем самым перенаправив его на другой объект. Ничего подобного нельзя сделать с именем массива, навсегда обреченным указывать в одно место - на нулевоГ! его эле мент. Если р - указатель, то можно написать р++, увеличив указатель на единицу и тем самым передвинув его к следующей переменной. Однако инструкция а++ (где а - имя массива) вызовет сообп1ение компилятора об ошибке.

Из всего сказанного может сложиться впечатление, что массивы, создшщые оператором new, и настоящие массивы, объявленные в программе, ничем пе отличаются, ведь к элементу и того, и другого можно обратиться но указателю р[ i ] независимо от того, что такое р - имя указателя или имя массива. Но одно (причем существенное) различие все же есть. Обычные пе1>емен1гые и массивы определенные внутри функций, пропадают при нозвра щении из функции во внешний мир . Однако память, выделенная оператором new, сама не освобождается. Если

ничего не делать при выходе из функции, память останется занятой, и профаммауже не сможет ею воспользоваться. Если функция часто вызывается, па\щти будет оставаться все меньше. Наступит момент, когда ее не останется вовсе, и профамма аварийно завершится. Чтобы этого пе произошло, нужно перед выходом из функции освобождать память оператором delete: void scmethingOj

int =*p=new int[10]:

delete []p;

Обратите внимание па квадратные скобки, стоян1ие между оператором delete и именем указателя. Без них оператор del ete освободил бы память, занимаемую только нулевым элементом созданного оператором new массива. Квад1)атныс скобки показывают, что нужно освобо- дить намять, занимаемую всеми десятью переменными. Подробнее о жизни переменных и освобождении памяти рассказывается в главе 7.

Задача 5.1. Что делает представленная ниже программа? Какое число она покажет на экране?

#inc1ude <iostream>

using namespace std;

int main(){

int a[5]={1.2.3.4.5};

int *p=&a[0];

int s=0:

for(int i=0;i<5;i++)

s+=*p++: cout s end!: return 0: }

Зная связь указателей и массивов, легко понять, как массивы передаются функции. Очевидно, функции, чтобы работать с элементами массива, достаточно знать их число и получить указатель на нулевой элемент.



1 ... 7 8 9 [ 10 ] 11 12 13 ... 37

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика