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

  • Спецификаторы типа const и volatile

  • Спецификатор типа const

  • Спецификатор типа volatile

  • Спецификаторы классов памяти

  • Спецификатор класса памяти auto

  • Спецификатор класса памяти extern

  • Статические переменные

  • Локальные static-переменные

  • Шилдт c++_базовый_курс издание 3. Герберт Шилдт С базовый курс


    Скачать 9.37 Mb.
    НазваниеГерберт Шилдт С базовый курс
    АнкорШилдт c++_базовый_курс издание 3.pdf
    Дата13.02.2017
    Размер9.37 Mb.
    Формат файлаpdf
    Имя файлаШилдт c++_базовый_курс издание 3.pdf
    ТипКнига
    #2637
    страница10 из 33
    1   ...   6   7   8   9   10   11   12   13   ...   33
    Глава 9: Еще о типах данных и операторах
    Прежде чем переходить к более сложным средствам C++, имеет смысл подробнее познакомиться с некоторыми типами данных и операторами. Кроме уже рассмотренных нами типов данных, в C++ определены и другие. Одни из них состоят из модификаторов,
    добавляемых к уже известным вам типам. Другие включают перечисления, а третьи используют ключевое слово typedef. C++ также поддерживает ряд операторов, которые значительно расширяют область действия языка и позволяют решать задачи программирования в весьма широком диапазоне. Речь идет о поразрядных операторах,
    операторах сдвига, а также операторах "?" и sizeof. Кроме того, в этой главе рассматриваются такие специальные операторы, как new и delete. Они предназначены для поддержки С++-системы динамического распределения памяти.
    Спецификаторы типа const и volatile
    Спецификаторы типа const и volatile управляют доступом к переменной.
    В C++ определено два спецификатора типа, которые оказывают влияние на то, каким образом можно получить доступ к переменным или модифицировать их. Это спецификаторы const и volatile. Официально они именуются cv-спецификаторами и должны предшествовать базовому типу при объявлении переменной.
    Спецификатор типа const
    Переменные, объявленные с использованием спецификатора const, не могут изменить свои значения во время выполнения программы. Однако любой const-переменной можно присвоить некоторое начальное значение. Например, при выполнении инструкции const double version = 3.2;
    создается double-переменная version, которая содержит значение 3.2, и это значение программа изменить уже не может. Но эту переменную можно использовать в других выражениях. Любая const-переменная получает значение либо во время явно задаваемой инициализации, либо при использовании аппаратно-зависимых средств. Применение спецификатора const к объявлению переменной гарантирует, что она не будет модифицирована другими частями вашей программы.
    Спецификатор const предотвращает модификацию переменной при выполнении
    программы.
    Спецификатор const имеет ряд важных применений. Возможно, чаще всего его используют для создания const-параметров типа указатель. Такой параметр-указатель защищает объект, на который он ссылается, от модификации со стороны функции. Другими словами, если параметр-указатель предваряется ключевым словом const, никакая инструкция этой функции не может модифицировать переменную, адресуемую этим параметром. Например, функция code() в следующей короткой программе сдвигает каждую букву в сообщении на одну алфавитную позицию (т.е. вместо буквы 'А' ставится буква 'Б' и т.д.), отображая таким образом сообщение в закодированном виде. Использование спецификатора const в объявлении параметра не позволяет коду функции модифицировать объект, на который указывает этот параметр.

    #include
    using namespace std;
    void code(const char *str);
    int main()
    {
    code("Это тест.");
    return 0;
    }
    /* Использование спецификатора const гарантирует, что str не может изменить аргумент, на который он указывает.
    */
    void code(const char *str)
    {
    while(*str) {
    cout << (char) (*str+1);
    str++;
    }
    }
    Поскольку параметр str объявляется как const-указатель, у функции code() нет никакой возможности внести изменения в строку, адресуемую параметром str. Но если вы попытаетесь написать функцию code() так, как показано в следующем примере,
    то обязательно получите сообщение об ошибке, и программа не скомпилируется.
    // Этот код неверен.
    void code(const char *str)

    {
    while(*str) {
    *str = *str + 1; // Ошибка, аргумент модифицировать нельзя.
    cout << (char) *str;
    str++;
    }
    }
    Поскольку параметр str является const-указателем, его нельзя использовать для модификации объекта, на который он ссылается.
    Спецификатор const можно также использовать для ссылочных параметров, чтобы не допустить в функции модификацию переменных, на которые ссылаются эти параметры.
    Например, следующая программа некорректна, поскольку функция f() пытается модифицировать переменную, на которую ссылается параметр i.
    // Нельзя модифицировать const-ссылки.
    #include
    using namespace std;
    void f(const int &i);
    int main()
    {
    int к = 10;
    f(к);
    return 0;
    }
    // Использование ссылочного const-параметра.
    void f (const int &i)
    {
    i = 100; // Ошибка, нельзя модифицировать const-ссылку.
    cout << i;
    }
    Спецификатор const еще можно использовать для подтверждения того, что ваша программа не изменяет значения некоторой переменной. Вспомните, что переменная типа
    const может быть модифицирована внешними устройствами, т.е. ее значение может быть установлено каким-нибудь аппаратным устройством (например, датчиком). Объявив переменную с помощью спецификатора const, можно доказать, что любые изменения,
    которым подвергается эта переменная, вызваны исключительно внешними событиями.
    Наконец, спецификатор const используется для создания именованных констант. Часто в программах многократно применяется одно и то же значение для различных целей.
    Например, необходимо объявить несколько различных массивов таким образом, чтобы все они имели одинаковый размер. Когда нужно использовать подобное "магическое число",
    имеет смысл реализовать его в виде const-переменной. Затем вместо реального значения можно использовать имя этой переменной, а если это значение придется впоследствии изменить, вы измените его только в одном месте программы. Следующая программа позволяет попробовать этот вид применения спецификатора const "на вкус".
    #include
    using namespace std;
    const int size = 10;
    int main()
    {
    int A1[size], A2[size], A3[size];
    // . . .
    }
    Если в этом примере понадобится использовать новый размер для массивов, вам потребуется изменить только объявление переменной size и перекомпилировать программу.
    В результате все три массива автоматически получат новый размер.
    Спецификатор типа volatile

    Спецификатор volatile информирует компилятор о том, что данная переменная может
    быть изменена внешними (по отношению к программе) факторами.
    Спецификатор volatile сообщает компилятору о том, что значение соответствующей переменной может быть изменено в программе неявным образом. Например, адрес некоторой глобальной переменной может передаваться управляемой прерываниями подпрограмме тактирования, которая обновляет эту переменную с приходом каждого импульса сигнала времени. В такой ситуации содержимое переменной изменяется без использования явно заданных инструкций программы. Существуют веские основания для того, чтобы сообщить компилятору о внешних факторах изменения переменной. Дело в том,
    что
    С++-компилятору разрешается автоматически оптимизировать определенные выражения в предположении, что содержимое той или иной переменной остается неизменным, если оно не находится в левой части инструкции присваивания. Но если некоторые факторы (внешние по отношению к программе) изменят значение этого поля,
    такое предположение окажется неверным, в результате чего могут возникнуть проблемы.
    Например, в следующем фрагменте программы предположим, что переменная clock
    обновляется каждую миллисекунду часовым механизмом компьютера. Но, поскольку переменная clock не объявлена с использованием спецификатора volatile, этот фрагмент кода может иногда работать недолжным образом. (Обратите особое внимание на строки,
    обозначенные буквами "А" и "Б".)
    int clock, timer;
    // ...
    timer = clock; // строка A
    // ... Какие-нибудь действия.
    cout << "Истекшее время " << clock-timer; // строка Б
    В этом фрагменте переменная clock получает свое значение, когда она присваивается переменной timer в строке А. Но, поскольку переменная clock не объявлена с использованием спецификатора volatile, компилятор волен оптимизировать этот код,
    причем таким способом, при котором значение переменной clock, возможно, не будет опрошено в инструкции cout (строка Б), если между строками А и Б не будет ни одного промежуточного присваивания значения переменной clock. (Другими словами, в строке Б
    компилятор может просто еще раз использовать значение, которое получила переменная
    clock в строке А.) Но если между моментами выполнения строк А и Б поступят очередные импульсы сигнала времени, то значение переменной clock обязательно изменится, а строка
    Б в этом случае не отразит корректный результат.
    Для решения этой проблемы необходимо объявить переменную clock с ключевым словом
    volatile.
    volatile int clock;
    Теперь значение переменной clock будет опрашиваться при каждом ее использовании.

    И хотя на первый взгляд это может показаться странным, спецификаторы const и volatile
    можно использовать вместе. Например, следующее объявление абсолютно допустимо. Оно создает const-указатель на volatile-объект.
    const volatile unsigned char *port = (const volatile char *)
    0x2112;
    В этом примере для преобразования целочисленного литерала 0x2112 в const-указатель на volatile-символ необходимо применить операцию приведения типов.
    Спецификаторы классов памяти
    C++ поддерживает пять спецификаторов классов памяти:
    auto extern register static mutable
    Спецификаторы классов памяти определяют, как должна храниться переменная.
    С помощью этих ключевых слов компилятор получает информацию о том, как должна храниться переменная. Спецификатор классов памяти необходимо указывать в начале объявления переменной.
    Спецификатор mutable применяется только к объектам классов, о которых речь впереди.
    Остальные спецификаторы мы рассмотрим в этом разделе.
    Спецификатор класса памяти auto
    Редко используемый спецификатор auto объявляет локальную переменную.
    Спецификатор auto объявляет локальную переменную. Но он используется довольно редко (возможно, вам никогда и не доведется применить его), поскольку локальные переменные являются "автоматическими" по умолчанию. Вряд ли вам попадется это ключевое слово и в чужих программах.
    Спецификатор класса памяти extern
    Все программы, которые мы рассматривали до сих пор, имели довольно скромный размер. Реальные же компьютерные программы гораздо больше. По мере увеличения размера файла, содержащего программу, время компиляции становится иногда раздражающе долгим. В этом случае следует разбить программу на несколько отдельных файлов. После этого небольшие изменения, вносимые в один файл, не потребуют перекомпиляции всей программы. При разработке больших проектов такой многофайловый подход может сэкономить существенное время. Реализовать этот подход позволяет ключевое слово extern.
    В программах, которые состоят из двух или более файлов, каждый файл должен "знать"
    имена и типы глобальных переменных, используемых программой в целом. Однако нельзя
    просто объявить копии глобальных переменных в каждом файле. Дело в том, что в C++
    программа может включать только одну копию каждой глобальной переменной.
    Следовательно, если вы попытаетесь объявить необходимые глобальные переменные в каждом файле, возникнут проблемы. Когда компоновщик попытается скомпоновать эти файлы, он обнаружит дублированные глобальные переменные, и компоновка программы не состоится. Чтобы выйти из этого затруднительного положения, достаточно объявить все глобальные переменные в одном файле, а в других использовать extern-объявления, как показано на рис. 9.1.
    Спецификатор extern объявляет переменную, но не выделяет для нее области памяти.
    В файле F1 объявляются и определяются переменные х, у и ch. В файле F2 используется скопированный из файла F1 список глобальных переменных, к объявлению которых добавлено ключевое слово extern. Спецификатор extern делает переменную известной для модуля, но в действительности не создает ее. Другими словами, ключевое слово extern
    предоставляет компилятору информацию о типе и имени глобальных переменных, повторно не выделяя для них памяти. Во время компоновки этих двух модулей все ссылки на эти внешние переменные будут определены.
    До сих пор мы не уточняли, в чем состоит различие между объявлением и определением переменной, но здесь это очень важно. При объявлении, переменной присваивается имя и
    тип, а посредством определения для переменной выделяется память. В большинстве случаев объявления переменных одновременно являются определениями. Предварив имя переменной спецификатором extern, можно объявить переменную, не определяя ее.
    Существует еще одно применение для ключевого слова extern, которое не связано с
    многофайловыми проектами. Не секрет, что много времени уходит на объявления глобальных переменных, которые, как правило, приводятся в начале программы, но это не всегда обязательно. Если функция использует глобальную переменную, которая определяется ниже (в том же файле), в теле функции ее можно специфицировать как внешнюю (с помощью ключевого слова extern). При обнаружении определения этой переменной компилятор вычислит соответствующие ссылки на нее.
    Рассмотрим следующий пример. Обратите внимание на то, что глобальные переменные
    first и last объявляются не перед, а после функции main().
    #include
    using namespace std;
    int main()
    {
    extern int first, last;
    //
    Использование глобальных переменных.
    cout << first << " " << last << "\n";
    return 0;
    }
    // Глобальное определение переменных first и last.
    int first = 10, last = 20;
    При выполнении этой программы на экран будут выведены числа 10 и 20, поскольку глобальные переменные first и last, используемые в инструкции cout, инициализируются этими значениями. Поскольку extern-объявление в функции main() сообщает компилятору о том, что переменные first и last объявляются где-то в другом месте (в данном случае ниже,
    но в том же файле), программу можно скомпилировать без ошибок, несмотря на то, что переменные first и last используются до их определения.
    Важно понимать, что extern-объявления переменных, показанные в предыдущей программе, необходимы здесь только по той причине, что переменные first и last не были определены до их использования в функции main(). Если бы их определения компилятор обнаружил раньше определения функции main(), необходимости в extern-инструкции не было бы. Помните, если компилятор обнаруживает переменную, которая не была объявлена в текущем блоке, он проверяет, не совпадает ли она с какой-нибудь из переменных,
    объявленных внутри других включающих блоков. Если нет, компилятор просматривает ранее объявленные глобальные переменные. Если обнаруживается совпадение их имен,
    компилятор предполагает, что ссылка была именно на эту глобальную переменную.
    Спецификатор extern необходим только в том случае, если вы хотите использовать переменную, которая объявляется либо ниже в том же файле, либо в другом.
    И еще. Несмотря на то что спецификатор extern объявляет, но не определяет переменную, существует одно исключение из этого правила. Если в extern-объявлении переменная инициализируется, то такое extern-объявление становится определением. Это очень важный момент, поскольку любой объект может иметь несколько объявлений, но только одно определение.
    Статические переменные
    Переменные типа static — это переменные "долговременного" хранения, т.е. они хранят свои значения в пределах своей функции или файла. От глобальных они отличаются тем,
    что за рамками своей функции или файла они неизвестны. Поскольку спецификатор static
    по-разному определяет "судьбу" локальных и глобальных переменных, мы рассмотрим их в отдельности.
    Локальные static-переменные
    Локальная static-переменная поддерживает свое значение между вызовами функции.
    Если к локальной переменной применен модификатор static, то для нее выделяется постоянная область памяти практически так же, как и для глобальной переменной. Это позволяет статической переменной поддерживать ее значение между вызовами функций.
    (Другими словами, в отличие от обычной локальной переменной, значение static- переменной не теряется при выходе из функции.) Ключевое различие между статической локальной и глобальной переменными состоит в том, что статическая локальная переменная известна только блоку, в котором она объявлена. Таким образом, статическую локальную переменную в некоторой степени можно назвать глобальной переменной,
    которая имеет ограниченную область видимости.
    Чтобы объявить статическую переменную, достаточно предварить ее тип ключевым словом static. Например, при выполнении этой инструкции переменная count объявляется статической.
    static int count;
    Статической переменной можно присвоить некоторое начальное значение. Например, в этой инструкции переменной count присваивается начальное значение 200:
    static int count = 200;
    Локальные static-переменные инициализируются только однажды, в начале выполнения программы, а не при каждом входе в функцию, в которой они объявлены.
    Возможность использования статических локальных переменных важна для создания независимых функций, поскольку существуют такие типы функций, которые должны сохранять их значения между вызовами. Если бы статические переменные не были предусмотрены в C++, пришлось бы использовать вместо них глобальные, что открыло бы путь для всевозможных побочных эффектов.
    Рассмотрим пример использования static-переменной. Она служит для хранения текущего среднего значения от чисел, вводимых пользователем.

    /* Вычисляем текущее среднее значение от чисел, вводимых пользователем.
    */
    #include
    using namespace std;
    int r_avg(int i);
    int main()
    {
    int num;
    do {
    cout << "Введите числа (-1 означает выход): ";
    cin >> num;
    if(num != -1)
    cout << "Текущее среднее равно: " << r_avg(num);
    cout << '\n';
    }while(num > -1);
    return 0;
    }
    // Вычисляем текущее среднее.
    int r_avg(int i)
    {
    static int sum=0, count=0;
    sum = sum + i;
    count++;
    return sum / count;
    }
    Здесь обе локальные переменные sum и count объявлены статическими и инициализированы значением 0. Помните, что для статических переменных инициализация выполняется только один раз (при первом выполнении функции), а не при каждом входе в функцию. В этой программе функция r_avg() используется для вычисления текущего среднего значения от чисел, вводимых пользователем. Поскольку обе переменные sum и
    count являются статическими, они поддерживают свои значения между вызовами функции
    r_avg(), что позволяет нам получить правильный результат вычислений. Чтобы убедиться в необходимости модификатора static, попробуйте удалить его из программы. После этого программа не будет работать корректно, поскольку промежуточная сумма будет теряться при каждом выходе из функции r_avg().
    1   ...   6   7   8   9   10   11   12   13   ...   33


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