Udostępnij za pośrednictwem


System typów języka C++

Pojęcie typu jest ważne w języku C++. Zwracana wartość każdej zmiennej, argumentu funkcji oraz funkcji musi mieć typ, aby mogła zostać skompilowana. Ponadto wszystkie wyrażenia (w tym wartości literału) są niejawnie podane typowi przez kompilator przed ich oceną. Niektóre przykłady typów obejmują wbudowane typy, takie jak int przechowywanie wartości całkowitych, double przechowywanie wartości zmiennoprzecinkowych lub standardowych typów bibliotek, takich jak klasa std::basic_string do przechowywania tekstu. Możesz utworzyć własny typ, definiując element class lub struct. Typ określa ilość pamięci przydzielonej dla zmiennej (lub wynik wyrażenia). Typ określa również rodzaje wartości, które mogą być przechowywane, jak kompilator interpretuje wzorce bitowe w tych wartościach i operacje, które można na nich wykonać. Ten artykuł zawiera nieformalny przegląd głównych funkcji systemu typów C++.

Terminologia

Typ skalarny: typ, który zawiera pojedynczą wartość zdefiniowanego zakresu. Skalarne obejmują typy arytmetyczne (wartości całkowite lub zmiennoprzecinkowe), składowe typu wyliczenia, typy wskaźników, typy wskaźników do składowych i std::nullptr_t. Typy podstawowe są zwykle typami skalarnymi.

Typ złożony: typ, który nie jest typem skalarnym. Typy złożone obejmują typy tablic, typy funkcji, typy klas (lub struktury), typy unii, wyliczenia, odwołania i wskaźniki do niestacjonalnych składowych klas.

Zmienna: symboliczna nazwa ilości danych. Nazwa może służyć do uzyskiwania dostępu do danych, do których odnosi się w całym zakresie kodu, w którym jest definiowany. W języku C++zmienna jest często używana do odwoływania się do wystąpień typów danych skalarnych, natomiast wystąpienia innych typów są zwykle nazywane obiektami.

Obiekt: Dla uproszczenia i spójności w tym artykule użyto terminu object w celu odwoływania się do dowolnego wystąpienia klasy lub struktury. Gdy jest on używany w ogólnym sensie, zawiera wszystkie typy, nawet zmienne skalarne.

Typ zasobnika (zwykłe stare dane): ta nieformalna kategoria typów danych w języku C++ odnosi się do typów skalarnych (zobacz sekcję Typy podstawowe) lub klasy POD. Klasa POD nie ma statycznych składowych danych, które nie są również identyfikatorami POD i nie ma konstruktorów zdefiniowanych przez użytkownika, destruktorów zdefiniowanych przez użytkownika ani operatorów przypisania zdefiniowanych przez użytkownika. Klasa POD nie ma też żadnych funkcji wirtualnych, nie ma klasy podstawowej ani żadnych prywatnych lub chronionych niestatycznych składowych danych. Typy POD są często używane do wymiany danych zewnętrznych, na przykład przy użyciu modułu napisanego w języku C (który ma tylko typy POD).

Określanie typów zmiennych i funkcji

Język C++ jest językiem silnie typizowanego i statycznie typizowanego języka; każdy obiekt ma typ i ten typ nigdy się nie zmienia. W przypadku deklarowania zmiennej w kodzie należy jawnie określić jej typ lub użyć auto słowa kluczowego , aby wywoływać typ z inicjatora. Podczas deklarowania funkcji w kodzie należy określić typ jego zwracanej wartości i każdego argumentu. Użyj zwracanego typu void wartości, jeśli żadna wartość nie jest zwracana przez funkcję. Wyjątek dotyczy używania szablonów funkcji, które umożliwiają używanie argumentów dowolnego typu.

Po pierwszym zadeklarowaniu zmiennej nie można zmienić jej typu w pewnym późniejszym momencie. Można jednak skopiować wartość zmiennej lub wartość zwracaną funkcji do innej zmiennej innego typu. Takie operacje są nazywane konwersjami typów, które czasami są niezbędne, ale są również potencjalnymi źródłami utraty lub niepoprawności danych.

Podczas deklarowania zmiennej typu ZASOB zdecydowanie zalecamy jego zainicjowanie , co oznacza nadanie jej wartości początkowej. Dopóki zmienna nie zostanie zainicjalizowana, ma ona wartość „śmieciową”, składającą się z bitów, które poprzednio znajdowały się w tej lokalizacji pamięci. Jest to ważny aspekt języka C++, który należy pamiętać, zwłaszcza jeśli pochodzisz z innego języka, który obsługuje inicjację. W przypadku deklarowania zmiennej typu klasy innej niż POD konstruktor obsługuje inicjowanie.

Poniższy przykład pokazuje kilka prostych deklaracji zmiennych z opisami każdej z nich. W przykładzie pokazano również, jak kompilator używa informacji o typie, aby umożliwiać lub uniemożliwiać pewne kolejne operacje na zmiennej.

int result = 0;              // Declare and initialize an integer.
double coefficient = 10.8;   // Declare and initialize a floating
                             // point value.
auto name = "Lady G.";       // Declare a variable and let compiler
                             // deduce the type.
auto address;                // error. Compiler cannot deduce a type
                             // without an intializing value.
age = 12;                    // error. Variable declaration must
                             // specify a type or use auto!
result = "Kenny G.";         // error. Can't assign text to an int.
string result = "zero";      // error. Can't redefine a variable with
                             // new type.
int maxValue;                // Not recommended! maxValue contains
                             // garbage bits until it is initialized.

Podstawowe (wbudowane) typy

W odróżnieniu do innych języków, C++ nie ma uniwersalnego typu bazowego, z którego wywodzą się wszystkie inne typy. Język zawiera wiele podstawowych typów, nazywanych również typami wbudowanymi. Te typy obejmują typy liczbowe, takie jak int, , doublelong, , booloraz charwchar_t typy dla znaków ASCII i UNICODE, odpowiednio. Większość całkowitych typów podstawowych (z wyjątkiem booltypów , double, wchar_ti powiązanych) ma unsigned wszystkie wersje, które modyfikują zakres wartości, które może przechowywać zmienna. Na przykład , intktóry przechowuje 32-bitową liczbę całkowitą ze znakiem, może reprezentować wartość z -2,147,483,648 do 2,147,483,647. Element unsigned int, który jest również przechowywany jako 32 bity, może przechowywać wartość z zakresu od 0 do 4294 967 295. Całkowita liczba możliwych wartości w każdym przypadku jest taka sama, zmienia się tylko zakres.

Kompilator rozpoznaje te wbudowane typy i ma wbudowane reguły, które określają, jakie operacje można wykonywać na nich, oraz sposób ich konwertowania na inne typy podstawowe. Aby uzyskać pełną listę wbudowanych typów oraz ich rozmiar i limity liczbowe, zobacz Wbudowane typy.

Na poniższej ilustracji przedstawiono względne rozmiary wbudowanych typów w implementacji języka Microsoft C++:

Diagram względnego rozmiaru w bajtach kilku wbudowanych typów.

W poniższej tabeli wymieniono najczęściej używane typy podstawowe oraz ich rozmiary w implementacji języka Microsoft C++:

Typ Rozmiar Komentarz
int 4 bajty Wybór domyślny dla wartości całkowitych.
double 8 bajtów Wybór domyślny dla wartości zmiennopozycyjnych.
bool 1 bajt Przedstawia wartości, które mogą być prawdziwe lub fałszywe.
char 1 bajt Używaj dla znaków ASCII w starszych ciągach typu C lub obiektach std::string, które nigdy nie będą musiały zostać skonwertowane na UNICODE.
wchar_t 2 bajty Przedstawia wartości znaku „szerokiego”, które mogą być zakodowane w formacie UNICODE (UTF-16 w systemie Windows, inne systemy operacyjne mogą się różnić). wchar_t to typ znaku używany w ciągach typu std::wstring.
unsigned char 1 bajt Język C++ nie ma wbudowanego typu bajtów. Użyj unsigned char polecenia , aby reprezentować wartość bajtu.
unsigned int 4 bajty Wybór domyślny dla flag bitowych.
long long 8 bajtów Reprezentuje znacznie większy zakres wartości całkowitych.

Inne implementacje języka C++ mogą używać różnych rozmiarów dla niektórych typów liczbowych. Aby uzyskać więcej informacji o rozmiarach i relacjach rozmiaru, których wymaga standard C++, zobacz Wbudowane typy.

Typ void

Typ void jest specjalnym typem; nie można zadeklarować zmiennej typu , ale można zadeklarować zmienną typu voidvoid * (wskaźnik do void), która jest czasami konieczna w przypadku przydzielania nieprzetworzonej (nietypowej) pamięci. Jednak wskaźniki void nie są bezpieczne dla typu, a ich użycie jest zniechęcane do nowoczesnego języka C++. W deklaracji funkcji zwracana wartość oznacza, void że funkcja nie zwraca wartości; użycie jej jako typu zwracanego jest typowym i akceptowalnym użyciem voidelementu . Chociaż język C wymaga funkcji, które mają zerowe parametry do zadeklarowania void na liście parametrów, fn(void)na przykład , ta praktyka jest odradzana w nowoczesnym języku C++; należy zadeklarować fn()funkcję bez parametrów . Aby uzyskać więcej informacji, zobacz Konwersje typów i bezpieczeństwo typów.

const kwalifikator typu

Każdy wbudowany lub zdefiniowany przez użytkownika typ może być kwalifikowany const przez słowo kluczowe . Ponadto funkcje składowe mogą być const-kwalifikowane, a nawet const-przeciążone. Nie można zmodyfikować wartości const typu po zainicjowaniu.

const double PI = 3.1415;
PI = .75; //Error. Cannot modify const variable.

const Kwalifikator jest szeroko używany w deklaracjach funkcji i zmiennych, a "const correctness" jest ważną koncepcją w języku C++. Zasadniczo oznacza const to, że w celu zagwarantowania w czasie kompilacji wartości nie są modyfikowane przypadkowo. Aby uzyskać więcej informacji, zobacz const.

Typ const różni się od jego wersji innejconst niż wersja, const int na przykład jest odrębnym typem od int. Operator języka C++ const_cast można używać w rzadkich przypadkach, gdy musisz usunąć const-ness ze zmiennej. Aby uzyskać więcej informacji, zobacz Konwersje typów i bezpieczeństwo typów.

Typy ciągu

Mówiąc ściśle, język C++ nie ma wbudowanego typu ciągu; char i wchar_t przechowuj pojedyncze znaki — należy zadeklarować tablicę tych typów, aby przybliżyć ciąg, dodając wartość null zakończenia (na przykład ASCII '\0') do elementu tablicy jeden obok ostatniego prawidłowego znaku (nazywanego również ciągiem w stylu C). Ciągi stylu C wymagały znacznie więcej kodu lub korzystania z funkcji zewnętrznej biblioteki obsługującej ciągi. Jednak w nowoczesnym języku C++mamy standardowe typy std::string bibliotek (dla ciągów znaków typu 8-bitowego) lub char (dla 16-bitowych std::wstringwchar_tciągów znaków typu). Te kontenery biblioteki standardowej języka C++ mogą być uważane za natywne typy ciągów, ponieważ są one częścią standardowych bibliotek, które są zawarte w dowolnym zgodnym środowisku kompilacji C++. #include <string> Użyj dyrektywy , aby udostępnić te typy w programie. (Jeśli używasz MFC lub ATL, CString klasa jest również dostępna, ale nie jest częścią standardu C++). Korzystanie z tablic znaków zakończonych wartością null (wcześniej wymienionych ciągów w stylu C) jest odradzane we współczesnym języku C++.

Typy definiowane przez użytkownika

Podczas definiowania classkonstrukcji , , structunionlub enumjest używana w pozostałej części kodu tak, jakby była to podstawowy typ. Zajmuje znany obszar w pamięci, a niektóre zasady sposobu, w jaki można go używać, dotyczą sprawdzania w czasie kompilacji i w trakcie uruchomienia, przez cały okres istnienia programu. Główne różnice między głównymi wbudowanymi typami a typami definiowanymi przez użytkownika są następujące:

  • Kompilator nie ma wbudowanej wiedzy o typie zdefiniowanym przez użytkownika. Poznaje typ, gdy po raz pierwszy napotka definicję podczas procesu kompilacji.

  • Ty określasz, jakie operacje mogą zostać przeprowadzone na typie i jak może on zostać przekonwertowany do innych typów, definiując (przez przeciążenie) odpowiednie operatory, albo jako funkcje składowych, albo jako funkcje, które nie są składowymi. Aby uzyskać więcej informacji, zobacz Przeciążenie funkcji

Typy wskaźnika

Podobnie jak w najwcześniejszych wersjach języka C++ nadal umożliwia deklarowanie zmiennej typu wskaźnika przy użyciu specjalnego deklaratora * (gwiazdki). Typ wskaźnika przechowuje adres lokalizacji w pamięci, gdzie jest przechowywana rzeczywista wartość danych. W nowoczesnym języku C++te typy wskaźników są nazywane nieprzetworzonymi wskaźnikami i są dostępne w kodzie za pomocą operatorów specjalnych: * (gwiazdka) lub -> (kreska z większą niż, często nazywaną strzałką). Ta operacja dostępu do pamięci jest nazywana wyłuszczeniem. Który operator, którego używasz, zależy od tego, czy wskaźnik jest wyłuszczany do skalarnego, czy wskaźnika do elementu członkowskiego w obiekcie.

Praca z typami wskaźników od dawna jest jednym z najtrudniejszych i najbardziej dezorientujących aspektów tworzenia programów w C i C++. W tej sekcji opisano niektóre fakty i praktyki ułatwiające korzystanie z pierwotnych wskaźników, jeśli chcesz. Jednak w nowoczesnym języku C++nie jest już wymagane (lub zalecane) używanie nieprzetworzonych wskaźników własności obiektów ze względu na ewolucję inteligentnego wskaźnika (omówionego bardziej na końcu tej sekcji). Nadal przydatne i bezpieczne jest używanie surowych wskaźników do obserwacji obiektów. Jeśli jednak musisz użyć ich do własności obiektu, należy to zrobić ostrożnie i ostrożnie rozważyć sposób tworzenia i niszczenia obiektów należących do nich.

Pierwszą rzeczą, którą należy wiedzieć, jest to, że deklaracja nieprzetworzonej zmiennej wskaźnika przydziela tylko wystarczającą ilość pamięci do przechowywania adresu: lokalizacja pamięci, do której wskaźnik odwołuje się podczas wyłuskania. Deklaracja wskaźnika nie przydziela pamięci potrzebnej do przechowywania wartości danych. (Ta pamięć jest również nazywana magazynem zapasowym). Innymi słowy, deklarując zmienną nieprzetworzonego wskaźnika, tworzysz zmienną adresową pamięci, a nie rzeczywistą zmienną danych. Jeśli wyłuszczenie zmiennej wskaźnika przed upewnieniem się, że zawiera prawidłowy adres magazynu kopii zapasowych, powoduje niezdefiniowane zachowanie (zazwyczaj błąd krytyczny) w programie. Poniższy przykład przedstawia ten rodzaj błędu:

int* pNumber;       // Declare a pointer-to-int variable.
*pNumber = 10;      // error. Although this may compile, it is
                    // a serious error. We are dereferencing an
                    // uninitialized pointer variable with no
                    // allocated memory to point to.

Przykład wyłuskuje typ wskaźnika, bez przydzielania pamięci na przechowywanie rzeczywistych danych całkowitoliczbowych lub prawidłowego przypisanego adresu pamięci. Poniższy kod poprawia te błędy:

    int number = 10;          // Declare and initialize a local integer
                              // variable for data backing store.
    int* pNumber = &number;   // Declare and initialize a local integer
                              // pointer variable to a valid memory
                              // address to that backing store.
...
    *pNumber = 41;            // Dereference and store a new value in
                              // the memory pointed to by
                              // pNumber, the integer variable called
                              // "number". Note "number" was changed, not
                              // "pNumber".

Poprawiony przykład kodu używa pamięci stosu lokalnego do utworzenia magazynu zapasowego, do pNumber którego wskazuje. Dla uproszczenia używamy podstawowego typu. W praktyce magazyny zapasowe wskaźników są najczęściej typami zdefiniowanymi przez użytkownika, które są dynamicznie przydzielane w obszarze pamięci nazywanym stertą (lub ) przy użyciu wyrażenia kluczowego (w programowaniu w stylu C użyto starszej new funkcji biblioteki środowiska uruchomieniowego języka C). Po przydzieleniu te zmienne są zwykle określane jako obiekty, zwłaszcza jeśli są oparte na definicji klasy. Pamięć przydzielona new za pomocą polecenia musi zostać usunięta przez odpowiednią delete instrukcję (lub, jeśli funkcja została użyta malloc() do jej przydzielenia, funkcja free()środowiska uruchomieniowego języka C ).

Można jednak łatwo zapomnieć o usunięciu dynamicznie przydzielonego obiektu — zwłaszcza w złożonym kodzie, co powoduje usterkę zasobu nazywaną wyciekiem pamięci. Z tego powodu stosowanie surowych wskaźników jest odradzane we współczesnym języku C++. Prawie zawsze lepiej jest opakowować nieprzetworzone wskaźniki w inteligentnym wskaźniku, który automatycznie zwalnia pamięć po wywołaniu destruktora. (Oznacza to, że gdy kod wykracza poza zakres inteligentnego wskaźnika). Używając inteligentnych wskaźników, praktycznie eliminujesz całą klasę usterek w programach języka C++. W poniższym przykładzie przyjęto MyClass założenie, że jest typem zdefiniowanym przez użytkownika, który ma metodę publiczną DoSomeWork();

void someFunction() {
    unique_ptr<MyClass> pMc(new MyClass);
    pMc->DoSomeWork();
}
  // No memory leak. Out-of-scope automatically calls the destructor
  // for the unique_ptr, freeing the resource.

Aby uzyskać więcej informacji na temat inteligentnych wskaźników, zobacz Inteligentne wskaźniki.

Aby uzyskać więcej informacji na temat konwersji wskaźników, zobacz Konwersje typów i bezpieczeństwo typów.

Aby uzyskać więcej informacji na temat wskaźników ogólnie, zobacz Wskaźniki.

Typy danych Windows

W klasycznym programowaniu Win32 dla języków C i C++większość funkcji używa definicji typów i #define makr specyficznych dla systemu Windows (zdefiniowanych w programie windef.h) w celu określenia typów parametrów i zwracanych wartości. Te typy danych systemu Windows są głównie nazwami specjalnymi (aliasami) nadanym wbudowanym typom C/C++. Aby uzyskać pełną listę tych definicji typów i definicji preprocesora, zobacz Typy danych systemu Windows. Niektóre z tych definicji typów, takie jak HRESULT i LCID, są przydatne i opisowe. Inne, takie jak INT, nie mają specjalnego znaczenia i są tylko aliasami dla podstawowych typów języka C++. Inne typy danych systemu Windows mają nazwy pochodzące z czasów programowania w C i procesorów 16-bitowych. Nie mają one żadnego celu ani znaczenia w przypadku nowoczesnego sprzętu lub systemu operacyjnego. Istnieją również specjalne typy danych skojarzone z biblioteką środowisko wykonawcze systemu Windows wymienioną jako środowisko wykonawcze systemu Windows podstawowe typy danych. W nowoczesnym języku C++ogólne wytyczne dotyczą preferowania typów podstawowych języka C++, chyba że typ systemu Windows komunikuje pewne dodatkowe znaczenie dotyczące interpretacji wartości.

Więcej informacji

Aby uzyskać więcej informacji na temat systemu typów języka C++, zobacz następujące artykuły.

Typy wartości
Opisuje typy wartości wraz z problemami dotyczącymi ich użycia.

Konwersje typów i bezpieczeństwo typów
Opisuje typowe problemy przy konwersji typu i pokazuje, jak ich unikać.

Zobacz też

Witamy z powrotem w języku C++
Dokumentacja języka C++
Standardowa biblioteka C++