Klasy ogólne [C++/CLI]
Klasa ogólna jest deklarowana przy użyciu następującego formularza:
Składnia
[attributes]
generic <class-key type-parameter-identifier(s)>
[constraint-clauses]
[accessibility-modifiers] ref class identifier [modifiers]
[: base-list]
{
class-body
} [declarators] [;]
Uwagi
W powyższej składni używane są następujące terminy:
Atrybuty
(Opcjonalnie) Dodatkowe informacje deklaratywne. Aby uzyskać więcej informacji na temat atrybutów i klas atrybutów, zobacz Atrybuty.
klucz klasy
class
lub typename
.
type-parameter-identifier(s), rozdzielana przecinkami lista identyfikatorów określających nazwy parametrów typu.
constraint-clauses
Lista (nie rozdzielona przecinkami) klauzul where określająca ograniczenia parametrów typu. Przyjmuje formularz:
gdzie type-parameter-identifier : constraint-list...
constraint-list
klasa lub interfejs[,
...]
modyfikatory ułatwień dostępu
Modyfikatory ułatwień dostępu dla klasy ogólnej. W przypadku środowisko wykonawcze systemu Windows jedynym dozwolonym modyfikatorem jest private
. W przypadku środowiska uruchomieniowego języka wspólnego dozwolone modyfikatory to private
i public
.
identifier
Nazwa klasy ogólnej, dowolny prawidłowy identyfikator języka C++.
Modyfikatorów
(Opcjonalnie) Dozwolone modyfikatory obejmują zapieczętowane i abstrakcyjne.
lista bazowa
Lista zawierająca jedną klasę bazową i wszystkie zaimplementowane interfejsy rozdzielone przecinkami.
treść klasy
Treść klasy, zawierająca pola, funkcje składowe itp.
Deklaratorów
Deklaracje dowolnych zmiennych tego typu. Na przykład: ^
identyfikator[,
...]
Można zadeklarować klasy ogólne, takie jak te (należy pamiętać, że słowo kluczowe class
może być używane zamiast typename
). W tym przykładzie ItemType
wartości i ValueType
KeyType
są nieznanymi typami określonymi w punkcie, w którym typ. HashTable<int, int>
jest skonstruowanym typem typu HashTable<KeyType, ValueType>
ogólnego . Z jednego typu ogólnego można utworzyć wiele różnych skonstruowanych typów. Skonstruowane typy skonstruowane z klas ogólnych są traktowane jak każdy inny typ klasy ref.
// generic_classes_1.cpp
// compile with: /clr
using namespace System;
generic <typename ItemType>
ref struct Stack {
// ItemType may be used as a type here
void Add(ItemType item) {}
};
generic <typename KeyType, typename ValueType>
ref class HashTable {};
// The keyword class may be used instead of typename:
generic <class ListItem>
ref class List {};
int main() {
HashTable<int, Decimal>^ g1 = gcnew HashTable<int, Decimal>();
}
Oba typy wartości (wbudowane typy, takie jak int
lub lub double
, lub typy wartości zdefiniowane przez użytkownika) i typy referencyjne mogą być używane jako argument typu ogólnego. Składnia w definicji ogólnej jest taka sama, niezależnie od tego. Składniowo nieznany typ jest traktowany tak, jakby był typem referencyjnym. Jednak środowisko uruchomieniowe jest w stanie określić, czy typ rzeczywiście używany jest typem wartości i zastąpić odpowiedni wygenerowany kod dla bezpośredniego dostępu do elementów członkowskich. Typy wartości używane jako argumenty typu ogólnego nie są boxed i dlatego nie mają kary za wydajność związaną z boksem. Składnia używana w treści generycznej powinna mieć T^
wartość i ->
zamiast .
. Każde użycie metody ref new, gcnew dla parametru typu będzie odpowiednio interpretowane przez środowisko uruchomieniowe jako proste utworzenie typu wartości, jeśli argument typu jest typem wartości.
Można również zadeklarować klasę ogólną z ograniczeniami dla parametrów typu ogólnego (C++/CLI) dla typów, które mogą być używane dla parametru typu. W poniższym przykładzie dowolny typ używany przez ItemType
program musi implementować IItem
interfejs. Próba użycia elementu int
, na przykład, który nie implementuje IItem
, spowoduje wygenerowanie błędu czasu kompilacji, ponieważ argument typu nie spełnia ograniczenia.
// generic_classes_2.cpp
// compile with: /clr /c
interface class IItem {};
generic <class ItemType>
where ItemType : IItem
ref class Stack {};
Klasy ogólne w tej samej przestrzeni nazw nie mogą być przeciążone, zmieniając tylko liczbę lub typy parametrów typu. Jeśli jednak każda klasa znajduje się w innej przestrzeni nazw, może być przeciążona. Rozważmy na przykład następujące dwie klasy i MyClass
MyClass<ItemType>
, w przestrzeniach A
nazw i B
. Dwie klasy można następnie przeciążyć w trzeciej przestrzeni nazw C:
// generic_classes_3.cpp
// compile with: /clr /c
namespace A {
ref class MyClass {};
}
namespace B {
generic <typename ItemType>
ref class MyClass2 { };
}
namespace C {
using namespace A;
using namespace B;
ref class Test {
static void F() {
MyClass^ m1 = gcnew MyClass(); // OK
MyClass2<int>^ m2 = gcnew MyClass2<int>(); // OK
}
};
}
Nie można wpisać parametrów klasy bazowej i interfejsów podstawowych. Jednak klasa bazowa może obejmować parametr typu jako argument, jak w następującym przypadku:
// generic_classes_4.cpp
// compile with: /clr /c
generic <typename ItemType>
interface class IInterface {};
generic <typename ItemType>
ref class MyClass : IInterface<ItemType> {};
Konstruktory i destruktory są wykonywane raz dla każdego wystąpienia obiektu (jak zwykle); Konstruktory statyczne są wykonywane raz dla każdego skonstruowanego typu.
Pola w klasach ogólnych
W tej sekcji przedstawiono użycie wystąpień i pól statycznych w klasach ogólnych.
Zmienne wystąpienia
Zmienne wystąpienia klasy ogólnej mogą mieć typy i inicjatory zmiennych, które zawierają dowolne parametry typu z otaczającej klasy.
Przykład: różne klasy ogólne
W poniższym przykładzie trzy różne wystąpienia klasy ogólnej MyClass<ItemType> są tworzone przy użyciu odpowiednich argumentów typu (int
, double
, i string).
// generics_instance_fields1.cpp
// compile with: /clr
// Instance fields on generic classes
using namespace System;
generic <typename ItemType>
ref class MyClass {
// Field of the type ItemType:
public :
ItemType field1;
// Constructor using a parameter of the type ItemType:
MyClass(ItemType p) {
field1 = p;
}
};
int main() {
// Instantiate an instance with an integer field:
MyClass<int>^ myObj1 = gcnew MyClass<int>(123);
Console::WriteLine("Integer field = {0}", myObj1->field1);
// Instantiate an instance with a double field:
MyClass<double>^ myObj2 = gcnew MyClass<double>(1.23);
Console::WriteLine("Double field = {0}", myObj2->field1);
// Instantiate an instance with a String field:
MyClass<String^>^ myObj3 = gcnew MyClass<String^>("ABC");
Console::WriteLine("String field = {0}", myObj3->field1);
}
Integer field = 123
Double field = 1.23
String field = ABC
Zmienne statyczne
Podczas tworzenia nowego typu ogólnego tworzone są nowe wystąpienia dowolnych zmiennych statycznych, a każdy konstruktor statyczny dla tego typu jest wykonywany.
Zmienne statyczne mogą używać dowolnych parametrów typu z otaczającej klasy.
Przykład: Używanie zmiennych statycznych
W poniższym przykładzie pokazano użycie pól statycznych i konstruktora statycznego w klasie ogólnej.
// generics_static2.cpp
// compile with: /clr
using namespace System;
interface class ILog {
void Write(String^ s);
};
ref class DateTimeLog : ILog {
public:
virtual void Write(String^ s) {
Console::WriteLine( "{0}\t{1}", DateTime::Now, s);
}
};
ref class PlainLog : ILog {
public:
virtual void Write(String^ s) { Console::WriteLine(s); }
};
generic <typename LogType>
where LogType : ILog
ref class G {
static LogType s_log;
public:
G(){}
void SetLog(LogType log) { s_log = log; }
void F() { s_log->Write("Test1"); }
static G() { Console::WriteLine("Static constructor called."); }
};
int main() {
G<PlainLog^>^ g1 = gcnew G<PlainLog^>();
g1->SetLog(gcnew PlainLog());
g1->F();
G<DateTimeLog^>^ g2 = gcnew G<DateTimeLog^>();
g2->SetLog(gcnew DateTimeLog());
// prints date
// g2->F();
}
Static constructor called.
Static constructor called.
Static constructor called.
Test1
Metody w klasach ogólnych
Metody w klasach ogólnych mogą być same w sobie ogólne; metody inne niż ogólne będą niejawnie sparametryzowane przez parametr typu klasy.
Następujące specjalne reguły dotyczą metod w klasach ogólnych:
Metody w klasach ogólnych mogą używać parametrów typu jako parametrów, typów zwracanych lub zmiennych lokalnych.
Metody w klasach ogólnych mogą używać otwartych lub zamkniętych typów skonstruowanych jako parametrów, zwracanych typów lub zmiennych lokalnych.
Metody inne niż ogólne w klasach ogólnych
Metody w klasach ogólnych, które nie mają dodatkowych parametrów typu, są zwykle określane jako nieogólne, chociaż są niejawnie sparametryzowane przez otaczającą klasę ogólną.
Podpis metody innej niż ogólna może zawierać jeden lub więcej parametrów typu otaczającej klasy, bezpośrednio lub w otwartym typie skonstruowanym. Na przykład:
void MyMethod(MyClass<ItemType> x) {}
Treść takich metod może również używać tych parametrów typu.
Przykład: deklarowanie metody innej niż ogólna
W poniższym przykładzie zadeklarowana jest metoda niegeneryczna , ProtectData
wewnątrz klasy MyClass<ItemType>
ogólnej . Metoda używa parametru ItemType
typu klasy w podpisie w otwartym skonstruowanym typie.
// generics_non_generic_methods1.cpp
// compile with: /clr
// Non-generic methods within a generic class.
using namespace System;
generic <typename ItemType>
ref class MyClass {
public:
String^ name;
ItemType data;
MyClass(ItemType x) {
data = x;
}
// Non-generic method using the type parameter:
virtual void ProtectData(MyClass<ItemType>^ x) {
data = x->data;
}
};
// ItemType defined as String^
ref class MyMainClass: MyClass<String^> {
public:
// Passing "123.00" to the constructor:
MyMainClass(): MyClass<String^>("123.00") {
name = "Jeff Smith";
}
virtual void ProtectData(MyClass<String^>^ x) override {
x->data = String::Format("${0}**", x->data);
}
static void Main() {
MyMainClass^ x1 = gcnew MyMainClass();
x1->ProtectData(x1);
Console::WriteLine("Name: {0}", x1->name);
Console::WriteLine("Amount: {0}", x1->data);
}
};
int main() {
MyMainClass::Main();
}
Name: Jeff Smith
Amount: $123.00**
Metody ogólne w klasach ogólnych
Metody ogólne można zadeklarować zarówno w klasach ogólnych, jak i niegenerycznych. Na przykład:
Przykład: deklarowanie metod ogólnych i niegenerycznych
// generics_method2.cpp
// compile with: /clr /c
generic <typename Type1>
ref class G {
public:
// Generic method having a type parameter
// from the class, Type1, and its own type
// parameter, Type2
generic <typename Type2>
void Method1(Type1 t1, Type2 t2) { F(t1, t2); }
// Non-generic method:
// Can use the class type param, Type1, but not Type2.
void Method2(Type1 t1) { F(t1, t1); }
void F(Object^ o1, Object^ o2) {}
};
Metoda niegeneryczna jest nadal ogólna w tym sensie, że jest sparametryzowana przez parametr typu klasy, ale nie ma dodatkowych parametrów typu.
Wszystkie typy metod w klasach ogólnych mogą być ogólne, w tym statyczne, wystąpienia i metody wirtualne.
Przykład: deklarowanie i używanie metod ogólnych
W poniższym przykładzie pokazano deklarowanie i używanie metod ogólnych w klasach ogólnych:
// generics_generic_method2.cpp
// compile with: /clr
using namespace System;
generic <class ItemType>
ref class MyClass {
public:
// Declare a generic method member.
generic <class Type1>
String^ MyMethod(ItemType item, Type1 t) {
return String::Concat(item->ToString(), t->ToString());
}
};
int main() {
// Create instances using different types.
MyClass<int>^ myObj1 = gcnew MyClass<int>();
MyClass<String^>^ myObj2 = gcnew MyClass<String^>();
MyClass<String^>^ myObj3 = gcnew MyClass<String^>();
// Calling MyMethod using two integers.
Console::WriteLine("MyMethod returned: {0}",
myObj1->MyMethod<int>(1, 2));
// Calling MyMethod using an integer and a string.
Console::WriteLine("MyMethod returned: {0}",
myObj2->MyMethod<int>("Hello #", 1));
// Calling MyMethod using two strings.
Console::WriteLine("MyMethod returned: {0}",
myObj3->MyMethod<String^>("Hello ", "World!"));
// generic methods can be called without specifying type arguments
myObj1->MyMethod<int>(1, 2);
myObj2->MyMethod<int>("Hello #", 1);
myObj3->MyMethod<String^>("Hello ", "World!");
}
MyMethod returned: 12
MyMethod returned: Hello #1
MyMethod returned: Hello World!
Używanie typów zagnieżdżonych w klasach ogólnych
Podobnie jak w przypadku zwykłych klas, można zadeklarować inne typy wewnątrz klasy ogólnej. Deklaracja klasy zagnieżdżonej jest niejawnie sparametryzowana przez parametry typu deklaracji klasy zewnętrznej. W związku z tym dla każdego skonstruowanego typu zewnętrznego zdefiniowano odrębną klasę zagnieżdżoną. Na przykład w deklaracji ,
// generic_classes_5.cpp
// compile with: /clr /c
generic <typename ItemType>
ref struct Outer {
ref class Inner {};
};
Typ Outer<int>::Inner
nie jest taki sam jak typ Outer<double>::Inner
.
Podobnie jak w przypadku metod ogólnych w klasach ogólnych, dodatkowe parametry typu można zdefiniować dla typu zagnieżdżonego. Jeśli używasz tych samych nazw parametrów typu w klasie wewnętrznej i zewnętrznej, parametr typu wewnętrznego ukryje parametr typu zewnętrznego.
// generic_classes_6.cpp
// compile with: /clr /c
generic <typename ItemType>
ref class Outer {
ItemType outer_item; // refers to outer ItemType
generic <typename ItemType>
ref class Inner {
ItemType inner_item; // refers to Inner ItemType
};
};
Ponieważ nie ma możliwości odwoływania się do parametru typu zewnętrznego, kompilator utworzy ostrzeżenie w tej sytuacji.
Podczas konstruowania zagnieżdżonych typów ogólnych są nazwane, parametr typu dla typu zewnętrznego nie znajduje się na liście parametrów typu dla typu wewnętrznego, mimo że typ wewnętrzny jest niejawnie sparametryzowany przez parametr typu zewnętrznego. W powyższym przypadku nazwa skonstruowanego typu to Outer<int>::Inner<string>
.
Przykład: Kompilowanie i odczytywanie połączonej listy
W poniższym przykładzie pokazano tworzenie i odczytywanie połączonej listy przy użyciu typów zagnieżdżonych w klasach ogólnych.
// generics_linked_list.cpp
// compile with: /clr
using namespace System;
generic <class ItemType>
ref class LinkedList {
// The node class:
public:
ref class Node {
// The link field:
public:
Node^ next;
// The data field:
ItemType item;
} ^first, ^current;
};
ref class ListBuilder {
public:
void BuildIt(LinkedList<double>^ list) {
/* Build the list */
double m[5] = {0.1, 0.2, 0.3, 0.4, 0.5};
Console::WriteLine("Building the list:");
for (int n=0; n<=4; n++) {
// Create a new node:
list->current = gcnew LinkedList<double>::Node();
// Assign a value to the data field:
list->current->item = m[n];
// Set the link field "next" to be the same as
// the "first" field:
list->current->next = list->first;
// Redirect "first" to the new node:
list->first = list->current;
// Display node's data as it builds:
Console::WriteLine(list->current->item);
}
}
void ReadIt(LinkedList<double>^ list) {
// Read the list
// Make "first" the "current" link field:
list->current = list->first;
Console::WriteLine("Reading nodes:");
// Read nodes until current == null:
while (list->current != nullptr) {
// Display the node's data field:
Console::WriteLine(list->current->item);
// Move to the next node:
list->current = list->current->next;
}
}
};
int main() {
// Create a list:
LinkedList<double>^ aList = gcnew LinkedList<double>();
// Initialize first node:
aList->first = nullptr;
// Instantiate the class, build, and read the list:
ListBuilder^ myListBuilder = gcnew ListBuilder();
myListBuilder->BuildIt(aList);
myListBuilder->ReadIt(aList);
}
Building the list:
0.1
0.2
0.3
0.4
0.5
Reading nodes:
0.5
0.4
0.3
0.2
0.1
Właściwości, zdarzenia, indeksatory i operatory w klasach ogólnych
Właściwości, zdarzenia, indeksatory i operatory mogą używać parametrów typu otaczającej klasy ogólnej jako wartości zwracanych, parametrów lub zmiennych lokalnych, takich jak kiedy
ItemType
jest parametrem typu klasy:public ItemType MyProperty {}
Właściwości, zdarzenia, indeksatory i operatory nie mogą być sparametryzowane.
Przykład: deklarowanie właściwości wystąpienia
W tym przykładzie pokazano deklaracje właściwości wystąpienia w klasie ogólnej.
// generics_generic_properties1.cpp
// compile with: /clr
using namespace System;
generic <typename ItemType>
ref class MyClass {
private:
property ItemType myField;
public:
property ItemType MyProperty {
ItemType get() {
return myField;
}
void set(ItemType value) {
myField = value;
}
}
};
int main() {
MyClass<String^>^ c = gcnew MyClass<String^>();
MyClass<int>^ c1 = gcnew MyClass<int>();
c->MyProperty = "John";
c1->MyProperty = 234;
Console::Write("{0}, {1}", c->MyProperty, c1->MyProperty);
}
John, 234
Przykład: klasa ogólna ze zdarzeniem
W następnym przykładzie przedstawiono klasę ogólną ze zdarzeniem.
// generics_generic_with_event.cpp
// compile with: /clr
// Declare a generic class with an event and
// invoke events.
using namespace System;
// declare delegates
generic <typename ItemType>
delegate void ClickEventHandler(ItemType);
// generic class that defines events
generic <typename ItemType>
ref class EventSource {
public:
// declare the event OnClick
event ClickEventHandler<ItemType>^ OnClick;
void FireEvents(ItemType item) {
// raises events
OnClick(item);
}
};
// generic class that defines methods that will called when
// event occurs
generic <typename ItemType>
ref class EventReceiver {
public:
void OnMyClick(ItemType item) {
Console::WriteLine("OnClick: {0}", item);
}
};
int main() {
EventSource<String^>^ MyEventSourceString =
gcnew EventSource<String^>();
EventSource<int>^ MyEventSourceInt = gcnew EventSource<int>();
EventReceiver<String^>^ MyEventReceiverString =
gcnew EventReceiver<String^>();
EventReceiver<int>^ MyEventReceiverInt = gcnew EventReceiver<int>();
// hook handler to event
MyEventSourceString->OnClick += gcnew ClickEventHandler<String^>(
MyEventReceiverString, &EventReceiver<String^>::OnMyClick);
MyEventSourceInt->OnClick += gcnew ClickEventHandler<int>(
MyEventReceiverInt, &EventReceiver<int>::OnMyClick);
// invoke events
MyEventSourceString->FireEvents("Hello");
MyEventSourceInt->FireEvents(112);
// unhook handler to event
MyEventSourceString->OnClick -= gcnew ClickEventHandler<String^>(
MyEventReceiverString, &EventReceiver<String^>::OnMyClick);
MyEventSourceInt->OnClick -= gcnew ClickEventHandler<int>(
MyEventReceiverInt, &EventReceiver<int>::OnMyClick);
}
Ogólne struktury
Reguły deklarowania i używania struktur ogólnych są takie same jak w przypadku klas ogólnych, z wyjątkiem różnic zanotowane w dokumentacji języka Visual C++.
Przykład: deklarowanie struktury ogólnej
Poniższy przykład deklaruje ogólną strukturę , MyGenStruct
z jednym polem, myField
i przypisuje wartości różnych typów (int
, double
, String^
) do tego pola.
// generics_generic_struct1.cpp
// compile with: /clr
using namespace System;
generic <typename ItemType>
ref struct MyGenStruct {
public:
ItemType myField;
ItemType AssignValue(ItemType item) {
myField = item;
return myField;
}
};
int main() {
int myInt = 123;
MyGenStruct<int>^ myIntObj = gcnew MyGenStruct<int>();
myIntObj->AssignValue(myInt);
Console::WriteLine("The field is assigned the integer value: {0}",
myIntObj->myField);
double myDouble = 0.123;
MyGenStruct<double>^ myDoubleObj = gcnew MyGenStruct<double>();
myDoubleObj->AssignValue(myDouble);
Console::WriteLine("The field is assigned the double value: {0}",
myDoubleObj->myField);
String^ myString = "Hello Generics!";
MyGenStruct<String^>^ myStringObj = gcnew MyGenStruct<String^>();
myStringObj->AssignValue(myString);
Console::WriteLine("The field is assigned the string: {0}",
myStringObj->myField);
}
The field is assigned the integer value: 123
The field is assigned the double value: 0.123
The field is assigned the string: Hello Generics!