10.1 -- Niejawna konwersja typów

Wprowadziliśmy konwersję typów na lekcji 4.12 -- Wprowadzenie do konwersji typów i static_cast. Podsumowując najważniejsze punkty tej lekcji:

  • Proces konwersji danych z jednego typu na inny nazywa się „konwersją typu”.
  • Niejawna konwersja typów jest wykonywana automatycznie przez kompilator, gdy wymagany jest jeden typ danych, ale dostarczany jest inny typ danych.
  • Jawna konwersja typu jest wymagana przy użyciu operatora rzutowania, takiego jak static_cast.
  • Konwersje nie powodują zmiany konwertowanych danych. Zamiast tego proces konwersji wykorzystuje te dane jako dane wejściowe i generuje przekonwertowany wynik.
  • Podczas konwertowania wartości na wartość innego typu proces konwersji tworzy obiekt tymczasowy typu docelowego, w którym przechowywany jest wynik konwersji.

W pierwszej połowie tego rozdziału przyjrzymy się bliżej działaniu konwersji typów. Zaczniemy od niejawnych konwersji w tej lekcji, a jawnych konwersji typu (casting) w następnej lekcji 10.6 -- Jawna konwersja typów (casting) i static_cast. Ponieważ konwersja typów jest stosowana wszędzie, ważne jest pewne zrozumienie tego, co dzieje się pod maską, gdy konieczna jest konwersja. Wiedza ta jest również istotna przy zrozumieniu działania przeciążonych funkcji (funkcji, które mogą mieć taką samą nazwę jak inne funkcje).

Nota autora

W tym rozdziale skupimy się na konwersji wartości na inne typy wartości. Inne typy konwersji omówimy po wprowadzeniu wymaganych tematów (takich jak wskaźniki, referencje, dziedziczenie itp.).

Dlaczego potrzebne są konwersje

Wartość obiektu jest przechowywana jako sekwencja bitów, a typ danych obiektu mówi kompilatorowi, jak zinterpretować te bity na znaczące wartości. Różne typy danych mogą w różny sposób reprezentować „tę samą” wartość. Na przykład wartość całkowita 3 może być przechowywany jako plik binarny 0000 0000 0000 0000 0000 0000 0000 0011, natomiast wartość zmiennoprzecinkowa 3.0 może być przechowywany jako plik binarny 0100 0000 0100 0000 0000 0000 0000 0000.

Co więc się stanie, gdy zrobimy coś takiego?

float f{ 3 }; // initialize floating point variable with int 3

W takim przypadku kompilator nie może po prostu skopiować bitów używanych do reprezentacji int wartości 3 do pamięci przeznaczonej dla float zmienną f. Jeśli tak się stanie, to kiedy f (który ma typ float) została oceniona, bity te będą interpretowane jako a float zamiast inti kto wie co float wartość, z którą skończymy!

Na marginesie…

Poniższy program faktycznie drukuje int wartości 3 jakby to był A float:

#include <iostream>
#include <cstring>

int main()
{
    int n { 3 };                        // here's int value 3
    float f {};                         // here's our float variable
    std::memcpy(&f, &n, sizeof(float)); // copy the bits from n into f
    std::cout << f << '\n';             // print f (containing the bits from n)

    return 0;
}

Daje to następujący wynik:

4.2039e-45

Zamiast tego wartość całkowita 3 należy przeliczyć na równoważną wartość zmiennoprzecinkową 3.0, które można następnie zapisać w przeznaczonej dla nich pamięci f (używając reprezentacji bitowej dla float wartości 3.0) .

Kiedy następuje niejawna konwersja typu

Niejawna konwersja typu (tzw automatyczna konwersja typów lub przymus) jest wykonywane automatycznie przez kompilator, gdy wyrażenie pewnego typu zostanie dostarczone w kontekście, w którym oczekiwany jest inny typ. Zdecydowana większość konwersji typów w C++ to niejawne konwersje typów. Na przykład niejawna konwersja typów ma miejsce we wszystkich następujących przypadkach:

Podczas inicjalizacji (lub przypisania wartości) zmiennej o wartości innego typu danych:

double d{ 3 }; // int value 3 implicitly converted to type double
d = 6; // int value 6 implicitly converted to type double

Gdy typ zwracanej wartości różni się od zadeklarowanego typu zwracanego przez funkcję:

float doSomething()
{
    return 3.0; // double value 3.0 implicitly converted to type float
}

Podczas używania niektórych operatorów binarnych z operandami różnych typów:

double division{ 4.0 / 3 }; // int value 3 implicitly converted to type double

W przypadku użycia wartości innej niż logiczna w instrukcji if:

if (5) // int value 5 implicitly converted to type bool
{
}

Gdy argument przekazany do funkcji jest innego typu niż parametr funkcji:

void doSomething(long l)
{
}

doSomething(3); // int value 3 implicitly converted to type long

Skąd więc kompilator wie, jak przekonwertować wartość na inny typ?

Standardowe konwersje

Jako część języka podstawowego, standard C++ definiuje zbiór reguł konwersji znanych jako „konwersje standardowe”. The standardowe konwersje określ sposób, w jaki różne typy podstawowe (i niektóre typy złożone, w tym tablice, odniesienia, wskaźniki i wyliczenia) są konwertowane na inne typy w tej samej grupie.

Począwszy od C++23, istnieje 14 różnych standardowych konwersji. Można je z grubsza pogrupować w 5 ogólnych kategorii:

KategoriaZnaczenieLink
Promocje liczboweKonwersje małych typów całkowitych na int lub unsigned int oraz float Do double.10.2 -- Promocja zmiennoprzecinkowa i całkowa
Konwersje liczboweInne konwersje całkowe i zmiennoprzecinkowe, które nie są promocjami.10.3 -- Konwersje liczbowe
Konwersje kwalifikacyjneKonwersje, które dodają lub usuń const lub volatile.
Transformacje wartościKonwersje zmieniające kategorię wartości wyrażenia12.2 — Kategorie wartości (lwartości i rwartości)
Konwersja wskaźnikówKonwersje z std::nullptr na typy wskaźników lub typy wskaźników na inne typy wskaźników

Na przykład konwersja wartości int wartość do A float należy do kategorii konwersji numerycznych, więc kompilator, aby wykonać taką konwersję, kompilator musi po prostu zastosować int Do float reguły konwersji numerycznej.

Konwersje liczbowe i promocje liczbowe są najważniejszymi z tych kategorii i omówimy je bardziej szczegółowo w nadchodzących lekcjach.

Dla zaawansowanych czytelników

Oto pełna lista standardowych konwersji:

KategoriaKonwersja standardowaDescriptionRównież Zobacz
Transformacja wartościLwartość na wartośćKonwertuje wyrażenie lwartości na wyrażenie rwartości12.2 — Kategorie wartości (lwartości i rwartości)
Transformacja wartościTablica na wskaźnikKonwertuje tablicę w stylu C na wskaźnik na pierwszy element tablicy (czyli tablicę zanik)17.8 -- Zanikanie tablicy w stylu C
Transformacja wartościFunkcja na wskaźnikKonwertuje funkcję na wskaźnik funkcji20.1 -- Wskaźniki funkcji
Transformacja wartościTymczasowa materializacjaKonwertuje wartość na obiekt tymczasowy
Kwalifikacja konwersjaKwalifikacja konwersjaDodaje lub usuwa const lub volatile z typów
Promocje liczboweIntegracyjne promocjeKonwertuje mniejsze typy całkowite na int lub unsigned int10.2 -- Promocja zmiennoprzecinkowa i całkowa
Promocje liczbowePromocje zmiennoprzecinkoweKonwertuje float Do double10.2 -- Promocja zmiennoprzecinkowa i całkowa
Konwersje liczboweKonwersje całkoweKonwersje całkowe, które nie są całkowe promocje10.3 -- Konwersje liczbowe
Konwersje liczboweKonwersje zmiennoprzecinkoweKonwersje zmiennoprzecinkowe, które nie są promocjami zmiennoprzecinkowymi10.3 -- Konwersje liczbowe
Konwersje liczboweKonwersje całkowo-zmiennoprzecinkoweKonwertuje typy całkowite i zmiennoprzecinkowe10.3 -- Konwersje liczbowe
Konwersje liczboweKonwersje logiczneKonwertuje wyliczenie całkowe, bez zakresu, wskaźnik lub wskaźnik na memver na bool4.10 - Wprowadzenie do instrukcji if
Konwersja wskaźnikówKonwersja wskaźnikówKonwertuje std::nullptr na wskaźnik lub wskaźnik na pusty wskaźnik lub klasę bazową
Konwersja wskaźnikówWskaźnik na element konwersjeKonwertuje std::nullptr na wskaźnik na element
lub wskaźnik na element klasy bazowej na wskaźnik na element klasy pochodnej
Konwersja wskaźnikówKonwersja wskaźników funkcyjnychKonwertuje wskaźnik na funkcję noexcept wskaźnik do funkcji

Konwersja typu może się nie powieść

Gdy zostanie wywołana konwersja typu (niejawnie lub jawnie), kompilator określi, czy może przekonwertować wartość z bieżącego typu na żądany typ. Jeśli uda się znaleźć prawidłową konwersję, kompilator utworzy nową wartość żądanego typu.

Jeśli kompilator nie znajdzie akceptowalnej konwersji, kompilacja zakończy się błędem. Konwersja typów może zakończyć się niepowodzeniem z wielu powodów. Na przykład kompilator może nie wiedzieć, jak przekonwertować wartość między typem oryginalnym a typem żądanym.

Na przykład:

int main()
{
    int x { "14" };

    return 0;
}

Ponieważ nie ma standardowej konwersji z literału ciągu „14” na int, kompilator zgłosi błąd. Na przykład GCC generuje błąd: prog.cc:3:13: error: invalid conversion from 'const char*' to 'int' [-fpermissive].

W innych przypadkach określone funkcje mogą uniemożliwiać niektóre kategorie konwersji. Na przykład:

int x { 3.5 }; // brace-initialization disallows conversions that result in data loss

Nawet jeśli kompilator wie, jak przekonwertować double wartość na int wartość, konwersje zawężające są niedozwolone podczas korzystania z inicjalizacji nawiasów klamrowych.

Istnieją również przypadki, w których kompilator może nie być w stanie określić, która z kilku możliwych konwersji typów jest najlepsza do użycia. Zobaczymy tego przykłady na lekcji 11.3 — Rozwiązywanie przeciążenia funkcji i dopasowania niejednoznaczne.

Pełny zestaw reguł opisujących sposób działania konwersji typów jest zarówno długi, jak i skomplikowany, a w większości przypadków konwersja typów „po prostu działa”. W następnym zestawie lekcji omówimy najważniejsze rzeczy, które musisz wiedzieć o standardowych konwersjach. Jeśli w jakimś nietypowym przypadku wymagane są bardziej szczegółowe informacje, pełne zasady są szczegółowo opisane w technicznej dokumentacji referencyjnej dotyczącej konwersji niejawnych.

Zaczynajmy!

guest
Twój adres e-mail nie zostanie wyświetlony
Znalazłeś błąd? Zostaw komentarz powyżej!
Komentarze związane z poprawkami zostaną usunięte po przetworzeniu, aby pomóc zmniejszyć bałagan. Dziękujemy za pomoc w ulepszaniu witryny dla wszystkich!
Awatary z https://gravatar.com/ są połączone z podanym adresem e-mail.
Powiadamiaj mnie o odpowiedziach:  
205 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze