[itk] Számítástechnika kezdőknek

C++ programozás kezdőknek - alapvető típuskonverziók

[2021. június 24.] [ christo161 ]

Az előző tananyagrészekben már volt arról szó, hogy a C++ nyelvben egy változó létrehozásakor meg kell adni, hogy mi legyen a változó típusa, és amíg egy változó létezik, nem változtatható meg a típusa, vagyis ugyanaz marad, amit a létrehozásakor megadtunk.
C++ nyelvben esetleg csak arra van lehetőség, hogy egy adott típusú változó értékét egy nagyjából hasonló típusú változónak adjuk értékül. Az sem biztos, hogy ez egy megszokott értékadással elvégezhető, például szám stringgé, vagy string számmá alakítása C++ nyelvben csak segédfüggvényekkel végezhető el (pl. a Javascript vagy PHP nyelvekben ilyesmire nincs szükség), ezzel szemben például egy valós szám típusú változó értéke értékül adható egy egész szám típusú változónak egy egyszerű értékadással, segédfüggvények használata nélkül.
Ebben a tananyagrészben főként az alaptípusok közötti konverziókról lesz szó, kivéve a logikai (bool típusú) konverziókat, mert azt a logikai változók tananyagrészben tárgyaljuk.
Ebben a tananyagrészben nem lesz szó string-szám, szám-string konverziókról, dynamic_cast-ról, polimorfizmusról, const_castról, reinterpret_cast-ról.

Előző tananyagrész: alaptípusok jellemzői
Következő tananyagrész: elágazások, logikai kifejezések

Tartalom

Alapvető tudnivalók

Programozási nyelvek típusrendszere

A C++ egy statikusan típusos- és erősen típusos programozási nyelv.

A statikusan típusos programozási nyelvekben fordítási időben ki kell derülnie minden kifejezés és részkifejezés típusának.
Például ha definiálok (létrehozok) egy változót, akkor a definícióban vagy szerepelnie kell a típusának (pl. int example;), vagy ha auto kulcsszót használok, akkor a kezdőérték típusából következteti ki a fordító (pl. az auto example = 3.14; utasítás esetén az example nevű változó double típusú lesz).
Például ha leírok egy szám literált, mondjuk azt, hogy 128, akkor az int típusú, ha mögéírok egy L betűt (128L), akkor long int típusú, ha szerepel benne tizedespont (128.0), akkor double típusú, ha szerepel benne tizedespont, és mögéírok egy L betűt (128.0L), akkor lond double típusú... satöbbi.

Az erősen (vagy szigorúan) típusos programozási nyelvekben az adat típusa meghatározza az adattal végezhető műveleteket is (például stringeket nem lehet szorozni/osztani, akkor sem, ha számokat tartalmaznak), valamint ezekre a programozási nyelvekre jellemző, hogy egy változó típusa nem változhat meg.

Léteznek dinamikusan típusos és gyengén típusos programozási nyelvek is, például a Javascript vagy a PHP. Ezekben a nyelvekben a változók létrehozásakor nem kell megadni a változók típusát, illetve a program futása közben egy változóban tárolhatóak különböző típusú értékek is.

Abból, hogy egy nyelv statikusan típusos, nem következik, hogy erősen típusos is vagy fordítva. Például a Python dinamikusan típusos és erősen típusos.

Implicit és explicit típuskonverzió

Implicit típuskonverzió

Olyan típuskonverzió amire vonatkozóan a forráskódban nem szerepel a típuskonverzió megvalósításához használható kifejezés vagy utasítás (pl. static_cast operátor). Az implicit típuskonverziót szokták nevezni automatikus konverziónak is.
Előfordulhat, hogy a programozó leír egy kifejezést/utasítást, és nincs tisztában vele, hogy ott egy implicit típuskonverzió történik. Ez akár jártasabb programozókkal is megeshet, és akár hibaforrás is lehet, ezért érdemes tudni, hogy milyen esetekben történhet implicit típuskonverzió, és javasolt arra törekedni, hogy minél kevesebb implicit típuskonverzió legyen a programunk forráskódjában.

Értékadás esetén

Kézenfekvő példa, ha egy változónak egy eltérő típusú változó értékét adjuk.

//implicit type conversion example
//int to double
#include <iostream> int main() { int example_1 = 5; double example_2 = example_1; std::cout << example_1 / 2 << '\n'; std::cout << example_2 / 2 << '\n'; }

Ekkor az example_1 nevű változó értéke int típusról át lesz konvertálva double típusúvá, anélkül, hogy erre vonatkozó kifejezés/utasítás szerepelne a kódban. Az example_1 változó természetesen marad int típusú, csak az aktuális értéke lesz átkonvertálva double típusúvá, és lesz értékül adva az example_2 változónak.
Noha ezt követően mindkét változó értéke 5 lesz, nem egészen ugyanazt jelenti, ha ez az érték int vagy double típusú. Tipikusan például az osztás esetén jól látható az eltérés, amit a két utolsó utasítás szemléltet.

Nem csak alaptípusú változók között fordulhat elő implicit típuskonverzió. Ebben a példában egy karaktertömb (vagy úgynevezett C stílusú string) értékét adjuk értékül egy std::stringnek.
(A karaktertömbökről csak a tömbökről szóló tananyagrészt követően lesz részletesebben szó.)

//implicit type conversion example
//C string to std::string
#include <iostream>
#include <string>

int main() {
  char char_array_example[] = "Hello World!";
  std::string string_example = char_array_example;

  std::cout << string_example << '\n';
}

Egyes programozási nyelvekkel (pl. Javascript, PHP) ellentétben a C++ nyelvben implicit típuskonverzióval nem tudunk stringet egy alaptípusú változónak (vagy fordítva) értékül adni.

Eltérő típusú részkifejezések esetén

Általában igaz az, hogyha egy kifejezésben két különböző típusú részkifejezés szerepel, akkor a szűkebb tartományon értelmezett típusú részkifejezés át lesz konvertálva a bővebb tartományon értelmezett típusra.

Például ha egy kifejezésben van egy int típusú és egy double típusú részkifejezés, akkor az int típusú részkifejezés double típusúvá konvertálódik.
Erre jó példa a valós osztás. Az 5.0/2 kifejezésben az 5.0 double típusú, a 2 pedig int típusú. A kifejezés kiértékelésekor (fordítási időben) a 2 át lesz konvertálva double típusúvá. Arról, hogy a kifejezés eredménye double típusú, könnyen meggyőződhetünk, ha kiíratjuk az eredményt, hiszen a 2.5 érték nyilván nem lehet int típusú:

std::cout << 5.0/2 << '\n';

Vannak olyan esetek, amikor az implicit típuskonverzió nem túl logikus, például az unsigned int és int típusú részkifejezést tartalmazó kifejezések kiértékelése esetén. Ekkor ugyanis az int konvertálódik unsigned intté, ami hibát okozhat, ha az int típusú részkifejezés negatív értékű.

Amit még érdemes lehet tudni, hogy az intnél szűkebb tartományon értelmezett típusok, amikben technikailag egész számokat tárolunk (bool, char, short int) sok esetben intté lesznek konvertálva. Ezt angolul integral promotionnek nevezzük.

Ebben a példában jól látható, hogy az eredmény int típusú, hiszen egész számként íródik ki.

//integral promotion example
//bool, char to int
#include <iostream>

int main() {
  //the result printed as int
  std::cout << false + true + '\n' << '\n';
  std::cout << '\0' + '0' << '\n';
}

Valójában elég csak egy + jelet írni egy karakter típusú érték elé, és ettől int típusúvá konvertálódik, bár ehelyett talán érdemesebb explicit típuskonverziót használni, mert aki olvassa a kódot, nem biztos, hogy könnyen rá fog jönni, hogy a + operátor a típuskonverziót szolgálja:

//integram promotion example
//char to int

int main() {
  char char_example = '\t';

  std::cout << +char_example << '\n';
  std::cout << +'\r' << '\n';
}

Ha azt szeretnénk megtudni, hogy a short int mikor konvertálódik intté, akkor például a typeid operátort használhatjuk, ha beincludeoljuk a C++ standard library typeinfo header fájlját.

//integral promotion example
//short int to int
#include <iostream>
#include <typeinfo>

int main() {
  short int short_example1 = 10;
  short int short_example2 = 20;
  std::cout << "type of short int: \n" << typeid( short_example1 ).name() << '\n';
  std::cout << "type of -(short int): \n" << typeid ( -short_example1 ).name() << '\n';
  std::cout << "type of short int + short int: \n" << typeid( short_example1 + short_example2 ).name() << '\n';
}

Ha egy kifejezésben különböző alaptípusú részkifejezések találhatóak, akkor azok közül mindig olyan típusúra konvertálódik a többi nem ugyanolyan típusú, amelyik ebben a felsorolásban a legfelül szerepel:

  • long double
  • double
  • float
  • unsigned long long
  • long long
  • unsigned long
  • long
  • unsigned int
  • int

Természetesen nem csak alaptípusok között lehet implicit konverzió. Tipikusan például string literálok és std::stringek között. Ha string literál és std::string szerepel egy kifejezésben, akkor a string literál std::string típusúvá konvertálódik:

//implicit conversion example
//string literal to std::string
#include <iostream>
#include <typeinfo>
#include <string>

int main() {
  std::cout << "type of string literal: \n" << typeid( "str_literal" ).name() << '\n';
  std::cout << "type of string literal + std::string: \n" << typeid( "str_literal" + std::string("example") ).name() << '\n';
}

Függvényhívás esetén

További példa, ha egy függvény vár valamilyen típusú értéket/változót, de egy eltérő típusú értéket/változót adunk neki.

Például a C++ Standard Library cctype header fájljában deklarált toupper függvény int típust fogad és int típust is ad vissza. Ha átadunk neki egy char típusú értéket, azt automatikusan átalakítja int típusúvá, viszont ha például cout-tal íratjuk ki a toupper által visszaadott eredményt, akkor az int típusúként lesz kiírva (ezzel szemben a cout.put átalakítja char típusúvá).

//implicit type conversion example
#include <iostream>
#include <cctype>

int main() {
  //toupper return type is int, cout prints it as int
  std::cout << (toupper('a'));

  //newline
  std::cout.put('\n');

  //toupper return type is int, cout.put prints it as char
  std::cout.put(toupper('a'));
}

A javaslat az, hogy mindig figyeljünk arra, hogy egy függvény milyen típusú paramétereket vár, és milyen típusú értéket ad vissza. A C++ Standard Library függvényei esetén referenciaoldalakon ezt könnyen megnézhetjük. Például a toupper függvény paramétereinek típusát és visszatérési típusát ezen az oldalon láthatjuk:

Ebből a sorból deríthetjük ki a paraméterek típusát és a visszatérési típust:

int toupper ( int c );

A kerek zárójelek között találhatók a függvény paraméterei (amiket a függvény átvesz, amikkel a függvény dolgozik), ezeknek jelen esetben a neve nem fontos, csak a típusa, és a típusok sorrendje (a toupper függvénynek csak egy paramétere van, így a paraméterek sorrendjét nem lehet eltéveszteni).
A függvény neve előtt lévő típus pedig a függvény által visszaadott eredmény (úgynevezett visszatérési érték) típusa.

Konstruktorhívás esetén

Amikor például egy std::stringnek (vagy egyéb osztály objektumpéldányának) adunk kezdőértéket, konstruktorhívás történik, mely esetben a függvényhívásokhoz hasonlóan szintén történhet konverzió, ami szintén hibalehetőség.
Ebben a példában az első esetben a 40 a második paraméterben megadott karakter darabszámát jelenti, a második esetben viszont karakterré konvertálódik (40-es ascii kódú karakter lesz belőle, és összefűzésre kerül a második paraméterrel.

//std::string constructors example
#include <iostream>
#include <string>

int main() {
  std::string str_example1(40,'-');
  std::string str_example2{40,'-'};

  std::cout << str_example1 << '\n';
  std::cout << str_example2 << '\n';
}

A konstruktorokról részletes tananyagrész fog szólni, de az implicit típuskonverzió esetén mindenképp illik megemlíteni.

Egyéb tananyagok az implicit típuskonverzióval kapcsolatban:

Explicit típuskonverzió

Olyan típuskonverzió amit egy, a forráskódban elhelyezett kifejezéssel/utasítással valósítunk meg, mely kifejezés/utasítás szerepe típuskonverzió megvalósítása (C++ nyelvben például static_cast, dynamic_cast, reinterpret_cast, const_cast).
Ezt a megfogalmazást úgy kell érteni, hogy az implicit típuskonverzió esetén is kifejezés/utasítás okozza a típuskonverziót, de azoknak a kifejezéseknek/utasításoknak (pl. értékadás, függvényhívás) alapvetően nem a típuskonverzió a célja.

A C++ Core Guidelinesban azt javasolják, hogy lehetőleg az explicit típuskonverziót is kerüljük, ennek ellenére mindenképp érdemes tudni a létezéséről és arról, hogyan tudjuk használni, illetve a tananyagban is lesznek olyan bemutató jellegű példák, amikben szerepel.

static_cast

Alaptípusról egy másik alaptípusra történő konvertáláshoz a C++ nyelvben a static_cast operátort használhatjuk. Például ha egy char típusú változó értékét számként szeretnénk kiíratni:

//static_cast example
#include <iostream>

int main() {
  char example1 = '\0';
  char example2 = '\t';
  char example3 = '\n';

  std::cout << "value of example1 converted to int: " << static_cast<int>(example1) << '\n';
  std::cout << "value of example2 converted to int: " << static_cast<int>(example2) << '\n';
  std::cout << "value of example3 converted to int: " << static_cast<int>(example3) << '\n';
}

narrow_cast

A narrow_cast operátorról nem lesz részletesen szó ebben a tananyagban, mivel (a tananyag írásakor) nem a C++ standard library része, de említés szintjén talán érdemes szerepelnie a felsorolásban.
Ha nem használjuk, a semminél talán az is jobb, ha a kérdéses sor elé egy //narrow_cast kommentet írunk, hogyha esetleg más is olvassa a kódot, el tudja dönteni, hogy ez az utasítás szándékos.

Lényegében arra szolgál, hogy ha szándékosan szeretnénk egy bővebb tartományon értelmezett típusú kifejezést egy szűkebb tartományon értelmezett változónak értékül adni, akkor ezt egy külön erre a célra létrehozott operátorral tudjuk jelölni a forráskódban.

//bad
double example_1 = 3.4;
int example_2 = example_1;
//good
double example_1 = 3.4;
int example_2 = narrow_cast<int>(example_1);

C style cast

Az úgynevezett C stílusú castolás (angolul C style cast) a konvertálni kívánt kifejezés előtt szereplő kerekzárójelek között megadott típusnévvel valósítható meg, például:

char example = 'a';
std::cout << (int) example << '\n';

Ennek a használatát C++ nyelvben kerülendőnek tekintik.

Egyéb tananyagok az explicit típuskonverzióról:

Egyéb tananyagok a típuskonverzióval kapcsolatban:

Kapcsolódó tananyagrészek: string-szám, szám-string konverziók, dynamic cast

Előző tananyagrész: alaptípusok jellemzői
Következő tananyagrész: elágazások, logikai kifejezések

A bejegyzés trackback címe:

https://itkezdoknek.blog.hu/api/trackback/id/tr4116458218

Kommentek:

A hozzászólások a vonatkozó jogszabályok  értelmében felhasználói tartalomnak minősülnek, értük a szolgáltatás technikai  üzemeltetője semmilyen felelősséget nem vállal, azokat nem ellenőrzi. Kifogás esetén forduljon a blog szerkesztőjéhez. Részletek a  Felhasználási feltételekben és az adatvédelmi tájékoztatóban.

Nincsenek hozzászólások.
süti beállítások módosítása