ООП. Пособие по ООП в С++. Создание сложных программных систем Объектно ориентированная технология
Скачать 2.1 Mb.
|
И istream и ostream наследуются классом fstream, который позволяет выполнять операции ввода и вывода. Функция seekp() перемещает ассоциированный с файлом указатель записи на offset записей или символов. Функция seekp() определена в ostream и наследована ostream. //изменение записи с заданным номером в двустороннем потоке void ChengeRecord( int pos, char *namefile) { fstream fdirect(namefile,ios::binary|ios::out| ios::in); record r; fdirect.seekg(pos* sizeof (r),ios::beg); fdirect.read(( char *) &r, sizeof r); fdirect.seekg(- sizeof (r),ios::cur); strcpy(r.info, "****" ); fdirect.write(( char *) &r, sizeof r); } 14.9.3 Пример применение прямого доступа для перемещения по файлу 1) Создать поток для прямого доступа к двоичному файлу, содержащему три записи с данными Шилдт Методики 2015 Страуструп Язык С++ 2015 Иванова Г.И. ООП 2012 1) Открыть поток для чтения и записи 2) Изменить фамилию Страуструп на Straustrup. Над найти запись с фамилией Страуструп. Она будет в процессе поиска прочитана в переменную Х, после ее чтения указатель за этой записью. 3) Изменить прочитанную запись в переменной Х. 4) Записать измененную запись на ее место в файле, для этого: • Переместить указатель на одну запись назад • Выполнить запись в файл значения переменной Х. 5) Показать содержание файла. Для этого надо установить указатель перед первой записью #include "stdafx.h" #include #include #include #include { string Fam; string Name; int year; }; void out_bin_file(fstream &fb) { book x; fb.read(( char *)&x, sizeof (book)); while (!fb.eof()) { cout< *)&x, sizeof (book)); } fb.close(); } //поиск записи в файле и изменения void cheng_bin_file(fstream &fbpr, string newfnam, string oldfnam) { book x; int i=0; do { fbpr.read(( char *)&x, sizeof (book)); if (x.Fam==oldfnam) { //заменитть фамилию в записи на новую x.Fam=newfnam; //смещение назад на одну запись (на старое место) fbpr.seekg( sizeof (book)*(i-1),ios::cur); //запись Х в файл fbpr.write(( char *)&x, sizeof (book)); return ; } } while (!fbpr.eof()); } int _tmain( int argc, _TCHAR* argv[]) { setlocale(LC_ALL, "rus" ); char fnameBin[30]= "B.dat" ; book x; fstream fbpr; fbpr.open(fnameBin,ios::out|ios::in|ios::binary ); if !fbpr) { cout<< "файл не открыт" ; return 1; } string oldfnam= "Страуструп" , newfnam= "Straustrup" ; cheng_bin_file(fbpr, newfnam, oldfnam); //установить указатель перед первой записью //чтобы вывести записи файла и не открывать его снова fbpr.seekg( sizeof (book)*0,ios::beg); out_bin_file(fbpr); fbpr.close(); return 0; } 15 Графический интерфейс С++/CLI 15.1 Введение в С++/CLI C++/CLI — язык для среды программирования Microsoft .NET. Он интегрирует С++ стандарта ISO с Объединённой Системой Типов (Unified Type System, UTS), рассматриваемой как часть общеязыковой инфраструктуры (Common Language Infrastructure, CLI). Он поддерживает и исходный уровень, и функциональную совместимость исполняемых файлов, скомпилированных с родного и управляемого C++. C++/CLI представляет собой дальнейшее развитие Managed C++. C++/CLI стандартизирован в ECMA как ECMA-372. При реализации обработчиков событий элементов формы может возникнуть необходимость писать код на языке C++/CLI, но новых конструкций будет немного, они легко ассоциируются со стандартом С++. Операторы ^ и gcnew языка C++ CLI - это местные аналоги *(указатель) и new из обычного C++, имеющие место в managed C++, он же C++/CLI. Последний представляет собой сочетание нормального C++ и C#. C++/CLI - это набор расширений языка C++, позволяющий создавать гибридные управляемые и низкоуровневые библиотеки DLL. С применением расширений C++/CLI вы сможете определять управляемые и неуправляемые классы и функции в пределах одного файла .cpp, использовать управляемые и низкоуровневые типы C и C++, как в обычном программном коде на C++, то есть простым подключением заголовочного файла и связыванием с библиотекой. Эти широчайшие возможности можно использовать для создания управляемых типов-оберток, пригодных для использования в любом языке .NET, а так же низкоуровневые классы-обертки и функции (доступные через файлы .dll, .lib и .h), пригодные для использования в программном коде на C/C++. “Что же такое MC++?”. С одной стороны, вы можете смело использовать в своих программах все привычные возможности C++, шаблоны и множественное наследование, перегрузку операторов и прямую работу с памятью. Вся разница лишь в том, что компилятор будет генерировать управляемый код (MSIL) вместо ассемблера. Но! Это касается только обычных типов C++. Если же у вас возникнет необходимость (а она обязательно возникнет) в использовании gc- и value-типов, то вам не придётся заботиться об удалении объектов, CLR будет сама производить начальную инициализацию переменных и проверять допустимость значений аргументов во время исполнения. Платой за это будет следование всем ограничениям управляемой среды. Таким образом, фактически мы имеем два разных языка в одном, которые можно легко смешивать. Единственная проблема – теперь нам придётся постоянно помнить, с каким из них в данный момент мы имеем дело. Что такое “управляемый” и “неуправляемый” код? С неуправляемым всё ясно – это обычный “родной” код Windows/Intel. С управляемыми объектами тоже понятно – CLR может их полностью контролировать. Не понятно только, как быть с обычными C++ программами, которые не используют управляемые объекты, но компилируются в MSIL код. 15.1.1 Управляемые типы, массивы и указатели CLR поддерживает автоматическую сборку мусора и обеспечивает безопасную передачу данных между различными частями системы. Для обеспечения этих возможностей среда CLR должна владеть полной информацией о типах данных. В результате получена возможность создавать и использовать такие типы наряду с обычными типами C++, не прибегая к ухищрениям наподобие библиотек типов для COM-объектов. Объявление управляемого типа в Managed Extensions for C++ (CLR), производится с помощью ключевых слов __gc или __value. __gc Идентификатор __gc применяется для объявления сложных типов, массивов и указателей, размещаемых в куче среды исполнения CLR. Сокращение gc, скорее всего, происходит от garbage collection. Рассмотрим пример: __gc class Foo { public: Foo(); void Fun(); }; void test() { Foo *p = new Foo; p->Fun(); } Ключевое слово __gc перед объявлением класса говорит компилятору, что наш класс является управляемым и подчиняется всем правилам среды CLR. В частности, нам не нужно вызывать деструктор для удаления объекта из памяти, эту работу за нас сделает CLR. Более того, если мы всё же вызовем оператор delete, то компилятор выдаст сообщение об ошибке, говорящее о том, что в нашем классе не определён деструктор. Определение деструктора может быть добавлено, и вызов оператора delete приведёт к его немедленному вызову, но, тем не менее, память, занятая объектом, освобождена не будет. Если же оператор delete не вызывается, то деструктор будет вызван CLR по своему усмотрению во время сборки мусора. Такое поведение деструкторов управляемых классов обусловлено тем, что компилятор фактически переименовывает их в метод Finalize, являющийся стандартным для среды CLR и вызываемый ей непосредственно перед удалением объекта из памяти. Здесь будет уместно заметить, что все управляемые типы CLR происходят от класса System::Object, который и содержит виртуальный метод Finalize. Компилятор добавляет это наследование автоматически, хотя вполне допустимо делать это явно. Ещё одной особенностью приведённого выше кода является то, что мы можем создать экземпляр класса только в управляемой куче, например, следующий код является некорректным и приведёт к ошибке компиляции: void test() { Foo p; // ошибка p.Fun(); } __gc arrays Так же, как и для объектов управляемых типов, память для управляемых массивов выделяется в куче CLR. Массивы могут содержать переменное число элементов и всегда инициализируются при создании. Для объявления управляемых массивов используется специальный синтаксис: int ar __gc[] = new int __gc[10]; ar[0] = 1; ar = new int __gc[20]; Создать массив объектов Foo можно следующим образом Foo *ar[] = new Foo*[10]; ar[0] = new Foo; При использовании расширения C++/CLI маршалинг выполняется вручную, благодаря чему разработчик имеет более полный контроль и более полное представление о накладных расходах. Маршалингом (marshalling) называется процесс создания моста между управляемым кодом (managed code) и неуправляемым кодом (unmanaged code) это подсистема, которая передает сообщения из managed-окружения в unmanaged-окружение и обратно. Еще одно преимущество расширения C++/CLI состоит в том, что оно позволяет имитировать интерфейс объединения запросов, даже если у вас нет доступа к исходным текстам вызывающего кода, многократно вызывая низкоуровневые методы без необходимости каждый раз пересекать границу между управляемым и неуправляемым кодом. В коде, представленном ниже, мы реализовали неуправляемый класс NativeEmployee и управляемый класс Employee, служащий оберткой для первого. Управляемый код будет обращаться только к управляемому классу. #include #include #include #include NativeEmployee(const wchar_t *employeeName, int age) : _employeeName(employeeName), _employeeAge(age) { } void DoWork(const wchar_t **tasks, int numTasks) { for (int i = 0; i < numTasks; i++) { wprintf(L"Пользователь %s работает в задаче %s\n", _employeeName.c_str(), tasks[i]); } } int GetAge() const { return _employeeAge; } const wchar_t *GetName() const { return _employeeName.c_str(); } private: std::wstring _employeeName; int _employeeAge; }; #pragma managed namespace EmployeeLib { public ref class Employee { { public: Employee(String ^employeeName, int age) { // Вариант 1: // IntPtr pEmployeeName = Marshal::StringToHGlobalUni(employeeName); // m_pEmployee = new NativeEmployee( // reinterpret_cast // Marshal::FreeHGlobal(pEmployeeName); // Вариант 2 (прямой указатель на закрепленную // управляемую строку, самый быстрый): pin_ptr PtrToStringChars(employeeName); _employee = new NativeEmployee(ppEmployeeName, age); } Employee() { delete _employee; _employee = nullptr; } int GetAge() { return _employee->GetAge(); } String ^GetName() { // Вариант 1: // return Marshal::PtrToStringUni( // (IntPtr)(void *) _employee->GetName()); // Вариант 2: return msclr::interop::marshal_as >GetName()); // Вариант 3 (самый быстрый): return gcnew String(_employee->GetName()); } void DoWork(array // marshal_context - это управляемый класс, размещаемый // (в динамической памяти сборщика мусора) с использованием // семантики, напоминающей стек. Его деструктор IDisposable::Dispose() // будет вызван после выхода из области видимости этой функции msclr::interop::marshal_context ctx; const wchar_t **pTasks = new const wchar_t*[tasks->Length]; for (int i = 0; i < tasks->Length; i++) { String ^t = tasks[i]; pTasks[i] = ctx.marshal_as } m_pEmployee->DoWork(pTasks, tasks->Length); // деструктор контекста освободит неуправляемую // память, выделенную методом marshal_as delete[] pTasks; } private: NativeEmployee *_employee; }; } В этом коде конструктор Employee демонстрирует два способа преобразования управляемых строк в неуправляемый: первый основан на выделении памяти с помощью GlobalAlloc, которую необходимо будет освободить явно, а второй временно закрепляет управляемую строку в памяти и возвращает прямой указатель. Второй способ выполняется быстрее, но его можно использовать, только когда неуправляемый код принимает строки, завершающиеся нулевым символом, в кодировке UTF-16, и он не записывает ничего в память по указателю. Кроме того, закрепление управляемых объектов на длительное время может привести к фрагментации памяти, поэтому, если перечисленные требования не удовлетворяются, вам придется прибегнуть к копированию строк. Метод GetName() класса Employee демонстрирует три способа преобразования неуправляемых строк в управляемые: первый основан на использовании класса System.Runtime.InteropServices.Marshal, второй использует функцию marshal_as, объявленную в заголовочном файле msclr/marshal.h, и, наконец, третий использует конструктор класса System.String, являющийся наиболее быстрым. Метод DoWork() класса Employee принимает управляемый массив (или управляемые строки) и преобразует его в массив указателей типа wchar_t, указывающих на строки; фактически это массив строк в стиле языка C. Преобразование управляемых строк в неуправляемые выполняется с применением метода marshal_as класса объекта marshal_context. В отличие от глобальной функции marshal_as, объект marshal_context используется для преобразований, требующих освобождения занимаемых при этом ресурсов. Обычно для преобразования управляемых данных в неуправляемые метод marshal_as выделяет неуправляемую память, которую следует освободить после выполнения операции. Объект marshal_context содержит связанный список операций освобождения ресурсов, которые выполняются в момент уничтожения объекта. Подводя итоги можно сказать, что расширение C++/CLI обеспечивает полный контроль над маршалингом и не требует дублирования объявлений функций, что чревато ошибками, особенно когда часто приходится изменять сигнатуры неуправляемых функций. 15.1.2 Тип String^ Для использования методов marshal_as() и marshal_context::marshal_as() нужно подключить один или несколько заголовочных файлов из папки msclr: marshal.h, marshal_windows.h, marshal_cppstd.h или marshal_atl.h. Определить, какой метод использовать и какой заголовочный файл подключать, можно из таблицы: Из типа В тип Метод Заголовочный файл Строки в языке C System::String^ const char* marshal_context::marshal_as( ) marshal.h const char* System::String^ marshal_as() marshal.h char* System::String^ marshal_as() marshal.h Из типа В тип Метод Заголовочный файл System::String^ const wchar_t* marshal_context::marshal_as( ) marshal.h const wchar_t* System::String^ marshal_as() marshal.h wchar_t* System::String^ marshal_as() marshal.h WinAPI и COM System::IntPtr HANDLE marshal_as() marshal_windows. h HANDLE System::IntPtr marshal_as() marshal_windows. h System::String^ BSTR marshal_context::marshal_as( ) marshal_windows. h BSTR System::String^ marshal_as() marshal.h System::String^ bstr_t marshal_as() marshal_windows. h bstr_t System::String^ marshal_as() marshal_windows. h Стандартная библиотека C++ System::String^ std::string marshal_as() marshal_cppstd.h std::string System::String^ marshal_as() marshal_cppstd.h System::String^ std::wstring marshal_as() marshal_cppstd.h std::wstring System::String^ marshal_as() marshal_cppstd.h ATL System::String^ CStringT CStringT System::String^ marshal_as() marshal_atl.h System::String^ CStringT CStringT System::String^ marshal_as() marshal_atl.h System::String^ CComBSTR marshal_as() marshal_atl.h CComBSTR System::String^ marshal_as() marshal_atl.h Другие преобразования, кроме указанных в таблице, не поддерживаются. 15.1.3 Указатели С++ и ссылки CLI В Visual C++ (CLR – Common Language Runtime) поддерживаются три типа указателей: • управляемые указатели (managed pointers); • неуправляемые указатели (unmanaged pointers); • неуправляемые указатели на функции (unmanaged function pointers). Отличие между управляемыми и неуправляемыми указателями состоит в следующем: в отличие от управляемого указателя неуправляемому указателю можно присвоить любой адрес памяти, даже той, которая находится за пределами исполнительной среды. В этом случае память явяется нерегулируемой. В случае регулируемой памяти управляемому указателю присвоить адрес за пределами исполнительной среды не удастся. Управляемые указатели – это указатели ссылочного типа. Эти указатели передаются для аргументов, методов, которые передаются по ссылке. Эти указатели являются совместимыми со спецификацией Common Language Runtime (CLR). Управляемые указатели являются ссылками на объекты. Эти объекты размещаются в общей управляемой памяти, которая выделяется для программы в момент ее выполнения. В таких указателях вместо символа ‘*’ применяется символ ‘^’. Для выделения В управляемых указателях утилита gcnew формирует экземпляр некоторого объекта. Утилита gcnew выделяет память для экземпляра объекта и возвращает ссылку на этот объект. Описание этого объекта должно начинаться с ключевого слова ref. Ключевое слово ref дает информацию о том, что описание есть ссылочного типа. Общая форма использования ключевого слова ref имеет вид: ref описание; После такого описания память под объект выделяется с помощью утилиты gcnew. Пример использования управляемого указателя на функцию в среде C++/CLI Использование указателя на функцию в среде C++/CLI для приложений, созданных по шаблону Windows Forms. В этом случае нужно использовать так называемые делегаты. Делегаты поддерживаются в .NET Framework. Пусть даны 2 функции, которые имеют одинаковое количество параметров и возвращают одинаковое значение: // функция, которая возводит x в степень y public: double Power(int x, int y) { double res; res = System::Math::Pow((double)x, (double)y); return res; } // функция, которая делит x на y public: double Div(int x, int y) { double res; res = (double)x / (double)y; return res; } Чтобы использовать делегат нужно сначала в некотором месте класса формы (или другого класса) дать следующее описание: // описание делегата DelFun, который получает два параметра типа int // и возвращает значение типа double public: delegate double DelFun(int , int); После этого, в некотором методе (например, обработчике события клика на кнопке) можно использовать этот указатель следующим образом: double result; DelFun ^ PFun = gcnew DelFun(this, &Lab1::Form1::Power); result = PFun(3,2); // result = 9 PFun = gcnew DelFun(this, &Lab1::Form1::Div); result = PFun(3,2); // result = 1.5 В вышеприведенном примере в строках &Lab1::Form1::Power &Lab1::Form1::Div идентификаторы имеют следующее назначение: Lab1 – название проекта; Form1 – название главной формы приложения (имя переменной); Power – название функции. |