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

  • 1.2.4. Области видимости

  • 1.2.4.1. Глобальные переменные

  • Совет Не используйте глобальные переменные.Если вы используете их, рано или поздно вы об этом пожалеете. Поверьте нам. Глобальные константы вродеconst

  • 1.2.4.2. Локальные переменные

  • 1.3.1. Арифметические операторы

  • Таблица 1.2.

  • Таблица 1.3.

  • Совет Для булевых выражений всегда используйте тип bool.Пожалуйста, обратите внимание, что сравнения нельзя выстраивать цепочка- ми наподобие следующей:bool in_bound = min

  • 1.3.3. Побитовые операторы

  • Основы C Моим детям. Никогда не смейтесь, помогая мне осваивать


    Скачать 1.68 Mb.
    НазваниеОсновы C Моим детям. Никогда не смейтесь, помогая мне осваивать
    Дата12.10.2022
    Размер1.68 Mb.
    Формат файлаpdf
    Имя файлаsovremennyy-cpp-piter.pdf
    ТипГлава
    #729589
    страница2 из 9
    1   2   3   4   5   6   7   8   9

    33
    1.2.3. Не сужающая инициализация в C++11
    Представим, что мы инициализируем переменную типа long длинным числом:
    long l2= 1234567890123;
    Этот код корректно компилируется и работает, если переменная типа long за- нимает 64 бита, как это происходит на большинстве 64-битовых платформ. Если тип long имеет длину всего 32 бита (этого можно добиться путем компиляции с использованием флага наподобие
    -m32), то показанное выше значение оказыва- ется слишком большим. Однако программа продолжает компилироваться (может быть, и с предупреждениями) и выполняться, но с другим значением, в котором отсечены ведущие биты.
    В C++11 введена инициализация, обеспечивающая отсутствие потери данных, или, иными словами, не допускающая сужения значений. Это достигается с помо- щью унифицированной инициализации (uniform initialization) или инициализации с
    фигурными скобками
    (braced initialization), которую мы только бегло затрагиваем здесь, а более подробно будем изучать в разделе 2.3.4. Значения в фигурных скоб- ках не могут быть сужены:
    long l= { 1234567890123 };
    Теперь компилятор будет проверять, может ли переменная l хранить указан- ное значение в целевой архитектуре.
    Защита компилятора от сужения позволяет нам убедиться, что значения не потеряют точности при инициализации. Обычная инициализация int числом с плавающей точкой разрешается в силу выполнения неявного преобразования:
    int i1 = 3.14; // Компилируется, несмотря на сужение (на ваш риск) int i1n = {3.14}; // Ошибка сужения: теряется дробная часть
    Новая разновидность инициализации во второй строке запрещает его, по- скольку при этом будет отсечена дробная часть числа с плавающей точкой. Ана- логично присваивание отрицательных значений беззнаковым переменным или константам пропускается традиционной инициализацией, но отклоняется новой разновидностью:
    unsigned u2 = -3; // Компилируется, несмотря на сужение (на ваш риск) unsigned u2n={-3}; // Ошибка сужения: отрицательное значение
    В предыдущих примерах мы использовали в инициализации литералы, и ком- пилятор проверял, представимо ли конкретное значение указанным типом:
    float f1= { 3.14 }; // OK
    Значение 3.14 не может быть представлено с абсолютной точностью в двоич- ном формате с плавающей точкой, но компилятор может установить значение f1 близким к 3.14. Когда float инициализируется переменной или константой double (не литералом), необходимо рассмотреть все возможные значения double и то, являются ли они преобразуемыми в тип float без потерь.
    02_ch01.indd 33 14.07.2016 10:46:06

    Основы C++
    34
    double d; float f2= {d}; // Ошибка сужения
    Обратите внимание, что сужение между двумя типами может быть взаимным:
    unsigned u3= {3}; int i2= {2}; unsigned u4= {i2}; // Ошибка сужения: возможно отрицательное значение int i3= {u3}; // Ошибка сужения: возможно слишком большое значение
    Типы signed int и unsigned int имеют одинаковый размер, но не все значе- ния каждого типа представимы другим типом.
    1.2.4. Области видимости
    Области видимости определяют время жизни и видимость (не статических) переменных и констант и способствуют установлению структуры в наших про- граммах.
    1.2.4.1. Глобальные переменные
    Каждая переменная, которую мы намерены использовать в программе, должна быть объявлена с указанием ее типа в некоторой более ранней точке кода. Пере- менная может находиться в глобальной или локальной области видимости. Гло- бальная переменная объявляется вне всех функций. После объявления обращать- ся к глобальным переменным можно в любом месте кода, даже внутри функций.
    Это выглядит очень удобным, в первую очередь, потому, что делает переменные легко доступными, но с ростом размера программы отслеживать изменения гло- бальных переменных становится более трудно и болезненно. В некоторый момент каждое изменение кода потенциально способно вызвать лавину ошибок.
    Совет
    Не используйте глобальные переменные.
    Если вы используете их, рано или поздно вы об этом пожалеете. Поверьте нам.
    Глобальные константы вроде
    const double pi= 3.14159265358979323846264338327950288419716939;
    можно применять, поскольку они не вызывают побочных действий.
    1.2.4.2. Локальные переменные
    Локальная переменная объявляется внутри тела функции. Ее видимость/до- ступность ограничивается заключенным в фигурные скобки
    {} блоком, в кото- ром она объявлена. Точнее, область видимости переменной начинается с ее объяв- ления и заканчивается закрывающей фигурной скобкой блока, в котором она объявлена.
    02_ch01.indd 34 14.07.2016 10:46:06

    1.2. Переменные
    35
    Если мы определяем pi в функции main:
    int main ()
    { const double pi= 3.14159265358979323846264338327950288419716939; std::cout << "pi = " << pi << ".\n";
    }
    то переменная pi существует только в функции main. Мы можем определять бло- ки внутри функций и других блоков:
    int main ()
    {
    { const double pi= 3.14159265358979323846264338327950288419716939;
    }
    // Ошибка: pi вне области видимости: std::cout << "pi = " << pi << ".\n";
    }
    В этом примере определение pi ограничено блоком внутри функции, так что попытка вывода этого значения в оставшейся части функции приводит к ошибке времени компиляции, поскольку pi находится вне области видимости.
    1.2.4.3. Сокрытие
    Когда во вложенных областях видимости имеется переменная с тем же именем, видна только одна переменная. Переменная во внутренней области видимости скрывает одноименные переменные во внешних областях. Например:
    int main ()
    { int a= 5; // Определение a №1
    {
    a = 3; // Присваивание a №1, a №2 еще не определена int a; // Определение a №2
    a = 8; // Присваивание a №2, a №1 скрыта
    {
    a = 7; // Присваивание a №2
    }
    } // Конец области видимости a №2
    a = 11; // Присваивание a №1 (a №2 вне области видимости) return 0;
    }
    Из-за сокрытия мы должны различать время жизни и видимость переменных.
    Например, продолжительность жизни переменной a №1 — от ее объявления до конца функции main. Однако она видима только от ее объявления до объявле- ния a №2 и вновь после закрытия блока, содержащего переменную a №2. Факти- чески видимость представляет собой время жизни минус время, когда перемен- ная скрыта.
    02_ch01.indd 35 14.07.2016 10:46:07

    Основы C++
    36
    Определение одного и того же имени переменной дважды в одной области ви- димости является ошибкой.
    Преимуществом областей видимости является то, что нам не нужно беспоко- иться о том, не определено ли имя где-то за пределами области видимости. Оно будет просто скрыто, и не создаст конфликт имен
    4
    . К сожалению, сокрытие делает недоступными одноименные переменные из внешней области видимости. В неко- торой степени справиться с этим можно с помощью разумного переименования.
    Лучшим решением для управления вложенностью и доступностью являются про- странства имен (см. раздел 3.2.1).
    Статические (
    static) переменные являются исключением, которое подтверж- дает правило. Они живут до конца выполнения программы, но видны только в своей области видимости. Опасаясь, что их подробное описание на этом этапе знакомства с языком программирования будет более отвлекающим, чем полез- ным, мы отложили их обсуждение до раздела А.2.2.
    1.3. Операторы
    C++ имеет множество встроенных операторов. Имеются различные виды опе- раторов.
    Вычислительные

    Арифметические.

    ++, +, *, %, …
    Булевы

    Сравнения.
    *
    <=, !=, …
    Логические.
    *
    && и ||
    Побитовые.

    , << и >>, &, ^ и |
    Присваивания.

    =, +=, …
    Потока управления. Вызов функции,

    ?: и ,
    Работы с памятью.

    new и delete
    Доступа.

    ., ->, [ ], *, …
    Работы с типами.

    dynamic_cast, typeid, sizeof, alignof, …
    Обработки ошибок.

    throw
    В этом разделе приведен обзор операторов. Некоторые операторы лучше опи- сывать в контексте соответствующих возможностей языка; например, разреше- ние области видимости лучше всего объяснять вместе с пространствами имен.
    4
    В противоположность макросам, устаревшей и безответственной возможности, унаследованной от C, которой следует избегать любой ценой, поскольку она подрывает всю структуру и надежность языка.
    02_ch01.indd 36 14.07.2016 10:46:07

    1.3. Операторы
    37
    Большинство операторов могут быть перегружены для пользовательских типов, т.е. мы можем решать, какие вычисления выполняются, когда один или несколько аргументов в выражении имеют созданные нами типы.
    В конце этого раздела вы найдете краткую таблицу (табл. 1.8) приоритетов операторов. Может оказаться полезным распечатать ее и хранить рядом с монито- ром — так поступают многие программисты, и почти никто не знает весь список приоритетов наизусть. Если вы не уверены в приоритетах или если вы считаете, что так код будет более понятен программистам, работающим с вашими исходны- ми текстами, без колебаний используйте скобки вокруг подвыражений. В разделе
    В.2 имеется полный список всех операторов с краткими описаниями и ссылками.
    1.3.1. Арифметические операторы
    В табл. 1.2 перечислены все арифметические операторы, доступные в C++. Мы рас- сортировали их согласно приоритетам, но давайте рассмотрим их по одному.
    Таблица 1.2. Арифметические операторы
    Операция
    Выражение
    Пост-инкремент x++
    Пост-декремент x--
    Пре-инкремент
    ++x
    Пре-декремент
    --x
    Унарный плюс
    +x
    Унарный минус
    -x
    Умножение x * y
    Деление x / y
    Остаток от деления (деление по модулю)
    x % y
    Сложение x + y
    Вычитание x - y
    Первой разновидностью операций являются инкремент и декремент. Эти опе- рации могут использоваться для увеличения или уменьшения числа на 1. Так как они изменяют значение числа, они имеют смысл только для переменных, но не для временных результатов, например:
    int i = 3; i++; // Теперь i равно 4 const int j= 5; j++; // Ошибка: j является константой
    (3+5)++; // Ошибка: 3 + 5 является временным результатом
    Короче говоря, операциям инкремента и декремента нужно что-то, что изме- няемо и адресуемо. Техническим термином для адресуемого элемента данных является lvalue (см. определение В.1 в приложении В). В приведенном выше
    02_ch01.indd 37 14.07.2016 10:46:07

    Основы C++
    38
    фрагменте кода это верно только для переменной i. В противоположность ему j является константой, а значение
    3+5 не адресуемо.
    Обе записи — префиксная и постфиксная — одинаково добавляют 1 к значе- нию переменной или вычитают 1 из него. Однако смысл выражения инкремента и декремента различается для префиксных и постфиксных операторов. Префик- сные операторы возвращают измененное значение, а постфиксные — старое зна- чение, например:
    int i = 3, j= 3; int k = ++i + 4; // i = 4, k = 8 int l = j++ + 4; // j = 4, l = 7
    В конечном итоге и i, и j равны 4. Однако при вычислении l используется ста- рое значение j, в то время как в первом сложении используется уже увеличенное значение i.
    В общем случае лучше воздерживаться от использования инкремента и декре- мента в математических выражениях и заменять их выражениями j+1 и тому по- добными или выполнять инкремент и декремент отдельно. Так исходный текст легче читается и понимается человеком, а компилятору легче оптимизировать код, когда математические выражения не имеют побочных эффектов. Вскоре мы увидим, с чем это связано (раздел 1.3.12).
    Унарный минус изменяет знак числового значения:
    int i= 3; int j= -i; // j = -3
    Унарный плюс не выполняет никакого арифметического действия над стандарт- ными типами. Для пользовательских типов мы можем определить свое поведение для унарного плюса и минуса. Как показано в табл.1.2, эти унарные операторы имеют приоритет, совпадающий с приоритетом префиксных инкремента и декре- мента.
    Операции
    * и / являются естественными умножением и делением, и обе они определяются для всех числовых типов. Когда оба аргумента деления являются целыми числами, дробная часть результата отбрасывается (округление по направ- лению к нулю). Оператор
    % возвращает остаток от целочисленного деления. Та- ким образом, оба аргумента этого оператора должны иметь целочисленный тип.
    Последними по очереди, но не по важности идут операторы
    + и -, которые обозначают сложение и вычитание двух переменных или выражений.
    Семантические сведения об операциях — как округляются результаты или как обрабатывается переполнение — в языке не определены. По соображениям про- изводительности C++ оставляет окончательное решение за используемым аппа- ратным обеспечением.
    В общем случае унарные операторы имеют более высокий приоритет, чем би- нарные. В тех редких случаях, когда применяются и префиксный, и постфиксный унарные операторы, префиксный оператор имеет более высокий приоритет, чем постфиксный.
    02_ch01.indd 38 14.07.2016 10:46:08

    1.3. Операторы
    39
    Бинарные операторы ведут себя так же, как и в математике. Умножение и де- ление имеют больший приоритет, чем сложение и вычитание, а сами операции являются левоассоциативными, т.е.
    x - y + z всегда трактуется как
    (x - y) + z
    Есть кое-что, что действительно важно запомнить: порядок вычисления аргу- ментов не определен. Взгляните на этот код:
    int i= 3, j= 7, k; k= f(++i) + g(++i) + j;
    В этом примере ассоциативность гарантирует, что первое сложение выполняет- ся до второго. Но какое выражение вычисляется первым — f(++i) или g(++i), — зависит от реализации компилятора. Таким образом, k может принимать любое значение — f(4)+g(5)+7, f(5)+g(4)+7 или даже f(5)+g(5)+7. Кроме того, не- льзя полагать, что результат будет тем же самым на другой платформе. В общем случае изменять значения в выражениях — опасная практика. При определенных условиях это работает, но мы всегда должны обращать особое внимание на такой код и тщательно его тестировать. Словом, лучше потратить время на ввод допол- нительных символов и выполнять изменения отдельно. Подробнее об этом мы поговорим в разделе 1.3.12.

    c++03/num_1.cpp
    С помощью этих операторов мы можем написать нашу первую (завершенную) числовую программу:
    #include int main ()
    { const float r1= 3.5, r2 = 7.3, pi = 3.14159; float area1 = pi*r1*r1; std::cout << "Круг с радиусом " << r1 << " имеет площадь "
    << area1 << "." << std::endl; std::cout << "Среднее " << r1 << " и " << r2 << " равно "
    << (r1 + r2)/2 << "." << std::endl;
    }
    Если аргументы бинарной операции имеют различные типы, один или не- сколько аргументов автоматически приводятся к общему типу в соответствии с правилами из раздела В.3.
    Преобразование может приводить к потере точности. Числа с плавающей точ- кой предпочтительнее целых чисел, и очевидно, что преобразование 64-разрядного long в 32-разрядный float приводит к потере точности; даже 32-разрядные значе- ния типа int не всегда могут быть представлены правильно в виде 32-разрядных
    02_ch01.indd 39 14.07.2016 10:46:08

    Основы C++
    40
    значений типа float, так как некоторые биты нужны для представления показа- теля степени. Бывают также ситуации, когда целевая переменная может хранить правильный результат, но точность уже потеряна при промежуточных расчетах.
    Чтобы проиллюстрировать это поведение преобразования, давайте рассмотрим следующий пример:
    long l= 1234567890123; long l2= l + 1.0f - 1.0; // Неточно long l3= l + (1.0f - 1.0); // Верно
    На платформе автора это приводит к следующему результату:
    l2 = 1234567954431 l3 = 1234567890123
    В случае l2 мы теряем точность из-за промежуточных преобразований, в то время как l3 вычисляется правильно. Этот пример, правда, носит искусственный характер, но вы должны быть осведомлены о риске, связанном с неточными про- межуточными результатами.
    К счастью, вопрос неточности не будет беспокоить нас в следующем разделе.
    1.3.2. Булевы операторы
    Булевы операторы включают логические операторы и операторы сравнения.
    Все они, как и предполагается в названии, возвращают значения типа bool. Эти операторы и их смысл перечислены в табл. 1.3 (сгруппированы в соответствии с приоритетом).
    Таблица 1.3. Булевы операторы
    Операция
    Выражение
    Нет
    !b
    Больше x > y
    Больше или равно x >= y
    Меньше x < y
    Меньше или равно x <= y
    Равно x == y
    Не равно x != y
    Логическое И
    b && c
    Логическое ИЛИ
    b || c
    Бинарные операторы сравнения и логические операторы имеют приоритеты, меньшие, чем приоритеты всех арифметических операторов. Это означает, что выражение наподобие
    4>=1+7 вычисляется так, как если бы оно было записано как
    4>=(1+7). И наоборот, унарный оператор ! для логического отрицания имеет более высокий приоритет, чем приоритет любого бинарного оператора.
    02_ch01.indd 40 14.07.2016 10:46:08

    1.3. Операторы
    41
    В старом (или старомодном) коде вы можете увидеть логические операции, вы- полняемые над значениями типа int. Воздержитесь от этого в своем коде — это менее удобочитаемо и может вести к неожиданному поведению программы.
    Совет
    Для булевых выражений всегда используйте тип bool.
    Пожалуйста, обратите внимание, что сравнения нельзя выстраивать цепочка- ми наподобие следующей:
    bool in_bound = min <= x <= y <= max; // Ошибка
    Вместо этого требуется более длинное логическое выражение:
    bool in_bound = min <= x && x <= y && y <= max;
    В следующем разделе вы увидите операторы, очень похожие на рассмотренные.
    1.3.3. Побитовые операторы
    Эти операторы позволяют работать с отдельными битами целочисленных ти- пов. Они очень важны для системного программирования и не так важны при разработке современного программного обеспечения. В табл. 1.4 эти операторы сгруппированы в соответствии с приоритетом.
    1   2   3   4   5   6   7   8   9


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