Поразрядные логические операции




Поразрядные логические операции & | ^ ~ >> << применяются к целым, то есть к объектам типа char, short, int, long ик их беззнаковым аналогам. Результат операции также будет целым. Чаще всего поразрядные логические операции используются дляработы с небольшим по величине множеством данных (массивом разрядов).В этом случае каждый разряд беззнакового целого представляет одинэлемент множества, и число элементов определяется количеством разрядов.Бинарная операция & интерпретируется как пересечение множеств,операция | как объединение, а операция ^ как разность множеств.С помощью перечисления можно задать имена элементам множества.Ниже приведен пример, заимствованный из <iostream.h>: class ios { public: enum io_state { goodbit=0, eofbit=1, failbit=2, badbit=4 }; // ... }; Состояние потока можно установить следующим присваиванием: cout.state = ios::goodbit; Уточнение именем ios необходимо, потому что определение io_state находитсяв классе ios, а также чтобы не возникло коллизий, если пользователь заведет своиимена наподобие goodbit. Проверку на корректность потока и успешное окончание операции можнозадать так: if (cout.state&(ios::badbit|ios::failbit)) // ошибка в потоке Еще одни скобки необходимы потому, что операция & имеет более высокийприоритет, чем операция "|". Функция, обнаружившая конец входного потока, может сообщать об этом так: cin.state |= ios::eofbit; Операция |= используется потому, что в потоке уже могла быть ошибка(т.е. state==ios::badbit), и присваивание cin.state =ios::eofbit; могло бы затереть ее признак. Установить отличия в состоянии двухпотоков можно следующим способом: ios::io_state diff = cin.state^cout.state; Для таких типов, как io_state, нахождение различий не слишком полезнаяоперация, но для других сходных типов она может оказаться весьмаполезной. Например, полезно сравнение двух разрядных массива, один изкоторых представляет набор всех возможных обрабатываемых прерываний,а другой - набор прерываний, ожидающих обработки. Отметим, что использование полей ($$R.9.6) может служить удобными более лаконичным способом работы с частями слова, чем сдвиги имаскирование. С частями слова можно работать и с помощью поразрядныхлогических операций. Например, можно выделить средние 16 разрядовиз средины 32-разрядного целого: unsigned short middle(int a) { return (a>>8)&0xffff; } Только не путайте поразрядные логические операции с просто логическимиоперациями: && || ! Результатом последних может быть 0 или 1, и они в основномиспользуются в условных выражениях операторов if, while или for($$3.3.1). Например, !0 (не нуль) имеет значение 1, тогда как ~0(дополнение нуля) представляет собой набор разрядов "все единицы",который обычно является значением -1 в дополнительном коде.

Преобразование типа

Иногда бывает необходимо явно преобразовать значение одного типа взначение другого. Результатом явного преобразования будетзначение указанного типа, полученное из значения другого типа.Например: float r = float(1); Здесь перед присваиванием целое значение 1 преобразуется в значениес плавающей точкой 1.0f. Результат преобразования типа не являетсяадресом, поэтому ему присваивать нельзя (если только тип не являетсяссылкой). Существуют два вида записи явного преобразования типа:традиционная запись, как операция приведения в С, например, (double)aи функциональная запись, например, double(a). Функциональную записьнельзя использовать для типов, которые не имеют простого имени.Например, чтобы преобразовать значение в тип указателя, надо илииспользовать приведение char* p = (char*)0777; или определить новое имя типа: typedef char* Pchar; char* p = Pchar(0777); По мнению автора, функциональная запись в нетривиальных случаяхпредпочтительнее. Рассмотрим два эквивалентных примера: Pname n2 = Pbase(n1->tp)->b_name; // функциональная запись Pname n3 = ((Pbase)n2->tp)->b_name; // запись с приведением Поскольку операция -> имеет больший приоритет, чем операция приведения,последнее выражение выполняется так: ((Pbase)(n2->tp))->b_name Используя явное преобразование в тип указателя можно выдать данный объектза объект произвольного типа. Например, присваивание any_type* p = (any_type*)&some_object; позволит обращаться к некоторому объекту (some_object) через указательp как к объекту произвольного типа (any_type). Тем не менее, еслиsome_object в действительности имеет тип не any_type, могут получитьсястранные и нежелательные результаты. Если преобразование типа не является необходимым, его вообще следуетизбегать. Программы, в которых есть такие преобразования, обычнотруднее понимать, чем программы, их не имеющие. В то же времяпрограммы с явно заданными преобразованиями типа понятнее,чем программы, которые обходятся без таких преобразований, потому чтоне вводят типов для представления понятий более высокого уровня.Так, например, поступают программы, управляющие регистром устройства спомощью сдвига и маскирования целых, вместо того, чтобы определитьподходящую структуру (struct) и работать непосредственно с ней(см. $$2.6.1). Корректность явного преобразования типа частосущественно зависит от того, насколько программист понимает, как языкработает с объектами различных типов, и какова специфика данной реализацииязыка. Приведем пример: int i = 1; char* pc = "asdf"; int* pi = &i; i = (int)pc; pc = (char*)i; // осторожно: значение pc может измениться. // На некоторых машинах sizeof(int) // меньше, чем sizeof(char*) pi = (int*)pc; pc = (char*)pi; // осторожно: pc может измениться // На некоторых машинах char* имеет не такое // представление, как int* Для многих машин эти присваивания ничем не грозят, но для некоторыхрезультат может быть плачевным. В лучшем случае подобная программабудет переносимой. Обычно без особого риска можно предположить,что указатели на различные структуры имеют одинаковое представление.Далее, произвольный указатель можно присвоить (без явного преобразованиятипа) указателю типа void*, а void* может быть явно преобразованобратно в указатель произвольного типа. В языке С++ явные преобразования типа оказывается излишними во многихслучаях, когда в С (и других языках) они требуются. Во многихпрограммах можно вообще обойтись без явных преобразований типа, а вомногих других они могут быть локализованы в нескольких подпрограммах.

Свободная память

Именованный объект является либо статическим, либо автоматическим(см.$$2.1.3). Статический объект размещается в памяти в момент запускапрограммы и существует там до ее завершения. Автоматический объектразмещается в памяти всякий раз, когда управление попадает в блок,содержащий определение объекта, и существует только до тех пор, покауправление остается в этом блоке. Тем не менее, часто бывает удобносоздать новый объект, который существует до тех пор, пока онне станет ненужным. В частности, бывает удобно создать объект, которыйможно использовать после возврата из функции, где он был создан.Подобные объекты создает операция new, а операция delete используетсядля их уничтожения в дальнейшем. Про объекты, созданные операцией new,говорят, что они размещаются в свободной памяти. Примерами такихобъектов являются узлы деревьев или элементы списка, которые входятв структуры данных, размер которых на этапе трансляции неизвестен.Давайте рассмотрим в качестве примера набросок транслятора, которыйстроится аналогично программе калькулятора. Функции синтаксическогоанализа создают из представлений выражений дерево, которое будетв дальнейшем использоваться для генерации кода. Например: struct enode { token_value oper; enode* left; enode* right; }; enode* expr() { enode* left = term(); for(;;) switch(curr_tok) { case PLUS: case MINUS: get_token(); enode* n = new enode; n->oper = curr_tok; n->left = left; n->right = term(); left = n; break; default: return left; } } Генератор кода может использовать дерево выражений, например так: void generate(enode* n) { switch (n->oper) { case PLUS: // соответствующая генерация delete n; } } Объект, созданный с помощью операции new, существует, до тех пор,пока он не будет явно уничтожен операцией delete. После этогопамять, которую он занимал, вновь может использоваться new. Обычно нетникакого "сборщика мусора", ищущего объекты, на которые никтоне ссылается, и предоставляющего занимаемую ими память операции new дляповторного использования. Операндом delete может бытьтолько указатель, который возвращает операция new, или нуль.Применение delete к нулю не приводит ни к каким действиям. Операция new может также создавать массивы объектов, например: char* save_string(const char* p) { char* s = new char[strlen(p)+1]; strcpy(s,p); return s; } Отметим, что для перераспределения памяти, отведенной операцией new,операция delete должна уметь определять размер размещенного объекта.Например: int main(int argc, char* argv[]) { if (argc < 2) exit(1); char* p = save_string(arg[1]); delete[] p; } Чтобы добиться этого, приходится под объект, размещаемый стандартнойоперацией new, отводить немного больше памяти, чем под статический(обычно, больше на одно слово). Простой оператор delete уничтожаетотдельные объекты, а операция delete[] используется для уничтожениямассивов. Операции со свободной памятью реализуются функциями ($$R.5.3.3-4): void* operator new(size_t); void operator delete(void*); Здесь size_t - беззнаковый целочисленный тип, определенный в <stddef.h>. Стандартная реализация функции operator new() не инициализируетпредоставляемую память. Что случится, когда операция new не сможет больше найти свободнойпамяти для размещения? Поскольку даже виртуальная память небесконечна,такое время от времени происходит. Так, запрос вида: char* p = new char [100000000]; обычно не проходит нормально. Когда операция new не может выполнитьзапрос, она вызывает функцию, которая была задана как параметрпри обращении к функции set_new_handler() из <new.h>. Например,в следующей программе: #include <iostream.h> #include <new.h> #include <stdlib.h> void out_of_store() { cerr << "operator new failed: out of store\n"; exit(1); } int main() { set_new_handler(&out_of_store); char* p = new char[100000000]; cout << "done, p = " << long(p) << '\n'; } скорее всего, будет напечатано не "done", а сообщение: operator new failed: out of store // операция new не прошла: нет памяти С помощью функции new_handler можно сделать нечто более сложное,чем просто завершить программу. Если известен алгоритм операций new иdelete (например, потому, что пользователь определил свои функцииoperator new и operator delete), то обработчик new_handler можетпопытаться найти свободную память для new. Другими словами,пользователь может написать свой "сборщик мусора", тем самым сделаввызов операции delete необязательным. Однако такая задача,безусловно, не под силу новичку. По традиции операция new просто возвращает указатель 0, если неудалось найти достаточно свободной памяти. Реакция же на этоnew_handler не была установлена. Например, следующая программа: #include <stream.h> main() { char* p = new char[100000000]; cout << "done, p = " << long(p) << '\n'; } выдаст done, p = 0 Память не выделена, и вам сделано предупреждение! Отметим, что, задав реакцию на такую ситуацию в функции new_handler, пользователь берет на себя проверку: исчерпана ли свободная память. Она должна выполняться при каждом обращении в программе к new (если только пользователь не определил собственные функции для размещения объектов пользовательских типов; см.$$R.5.5.6).

Сводка операторов

Полное и последовательное описание операторов С++ содержится в $$R.6. Советуем ознакомиться с этим разделом. Здесь же дается сводка операторов и несколько примеров. ------------------------------------------------------------------ Синтаксис операторов------------------------------------------------------------------ оператор: описание { список-операторов opt } выражение opt ; if ( выражение ) оператор if ( выражение ) оператор else оператор switch ( выражение ) оператор while ( выражение ) оператор do оператор while ( выражение ) for (начальный-оператор-for выражение opt; выражение opt) оператор case выражение-константа : оператор default : оператор break ; continue ; return выражение opt ; goto идентификатор ; идентификатор : оператор список-операторов: оператор список-операторов оператор начальный-оператор-for: описание выражение opt ;---------------------------------------------------------------------- Обратите внимание, что описание является оператором, но нет операторовприсваивания или вызова функции (они относятся к выражениям).

Выбирающие операторы

Значение можно проверить с помощью операторов if или switch: if ( выражение ) оператор if ( выражение ) оператор else оператор switch ( выражение ) оператор В языке С++ среди основных типов нет отдельного булевского (тип со значениями истина, ложь). Все операции отношений: == != < > <= >= дают в результате целое 1, если отношение выполняется, и 0 в противном случае. Обычно определяют константы TRUE как 1 и FALSE как 0. В операторе if, если выражение имеет ненулевое значение, выполняется первый оператор, а иначе выполняется второй (если он указан). Таким образом, в качестве условия допускается любое выражение типа целое или указатель. Пусть a целое, тогда if (a) // ... эквивалентно if (a != 0) ... Логические операции && || ! обычно используются в условиях. В операциях && и || второй операнд не вычисляется, если результат определяется значением первого операнда. Например, в выражении if (p && l<p->count) // ... сначала проверяется значение p, и только если оно не равно нулю, то проверяется отношение l<p->count. Некоторые простые операторы if удобно заменять выражениями условия. Например, вместо оператора if (a <= b) max = b; else max = a; лучше использовать выражение max = (a<=b) ? b : a; Условие в выражении условия не обязательно окружать скобками, но если их использовать, то выражение становится понятнее. Простой переключатель (switch) можно записать с помощью серии операторов if. Например, switch (val) { case 1: f(); break; case 2: g(); break; default: h(); break; } можно эквивалентно задать так: if (val == 1) f(); else if (val == 2) g(); else h(); Смысл обеих конструкций совпадает, но все же первая предпочтительнее,поскольку в ней нагляднее показана суть операции: проверка насовпадение значения val со значением из множества констант. Поэтому внетривиальных случаях запись, использующая переключатель, понятнее. Нужно позаботиться о каком-то завершении оператора, указанногов варианте переключателя, если только вы не хотите, чтобы сталивыполняться операторы из следующего варианта. Например,переключатель switch (val) { // возможна ошибка case 1: cout << "case 1\n"; case 2: cout << "case 2\n"; default: cout << "default: case not found\n"; } при val==1 напечатает к большому удивлению непосвященных: case 1 case 2 default: case not found Имеет смысл отметить в комментариях те редкие случаи, когда стандартный переход на следующий вариант оставлен намеренно. Тогда этот переход во всех остальных случаях можно смело считать ошибкой. Для завершения оператора в варианте чаще всего используется break, но иногда используются return и даже goto. Приведем пример: switch (val) { // возможна ошибка case 0: cout << "case 0\n"; case1: case 1: cout << "case 1\n"; return; case 2: cout << "case 2\n"; goto case1; default: cout << "default: case not found\n"; return; } Здесь при значении val равном 2 мы получим: case 2 case 1 Отметим, что метку варианта нельзя использовать в операторе goto: goto case 2; // синтаксическая ошибка

Оператор goto





©2015-2018 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Нарушение авторских прав и Нарушение персональных данных

Обратная связь

ТОП 5 активных страниц!