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

  • И наконец, можно узнать, как дела у ученика 27: bool status = quiz1 amp; (1UL lt;lt; 27); // как дела у ученика 27

  • Язык программирования C Пятое издание


    Скачать 1.85 Mb.
    НазваниеЯзык программирования C Пятое издание
    Дата15.07.2019
    Размер1.85 Mb.
    Формат файлаpdf
    Имя файла620354-www.libfox.ru.pdf
    ТипДокументы
    #84130
    страница11 из 54
    1   ...   7   8   9   10   11   12   13   14   ...   54

    используемый для отображения элементов вектора, задействует префиксный оператор инкремента?
    Упражнение 4.19. С учетом того, что ptr указывает на тип int, vec — вектор vector<int>, a ival имеет тип int, объясните поведение каждого из следующих выражений. Есть ли среди них неправильные? Почему? Как их исправить?
    (a) ptr != 0 && *ptr++ (b) ival++ && ival
    (с) vec[ival++] <= vec[ival]
    4.6. Операторы доступа к членам
    Page 193/1103

    Операторы точка (.) (dot operator) (см. раздел 1.5.2) и стрелка (->) (arrow operator) (см.
    раздел 3.4.1) обеспечивают доступ к члену. Оператор точка выбирает член из объекта типа класса; оператор стрелка определен так, что код ptr -> mem эквивалентен коду (* ptr ). mem . string s1 = "a string", *p = &s1; auto n = s1.size(); // вызов функции-члена size() строки s1 n = (*p).size(); // вызов функции-члена size() объекта, на который
    // указывает указатель p n = p->size(); // эквивалент (*p).size()
    Поскольку приоритет обращения к значению ниже, чем оператора точка, часть обращения к значению следует заключить в скобки. Если пропустить круглые скобки, этот код поведет себя совсем по-иному:
    // вызов функции-члена size() объекта, на который указывает указатель p
    // затем обращение к значению результата!
    *p.size(); // ошибка: p - указатель, он не имеет функции-члена size()
    Этот код пытается вызвать функцию-член size() объекта p. Однако p — это указатель, у которого нет никаких членов; этот код не будет откомпилирован.
    Оператор стрелка получает операнд в виде указателя и возвращает l-значение. Оператор точка возвращает l-значение, если объект, член которого выбирается, является l-значением;
    в противном случае результат — r-значение. Упражнения раздела 4.6
    Упражнение 4.20. С учетом того, что iter имеет тип vector<string>::iterator, укажите, какие из следующих выражений допустимы, если таковые имеются. Объясните поведение допустимых выражений, и почему ошибочные не допустимы?
    Page 194/1103

    (a) *iter++; (b) (*iter)++; (с) *iter.empty()
    (d) iter->empty(); (e) ++*iter; (f) iter++->empty();
    4.7. Условный оператор
    Условный оператор (оператор ?:) (conditional operator) позволяет внедрять простые конструкции if...else непосредственно в выражение. Условный оператор имеет следующий синтаксис: условие ? выражение1 : выражение2 ; где условие — это выражение, используемое в качестве условия, а выражение1 и выражение2 — это выражения одинаковых типов (или типов, допускающих преобразование в общий тип). Эти выражения выполняются в зависимости от условия . Если условие истинно, то выполняется выражение1 ; в противном случае выполняется выражение2 . В качестве примера использования условного оператора рассмотрим код,
    определяющий, является ли оценка (grade) проходной (pass) или нет (fail): string finalgrade = (grade < 60) ? "fail" : "pass";
    Условие проверяет, не меньше ли оценка 60. Если это так, то результат выражения "fail"; в противном случае — результат "pass". Подобно операторам логического AND и OR
    (&& и ||), условный оператор гарантирует, что выполнено будет только одно из выражений, выражение1 или выражение2 .
    Результат условного оператора — l-значение, если оба выражения l-значения или если они допускают преобразование в общий тип l-значения. В противном случае результат —
    r-значение. Вложенные условные операторы
    Один условный оператор можно вложить в другой. Таким образом, условный оператор применяются как условие или как один или оба выражения другого условного оператора. В качестве примера используем пару вложенных условных операторов для трехступенчатой проверки оценки, чтобы выяснить, является ли
    Page 195/1103
    она выше проходной, просто проходной или непроходной. finalgrade = (grade > 90) ? "high pass"
    : (grade < 60) ? "fail" : "pass";
    Первое условие проверяет, не выше ли оценка 90. Если это так, то выполняется выражение после ?, возвращающее литерал "high pass". Если условие ложно, выполняется ветвь :,
    которая сама является другим условным выражением. Это условное выражение проверяет,
    не меньше ли оценка 60. Если это так, то обрабатывается ветвь ?, возвращающая литерал "fail". В противном случае ветвь : возвращает литерал "pass".
    Условный оператор имеет правосторонний порядок, т.е. его операнды группируются (как обычно) справа налево. Порядок объясняет тот факт, что правое условное выражение,
    сравнивающее grade со значением 60, образует ветвь : левого условного выражения.
    Вложенные условные выражения быстро становятся нечитабельными, поэтому нежелательно создавать больше двух или трех вложений. Применение условного оператора в выражении вывода
    Условный оператор имеет довольно низкий приоритет. Когда условный оператор используется в большом выражении, его, как правило, следует заключать в круглые скобки.
    Например, условный оператор нередко используют для отображения одного из значений в зависимости от результата проверки условия. Отсутствие круглых скобок вокруг условного оператора в выражении вывода может привести к неожиданным результатам: cout << ((grade < 60) ? "fail" : "pass"); // выводит pass или fail cout << (grade < 60) ? "fail" : "pass"; // выводит 1 или 0! cout << grade < 60 ? "fail" : "pass"; // ошибка: сравнивает cout с 60
    Второе выражение использует сравнение grade и 60 как операнд оператора <<. В
    зависимости от истинности или ложности выражения grade < 60 выводится значение 1 или
    0. Оператор << возвращает объект cout, который и проверяется в условии условного оператора. Таким образом, второе выражение эквивалентно следующему: cout << (grade < 60); // выводит 1 или 0 cout ? "fail" : "pass"; // проверяет cout, а затем возвращает один из
    // этих двух литералов в зависимости от
    // истинности объекта cout
    Page 196/1103

    Последнее выражение ошибочно, поскольку оно эквивалентно следующему: cout << grade; // приоритет оператора ниже, чем у
    // сдвига, поэтому сначала выводится оценка, cout < 60 ? "fail" : "pass"; // затем cout сравнивается с 60! Упражнения раздела 4.7
    Упражнение 4.21. Напишите программу, использующую условный оператор для поиска в векторе vector<int> элементов с нечетным значением и их удвоения.
    Упражнение 4.22. Дополните программу, присваивающую переменной значение оценки
    (высокая, проходная, не проходная), еще одной оценки, минимально проходной, от 60 до 75
    включительно. Напишите две версии: одна использует только условные операторы; вторая использует один или несколько операторов if. Как по вашему, какую версию проще понять и почему?
    Упражнение 4.23. Следующее выражение не компилируется из-за приоритета операторов.
    Используя таблицу из раздела 4.12, объясните причину проблемы. Как ее исправить? string s = "word"; string p1 = s + s[s.size() - 1] == 's' ? "" : "s" ;
    Упражнение 4.24. Программа, различавшая проходную и непроходную оценку, зависела от того факта, что условный оператор имеет правосторонний порядок. Опишите, как обрабатывался бы этот оператор, имей он левосторонний порядок.
    4.8. Побитовые операторы
    Побитовые операторы (bitwise operator) получают операнды целочисленного типа, которые они используют как коллекции битов. Эти операторы позволяют проверять и устанавливать отдельные биты. Как будет описано в разделе 17.2, эти операторы можно также использовать для библиотечного типа bitset, представляющего коллекцию битов изменяемого размера.
    Как обычно, если операнд — "малое целое число", его значение сначала преобразуется
    (раздел 4.11) в больший целочисленный тип. Операнды могут быть знаковыми или беззнаковыми.
    Таблица 4.3. Побитовые операторы (левосторонний порядок) Оператор Действие
    Применение

    Побитовое NOT выражение << Сдвиг влево выражение1 <<
    Page 197/1103
    выражение2 >> Сдвиг вправо выражение1 >> выражение2 & Побитовое AND выражение1 & выражение2 ^ Побитовое XOR выражение1 ^ выражение2 | Побитовое OR выражение1 | выражение2
    Если операнд знаковый и имеет отрицательное значение, то способ обработки "знакового разряда" большинства битовых операций зависит от конкретной машины. Кроме того,
    результат сдвига влево, изменяющего знаковый разряд, непредсказуем.
    Поскольку нет никаких гарантий однозначного выполнения побитовых операторов со знаковыми переменными на разных машинах, настоятельно рекомендуется использовать в них только беззнаковые целочисленные значения. Побитовые операторы сдвига
    Мы уже использовали перегруженные версии операторов >> и <<, которые библиотека IO определяет для ввода и вывода. Однако первоначальное значение этих операторов — побитовый сдвиг операндов. Они возвращают значение, являющееся копией
    (возможно преобразованной) левого операнда, биты которого сдвинуты. Правый операнд не должен быть отрицательным, и его значение должно быть меньше количества битов результата. В противном случае операция имеет неопределенный результат. Биты сдвигаются влево (<<) или право (>>), при этом вышедшие за пределы биты отбрасываются.
    Оператор сдвига влево (<<) (left-shift operator) добавляет нулевые биты справа.
    Поведение оператора сдвига вправо (>>) (right-shift operator) зависит от типа левого операнда: если он беззнаковый, то оператор добавляет слева нулевые биты; если он знаковый, то результат зависит от конкретной реализации: слева вставляются либо копии знакового разряда, либо нули.
    В этих примерах подразумевается, что младший бит расположен справа, тип char содержит 8
    битов, а тип int — 32 бита
    //
    0233 - восьмеричный литерал (см. раздел 2.1.3) unsigned char bits = 0233; 1 0 0 1 1 0 1 1 bits << 8 // bits преобразуется в int и сдвигается влево на 8 битов
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 1 0 1 1 0 0 0 0 0 0 0 0
    Page 198/1103
    bits << 31 // сдвиг влево на 31 бит отбрасывает крайние левые биты
    1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 bits >> 3 // сдвиг вправо на 3 бита отбрасывает 3 крайних правых бита
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 1 Побитовый оператор NOT
    Побитовый оператор NOT () (bitwise NOT operator) создает новое значение с инвертированными битами своего операнда. Каждый бит, содержащий 1, превращается в 0;
    каждый бит, содержащий 0, — в 1. unsigned char bits = 0227; 1 0 0 1 0 1 1 1
    bits
    1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 0 1 0 0 0
    Здесь операнд типа char сначала преобразуется в тип int. Это оставляет значение неизменным, но добавляет нулевые биты в позиции старших разрядов. Таким образом,
    преобразование в тип int добавляет 24 бита старших разрядов, заполненных нулями. Биты преобразованного значения инвертируются. Побитовые операторы AND, OR и XOR
    Побитовые операторы AND (&), OR (|) и XOR (^) создают новые значения с битовым шаблоном, состоящим из двух их операндов. unsigned char b1 = 0145; 0 1 1 0 0 1 0 1 unsigned char b2 = 0257; 1 0 1 0 1 1 1 1 b1 & b2 Все 24 старших бита 0 0 0 1 0 0 1 0 1 b1 | b2 Все 24 старших бита 0 1 1 1 0 1 1 1 1 b1 ^ b2 Все 24 старших бита 0 1 1 0 0 1 0 1 0
    Каждая битовая позиция результата побитового оператора AND (&) содержит 1, если оба операнда содержат 1 в этой позиции; в противном случае результат — 0. У побитового оператора OR (|) бит содержит 1, если один или оба операнда содержат 1; в противном случае результат — 0. Для побитового оператора XOR (^) бит содержит 1, если любой, но не оба операнда содержат 1; в противном случае результат — 0.
    Побитовые и логические (см. раздел 4.3) операторы нередко путают. Например, путают побитовый оператор & с логическим &&, побитовый | с логическим || и побитовый с логическим !. Использование побитовых операторов
    Рассмотрим пример использования побитовых операторов. Предположим, что есть класс с 30
    учениками. Каждую неделю класс отвечает на контрольные вопросы с оценкой "сдано/не сдано". Результаты всех контрольных записываются, по одному биту на ученика, чтобы
    Page 199/1103
    представить успешную оценку или нет. Каждую контрольную можно представить в виде беззнакового целочисленного значения. unsigned long quiz1 = 0; // это значение используется
    // как коллекция битов
    Переменная quiz1 определена как unsigned long. Таким образом, на любой машине она будет содержать по крайней мере 32 бита. Переменная quiz1 инициализируется явно, чтобы ее значение было определено изначально.
    Учитель должен быть способен устанавливать и проверять отдельные биты. Например,
    должна быть возможность установить бит, соответствующий ученику номер 27, означающий,
    что этот ученик сдал контрольную. Чтобы указать, что ученик 27 прошел контрольную,
    создадим значение, у которого установлен только бит номер 27. Если затем применить побитовый оператор OR к этому значению и значению переменной quiz1, то все биты, кроме бита 27, останутся неизменными.
    В данном примере счет битов переменной quiz1 начинается с 0, соответствующего младшему биту, 1 соответствует следующему биту и т.д.
    Чтобы получить значение, означающее, что ученик 27 сдал контрольную, используется оператор сдвига влево и целочисленный литерал 1 типа unsigned long (см. раздел 2.1.3).
    1UL << 27 // создает значение только с одним установленным битом
    // в позиции 27
    Первоначально переменная 1UL имеет 1 в самом младшем бите и по крайней мере 31
    нулевой бит. Она определена как unsigned long, поскольку тип int гарантированно имеет только 16 битов, а необходимо по крайней мере 27. Это выражение сдвигает 1 на 27 битовых позиций, вставляя в биты позади 0.
    К этому значению и значению переменной quiz1 применяется оператор OR. Поскольку необходимо изменить значение самой переменной quiz1, используем составной оператор присвоения (см. раздел 4.4): quiz1 |= 1UL << 27; // указать, что ученик номер 27 сдал контрольную
    Оператор |= выполняется аналогично оператору +=. quiz1 = quiz1 | 1UL << 27; // эквивалент quiz1 |= 1UL << 21;
    Предположим, что учитель пересмотрел контрольные и обнаружил, что ученик 27 фактически списал работу. Теперь учитель должен сбросить бит 27 в 0. На сей раз необходимо целое число, бит 27 которого сброшен, а все остальные установлены в 1. Применение побитового
    Page 200/1103

    AND к этому значению и значению переменной quiz1 позволяет сбросить только данный бит: quiz1 &= (1UL << 27); // ученик номер 27 не прошел контрольную
    Мы получаем значение со всеми установленными битами, кроме бита 27, инвертируя предыдущее значение. У него все биты были сброшены в 0, кроме бита 27, который был установлен в 1. Применение побитового NOT к этому значению сбросит бит 27, а все другие установит. Применение побитового AND к этому значению и значению переменной quiz1
    оставит неизменными все биты, кроме бита 27.

    И наконец, можно узнать, как дела у ученика 27: bool status = quiz1 & (1UL << 27); // как дела у ученика 27?
    Здесь оператор AND применяется к значению с установленным битом 27 и значением переменной quiz1. Результат отличен от нуля (т.е. истинен), если бит 27 в значении переменной quiz1 установлен; в противном случае он нулевой. Операторы сдвига (они же ввода и вывода) имеют левосторонний порядок
    Хотя многие программисты никогда не используют побитовые операторы непосредственно,
    почти все они использует их перегруженные версии в виде операторов ввода и вывода.
    Перегруженный оператор имеет тот же приоритет и порядок, что и его встроенная версия.
    Поэтому программисты должны иметь понятие о приоритете и порядке операторов сдвига,
    даже если они никогда не используют их встроенные версии.
    Поскольку операторы сдвига имеют левосторонний порядок, выражение cout << "hi" << " there" << endl; выполняется так:
    ( (cout << "hi") << " there" ) << endl;
    В этом операторе операнд "hi" группируется с первым символом <<. Его результат группируется со вторым, а его результат с третьим символом.
    Приоритет операторов сдвига средний: ниже, чем у арифметических операторов, но выше,
    чем у операторов отношения, присвоения и условных операторов. Эти различия в уровнях приоритета свидетельствуют о том, что для правильной группировки операторов с более низким приоритетом следует использовать круглые скобки. cout << 42 + 10; // ok: приоритет + выше, поэтому выводится сумма cout << (10 < 42); // ok: группировку определяют скобки; выводится 1 cout << 10 < 42; // ошибка: попытка сравнить cout с 42!
    Последний оператор cout интерпретируется так
    Page 201/1103

    (cout << 10) < 42;
    Он гласит: "записать 10 в поток cout, а затем сравнить результат (т.е. поток cout) со значением 42". Упражнения раздела 4.8
    Упражнение 4.25. Каково значение выражения 'q' << 6 на машине с 32-битовыми целыми числами и 8-битовыми символами, с учетом, что символ 'q' имеет битовое представление

    01110001?
    Упражнение 4.26. Что будет, если в приведенном выше примере оценки учеников использовать для переменной quiz1 тип unsigned int?
    Упражнение 4.27. Каков результат каждого из этих выражений? unsigned long ul1 = 3, ul2 = 7;
    (a) ul1 & ul2 (b) ul1 | ul2
    (c) ul1 && ul2 (d) ul1 || ul2 4.9. Оператор sizeof
    Оператор sizeof возвращает размер в байтах результата выражения или указанного по имени типа. Оператор имеет правосторонний порядок. Результат оператора sizeof — это константное выражение (см. раздел 2.4.4) типа size_t (см. раздел 3.5.2). Оператор существует в двух формах. sizeof( тип ) sizeof выражение
    Во второй форме оператор sizeof возвращает размер типа, возвращаемого выражением.
    Оператор sizeof необычен тем, что он не выполняет свой операнд.
    Sales_data data, *p; sizeof(Sales_data); // размер, необходимый для хранения объекта
    // типа Sales_item sizeof data; // размер типа данных, аналог sizeof(Sales_data) sizeof p; // размер указателя
    Page 202/1103
    sizeof *p; // размер типа, на который указывает указатель p,
    // т.е. sizeof(Sales_data) sizeof data.revenue; // размер типа члена revenue класса Sales_data sizeof Sales_data::revenue; // альтернативный способ получения
    // размера revenue
    Наиболее интересен пример sizeof *p. Во-первых, поскольку оператор sizeof имеет правосторонний порядок и тот же приоритет, что и оператор *, это выражение группируется справа налево. Таким образом, оно эквивалентно выражению sizeof(*p). Во-вторых, поскольку оператор sizeof не выполняет свой операнд, не имеет значения, допустим ли указатель p (т.е.
    инициализирован ли он) (см. раздел 2.3.2). Обращения к значению недопустимого указателя оператор sizeof не осуществляет, и указатель фактически не используется, поэтому он безопасен. Ему и не нужно обращаться к значению указателя, чтобы выяснить, какой тип он возвратит.
    По новому стандарту для доступа к члену класса при получении его размера можно использовать оператор области видимости. Обычно к членам класса можно обратиться только через объект этого класса. Больше не обязательно предоставлять объект, так как оператор sizeof не обязан выбирать член класса, чтобы узнать его размер.
    Результат применения оператора sizeof частично зависит от типа, к которому он применен.
    • Если это тип char или выражения, результат которого имеет тип char, то это гарантированно будет 1.
    • Если это ссылка, то возвращает размер типа объекта, на который она ссылается.
    • Если это указатель, то возвращается размер, необходимый для хранения указателя.
    • Если это обращение к значению указателя, то возвращается размер типа объекта, на который он указывает, вне зависимости от его допустимости.
    • Если это массив, то возвращается размер всего массива. Это эквивалентно получению размера элемента массива и его умножению на количество элементов. Обратите внимание,
    что оператор sizeof не преобразует массив в указатель.
    • Если это строка или вектор, то возвращается размер только фиксированной части этих типов; но не размер, используемый элементами объекта.
    Поскольку оператор sizeof возвращает размер всего массива, разделив размер массива на размер элемента, можно определить количество элементов в массиве:
    // sizeof(ia)/sizeof(*ia) возвращает количество элементов в ia
    Page 203/1103
    constexpr size_t sz = sizeof (ia)/sizeof(*ia); int arr2[sz]; // ok: sizeof возвращает константное выражение
    //
    (p. 2.4.4)
    Так как оператор sizeof возвращает константное выражение, его результат можно использовать в выражении для определения размерности массива. Упражнения раздела 4.9
    Упражнение 4.28. Напишите программу для вывода размера каждого из встроенных типов.
    Упражнение 4.29. Предскажите вывод следующего кода и объясните свое рассуждение.

    1   ...   7   8   9   10   11   12   13   14   ...   54


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