|
Программирование >> Поддержка объектно-ориентированного программирования
или определить новое имя типа: 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* может быть явно преобразован обратно в указатель произвольного типа. В языке С++ явные преобразования типа оказывается излишними во многих случаях, когда в С (и других языках) они требуются. Во многих программах можно вообще обойтись без явных преобразований типа, а во многих других они могут быть локализованы в нескольких подпрограммах. 3.2.6 Свободная память Именованный объект является либо статическим, либо автоматическим (см.$$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()
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |