Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Штерн В. - Основы C++. Методы программной инженерии - 2003

.pdf
Скачиваний:
238
Добавлен:
13.08.2013
Размер:
28.32 Mб
Скачать

140

Часть ! # Введение в програттироваиив на С^^

switch(ch) {

"Результат равен " «

ор1 +ор2 «

 

// обязательные фигурные скобки

case

'+"

cout «

endl;

case ' * cout «

"Результат равен " «

ор1 * ор2 «

endl;

case ' -

cout «

"Результат равен " «

ор1 - ор2 «

endl;

case

7'

if (ор2 != 0.0)

ор1 /ор2 «

endl;

 

cout «

"Результат равен " «

 

else

"Деление на ноль" « endl;

 

 

 

 

cout «

 

 

default: cout « "Недопустимая операция" « endl;

/ /

провал

 

exit(EXIT_FAILURE); }

 

exit(EXIT_SUCCESS);

 

 

/ /

все OK

Литеральные метки в ветвях case — это не переменные. Они представляют собой константы этапа компиляции (здесь ' + ' , ' * ' и т. д.). В случае оператора switch две метки case не могут быть одинаковыми (если только они не относятся к разным операторам switch).

При выполнении значение выражения switch (в данном случае переменная ch) сравнивается (сверху вниз) с литералами case. Если значение выражения совпа­ дает с меткой, то выполнение продолжается с операторов, следующих за этой меткой до конца оператора switch.

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

Введите операнд, операцию и другой операнд: 22+2 Результат равен 24 Результат равен 44 Результат равен 20 Результат равен 11 Недопустимая операция

Рис. 4 . 23 . Результаты

выполнения

программы

(некоррект,ной)

из лист^инга 4.27

Результат выполнения программы из листин­ га 4.27 показан на рис. 4.23. Оператор switch не представляет собой конструкцию с большим числом ветвей. Это многовходовая конструкция. Если нужна разветвленная структура, ее можно построить из оператора switch, используя опера­ торы break, goto или return, для завершения отдельной ветви. Листинг 4.28 показывает луч­ ший вариант нашего оператора switch. Результат программы будет такой же, как на рис. 4.22.

Листинг 4.28. Калькулятор с оператором switch (улучшенная программа)

#inclucle <iostream> #inclucle <cstcllib> using namespace std;

void main(void)

double op1, op2; char ch;

cout «

"Введите операнд, операцию и другой операнд:

cin »

ор1 » ch » ор2;

 

Глава 4 • Управление ходом вьшолнения nporpaiMiMbi C^'t-

141

switch(ch) {

 

"Результат равен " «

ор1 + ор2 «

// обязательные фигурные скобки

case '+' : cout «

endl;

 

 

break;

"Результат равен " «

ор1 * ор2 «

endl;

 

case '*' : cout «

 

 

break;

"Результат равен " «

ор1 - ор2 «

endl;

 

case '-' : cout «

 

case V

break;

 

 

 

 

 

 

 

: if

(op2 != 0.0)

 

 

 

 

 

 

cout

«

"Результат равен "

«

op1 / op2 « endl;

 

 

else

 

 

 

 

 

 

 

 

cout

«

"Деление на ноль" «

 

endl;

 

 

 

 

break;

 

 

 

 

 

 

 

default:

cout «

"Недопустимая операция" «

endl;

 

 

 

 

break;

}

 

 

 

 

/ /

здесь break необязателен

 

exit(EXIT_SUCCESS);

 

 

 

 

/ /

следующий оператор

 

}

Оператор break внутри switch передает управление следующему оператору после закрываюндей скобки switch. Оператор exitO завершает функцию, как и ранее.

Для передачи управления из оператора switch можно использовать оператор goto, но в большинстве случаев достаточно оператора break. В некоторых про­ граммах break помещается даже перед закрывающей фигурной скобкой switch. Такой break бесполезен, но может предотвратить ошибки, если к оператору switch добавляются другие ветви, а break при этом не вставляется. Не столь важный момент, но всегда полезно облегчить работу тому, кто будет сопровождать программу.

Многовходовость оператора switch (а не наличие большого числа ветвей) можно использовать, чтобы избежать повтора кода, когда в нескольких ветвях требуется одна и та же обработка. Возьмем, например, переменную response, которая содержит ответ пользователя на запрос приложения. Предположим, нужно выполнять какое-то одно действие, когда пользователь вводит ' у' или ' У , и другое, когда вводится ' п' или ' N'. И даже третье, когда получен иной ответ. Оператор switch для обработки ответа пользователя может выглядеть так:

switch (responce) { case 'у' : case 'Y' :

cout « "Спасибо за подтверждение\п"; break; case ' n ' : case ' N ' :

cout « "Запрос отменен\п"; break; default: cout « "Некорректный ответ\п"; }

Если, например, получен ответ ' у', то выполняется неявный пустой оператор между case 'у': и case 'Y':, а затем оператор за следующей меткой (в данном случае 'У').

Конечно, то же самое можно сделать с помощью последовательности опера­ торов условия, но оператор switch делает это лучше — программу легче читать, а за ее выполнением проще проследить. Очень мощное инструментальное средство.

I 142

Итоги

Часть I • Введение в програтытрошоитв на C-^'i-

Здесь много рассказывалось об управляюндих конструкциях языка C+ + . Этот язык содержит традиционные операторы условия и циклы, позволяющие про­ граммисту представлять сложные алгоритмы. Уникальная особенность С+Н возможность помещать логические выражения в операторы условия и циклы. В сочетании со способностью C++ интерпретировать любое ненулевое значение как true программист получает в свое распоряжение сильный инструмент для написания лаконичного и мощного кода.

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

Другие управляющие конструкции C++, переходы и переключатели, не так необходимы для создания программ, как условные операторы и циклы. Можно написать на C++ вполне работоспособный код и без них. Но они неоценимы как инструмент вашего профессионального мастерства. Без применения данных операторов и их корректного использования ваш код нельзя будет считать профес­ сиональной программой C+ + , так что не забудьте выделить время на изучение и практическое освоение этих элементов языка.

^ # / ^ J

/ \ агрегирование с помощью типов данных^

определяемых программистом

Темы данной главы

^Массивы как однородные агрегаты

^Структуры кок неоднородные агрегаты

^Объединения, перечисления и битовые поля %/ Итоги

I ^ ^ X предыдущей главе рассказывалось об инструментальных средствах

Ж^'[Якдяя реализации алгоритмов в C++ . С помощью условных операторов,

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

инаучимся писать хорошо спроектированные программы C++ . Здесь обсуждается расширение набора имеющихся в языке типов данных.

C + + позволяет программисту определять совокупности данных: массивы (однородные совокупности), структуры (неоднородные) и производные типы. Как уже говорилось, они иногда называются типами, определяемыми пользователем. Это точка зрения разработчика компилятора, а не программиста. Для програм­ миста пользователь — тот, кто выполняет программу (или работает с ее резуль­ татами). Вот почему здесь типы данных, определяемые в программе, называются

типами, определяемыми программистом.

В программе можно определять переменные типов (еще они называются объектами) точно так же, как и переменные встроенных типов (целые, символь­ ные и пр.).

Для определения переменных в C + + используется тот же синтаксис. Правила работы с ними те же. Фактически программист может просто расширить набор типов данных C+ + , добавив собственные типы данных. Определяемые програм­ мистом типы данных добавляются к стандартным типам. Их можно использовать как блоки для определения новых, еще более сложных типов. Данная глава посвя­ щена обсуждению массивов, структур и их вариаций: объединений, битовых полей и перечислений.

Классы C + + представляют собой совокупности данных и функций, но к ним можно будет перейти только после того, как мы подробнее обсудим функции C++ . Описание функций уже содержалось в главе 2 — этого достаточно для понимания основных концепций функций, но не для понимания классов и способов их исполь­ зования.

Щ 144 I

Часть i ^ Введение в г1рогра1\л1^ирование на С^-^

Здесь приведено больше базовой информации, а обсуждаемый материал раз­ нообразнее и сложнее. Возможно, приступить к изучению классов будет проще, если сосредоточиться на массивах (только одномерных) и структурах (но не иерар­ хических). Объединения и битовые поля — методы программирования, имеющие мало общего с классами. Это не значит, что они не важны. К данной главе можно вернуться, когда вам потребуется расширить навыки программирования.

Насчет перечислений сказать трудно. Формально для понимания классов они не нужны, но программисты часто используют перечисления для определения раз­ меров компонентов класса. В главе 9 даны некоторые примеры перечислений. Они интуитивно понятны, но если потребуется более углубленное описание пере­ числений, лучше вернуться к этой главе и найти их здесь.

Массивы как однородные агрегаты

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

Массивы как векторы значений

Обычные переменные, о которых рассказывалось в главе 3, называют скаля­ рами или элементарными (простыми) переменными.

Они характеризуются одним значением. Иногда желательно различать отдель­ ные компоненты значения, например целую и дробную часть числа с плавающей точкой. Но в языке такое различие не поддерживается. Он интерпретирует пере­ менные как единое целое, а не нечто, состоящее из компонентов. Вот почему эти переменные называются скалярными или элементарными. Чтобы выделить отдельные части из числа с плавающей точкой, нужно придумать код C+ + , кото­ рый это делает. Он не слишком сложный (имеются библиотечные функции), но заранее определенного в языке способа нет.

fraction = X - floor(x); / / получить дробную часть х

Здесь X и fraction — числа с плавающей точкой, а floor() — функция, опреде­ ленная в библиотечном заголовочном файле math.h (или cmath), она возвращает преобразованное в double максимальное целое, не превышающее аргумента. Сам язык интерпретирует значения встроенных типов как элементарные.

Массивы представляют собой векторы. Их состояние характеризуется набором значений, а не одним значением. К каждому значению — компоненту можно обра­ щаться с помощью специальной записи C++ (операции индекса). '

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

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

Глава 5 • Агрегирование с помощью типов, определяемых профоммистом

145

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

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

Иногда размер массива уже точно известен заранее. Например, массив, состояш,ий из рабочих часов разных дней недели, содержит семь компонентов. То же относится к компонентам, содержаш,им число дней в месяце. Другие массивы могут представлять шахматную доску со всеми ее клетками. В большинстве случаев приходится искать разумный компромисс, выделяя больше элементов, чем может потребоваться, но не слишком много (например, двойное количество). Такое ре­ шение "разумно", поскольку поддерживается кодом, проверяюш,им переполнение и предпринимаюш,им "разумные" действия, когда оно происходит. Для некоторых "разумность" может означать завершение программы, для других — завершение ввода с уведомлением пользователя.

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

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

Определение массивов в С+ +

Как и любая переменная C+ + , переменная типа массива должна предвари­ тельно определяться и лишь затем использоваться. Определение массива свя­ зывает имя массива с элементами массива и числом элементов. Как и любое определение, определение массива приводит к выделению памяти во время вы­ полнения, как и другие определения, оно заканчивается точкой с запятой. Масси­ вы можно определять по одному на каждой строке или комбинировать на одной строке несколько определений, например:

i nt hours[7]; char grade[35]; double amount[20];

146

Часть i ^ Введение в програттироваиыв на Сл-^

На этой строке определяются три массива: массив hours[] из 7 целочисленных компонентов, массив gracle[] из 35 символьных компонентов и массив amount[] из 20 компонентов с плаваюш^ей точкой двойной точности. Обратите внимание на добавление к имени массива квадратных скобок. Такое обозначение в тексте говорит о том, что речь идет о векторе с несколькими значениями, а не о скаляре

содним значением.

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

и скалярных типов, если их типы совпадают, например:

i nt category[7], i , num, scores[35], n;

Некоторые программисты выбирают имена массивов, используя множествен­ ное число. Когда массив передается функции в качестве параметра, можно сказать, что функция получает не одно значение, а набор значений, например sum (scores). Другие предпочитают использовать имена в единственном числе. При ссылке на отдельные элементы массива по индексу, например category[i], естественнее именовать их именно в единственном числе. Вообш,е говоря, это не столь важно.

Размер массива должен быть известен во время компиляции, но он не обяза­ тельно задается литеральным значением. Можно использовать определенный в #clefine символьный литерал, целочисленную константу или целочисленное выражение любой сложности. Единственное требование состоит в том, чтобы это выражение вычислялось на этапе компиляции, а на не этапе выполнения. Например:

#clefine MAX_RATES 35

/ /

размер массива определен как значение #define

int

const

NUM_ITEMS = 10;

/ /

размер массива - константа

int

rates

[MAX_RATES]; double

amount[2*NUM_ITEMS];

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

int hours[7] = { 8,

8, 12, 8. 4, О,

О };

/ /

7 значений

 

int side[5] = (40,35,41 } ;

 

/ /

другие элементы массива - это нули

char

option[2] = {

*Y',

'N*, ' y ' ,

};

/ /

синтаксическая

ошибка

int

week[52] = { ,

, 40,

48 };

 

/ /

синтаксическая

ошибка

Первое значение инициализирует первый компонент массива, второе — вто­ рой и т. д. Начальные значения должны иметь тип, совпадаюш,ий с типом массива; если типы разные, должно обеспечиваться преобразование ме>кду ними. Это те же преобразования смешанных типов и выражений, о которых уже рассказывалось в главе 3. (Например, допускается инициализация компонентов массива типа double с помош^ью целочисленных значений.)

В данных примерах подставляются значения для каждого компонента массива hours[]. Подставляемых значений может быть меньше числа элементов массива, как для массива side[]. Компоненты в этом случае инициализируются, начиная с первого, пока не исчерпается весь список. Компоненты, оставшиеся без значе­ ний, инициализируются нулем соответствуюш,его типа. Не следует задавать инициализируюш,их значений больше, чем число элементов в массиве, как для массива options[]. Нельзя пропускать некоторые компоненты, используя запятые, как сделано для массива week[]. JCL (Job Control Language) допускает такой синтак­ сис, но С+Н не JCL.

Глава 5 • Агрегирование с помощью типов, определяемых профоммистом

[ 147 g

Как и скалярную переменную, переменную-массив, определенную в одном файле, можно использовать в алгоритмах другого файла. Для этого в данном файле должна объявляться переменная с тем же именем. Основная разница между определением и объявлением массива в том, что в объявлении не задается размер массива. При объявлении массива для него не выделяется память. (Это задача определения массива.) Хотя в С4-+ объявления и определения похожи, программист должен различать их.

Например, в некоторых других файлах могут потребоваться значения компо­ нентов массива hours[ ] или понадобится вычислять эти значения для присваива­ ния. В этом файле массив hours[] можно определить так:

extern int hours[]; / / объявление: память не выделяется

Чтобы это объявление было допустимым, исходное определение массива hours[ ] следует разместить вне любой функции как глобальную переменную.

Аналогично объявлениям скалярных переменных, объявления массива исполь­ зуются для присваивания массиву адреса в памяти. После этого программа в дан­ ном файле может обращаться к элементам массива hours[ ], как будто массив был определен в том же файле. Поскольку объявления массивов (как и любые другие объявления) не вьщеляют память, инициализации они не поддерживают.

C++ позволяет программисту использовать синтаксис объявления для опреде­ ления массивов. Это делается, когда размер массива задается числом инициализа­ торов, а не явной константой этапа компиляции. Например:

double rates[] = { 1.0, 1.2, 1.4 };

/ / три элемента

Здесь выделяются и инициализируются три элемента массива, несмотря на запись в объявлении rates[]. Такое определение аналогично следуюш.ему:

double rates[3] = { 1.0, 1.2, 1.4 };

/ / явный счетчик

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

Один из способов решения данной проблемы состоит в вычислении количества элементов массива с помощью операции sizeof (см. главу 3). Разделив размер массива на размер одного компонента, можно получить число элементов массива.

int num = sizeof(rates) / sizeqf(double);

Обратите внимание на последовательность тем в обсуждении массивов C+ + . Она аналогична обсуждению средств определения других данных. Каждый раз рассказывается о смысле очередного средства C++ (переменных в главе 3, мас­ сивов в этой главе, затем структур, классов, составных и производных классов), речь идет о синтаксисе определений (и объявлений), а затем о вопросах инициали­ зации. Эта последовательность не случайна. Инициализация имеет очень важное значение в C++, и мы будем изучать методы инициализации, соответствующие каждому виду использования памяти в C++.

Операции с массивами

За обсуждением инициализации неизбежно следует описание операций с мас­ сивами. Что можно с ними делать? C++ предлагает для этого достаточно ограни­ ченный набор средств. Здесь нельзя присвоить одну переменную-массив другой, недопустимо их сравнивать, складывать, умножать и т. д. Единственное, что мож­ но делать с массивами,— передавать их функции как аргумент. Таким образом, чтобы присвоить один массив другому, сравнить два массива и т.д., придется написать собственный код или использовать библиотеку функций.

I 148 I

Часть I« Введение в oporpaivi^iipOBaHne на C-^-^

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

Например, sicle[2] обозначает элемент массива side с индексом 2. В любом случае sicle[2] — обычная скалярная целочисленная переменная. Поскольку sicle[] — это массив целых значений, с side[2] можно делать все то, что разре­ шается делать с целочисленной переменной как с 1-значением или г-значением. Различие только в имени — вместо идентификатора целочисленной переменной используется имя массива, плюс индекс и операция индекса:

side[2] = 40;

/ /

используется как 1-значение

num = side[2] * 2;

/ /

используется как г-значение

На первой строке side[2] получает значение 40, которое сохраняется по соот­ ветствующему адресу. На второй — значение по адресу side[2] умножается на 2 и результат сохраняется в переменной num (она должна быть числовой). Как видно, отдельные элементы массива не имеют своих имен. Их имена составляются из имени массива и значения индекса.

В C+-f используется вполне обычное обозначение элементов массива. Необыч­ но лишь то, что С+4- интерпретирует квадратные скобки как операцию, а не просто как обозначение элемента. Эта операция имеет высокий приоритет — она находится в верхней части таблицы операций C+-f (см. таблицу 3.1). Как у любой операции, у нее есть операнды — имя массива и значение индекса. Эта операция применяется к имени side и значению 2, а результатом будет side[2] — имя компонента массива.

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

Индекс не обязан быть литеральным значением и даже значением, известным на этапе компиляции. В качестве индекса может применяться любое числовое выражение. Если выражение имеет тип с плавающей точкой, символьный, short или long, оно преобразуется в целое. Здесь, например, на этапе выполнения вы­ зывается функция foo(), а возвращаемое ею значение используется для вычисле­ ния индекса:

side[3*foo()] = 40; / / допустимо?

Чтобы это было законно, нужно определить функцию foo(), а возвращаемое ею значение (индекс) должно быть определено в диапазоне индексов. Если значения присвоены только части элементам массива, то данный индекс должен быть ин­ дексом одного из этих элементов. Когда значения присвоены всем элементам мас­ сива, индекс должен лежать в границах между первым и последним элементом. Индексы вне границ массива будут ссылаться на не принадлежащие ему ячейки памяти и, следовательно, они не должны рассматриваться как элементы массива.

Проверка допустимости индекса

А теперь — внимание... Программист не*" может произвольно выбирать диа­ пазон значений индексов массивов. В С+Н- они фиксированы. Это. неприятно, поскольку часто желательно придать индексу некий смысл. Например, хочется хранить в массиве revenue[] данные по обороту с 1997 по 2006 гг. Было бы удобно использовать данный диапазон как значения индексов. Другие языки позволяют программисту выбирать диапазоны индексов, но в C + + это не так.

Глава 5 • Агрегирование с помощью типов, определяемых профаммистог1Л

| 149 |

 

Интересы разработчиков компилятора взяли верх над интересами прикладных

 

программистов. В C + + диапазон индексов не только фиксирован. Индексы здесь

 

начинаются с 0.

 

 

 

 

 

 

 

Да, индекс первого компонента любого массива С+Н

О, а не 1. Это очень

 

важно.

 

 

 

 

 

 

 

Например, если массив sicle[] содержит пять компонентов, то допустимыми

 

элементами массива будут sicle[0], sicle[1], side[2], side[3] и sicle[4]. Обратите

 

внимание, что side[5] не будет допустимым элементом массива.

 

 

 

Что случится, если по ошибке написать side[-l], side[5] или side[6]? Сооб-

 

ш,ит ли компилятор о такой ошибке? Нет. Значением индекса может быть зна­

 

чение этапа выполнения, на момент компиляции не известное. Разработчики

 

компилятора отказались от такой проверки. Даже если индекс при компиляции

 

представляет собой литеральное значение, которое легко проверить, компилятор

 

этого не делает. Допустимость индекса

не проверяется — C + +

предоставляет

 

данную задачу программисту.

 

 

 

 

 

 

 

Если в программе написано side[-l], то это, наверное, что-то значит, и в обя­

 

занности компилятора не входит разгадывать намерения программиста. Так что

 

встроенная проверка допустимости индексов на этапе компиляции не предусмот­

 

рена.

 

 

 

 

 

 

 

А есть ли проверка на этапе выполнения? Ведь некоторые другие языки про­

 

веряют на допустимость каждую ссылку на компоненты массива. Но не C+ + .

,

Такая проверка индекса или индексного выражения влияет на производительность,

 

а в C + + это "свяш,енная корова". А если в вашей программе производительность

 

не критична и желательно проверять допустимость индексов при выполнении

 

программы — нет проблем. Сделайте это сами — сравните значения

индексов

 

с границами массива. Встроенной проверки индексов нет.

 

 

 

 

 

Вероятно, при разработке языка предполагалось, что программисту не требу­

 

ется помош,ь со стороны компилятора и системы выполнения — он знает, что

 

делает. Нет нужды говорить, что такое предположение безосновательно, и ошибки

 

в индексах — частый источник проблем для программистов, работаюш^их с C+ + .

 

Причина такой негибкости (унаследованной из С) в том, что в качестве адреса

 

первого элемента массива используется имя массива. Смеидение первого элемен­

 

та от начала равно нулю. Смеш,ение второго элемента равно длине элемента

 

(она зависит от типа), смеш^ение третьего — двум длинам. Компилятору известен

 

размер элемента, и прош,е вычислить адрес элемента по смеш,ению, а не по его

 

позиции в массиве.

 

 

 

 

 

 

 

Когда значение индекса недопустимо, для вычисления адреса компонента мас­

 

сива в памяти компилятор все равно использует индекс как смеш.ение. В результа­

 

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

 

полезных целях, это может пройти незамеченным.

 

 

 

 

 

 

О с т о р о ж н о ! в C++ нет проверки допустимости индекса на этапе компиляции.

 

Нет ее и на этапе выполнения. Содержимое памяти может быть запорчено

 

программой. Будьте внимательны!

 

 

 

 

 

 

 

Рассмотрим некоторые последствия

ошибок

40

41

42

43

13540

 

при обработке индексов. В листинге 5.1

показана

 

 

 

 

 

 

 

программа, корректно присваиваюш^ая

значения

 

 

 

 

 

 

сторонам многоугольника, но некорректно их рас-

Рис.

 

 

 

 

 

печатываюш,ая. Первое значение индекса равно 1,

5 . 1 .

 

 

 

а последнее — 5. Результат выполнения програм-

Вывод

показывает

 

мы показан на рис. 5.1.

 

«^"^''^

«

программе

Соседние файлы в предмете Программирование на C++