Программирование на управляемом C++

В каждой новой версии Visual C++ компания Microsoft расширяет возможности языка во многих направлениях. Visual C++.NET не является исключением, поддерживая множество новых возможностей, для использования которых введены новые ключевые слова и атрибуты. В частности, появилась поддержка разработки кода на управляемом C++ для платформы .NET. В этой главе представлены несколько примеров, которые помогут читателю познакомиться с основными классами .NET Framework и приступить к самостоятельному написанию кода на управляемом C++. На примере использования класса Console (Консоль) продемонстрированы стандартные ввод и вывод, а кроме того, рассмотрены необычайно полезные классы String (Строка) и Array (Массив). Далее представлена программа управления системой бронирования гостиничных номеров, к которой мы еще не раз вернемся в следующих главах. Затем рассмотрены важные аспекты программирования на управляемом C++ для создания кода под платформу .NET: использование управляемых, неуправляемых, значимых (value), а также абстрактных типов, интерфейсы, упаковка и распаковка, делегаты, события, свойства и управляемые обработчики исключений. В заключение рассмотрены атрибуты C++ в контексте создания проектов ATL СОМ.

Место C++ в мире .NET

Одним из достоинств платформы .NET является то, что для разработки приложений, компонентов и сервисов на основе .NET можно использовать любой из широкого круга языков. Можно применять C++ с расширением управляемости, С# и VB.NET, созданные Microsoft, а также еще многие языки, разработанные другими компаниями. Но главное даже не то, что с помощью всех этих языков можно создавать приложения на основе .NET, a то, что во всех вопросах, относящихся к инициализации объектов, вызову методов, наследованию, обработке событий и даже обработке исключений, работа приложения не будет зависеть от языков реализации его составляющих. Это стало возможным благодаря тому, что языки .NET компилируются не на родной язык, а на общий промежуточный язык Intermediate Language (IL).
Как уже говорилось в предыду,щих главах, код, выполняющийся под управлением общеязыковой среды выполнения CLR (Common Language Runtime), называется управляемым кодом. Управляемый код отличается от обычного тем, что он компилируется не на родной набор инструкций ЦПУ, а в инструкции промежуточного языка IL, определенного платформой .NET. Промежуточный язык IL подобен обычному набору инструкций ЦПУ, отличаясь от последнего тем, что он изначально разрабатывался с поддержкой объектно-ориентированных и компонентно-ориентированных общих для языков черт, таких, как классы, объекты, методы, события и исключения. Благодаря тому, что исходный код, написанный на языках, поддерживающих .NET, компилируется в инструкции промежуточного языка IL, все эти языки являются полностью совместимыми.
Программы состоят из кода и данных, и общеязыковая среда выполнения CLR обеспечивает поддержку и управляемым данным, и управляемому коду. Как было сказано в предыдущих главах, управляемые данные размещаются в управляемой динамически распределяемой области памяти (куче), которая имеет весьма ценную особенность — автоматическую сборку мусора. Если при создании программ на обычном C++ программист должен сам создавать средства управления динамически распределяемой областью памяти, то общеязыковая среда выполнения CLR реализует этот процесс, отслеживая ссылки на объекты и автоматически освобождая ресурсы, занятые объектами, которые стали недоступны программе.
Используя C++ с расширениями управляемости, можно создавать управляемые код и данные. Однако в настоящее время C++ является единственным из всех языков .NET, с помощью которого можно создавать также и неуправляемые код и данные. Фактически, управляемые и неуправляемые код и данные на C++ могут быть определены в одном и том же исходном файле, и в некоторой степени эти два мира могут взаимодействовать. Хотя использование управляемых кода и данных имеет много преимуществ, оно может привести к снижению производительности и потере гибкости. Поэтому во многих случаях C++ оказывается лучшим выбором для создания программ. Другой причиной выбора из всех языков .NET именно C++ может быть желание совершенствовать ваше знание C++ и существующие наработки на этом языке.

Использование расширений управляемого C++


При разработке управляемого кода на Visual C++ используются несколько новых ключевых слов, а расширение компилятора C++, позволяющее создавать приложения для .NET, вызывается с помощью параметра /CLR (Компиляция для выполнения в общеязыковой среде). Этот параметр указывает компилятору, что в конечном файле следует применять набор инструкций промежуточного языка IL, а не обычный набор инструкций процессора. Новые ключевые слова используются при создании управляемого кода и не поддерживаются при создании обычного неуправляемого кода. Хотя наличие или отсутствие параметра /CLR (Компиляция для выполнения в общеязыковой среде) полностью определяет, будет ли компилятор генерировать управляемый (на промежуточном языке IL) или неуправляемый код, можно задавать режим компиляции для отдельных частей программы. Это осуществляется с помощью прагм #pragma:
#pragma managed
// Последующий код компилируется как управляемый
ttpragma unmanaged
// Последующий код компилируется как неуправляемый
Если задан параметр компилятора /CLR (Компиляция для выполнения в общеязыковой среде), то при отсутствии директив #pragma исходный код по умолчанию компилируется как управляемый. При отсутствии параметра /CLR (Компиляция для выполнения в общеязыковой среде) прагмы #pragma компилятором игнорируются, а код компилируется как неуправляемый.
Для использования возможностей расширения управляемости в исходный файл следует вставить директиву fusing с указанием сборки (assembly) mscorlib.dll, содержащей необходимую для работы управляемого кода информацию о типах. Такие сборки являются расширением для платформы .NET и обычно состоят из файлов DLL (или ЕХЕ). Кроме того, почти всегда определяется, что будет использовано пространство имен System (Системное пространство имен); это, однако, не обязательно для применения управляемого кода. Концепция пространств имен в C++ прямо копирует концепцию пространств имен многоязычной платформы .NET, представляющей собой иерархию имен. Эти два аспекта разработки кода для .NET обусловливают необходимость включения в начало исходного файла следующих двух строк:
fusing <mscorlib.dll>
// Требуется для управляемого кода на C++
using namespace System;
// используется пространство имен Система
// Не требуется, но обычно используется
Директива препроцессора fusing похожа на директиву #import в прежних версиях Visual C++ тем, что делает доступной для компилятора информацию о типах. В случае директивы #import информация о типах содержалась в библиотеках типов, обычно являвшихся файлами TLB, DLL, OCX или ЕХЕ. В случае директивы #using информация о типах представлена в форме метаданных, содержащихся в сборке .NET. Сборка mscorlib.dll содержит информацию о типах, необходимую всем приложениям .NET, включая информацию о базовом классе, являющемся предком всех управляемых классов, — классе System: :0bject (Система::Объект). Заметим, что в такой записи System (Системное пространство имен) обозначает пространство имен, a Object (Объект) — имя корневого класса иерархии управляемых типов.

Ваша первая программа на управляемом C++.NET


Хотя вы, почти наверняка, хорошо знакомы с C++, мы начнем с рассмотрения очень простого, но традиционного примера— программы HelloWorld (Привет, мир). В этом разделе мы расскажем, как написать, скомпилировать и запустить эту и другие программы.

Программа HelloWorld (Привет, мир)


Чуть ниже приведен пример кода из очень простой управляемой программы, которая выводит на консоль одну-единственную строку. Вы можете открыть сопровождающее решение [Как и для всех других примеров в данной книге, реализация программы HelloWorld доступна читателю в готовом виде. Исходные файлы этого проета находятся в папке C:\OI\NetCpp\Chap3\HelloWorld. Для того чтобы открыть его в Visual Studio, дважды щелкните на файле HelloWorld.sIn в Проводнике.] или создать свой проект и ввести текст программы самостоятельно. Для того чтобы это сделать, необходимо создать пустой проект HelloWorld (Привет, мир), добавить исходный код, а затем скомпилировать и запустить проект.
Как создать консольное приложение на управляемом C++
Создайте пустой проект консольного приложения Managed C++, называющийся HelloWorld (Привет, мир):
1. Откройте Visual Studio.NET. Выберите пункт меню File => New => Project (Файл => Создать => Проект) для того чтобы открыть диалог New Project (Создание проекта).
2. Выберите пункт Visual C++ Projects (Проекты Visual C++) в списке Project Types (Типы проектов).
3. Выберите пункт Managed C++ Empty Project (Пустой проект на управляемом C++) в списке Templates (Шаблоны).
4. Введите HelloWorld (Привет, мир) в качестве названия проекта.
5. Задайте папку, в которой будет храниться проект.
6. Щелкните на ОК для того чтобы закрыть диалог New Project (Создание проекта) и завершить создание нового проекта. Добавьте исходный код:
7. Щелкните правой кнопкой на папке Source Files (Исходные файлы) в окне Solution Explorer (Поиск решений).Выберите пункт меню Add => Add New Item (Добавить => Добавить новый элемент) для того, чтобы открыть диалог Add New Item dialog (Добавить новый элемент).
8. Выберите в списке Templates (Шаблоны) пункт C++ File (Файл C++).
9. Укажите HelloWorld (Привет, мир) в качестве названия проекта.
10. Не изменяйте значение расположения (Location), принятое по умолчанию.
11. Щелкните на кнопке Open (Открыть) для того, чтобы закрыть диалог Add New Item dialog (Добавить новый элемент) и открыть Source Editor (Редактор текстов программ).
12. Введите код примера HelloWorld (Привет, мир). Скомпилируйте и запустите проект:
13. Выберите пункт меню Build => Build (Создать => Создать).
14. Используйте сочетание клавиш Ctrl-F5 для запуска программы без отладчика.
Директива fusing необходима для всех программ на управляемом С^+. Она делает доступными для компилятора стандартные типы (такие, как Console (Консоль) и Object (Объект)), определенные в библиотеке классов NET. Класс Console (Консоль) находится в пространстве имен System (Системное пространство имен) и его полное имя — System: : Console (Система::Консоль) Данный класс содержит метод WnteLine, выводящий на консоль текст и добавляющий к нему символ новой строки.
//HelloWorld.cpp
fusing <mscorlib.dll> // требуется для кода на управляемом Ст+
void main(void) {
System: : Console : : WriteLme ( "Hello Wcrla'M ;
// ("Привет, мир"); }
Программа может быть скомпилирована либо в Visual Studio.NET, либо при помощи командной строки с параметром /CLR (Common Language Runtime compilation — компиляция для выполнения в общеязыковой среде). Если вы используете командную строку. вы должны определить соответствующую среду Простейший способ сделать это — открыть командное окно, выбирая пункты меню Start (Пуск) => Programs (Программы) => Microsoft Visual Studio.NET 7.0 => Visual Studio.NET Tools => Visual Studio.NET Command Prompt. В командной строке
cl /CLR HelioWorld.cpp
исходный файл компилируется, а затем автоматически компонуется так, что результатом является ЕХЕ-файл HelloWorld.exe. Позже мы расскажем, как создать управляемую динамически подключаемую библиотеку (DLL).
Полученную управляемую программу можно запустить в Visual Studio.NET или из командной строки, как обычный исполняемый файл. Результатом работы программы будет следующее сообщение:
Hello World
(Привет, мир)

Директива #using и оператор using


Директива fusing делает доступной для компилятора информацию о типах, содержащуюся в сборке. Сборка содержит метаданные (описание информации о типах) и код на промежуточном языке IL. C6opKamscorlib.dll содержит описания многих полезных стандартных классов, определенных в .NET Framework, в том числе класса Console (Консоль), использовавшегося в предыдущем примере, и класса Object (Объект), который является базовым для всех управляемых классов. Добавим, что директива #us_ng совершенно не похожа на директиву #include, вставляющую в компилируемый файл некоторый другой исходный файл. Как отмечено выше, директива fusing скорее напоминает по совершаемым действиям директиву # import.
В предыдущем примере System (Системное пространство имен) предсташшет пространство имен C++, прямо соответствующее пространству имен .NET, имеющему то же название. Полное название класса состоит из названия пространства имен, за которым следуют два двоеточия и название класса, например, System: :Console (Система::Консоль) Хотя выражение using namespace, в предыдущем примере не используется, оно позволяет использовать короткие имена классов, например, Console (Консоль). Обратим ваше внимание на то, что выражение using namespace (определенное стандартом ANSI C++) и директива fusing (определенная в Microsoft C++) — совершенно разные вещи. Приведем пример использования выражения using namespace, позволяющего заменить полное имя System: : Console (Система.:Консоль) укороченным Console (Консоль):
//HelloKorld.cpp
fusing <rrscorlib.dll>
using namespace System;
// использовать пространство имен Система;
// этот оператор позволяет использовать короткие имена классов
void main(void) {
Console::WriteLine("Hello World"); // "Привет, Мир"
// пространство имен опущено
}

Стандартный ввод-вывод


Класс System:: Console (Система::Консоль) обеспечивает поддержку стандартного ввода-вывода. Метод ReadLine класса System: : Console (Система::Консоль) считывает введенную с клавиатуры строку как текстовую. С помощью методов Write (Запись) и WriteLine класса System: :Console (Система::Консоль) на консоль выводится текстовая строка, и, говоря о методе WriteLine, также символ новой строки. Проще всего ввод с консоли выполняется путем считывания в объект String (Строка) с последующим преобразованием в необходимый тип данных. Чтобы выполнить это преобразование можно использовать методы ТоХхх класса System: : Convert (Система::Преобразовать).
В следующем примере такой метод используется для ввода с консоли температуры в градусах Фаренгейта, преобразования текстовой строки в число, вычисления температуры в градусах Цельсия и вывода на консоль значений температуры в градусах Фаренгейта и Цельсия.
//ConvertTemp.срр
fusing <mscorlib.dll>
using namespace System;
// использовать пространство имен Система;
_gc class InputWrapper
// класс сборщика мусора InputWrapper
{
public:
int getlnt(String *pprompt) // Строка
{
Console::Write(pprompt); // Запись
String *pbuf = Console::ReadLine(); // Строка
return Convert::ToInt32(pbuf); // Преобразовать
}
double getDouble(String *pprompt)
{
Console::Write(pprompt); // Запись
String *pbuf = Console::ReadLine(); // Строка
return Convert::ToDouble(pbuf); // Преобразовать
}
Decimal getDecimal(String *pprompt) // Десятичное число
{
Console::Write(pprompt); // Запись
String *pbuf = Console::ReadLine(); // Строка
return Convert::ToDecimal(pbuf); // Преобразовать
}
String *getString(String *pprompt) // Строка
{
Console::Write(pprompt); // Запись
String *pbuf = Console::ReadLine(); // Строка
return pbuf;
}
};
void main(void)
{
InputWrapper *piw = new InputWrapper;
int numTemp = piw->getlnt("How many temp's? "); // Сколько?
for (int i = 0; i < numTemp; i++)
{
int fahr = piw->getlnt("Temp. (Fahrenheit): "); // Фаренгейт
int Celsius = (fahr - 32) * 5 / 9; // Цельсия
Console::WriteLine (
"Fahrenheit = {0}", fahr.ToString()); // Фаренгейт
Console::WriteLine("Celsius = {0}", _box(Celsius)); // Цельсия
}
}
Заметим, что первым аргументом метода WriteLine является форматирующая строка. Например, при первом вызове метода WriteLine форматирующая строка имеет вид "Fahrenheit={0} ", где {0} — заглушка, указывающая, что на это место следует вставить второй аргумент WriteLine. Число, помещенное в фигурные скобки, определяет, какой именно из следующих за форматирующей строкой аргументов следует вывести в указанном месте (естественно, нумерация начинается с нуля). В нашем примере это число — 0, так как за форматирующей строкой следует только один аргумент. Подставляемые аргументы могут быть нескольких типов, включая строки или упакованные значения, что и продемонстрировано в примере. Приведем пример работы программы, в котором преобразование температур производится два раза:
How many temp's? 2
Temp. (Fahrenheit): 212
Fahrenheit = 212
Celsius = 100
Temp. (Fahrenheit): 32
Fahrenheit = 32
Celsius = 0
Перевод такой:
Сколько температур? 2
Фаренгейта: 212
Фаренгейта =212
Цельсия = 100
Фаренгейта: 32
Фаренгейта = 32
Цельсия = О
В следующей программе продемонстрировано, как выводить данные в некоторых форматах с помощью метода WriteLine. Для этого применяются коды форматирования. Чтобы получить более подробную информацию о кодах форматирования, используемых в методе WriteLine (совпадающих, кстати, с кодами для метода string: : Format (Строка::Формат)), обратитесь к документации по .NET SDK.
//FormatString.cpp #using <mscorlib.dll>
using namespace System;
// использовать пространство имен Система;
void main(void) {
Console::WriteLine(
"{0:C}, {1:D}, {2:E}, {3:F}, {4:G}, {5:N}, {6:X}",
_box(lOO), // поле валюты (currency)
_box(200), // десятичное число (decimal)
_Ьох(ЗОО), // экспонента (exponent)
_box(400), // с фиксированной точкой (fixed point)
_box(SOO), // общий (general)
_Ьох(бОО), // число (number)
_box(700) // шестнадцатеричное (hexadecimal)
); }
Вот выдача:
$100.00, 200, З.ООООООЕ+002, 400.00, 500, 600.00, 2ВС

Класс System:: string (Система::Строка)


Класс System:: String (Система::Строка) инкапсулирует как управляемый объект строку символов Unicode. Класс String (Строка) определен в пространстве имен System (Системное пространство имен) и является стандартной частью .NET Framework. Тип String (Строка) представляет собой конечный (sealed) класс; это означает, что он не может быть базовым для другого класса. Сам класс String (Строка) — производный от класса System: :Object (Система::Объект), являющегося основой иерархии классов .NET. Объект String (Строка) — неизменяемый, т.е. будучи однажды инициализированным, он не может быть изменен. Класс String (Строка) содержит методы, которые можно использовать для изменения объекта String (Строка), такие, как Insert (Вставка), Replace (Замена) и PadLeft. Однако, в действительности, указанные методы никогда не изменяют исходный объект. Вместо этого они возвращают новый объект String (Строка), содержащий измененный текст. Если вы хотите получить возможность изменять исходные данные, вам следует обратить внимание на класс StringBuilder, а не на сам класс String (Строка). В следующем фрагменте кода показано, что метод Replace (Замена) не влияет на содержимое исходного объекта String (Строка), но изменяет содержимое объекта StringBuilder:
//StringReplace.срр
#using <mscorlib.dll>
using namespace System; // для консоли и строк
// использовать пространство имен Система;
using namespace System::Text; // для StringBuilder
// использовать пространство имен Система::Текст;
void main(void) {
Console::WriteLine("String is immutable:");
// ("Строка является неизменной: ");
String *psl = S"Hello World"; // Строка "Привет, Мир"
String *ps2 = psl->Replace('Н', 'J'); // Замена
Console::WriteLine(psl);
Console::WriteLine(ps2);
Console::WriteLine("StringBuilder can be modified:"); // ("StringBuilder может изменяться: ");
StringBuilder *psbl = new StringBuilder(S"Hello World"); // Привет, Мир
StringBuilder *psb2 = psbl->Replace('H', 'J'); // Замена
Console::WriteLine(psb1);
Console::WriteLine(psb2);
}
Информация, выведенная на экран профаммой, показывает, что действительно, содержимое объекта, на который указывает psl, не изменяется, т.е. метод Replace (Замена) не изменяет исходный объект String (Строка). С другой стороны, объект *psbl изменяется методом Replace (Замена).
String is immutable:
Hello World
Jello World
StringBuilder can be modified:
Jello World
Jello World
Перевод такой [Добавлен редактором русского перевода. — Прим. ред.]:
Строка является неизменной:
Привет, Мир
Jello Мир
StringBuilder может измениться:
Jello Мир
Jello Мир
В приведенном выше фрагменте кода вы можете заметить строковые литералы, определенные с префиксом S и без него. Строковый литерал, определенный с использованием только кавычек, является указателем на char (символ), т.е. указателем на последовательность символов ASCII, заканчивающуюся нулем. Такой указатель не является указателем на объект String (Строка). А строковый литерал, определенный с префиксом S, является указателем на управляемый объект String (Строка). Префикс L, не использовавшийся в предыдущем примере, обозначает строку символов Unicode, которая также не является объектом String (Строка). Следующий фрагмент демонстрирует эти три типа строк:
char *psl = "ASCII string literal"; // неуправляемый
// символ *psl = "строковый литерал ASCII ";
_wchar_t *ps2 = L"Unicode string literal"; // неуправляемый
// L " строковый литерал Уникода ";
String *ps3 = S"String object literal"; // управляемый
// Строка *ps3 = S " строковый литерал - объект String ";
Класс String (Строка) содержит много полезных методов. Так, для сравнения объектов можно использовать метод Equals (Равняется), что продемонстрировано в следующем примере. Подробнее о методах объекта String (Строка) можно узнать из документации по .NET SDK.
//Strings.срр
fusing <mscorlib.dll>
using namespace System;
// использовать пространство имен Система;
void main(void) {
String *pstrl = new String ("hello");
// Строка *pstrl = новая Строка ("привет");
String *pstr2 = new String("hello");
// Строка *pstr2 = новая Строка ("привет");
if (pstrl->Equals(pstr2))
// если (pstrl-> Равняется (pstr2))
Console::WriteLine("equal"); // равны - выполняется else
Console::WriteLine("not equal"); // не равный - не
// выполняется if (pstrl==pstr2) // если (pstrl == pstr2)
Console::WriteLine("equal"); // равны - не выполняется else
Console::WriteLine("not equal"); // не равный - выполняется }
Результат работы программы показывает разницу между сравнением объектов String (Строка) с помощью метода Equals (Равняется) и оператора ==. Метод Equals (Равняется) проверяет равенство содержимого объектов, тогда как оператор == проверяет лишь равенство указателей (т.е. равенство адресов объектов в памяти).
Equal
not equal
Вот перевод [Добавлен редактором русского перевода. — Прим. ред.]:
равны
не равны
Метод ToString обеспечивает представление объекта String (Строка) для любого управляемого типа данных. Хотя метод ToString не является автоматически доступным для неуправляемых классов, он доступен для упакованных значимых и упакованных примитивных типов, таких, как int или float (с плавающей точкой). Упаковка и распаковка, также как значимые типы, управляемые и неуправляемые типы, будут рассмотрены ниже в этой главе.
Метод ToString наиболее часто используется для вывода информации, а также при отладке, и создаваемые управляемые классы обычно заменяют ToString так, чтобы он возвращал определенную разработчиком, удобочитаемую информацию об объекте. Метод Obj ect: : ToString просто возвращает полное имя класса данного объекта и его реализация (не особо полезная, впрочем) доступна через наследование любому управляемому типу. Следующий пример демонстрирует некоторые аспекты работы метода ToString:
//ToString.cpp
#using <mscorlib.dll>
using namespace System;
// использовать пространство имен Система;
_gc class ClassWithToString
// класс сборщика мусора ClassWithToString
{
public:
String *ToString() // отмена {
return new String("SomeClass - override"); // возвратить новую Строку ("SomeClass - отмена");
}
};
_gc class ClassNoToString
// класс сборщика мусора ClassNoToString
{
//ToString унаследованный, без отмены
};
void main(void)
{
int i = 3;
Console::WriteLine(i.ToString()); // перегрузка String*
Console::WriteLine(i); // перегрузка int
ClassWithToString *psc = new ClassWithToString;
Console::WriteLine(psc->ToString()); // перегрузка String*
Console::WriteLine(psc); // перегрузка Object*
ClassNoToString *psoc = new ClassNoToString;
Console::WriteLine(psoc->ToString()); // перегрузка String*
Console::WriteLine(psoc); // перегрузка Object*
int array _gc[]= new int _gc[5]; // массив сборщика мусора
Console::WriteLine(array->ToString()); // перегрузка String
// (Строка)
Console::WriteLine(array); // перегрузка Object*
}
Результат работы программы приведен ниже. Заметьте, что метод ToString можно вызывать явно как аргумент перегруженного метода WriteLine объекта String (Строка), а можно вызвать перегруженный метод WriteLine объекта String (Строка), который сам вызовет метод ToString. Заметьте также, что даже управляемый массив (который, на самом деле, является управляемым типом) поддерживает метод ToString.
3
3
SomeClass - override SomeClass - override ClassNoToString
ClassNoToString System.Int32[] System.Int32[]
Все идентичные строковые литералы типа String (Строка) автоматически представляются указателями на объекты, являющиеся экземплярами одного класса String (Строка). Это справедливо для объектов, представленных строковыми литералами типа string (Строка), — такие объекты задаются с помощью взятой в кавычки строки. Однако это не справедливо для строковых объектов String (Строка), явно создаваемых с помощью оператора new (создать). Следующий пример подтверждает это. В нем сравниваются два указателя на объект String (Строка), созданных с помощью оператора new (создать). Выведенные на консоль результаты подтверждают, что два идентичных строковых объекта string (Строка), определенных как взятые в кавычки одинаковые последовательности символов, являются одним и тем же объектом (выражение pstrl==pstr2 истинно для строковых объектов String (Строка)). С другой стороны, два одинаковых строковых объекта string (Строка), созданных с помощью оператора new (создать), являются на самом деле разными объектами (выражение pstrl==pstr2 имеет значение false (ложь)).
//StringLiteral.срр
#using <mscorlib.dll>
using namespace System;
// использовать пространство имен Система;
void main(void) {
String *pstrl; // Строка
String *pstr2; // Строка
// сравнение объектов - строковых литералов типа String
pstrl = S"hello"; // привет
pstr2 = S"hello"; // привет
if (pstrl->Equals(pstr2)) // если (pstrl-> Равняется (pstr2))
Console::WriteLine("equal"); // равны - выполнен else
Console::WriteLine("not equal"); // не равны - не выполнен if (pstrl==pstr2) // если (pstrl == pstr2)
Console::WriteLine("equal"); // равны - выполнен else
Console::WriteLine("not equal"); // не равны - не выполнен // сравнение новых объектов String (не литералов) pstrl = new String("hello"); // pstrl = новая Строка ("привет"); pstr2 = new String("hello"); // pstr2 = новая Строка ("привет"); if (pstrl->Equals(pstr2) ) // если (pstrl-> Равняется (pstr2))
Console::WriteLine("equal"); // равны - выполнен else
Console::WriteLine("not equal"); // не равны - не выполнен if (pstrl==pstr2) // если (pstrl == pstr2)
Console::WriteLine("equal"); // равны - не выполнен else
Console::WriteLine("not equal"); // не равны - выполнен }
Программа напечатает:
equal equal equal not equal
Вот перевод [Добавлен редактором русского перевода. — Прим. ред.]:
равны
равны
равны
не равны
Управляемые строковые литералы String (Строка) и неуправляемые строковые литералы ASCII и Unicode (благодаря автоматической упаковке) можно использовать в выражениях, в которых ожидается использование управляемого строкового объекта String (Строка). Однако управляемый строковый объект String (Строка) нельзя использовать там, где ожидается появление переменных неуправляемых типов. Следующий пример доказывает это. Обратите внимание на закомментированные строки. Не будучи закомментированными, они привели бы к сообщению об ошибке при компиляции.
//MixingStringTypes.срр
fusing <rascorlib.dll>
using namespace System;
// использовать пространство имен Система;
tinclude <wchar.h> // для wchar_t
void ExpectingManagedString(String *str){} // Строка *str void ExpectingASCIIString(char *str){} // символ *str void ExpectingUnicodeString(wchar_t *str){} void main(void) {
// ожидается управляемый тип
ExpectingManagedString(S"hello"); // полное соответствие
// привет ExpectingManagedString("hello"); // нет ошибки
// привет
ExpectingManagedString(L"hello"); // нет ошибки
// привет
// ожидается неуправляемый тип
ExpectingASCIIString("hello"); // полное соответствие
// привет //ExpectingASCIIString(S"hello"); // ошибка!
// привет ExpectingUnicodeString(L"hello"); // полное соответствие
// привет //ExpectingUnicodeString(S"hello"); // ошибка!
// привет }

Класс System::Array (Система::МAССИВ)


В отличие от массивов в обычном C++, которые являются простым типом указателя, управляемые массивы являются полноценными управляемыми объектами, расположенными в динамически распределяемой области. System: : Array (Система::Массив) — абстрактный класс, являющийся базовым для всех управляемых массивов. Для определения неуправляемых массивов можно использовать синтаксис обычного C++; для определения же управляемых массивов следует использовать либо ключевое слово _дс (сборщик мусора), либо указывать, что элементы массива относятся к управляемому типу. Далее приведены примеры определения массивов. Ключевое слово _дс (сборщик мусора) и управляемые типы подробнее рассмотрены ниже. Обратите внимание на две закомментированные строки, в которых при определении массива задается его величина. Величину массива можно задавать при определении неуправляемого (располагаемого в стеке) массива, но не при определении управляемого массива (располагаемого в динамически распределяемой области). Причина в том, что, подобно всем остальным управляемым типам, управляемый массив располагается в динамически распределяемой области, а не в стеке.
//ArraySyntax.срр
fusing <mscorlib.dll>
using namespace System;
// использовать пространство имен Система;
ttpragma warning(disable : 4101)
// уничтожить предупреждение о переменной, на которую нет ссылки:
// предупреждение (отключить: 4101)
void main(void) {
// традиционный синтаксис неуправляемого массива
int *pintUnManagedArrayOnHeap = new int [5];
int intUnManagedArray[5]; // нет ошибки для неуправляемого
// массива
// синтаксис управляемого массива,
// используется ключевое слово _дс (сборщик мусора) int intManagedArrayOnHeap _дс[] = new int _дс[5]; //int intManagedArray _gc[5]; // ошибка для управляемого
// массива
// синтаксис управляемого массива, используется
// управляемый тип элемента
String *strManagedArrayOnHeap[] = new String* [5]; // Строка
//String *strManagedArray[5]; // ошибка для управляемого
// массива }
Управляемые массивы имеют некоторые дополнительные, по сравнению с неуправляемыми массивами, свойства и ограничения.
• Управляемый массив можно определить только в управляемой динамически распределяемой области памяти. Его нельзя поместить вне кучи (т.е. он не может быть расположен в стеке).
• Описание управляемого массива не должно содержать размер массива, так как это предполагало бы его размещение вне динамически распределяемой области памяти. Вместо этого размер массива указывается при использовании оператора new (создать), создающего объект-массив, содержащийся в управляемой динамически распределяемой области памяти.
• Все управляемые массивы являются потомками класса System: :Array (Система::Массив), так что методы этого класса, как, например, Сору (Копировать), GetLength и GetType, также как и методы класса System: :Object (Система::Объект), наподобие ToString и Equals (Равняется), могут использоваться в любом управляемом массиве.
• При попытке доступа к элементу управляемого массива производится проверка принадлежности к диапазону, т.е. контроль границ. Одна из самых распространенных ошибок — обращение к несуществующему объекту по адресу, указывающему за пределы массива. При попытке обратиться к элементу, номер которого не попадает в диапазон индексов элементов, возникает исключение IndexOutOfRangeException.
Следующий пример показывает, как можно использовать обработчик исключений при попытке доступа к несуществующему элементу управляемого массива. Обратите внимание, что массив содержит пять элементов, а в цикле производится попытка установить значение шестого. Программа в обычном C++ выполнила бы такое действие, изменив содержимое памяти за пределами массива. Никто не скажет точно, чем это могло бы закончиться. При проверке корректности адреса выполняются два действия: во-первых, предотвращается изменение содержимого памяти за пределами массива; во-вторых, программе сообщается, что возникла подобная ситуация, тем самым давая возможность исправить ошибку еще на стадии тестирования. В обычном C++ такая ошибка часто не проявляется до тех пор, пока программа, по непонятным причинам, не прекращает работу, обычно в месте кода, далеко отстоящем от самой ошибки. И, разумеется, согласно закону Мэрфи, эта ошибка обнаруживается только тогда, когда программа уже передана заказчику. //IndexOutOfRangeException.срр
#using <mscorlib.dll> using namespace System;
// использовать пространство имен Система/void main () {
int intArray _gc[]= new int _gc[5]; // сборщик мусора [5]
for (int i=0; i<6; i++) // больше чем есть!!!
{
try {
intArray[i] = i; }
catch (IndexOutOfRangeException *piore) {
// нужно сделать кое-что более полезное, // чтобы здесь восстановиться Console::WriteLine("Oooops!"); Console::WriteLine(piore->get_Message()); } } }
Программа напечатает:
Oooops!
Exception of type System.IndexOutOfRangeException was thrown.
Перевод такой [Добавлен редактором русского перевода. — Прим. ред.]:
Возникло исключение типа Система.IndexOutOfRangeException.
Как и для неуправляемых массивов, нумерация элементов в управляемых массивах начинается с нуля. Однако, значения элементов управляемых массивов, в отличие от элементов неуправляемых массивов, автоматически инициализируются значением, принятым по умолчанию для каждого типа элемента массива. Переменным примитивных типов, таких, как int, char (символ), float (с плавающей точкой) и double (с удвоенной точностью) присваивается нуль. Элементам, указывающим на управляемые объекты, также по умолчанию присваивается нулевое значение (т.е. нулевой указатель). Элементы значимых типов инициализируются с помощью их принятого по умолчанию конструктора (т.е. конструктора, не имеющего аргументов). Значимые типы подробнее рассмотрены ниже.
В следующем примере иллюстрируется работа с массивами, и сравниваются управляемый двумерный массив и обычный неуправляемый двумерный же массив. Обратите внимание, что при работе с неуправляемым массивом используется старый синтаксис доступа к элементам массива [ ] [ ], тогда как при работе с управляемым массивом, который является истинно двумерным, используется синтаксис [, ]. Хотя в этом примере при использовании синтаксиса [ ] [ ] каждый из подмассивов имеет одинаковое количество элементов, в других случаях они могут иметь разные размеры (т.н. массив с неровным правым краем). Синтаксис [, ] предполагает использование истинно прямоугольного массива.
//Arrayl.срр
fusing <mscorlib.dll>
using namespace System;
// использовать пространство имен Система;
void main () {
// управляемый одномерный массив int
// (использующий сборщик мусора)
Console::WriteLine("managed ID array of int");
// ("управляемый одномерный массив int");
int intArray _gc[]= new int _gc[5];
for (int i=0; i<intArray->Length; i++)
{
intArray[i] = i;
Console::Write(intArray[i]); // Запись
Console::Write("\t"); // Запись } Console::WriteLine();
// управляемый двумерный массив Строк
// (использующий управляемый тип)
Console::WriteLine("managed 2D array of Strings");
// ("управляемый двумерный массив Строк ");
String *str2DArray[,] = new String *[2,3]; // новая Строка
for(int row=0; row<str2DArray->GetLength(0); row++)
//по строкам
{
for(int col=0; col<str2DArray->GetLength(l) ; col++)
//по столбцам
{
str2DArray[row,col] = (row*10 + col).ToString();
// str2DArray [строка, столбец] = (row*10 + столбец)
// .ToString ();
Console::Write(str2DArray[row,col]);
// Запись:: (str2DArray [строка, столбец]);
Console::Write("\t"); // Запись }
Console::WriteLine(); }
// неуправляемый двумерный массив int (для сравнения)
Console::WriteLine("unmanaged 2D array of int");
// ("неуправляемый двумерный массив int");
int int2DArray[2][3];
for(int row=0; row<2; row++) //по строкам
{
for (int col=0; col<3; col++) // по столбцам {
int2DArray[row][col] = row*10 + col;
// int2DArray [строка] [столбец] = row*10 + столбец; Console::Write(int2DArray[row][col]); // Запись:: (int2DArray [строка] [столбец]); Console::Write("\t"); // Запись }
Console::WriteLine(); } )
Результат работы программы приведен ниже. Управляемый прямоугольный двумерный массив содержит элементы типа String*, а неуправляемый — элементы типа int. Однако управляемый массив может также содержать и элементы неуправляемых типов, между тем как неуправляемый массив — лишь элементы неуправляемых типов.
managed ID array of int 01234 managed 2D array of Strings 012 10 11 12
unmanaged 2D array of int
0 1 2
10 11 12
Перевод такой:
управляемый одномерный массив int
01234
управляемый двумерный массив Строк
0 1 2
10 11 12
неуправляемый двумерный массив int
0 1 2
10 11 12
Приведем еще один пример, в котором сравнивается использование массива массивов (синтаксис [ ] [ ]) и прямоугольного двумерного массива (синтаксис [, ]). На этот раз, ради более корректного сравнения, оба массива содержат элементы типа int.
//Array2.срр
#using <mscorlib.dll>
using namespace System;
// использовать пространство имен Система;
void main(void) {
Console::WriteLine("Rectangular array using [,]");
// ("Использование прямоугольного массива [,] ");
int rect2DArray [,] = new int _gc [3,41; // сборщик мусора -
// управляемый
for(int row=0; row< rect2DArray ->GetLength(0); row++) // по строкам {
for(int col=0; col< rect2DArray->GetLength(1); col++)
// по столбцам
{
rect2DArray [row,col] = row*10 + col; // rect2DArray [строка, столбец] = row*10 + столбец; Console : -.Write (rect2DArray [row, col] ) ; // Запись:: (rect2DArray [строка, столбец]); Console::Write("\t"); // Запись }
Console::WriteLine(); }
Console::WriteLine("Array of arrays using [][]"); // ("использование массива массивов [] [] "); int arrayOfArray[3][4]; // неуправляемый for(int row=0; row<3 ; row++) // по строкам {
for(int col=0; col<4; col++) // по столбцам {
arrayOfArray[row][col] = row*10 + col;
Добавлен редактором русского перевода. — Прим. ред.
// arrayOfArray [строка] [столбец] = row*10 + столбец; Console::Write(arrayOfArray[row][col]); // Запись:: (arrayOfArray [строка] [столбец]); Console::Write("\t"); // Запись
}
Console::WriteLine(); } >
Программа напечатает:
Rectangular array using [,]
0 1 2 3
10 11 12 13
20 21 22 23
Array of arrays using [][]
0 1 2 3
10 11 12 13
20 21 22 23
Перевод такой:
Использование прямоугольного массива []
0 1 2 3
10 11 12 13
20 21 22 23
Использование массива массивов [] []
0 1 2 3
10 11 12 13
20 21 22 23
На рис. 3.1 и 3.2 показано размещение в памяти элементов массива массивов (объявленного с помощью синтаксиса [ ] [ ]) и прямоугольного двумерного массива (объявленного с помощью синтаксиса [, ]), использовавшихся в предыдущем примере.

Программа Hotel (Гостиница)
Теперь представим первую версию программы управления системой бронирования Гостиничных номеров, которую мы будем использовать и расширять в следующих главах. Обратите внимание, что класс Hotel (Гостиница) хранится не в сборке ЕХЕ, а в динамически подключаемой библиотеке (DLL).
Вы можете открыть готовое решение, находящееся в папке HotelRes\Hotel, или создать проект и ввести исходный код сами. Для того чтобы это сделать, необходимо создать проект библиотеки классов на управляемом C++ (Managed C++ Class Library project), называющийся Hotel (Гостиница), добавить исходный код, а затем скомпилировать проект. Заметьте, что поскольку выходной файл —динамически подключаемая библиотека (DLL), его не удастся протестировать до создания исполнимого файла (ЕХЕ) программы-клиента.
Создание библиотеки классов на управляемом C++ (Managed C++ Class Library project)
Создайте проект библиотеки классов на управляемом C++ под названием Hotel (Гостиница):
1. Откройте Visual Studio.NET.
2. Выберите пункт меню File^New^Project (ФайлОСоздаты^Проект) для того чтобы вызвать диалог New Project (Создание проекта).
3. Выберите в списке Project Туре (Тип проекта) Visual C++ Projects (Проекты на Visual C++).
4. Выберите в списке Template (Шаблон) Managed C++ Class Library Project (Проект библиотеки классов на управляемом C++),
5. Введите Hotel (Гостиница) в поле Name (Название).
6. Задайте папку, в которой будет сохранен проект.
7. Щелкните на кнопке ОК. для того чтобы закрыть диалог New Project (Создание проекта) и создать проект.
Добавьте исходный код:
8. Дважды щелкните на файле Hotel.cpp в окне Solution Explorer (Поиск решения).
9. Введите исходный код примера Hotel (Гостиница). Скомпилируйте проект:
10. Выберите пункт меню Build^Build (СборкаОСобрать).
Хотя определение класса уже присутствует в заголовочном файле и используется в качестве типа данных в срр-файлах, мы, ради простоты и наглядности, поместили данное ниже определение класса Hotel (Гостиница) непосредственно в исходный файл Hotel . срр. Это привело также к тому, что указанный файл стал больше похож на исходный файл С#, в котором директива I include отсутствует. Visual Studio создала, конечно, файл Hotel. h, но это не имеет значения, т.к. соответствующая директива #include была удалена из файла Hotel. срр.
//Hotel.cpp
finclude "stdafx.h" // имеет #using <mscorlib.dll>
using namespace System;
// использовать пространство имен Система;
public _gc class Hotel
// класс сборщика мусора Гостиница
{
private: // частный
String *pcity; // Строка
String *pname; // Строка
int number;
Decimal rate; // Десятичное
public:
Hotel(String *pcity, String *pname, // Гостиница
int number, double rate)
{
this->pcity = pcity;
this->pname = pname;
this->number = number;
this->rate = rate;
}
Hotel() // Гостиница
{
this->pcity = 0;
this->pname = 0;
this->number = 50; // значение по умолчанию 50
this->rate = 0;
}
String *GetCity() // Строка
{
return pcity;
}
String *GetName() // Строка
{
return pname;
}
int GetNumber()
{
return number;
}
void SetNumber(int val)
{
number = val;
}
Decimal GetRate() // Десятичное число
{
x return rate;
}
void SetRate(Decimal val) // Десятичное число
{
rate = val; } void RaisePrice(Decimal amount) // Десятичное количество
{
rate = rate+1;
}
};
Приведенный код компилируется затем в сборку NET, называющуюся Hotel dll Это можно сделать в Visual Studio NET, а можно — с помощью командной строки Если вы используете командную строку, то должны определить соответствующее окружение Простейший способ сделать это — открыть командное окно, выбрав пункты меню Start (Пуск) => Programs (Программы) => Microsoft Visual Studio NET 7 => Visual Studio NET Tools => Visual Studio NET Command Prompt В командной строке, приведенной ниже, компилируется исходный файл Hotel cpp:
cl /CLR Hotel.cpp /LD
Параметр /LD указывает, что компоновщик должен создать динамически подключаемую библиотеку (DLL), а не ЕХЕ-файл Класс Hotel (Гостиница) содержит частные (private) данные, два конструктора для инициализации данных и несколько общедоступных (public) методов
Для того чтобы продемонстрировать возможность использования в NET разных языков, следующая программа, которая тестирует созданный ранее компонент Hotel (Гостиница), реализована на С# Можно либо самостоятельно ее реализовать с использованием Visual Studio NET, либо просто открыть готовое решение, находящееся в папке HotelRes\TestHotel Для создания программы необходимо создать проект консольного приложения на С# (С# Console Application) TestHotel, добавить исходный код, затем ссылку на сборку Hotel (Гостиница), после чего скомпилировать и запустить программу
Создание консольного приложения на С# (С# Console Application):
Создайте проект консольного приложения С#, называющийся TestHotel
1. Откройте Visual Studio NET
2. Выберите пункт меню Fue => New => Project (Файл => Создать => Проект) для того чтобы вызвать диалог New Project (Создание проекта).
3 Выберите в списке Project Type (Тип проекта) Visual C# Projects (Проекты на Visual C#).
4 Выберите в списке Template (Шаблон) Console Application (Консольное приложение)
5 Введите 'TestHotel" в поле Name (Название)
6. Задайте папку, в которой будет сохранен проект
7. Щелкните на кнопке ОК для того чтобы закрыть диалог New Project (Создание проекта) и создать проект
Добавьте исходный код:
8. Щелкните правой кнопкой на файле Class.cs в окне Solution Explorer (Поиск решения) и выберите в меню пункт Rename (Переименовать)
9. Введите новое имя исходного файла — TestHotel.cs
10. Дважды щелкните на файле TestHotel cs в окне Solution Explorer (Поиск решения) для того чтобы открыть файл для редактирования.
11. Добавьте в файл TestHotel cs соответствующий исходный код
Добавьте ссылку на сборку Hotel (Гостиница):
12. Выберите пуню меню Project => Add Reference (Проект => Добавить ссылку).
13. Щелкните на кнопке Browse (Обзор)
14. Найдите папку, в которой хранится сборка Hotel (Гостиница).
15. Дважды щелкните на сборке Hotel dll.
16. Щелкните на кнопке ОК.
Скомпилируйте и запустите проект:
17. Выберите пункт меню Build => Build (Сборка => Собрать)
18. Нажмите сочетание клавиш Ctrl-F5 для запуска программы без отладчика
//TestHotel.cs
using System;
JI использование Системы;
public class TestHotel
// общедоступный класс TestHotel
public static void Main()
{
Hotel generic = new Hotel (); // универсальная новая
// Гостиница
ShowHotel (generic) ; // универсальная
Hotel ritz = new Hotel("Atlanta", "Ritz", 100, 95);
// Роскошная гостиница = новая Гостиница
// ("Атланта", "Роскошь", 100, 95),
ShowHotel(ritz);
ritz.RaisePrice(50m);
ritz.SetNumber(125);
ShowHotel(ritz);
; }
private static void ShowHotel (Hotel hotel)
// частный статический
ShowHotel (Гостиница гостиница)
{
Console.WriteLine(
"{0} {1}: number = {2}, rate = {3:C}",
hotel.GetCity(), hotel.GetName(), // гостиница
hotel.GetNumber(), hotel.GetRate()); // гостиница
}
}
Обратите внимание, что Visual Studio автоматически копирует Hotel.dll в ту же папку, в которую помещает файл TestHotel. exe Так происходит потому, что в проект С# была добавлена ссылка на данную сборку Это удобно, ведь если запустить программу-клиент, а общеязыковая среда выполнения CLR не найдет соответствующей сборки, возникнет исключение времени выполнения Приведем строки, выведенные на экран программой, созданной на С# с компонентом на C++:
number = 50, rate = $0.00
Atlanta Ritz: number = 100, rate = $95.00
Atlanta Ritz: number = 125, rate = $96.00

 
На главную | Содержание | < Назад....Вперёд >
С вопросами и предложениями можно обращаться по nicivas@bk.ru. 2013 г. Яндекс.Метрика