Программирование на C / C++ / Ален И. Голуб. Правила программирования на Си и Си++ [pdf]
.pdfС++ для начинающих |
182 |
|||
|
|
#inc1ude <iostream> |
|
|
|
|
|
||
|
|
#inc1ude "iStack.h" |
|
|
|
|
int main() { |
|
|
|
|
|
iStack stack( 32 ) ; |
|
|
|
|
stack.display(); |
|
|
|
|
for ( int ix = 1; ix < 51; ++ix ) |
|
|
|
{ |
|
|
|
|
|
if ( ix%2 == 0 ) |
|
|
|
|
stack.push( ix ); |
|
|
|
|
if ( ix%5 == 0 ) |
|
|
|
|
stack.display(); |
|
|
|
|
if ( ix%10 == 0 ) { |
|
|
|
|
int dummy; |
|
|
|
|
stack.pop( dummy ); stack.pop( dummy ); |
|
|
|
|
stack.display(); |
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|||
|
|
|
|
|
Вот результат работы программы: |
|
|||
|
|
|
|
|
|
|
|
( 0 )( bot: :top ) |
|
|
|
|
iStack push( 2 ) |
|
|
|
|
iStack push( 4 ) |
|
|
|
|
( 2 )( bot: 2 4 :top ) |
|
|
|
|
iStack push( 6 ) |
|
|
|
|
iStack push( 8 ) |
|
|
|
|
iStack push ( 10 ) |
|
|
|
|
( 5 )( bot: 2 4 6 8 10 :top ) |
|
|
|
|
iStack pop(): 10 |
|
|
|
|
iStack pop(): 8 |
|
|
|
|
( 3 )( bot: 2 4 6 :top ) |
|
|
|
|
iStack push( 12 ) |
|
|
|
|
iStack push( 14 ) |
|
|
|
|
( 5 )( bot: 2 4 6 12 14 :top ) |
|
|
|
|
iStack::push( 16 ) |
|
|
|
|
iStack::push( 18 ) |
|
|
|
|
iStack::push( 20 ) |
|
|
|
|
( 8 )( bot: 2 4 6 12 14 16 18 20 :top ) |
|
|
|
|
iStack::pop(): 20 |
|
|
|
|
iStack::pop(): 18 |
|
|
|
|
( 6 )( bot: 2 4 6 12 14 16 :top ) |
|
|
|
|
iStack::push( 22 ) |
|
|
|
|
iStack::push( 24 ) |
|
|
|
|
( 8 )( bot: 2 4 6 12 14 16 22 24 :top ) |
|
|
|
|
iStack::push( 26 ) |
|
|
|
|
iStack::push( 28 ) |
|
|
|
|
iStack::push( 30 ) |
|
|
|
|
( 11 )( bot: 2 4 6 12 14 16 22 24 26 28 30 :top ) |
|
|
|
|
iStack::pop(): 30 |
|
|
|
|
iStack::pop(): 28 |
|
|
|
|
( 9 )( bot: 2 4 6 12 14 16 22 24 26 :top ) |
|
|
|
|
iStack::push( 32 ) |
|
|
|
|
iStack::push( 34 ) |
|
|
|
|
( 11 )( bot: 2 4 6 12 14 16 22 24 26 32 34 :top ) |
|
|
|
|
iStack::push( 36 ) |
|
|
|
|
iStack::push( 38 ) |
|
|
|
|
iStack::push( 40 ) |
|
|
|
|
( 14 )( bot: 2 4 6 12 14 16 22 24 26 32 34 36 38 40 :top ) |
|
|
|
|
iStack::рор(): 40 |
|
|
|
|
iStack::popQ: 38 |
|
|
|
|
( 12 )( bot: 2 4 6 12 14 16 22 24 26 32 34 36 :top ) |
|
|
|
|
iStack::push( 42 ) |
|
|
|
|
iStack::push( 44 ) |
|
|
|
|
( 14 )( bot: 2 4 6 12 14 16 22 24 26 32 34 36 42 44 :top ) |
|
|
|
|
iStack::push( 46 ) |
|
|
|
|
iStack::push( 48 ) |
|
С++ для начинающих |
|
|
|
|
|
|
183 |
||
|
|
|
|
|
|
|
|
|
|
|
iStack::push( 50 |
) |
12 14 16 |
22 |
24 26 32 34 |
36 |
42 44 46 |
48 50 :top ) |
|
|
( 17 )( bot: 2 4 |
6 |
|
||||||
|
iStack::pop(): 50 |
|
|
|
|
|
|
|
|
|
iStack::pop(): 48 |
12 14 16 |
22 |
24 26 32 34 |
36 |
42 44 46 |
:top ) |
|
|
|
( 15 )( bot: 2 4 |
6 |
|
Упражнение 4.23
Иногда требуется операция peek(), которая возвращает значение элемента на вершине стека без извлечения самого элемента. Реализуйте функцию peek() и добавьте к программе main() проверку работоспособности этой функции.
Упражнение 4.24
В чем вы видите два основных недостатка реализации класса iStack? Как их можно исправить?
С++ для начинающих |
184 |
5. Инструкции
Мельчайшей независимой частью С++ программы является инструкция. Она соответствует предложению естественного языка, но завершается точкой с запятой (;), а не точкой. Выражение С++ (например, ival + 5) становится простой инструкцией, если после него поставить точку с запятой. Составная инструкция – это последовательность простых, заключенная в фигурные скобки. По умолчанию инструкции выполняются в порядке записи. Как правило, последовательного выполнения недостаточно для решения реальных задач. Специальные
управляющие конструкции позволяют менять порядок действий в зависимости от некоторых условий и повторять составную инструкцию определенное количество раз. Инструкции if, if-else и switch обеспечивают условное выполнение. Повторение обеспечивается инструкциями цикла while, do-while и for.
5.1. Простые и составные инструкции
Простейшей формой является пустая инструкция. Вот как она выглядит:
; // пустая инструкция
Пустая инструкция используется там, где синтаксис С++ требует употребления инструкции, а логика программы – нет. Например, в следующем цикле while, копирующем одну строку в другую, все необходимые действия производятся внутри круглых скобок (условной части инструкции). Однако согласно правилам синтаксиса С++ после while должна идти инструкция. Поскольку нам нечего поместить сюда (вся работа
while ( *string++ = inBuf++ )
уже выполнена), приходится оставить это место пустым:
; // пустая инструкция
Случайное появление лишней пустой инструкции не вызывает ошибки компиляции. Например, такая строка
ival = dval + sval;; // правильно: лишняя пустая инструкция
состоит из двух инструкций – сложения двух величин с присваиванием результата переменной ival и пустой.
Простая инструкция состоит из выражения, за которым следует точка с запятой.
// простые инструкции
int ival = 1024; // инструкция определения переменной ival; // выражение
ival + 5; // еще одно выражение
Например:
С++ для начинающих |
185 |
ival = ival +5; // присваивание
Условные инструкции и инструкции цикла синтаксически требуют употребления единственной инструкции, связанной с ними. Однако, как правило, этого недостаточно. В таких случаях употребляются составные инструкции – последовательность простых,
if ( ival0 > ival1 ) {
//составная инструкция, состоящая
//из объявления и двух присваиваний
int temp = ivalO; ivalO = ival1; ival1 = temp;
заключенная в фигурные скобки:
}
Составная инструкция может употребляться там же, где простая, и не нуждается в завершающей точке с запятой.
Пустая составная инструкция эквивалентна пустой простой. Приведенный выше пример
while ( *string++ = *inBuf++ )
с пустой инструкцией можно переписать так:
{} // пустая инструкция
Составную инструкцию, содержащую определения переменных, часто называют блоком. Блок задает локальную область видимости в программе – идентификаторы, объявленные внутри блока (как temp в предыдущем примере), видны только в нем. (Блоки, области видимости и время жизни объектов рассматриваются в главе 8.)
5.2. Инструкции объявления
ВС++ определение объекта, например int ival;
рассматривается как инструкция объявления (хотя в данном случае более правильно было бы сказать определения). Ее можно использовать в любом месте программы, где разрешено употреблять инструкции. В следующем примере объявления помечены комментарием //#n, где n – порядковый номер.
С++ для начинающих |
186 |
#include <fstream> #include <string> #include <vector> int main()
{
string fileName; // #1
cout << "Введите имя файла: "; cin >> fileName;
if ( fileName.empty() ) { // странный случай
cerr << "Пустое имя файла. Завершение работы.\n"; return -1;
}
ifstream inFile( fileName.c_str() ); // #2 if ( ! inFile ) {
cerr << "Невозможно открыть файл.\n"; return -2;
} |
|
string inBuf; |
// #3 |
vector< |
string > text; // #4 |
while ( |
inFile >> inBuf ) { |
for |
( int ix = 0; ix < inBuf .size(); ++ix ) // #5 |
//можно обойтись без ch,
//но мы использовали его для иллюстрации
if (( char ch = inBuf[ix] )=='.'){ // #6 ch = '_';
inBuf[ix] = ch;
}
text.push_back( inBuf );
}
if ( text.empty() ) return 0;
//одна инструкция объявления,
//определяющая сразу два объекта vector<string>::iterator iter = text.begin(), // #7
iend = text.end();
while ( iter != -iend ) { cout << *iter << '\n'; ++iter;
}
return 0;
}
Программа содержит семь инструкций объявления и восемь определений объектов. Объявления действуют локально; переменная объявляется непосредственно перед первым использованием объекта.
В 70-е годы философия программирования уделяла особое внимание тому, чтобы определения всех объектов находились в начале программы или блока, перед исполняемыми инструкциями. (В С, например, определение переменной не является инструкцией и обязано располагаться в начале блока.) В некотором смысле это была реакция на идиому использования переменных без предварительного объявления, чреватую ошибками. Такую идиому поддерживал, например, FORTRAN.
С++ для начинающих |
187 |
Поскольку в С++ объявление является обычной инструкцией, ему разрешено появляться в любом месте программы, где допустимо употребление инструкции, что дает возможность использовать локальные объявления.
Необходимо ли это? Для встроенных типов данных применение локальных объявлений является скорее вопросом вкуса. Язык их поощряет , разрешая объявлять переменные внутри условных частей инструкций if, if-else, switch, while, for. Те программисты, которые любят этот стиль, верят, что таким образом делают свои программы более понятными.
Локальные объявления становятся необходимостью, когда мы используем объекты классов, имеющие конструкторы и деструкторы. Если мы помещаем все объявления в начало блока или функции, происходят две неприятные вещи:
∙конструкторы всех объектов вызываются перед исполнением первой инструкции блока. Применение локальных объявлений позволяет “размазать” расходы на инициализацию по всему блоку;
∙что более важно, блок или функция могут завершиться до того, как будут действительно использованы все объявленные в начале объекты. Скажем, наша программа из предыдущего примера имеет два аварийных выхода: при вводе
пользователем пустого имени файла и при невозможности открыть файл с заданным именем. При этом последующие инструкции функции уже не выполняются. Если бы объекты inBuf и next были объявлены в начале блока,
конструкторы и деструкторы этих объектов в случае ненормального завершения функции вызывались бы совершенно напрасно.
Инструкция объявления может состоять из одного или более определений. Например, в
//одна инструкция объявления,
//определяющая сразу два объекта vector<string>::iterator iter = text.begin(),
нашей программе мы определяем два итератора вектора в одной инструкции: lend = text.end();
vector<string>::iterator iter = text.begin();
Эквивалентная пара, определяющая по одному объекту, выглядит так: vector<string>::iterator lend = text.end();
Хотя определение одного или нескольких объектов в одном предложении является скорее вопросом вкуса, в некоторых случаях – например, при одновременном определении объектов, указателей и ссылок – это может спровоцировать появление ошибок. Скажем, в следующей инструкции не совсем ясно, действительно ли программист хотел определить
указатель и объект или просто забыл поставить звездочку перед вторым
// то ли хотел определить программист?
идентификатором (используемые имена переменных наводят на второе предположение): string *ptrl, ptr2;
С++ для начинающих |
188 |
string *ptr1;
Эквивалентная пара инструкций не позволит допустить такую ошибку: string *ptr2;
В наших примерах мы обычно группируем определения объектов в инструкции по
int aCnt=0, eCnt=0, iCnt=0, oCnt=0, uCnt=0;
сходству употребления. Например, в следующей паре int charCnt=0, wordCnt=0;
первая инструкция объявляет пять очень похожих по назначению объектов – счетчиков пяти гласных латинского алфавита. Счетчики для подсчета символов и слов определяются во второй инструкции. Хотя такой подход нам кажется естественным и удобным, нет никаких причин считать его хоть чем-то лучше других.
Упражнение 5.1
Представьте себе, что вы являетесь руководителем программного проекта и хотите, чтобы применение инструкций объявления было унифицировано. Сформулируйте правила использования объявлений объектов для вашего проекта.
Упражнение 5.2
Представьте себе, что вы только что присоединились к проекту из предыдущего упражнения. Вы совершенно не согласны не только с конкретными правилами использования инструкций объявления, но и вообще с навязыванием каких-либо правил для этого. Объясните свою позицию.
5.3. Инструкция if
Инструкция if обеспечивает выполнение или пропуск инструкции или блока в
if ( условие )
зависимости от условия. Ее синтаксис таков:
инструкция
условие заключается в круглые скобки. Оно может быть выражением, как в этом примере:
if(a+b>c) { ... }
или инструкцией объявления с инициализацией:
if ( int ival = compute_value() ){...}
С++ для начинающих |
189 |
Область видимости объекта, объявленного в условной части, ограничивается ассоциированной с if инструкцией или блоком. Например, такой код вызывает ошибку
if ( int ival = compute_value() ) {
//область видимости ival
//ограничена этим блоком
}
// ошибка: ival невидим
компиляции:
if ( ! ival ) ...
Попробуем для иллюстрации применения инструкции if реализовать функцию min(), возвращающую наименьший элемент вектора. Заодно наша функция будет подсчитывать число элементов, равных минимуму. Для каждого элемента вектора мы должны проделать следующее:
1.Сравнить элемент с текущим значением минимума.
2.Если элемент меньше, присвоить текущему минимуму значение элемента и сбросить счетчик в 1.
3.Если элемент равен текущему минимуму, увеличить счетчик на 1.
4.В противном случае ничего не делать.
5.После проверки последнего элемента вернуть значение минимума и счетчика.
if ( minVal > ivec[ i ] )...// новое значение minVal
Необходимо использовать две инструкции if:
if ( minVal == ivec[ i ] )...// одинаковые значения
Довольно часто программист забывает использовать фигурные скобки, если нужно
if ( minVal > ivec[ i ] ) minVal = ivec[ i ];
выполнить несколько инструкций в зависимости от условия: occurs = 1; // не относится к if!
Такую ошибку трудно увидеть, поскольку отступы в записи подразумевают, что и minVal=ivec[i], и occurs=1 входят в одну инструкцию if. На самом же деле
инструкция
occurs = 1;
не является частью if и выполняется безусловно, всегда сбрасывая occurs в 1. Вот как должна быть составлена правильная if-инструкция (точное положение открывающей фигурной скобки является поводом для бесконечных споров):
С++ для начинающих |
190 |
if ( minVal > ivec[ i ] )
{
minVal = ivec[ i ]; occurs = 1;
}
if ( minVal == ivec [ i ] )
Вторая инструкция if выглядит так:
++occurs;
Заметим, что порядок следования инструкций в этом примере крайне важен. Если мы будем сравнивать minVal именно в такой последовательности, наша функция всегда
if ( minVal > ivec[ i ] ) { minVal = ivec[ i ]; occurs = 1;
}
//если minVal только что получила новое значение,
//то occurs будет на единицу больше, чем нужно
if |
( minVal == ivec[ i ] ) |
будет ошибаться на 1:
++occurs;
Выполнение второго сравнения не обязательно: один и тот же элемент не может одновременно быть и меньше и равен minVal. Поэтому появляется необходимость выбора одного из двух блоков в зависимости от условия, что реализуется инструкцией
if ( условие ) инструкция1
else
if-else, второй формой if-инструкции. Ее синтаксис выглядит таким образом:
инструкция2
инструкция1 выполняется, если условие истинно, иначе переходим к инструкция2.
if ( minVal == ivec[ i ] ) ++occurs;
else
if ( minVal > ivec[ i ] ) { minVal = ivec[ i ]; occurs = 1;
Например:
}
С++ для начинающих |
191 |
Здесь инструкция2 сама является if-инструкцией. Если minVal меньше ivec[i], никаких действий не производится.
if ( minVal < ivec[ i ] ) {} // пустая инструкция
else
if ( minVal > ivec[ i ] ) { minVal = ivec[ i ]; occurs = 1;
}
else // minVal == ivec[ i ]
В следующем примере выполняется одна из трех инструкций:
++occurs;
Составные инструкции if-else могут служить источником неоднозначного толкования, если частей else больше, чем частей if. К какому из if отнести данную часть else?
if ( minVal <= ivec[ i ] )
if ( minVal == ivec[ i ] ) ++occurs;
else {
minVal= ivec[ i ]; occurs= 1;
(Эту проблему иногда называют проблемой висячего else). Например:
}
Судя по отступам, программист предполагает, что else относится к самому первому, внешнему if. Однако в С++ неоднозначность висячих else разрешается соотнесением их с последним встретившимся if. Таким образом, в действительности предыдущий
if ( minVal <= ivec[ i ] ) { if ( minVal == ivec[ i ] )
++occurs; else {
minVal occurs
}
фрагмент означает следующее:
}
Одним из способов разрешения данной проблемы является заключение внутреннего if в фигурные скобки: