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

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



Цикл for each

Все операторы циклов, о которых я говорил до сих пор, применяются как в языке
ISO/ANSI С++, так и в C++/CLI. Но язык C++/CLI предлагает еще один роскошный
тип цикла, называемый for each. Он предназначен специально для итерации по объ-
ектам, принадлежащим к определенному набору, и поскольку вы пока ничего не зна-
ете об этом, я кратко представлю здесь цикл for each, отложив более подробные
пояснения на потом.


Создайте новый проект консольной программы CLR по имени ЕхЗ_17 и модифи-
цируйте код следующим образом:

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


176 Глава 3

 

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

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

Программа подсчитывает количество гласных и согласных в строке, содержащей-
ся в переменной proverb. Программа делает это путем итерации по каждому симво-
лу строки в цикле for each. Сначала определяется две переменных для накопления
общего количества гласных и согласных:

 


"За кулисами" они имеют тип C++/CLI Int32, который хранит 32-битные целые
значения.

Далее определяется строка для анализа:

Переменная proverb имеет тип StringA, который описывается как тип "дескрип-
тора String" (handle to String); дескриптор применяется для хранения местополо-
жения объекта в динамически распределяемой "куче" памяти под управлением CLR,
очищаемой сборщиком мусора. Позже вы более подробно узнаете о дескрипторах и
типе String'4, когда речь пойдет о типах классов C++/CLI; пока просто примите к
сведению, что этот тип применяется в объявлении переменных C++/CLI, предназна-
ченных для хранения строк.

Цикл for each, проходящий по символам строки, на которую ссылается proverb,
выглядит так:

 


Символы в строке proverb представлены в кодировке Unicode, поэтому для хране-
ния каждого из них применяется переменная типа wchar_t (эквивалент типа Char).
Цикл последовательно присваивает символы из строки proverb переменной цикла
ch, относящейся к типу C++/CLI Char. Эта переменная локальна по отношению к
циклу (другими словами, она существует только в блоке цикла). При первой итера-
ции ch содержит первый символ строки, при второй итерации — второй символ, при
третьей — третий и так далее, до тех пор, не будут обработаны все символы и цикл
завершен.

Внутри цикла определяется, является ли символ буквой, в следующем выражении



if:

Функция Char:: IsLetter () возвращает значение true, если аргумент (в данном
случае — ch) является буквой, и false — в противном случае. Таким образом, следую-
щий за if блок выполняется только в том случае, если ch содержит букву. Это необхо-
димо, поскольку вы не хотите, чтобы знаки препинания обрабатывались так, как если
бы они были буквами.

Установив, что ch — на самом деле буква, с помощью следующего оператора она
преобразуется в нижний регистр:



Решения и циклы 177

 

Здесь применяется функция Char:: ToLower () из библиотеки.NET Framework,
которая возвращает эквивалент своего аргумента (в данном случае — ch) в нижнем ре-
гистре. Если аргумент уже находится в нижнем регистре, функция просто возвращает
его без изменений. Преобразование символа в нижний регистр позволяет избежать
необходимости последовательно проверять его на вхождение в список гласных верх-
него и нижнего регистра.

Определение того, является ли ch гласной или согласной буквой, выполняется
внутри оператора switch:

 

 


В любом из пяти случаев, когда ch — гласная, вы увеличиваете значение счетчика
vowels; в противном случае увеличивается значение счетчика consonants. Оператор
switch выполняется для каждого символа в proverb, так что когда цикл завершается,
vowels содержит количество гласных в строке, a consonants — количество соглас-
ных. Затем результат выводится с помощью следующего оператора:

 


В последнем операторе значение vowels подставляется вместо {0} в строке, а зна-
чение consonants — вместо {1}. Это потому, что аргументы, следующие за первым
аргументом — строкой формата, нумеруются, начиная с 0.

 

Резюме

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

□ Базовые средства принятия решений основаны на наборе операций отноше-
ний, которые позволяют строить выражения проверки и сравнения, возвраща-
ющие в результате значения типа bool (true или false).

□ Решения можно принимать также на основе условий, возвращающих не булев-
ские значения. Любое ненулевое значение в проверочных условиях приводит-
ся к true, а нулевое значение — к false.

□ Основное средство принятия решений в С++ представлено оператором if.
Более высокая степень гибкости обеспечивается оператором switch и услов-
ной операцией.

□ В ISO/ANSI С++ предусмотрено три основных метода для повторного выпол-
нения блока операторов: цикл for, цикл while и цикл do-while. Цикл for по-
зволяет выполнять операторы, указанные в его теле, определенное количество
раз. Цикл while позволяет выполнять свои операторы до тех пор, пока его


проверочное условие возвращает true. И, наконец, цикл do-while выполняет
операторы как минимум один раз, а затем позволяет повторять их до тех пор,
пока указанное условие истинно.

□ В C++/CLI, в дополнение к трем перечисленным операторам циклов, пред-
усмотрена еще одна форма цикла — for each.

□ Циклы любого вида могут быть вложены в любые другие циклы.

□ Ключевое слово continue позволяет пропускать остаток операторов текущей
итерации цикла и сразу переходить к следующей итерации.

□ Ключевое слово break обеспечивает немедленный выход из цикла. Оно также
выполняет выход из switch в конце группы операторов case.

 

Упражнения

Исходные коды упражнений и их решения можно загрузить с Web-сайта издатель-
ства.

1. Напишите программу, которая читает из cin входные числа и суммирует их,
прекращая процесс после ввода 0. Сконструируйте три версии этой программы,
с использованием циклов while, do-while и for.

2. Напишите программу ISO/ANSI С++ для чтения символов с клавиатуры и под-
счета гласных. Программа должна прекращать работу при вводе Q (или q).
Используйте комбинацию бесконечного цикла для чтения символов с операто-
ром switch для их подсчета.

3. Напишите программу для распечатки таблицы умножения размером от 2 до 12
столбцов.

4. Предположим, что необходимо установить в программе переменную "режима
открытия файла" на базе двух атрибутов: типа файла, который может быть тек-
стовым или двоичным, и способа открытия файла — для чтения, записи или до-
бавления данных. Используя битовые операции (& и |), а также набор флагов,
предложите метод, позволяющий в одной целочисленной переменной устанав-
ливать любую комбинацию этих двух атрибутов. Напишите программу, которая
установит значение этой переменной, а затем декодирует его, распечатав все
установки флагов для всех возможных комбинаций атрибутов.

5. Воспроизведите пример ЕхЗ_2 в виде программы C++/CLI (можете использо-
вать Console:: ReadKey () для чтения символов с клавиатуры).

6. Напишите консольную программу CLR, которая определяет строку (типа
String'4) и затем анализирует ее символы для подсчета символов верхнего ре-
гистра, символов нижнего регистра, небуквенных символов и общего количе-
ства символов в строке.


 

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

 

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

□ Что такое массивы и как они применяются.

□ Как объявлять и инициализировать массивы различных типов.

□ Как объявлять и использовать многомерные массивы.

□ Что такое указатели и как их использовать.

□ Как объявлять и инициализировать указатели различных типов.

□ Отношения между указателями и массивами.

□ Ссылки, как они объявляются и некоторые начальные представления об их ис-
пользовании.

□ Как динамически выделять память для переменных в программах на "родном"
С++.

□ Как работает динамическое распределение памяти в программе CLR.

□ Отслеживание дескрипторов и ссылок и зачем они нужны в программах CLR.

□ Как работать со строками и массивами в программах C++/CLL

□ Что такое внутренние указатели, и как вы можете создавать и применять их.


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

 

Обработка множества однотипных
элементов данных

Вы уже знаете, как объявлять и инициализировать переменные различных типов,
каждая из которых содержит отдельный элемент информации. Я буду называть их
элементами данных. Вы знаете, как создать отдельный символ в переменной типа
char, отдельное целое число в переменной типа short, типа int, типа long, или же
отдельное число с плавающей точкой в переменной типа float или типа double.
Наиболее очевидное развитие этих идей — возможность ссылаться на несколько эле-
ментов данных определенного типа по одному имени переменной. Это позволило бы
вам управлять приложениями в более широком контексте.

Рассмотрим пример того, где это может понадобиться. Предположим, что вам
необходимо написать программу для ведения ведомости зарплат. Применение от-
дельных именованных переменных для каждой индивидуальной выплаты, связан-
ных с ней налоговых отчислений и так далее было бы, мягко говоря, непростой за-
дачей. Гораздо удобнее было бы справиться с этой проблемой, если бы можно было
ссылаться на наемного работника по какому-то обобщенному имени — к примеру,
employeeName — и также иметь другие обобщенные имена для различных данных,
связанных с каждым работником, вроде pay (выплата), tax (налог) и так далее. И, ко-
нечно же, нужен какой-то способ указания на конкретного работника из общей груп-
пы, вместе с данными из обобщенных переменных, которые его касаются. Подобные
требования возникают всегда, когда программа имеет дело с любыми коллекциями
однотипных данных — будь то члены баскетбольной команды или группа боевых ко-
раблей. Естественно, в С++ предусмотрены возможности для решения таких задач.

Массивы

Базовое решение всех этих проблем в ISO/ANSI С++ обеспечивают массивы.
Массив — это просто некоторое множество мест в памяти, называемых элементами
массива, или просто элементами, каждый из которых может хранить единицу дан-
ных определенного типа, и к которым можно обращаться по одному имени перемен-
ной. Имена сотрудников в программе платежной ведомости могут быть помещены
в один массив, суммы выплат — в другой, а суммы налогов каждого сотрудника — в
третий.

Индивидуальные элементы массива специфицируются значением индекса, кото-
рый представляет собой просто целое число — порядковый номер элемента в масси-
ве, причем первый имеет индекс 0, второй — 1 и так далее. Вы можете представлять
индекс массива как смещение от его первого элемента. Первый элемент имеет сме-
щение 0, а потому — индекс 0, а индекс 3 ссылается на четвертый элемент в массиве.
Что касается платежной ведомости, вы можете упорядочить массивы таким образом,
что если имя определенного сотрудника помещено в элемент массива employeeName
с определенным значением индекса, то массивы pay и tax также должны хранить
данные, ассоциированные с этим сотрудником, в элементах с тем же индексом.

Базовая структура массива проиллюстрирована на рис. 4.1.


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

 

На рис. 4.1 показан массив. Имя height содержит шесть элементов, каждый со-
держит отдельное значение. Это могут быть значения роста членов семьи, измерен-
ные в дюймах. Поскольку имеется шесть элементов, значение индекса находится в
пределах от 0 до 5. Чтобы сослаться на определенный элемент, вы пишете имя мас-
сива, за которым идет значение индекса определенного элемента, заключенное в ква-
дратные скобки. Так, например, чтобы сослаться на третий элемент, нужно написать
height [2]. Если вы представите индекс в виде смещения от первого элемента, то
легко увидеть, что значение индекса четвертого элемента будет равно 3.

Общий объем памяти, необходимый для хранения каждого элемента, определя-
ется его типом, и все элементы массива сохраняются в одном непрерывном блоке
памяти.

 

Объявление массивов

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

Поскольку каждое значение типа long занимает 4 байта памяти, весь массив по-
требует 24 байт. Массивы могут иметь любой размер — количество элементов огра-
ничено лишь объемом доступной памяти компьютера, на котором выполняется ваша
программа.

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

 


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



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

 

 

 

 


if(count <= 1) // count = 1 после ввода одной пары значений

{ //... необходимо минимум 2
cout «endl

«"Извините — необходимо минимум два измерения.";
return 0;

)

 

 

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


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

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

Как видим, во время второго периода, отображенного программой на выходе, ре-
жим движения был весьма неэкономным — вы либо много стояли в пробках, либо ча-
сто парковались.

Размеры двух массивов gas и miles, использованных для хранения входных дан-
ных, определялись значением константы по имени МАХ. Изменяя значение МАХ, вы
можете адаптировать программу для приема различного количества входных значе-
ний. Эта техника часто используется для обеспечения гибкости в отношении объема
обрабатываемой информации. Конечно, весь код программы должен быть написан с
учетом размеров массивов или любых других параметров, специфицированных пере-
менными const. На практике это предоставляет некоторое неудобство, однако, нет
причин, которые не позволили бы в определенной мере адаптировать такой подход.
Чуть позже вы увидите, как можно выделять память для хранения данных во время
выполнения программы, так что не придется фиксировать заранее определенный
объем памяти, выделенный для данных.

Ввод данных

Значения данных читаются в цикле while. Поскольку переменная-счетчик цикла
count может изменяться от 0 до МАХ - 1, мы не можем позволить пользователю про-
граммы ввести больше значений, чем может вместить массив. Вы инициализировали
переменные count и indicator значениями 0 и 1 у' соответственно, так что тело цик-
ла while будет выполнено, по крайней мере, один раз. Перед каждым вводом данных
выводится приглашение, и прочитанное значение записывается в соответствующий
элемент массива. Элемент, используемый для сохранения конкретного значения, опре-
деляется переменной count, которая при первом вводе содержит значение 0. Элемент
массива указывается в операторе cin посредством count в качестве индекса, и count
затем увеличивается на единицу, готовя к приему значения следующий элемент.

После ввода каждого значения программа запрашивает подтверждение намерения
продолжать ввод. Введенный символ читается в переменную indicator, затем про-
веряется в условии цикла. Цикл прерывается, если не будет введено ' у' или 1Y', или
же count достигнет значения МАХ.

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


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

Выдача результата

Вывод программы генерируется в цикле for. Управляющая переменная i изме-
няется от 0 до count-1, позволяя вычислять пробег как разницу между текущим по-
казанием miles [i] и предыдущим miles [i-1]. Обратите внимание, что значение
индекса может быть любым выражением, вычисление которого дает целочисленный
результат — корректное значение от нуля до величины, на единицу меньшей, чем ко-
личество элементов в массиве.

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

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

Инициализация массивов

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

int cubic_inches [5] = { 200, 250, 300, 350, 400 };

Массив называется cubic_inches, содержит пять элементов, каждый из кото-
рых хранит значение типа int. Элементы массива инициализированы списком
значений в фигурных скобках, соответствующим последовательности значений ин-
декса массива, поэтому в данном случае cubicinches [0] получает значение 2 00,
cubic_inches [1] — значение 250, cubic_inches [2] — значение 300 и так далее.

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


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



 

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

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

 

 


Вторая строка (соответствующая значениям от junk[0] до junk[4]) на вашем
компьютере может отличаться.

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

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

Удобный способ инициализации всего массива нулевыми значениями — это просто
указание единственного начального значения, равного 0, например:

long data[100] = {0}; // Инициализировать все элементы нулями

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


186 Глава 4

 

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

определяет массив из трех элементов с начальными значениями 2, 3 и 4.

Символьные массивы и обработка строк

Массив типа char называется символьным массивом и обычно используется для
хранения символьных строк. Символьная строка — это последовательность символов,
дополненная специальным символом-ограничителем, указывающим конец строки.
Ограничивающий строку символ записывается управляющей последовательностью
' \01, и иногда его называют нулевым символом, поскольку он представлен байтом, в
котором все биты равны 0. Строки, организованные подобным образом, часто назы-
вают строками в стиле С, поскольку такое определение строк впервые было представ-
лено в языке С, на основе которого Бьерн Страуструп разработал язык С++. Это не
единственное представление строк, которое вы можете использовать; позднее в этой
книге вы познакомитесь и с другими. В частности, программы C++/CLI используют
другое представление строк, а библиотека MFC определяет класс С St ring для пред-
ставления строк.

Представление строки в стиле С в памяти показано на рис. 4.2.


 

 


На рис. 4.2 можно видеть, как выглядит строка в памяти, и показана форма объяв-
ления строки, которую мы вскоре рассмотрим.

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

Вы можете объявить символьный массив и инициализировать его строчным лите-
ралом, например:

Обратите внимание, что ограничивающий символ '\0' добавляется компилято-
ром автоматически. Если вы включите его в строчный литерал явно, то у вас будет
два нулевых символа. Однако вы должны учитывать место для нулевого символа, ког-
да определяете необходимое количество элементов для символьного массива.

Вы можете позволить компилятору определить длину инициализированного мас-
сива самостоятельно, как вы видели на рис. 4.1. Вот другой пример:


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

 

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

Строковый ввод

Заголовочный файл <iostream> содержит определения множества функций для
чтения символов с клавиатуры. Одну их них мы рассмотрим здесь. Это — функция
get line (), которая читает последовательность вводимых с клавиатуры символов и
помещает ее в символьный массив в виде строки с ограничивающим нулем \0. Обычно
вы будете использовать функцию getline () в операторах вроде следующего:

 


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







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







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