Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
A.doc
Скачиваний:
36
Добавлен:
09.04.2015
Размер:
5.6 Mб
Скачать

178 Глава 4

Описание полученных результатов

В этом примере нет ввода. Все операции выполняются с инициализированными значениями переменных. После сохранения адреса numberl в указателе pnumber зна- чение numberl увеличивается косвенно, через указатель:

Обратите внимание, что при объявлении указателя pnumber его значение инициализирова- но NULL. В следующем разделе я расскажу об инициализации указателей.

Операция разыменования говорит о том, что вы добавляете 11 к содержимому пе- ременной, на которую указывает pnumber, то есть к numberl. Если вы забудете напи- сать символ * в этом операторе, то это будет означать попытку прибавить 11 к адресу, который хранится в указателе.

Значение numberl и адрес numberl, который хранится в pnumber, отображаются на экране. Для представления значения адреса в шестнадцатеричной форме применя- ется манипулятор hex.

Вы можете получить значение обычной целочисленной переменной в шестнадца- теричном виде, используя манипулятор hex. Вы посылаете его в выходной поток точ- но таким же способом, как делаете это с endl, в результате чего весь последующий вывод идет в шестнадцатеричном формате. Если вы хотите, чтобы последующий вы- вод был в десятичном виде, то для этого должны применить манипулятор dec, чтобы переключить режим вывода обратно в десятичный.

После вывода первой строки содержимое pnumber устанавливается в значение адреса number2. Затем переменной numberl присваивается значение 10, умноженное на number2:

Это вычисление выполняется путем косвенного доступа к number2 через указа- тель. Вторая строка вывода показывает результат этого вычисления.

Значения адресов, которые вы видите в выводе, могут существенно отличаться от приведенных выше, поскольку они зависят от того, куда именно в память была загружена программа, что, в свою очередь, зависит от того, как сконфигурирована ваша операционная система. Префикс Ох в значениях адреса означает, что они ото- бражаются в шестнадцатеричном формате. Обратите внимание, что адреса &numberl и pnumber (когда он содержит &number2), отличаются на четыре байта. Это показы- вает, что numberl и number2 занимают соседние участки памяти, поскольку каждое значение типа long занимает 4 байта. Вывод доказывает, что все работает так, как и следовало ожидать.

Инициализация указателей

Использование неинициализированных указателей чрезвычайно опасно. Вы може- те легко перезаписать произвольную область памяти через неинициализированный указатель. Полученный ущерб при этом зависит лишь от степени вашего везения, по- этому инициализировать указатели — более чем хорошая идея. Очень легко инициа- лизировать указатель адресом переменной, которая уже определена. Здесь вы можете видеть, как я инициализировал указатель pnumber адресом переменной number, про- сто применив операцию & к имени переменной:

Массивы, строки и указатели 179

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

Конечно, объявляя указатель, вы можете решить не инициализировать его адре- сом определенной переменной. В этом случае его можно инициализировать указате- лем, эквивалентным нулю. Для этого в Visual С++ предусмотрен символ NULL, кото- рый уже определен как 0, поэтому указатель можно объявлять и инициализировать с помощью следующего оператора вместо того, что вы видели ранее:

Это гарантирует, что указатель не содержит адреса, который воспринимается, как корректный, и представляет ему значение, которое можно проверять в операторе if, вроде такого:

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

К тому же это лучше согласовано с принятым 'хорошим тоном' ISO/ANSI С++, поскольку если у вас есть именованный объект в С++, он должен иметь тип; однако NULL не имеет типа — это просто псевдоним для 0. Как вы увидите дальше в этой главе, в С++/СЫ дела обстоят несколько иначе.

Для использования 0 в качестве инициализирующего значения указателя, нужно просто написать:

Чтобы проверить, содержит ли указатель корректный адрес, используйте следую- щий оператор:

С тем же успехом можете применить и такой оператор:

Этот оператор делает то же самое, что и предыдущий. Конечно, вы также можете использовать и такую форму:

Адрес, на который указывает NULL-указатель, содержит "мусорное" значение. Никогда не пытайтесь разыменовывать нулевой указатель, поскольку это приведет к немедленному пре- рыванию работы вашей программы.

Указатели на char

Указатель типа char * обладает интересным свойством — он может быть инициа- лизирован строковым литералом. Так, например, можно объявить и инициализиро- вать указатель следующим оператором:

char* proverb = "A miss is as good as a mile.";

Э

Теперь вы можете переписать ранее приведенный пример со счастливыми звезда- ми, используя указатели вместо массива, чтобы увидеть, как это будет работать.

то выглядит похожим на инициализацию массива char, однако есть небольшое отличие. Здесь создается строковый литерал (на самом деле, массив типа const char), содержащий символьную строку, заключенную в кавычки, ограниченную сим- волом \0, и его адрес присваивается указателю proverb. Адресом литерала будет адрес его первого символа. Это показано на рис. 4.6.

О

}

писание полученных результатов

Массив из Ех4_04 . срр заменен шестью указателями, от pstrl до pstr б, каждый из которых инициализирован строкой — именем. К тому же объявлен дополнитель- ный указатель pstr, инициализированный фразой, которая должна появляться в начале нормальной строки вывода. Поскольку здесь используются дискретные ука- затели, легче применить оператор switch для выбора соответствующего выходно- го сообщения, нежели использовать if, как вы это делали в оригинальной версии. Любое неправильное введенное значение будет обработано опцией default опера- тора switch.

Вывод строки, на которую указывает указатель, не может быть проще. Как види- те, в операторе вывода вы просто записываете имя указателя. Может показаться, что здесь, как и в Ех4_05 . срр, появление имени указателя в операторе вывода приведет к отображению значения содержащегося в нем адреса, однако это не так. В чем раз- ница? Ответ заключается в том, что операция вывода видит, что типом ее аргумента является "указатель на char" и трактует указатели этого типа специальным образом — как строку (которая есть массив char), поэтому выводится сама строка, а не ее адрес.

Использование указателей в этом примере исключает лишний расход памяти, ко- торый имелся в предыдущей версии этой программы с массивами, но программа вы- глядит слишком многословной. Должен существовать лучший способ. И конечно же он есть — использование массива указателей.

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

Описание полученных результатов

В данном случае вы получили практически наилучшее из возможных решений. Имеется одномерный массив указателей на char, объявленный так, что компилятор может самостоятельно определить его размер на основе списка инициализирующих строк. Использование памяти в данном примере показано на рис. 4.7.

Если сравнить с применением "нормального" массива, то, наверное, этот пример требует меньших накладных расходов памяти. Однако посмотрим, что получилось на самом деле. В случае с массивами нужно было установить длину каждой строки равной максимальной из них, и в результате шесть строк по семнадцать байт каждая занимали 102 байта, поэтому, применив массив указателей, мы сэкономили всего -1 байт! Что не так? Дело в том, что для этого небольшого числа относительно корот- ких строк становится существенным размер дополнительного массива указателей. Вы получите экономию памяти, если будете работать с большим числом строк, большей и разнообразной длины.

Экономия памяти — не единственное преимущество использования указателей. Во множестве случаев также экономится время. Подумайте, что случится, если вы за- хотите переместить "Oliver Hardy" на первую позицию, a "Robert Redford" — в конец списка. В случае с массивом указателей нужно всего лишь обменять значения двух указателей — сами строки при этом останутся там, где и были. Если же они будут хра- ниться как массивы char, как это было сделано в Ех4_04 . срр, понадобится выпол- нить значительный объем копирования — придется полностью скопировать строку "Robert Redford" в какое-то временное место, затем скопировать на ее место "Oliver Hardy", после чего скопировать "Robert Redford" в конечную позицию. Это потребует заметно больше времени для выполнения.

Поскольку здесь в качестве имени массива используется pstr, переменная, содер- жащая начало выходного сообщения, должна быть другой; она называется pstart. Строка для вывода выбирается очень простым оператором if, похожим на исходную версию примера. Оно позволяет отобразить либо имя выбранной звезды, либо соот- ветствующее сообщение, если пользователь ввел неверное значение.

Массивы, строки и указатели 183

Слабым местом этой программы является то, что код предполагает, что всего есть шесть вариантов выбора, даже несмотря на то, что компилятор выделяет простран- ство для массива указателей на основании размера списка строки инициализации. Поэтому если вы добавите в него новую строку, то вам придется изменять все части программы, чтобы принять это во внимание. Было бы здорово добавлять строки и позволить программе автоматически адаптироваться к их количеству.

Операция sizeof

И здесь на помощь приходит новая операция. Операция sizeof возвращает целое значение типа sizet, которое означает количество байт, занятых ее операндом. Вспомните из того, что было сказано ранее, что size_t — это тип, определенный в стандартной библиотеке, и обычно он основан на базовом типе unsigned int.

Взгляните на следующий оператор, который ссылается на переменную dice из предыдущего примера:

Значение выражения sizeof dice равно 4, поскольку переменная dice объявле- на как int, а потому занимает 4 байта. Поэтому приведенный оператор выведет на экран значение 4.

Операция sizeof может быть применена к элементу массива или к массиву в целом. Когда она применяется к имени массива, то возвращает количество байт, за- нятых всем массивом, в то время как примененная к отдельному элементу с соответ- ствующим индексом, она возвращает число байт, занятых данным элементом. Таким образом, в последнем примере можно получить количество элементов массива pstr следующим выражением: