C++
Шрифт:
Операции приращения особенно полезны для увеличения и уменьшения переменных в циклах. Например, оканчивающуюся нлем строку можно копировать так:
inline void cpy(char* p, const char* q) (* while (*p++ = *q++) ; *)
Напомню, что увеличение и уменьшение арифметических указателей, так же как сложение и вычитание указателей, осуществляется в терминах элементов вектора, на которые указывает указатель p++ приводит к тому, что p указывает на следующий элемент. Для указателя p типа T* по определению выполняется следующее:
long(p+1) == long(p)+sizeof(T);
3.2.4 Побитовые логические операции
Побитовые логические операции
amp; ! ^ ~
применяются к целым, то есть к объектам типа char, short, int, long и их unsigned аналогам, результаты тоже цлые.
Одно из стандартных применений побитовых логических опраций – реализация маленького множества (вектор битов). В этом случае каждый бит беззнакового целого представляет один член множества, а число членов ограничено числом битов. Бнарная операция amp; интерпретируется как пересечение, ! как объединение, а ^ как разность. Для наименования членов такого множества можно использовать перечисление. Вот маленький прмер, заимствованный из реализации (не пользовательского итерфейса) «stream.h»:
enum state_value (* _good=0, _eof=1, _fail=2, _bad=4 *); // хорошо, конец файла, ошибка, плохо
Определение _good не является необходимым. Я просто хтел, чтобы состояние, когда все в порядке, имело подходящее имя. Состояние потока можно установить заново следующим обрзом:
cout.state = _good;
Например, так можно проверить, не был ли испорчен поток или допущена операционная ошибка:
if (cout.state amp;(_bad!_fail)) // не good
Еще одни скобки необходимы, поскольку amp; имеет более всокий приоритет, чем !.
Функция, достигающая конца ввода, может сообщать об этом так:
cin.state != _eof;
Операция != используется потому, что поток уже может быть испорчен (то есть, state==_bad), поэтому
cin.state = _eof;
очистило бы этот признак. Различие двух потоков можно находить так:
state_value diff = cin.state^cout.state;
В случае типа stream_state (состояние потока) такая раность не очень нужна, но для других похожих типов она оказвается самой полезной. Например, при сравнении вектора бит, представляющего множество прерываний, которые обрабатываются, с другим, представляющим прерывания, ждущие обработки.
Следует заметить, что использование полей (#2.5.1) в действительности является сокращенной записью сдвига и маскрования для извлечения полей бит из слова. Это, конечно, моно сделать и с помощью побитовых логических операций, Например, извлечь средние 16 бит из 32-битового int можно следующим образом:
unsigned short middle(int a) (* return (a»»8) amp;0xffff; *)
Не путайте побитовые логические операции с логическими операциями:
amp; amp; !! !
Последние возвращают 0 или 1, и они главным образом ипользуются для записи проверки в операторах if, while или for (#3.3.1). Например, !0 (не ноль) есть значение 1, тогда как ~ 0 (дополнение нуля) есть набор битов все-единицы, который обычно является значением -1.
3.2.5 Преобразование типа
Бывает необходимо явно преобразовать значение одного тпа в значение другого. Явное преобразование типа дает значние одного типа для данного значения другого типа. Например:
float r = float(1);
перед присваиванием преобразует целое значение 1 к знчению с плавающей точкой 1.0. Результат преобразования типа не является lvalue, поэтому ему нельзя присваивать (если только тип не является ссылочным типом).
Есть два способа записи явного преобразования типа: трдиционная в C запись приведения к типу (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*) amp;some_object;
позволит работать посредством p с некоторым объектом some_object как с любым типом any_type.
Когда преобразование типа не необходимо, его следует ибегать. Программы, в которых используется много явных преоразований типов, труднее понимать, чем те, в которых это не делается. Однако такие программы легче понимать, чем программы, просто не использующие типы для представления понятий блее высокого уровня (например, программу, которая оперирует регистром устройства с помощью сдвига и маскирования, вместо того, чтобы определить подходящую struct и оперировать ею, см. #2.5.2). Кроме того, правильность явного преобразования типа часто критическим образом зависит от понимания програмистом того, каким образом объекты различных типов обрабатваются в языке, и очень часто от подробностей реализации. Например:
int i = 1; char* pc = «asdf»; int* pi = amp;i;
i = (int)pc; pc = (char*)i; // остерегайтесь! значение pc может изм//ниться // на некоторых машинах // sizeof(int)«sizeof(char*) pi = (int*)pc; pc = (char*)pi; // остерегайтесь! значение pc может изм// ниться // на некоторых машинах char* // представляется иначе, чем int*
На многих машинах ничего плохого не произойдет, но на других результаты будут катастрофическими. Этот код в лучшем случае непереносим. Обычно можно без риска предполагать, что указатели на различные структуры имеют одинаковое представлние. Кроме того, любой указатель можно (без явного преобразвания типа) присвоить void*, а void* можно явно преобразовать к указателю любого типа.
В С++ явное преобразование типа оказывается ненужным во многих случаях, когда C (и другие языки) требуют его. Во мнгих программах явного преобразования типа можно совсем избжать, а во многих других его применение можно локализовать в
небольшом числе подпрограмм.
3.2.6 Свободная память
Именованный объект является либо статическим, либо автоматическим (см. #2.1.3). Статический объект размещается во время запуска программы и существует в течение всего выполнния программы. Автоматический объект размещается каждый раз при входе в его блок и существует только до тех пор, пока из этого блока не вышли. Однако часто бывает полезно создать нвый объект, существующий до тех пор, пока он не станет больше не нужен. В частности, часто полезно создать объект, который можно использовать после возврата из функции, где он создаеся. Такие объекты создает операция new, а впоследствии унитожать их можно операцией delete. Про объекты, выделенные с помощью операции new, говорят, что они в свободной памяти. Такими объектами обычно являются вершины деревьев или элеметы связанных списков, являющиеся частью большей структуры данных, размер которой не может быть известен на стадии копиляции. Рассмотрим, как можно было бы написать компилятор в духе написанного настольного калькулятора. Функции синтаксческого анализа могут строить древовидное представление выржений, которое будет использоваться при генерации кода. Например: