С.Б. Липпман, Ж. Лажойе - Язык программирования С++ Вводный курс (1114944), страница 26
Текст из файла (страница 26)
Но даже самый точный тип long double не можетустранить ошибку округления. Вещественная величина в любом случае представляется снекоторой ограниченной точностью. (См. [SHAMPINE97] о проблемах округлениявещественных чисел.)Упражнение 4.1double dvall = 10.0, dva12 = 3.0;int ivall = 10, iva12 = 3;dvall / dva12;В чем разница между приведенными выражениями с операцией деления?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Примечание [O.A.1]: Какдолжны быть оформлены ссылкина книги, указанные вбиблиографии? Пришлите вашипожелания.С++ для начинающихОперации сравнения и логические операции в результате дают значение типа bool, тоесть true или false. Если же такое выражение встречается в контексте, требующемцелого значения, true преобразуется в 1, а false – в 0. Вот фрагмент кода,подсчитывающего количество элементов вектора, меньших некоторого заданногоvector<int>::iterator iter = ivec.beg-in() ;while ( iter != ivec.end() ) {// эквивалентно: e1em_cnt = e1em_cnt + (*iter < some_va1ue)// значение true/false выражения *iter < some_va1ue// превращается в 1 или 0e1em_cnt += *iter < some_va1ue;++iter;значения:}Мы просто прибавляем результат операции “меньше” к счетчику.
(Пара += обозначаетсоставной оператор присваивания, который складывает операнд, стоящий слева, иоперанд, стоящий справа. То же самое можно записать более компактно: elem_count =elem_count + n. Мы рассмотрим такие операторы в разделе 4.4.)Логическое И (&&) возвращает истину только тогда, когда истинны оба операнда.Логическое ИЛИ (||) дает истину, если истинен хотя бы один из операндов.Гарантируется, что операнды вычисляются слева направо и вычисление заканчивается,как только результирующее значение становится известно. Что это значит? Пусть даныexpr1 && expr2два выражения:expr1 || expr2Если в первом из них expr1 равно false, значение всего выражения тоже будет равнымfalse вне зависимости от значения expr2, которое даже не будет вычисляться. Вовтором выражении expr2 не оценивается, если expr1 равно true, поскольку значениевсего выражения равно true вне зависимости от expr2.Подобный способ вычисления дает возможность удобной проверки несколькихwhile ( ptr != О &&ptr->va1ue < upperBound &&ptr->va1ue >= 0 &¬Found( ia[ ptr->va1ue ] ))выражений в одном операторе AND:{ ...
}Указатель с нулевым значением не указывает ни на какой объект, поэтому применение кнулевому указателю операции доступа к члену вызвало бы ошибку (ptr->value).Однако, если ptr равен 0, проверка на первом шаге прекращает дальнейшее вычислениеподвыражений. Аналогично на втором и третьем шагах проверяется попадание величины142С++ для начинающихptr->value в нужный диапазон, и операция взятия индекса не применяется к массивуia, если этот индекс неправилен.Операция логического НЕ дает true, если ее единственный оператор равен false, иbool found = false;// пока элемент не найден// и ptr указывает на объект (не 0)while ( ! found && ptr ) {found = 1ookup( *ptr );++ptr;наоборот.
Например:}Подвыражение! foundдает true, если переменная found равна false. Это более компактная запись дляfound == falseАналогичноif ( found )эквивалентно более длинной записиif ( found == true )Использование операций сравнения достаточно очевидно. Нужно только иметь в виду,что, в отличие от И и ИЛИ, порядок вычисления операндов таких выражений не// Внимание! Порядок вычислений не определен!if ( ia[ index++ ] < ia[ index ] )определен. Вот пример, где возможна подобная ошибка:// поменять местами элементыПрограммист предполагал, что левый операнд оценивается первым и сравниваться будутэлементы ia[0] и ia[1]. Однако компилятор не гарантирует вычислений слева направо,и в таком случае элемент ia[0] может быть сравнен сам с собой. Гораздо лучшеif ( ia[ index ] < ia[ index+1 ] )// поменять местами элементынаписать более понятный и машинно-независимый код:++index;143С++ для начинающихЕще один пример возможной ошибки.
Мы хотели убедиться, что все три величины ival,// Внимание! это не сравнение 3 переменных друг с другомif ( ival != jva1 != kva1 )jval и kval различаются. Где мы промахнулись?// do something ...Значения 0, 1 и 0 дают в результате вычисления такого выражения true. Почему?Сначала проверяется ival != jval, а потом итог этой проверки (true/false –if ( ival != jva1 && ival != kva1 && jva1 != kva1 )преобразованной к 1/0) сравнивается с kval.
Мы должны были явно написать:// сделать что-то ...Упражнение 4.4Найдите неправильные или непереносимые выражения, поясните. Как их можно(a) ptr->iva1 != 0(с) ptr != 0 && *ptr++(e) vec[ iva1++ ] <= vec[ ival ];изменить? (Заметим, что типы объектов не играют роли в данных примерах.)(b) ival != jva1 < kva1 (d) iva1++ && ivalУпражнение 4.5Язык С++ не диктует порядок вычисления операций сравнения для того, чтобы позволитькомпилятору делать это оптимальным образом.
Как вы думаете, стоило бы в данномслучае пожертвовать эффективностью, чтобы избежать ошибок, связанных спредположением о вычислении выражения слева направо?4.4. Операции присваиванияint ival = 1024;Инициализация задает начальное значение переменной. Например:int *pi = 0;В результате операции присваивания объект получает новое значение, при этом староеival = 2048;пропадает:pi = &iva1;144С++ для начинающихИногда путают инициализацию и присваивание, так как они обозначаются одним и темже знаком =.
Объект инициализируется только один раз – при его определении. В то жевремя операция может быть применена к нему многократно.Что происходит, если тип объекта не совпадает с типом значения, которое ему хотятприсвоить? Допустим,ival = 3.14159; // правильно?В таком случае компилятор пытается трансформировать тип объекта, стоящего справа, втип объекта, стоящего слева.
Если такое преобразование возможно, компилятор неявноизменяет тип, причем при потере точности обычно выдается предупреждение. В нашемслучае вещественное значение 3.14159 преобразуется в целое значение 3, и это значениеприсваивается переменной ival.Если неявное приведение типов невозможно, компилятор сигнализирует об ошибке:pi = ival; // ошибкаНеявная трансформация типа int в тип указатель на int невозможна.
(Набордопустимых неявных преобразований типов мы обсудим в разделе 4.14.)Левый операнд операции присваивания должен быть l-значением. Очевидный примернеправильного присваивания:1024 = ival; // ошибкаint value = 1024;Возможно, имелось в виду следующее:value = ival; // правильноОднако недостаточно потребовать, чтобы операнд слева от знака присваивания был l-const int array_size = 8;int ia[ array_size ] = { 0, 1, 2, 2, 3, 5, 8, 13 };значением. Так, после определенийint *pia = ia;выражениеarray_size = 512; // ошибкаошибочно, хотя array_size и является l-значением: объявление array_size константойне дает возможности изменить его значение.
Аналогичноia = pia; // ошибкаia – тоже l-значение, но оно не может быть значением массива.145С++ для начинающихНеверна и инструкцияpia + 2=1; // ошибкаХотя pia+2 дает адрес ia[2], присвоить ему значение нельзя. Если мы хотим изменитьэлемент ia[2], то нужно воспользоваться операцией разыменования. Корректной будетследующая запись:*(pia + 2) = 1; // правильноОперация присваивания имеет результат – значение, которое было присвоено самомулевому операнду. Например, результатом такой операцииival = 0;является 0, а результатival = 3.14159;равен 3. Тип результата – int в обоих случаях. Это свойство операции присваиванияextern char next_char();int main(){char ch = next_char();while ( ch != '\n' ) {// сделать что-то ...ch = next_char();}// ...можно использовать в подвыражениях.
Например, следующий цикл}extern char next_char();int main(){char ch;while (( ch = next_char() ) != '\n' ) {// сделать что-то ...}// ...может быть переписан так:}Заметим, что вокруг выражения присваивания необходимы скобки, поскольку приоритетэтой операции ниже, чем операции сравнения. Без скобок первым выполняетсясравнение:next_char() != '\n'146С++ для начинающихи его результат, true или false, присваивается переменной ch. (Приоритеты операцийбудут рассмотрены в разделе 4.13.)Аналогично несколько операций присваивания могут быть объединены, если этоint main (){int ival, jval;ival = jval = 0; // правильно: присваивание 0 обеим переменнымпозволяют типы операндов.
Например:// ...}Обеим переменным ival и jval присваивается значение 0. Следующий примернеправилен, потому что типы pval и ival различны, и неявное преобразование типовint main (){int ival; int *pval;ival = pval = 0; // ошибка: разные типыневозможно. Отметим, что 0 является допустимым значением для обеих переменных:// ...}Верен или нет приведенный ниже пример, мы сказать не можем, , поскольку определениеint main(){// ...int ival = jval = 0; // верно или нет?// ...jval в нем отсутствует:}Это правильно только в том случае, если переменная jval определена в программе ранееи имеет тип, приводимый к int.