Г. Шилдт - Полный справочник по C++ (1109478), страница 25
Текст из файла (страница 25)
Переменная, прсдставляктшая собой указатель на указатель. объявляется слелуюшим образом: перел сс именем записывается дополнительная звездочка. Например, в следуюшем операторе переменная пемъа1апсе объявляется как указатель на указа- сль на число с птзваюшей точкой. й с1оае **пеыЬа1апсе Следует помнить, что переменная пемьвъапее не является указателем на число ; плаваюшсй точкой, она лишь ссылается на указатель этого типа.
Глава б. Указатели ул Чтобы извлечь значение переменной с помощью косвенной адресации, необходи. мо дважды применить оператор разыменования. Еьпс1иде своего.п> зпс азатп(оо1о) опт х, "р, **с)з х = 10з )3 = йхз с) = арз рт1птт("ьс)', **ц); /* вывод числа х */ те) цтп Оз ) Здесь переменная р объявлена как целочисленный указатель, а переменная о прел ставляет собой указатель на целочисленный указатель.
Функция рт1пет () выволит на зкран число Нй ~~~ Инициализация указателей Если нестатический локальный указатель объявлен, но не инициализирован, его значение остается неопределенным. (Глобальные и статические локальньш указатели автоматически инициализируются нулем.) При попытке применить указатель, содержащий неопределенный адрес, может разрушиться как программа, так и вся операционная система — хуже нс придумаешь! При работе с указателями большинство профессиональных программистов на языке С/С++ придерживаются следующего соглашения: указатель, не ссьпающиися па конкретную ячейку памяти, должен быть равен нулю.
По определению любой нулевой указа~ель ни на что не ссылается и не должен использоваться. Однако зто ешс не гарантирует безопасности. Использование нулевого указателя — всего лишь общепринятое соглашение. Это вовсе нс правило, диктуемое языком С или С++.
Например, если нулсвои указатель поместить в левую чань оператора присваивания, риск раз. рушсния программы и операционной системы остае~ся высоким. Поскольку предполагается, что нулевой указатель в вычислениях не используется, его можно применять для повышения наглялности и эффективности программ. Например, его можно использовать в казестве признака конца массива, содержащего указатели.
Это позволит предотвратить выход за пределы допустимого диапазона. Подобная ситуация иллюстрируется на примере функции велте)з(). /' Поиск имени */ 1пт веатсц(спет *р(), с)зат *палзе) тесззесет зп тот(с-Сз р(т)з ++с) 1Е(!естслзр(р[т], пате)) тесптп ез тетитп -1; /* имя не найдено */ Цикл вот внутри функции вевтс)з() выполняется до тех пор, пока не будет найдено требуемое имя, либо нс обнаружится нулевой указатель.
Как только нулевой указатель Часть!. Основм языка С++с подмножества С будет обнаружен. условие цикла станет ложным. и программа передаст управление следующему оператору. Программисты на С/С+э часто инициализируют строки. Пример такой инициализации приведен в функции вупеек еггогО из раздела "Массивы указателей". Рассмозрим еще один вариант инициализации строки в момент объявления. й сбег *р = "Здравствуйте"; Как яидим, указатель р не является массивом.
Попробуем понять, почему возможен такой способ инициализации. Все кол)пиляторы языка С/С++ созлдют так называемую таблицу строк (з(ппй (аЫе), в которой хранятся строковые константы, используемыс в программе. Следовательно, предыдущий оператор присвоит указателю р адрес строковой константы "Здравствуйте", записанной в таблицу строк. Указатель р используется в программе как обычная строка (однако изменять его нежелательно). Проиллюстрируем это следующим примером.
Вгпс1пде <все)1о.)1> $1пс1пс)е <вгглпд.н> сваг *р = "Здравствуйте"," зпс ллавп(тоуй ( геяйвсег ьпс /' Выводим строку в прямом и обратном порядке. */ рггпег(р); Еог(е=всг1еп(р)-1) С>-1; Е--) рг1пег["$с", р(Г)); гегцгп О; ) С формальной точки зрения в стандарте языка С++ строковый литерал имеет тип савве сваг *. Однако в языке С++ предусмотрено автоматическое преобразование втип снег ". Таким образом, рассмотренная выше программа является абсолютно правильной.
И все жс эта особенность языка считается нежелательной, поэтому это преобразование не следует применять. Создавая новые программы, строковые литералы нсобходил)о дсиствительно считать константами, а объявление указателя р записывать следующим образом. й сопве сваг *р = "Здравствуйте"; ~~ Указатели на функции Особенно малопонятным, хотя и действенным механизмом языка С++ являются укаяатели на функции (йгпс(юп рогп(сг). Нсслютря на то что функция нс является переменной, >на располагается в памяти, и, следовательно, ее адрес можно присваивать указателю. Этот .ырес считается точкой вхи)а в функцию.
Именно он используется при ее вызове. По;кольку указатель может ссылаться на функцию, се можно вызывать с помощью этого укалзтеля. Это позволяет также передавать функции другим функциям в качестве аргуме)пов. Адрес функции задается се именем, указанным без скобок и аргументов. Чтобы . азобраться в этом механизме, рассмотрим следующую программу. | Ф1пс1пг)е <вело.'и> Л(пс1пс)е <вгг1пд.н> лава 5, указатели чо1г) сйесх(сйах *а, сйах *Ь, Епс ("смр)(сопвс айаг ', попас сйат )) )пс ма(г.(чодо) сйат в1'(80), я2(80); Епс (*р)(сопев сйат *, сопев сйак *); р = вегсмр; пеев (я1); пеев(я2); сйесх(я1, в2, р)г песптп 0; чодг) сйесх(сйаг *а, сйаг *Ь, Епе (*сыр)(сопяс сйах *, сопяе сйаг *)) ( ркдпег("Проверка равенства.хп")г ЕЕ(!(*сыр)(а, Ы ) рзЕпеЕ(йравнк"); е1яе ртдпЕЕ["Не равны"); При вызове Функция ейее)еО получает два указателя иа символьные переменные и указатель на функцию.
Соответствующие аргументы объявлены в се заголовке. Обратите внимание иа то, как объявлен указатель иа функцию. Эту форму объявления следует применять для любых указателей на функции, независимо от того, какой тип имеют их аргументы и возвращаемые значения. Объявление *етр заключено в скобки лля того, чтобы компилятор правильно его интерпретировал. Выражение е) (*сгтр)(а, Ы внутри функции ейее)е() означает вызов функции яехегар[), на которую ссылается указатель етр, с аргументами а и ь. Скобки по-прежнему необхолил(ы. Зто — олин из способов вызвать функцию с помощью указателя.
Альтернативный вызов выглядит так: $ сир(а, Ы; Первый способ применяется чаще, поскольку оп позволяет явно продемонстрировать, что функция вызывается через указатель. (Иначе говоря, читатель программы сразу увидит, что переменная емр является указателем, а ие именем Функции.) Тем ие менее эти два способа эквивалентны. Обратите внимание па то, что функцию сйеск() можно вызвать, непосредственно указав функцию яетегар() в качестве ес аргумента. й сйесх(в1, в2, ягксмр); В агом случае отпадает необходимость в дополнительном указателе иа функцию. Может возникнуть закономерный вопрос: зачем вызывать функции с помощью указателей? На первый взгляд, это лишь усложняет программу, пе предлагая взамен никакой компенсации.
Тем ие менее иногда выгоднее вызывап функции через указатсли и даже создавать массивы указателей ца функции. рассмотрим в качестве примера синтаксический анализатор — сосзавиую часть компилятора, вычисляющую выражения. Оп часто вызывает различные математические функции [синус, косинус,, тап- Часть ). Основы языка С++: подмножество С гене и т.д.), средства ввода-вывода или функции доступа к ресурсам системы. Вместо создания большого оператора ви1есЬ, в котором пришлось бы перечислять все эти функции, можно создать массив указателей на них. В этом случае к функциям можно было бы обращаться по индексу. Чтобы оценить эффективность такого подхода, рассмотрим расширенную версию предыдущей программы.
В этом примере функция сиес)е() проверяет на равенство строки, состоящие из букв или цифр. Для этого она просто вызывает разные функции, выполняющие сравнение. Вап<1ийе <вгйво.Ь> $1пс1нйе <стуре.Ь> 61пс1ийе <всй)1Ь.Ь> Вьпс1нйе <всгапП.Ь> ноьй снеси(онат *а, сиат Ь, 1пе (*сыр)(сосне спат *, сопве спат *)): 1пе ппесгер(сосне спат "а, сопве сиат *Ы; апе ваап(еоьй) ( спат в1(80), в2 (80) ) пеев(в1) деев(в2] 11(1ва1риа(*в1)) снеси(в1, в2, вттспр); е1ве снеси(в1, в2, пнвсвр); хеентп 0; ноай сиест(снах *а, онат *Ь, 1пе (*егер)(сопве сиат *, сопят онат *)) рхйпст'("Проверка равенства.