Главная страница
Навигация по странице:

  • Упражнение 4.23

  • Упражнение 4.24 В чем вы видите два основных недостатка реализации класса iStack Как их можно исправить С++ для начинающих 184

  • Упражнение 5.1

  • Упражнение 5.2

  • Язык программирования C++. Вводный курс. С для начинающих


    Скачать 5.41 Mb.
    НазваниеС для начинающих
    Дата24.08.2022
    Размер5.41 Mb.
    Формат файлаpdf
    Имя файлаЯзык программирования C++. Вводный курс.pdf
    ТипДокументы
    #652350
    страница18 из 93
    1   ...   14   15   16   17   18   19   20   21   ...   93
    Упражнение 4.21
    Даны определения переменных: unsigned int ui;
    Какие неявные преобразования типов будут выполнены?
    (d) cva1 = ival + fvat + dva1;
    Упражнение 4.22
    Даны определения переменных: const string *ps;
    Перепишите следующие выражения, используя операторы явного преобразования типов:
    (d) pc = (char*) pv; const char *pc = (const char*) pcom; int ival = (int) 3.14159; extern char *rewrite_str( char* ); char *pc2 = rewrite_str( (char*) pc ); char cval; int ival; float fval; double dva1;
    (a) cva1 = 'a' + 3;
    (b) fval = ui - ival * 1.0;
    (c) dva1 = ui * fval; void *pv; int ival; char *pc; double dval;
    (a) pv = (void*)ps;
    (b) ival = int( *pc );
    (c) pv = &dva1;

    С++ для начинающих
    180
    4.15.
    Пример: реализация класса Stack
    Описывая операции инкремента и декремента, для иллюстрации применения их префиксной и постфиксной формы мы ввели понятие стека. Данная глава завершается примером реализации класса iStack – стека, позволяющего хранить элементы типа int.
    Как уже было сказано, с этой структурой возможны две основные операции – поместить элемент (push) и извлечь (pop) его. Другие операции позволяют получить информацию о текущем состоянии стека – пуст он (empty()) или полон (full()), сколько элементов в нем содержится (size()). Для начала наш стек будет предназначен лишь для элементов типа int. Вот объявление нашего класса:
    };
    В данном случае мы используем вектор фиксированного размера: для иллюстрации использования префиксных и постфиксных операций инкремента и декремента этого достаточно. (В главе 6 мы модифицируем наш стек, придав ему возможность динамически меняться.)
    Элементы стека хранятся в векторе _stack. Переменная _top содержит индекс первой свободной ячейки стека. Этот индекс одновременно представляет количество заполненных ячеек. Отсюда реализация функции size(): она должна просто возвращать текущее значение _top. inline int iStack::size() { return _top; }; empty()
    возвращает true, если _top равняется 0; full() возвращает true, если _top равен _stack.size()-1 (напомним, что индексация вектора начинается с 0, поэтому мы должны вычесть 1).
    }
    #include class iStack { public: iStack( int capacity )
    : _stack( capacity ), _top( 0 ) {} bool pop( int &va1ue ); boot push( int value ); bool full(); bool empty(); void display(); int size(); private: int _top; vector< int > _stack; inline bool iStack::empty() { return _top ? false : true; } inline bool iStack::full() { return _top < _stack.size()-l ? false : true;

    С++ для начинающих
    181
    Вот реализация функций pop() и push(). Мы добавили операторы вывода в каждую из них, чтобы следить за ходом выполнения:
    }
    Прежде чем протестировать наш стек на примере, добавим функцию display(), которая позволит напечатать его содержимое. Для пустого стека она выведет:
    ( 0 )
    Для стека из четырех элементов – 0, 1, 2 и 3 – результатом функции display() будет:
    ( 4 )( bot: 0 1 2 3 :top )
    Вот реализация функции display():
    }
    А вот небольшая программа для проверки нашего стека. Цикл for выполняется 50 раз.
    Четное значение (2, 4, 6, 8 и т.д.) помещается в стек. На каждой итерации, кратной 5 (5,
    10, 15...), распечатывается текущее содержимое стека. На итерациях, кратных 10 (10, 20,
    30...), из стека извлекаются два элемента и его содержимое распечатывается еще раз. bool iStack::pop( int &top_va1ue ) { if ( empty() ) return false; top_value = _stack[ --_top ]; cout << "iStack::pop(): " << top_value << endl; return true;
    } bool iStack::push( int value ) { cout << "iStack::push( " << value << " )\n"; if ( full() ) return false;
    _stack[ _top++ ] = value; return true; void iStack::display() { cout << "( " << size() << " )( bot: "; for ( int ix = 0; ix < _top; ++ix ) cout << _stack[ ix ] <<
    " "; cout << " :top )\n";

    С++ для начинающих
    182
    }
    Вот результат работы программы:
    ( 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 )
    #inc1ude
    #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();
    }

    С++ для начинающих
    183
    iStack::push( 50 )
    ( 17 )( bot: 2 4 6 12 14 16 22 24 26 32 34 36 42 44 46 48 50 :top ) iStack::pop(): 50 iStack::pop(): 48
    ( 15 )( bot: 2 4 6 12 14 16 22 24 26 32 34 36 42 44 46 :top )
    Упражнение 4.23
    Иногда требуется операция peek(), которая возвращает значение элемента на вершине стека без извлечения самого элемента. Реализуйте функцию peek() и добавьте к программе main() проверку работоспособности этой функции.
    Упражнение 4.24
    В чем вы видите два основных недостатка реализации класса iStack? Как их можно исправить?

    С++ для начинающих
    184
    5.
    Инструкции
    Мельчайшей независимой частью С++ программы является инструкция. Она соответствует предложению естественного языка, но завершается точкой с запятой
    (;), а не точкой. Выражение С++ (например, ival + 5) становится простой инструкцией, если после него поставить точку с запятой. Составная инструкция – это последовательность простых, заключенная в фигурные скобки. По умолчанию инструкции выполняются в порядке записи. Как правило, последовательного выполнения недостаточно для решения реальных задач. Специальные управляющие конструкции позволяют менять порядок действий в зависимости от некоторых условий и повторять составную инструкцию определенное количество раз. Инструкции if, if-else и switch обеспечивают условное выполнение.
    Повторение обеспечивается инструкциями цикла while, do-while и for.
    5.1.
    Простые и составные инструкции
    Простейшей формой является пустая инструкция. Вот как она выглядит:
    ; // пустая инструкция
    Пустая инструкция используется там, где синтаксис С++ требует употребления инструкции, а логика программы – нет. Например, в следующем цикле while, копирующем одну строку в другую, все необходимые действия производятся внутри круглых скобок (условной части инструкции). Однако согласно правилам синтаксиса С++ после while должна идти инструкция. Поскольку нам нечего поместить сюда (вся работа уже выполнена), приходится оставить это место пустым:
    ; // пустая инструкция
    Случайное появление лишней пустой инструкции не вызывает ошибки компиляции.
    Например, такая строка ival = dval + sval;; // правильно: лишняя пустая инструкция состоит из двух инструкций – сложения двух величин с присваиванием результата переменной ival и пустой.
    Простая инструкция состоит из выражения, за которым следует точка с запятой.
    Например: while ( *string++ = inBuf++ )
    // простые инструкции int ival = 1024; // инструкция определения переменной ival; // выражение ival + 5; // еще одно выражение

    С++ для начинающих
    185
    ival = ival +5; // присваивание
    Условные инструкции и инструкции цикла синтаксически требуют употребления единственной инструкции, связанной с ними. Однако, как правило, этого недостаточно.
    В таких случаях употребляются составные инструкции – последовательность простых, заключенная в фигурные скобки:
    }
    Составная инструкция может употребляться там же, где простая, и не нуждается в завершающей точке с запятой.
    Пустая составная инструкция эквивалентна пустой простой. Приведенный выше пример с пустой инструкцией можно переписать так:
    {} // пустая инструкция
    Составную инструкцию, содержащую определения переменных, часто называют блоком.
    Блок задает локальную область видимости в программе – идентификаторы, объявленные внутри блока (как temp в предыдущем примере), видны только в нем. (Блоки, области видимости и время жизни объектов рассматриваются в главе 8.)
    5.2.
    Инструкции объявления
    В С++ определение объекта, например int ival; рассматривается как инструкция объявления (хотя в данном случае более правильно было бы сказать определения). Ее можно использовать в любом месте программы, где разрешено употреблять инструкции. В следующем примере объявления помечены комментарием //#n, где n – порядковый номер. if ( ival0 > ival1 ) {
    // составная инструкция, состоящая
    // из объявления и двух присваиваний int temp = ivalO; ivalO = ival1; ival1 = temp; while ( *string++ = *inBuf++ )

    С++ для начинающих
    186
    }
    Программа содержит семь инструкций объявления и восемь определений объектов.
    Объявления действуют локально; переменная объявляется непосредственно перед первым использованием объекта.
    В 70-е годы философия программирования уделяла особое внимание тому, чтобы определения всех объектов находились в начале программы или блока, перед исполняемыми инструкциями. (В С, например, определение переменной не является инструкцией и обязано располагаться в начале блока.) В некотором смысле это была реакция на идиому использования переменных без предварительного объявления, чреватую ошибками. Такую идиому поддерживал, например, FORTRAN.
    #include
    #include
    #include 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::iterator iter = text.begin(), // #7 iend = text.end(); while ( iter != -iend ) { cout << *iter << '\n';
    ++iter;
    } return 0;

    С++ для начинающих
    187
    Поскольку в С++ объявление является обычной инструкцией, ему разрешено появляться в любом месте программы, где допустимо употребление инструкции, что дает возможность использовать локальные объявления.
    Необходимо ли это? Для встроенных типов данных применение локальных объявлений является скорее вопросом вкуса. Язык их поощряет , разрешая объявлять переменные внутри условных частей инструкций if, if-else, switch, while, for. Те программисты, которые любят этот стиль, верят, что таким образом делают свои программы более понятными.
    Локальные объявления становятся необходимостью, когда мы используем объекты классов, имеющие конструкторы и деструкторы. Если мы помещаем все объявления в начало блока или функции, происходят две неприятные вещи:

    конструкторы всех объектов вызываются перед исполнением первой инструкции блока. Применение локальных объявлений позволяет “размазать” расходы на инициализацию по всему блоку;

    что более важно, блок или функция могут завершиться до того, как будут действительно использованы все объявленные в начале объекты. Скажем, наша программа из предыдущего примера имеет два аварийных выхода: при вводе пользователем пустого имени файла и при невозможности открыть файл с заданным именем. При этом последующие инструкции функции уже не выполняются. Если бы объекты inBuf и next были объявлены в начале блока, конструкторы и деструкторы этих объектов в случае ненормального завершения функции вызывались бы совершенно напрасно.
    Инструкция объявления может состоять из одного или более определений. Например, в нашей программе мы определяем два итератора вектора в одной инструкции: lend = text.end();
    Эквивалентная пара, определяющая по одному объекту, выглядит так: vector::iterator lend = text.end();
    Хотя определение одного или нескольких объектов в одном предложении является скорее вопросом вкуса, в некоторых случаях – например, при одновременном определении объектов, указателей и ссылок – это может спровоцировать появление ошибок. Скажем, в следующей инструкции не совсем ясно, действительно ли программист хотел определить указатель и объект или просто забыл поставить звездочку перед вторым идентификатором (используемые имена переменных наводят на второе предположение): string *ptrl, ptr2;
    // одна инструкция объявления,
    // определяющая сразу два объекта vector::iterator iter = text.begin(), vector::iterator iter = text.begin();
    // то ли хотел определить программист?

    С++ для начинающих
    188
    Эквивалентная пара инструкций не позволит допустить такую ошибку: string *ptr2;
    В наших примерах мы обычно группируем определения объектов в инструкции по сходству употребления. Например, в следующей паре int charCnt=0, wordCnt=0; первая инструкция объявляет пять очень похожих по назначению объектов – счетчиков пяти гласных латинского алфавита. Счетчики для подсчета символов и слов определяются во второй инструкции. Хотя такой подход нам кажется естественным и удобным, нет никаких причин считать его хоть чем-то лучше других.
    Упражнение 5.1
    Представьте себе, что вы являетесь руководителем программного проекта и хотите, чтобы применение инструкций объявления было унифицировано. Сформулируйте правила использования объявлений объектов для вашего проекта.
    Упражнение 5.2
    Представьте себе, что вы только что присоединились к проекту из предыдущего упражнения. Вы совершенно не согласны не только с конкретными правилами использования инструкций объявления, но и вообще с навязыванием каких-либо правил для этого. Объясните свою позицию.
    5.3.
    Инструкция if
    Инструкция if обеспечивает выполнение или пропуск инструкции или блока в зависимости от условия. Ее синтаксис таков: инструкция условие заключается в круглые скобки. Оно может быть выражением, как в этом примере: if(a+b>c) { ... } или инструкцией объявления с инициализацией: if ( int ival = compute_value() ){...} string *ptr1; int aCnt=0, eCnt=0, iCnt=0, oCnt=0, uCnt=0; if ( условие )

    С++ для начинающих
    189
    Область видимости объекта, объявленного в условной части, ограничивается ассоциированной с if инструкцией или блоком. Например, такой код вызывает ошибку компиляции: if ( ! ival ) ...
    Попробуем для иллюстрации применения инструкции if реализовать функцию min(), возвращающую наименьший элемент вектора. Заодно наша функция будет подсчитывать число элементов, равных минимуму. Для каждого элемента вектора мы должны проделать следующее:
    1. Сравнить элемент с текущим значением минимума.
    2. Если элемент меньше, присвоить текущему минимуму значение элемента и сбросить счетчик в 1.
    3. Если элемент равен текущему минимуму, увеличить счетчик на 1.
    4. В противном случае ничего не делать.
    5. После проверки последнего элемента вернуть значение минимума и счетчика.
    Необходимо использовать две инструкции if: if ( minVal == ivec[ i ] )...// одинаковые значения
    Довольно часто программист забывает использовать фигурные скобки, если нужно выполнить несколько инструкций в зависимости от условия: occurs = 1; // не относится к if!
    Такую ошибку трудно увидеть, поскольку отступы в записи подразумевают, что и minVal=ivec[i]
    , и occurs=1 входят в одну инструкцию if. На самом же деле инструкция occurs = 1; не является частью if и выполняется безусловно, всегда сбрасывая occurs в 1. Вот как должна быть составлена правильная if-инструкция (точное положение открывающей фигурной скобки является поводом для бесконечных споров): if ( int ival = compute_value() ) {
    // область видимости ival
    // ограничена этим блоком
    }
    // ошибка: ival невидим if ( minVal > ivec[ i ] )...// новое значение minVal if ( minVal > ivec[ i ] ) minVal = ivec[ i ];

    С++ для начинающих
    190
    }
    Вторая инструкция if выглядит так:
    ++occurs;
    Заметим, что порядок следования инструкций в этом примере крайне важен. Если мы будем сравнивать minVal именно в такой последовательности, наша функция всегда будет ошибаться на 1:
    ++occurs;
    Выполнение второго сравнения не обязательно: один и тот же элемент не может одновременно быть и меньше и равен minVal. Поэтому появляется необходимость выбора одного из двух блоков в зависимости от условия, что реализуется инструкцией if-else
    , второй формой if-инструкции. Ее синтаксис выглядит таким образом: инструкция2 инструкция1 выполняется, если условие истинно, иначе переходим к инструкция2.
    Например:
    } if ( minVal > ivec[ i ] )
    { minVal = ivec[ i ]; occurs = 1; if ( minVal == ivec [ i ] ) if ( minVal > ivec[ i ] ) { minVal = ivec[ i ]; occurs = 1;
    }
    // если minVal только что получила новое значение,
    // то occurs будет на единицу больше, чем нужно if
    ( minVal == ivec[ i ] ) if ( условие ) инструкция1 else if ( minVal == ivec[ i ] )
    ++occurs; else if ( minVal > ivec[ i ] ) { minVal = ivec[ i ]; occurs = 1;

    С++ для начинающих
    1   ...   14   15   16   17   18   19   20   21   ...   93


    написать администратору сайта