- •6 Глава 1
- •12 Глава 1
- •14 Глава 1
- •16 Глава 1
- •18 Глава 1
- •20 Глава 1
- •22 Глава 1
- •24 Глава 1
- •26 Глава 1
- •31 Глава 1
- •34 Глава 2
- •36 Глава 2
- •Puc. 2.4. Дополнительные опции консольного приложения Win32
- •38 Глава 2
- •40 Глава 2
- •42 Глава 2
- •44 Глава 2
- •48 Глава 2
- •50 Глава 2
- •52 Глава 2
- •54 Глава 2
- •56 Глава 2
- •58 Глава 2
- •60 Глава 2
- •62 Глава 2
- •64 Глава 2
- •66 Глава 2
- •68 Глава 2
- •70 Глава 2
- •74 Глава 2
- •76 Глава 2
- •79 Глава 2
- •82 Глава 2
- •84 Глава 2
- •86 Глава 2
- •88 Глава 2
- •92 Глава 2
- •94 Глава 2
- •96 Глава 2
- •98 Глава 2
- •103 Глава 2
- •105 Глава 2
- •107 Глава 2
- •110 Глава 2
- •115 Глава 3
- •119 Глава 3
- •121 Глава 3
- •123 Глава 3
- •125 Глава 3
- •129 Глава 3
- •131 Глава 3
- •133 Глава 3
- •139 Глава 3
- •141 Глава 3
- •143 Глава 3
- •145 Глава 3
- •148 Глава 3
- •150 Глава 3
- •155 Глава 3
- •165 Глава 4
- •168 Глава 4
- •170 Глава 4
- •173 Глава 4
- •175 Глава 4
- •178 Глава 4
- •184 Глава 4
- •186 Глава 4
- •188 Глава 4
- •190 Глава 4
- •192 Глава 4
- •194 Глава 4
- •198 Глава 4
- •201 Глава 5
- •203 Глава 5
- •205 Глава 5
- •207 Глава 5
- •213 Глава 5
- •217 Глава 5
- •219 Глава 5
- •221 Глава 5
- •223 Глава 5
- •225 Глава 5
- •227 Глава 5
- •232 Глава 5
- •234 Глава 5
- •236 Глава 5
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 байта. Вывод доказывает, что все работает так, как и следовало ожидать.
Инициализация указателей
Массивы,
строки
и
указатели
Инициализируя указатель адресом другой переменной, помните, что эта перемен- ная должна быть объявлена до объявления указателя.
Конечно, объявляя указатель, вы можете решить не инициализировать его адре- сом определенной переменной. В этом случае его можно инициализировать указате- лем, эквивалентным нулю. Для этого в 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.";
Теперь
вы можете переписать ранее приведенный
пример со счастливыми звезда-
ми,
используя указатели вместо массива,
чтобы увидеть, как это будет работать.
О
}
Массив из Ех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, похожим на исходную версию примера. Оно позволяет отобразить либо имя выбранной звезды, либо соот- ветствующее сообщение, если пользователь ввел неверное значение.
Массивы,
строки
и
указатели
Слабым местом этой программы является то, что код предполагает, что всего есть шесть вариантов выбора, даже несмотря на то, что компилятор выделяет простран- ство для массива указателей на основании размера списка строки инициализации. Поэтому если вы добавите в него новую строку, то вам придется изменять все части программы, чтобы принять это во внимание. Было бы здорово добавлять строки и позволить программе автоматически адаптироваться к их количеству.
Операция sizeof
И здесь на помощь приходит новая операция. Операция sizeof возвращает целое значение типа sizet, которое означает количество байт, занятых ее операндом. Вспомните из того, что было сказано ранее, что size_t — это тип, определенный в стандартной библиотеке, и обычно он основан на базовом типе unsigned int.
Взгляните на следующий оператор, который ссылается на переменную dice из предыдущего примера:
Значение выражения sizeof dice равно 4, поскольку переменная dice объявле- на как int, а потому занимает 4 байта. Поэтому приведенный оператор выведет на экран значение 4.
Операция sizeof может быть применена к элементу массива или к массиву в целом. Когда она применяется к имени массива, то возвращает количество байт, за- нятых всем массивом, в то время как примененная к отдельному элементу с соответ- ствующим индексом, она возвращает число байт, занятых данным элементом. Таким образом, в последнем примере можно получить количество элементов массива pstr следующим выражением: