Udostępnij za pośrednictwem


Klasy ogólne

Klasy rodzajowej jest zadeklarowana za pomocą następującego formularza:

[attributes]
generic <class-key type-parameter-identifier(s)>
[constraint-clauses]
[accessibility-modifiers] ref class identifier  [modifiers]
[: base-list] 
{ 
class-body 
} [declarators] [;]

Uwagi

Składnia powyższego używane są następujące terminy:

  • attributes (opcjonalnie)
    Dodatkowe informacje deklaracyjne.Aby uzyskać więcej informacji dotyczących atrybutów i klas atrybutów Zobacz atrybuty.

  • klucz klasy
    Albo class lubtypename

  • Typ-parametr-identyfikator(-y),
    Rozdzielana przecinkami lista identyfikatorów Określanie nazwy parametrów typu.

  • klauzul CONSTRAINT
    Wykaz (nie rozdzielanych przecinkami) gdzie klauzule określające ograniczenia parametrów typu.Ma postać:

    where type-parameter-identifier : constraint-list ...

  • ograniczenie listy
    class-or-interface, ...

  • modyfikatorów ułatwień dostępu
    Modyfikatorów ułatwień dostępu dla klasą rodzajową.Dla Środowisko wykonawcze systemu Windows, modyfikator jest dozwolony tylko private.Common language runtime, Modyfikatory dozwolone są private i public.

  • Identyfikator
    Nazwa klasy rodzajowe, dowolny poprawny identyfikator języka C++.

  • Modyfikatory (opcjonalnie)
    Mogą zawierać Modyfikatory sealed i streszczenie.

  • Lista podstawowego
    Lista zawierająca jedną klasę bazową i wszelkich implementowane interfejsy wszystkich rozdzielonych przecinkami.

  • Treść klasy
    Treść klasy, zawierające pól, funkcje składowe itd.

  • declarators
    Deklaracje wszelkich zmiennych tego typu.For example: ^identifier, ...]

Można zadeklarować klasy rodzajowe, takie jak te (należy zauważyć, że słowo kluczowe klasy może być używany zamiast typename).W tym przykładzie ItemType, KeyType i ValueType są nieznane typy, które są określone w punkcie gdzie typ.HashTable<int, int>jest skonstruowany typ rodzajowy typ HashTable<KeyType, ValueType>.Liczba różnych typów konstruowanej mogą być skonstruowane z typu rodzajowego.Typy konstruowanej zbudowane z klas rodzajowych są traktowane jak innego typu 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 (albo wbudowane typy takie jak int lub double, lub wartości zdefiniowane przez użytkownika typy) i typy odwołań mogą zostać wykorzystane jako argument typu rodzajowego.Składnia definicji rodzajowy jest taka sama bez względu na to.Syntaktycznie nieznany typ jest traktowana tak, jakby była typu odwołania.Jednak środowiska wykonawczego jest możliwość ustalenia, jeśli typ faktycznie używany jest typ wartości i podstawić odpowiednie wygenerowany kod dla bezpośredniego dostępu do członków.Typy wartości używane jako argumenty typu rodzajowego nie są zapakowane i dlatego nie poniósł spadku wydajności związane z boksie.Składnia używana w ramach organu Generic powinny być T ^ i "—>"zamiast z".'.Każde korzystanie z ref new, gcnew (C++ Component Extensions) dla typu parametru będą odpowiednio interpretowane w czasie wykonywania jako prostego tworzenia typu wartości Jeśli argument Typ jest typem wartości.

Można również zadeklarować klasy rodzajowej z Ograniczenia parametrów typu ogólnego (C++/CLI) na typy, które mogą być używane dla tego parametru typu.W poniższym przykładzie dowolny typ używany dla ItemType musi implementować IItem interfejsu.Podjęto próbę użycia int, na przykład, który nie implementuje IItem, wyprodukuje błąd kompilacji, ponieważ argument typ spełnia ograniczenia.

// generic_classes_2.cpp
// compile with: /clr /c
interface class IItem {};
generic <class ItemType>
where ItemType : IItem
ref class Stack {};

Klasy rodzajowe w tym samym obszarem nazw nie można obciążać tylko zmieniając numer lub typy parametrów typu.Jednakże jeżeli każdej klasy mieszka w innej przestrzeni nazw, one mogą być przeciążone.Na przykład, rozważmy następujące dwie klasy, MyClass i MyClass<ItemType>, w obszarach nazw A i B.Dwie klasy mogą następnie być przeciążone w trzecim obszarze 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
      }
   };
}

Klasa podstawowa i interfejsach podstawowych nie mogą być parametrów typu.Jednak klasa podstawowa może obejmować parametru typu jako argument, tak jak w przypadku następujących:

// 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ą wykonane raz dla każdej instancji obiektu (jak zwykle); Konstruktory statyczne są wykonywane raz dla każdego typu konstruowanej.

Pola w klas rodzajowych

W tej sekcji przedstawiono użycie instancji i statycznego pola w klas rodzajowych.

Zmienne instancji

Zmienne instancji klasy rodzajowe mogą mieć typy i zmiennej inicjatorów, które obejmują wszelkie parametry typu z otaczającym klasy.

Przykład

W poniższym przykładzie, trzy wystąpienia różnych rodzajowy klasy MojaKlasa<ItemType>, są tworzone za pomocą argumentów odpowiedniego typu (int, Podwójna, i ciąg).

// 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);
   }
  

Poniższy przykład demonstruje użycie statycznego pola i konstruktorze statycznym w ramach klasą rodzajową.

// 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();
}
  

W poniższym przykładzie deklaruje metodę nierodzajową, ProtectData, wewnątrz klasy rodzajowej, MyClass<ItemType>.Metoda wykorzystuje parametr typu klasy ItemType w jego podpis w polu Otwórz typ konstruowanej.

// 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();
}
  
// 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) {}
};

Metodę nierodzajową jest nadal rodzajowe w tym sensie, że jest parametryzowana przez parametr typu tej klasy, ale go nie ma typu dodatkowych parametrów.

Wszystkie rodzaje metod klas rodzajowy może być rodzajowy, w tym statycznych, wystąpienia i metodach wirtualnych.

Poniższy przykład demonstruje deklarowanie i przy użyciu metody rodzajowe w ramach klas rodzajowych:

// 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!");
}
  
// 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);
}
  

Ten przykład pokazuje deklaracji właściwości instancji w ramach klasą rodzajową.

// 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);
}
  

W następnym przykładzie klasą rodzajową 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);
}

W poniższym przykładzie deklaruje rodzajowy struct, MyGenStruct, z jednym polem, myFieldi przypisuje wartości różnych typów (int, Podwójna, ciąg ^) 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);
}
  

Zmienne statyczne

Przy tworzeniu nowego typu rodzajowego tworzone są nowe wystąpienia wszelkie zmienne statyczne i wszelkie konstruktorze statycznym dla tego typu jest wykonywany.

Zmienne statyczne można używać żadnych parametrów typu z otaczającym klasy.

Metody rodzajowe klas

Metody rodzajowe klas mogą być rodzajowy. metody nierodzajową będzie niejawnie sparametryzowana przez parametr typu klasy.

Następujące specjalne zasady stosuje się do metod wewnątrz klas rodzajowych:

  • Metody rodzajowe klas można używać parametrów typu jako parametry, zwracanych typów lub zmiennych lokalnych.

  • Metody rodzajowe klas można używać otwartej lub zamkniętej konstruowanej typów jako parametry, zwracanych typów lub zmiennych lokalnych.

Niepodstawowego metod klas rodzajowy

Metody rodzajowe klas, które mają bez parametrów typu dodatkowe są zwykle określane jako nierodzajową chociaż są one niejawnie sparametryzowana przez otaczający klasą rodzajową.

Podpis metodę nierodzajową może zawierać jeden lub więcej parametrów typ otaczający klasy bezpośrednio lub otwartego typu konstruowanej.Na przykład:

void MyMethod(MyClass<ItemType> x) {}

Ciała tych metod można również użyć tych parametrów typu.

Metody rodzajowe w klas rodzajowych

Można zadeklarować metody rodzajowe w klasach rodzajowa i nie uniwersalne.Na przykład:

Zagnieżdżone typy klas rodzajowych

Podobnie jak w przypadku zwykłych klas można zadeklarować innych typów wewnątrz klasy rodzajowej.Deklaracja klasy zagnieżdżonych niejawnie jest parametryzowana przez parametry Typ deklaracji klasy zewnętrzne.W efekcie klasą zagnieżdżoną odrębne jest zdefiniowany dla każdego typu zewnętrznym skonstruowane.Na przykład w deklaracji,

// generic_classes_5.cpp
// compile with: /clr /c
generic <typename ItemType>
ref struct Outer {
   ref class Inner {};
};

Typ zewnętrzne<int>:: wewnętrzna nie jest taki sam, jak typ zewnętrzne<podwójna>:: wewnętrznej.

Podobnie jak w przypadku metody rodzajowe w klas rodzajowych, można zdefiniować parametry dodatkowe typu dla typu zagnieżdżonego.Jeśli używasz te same nazwy parametru typu klasy wewnętrzne i zewnętrzne, parametr typu wewnętrzny ukryje parametru 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 istnieje żaden sposób odnoszą się do parametru typu zewnętrznego, kompilator powoduje wygenerowanie ostrzeżenia w tej sytuacji.

Gdy konstruowanej zagnieżdżone typy rodzajowe są nazwane, parametru typu dla typu zewnętrznej nie znajduje się na liście Typ parametru typu wewnętrzny, mimo że wewnętrzny typ niejawnie jest parametryzowana przez parametr typu zewnętrznego.W powyższym przypadku nazwa typu konstruowanej byłoby zewnętrzne<int>:: wewnętrzny<ciąg>.

Poniższy przykład ilustruje tworzenie i odczytywanie połączonej listy zagnieżdżone typy klas rodzajowych.

Właściwości, zdarzenia, indeksatory i podmioty gospodarcze w klas rodzajowych

  • Właściwości, zdarzenia, indeksatory i operatorów można używać parametrów typu otaczającego klasą rodzajową jako wartości zwracane, parametry lub zmiennych lokalnych, jak np. przy ItemType jest parametrem typu klasy:

    public ItemType MyProperty {}
    
  • Właściwości, zdarzenia, indeksatory i podmioty gospodarcze nie mogą same być parametryczne.

Rodzajowy strukturach

Zasady deklarowania i przy użyciu strukturach rodzajowe są takie same, jak dla klas rodzajowych, z wyjątkiem różnic zauważyć w w Skorowidzu języka Visual C++.

Zobacz też

Inne zasoby

Typy ogólne (C++ Component Extensions)