Typ Unii - Union type

W informatyce , o zjednoczenie to wartość , która może mieć dowolną z kilku przedstawień lub formatów w obrębie tej samej pozycji w pamięci ; która składa się ze zmiennej, która może posiadać taką strukturę danych . Niektóre języki programowania obsługują specjalne typy danych , zwane typami unii , służące do opisywania takich wartości i zmiennych. Innymi słowy, definicja typu unii określa, który z dozwolonych typów pierwotnych może być przechowywany w jego instancjach, np. „liczba zmiennoprzecinkowa czy długa”. W przeciwieństwie do rekordu (lub struktury), który można zdefiniować jako zawierający pływak i liczbę całkowitą; w unii w danym momencie istnieje tylko jedna wartość.

Unię można przedstawić jako fragment pamięci używany do przechowywania zmiennych o różnych typach danych. Po przypisaniu nowej wartości do pola, istniejące dane zostaną nadpisane nowymi danymi. Obszar pamięci przechowujący wartość nie ma wewnętrznego typu (innego niż tylko bajty lub słowa pamięci), ale wartość może być traktowana jako jeden z kilku abstrakcyjnych typów danych , posiadających typ wartości, która została ostatnio zapisana w obszarze pamięci.

W teorii typów związek ma typ sumy ; odpowiada to rozłącznej unii w matematyce.

W zależności od języka i typu wartość unii może być używana w niektórych operacjach, takich jak przypisanie i porównanie pod kątem równości, bez znajomości konkretnego typu. Inne operacje mogą wymagać tej wiedzy, czy to przez niektóre informacje zewnętrzne, czy przez użycie związku oznaczonego .

Związki nieoznaczone

Ze względu na ograniczenia ich użycia, nieoznakowane unie są generalnie udostępniane tylko w językach bez typu lub w sposób niepewny typu (jak w C ). Mają tę zaletę, że nie wymagają miejsca na przechowywanie znacznika typu danych, w porównaniu z prostymi uniami znakowanymi.

Nazwa „unia” wywodzi się z formalnej definicji typu. Jeśli typ jest uważany za zbiór wszystkich wartości, które ten typ może przyjąć, typ unii jest po prostu matematyczną unią tworzących go typów, ponieważ może przyjąć dowolną wartość, jaką może przyjąć dowolne z jego pól. Ponadto, ponieważ suma matematyczna odrzuca duplikaty, jeśli więcej niż jedno pole unii może przyjąć jedną wspólną wartość, nie można na podstawie samej wartości odróżnić, które pole zostało zapisane jako ostatnie.

Jednak jedną z użytecznych funkcji programistycznych unii jest mapowanie mniejszych elementów danych na większe w celu łatwiejszej manipulacji. Struktura danych składająca się na przykład z 4 bajtów i 32-bitowej liczby całkowitej może tworzyć unię z 64-bitową liczbą całkowitą bez znaku, a tym samym być łatwiej dostępna do celów porównawczych itp.

Związki w różnych językach programowania

ALGOL 68

ALGOL 68 ma oznakowane związki i używa klauzuli case do rozróżniania i wyodrębniania typu składowego w czasie wykonywania. Suma zawierająca inną sumę jest traktowana jako zbiór wszystkich jej możliwości składowych.

Składnia typu unii C/C++ i pojęcie rzutowań pochodzi z ALGOL 68, jednak w formie nieotagowanej.

C/C++

W C i C++ nieoznakowane związki są wyrażane prawie dokładnie tak samo jak struktury ( structs ), z tą różnicą, że każdy element członkowski danych zaczyna się w tej samej lokalizacji w pamięci. Elementy danych, podobnie jak w strukturach, nie muszą być wartościami pierwotnymi, a w rzeczywistości mogą być strukturami lub nawet innymi związkami. C++ (od C++11 ) pozwala również, aby element członkowski danych był dowolnym typem, który ma pełnoprawny konstruktor/destruktor i/lub konstruktor kopii lub nietrywialny operator przypisania kopii. Na przykład, możliwe jest, aby mieć standard C ++ ciąg jako członek Unii.

Podobnie jak struktura, wszyscy członkowie związku są domyślnie publiczni. Słowa kluczowe private, publici protectedmogą być używane w strukturze lub Unii w dokładnie taki sam sposób, w jaki są używane w klasie do definiowania prywatnego, publicznego i chronionego dostępu do elementów członkowskich.

Podstawowym zastosowaniem unii jest umożliwienie dostępu do wspólnej lokalizacji za pomocą różnych typów danych, na przykład sprzętowego dostępu do wejścia/wyjścia, współdzielenia pól bitowych i słów lub określania typu . Związki mogą również zapewnić polimorfizm niskiego poziomu . Jednak nie ma sprawdzania typów, więc to od programisty zależy, czy dostęp do odpowiednich pól jest możliwy w różnych kontekstach. Odpowiednie pole zmiennej unii jest zwykle określane przez stan innych zmiennych, prawdopodobnie w otaczającej strukturze.

Jeden wspólny idiom programowania w C używa unii do wykonywania tego, co C++ wywołuje reinterpret_cast , przypisując je do jednego pola unii i odczytując je z innego, jak to się dzieje w kodzie, który zależy od surowej reprezentacji wartości. Praktycznym przykładem jest metoda obliczania pierwiastków kwadratowych przy użyciu reprezentacji IEEE . Nie jest to jednak ogólnie bezpieczne korzystanie ze związków.

Specyfikatory struktury i unii mają tę samą formę. [ . . . ] Wielkość związku jest wystarczająca, aby pomieścić największych jego członków. Wartość co najwyżej jednego z elementów członkowskich może być przechowywana w obiekcie Union w dowolnym momencie. Wskaźnik do obiektu unii, odpowiednio przekonwertowany, wskazuje na każdego z jego elementów (lub jeśli element jest polem bitowym, to na jednostkę, w której się znajduje) i odwrotnie.

—  ANSI/ISO 9899:1990 (norma ANSI C) Sekcja 6.5.2.1

Anonimowy związek

W C++, C11 i jako niestandardowe rozszerzenie w wielu kompilatorach związki mogą być również anonimowe. Ich członków danych nie trzeba odwoływać się, zamiast tego uzyskuje się bezpośredni dostęp. Mają pewne ograniczenia w przeciwieństwie do tradycyjnych unii: w C11 muszą należeć do innej struktury lub unii, a w C++ nie mogą mieć metod ani specyfikatorów dostępu.

Samo pominięcie części składni dotyczącej nazwy klasy nie powoduje, że unia staje się unią anonimową. Aby związek kwalifikował się jako związek anonimowy, deklaracja nie może deklarować obiektu. Przykład:

#include <iostream>
#include <cstdint>

int main() {
   union {
      float f;
      std::uint32_t d; // Assumes float is 32 bits wide
   };

   f = 3.14f;
   std::cout << "Hexadecimal representation of 3.14f:" 
             << std::hex << d << '\n';
}

Anonimowe związki są również przydatne w structdefinicjach języka C, aby zapewnić poczucie przestrzeni nazw.

Przejrzysty związek

W kompilatorach uniksopodobnych, takich jak GCC, Clang i IBM XL C for AIX, transparent_unionatrybut jest dostępny dla typów unii. Typy zawarte w unii można przekonwertować w sposób przezroczysty na sam typ unii w wywołaniu funkcji, pod warunkiem, że wszystkie typy mają ten sam rozmiar. Jest przeznaczony głównie do funkcji z wieloma interfejsami parametrów, co jest wymagane przez wczesne rozszerzenia Uniksa i późniejszą restandaryzację.

COBOL

W języku COBOL elementy danych sumy są definiowane na dwa sposoby. Pierwszy używa słowa kluczowego RENAMES (66 level), które skutecznie mapuje drugi element danych alfanumerycznych na górze tej samej lokalizacji pamięci, co poprzedni element danych. W poniższym przykładowym kodzie element danych PERSON-REC jest zdefiniowany jako grupa zawierająca inną grupę i numeryczny element danych. PERSON-DATA jest zdefiniowany jako alfanumeryczny element danych, który zmienia nazwę PERSON-REC , traktując dalsze bajty danych jako dane znakowe.

  01  PERSON-REC.
      05  PERSON-NAME.
          10  PERSON-NAME-LAST    PIC X(12).
          10  PERSON-NAME-FIRST   PIC X(16).
          10  PERSON-NAME-MID     PIC X.
      05  PERSON-ID               PIC 9(9) PACKED-DECIMAL.
  
  01  PERSON-DATA                 RENAMES PERSON-REC.

Drugim sposobem zdefiniowania typu unii jest użycie słowa kluczowego REDEFINES . W poniższym przykładowym kodzie element danych NUMER-WER jest zdefiniowany jako 2-bajtowa binarna liczba całkowita zawierająca numer wersji. Drugi element danych VERS-BYTES jest zdefiniowany jako dwuznakowa zmienna alfanumeryczna. Ponieważ drugi element jest przedefiniowany w stosunku do pierwszego elementu, oba elementy mają ten sam adres w pamięci, a zatem współdzielą te same podstawowe bajty danych. Pierwsza pozycja interpretuje dwa bajty danych jako wartość binarną, podczas gdy druga pozycja interpretuje bajty jako wartości znakowe.

  01  VERS-INFO.
      05  VERS-NUM        PIC S9(4) COMP.
      05  VERS-BYTES      PIC X(2)
                          REDEFINES VERS-NUM

Pascal

W Pascalu istnieją dwa sposoby tworzenia unii. Jednym z nich jest standardowy sposób przechodzenia przez rekord wariantu. Drugi to niestandardowy sposób deklarowania zmiennej jako bezwzględnej, co oznacza, że ​​jest ona umieszczona w tym samym miejscu pamięci co inna zmienna lub pod adresem bezwzględnym. Chociaż wszystkie kompilatory Pascala obsługują rekordy wariantów, tylko niektóre obsługują zmienne bezwzględne.

Na potrzeby tego przykładu wszystkie typy liczb całkowitych są następujące: bajt to 8 bitów, słowo to 16 bitów, a liczba całkowita to 32 bity.

Poniższy przykład przedstawia niestandardową formę bezwzględną:

VAR
    A: Integer;
    B: Array[1..4] of Byte absolute A;
    C: Integer absolute 0;

W pierwszym przykładzie każdy z elementów tablicy B odwzorowuje jeden z określonych bajtów zmiennej A. W drugim przykładzie zmienna C jest przypisana do dokładnego adresu maszyny 0.

W poniższym przykładzie rekord ma warianty, z których niektóre mają tę samą lokalizację, co inne:

TYPE
     TSystemTime = record
       Year, Month, DayOfWeek, Day : word;
       Hour, Minute, Second, MilliSecond: word;
     end;  
     TPerson = RECORD
        FirstName, Lastname: String;
        Birthdate: TSystemTime;
        Case isPregnant: Boolean of 
           true: (DateDue:TSystemTime);
           false: (isPlanningPregnancy: Boolean);
        END;

PL/I

W języku PL/I pierwotnym terminem dla unii był cell , który jest nadal akceptowany jako synonim unii przez kilka kompilatorów. Deklaracja unii jest podobna do definicji struktury, gdzie elementy znajdujące się na tym samym poziomie w deklaracji unii zajmują to samo miejsce. Elementy unii mogą mieć dowolny typ danych, w tym struktury i tablice. Tutaj vers_num i vers_bytes zajmują te same lokalizacje pamięci.

  1  vers_info         union,
     5 vers_num        fixed binary,
     5 vers_bytes      pic '(2)A';

Alternatywą dla deklaracji unii jest atrybut DEFINED, który umożliwia alternatywne deklaracje przechowywania, jednak typy danych zmiennych bazowych i zdefiniowanych muszą być zgodne.

Składnia i przykład

C/C++

W C i C++ składnia to:

union <name>
{
    <datatype>  <1st variable name>;
    <datatype>  <2nd variable name>;
    .
    .
    .
    <datatype>  <nth variable name>;
} <union variable name>;

Struktura może również należeć do unii, jak pokazano w poniższym przykładzie:

union name1
{
    struct name2
    {  
        int     a;
        float   b;
        char    c;
    } svar;
    int     d;
} uvar;

Ten przykład definiuje zmienną uvarjako unię (oznaczoną jako name1), która zawiera dwa elementy członkowskie, strukturę (oznaczoną jako name2) o nazwie svar(która z kolei zawiera trzy elementy członkowskie) i zmienną całkowitą o nazwie d.

Związki mogą występować w strukturach i tablicach i na odwrót:

struct
{  
    int flags;
    char *name;
    int utype;
    union {
        int ival;
        float fval;
        char *sval;
    } u;
} symtab[NSYM];

Liczba ival jest określana jako , symtab[i].u.ivala pierwszy znak łańcucha sval przez *symtab[i].u.svallub symtab[i].u.sval[0].

PHP

Typy Unii zostały wprowadzone w PHP 8.0. Wartości są niejawnie "oznaczone" typem przez język i mogą być pobierane przez "gettype()".

class Example
{
    private int|float $foo;

    public function squareAndAdd(float|int $bar): int|float
    {
        return $bar ** 2 + $this->foo;
    }
}

Pyton

Wsparcie dla pisania zostało wprowadzone w Pythonie 3.5. Nowa składnia typów unii została wprowadzona w Pythonie 3.10.

class Example:
    foo = 0

    def square_and_add(self, bar: int | float) -> int | float:
        return bar ** 2 + self.foo

Maszynopis

Typy Unii są obsługiwane w TypeScript. Wartości są niejawnie „oznaczone” typem przez język i mogą być pobierane przez „typeof()”.

function successor(n: number | bigint): number | bigint {
    return ++n
}

Różnica między związkiem a strukturą

Unia to klasa, której wszystkie elementy członkowskie danych są mapowane na ten sam adres w jej obiekcie. Rozmiar obiektu unii jest zatem rozmiarem jego największego elementu członkowskiego danych.

W strukturze wszystkie jej składowe danych są przechowywane w ciągłych lokalizacjach pamięci. Rozmiar obiektu struktury jest zatem rozmiarem sumy wszystkich jego członków danych.

Ten wzrost wydajności miejsca, choć cenny w pewnych okolicznościach, wiąże się z dużymi kosztami bezpieczeństwa: logika programu musi zapewniać, że odczytuje tylko pole ostatnio zapisane na wszystkich możliwych ścieżkach wykonania. Wyjątkiem jest sytuacja, w której do konwersji typów używane są unii : w tym przypadku określone pole jest zapisywane, a następnie odczytywane pole jest celowo inne.

Jako przykład ilustrujący ten punkt, deklaracja

struct foo { int a; float b; }

definiuje obiekt danych z dwoma elementami zajmującymi kolejne miejsca w pamięci:

                ┌─────┬─────┐
           foo  │  a  │  b  │
                └─────┴─────┘
                   ↑     ↑
Memory address:  0150  0154

Natomiast deklaracja

union bar { int a; float b; }

definiuje obiekt danych z dwoma elementami zajmującymi tę samą lokalizację w pamięci:

                ┌─────┐
           bar  │  a  │
                │  b  │
                └─────┘
                   ↑
Memory address:  0150

Struktury są używane, gdy „obiekt” składa się z innych obiektów, takich jak obiekt punktowy składający się z dwóch liczb całkowitych, będących współrzędnymi x i y:

typedef struct {
    int x;           // x and y are separate
    int y;
} tPoint;

Związki są zwykle używane w sytuacji, gdy obiekt może być jedną z wielu rzeczy, ale tylko jedną naraz, na przykład system pamięci bez typu:

typedef enum { STR, INT } tType;
typedef struct {
    tType typ;          // typ is separate.
    union {
        int ival;       // ival and sval occupy same memory.
        char *sval;
    };
} tVal;

Zobacz też

Bibliografia

Zewnętrzne linki