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

  • Поскольку переменные // sum и count являются статическими, // они сохраняют свои значения между // вызовами функции running_avg().

  • Глобальные static -переменные

  • Спросим у опытного программиста

  • Эти переменные оптимизированы для register int sum = 0; // для получения максимальной скорости. С++: руководство для начинающих 317

  • Вопрос. Когда я добавил в программу спецификатор

  • Спросим у опытного программиста С++: руководство для начинающих 319 Еще о типах данных и операторах7

  • ВАЖНО! 7.6. Ключевое слово typedef

  • Справочник по C, Полный справочник по C


    Скачать 393.9 Kb.
    НазваниеСправочник по C, Полный справочник по C
    Анкорaboba
    Дата09.07.2021
    Размер393.9 Kb.
    Формат файлаpdf
    Имя файлаcrukovodstvodlyanachinayushchih.pdf
    ТипСправочник
    #223848
    страница3 из 5
    1   2   3   4   5
    312
    Модуль 7. Еще о типах данных и операторах
    << running_avg(num);
    cout << '\n';
    } while(num > -1);
    return 0;
    }
    // Вычисляем текущее среднее.
    int running_avg(int i)
    {
    static int sum = 0, count = 0; //
    Поскольку переменные
    //
    sum и count являются статическими,
    //
    они сохраняют свои значения между
    //
    вызовами функции running_avg().
    sum = sum + i;
    count++;
    return sum / count;
    }
    Здесь обе локальные переменные sum и count объявлены статическими и инициализированы значением 0. Помните, что для статических переменных ини- циализация выполняется только один раз (при первом выполнении функции), а не при каждом входе в функцию. В этой программе функция running_avg() используется для вычисления текущего среднего значения от чисел, вводимых пользователем. Поскольку обе переменные sum и count являются статическими, они поддерживают свои значения между вызовами функции running_avg(), что позволяет нам получить правильный результат вычислений. Чтобы убедить- ся в необходимости модификатора static, попробуйте удалить его из програм- мы. После этого программа не будет работать корректно, поскольку промежуточ- ная сумма будет теряться при каждом выходе из функции running_avg().
    Глобальные
    static
    -переменные
    Если модификатор static применен к глобальной переменной, то компиля- тор создаст глобальную переменную, которая будет известна только для файла, в котором она объявлена. Это означает, что, хотя эта переменная является гло- бальной, другие функции в других файлах не имеют о ней “ни малейшего поня-

    С++: руководство для начинающих
    313
    Еще о типах данных и операторах
    7
    тия” и не могут изменить ее содержимое. Поэтому она и не может стать “жертвой” несанкционированных изменений. Следовательно, для особых ситуаций, когда локальная статичность оказывается бессильной, можно создать небольшой файл, который будет содержать лишь функции, использующие глобальные static- переменные, отдельно скомпилировать этот файл и работать с ним, не опасаясь вреда от побочных эффектов “всеобщей глобальности”.
    Рассмотрим пример, который представляет собой переработанную версию программы (из предыдущего подраздела), вычисляющей текущее среднее значе- ние. Эта версия состоит из двух файлов и использует глобальные static-пе- ременные для хранения значений промежуточной суммы и счетчика вводимых чисел. В эту версию программы добавлена функция reset(), которая обнуляет
    (“сбрасывает”) значения глобальных static-переменных.
    // --------------------- Первый файл ---------------------
    #include
    using namespace std;
    int running_avg(int i);
    void reset();
    int main()
    {
    int num;
    do {
    cout <<
    "Введите числа (-1 для выхода, -2 для сброса): ";
    cin >> num;
    if(num==-2) {
    reset();
    continue;
    }
    if(num != -1)
    cout << "Среднее значение равно: "
    << running_avg(num);
    cout << '\n';
    } while(num != -1);
    return 0;

    314
    Модуль 7. Еще о типах данных и операторах
    }
    // --------------------- Второй файл --------------------- static int sum=0, count=0; //
    Эти переменные известны
    //
    только в файле, в котором
    //
    они объявлены.
    int running_avg(int i)
    {
    sum = sum + i;
    count++;
    return sum / count;
    }
    void reset()
    {
    sum = 0;
    count = 0;
    }
    В этой версии программы переменные sum и count являются глобально ста- тическими, т.е. их глобальность ограничена вторым файлом. Итак, они исполь- зуются функциями running_avg() и reset(), причем обе они расположены во втором файле. Этот вариант программы позволяет сбрасывать накопленную сумму (путем установки в исходное положение переменных sum и count), чтобы можно было усреднить другой набор чисел. Но ни одна из функций, расположен- ных вне второго файла, не может получить доступ к этим переменным. Работая с данной программой, можно обнулить предыдущие накопления, введя число –2.
    В этом случае будет вызвана функция reset(). Проверьте это. Кроме того, по- пытайтесь получить из первого файла доступ к любой из переменных sum или count
    . (Вы получите сообщение об ошибке.)
    Итак, имя локальной static-переменной известно только функции или блоку кода, в котором она объявлена, а имя глобальной static-переменной — только файлу, в котором она “обитает”. По сути, модификатор static позволяет перемен- ным существовать так, что о них знают только функции, использующие их, тем са- мым “держа в узде” и ограничивая возможности негативных побочных эффектов.
    Переменные типа static позволяют программисту “скрывать” одни части своей программы от других частей. Это может оказаться просто супердостоинством, ког- да вам придется разрабатывать очень большую и сложную программу.

    С++: руководство для начинающих
    315
    Еще о типах данных и операторах
    7
    Вопрос. Я слышал, что некоторые C++-программисты не используют глобальные
    static
    -переменные. Так ли это?
    Ответ. Несмотря на то что глобальные static
    -переменные по-прежнему допу- стимы и широко используются в C++-коде, стандарт C++ не предусма- тривает их применения. Для управления доступом к глобальным перемен- ным рекомендуется другой метод, который заключается в использовании пространств имен. Этот метод описан ниже в данной книге. При этом гло- бальные static
    -переменные широко используются C-программистами, поскольку в C не поддерживаются пространства имен. Поэтому вам еще долго придется встречаться с глобальными static
    -переменными.
    Спросим у опытного программиста
    ВАЖНО!
    7.4.
    Регистровые переменные
    Возможно, чаще всего используется спецификатор класса памяти register .
    Для компилятора модификатор register означает предписание обеспечить та- кое хранение соответствующей переменной, чтобы доступ к ней можно было по- лучить максимально быстро. Обычно переменная в этом случае будет храниться либо в регистре центрального процессора (ЦП), либо в кэш-памяти (быстродей- ствующей буферной памяти небольшой емкости). Вероятно, вы знаете, что до- ступ к регистрам ЦП (или к кэш-памяти) принципиально быстрее, чем доступ к основной памяти компьютера. Таким образом, переменная, сохраняемая в ре- гистре, будет обслужена гораздо быстрее, чем переменная, сохраняемая, напри- мер, в оперативной памяти (ОЗУ). Поскольку скорость, с которой к переменным можно получить доступ, определяет, по сути, скорость выполнения вашей про- граммы, для получения удовлетворительных результатов программирования важно разумно использовать спецификатор register.
    Формально спецификатор register представляет собой лишь запрос, кото- рый компилятор вправе проигнорировать. Это легко объяснить: ведь количество регистров (или устройств памяти с малым временем выборки) ограничено, причем для разных сред оно может быть различным. Поэтому, если компилятор исчерпает память быстрого доступа, он будет хранить register-переменные обычным спо- собом. В общем случае неудовлетворенный register-запрос не приносит вреда, но, конечно же, и не дает никаких преимуществ хранения в регистровой памяти.
    Как правило, программист может рассчитывать на удовлетворение register-за- проса по крайней мере для двух переменных, обработка которых действительно будет оптимизирована с точки зрения максимально возможной скорости.

    316
    Модуль 7. Еще о типах данных и операторах
    Поскольку быстрый доступ можно обеспечить на самом деле только для огра- ниченного количества переменных, важно тщательно выбрать, к каким из них применить модификатор register. (Лишь правильный выбор может повысить быстродействие программы.) Как правило, чем чаще к переменной требуется до- ступ, тем большая выгода будет получена в результате оптимизации кода с помо- щью спецификатора register. Поэтому объявлять регистровыми имеет смысл управляющие переменные цикла или переменные, к которым выполняется до- ступ в теле цикла.
    На примере следующей функции показано, как используются register-пе- ременные для повышения быстродействия функции summation(), которая вы- числяет сумму значений элементов массива. Здесь как раз и предполагается, что реально для получения скоростного эффекта будут оптимизированы только две переменные.
    // Демонстрация использования register-переменных.
    #include using namespace std; int summation(int nums[], int n); int main()
    { int vals[] = { 1, 2, 3, 4, 5 }; int result; result = summation(vals, 5); cout << "Сумма элементов массива равна " << result << "\
    n"; return 0;
    }
    // Функция возвращает сумму int-элементов массива. int summation(int nums[], int n)
    { register int i; //
    Эти переменные оптимизированы для
    register int sum = 0; //
    для получения максимальной скорости.

    С++: руководство для начинающих
    317
    Еще о типах данных и операторах
    7
    for(i = 0; i < n; i++) sum = sum + nums[i]; return sum;
    }
    Здесь переменная i, которая управляет циклом for, и переменная sum, к ко- торой осуществляется доступ в теле цикла, определены с использованием спе- цификатора register. Поскольку обе они используются внутри цикла, можно рассчитывать на то, что их обработка будет оптимизирована для реализации бы- строго к ним доступа. В этом примере предполагалось, что реально для получе- ния скоростного эффекта будут оптимизированы только две переменные, поэто- му nums и n не были определены как register-переменные, ведь доступ к ним реализуется не столь часто, как к переменным i и sum. Но если среда позволяет оптимизацию более двух переменных, то имело бы смысл переменные nums и n также объявить с использованием спецификатора register, что еще больше по- высило бы быстродействие программы.
    Вопросы для текущего контроля
    1.
    Локальная переменная, объявленная с использованием модификатора static
    , ________ свое значение между вызовами функции.
    2.
    Спецификатор extern используется для объявления переменной без ее определения. Верно ли это?
    3.
    Какой спецификатор служит для компилятора запросом на оптимизацию обработки переменной с целью повышения быстродействия программы?*
    1. Локальная переменная, объявленная с использованием модификатора static
    , сохраняет свое значение между вызовами функции.
    2. Верно, спецификатор extern действительно используется для объявления пере- менной без ее определения.
    3. Запросом на оптимизацию обработки переменной с целью повышения быстродей- ствия программы служит спецификатор register.

    318
    Модуль 7. Еще о типах данных и операторах
    ВАЖНО!
    7.5.
    Перечисления
    В C++ можно определить список именованных целочисленных констант. Та- кой список называется перечислением (enumeration). Эти константы можно затем использовать везде, где допустимы целочисленные значения (например, в цело- численных выражениях). Перечисления определяются с помощью ключевого слова enum , а формат их определения имеет такой вид:
    enum
    имя_типа { список_перечисления } список_переменных;
    Под элементом список_перечисления понимается список разделенных запятыми имен, которые представляют значения перечисления. Элемент спи-
    сок_переменных необязателен, поскольку переменные можно объявить позже, используя имя_типа перечисления.
    В следующем примере определяется перечисление transport и две перемен- ные типа transport с именами t1 и t2.
    enum transport { car, truck, airplane, train, boat } t1, t2;
    Определив перечисление, можно объявить другие переменные этого типа, ис- пользуя имя типа перечисления. Например, с помощью следующей инструкции объявляется одна переменная how перечисления transport.
    transport how;
    Вопрос. Когда я добавил в программу спецификатор register, то не заметил ни-
    каких изменений в быстродействии. В чем причина?
    Ответ
    .
    Большинство компиляторов (благодаря прогрессивной технологии их разработки) автоматически оптимизируют программный код. Поэтому во многих случаях внесение спецификатора register в объявление переменной не ускорит выполнение программы, поскольку обработка этой переменной уже оптимизирована. Но в некоторых случаях исполь- зование спецификатора register оказывается весьма полезным, так как он позволяет сообщить компилятору, какие именно переменные вы считаете наиболее важными для оптимизации. Это особенно ценно для функций, в которых используется большое количество переменных, и ясно, что всех их невозможно оптимизировать. Следовательно, несмотря на прогрессивную технологию разработки компиляторов, спецификатор register по-прежнему играет важную роль для эффективного про- граммирования.
    Спросим у опытного программиста

    С++: руководство для начинающих
    319
    Еще о типах данных и операторах
    7
    Эту инструкцию можно записать и так.
    enum transport how;
    Однако использование ключевого слова enum здесь излишне. В языке C (ко- торый также поддерживает перечисления) обязательной была вторая форма, по- этому в некоторых программах вы можете встретить подобную запись.
    С учетом предыдущих объявлений при выполнении следующей инструкции переменной how присваивается значение airplane.
    how = airplane;
    Важно понимать, что каждый символ списка перечисления означает целое число, причем каждое следующее число (представленное идентификатором) на единицу больше предыдущего. По умолчанию значение первого символа пере- числения равно нулю, следовательно, значение второго — единице и т.д. Поэтому при выполнении этой инструкции cout << car << ' ' << train;
    на экран будут выведены числа 0 и 3.
    Несмотря на то что перечислимые константы автоматически преобразуются в целочисленные, обратное преобразование автоматически не выполняется. На- пример, следующая инструкция некорректна.
    how = 1; // ошибка
    Эта инструкция вызовет во время компиляции ошибку, поскольку автомати- ческого преобразования целочисленных значений в значения типа transport не существует. Откорректировать предыдущую инструкцию можно с помощью операции приведения типов.
    fruit = (transport) 1; // Теперь все в порядке,
    // но стиль не совершенен.
    Теперь переменная how будет содержать значение truck, поскольку эта transport
    -константа связывается со значением 1. Как отмечено в комментарии, несмотря на то, что эта инструкция стала корректной, ее стиль оставляет желать лучшего, что простительно лишь в особых обстоятельствах.
    Используя инициализатор, можно указать значение одной или нескольких перечислимых констант. Это делается так: после соответствующего элемента списка перечисления ставится знак равенства и нужное целое число. При исполь- зовании инициализатора следующему (после инициализированного) элементу списка присваивается значение, на единицу превышающее предыдущее значение инициализатора. Например, при выполнении следующей инструкции константе airplane присваивается значение 10.
    enum transport { car, truck, airplane = 10, train, boat };

    320
    Модуль 7. Еще о типах данных и операторах
    Теперь все символы перечисления transport имеют следующие значения.
    car 0
    truck
    1
    airplane
    10
    train 11
    boat
    12
    Часто ошибочно предполагается, что символы перечисления можно вводить и выводить как строки. Например, следующий фрагмент кода выполнен не будет.
    // Слово "train" на экран таким образом не попадет.
    how = train;
    cout << how;
    Не забывайте, что символ train — это просто имя для некоторого целочислен- ного значения, а не строка. Следовательно, при выполнении предыдущего кода на экране отобразится числовое значение константы train, а не строка "train".
    Конечно, можно создать код ввода и вывода символов перечисления в виде строк, но он выходит несколько громоздким. Вот, например, как можно отобразить на экране названия транспортных средств, связанных с переменной how.
    switch(how) {
    case car: cout << "Automobile";
    break;
    case truck: cout << "Truck";
    break;
    case airplane: cout << "Airplane";
    break;
    case train: cout << "Train";
    break;
    case boat: cout << "Boat";
    break;
    }
    Иногда для перевода значения перечисления в соответствующую строку можно объявить массив строк и использовать значение перечисления в каче- стве индекса. Например, следующая программа выводит названия трех видов транспорта.

    С++: руководство для начинающих
    321
    Еще о типах данных и операторах
    7
    // Демонстрация использования перечисления.
    #include using namespace std; enum transport { car, truck, airplane, train, boat }; char name[][20] = {
    "Automobile",
    "Truck",
    "Airplane",
    "Train",
    "Boat"
    }; int main()
    { transport how; how = car; cout << name[how] << '\n'; how = airplane; cout << name[how] << '\n'; how = train; cout << name[how] << '\n'; return 0;
    }
    Вот результаты выполнения этой программы.
    Automobile
    Airplane
    Train
    Использованный в этой программе метод преобразования значения пере- числения в строку можно применить к перечислению любого типа, если оно не содержит инициализаторов. Для надлежащего индексирования массива строк перечислимые константы должны начинаться с нуля, быть строго упорядочен-

    322
    Модуль 7. Еще о типах данных и операторах ными по возрастанию, и каждая следующая константа должна быть больше пред- ыдущей точно на единицу.
    Из-за того, что значения перечисления необходимо вручную преобразовывать в удобные для восприятия человеком строки, они, в основном, используются там, где такое преобразование не требуется. Для примера рассмотрите перечисление, используемое для определения таблицы символов компилятора.
    ВАЖНО!
    7.6.
    Ключевое слово typedef
    В C++ разрешается определять новые имена типов данных с помощью ключе- вого слова typedef. При использовании typedef-имени не создается новый тип данных, а лишь определяется новое имя для уже существующего типа. Благодаря typedef
    -именам можно сделать машинозависимые программы более переноси- мыми: для этого иногда достаточно изменить typedef-инструкции. Это сред- ство также позволяет улучшить читабельность кода, поскольку для стандартных типов данных с его помощью можно использовать описательные имена. Общий формат записи инструкции typedef таков.
    typedef
    тип имя;
    Здесь элемент тип означает любой допустимый тип данных, а элемент имя — но- вое имя для этого типа. При этом заметьте: новое имя определяется вами в каче- стве дополнения к существующему имени типа, а не для его замены.
    Например, с помощью следующей инструкции можно создать новое имя для типа float.
    typedef float balance;
    Эта инструкция является предписанием компилятору распознавать иденти- фикатор balance как еще одно имя для типа float. После этой инструкции мож- но создавать float-переменные с использованием имени balance.
    balance over_due;
    Здесь объявлена переменная с плавающей точкой over_due типа balance, ко- торый представляет собой стандартный тип float с другим названием.
    1   2   3   4   5


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