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

  • Примечание [O.A.1]

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


    Скачать 5.41 Mb.
    НазваниеС для начинающих
    Дата24.08.2022
    Размер5.41 Mb.
    Формат файлаpdf
    Имя файлаЯзык программирования C++. Вводный курс.pdf
    ТипДокументы
    #652350
    страница14 из 93
    1   ...   10   11   12   13   14   15   16   17   ...   93
    136
    Alice Emma has long flowing red hair. Her Daddy says when the wind blows through her hair, it looks almost alive, 1ike a fiery bird in flight. A beautiful fiery bird, he tells her, magical but untamed. "Daddy, shush, there is no such thing," she tells him, at the same time wanting him to tell her more. Shyly, she asks,
    "I mean, Daddy, is there?"
    Слов: 65 the/The: 2
    it/It: 1 согласных: 190 a: 22 e: 30 i: 24 о: 10 u: 7
    Упражнение 3.26
    В наших реализациях конструкторов и операций присваивания содержится много повторов. Попробуйте вынести повторяющийся код в отдельную закрытую функцию- член, как это было сделано в разделе 2.3. Убедитесь, что новый вариант работоспособен.
    Упражнение 3.27
    Модифицируйте тестовую программу так, чтобы она подсчитывала и согласные b, d, f, s, t.
    Упражнение 3.28
    Напишите функцию-член, подсчитывающую количество вхождений символа в строку
    String
    , используя следующее объявление:
    };
    Упражнение 3.29
    Реализуйте оператор конкатенации строк (+) так, чтобы он конкатенировал две строки и возвращал результат в новом объекте String. Вот объявление функции:
    }; class String { public:
    // ... int count( char ch ) const;
    // ... class String { public:
    // ...
    String operator+( const String &rhs ) const;
    // ...

    С++ для начинающих
    137
    4
    4.
    Выражения
    В главе 3 мы рассмотрели типы данных – как встроенные, так и предоставленные стандартной библиотекой. Здесь мы разберем предопределенные операции, такие, как сложение, вычитание, сравнение и т.п., рассмотрим их приоритеты. Скажем, результатом выражения 3+4*5 является 23, а не 35 потому, что операция умножения (*) имеет более высокий приоритет, чем операция сложения (+). Кроме того, мы обсудим вопросы преобразований типов данных – и явных, и неявных.
    Например, в выражении 3+0.7 целое значение 3 станет вещественным перед выполнением операции сложения.
    4.1.
    Что такое выражение?
    Выражение состоит из одного или более операндов, в простейшем случае – из одного литерала или объекта. Результатом такого выражения является r-значение его операнда.
    Например:
    }
    Результатом вычисления выражения 3.14159 станет 3.14159 типа double, выражения "melancholia"
    – адрес первого элемента строки типа const char*. Значение выражения upperBound – это значение объекта upperBound, а его типом будет тип самого объекта.
    Более общим случаем выражения является один или более операндов и некоторая операция, применяемая к ним: first_name + " " + 1ast_name
    Операции обозначаются соответствующими знаками. В первом примере сложение применяется к salary и raise. Во втором выражении size делится на 2. Частное используется как индекс для массива ivec. Получившийся в результате операции взятия индекса элемент массива умножается на delta. В третьем примере два строковых объекта конкатенируются между собой и со строковым литералом, создавая новый строковый объект.
    Операции, применяемые к одному операнду, называются унарными (например, взятие адреса (&) и разыменование (*)), а применяемые к двум операндам – бинарными. Один и void mumble() {
    3.14159;
    "melancholia"; upperBound; salary + raise ivec[ size/2 ] * delta

    С++ для начинающих
    138
    тот же символ может обозначать разные операции в зависимости от того, унарна она или бинарна. Так, в выражении
    *ptr
    *
    представляет собой унарную операцию разыменования. Значением этого выражения является значение объекта, адрес которого содержится в ptr. Если же написать: var1 * var2 то звездочка будет обозначать бинарную операцию умножения.
    Результатом вычисления выражения всегда, если не оговорено противное, является r- значение. Тип результата арифметического выражения определяется типами операндов.
    Если операнды имеют разные типы, производится преобразование типов в соответствии с предопределенным набором правил. (Мы детально рассмотрим эти правила в разделе
    4.14.)
    Выражение может являться составным, то есть объединять в себе несколько подвыражений. Вот, например, выражение, проверяющее на неравенство нулю указатель и объект, на который он указывает (если он на что-то указывает)7: ptr != 0 && *ptr != 0
    Выражение состоит из трех подвыражений: проверку указателя ptr, разыменования ptr и проверку результата разыменования. Если ptr определен как int *ptr = &ival; то результатом разыменования будет 1024 и оба сравнения дадут истину. Результатом всего выражения также будет истина (оператор && обозначает логическое И).
    Если посмотреть на этот пример внимательно, можно заметить, что порядок выполнения операций очень важен. Скажем, если бы операция разыменования ptr производилась до его сравнения с 0, в случае нулевого значения ptr это скорее всего вызвало бы крах программы. В случае операции И порядок действий строго определен: сначала оценивается левый операнд, и если его значение равно false, правый операнд не вычисляется вовсе. Порядок выполнения операций определяется их приоритетами, не всегда очевидными, что вызывает у начинающих программистов на С и С++ множество ошибок. Приоритеты будут приведены в разделе 4.13, а пока мы расскажем обо всех операциях, определенных в С++, начиная с наиболее привычных.
    4.2.
    Арифметические операции
    Таблица 4.1. Арифметические операции
    7 Проверку на неравенство 0 можно опустить. Полностью эквивалентна приведенной и более употребима следующая запись: ptr
    && *ptr.
    int ival = 1024;

    С++ для начинающих
    139
    Символ операции
    Значение
    Использование
    *
    Умножение expr * expr
    /
    Деление expr / expr
    %
    Остаток от деления expr % expr
    +
    Сложение expr + expr
    -
    Вычитание expr – expr
    Деление целых чисел дает в результате целое число. Дробная часть результата, если она есть, отбрасывается: int iva12 = 21 / 7;
    И ival1, и ival2 в итоге получат значение 3.
    Операция остаток (%), называемая также делением по модулю, возвращает остаток от деления первого операнда на второй, но применяется только к операндам целого типа
    (char, short, int, long). Результат положителен, если оба операнда положительны. Если же один или оба операнда отрицательны, результат зависит от реализации, то есть машинно-зависим. Вот примеры правильного и неправильного использования деления по модулю: iva1 % dval; // ошибка: операнд типа double
    Иногда результат вычисления арифметического выражения может быть неправильным либо не определенным. В этих случаях говорят об арифметических исключениях (хотя они не вызывают возбуждения исключения в программе). Арифметические исключения могут иметь чисто математическую природу (скажем, деление на 0) или происходить от представления чисел в компьютере – как переполнение (когда значение превышает величину, которая может быть выражена объектом данного типа). Например, тип char содержит 8 бит и способен хранить значения от 0 до 255 либо от -128 до 127 в зависимости от того, знаковый он или беззнаковый. В следующем примере попытка присвоить объекту типа char значение 256 вызывает переполнение: int ivall = 21 / 6;
    3.14 % 3; // ошибка: операнд типа double
    21 % 6; // правильно: 3 21 % 7; // правильно: 0 21 % -5; // машинно-зависимо: -1 или 1 int iva1 = 1024; double dval = 3.14159; iva1 % 12; // правильно:

    С++ для начинающих
    140
    }
    Для представления числа 256 необходимы 9 бит. Переменная byte_value получает некоторое неопределенное (машинно-зависимое) значение. Допустим, на нашей рабочей станции SGI мы получили 0. Первая попытка напечатать это значение с помощью: cout << "byte_va1ue: " << byte_va1ue << endl; привела к результату: byte_value:
    После некоторого замешательства мы поняли, что значение 0 – это нулевой символ
    ASCII, который не имеет представления при печати. Чтобы напечатать не представление символа, а его значение, нам пришлось использовать весьма странно выглядящее выражение: static_cast(byte_value) которое называется явным приведением типа. Оно преобразует тип объекта или выражения в другой тип, явно заданный программистом. В нашем случае мы изменили byte_value на int. Теперь программа выдает более осмысленный результат: byte_value: 0
    На самом деле нужно было изменить не значение, соответствующее byte_value, а поведение операции вывода, которая действует по-разному для разных типов. Объекты типа char представляются ASCII-символами (а не кодами), в то время как для объектов типа int мы увидим содержащиеся в них значения. (Преобразования типов рассмотрены в разделе 4.14.)
    Это небольшое отступление от темы – обсуждение проблем преобразования типов – вызвано обнаруженной нами погрешностью в работе нашей программы и в каком-то смысле напоминает реальный процесс программирования, когда аномальное поведение программы заставляет на время забыть о том, ради достижения какой, собственно, цели она пишется, и сосредоточиться на несущественных, казалось бы, деталях. Такая мелочь, как недостаточно продуманный выбор типа данных, приводящий к переполнению, может стать причиной трудно обнаруживаемой ошибки: из соображений эффективности проверка на переполнение не производится во время выполнения программы.
    #include int main() { char byte_value = 32; int ival = 8;
    // переполнение памяти, отведенной под byte_value byte_value = ival * byte_value; cout << "byte_value: " <(byte_value) << endl;

    С++ для начинающих
    141
    Стандартная библиотека С++ имеет заголовочный файл limits, содержащий различную информацию о встроенных типах данных, в том числе и диапазоны значений для каждого типа. Заголовочные файлы climits и cfloat также содержат эту информацию.
    (Об использовании этих заголовочных файлов для того, чтобы избежать переполнения и потери значимости, см. главы 4 и 6 [PLAUGER92]).
    Арифметика вещественных чисел создает еще одну проблему, связанную с округлением.
    Вещественное число представляется фиксированным количеством разрядов (разным для разных типов – float, double и long double), и точность значения зависит от используемого типа данных. Но даже самый точный тип long double не может устранить ошибку округления. Вещественная величина в любом случае представляется с некоторой ограниченной точностью. (См. [SHAMPINE97] о проблемах округления вещественных чисел.)
    Упражнение 4.1
    В чем разница между приведенными выражениями с операцией деления? ivall / iva12;
    Упражнение 4.2
    Напишите выражение, определяющее, четным или нечетным является данное целое число.
    Упражнение 4.3
    Найдите заголовочные файлы limits, climits и cfloat и посмотрите, что они содержат.
    4.3.
    Операции сравнения и логические операции
    Таблица 4.2. Операции сравнения и логические операции
    Символ операции
    Значение
    Использование
    !
    Логическое НЕ
    !expr
    <
    Меньше expr1 < expr2
    <=
    Меньше или равно expr1 <= expr2
    >
    Больше expr1 > expr2
    >=
    Больше или равно expr1 >= expr2
    ==
    Равно expr1 == expr2
    !=
    Не равно expr1 != expr2
    &&
    Логическое И expr1 && expr2
    ||
    Логическое ИЛИ expr1 || expr2
    Примечание. Все операции в результате дают значение типа bool double dvall = 10.0, dva12 = 3.0; int ivall = 10, iva12 = 3; dvall / dva12;
    Примечание [O.A.1]: Как должны быть оформлены ссылки на книги, указанные в библиографии? Пришлите ваши пожелания.

    С++ для начинающих
    142
    Операции сравнения и логические операции в результате дают значение типа bool, то есть true или false. Если же такое выражение встречается в контексте, требующем целого значения, true преобразуется в 1, а false – в 0. Вот фрагмент кода, подсчитывающего количество элементов вектора, меньших некоторого заданного значения:
    }
    Мы просто прибавляем результат операции “меньше” к счетчику. (Пара += обозначает составной оператор присваивания, который складывает операнд, стоящий слева, и операнд, стоящий справа. То же самое можно записать более компактно: elem_count = elem_count + n
    . Мы рассмотрим такие операторы в разделе 4.4.)
    Логическое И (&&) возвращает истину только тогда, когда истинны оба операнда.
    Логическое ИЛИ (||) дает истину, если истинен хотя бы один из операндов.
    Гарантируется, что операнды вычисляются слева направо и вычисление заканчивается, как только результирующее значение становится известно. Что это значит? Пусть даны два выражения: expr1 || expr2
    Если в первом из них expr1 равно false, значение всего выражения тоже будет равным false вне зависимости от значения expr2, которое даже не будет вычисляться. Во втором выражении expr2 не оценивается, если expr1 равно true, поскольку значение всего выражения равно true вне зависимости от expr2.
    Подобный способ вычисления дает возможность удобной проверки нескольких выражений в одном операторе AND:
    { ... }
    Указатель с нулевым значением не указывает ни на какой объект, поэтому применение к нулевому указателю операции доступа к члену вызвало бы ошибку (ptr->value).
    Однако, если ptr равен 0, проверка на первом шаге прекращает дальнейшее вычисление подвыражений. Аналогично на втором и третьем шагах проверяется попадание величины vector::iterator iter = ivec.beg-in() ; while ( iter != ivec.end() ) {
    // эквивалентно: e1em_cnt = e1em_cnt + (*iter < some_va1ue)
    // значение true/false выражения *iter < some_va1ue
    // превращается в 1 или 0 e1em_cnt += *iter < some_va1ue;
    ++iter; expr1 && expr2 while ( ptr !=
    О && ptr->va1ue < upperBound && ptr->va1ue >= 0 && notFound( ia[ ptr->va1ue ] ))

    С++ для начинающих
    143
    ptr->value в нужный диапазон, и операция взятия индекса не применяется к массиву ia
    , если этот индекс неправилен.
    Операция логического НЕ дает true, если ее единственный оператор равен false, и наоборот. Например:
    }
    Подвыражение
    ! found дает true, если переменная found равна false. Это более компактная запись для found == false
    Аналогично if ( found ) эквивалентно более длинной записи if ( found == true )
    Использование операций сравнения достаточно очевидно. Нужно только иметь в виду, что, в отличие от И и ИЛИ, порядок вычисления операндов таких выражений не определен. Вот пример, где возможна подобная ошибка:
    // поменять местами элементы
    Программист предполагал, что левый операнд оценивается первым и сравниваться будут элементы ia[0] и ia[1]. Однако компилятор не гарантирует вычислений слева направо, и в таком случае элемент ia[0] может быть сравнен сам с собой. Гораздо лучше написать более понятный и машинно-независимый код:
    ++index; bool found = false;
    // пока элемент не найден
    // и ptr указывает на объект (не 0) while ( ! found && ptr ) { found = 1ookup( *ptr );
    ++ptr;
    //
    Внимание! Порядок вычислений не определен! if ( ia[ index++ ] < ia[ index ] ) if ( ia[ index ] < ia[ index+1 ] )
    // поменять местами элементы

    С++ для начинающих
    144
    Еще один пример возможной ошибки. Мы хотели убедиться, что все три величины ival, jval и kval различаются. Где мы промахнулись?
    // do something ...
    Значения 0, 1 и 0 дают в результате вычисления такого выражения true. Почему?
    Сначала проверяется ival != jval, а потом итог этой проверки (true/false – преобразованной к 1/0) сравнивается с kval. Мы должны были явно написать:
    // сделать что-то ...
    Упражнение 4.4
    Найдите неправильные или непереносимые выражения, поясните. Как их можно изменить? (Заметим, что типы объектов не играют роли в данных примерах.)
    (b) ival != jva1 < kva1 (d) iva1++ && ival
    Упражнение 4.5
    Язык С++ не диктует порядок вычисления операций сравнения для того, чтобы позволить компилятору делать это оптимальным образом. Как вы думаете, стоило бы в данном случае пожертвовать эффективностью, чтобы избежать ошибок, связанных с предположением о вычислении выражения слева направо?
    4.4.
    Операции присваивания
    Инициализация задает начальное значение переменной. Например: int *pi = 0;
    В результате операции присваивания объект получает новое значение, при этом старое пропадает: pi = &iva1;
    //
    Внимание! это не сравнение 3 переменных друг с другом if ( ival != jva1 != kva1 ) if ( ival != jva1 && ival != kva1 && jva1 != kva1 )
    (a) ptr->iva1 != 0
    (
    с) ptr != 0 && *ptr++
    (e) vec[ iva1++ ] <= vec[ ival ]; int ival = 1024; ival = 2048;

    С++ для начинающих
    145
    Иногда путают инициализацию и присваивание, так как они обозначаются одним и тем же знаком =. Объект инициализируется только один раз – при его определении. В то же время операция может быть применена к нему многократно.
    Что происходит, если тип объекта не совпадает с типом значения, которое ему хотят присвоить? Допустим, ival = 3.14159; // правильно?
    В таком случае компилятор пытается трансформировать тип объекта, стоящего справа, в тип объекта, стоящего слева. Если такое преобразование возможно, компилятор неявно изменяет тип, причем при потере точности обычно выдается предупреждение. В нашем случае вещественное значение 3.14159 преобразуется в целое значение 3, и это значение присваивается переменной ival.
    Если неявное приведение типов невозможно, компилятор сигнализирует об ошибке: pi = ival; // ошибка
    Неявная трансформация типа int в тип указатель на int невозможна. (Набор допустимых неявных преобразований типов мы обсудим в разделе 4.14.)
    Левый операнд операции присваивания должен быть l-значением. Очевидный пример неправильного присваивания:
    1024 = ival; // ошибка
    Возможно, имелось в виду следующее: value = ival; // правильно
    Однако недостаточно потребовать, чтобы операнд слева от знака присваивания был l- значением. Так, после определений int *pia = ia; выражение array_size = 512; // ошибка ошибочно, хотя array_size и является l-значением: объявление array_size константой не дает возможности изменить его значение. Аналогично ia = pia; // ошибка ia
    – тоже l-значение, но оно не может быть значением массива. int value = 1024; const int array_size = 8; int ia[ array_size ] = { 0, 1, 2, 2, 3, 5, 8, 13 };

    С++ для начинающих
    1   ...   10   11   12   13   14   15   16   17   ...   93


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