Студопедия
Случайная страница | ТОМ-1 | ТОМ-2 | ТОМ-3
АрхитектураБиологияГеографияДругоеИностранные языки
ИнформатикаИсторияКультураЛитератураМатематика
МедицинаМеханикаОбразованиеОхрана трудаПедагогика
ПолитикаПравоПрограммированиеПсихологияРелигия
СоциологияСпортСтроительствоФизикаФилософия
ФинансыХимияЭкологияЭкономикаЭлектроника

Программирование в Visual С++ 2005 14 страница




В этом фрагменте сначала объявляется массив char размером в МАХ элементов,
а затем в него читается строка из cin с помощью функции getline (). Источник
данных — cin — записывается так, как показано, с точкой, отделяющей его от име-
ни функции. Что именно означают аргументы функции getline (), можно видеть на
рис. 4.3.


 


Поскольку последний аргумент, переданный функции getline () — это ' \п' (сим-
вол новой строки), а второй аргумент — МАХ, символы читаются из cin до тех пор,
пока не будут прочитан символ '\n', или когда будет прочитано МАХ-1 символов — в
зависимости от того, что произойдет раньше. Максимальное количество прочитан-
ных символов равно МАХ-1, чтобы позволить дописать символ-ограничитель '\0' к
последовательности символов, сохраняемых в массиве. Символ '\n' генерируется,
когда вы нажимаете клавишу <Enter> на клавиатуре, а потому это — наиболее удобный
символ для завершения ввода. Однако вы можете указать какой-то другой символ в
качестве признака завершения ввода, изменив последний аргумент. Символ '\n' не
сохраняется во входном массиве name, но как уже было сказано, в конец введенной
строки, помещенной в массив, добавляется '\0'.



Типичный вывод этой программы должен выглядеть так:

Введите строку не длиннее 80 символов:
Радиация погубит ваши гены

Строка "Радиация погубит ваши гены" содержит 2 6 символов.

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

Эта программа объявляет символьный массив buffer и читает символьную строку
в этот массив с клавиатуры после вывода приглашения на ввод. Чтение с клавиатуры
прекращается, когда пользователь нажимает <Enter>, или после того, как прочитано
МАХ-1 символов.

Цикл while используется для подсчета количества прочитанных символов. Цикл
продолжается до тех пор, пока текущий символ, находящийся в buffer [count], не
равен 1 \ 0 '. Такая проверка текущего символа при проходе по элементам массива ча-
сто применяется в "родном" С++. Единственным действием, выполняемым в цикле,
является инкремент значения счетчика count для каждого ненулевого символа.

Существует также библиотечная функция strlen (), которая избавляет вас от са-
мостоятельного кодирования при подсчете символов строки. Если вы хотите ее ис-
пользовать, то должны включить в свою программу заголовочный файл <cstring> с
помощью директивы :

Буква "с" в имени заголовочного файла говорит о том, что этот файл содержит
определения, относящиеся к библиотеке языка С, которая является частью библио-




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

 

теки С++. Этот заголовок также содержит функцию wcsnlen (), возвращающей длину
строки широких символов.

Используя функцию strlen (), вы можете заменить цикл while следующим опе-
ратором:

Аргументом служит имя массива, содержащего строку, а возвращает функция
strlen () длину этой строки в виде целочисленного значения типа size_t. Многие
функции стандартной библиотеки возвращают значение типа size_t, а сам тип
size_t определен в стандартной библиотеке с помощью оператора typedef как эк-
вивалент одного из фундаментальных типов, обычно — unsigned int. Причина при-
менения size_t вместо непосредственного использования фундаментального типа
связана с тем, что это обеспечивает гибкость в том, что действительный тип, скрыва-
ющийся за псевдонимом size_t, может определяться конкретной реализацией С++.
Стандарт С++ разрешает варьировать диапазоны допустимых значений фундаменталь-
ных типов для наилучшего соответствия аппаратной архитектуре, и sizet может
быть определено как эквивалент наиболее подходящего типа в текущем аппаратном
окружении.

В конце примера введенная строка и количество ее символов отображаются един-
ственным оператором вывода. Обратите внимание на применение управляющей по-
следовательности * \ " ' для вывода двойной кавычки.

Многомерные массивы

Массивы, которые мы определяли до сих пор, имеют один индекс и называются
одномерными массивами. Но массив может иметь и более одного индексного зна-
чения — в этом случае он называется многомерным массивом. Предположим, что у
вас есть поле, на котором расположена плантация бобов, по 10 растений на грядке, и
это поле содержит 12 таких грядок (то есть всего имеется 120 единиц растений). Вы
можете объявить массив для записи веса бобов, собранных от каждого растения, ис-
пользуя следующий оператор:

Здесь объявляется двумерный массив beans, где первый индекс — номер грядки,
а второй — номер растения на грядке. Чтобы обратиться к любому конкретному эле-
менту, необходимо указать два индекса. Например, вы можете установить значение
элемента, соответствующего пятому растению в третьей грядке, следующим операто-
ром:

Напомним, что значения индексов начинаются с нуля, поэтому значение индекса
третьей грядки — 2, а индекс пятого растения — 4.

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

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


190 Глава 4

 

чать страну. Если предположить, что вы — настолько же успешный продавец, как и
фермер, то выращивание такого количества бобов, в конце концов, станет оказывать
влияние на озоновый слой.

Массивы хранятся в памяти так, что самый правый индекс растет быстрее всего.
То есть массив data [3] [4] — это трехмерный массив, состоящий из массивов по че-
тыре элемента в каждом. Организация такого массива показана на рис. 4.4.

Элементы массива располагаются в непрерывном блоке памяти, как показано стре-
лочками на рис. 4.4. Первый индекс выбирает определенную строку внутри массива,
а второй индекс выбирает элемент внутри строки.

 


 


Обратите внимание, что двумерный массив в родном С++ — на самом деле одномер-
ный массив, состоящий из одномерных массивов. Массив с тремя измерениями в род-
ном С++ — это одномерный массив элементов, в котором каждый элемент представля-
ет собой одномерный массив одномерных массивов. Большую часть времени вам не
придется об этом беспокоиться, но как вы увидите позднее, массивы C++/CLI отлича-
ются от массивов С++. Из сказанного следует, что для массива, показанного на рис. 4.4,
выражения data [ 0 ], data [ 1 ] и data [ 2 ] представляют одномерные массивы.

Инициализация многомерных массивов

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

 


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


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

 

Вы можете пропустить инициализацию значений в любой строке, в этом случае
остальные элементы массива инициализируются нулевыми значениями, например:

 


Я дополнил пробелами списки инициализации, чтобы показать, где пропущены
значения. Элементы data[0] [3], data[l] [2] и data[l] [3] не получают инициали-
зирующих значений и потому равны нулю.

Если вы хотите инициализировать весь массив нулевыми значениями, вы можете
написать просто:

 

 


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


Вы можете использовать один двумерный массив для сохранения нескольких
строк в стиле С. Как это работает, можно увидеть на примере.



192 Глава 4

 

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

Помимо развлекательного значения, главное, что интересно в этом примере — объ-
явление массива stars. Это двумерный массив элементов типа char, который может
содержать до шести строк, каждая из которых имеет до 80 символов длиной (вклю-
чая ограничивающий нулевой символ, автоматически добавляемый компилятором).
Инициализирующие строки массива заключены в кавычки и разделены запятыми.

Один из недостатков подобного применения массивов заключается в том, что значительная
часть памяти не используется. Все строки короче 80 символов и потому оставшиеся избы-
точные элементы массивов расходуются впустую.

Вы можете также позволить компилятору определить, сколько именно строк не-
обходимо поместить в массив, пропустив величину первого измерения массива и объ-
явив его следующим образом:

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

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

Когда вам необходимо сослаться на строку при выводе в следующем операторе,
достаточно указать только значение первого индекса:

 

 


Значение с одним индексом выбирает конкретный 80-символьный подмассив, а
операция вывода отображает его содержимое вплоть до нулевого символа. Индекс
указан как dice - 1, потому что dice имеет значения от 1 до б, в то время как значе-
ния индекса варьируются от 0 до 5.

 

Косвенный доступ к данным

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


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

 

Что такое указатель?

Каждое место в памяти, которое вы используете для хранения данных, имеет
адрес. Адрес предоставляет возможность для оборудования компьютера обращаться
к определенному элементу данных. Указатель — это переменная, которая сохраняет
адрес другой переменной определенного типа. Переменная-указатель обладает име-
нем, как и любая другая переменная, и также имеет тип, определяющий то, на какого
рода данные она может указывать. Переменная, являющаяся указателем, содержащим
адрес местоположения в памяти, которое хранит значение типа int, называется "ука-
зателем на int".

Объявление указателей

Объявления указателей подобно объявлению обычных переменных, за исключе-
нием того, что имя дополняется впереди звездочкой, говорящей о том, что данная
переменная является указателем. Например, чтобы объявить указатель pnumber типа
long, можно использовать следующий оператор:

Это объявление записано со звездочкой, расположенной ближе к имени перемен-
ной. Если хотите, можете также написать так:

Компилятору это безразлично. Однако типом переменной pnumber является "ука-
затель на long", что часто обозначается помещением звездочки ближе к имени типа.
Не важно, какой стиль вы предпочтете — важно придерживаться его согласованно.

Можно смешивать объявления обычных переменных и указателей в одном опера-
торе, например:

Этот оператор, как и ранее, объявляет указатель pnumber типа "указатель на
long", но также объявляет переменную number типа long. В конечном итоге, вероят-
но, лучше все-таки объявлять указатели отдельно от других переменных; в противном
случае такой оператор может ввести в заблуждение в отношении типа объявляемых
переменных, особенно если вы предпочитаете размещать * рядом с именем типа.
Следующие операторы, безусловно, выглядят яснее, к тому же размещение объявле-
ния в отдельных строках позволяет вам добавить им индивидуальные комментарии,
что облегчает чтение программы.

 


В С++ существует общепринятое соглашение — называть переменные-указатели
именами, начинающимися с р. Это позволяет сразу увидеть, какие переменные явля-
ются указателями, что, в свою очередь, упрощает восприятие программ.

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


194 Глава 4

 

Операция получения адреса

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

Результат выполнения этой операции можно видеть на рис. 4.5.


 

 


Вы можете использовать операцию & для получения адреса любой переменной, но
для сохранения его вам понадобится переменная-указатель соответствующего типа.
Например, если нужно сохранить адрес переменной double, указатель должен быть
объявлен с типом double *, то есть "указатель на double".

Использование указателей

Получение адреса переменной и сохранение его в переменной-указателе — это
очень хорошо, но более интересно, как его использовать. Основной смысл приме-
нения указателя состоит в возможности доступа к данным, на которые он указывает.
Это делается с помощью операции разыменования (*).

Операция разыменования

Для доступа к переменной, на которую указывает указатель, вы будете применять
операцию разыменования указателя (*), подставляя в качестве операнда имя пере-
менной-указателя. Название "операция разыменования" или "операция косвенного
доступа" говорит о том, что обращение к данным не прямое, и что для получения до-
ступа к данным, на которые указывает указатель, его следует "разыменовать".

Один аспект этой операции, который может показаться запутанным, связан с суще-
ствованием разных применений одного и того же символа *. Во-первых, это — опера-
ция арифметического умножения, во-вторых — операция разыменования, и вдобавок
она используется при объявлении переменных-указателей. Всякий раз, встречая сим-
вол *, компилятор определяет его значение в каждом конкретном случае из контекста.
Когда вы перемножаете две переменных, например, А*В, не существует никакой дру-
гой осмысленной интерпретации этого выражения, кроме как операции умножения.


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

 

Зачем нужны указатели?

Вопрос, который обычно возникает при изучении указателей, звучит так: "Зачем
они вообще нужны?". В конце концов, получить адрес переменной, которая вам уже
известна, сохранить его в указателе, чтобы потом разыменовать, выглядит излишним,
то есть чем-то таким, без чего вполне можно обойтись. Однако существует несколько
причин, объясняющих важность указателей.

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


 




В следующем примере вы можете испытать различные аспекты операций с указа-
телями.

 


На моем компьютере этот пример генерирует следующий вывод:

numberl = 66 Snumberl = 0012FEC8
numberl = 990 pnumber = 0012FEBC *pnumber = 99


196 Глава 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, про-
сто применив операцию & к имени переменной:

 

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

 

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

Конечно, объявляя указатель, вы можете решить не инициализировать его адре-
сом определенной переменной. В этом случае его можно инициализировать указате-
лем, эквивалентным нулю. Для этого в 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 каждый элемент может указывать на независимую
строку, причем длины этих строк могут быть разными. Вы можете объявить массив
указателей точно так же, как объявляете нормальный массив. Давайте перепишем
предыдущий пример, используя массив указателей.


Дата добавления: 2015-11-04; просмотров: 45 | Нарушение авторских прав







mybiblioteka.su - 2015-2024 год. (0.032 сек.)







<== предыдущая лекция | следующая лекция ==>