Tehát már tudjuk, hogyan deklaráljunk, definiáljunk és használjunk függvényeket a programokban. Ebben a fejezetben a speciális formájukról – a túlterhelt funkciókról – lesz szó. Két függvényt túlterheltnek nevezünk, ha azonos a nevük, ugyanabban a hatókörben vannak deklarálva, de a formális paraméterek listája eltérő. Elmagyarázzuk, hogyan deklarálják ezeket a függvényeket, és miért hasznosak. Majd megfontoljuk a megoldásuk kérdését, i.e. arról, hogy a több túlterhelt függvény közül melyiket hívják meg a program végrehajtása során. Ez a probléma az egyik legnehezebb a C++-ban. Aki szeretne belemenni a részletekbe, annak érdekes lesz elolvasnia a fejezet végén található két részt, amelyek részletesebben foglalkoznak az argumentumtípus-konverzió és a túlterhelés-feloldás témakörével.

9.1. Túlterhelt függvénydeklarációk

Most, miután megtanultuk a függvények deklarálását, meghatározását és használatát a programokban, megismerkedünk túlterhelés egy másik szempont a C++-ban. A túlterhelés lehetővé teszi több, azonos nevű függvény használatát, amelyek hasonló műveleteket hajtanak végre különböző típusú argumentumokkal.
Ön már kihasználta az előre meghatározott túlterhelt funkció előnyeit. Például a kifejezés értékeléséhez

az egész összeadás műveletét hívjuk, míg a kifejezés kiértékelését

1.0 + 3.0

lebegőpontos összeadást végez. Ennek vagy annak a műveletnek a kiválasztása a felhasználó számára észrevétlenül történik. Az összeadás operátor túlterhelt a különböző típusú operandusok befogadásához. Nem a programozó, hanem a fordító feladata, hogy felismerje a kontextust és alkalmazza az operandusok típusának megfelelő műveletet.
Ebben a fejezetben bemutatjuk, hogyan határozhatja meg saját túlterhelt funkcióit.

9.1.1. Miért kell túlterhelni a függvény nevét?

A beépített összeadási művelethez hasonlóan szükségünk lehet egy olyan funkciókészletre, amely ugyanazt a műveletet hajtja végre, de különböző típusú paramétereken. Tegyük fel, hogy olyan függvényeket akarunk definiálni, amelyek az átadott paraméterértékek közül a legnagyobbat adják vissza. Ha nem lenne túlterhelés, minden ilyen függvénynek egyedi nevet kellene adni. Például a max() függvénycsalád így nézhet ki:

int i_max(int, int); int vi_max(const vektor &); int matrix_max(const mátrix &);

Mindazonáltal mindegyik ugyanazt csinálja: a paraméterértékek közül a legnagyobbat adják vissza. A felhasználó szemszögéből itt csak egy művelet van - a maximum kiszámítása és megvalósításának részletei kevéssé érdekesek.
A megfigyelt lexikai összetettség a programozási környezet korlátait tükrözi: minden, ugyanabban a hatókörben előforduló névnek egyedi entitásra kell utalnia (objektum, függvény, osztály stb.). Egy ilyen korlátozás a gyakorlatban bizonyos kellemetlenségeket okoz, mivel a programozónak emlékeznie kell vagy valahogy meg kell találnia az összes nevet. A funkció túlterhelése segít ezen a problémán.
Túlterhelést használva a programozó ilyesmit írhat:

int ix = max(j, k); vektor vec; //... int iy = max(vec);

Ez a megközelítés számos helyzetben rendkívül hasznosnak bizonyul.

9.1.2. A függvénynév túlterhelése

A C++ nyelvben két vagy több függvény is kaphat azonos nevet, feltéve, hogy a paraméterlistájuk vagy a paraméterek számában, vagy típusában különbözik. NÁL NÉL ezt a példát túlterhelt max() függvényt deklarálunk:

intmax(int, int); int max(const vektor &); int max(const mátrix &);

Minden túlterhelt deklarációhoz külön max() függvénydefiníció szükséges, megfelelő paraméterlistával.
Ha egy függvénynév többször van deklarálva valamilyen hatókörben, akkor a második (és az azt követő) deklarációt a fordító a következőképpen értelmezi:

  • ha két függvény paraméterlistája eltér a paraméterek számában vagy típusában, akkor a függvények túlterheltnek minősülnek: // túlterhelt függvények void print(const string &); void print(vektor &);
  • ha két függvény deklarációjában a visszatérési típus és a paraméterlista megegyezik, akkor a második deklarációt ismétlődőnek tekintjük: // ugyanazon függvény deklarációi void print(const string &str); void print(const string &); A paraméterek neveit nem vesszük figyelembe a deklarációk összehasonlításakor;
    ha két függvény paraméterlistája megegyezik, de a visszatérési típusok eltérőek, akkor a második deklarációt hibásnak tekinti (inkonzisztens az elsővel), és a fordító hibaként jelzi: unsigned int max(int ​​i1, int i2); int max(int ​​i1, int i2);
    // hiba: csak a típusok különböznek
    // értékeket ad vissza

A túlterhelt függvények nem csak visszatérési típusukban különbözhetnek egymástól; ha két függvény paraméterlistája csak az alapértelmezett argumentumértékekben tér el, akkor a második deklarációt megismételtnek tekintjük:

// azonos függvény deklarációi int max (int *ia, int sz); int max (int *ia, int = 10);

A typedef kulcsszó alternatív nevet hoz létre egy meglévőhöz adattípus, nem jön létre új típus. Ezért, ha két függvény paraméterlistája csak abban különbözik, hogy az egyik typedef-et használ, a másik pedig olyan típust, amelynek typedef álneve, akkor a listák azonosnak minősülnek, mint a calc() függvény következő két deklarációjában. Ebben az esetben a második deklaráció fordítási hibát ad, mert a visszatérési érték eltér a korábban megadott értéktől:

// typedef nem vezet be új típusú typedef double DOLLAR; // hiba: ugyanazok a paraméterlisták, de különböző // visszatérési típusok extern DOLLAR calc(DOLLAR); extern int calc(double);

A const vagy a volatile specifikátorokat nem veszik figyelembe egy ilyen összehasonlításnál. Így a következő két nyilatkozatot azonosnak tekintjük:

// deklarálja ugyanazt a függvényt void f(int); void f(const int);

A const specifikátor csak a függvénydefiníción belül fontos: azt jelzi, hogy tilos a paraméter értékének megváltoztatása a függvénytörzsben. Az érték által átadott argumentum azonban ugyanúgy használható a függvény törzsében, mint egy szabályos aktivált változó: a függvényen kívül a változások nem láthatók. (Az argumentumok átadásának módszereit, különösen az érték átadását a 7.3. szakasz tárgyalja.) Ha egy const specifikátort adunk egy érték által átadott paraméterhez, az nincs hatással annak értelmezésére. Bármely int típusú érték átadható egy f(int)-ként deklarált függvénynek, akárcsak az f(const int) függvény. Mivel mindkettő ugyanazt az argumentumértéket veszi fel, a fenti deklarációk nem tekinthetők túlterheltnek. f() így definiálható

Érvénytelen f(int i) ( )

Érvénytelen f(konst int i) ( )

E két definíció jelenléte egy programban hiba, mivel ugyanaz a függvény kétszer van definiálva.
Ha azonban egy const vagy volatile specifikátort alkalmazunk egy mutató vagy referencia típusú paraméterre, akkor azt figyelembe veszi a deklarációk összehasonlításakor.

// különböző függvényeket void deklarálunk f(int*); void f(const int*); // és itt különböző függvények vannak deklarálva
void f(int&);
void f(const int&);

9.1.3. Mikor ne terhelje túl a függvény nevét

Milyen esetekben nem előnyös a név túlterhelés? Például, ha a függvényekhez különböző neveket rendelünk, akkor a program könnyebben olvasható. Íme néhány példa. A következő funkciók ugyanazon absztrakt dátumtípuson működnek. Elsősorban jó jelöltek a túlterhelésre:

void setDate(Dátum&, int, int, int); Dátum &convertDate(const string &); void printDate(const Date&);

Ezek a függvények ugyanazon az adattípuson, a Dátum osztályon működnek, de szemantikailag különböző műveleteket hajtanak végre. Ebben az esetben a különböző nevek használatához kapcsolódó lexikai bonyolultság abból a konvencióból fakad, amelyet a programozó alkalmaz, hogy az adattípuson egy műveletsort biztosítson, és a függvényeket e műveletek szemantikájának megfelelően nevezze el. Igaz, a C++ osztályú mechanizmus redundánssá teszi az ilyen konvenciót. Az ilyen függvényeket a Date osztály tagjaivá kell tennünk, de hagyjunk különböző neveket, amelyek tükrözik a művelet jelentését:

#beleértve class Date ( public: set(int, int, int); Date& convert(const string &); void print(); // ...
};

Vegyünk egy másik példát. A következő öt Képernyőtag-függvény különféle műveleteket hajt végre a képernyőkurzoron, amely ugyanannak az osztálynak a tagja. Ésszerűnek tűnhet ezeket a függvényeket a move() általános név alatt túlterhelni:

Screen& moveHome(); Screen& moveAbs(int, int); Screen& moveRel(int, int, char *irány); Screen& moveX(int); Screen& moveY(int);

Az utolsó két függvényt azonban nem lehet túlterhelni, mivel ugyanazokat a paraméterlistákat tartalmazzák. Az aláírás egyedivé tételéhez egyesítsük őket egyetlen funkcióba:

// függvény, amely egyesíti a moveX() és a moveY() függvényeket Screen& move(int, char xy);

Mostantól minden függvénynek más paraméterlistája van, így a move() név alatt túlterhelhetők. Ezt azonban nem szabad megtenni: a különböző nevek olyan információkat hordoznak, amelyek nélkül a program nehezebb lesz megérteni. Például az e funkciók által végrehajtott kurzormozgatási műveletek eltérőek. Például a moveHome() speciális fajta balra mozgatást hajt végre felső sarok képernyő. Az alábbi két hívás közül melyik a felhasználóbarátabb és könnyebben megjegyezhető?

// melyik hívás világosabb? myScreen.home(); // erre gondolunk! myScreen.move();

Bizonyos esetekben nem kell túlterhelnie a függvény nevét, vagy nem kell különböző neveket hozzárendelnie: az alapértelmezett argumentumértékek használatával több függvényt is egyesíthet egybe. Például a kurzorvezérlő funkciókat

MoveAbs(int, int); moveAbs(int, int, char*);

eltérnek egy harmadik char* típusú paraméter jelenlétében. Ha a megvalósításuk hasonló, és ésszerű alapértelmezett érték található a harmadik argumentumhoz, akkor mindkét függvény helyettesíthető eggyel. Ebben az esetben egy 0 értékű mutató alkalmas az alapértelmezett érték szerepére:

Move(int, int, char* = 0);

Használjon bizonyos szolgáltatásokat, ha az alkalmazáslogika megköveteli. Nem szükséges túlterhelt függvényeket beletenni egy programba csak azért, mert léteznek.

9.1.4. Túlterhelés és terjedelem A

Minden túlterhelt funkció ugyanabban a hatókörben van deklarálva. Például egy lokálisan deklarált függvény nem terhel túl, hanem egyszerűen elrejti a globálisat:

#beleértve void print(const string &); voidprint(dupla); // túlterheli a print() void fooBar(int ival)
{
// külön hatókör: elrejti a print() mindkét megvalósítását
extern void print(int); // hiba: a print(const string &) nem látható ezen a területen
print("Érték: ");
print(ival); // helyes: a print(int) látható
}

Mivel minden osztály meghatározza a saját hatókörét, a két különböző osztályba tartozó függvények nem terhelik túl egymást. (Az osztálytagfüggvényekkel a 13. fejezet foglalkozik. Az osztálytagfüggvények túlterhelésének megoldásával a 15. fejezet foglalkozik.)
Az ilyen függvények deklarálása a névtérben is megengedett. Mindegyikhez tartozik saját hatókör is, hogy a különböző hatókörben deklarált függvények ne terheljék túl egymást. Például:

#beleértve névtér IBM ( extern void print(const string &); extern void print(double); // túlterheli a print() ) névtér Disney ( // külön hatókör: // nem terheli túl a print() függvényt az IBM névtérből extern void nyomtatás (int); )

A deklarációk és direktívák használata segít a névtértagok elérhetővé tételében más hatókörökben. Ezek a mechanizmusok némileg befolyásolják a túlterhelt függvények deklarációit. (A deklarációk és a direktívák használatáról a 8.6. fejezetben volt szó.)

Hogyan befolyásolja a deklaráció használata a funkció túlterhelését? Emlékezzünk vissza, hogy bevezet egy álnevet egy névtértaghoz abban a hatókörben, amelyben a deklaráció előfordul. Mit csinálnak az ilyen deklarációk a következő programban?

Névtér libs_R_us ( int max(int, int); int max(double, double); extern void print(int);
külső üres nyomtatás (dupla);
) // nyilatkozatok használatával
a libs_R_us::max;
a libs_R_us::print(double); // hiba void func()
{
max(87, 65); // meghívja a libs_R_us::max(int, int)
max(35,5; 76,6); // meghívja a libs_R_us::max(double, double)

Az első használó deklaráció mindkét libs_R_us::max függvényt a globális hatókörbe hozza. Most a max() függvények bármelyike ​​meghívható a func()-on belül. Az argumentumok típusai határozzák meg, hogy melyik függvényt kell meghívni. A második használó deklaráció egy hiba: nem lehet paraméterlistája. A libs_R_us::print() függvény csak így van deklarálva:

A libs_R_us::print;

A use deklaráció mindig elérhetővé tesz minden túlterhelt függvényt a megadott néven. Ez a korlátozás biztosítja, hogy a libs_R_us névtér interfész ne sérüljön. Egyértelmű, hogy hívás esetén

Nyomtatás(88);

a névtér szerzője a libs_R_us::print(int) függvény meghívását várja. Ha megengedi a felhasználónak, hogy a több túlterhelt függvény közül csak egyet vegyen fel a hatókörbe, akkor a program viselkedése kiszámíthatatlanná válik.
Mi történik, ha a use-deklaráció egy már létező nevű függvényt hoz hatókörbe? Ezek a függvények úgy néznek ki, mintha ott vannak deklarálva, ahol a use deklaráció előfordul. Ezért a bevezetett függvények részt vesznek az adott hatókörben található összes túlterhelt függvény nevének feloldásában:

#beleértve névtér libs_R_us ( extern void print(int); extern void print(double); ) extern void print(const string &); // libs_R_us::print(int) és libs_R_us::print(double)
// túlterhelés nyomtatás(const string &)
a libs_R_us::print; void fooBar (int ival)
{
// print(const string &)
}

A use deklaráció két deklarációt ad a globális hatókörhöz: egyet a print(int) és egyet a print(double) számára. Ezek álnevek a libs_R_us térben, és szerepelnek a print nevű túlterhelt függvényekben, ahol a globális print(const string &) már létezik. A fooBar nyomtatási túlterhelésének feloldásakor mindhárom funkciót figyelembe veszik.
Ha egy using deklaráció bevezet valamilyen függvényt egy olyan hatókörbe, amely már rendelkezik ugyanazzal a névvel és ugyanazzal a paraméterlistával, az hibának minősül. A using-deklaráció nem lehet print(int) függvény álnéven a libs_R_us névtérben, ha a print(int) már létezik a globális hatókörben. Például:

Névtér libs_R_us ( void print(int); void print(double); ) void print(int); a libs_R_us::print; // hiba: ismételt deklaráció print(int) void fooBar(int ival)
{
print(ival); // melyik nyomtatás? ::print vagy libs_R_us::print
}

Megmutattuk, hogyan kapcsolódnak egymáshoz a használat-deklarációk és a túlterhelt függvények. Most nézzük meg a use-direktíva használatának sajátosságait. A use direktíva hatására a névtértagok ezen a területen kívül deklarálva jelennek meg, és hozzáadják őket egy új hatókörhöz. Ha már van egy ilyen nevű függvény ebben a hatókörben, akkor túlterhelés lép fel. Például:

#beleértve névtér libs_R_us ( extern void print(int); extern void print(double); ) extern void print(const string &); // direktíva használatával
// print(int), print(double) és print(const string &) elemek
// ugyanaz a túlterhelt függvénykészlet
névtér használatával libs_R_us; void fooBar (int ival)
{
print("Érték: "); // meghívja a globális függvényt
// print(const string &)
print(ival); // meghívja a libs_R_us::print(int)
}

Ez akkor is igaz, ha több direktívát használunk. Az azonos nevű függvények, amelyek különböző terek tagjai, ugyanabban a halmazban szerepelnek:

IBM névtér ( int print(int); ) névtér Disney ( double print(double); ) // a-direktíva használatával // számos túlterhelt függvényt generál különböző // névterekből az IBM névterek használatával; Disney névtér használata; hosszú dupla nyomtatás (hosszú dupla); int main() (
nyomtat(1); // IBM::print(int) néven
print(3.1); // Disney felhívása::print(double)
visszatérés 0;
}

A globális hatókörben a print nevű túlterhelt függvénykészlet a print(int), a print(double) és a print(long double) függvényeket tartalmazza. Mindegyiket figyelembe veszi a main() a túlterhelés-feloldás során, bár eredetileg különböző névterekben voltak meghatározva.
Tehát ismét a túlterhelt funkciók ugyanabban a hatókörben vannak. Főleg deklarációk és olyan direktívák használatának eredményeként kerülnek oda, amelyek más hatókörökből származó neveket tesznek elérhetővé.

9.1.5. extern "C" direktíva és túlterhelt A függvények

A 7.7. szakaszban láttuk, hogy a külső "C" bind direktíva használható egy C++ programban annak jelzésére, hogy valamilyen objektum C részben van. Hogyan befolyásolja ez a direktíva a túlterhelt függvénydeklarációkat? A C++ és C nyelven írt függvények lehetnek ugyanabban a halmazban?
A bind direktíva a sok túlterhelt függvény közül csak egyet határozhat meg. Például a következő program hibás:

// hiba: két túlterhelt függvényhez megadott direktíva extern "C" extern "C" void print(const char*); külső "C" void print(int);

A következő példa egy túlterhelt calc() függvényre a külső "C" direktíva tipikus használatát szemlélteti:

ClassSmallInt(/* ... */); osztály BigNum(/* ... */); // a C-ben írt függvény mindkettő meghívható programból,
// C-ben írt vagy C++-ban írt programból.
// A C++ függvények olyan paramétereket kezelnek, amelyek osztályok
extern "C" double calc(double);
extern SmallInt calc(const SmallInt&);
extern BigNum calc(const BigNum&);

A C-ben írt calc() függvény C-ből és C++ programból is meghívható. A másik két függvény egy osztályt vesz fel paraméterként, ezért csak C++ programban használható. A nyilatkozatok sorrendje nem jelentős.
A bind direktíva irreleváns annak eldöntésében, hogy melyik függvényt hívjuk meg; csak a paraméter típusok fontosak. Az átadott argumentumok típusainak leginkább megfelelő függvény kerül kiválasztásra:

Smallint si = 8; int main() ( calc(34); // C függvény hívása calc(double) calc(si); // C++ függvény calc(const SmallInt &) // ... return 0; )

9.1.6. Túlterhelt függvényekre mutatók A

Mutatót deklarálhat a sok túlterhelt függvény egyikére. Például:

külső void ff(vektor ); extern void ff(unsigned int); // milyen függvényre mutat a pf1?
void (*pf1)(unsigned int) =

Mivel az ff() függvény túlterhelt, az &ff inicializáló önmagában nem elegendő a kiválasztáshoz helyes opció. Annak megértéséhez, hogy melyik függvény inicializálja a mutatót, a fordító az összes túlterhelt függvény halmazában megkeresi azt, amelyiknek ugyanaz a visszatérési típusa és paraméterlistája, mint annak a függvénynek, amelyre a mutató hivatkozik. Esetünkben az ff(unsigned int) függvény lesz kiválasztva.
De mi van akkor, ha nincs olyan függvény, amely pontosan megfelelne a mutató típusának? Ezután a fordító hibaüzenetet ad:

külső void ff(vektor ); extern void ff(unsigned int); // hiba: egyezés nem található: érvénytelen paraméterlista void (*pf2)(int) = // hiba: egyezés nem található: rossz visszatérési típus double (*pf3)(vektor ) = &ff;

A hozzárendelés hasonló módon működik. Ha a mutató értéke egy túlterhelt függvény címe, akkor a függvényre mutató mutató típusát használjuk a hozzárendelési operátor jobb oldalán található operandus kiválasztásához. Ha pedig a fordító nem talál a kívánt típusnak pontosan megfelelő függvényt, akkor hibaüzenetet ad ki. Így a függvénymutatók közötti típuskonverzió soha nem történik meg.

Mátrix calc(const mátrix &); intcalc(int, int); int (*pc1)(int, int) = 0;
int (*pc2)(int, double) = 0; // ...
// helyes: a calc(int, int) függvény van kiválasztva
pc1 = // hiba: nincs egyezés: érvénytelen második paramétertípus
pc2=

9.1.7. Biztonságos kötés A

Túlterhelés használatakor az a benyomásunk támad, hogy egy programnak több azonos nevű függvénye is lehet, különböző paraméterlistákkal. Ez a lexikális kényelem azonban csak a forrásszöveg szintjén létezik. A legtöbb fordítórendszerben az ezt a szöveget feldolgozó programok futtatható kód létrehozása érdekében megkövetelik, hogy minden név elkülönüljön. A linkszerkesztők általában megengedik Külső linkek lexikálisan. Ha egy ilyen szerkesztő kétszer vagy többször találkozik a print névvel, nem tudja megkülönböztetni őket típusanalízissel (a típusinformáció ekkorra általában elveszik). Tehát csak egy üzenetet nyomtat az újradefiniált karakternyomtatásról, és kilép.
A probléma megoldása érdekében a függvény nevét a paraméterlistával együtt egyedi belső nevet adjuk. A fordító után meghívott programok csak ezt a belső nevet látják. A névfeloldás pontos módja a megvalósítástól függ. Az általános ötlet az, hogy a paraméterek számát és típusát karakterláncként ábrázoljuk, és hozzáfűzzük a függvény nevéhez.
Amint azt a 8.2. szakaszban említettük, az ilyen kódolás különösen azt garantálja, hogy az azonos nevű függvények két, különböző fájlokban található, eltérő paraméterlistájú deklarációját a linker ne tekintse ugyanazon függvény deklarációjának. Mivel ez a módszer segít megkülönböztetni a túlterhelt funkciókat a hivatkozásszerkesztési fázisban, biztonságos linkelésről beszélünk.
A névdíszítés nem vonatkozik a külső "C" direktívával deklarált függvényekre, mivel a sok túlterhelt függvény közül csak egy írható tiszta C-ben. Két különböző paraméterlistájú, külső "C"-ként deklarált függvényt a linker egyként kezel és ugyanaz a karakter.

9.1. gyakorlat

Miért kell deklarálni a túlterhelt függvényeket?

9.2. gyakorlat

Hogyan lehet deklarálni az error() függvény túlterhelt verzióit, hogy a következő hívások helyesek legyenek:

Int index; int felső határ; char selectVal; // ... error("Tömb határokon kívül: ", index, felső határ); error("Osztás nullával"); error("Érvénytelen kijelölés", selectVal);

9.3. gyakorlat

Magyarázza meg a második deklaráció hatását az alábbi példák mindegyikében:

(a) intcalc(int, int); int calc(const int, const int); (b) int get(); double get(); (c) int *reset(int *); double *reset(double *): (d) extern "C" int compute(int *, int); extern "C" double compute(double *, double);

9.4. gyakorlat

Az alábbi inicializálások közül melyik eredményez hibát? Miért?

(a) void reset(int *); void (*pf)(void *) = visszaállítás; (b) intcalc(int, int); int (*pf1)(int, int) = számított; (c) külső "C" int compute(int *, int); int (*pf3)(int*, int) = számítás; (d) érvénytelen (*pf4) (állandó mátrix &) = 0;

9.2. A túlterhelési megoldás három lépése

Funkció túlterhelési felbontás az úgynevezett túlterhelt halmazból a függvény kiválasztásának folyamatát, amelyet meg kell hívni. Ez a folyamat a meghívásakor megadott argumentumokon alapul. Vegyünk egy példát:

T t1, t2; void f(int, int); void f(float, float); int main() (
f(t1, t2);
visszatérés 0;
}

Itt a túlterhelés-feloldási folyamat során a T típusától függően meghatározzák, hogy az f(int,int) vagy f(float,float) függvényt hívjuk-e meg az f(t1,t2) kifejezés feldolgozása során, vagy hiba van. rögzítésre kerül.
A funkciók túlterhelésének feloldása a C++ nyelv egyik legösszetettebb aspektusa. Az összes részlet megértése érdekében a kezdő programozóknak komoly nehézségekkel kell szembenézniük. Ezért be ez a szekció csak bemutatjuk rövid áttekintés a túlterhelési megoldás működését, hogy legalább némi benyomást keltsen a folyamatról. Azok számára, akik többet szeretnének tudni, a következő két rész részletesebb leírást ad.
A funkciótúlterhelés feloldásának folyamata három lépésből áll, amelyeket a következő példában mutatunk be:

void f(); void f(int); void f(double, double = 3,4); void f(char *, char *); void main() (
f(5,6);
visszatérés 0;
}

A funkció túlterhelésének feloldásakor a következő lépéseket kell végrehajtani:

  1. Ki van emelve az adott hívás túlterhelt függvényeinek halmaza, valamint a függvénynek átadott argumentumok listájának tulajdonságai.
  2. A túlterhelt függvények közül azokat választjuk ki, amelyek az adott argumentumokkal hívhatók, számuk és típusuk figyelembevételével.
  3. Megkeresi a hívásnak leginkább megfelelő függvényt.

Vegyük sorra az egyes tételeket.
Az első lépés a túlterhelt függvények készletének azonosítása, amelyet ebben a hívásban figyelembe kell venni. Az ebben a halmazban szereplő függvényeket jelölteknek nevezzük. A jelölt függvény egy olyan függvény, amelynek a neve megegyezik a meghívóval, és deklarációja a hívás pontján látható. Példánkban négy ilyen jelölt van: f(), f(int), f(double, double) és f(char*, char*).
Ezt követően azonosítják az átadott argumentumok listájának tulajdonságait, azaz. számuk és típusuk. Példánkban a lista két kettős argumentumból áll.
A második lépésben a jelöltek halmaza közül kiválasztják az életképeseket - azokat, amelyek az adott argumentumokkal meghívhatók A perzisztens függvénynek vagy annyi formális paramétere van, amennyi a hívott függvénynek átadott tényleges argumentum, vagy több, de ekkor minden további paraméternél alapértelmezett érték. Ahhoz, hogy egy függvényt perzisztensnek lehessen tekinteni, a hívásban átadott bármely tényleges argumentumnak át kell alakulnia a deklarációban megadott formális paraméter típusára.

Példánkban két beállított függvény van, amelyek a megadott argumentumokkal hívhatók meg:

  • az f(int) függvény megmaradt, mert csak egy paramétere van, és a tényleges dupla argumentum átalakul formális int paraméterré;
  • az f(double,double) függvény megmaradt, mert a második argumentumnak van egy alapértelmezett értéke, és az első formális paraméter double típusú, ami pontosan a tényleges argumentum típusa.

Ha a második lépés után nem találtunk stabil függvényeket, akkor a hívást hibásnak tekintjük. Ilyenkor azt mondjuk, hogy hiányzik a levelezés.
A harmadik lépés a hívás kontextusának leginkább megfelelő funkció kiválasztása. Az ilyen funkciót a legjobb állásnak (vagy legjobb illeszkedésnek) nevezzük. Ebben a lépésben rangsorolják azokat a konverziókat, amelyekkel a tényleges argumentumok típusait a létrehozott függvény formális paramétereinek típusaira öntötték. A legalkalmasabbnak azt a funkciót tekintjük, amelyhez a következő feltételek teljesülnek:
a tényleges argumentumokra alkalmazott transzformációk nem rosszabbak, mint bármely más jól bevált függvény meghívásához szükséges transzformációk;
egyes argumentumok esetében az alkalmazott konverziók jobbak, mint azok a konverziók, amelyek ugyanazon argumentumok leadásához más jól bevált függvények hívásaiban szükségesek.
A típuskonverziókat és azok rangsorolását részletesebben a 9.3 fejezet tárgyalja. Itt csak röviden tekintjük át példánkban a rangsorolási transzformációkat. A létrehozott f(int) függvényhez a tényleges argumentum dupla int típusú szabványos leadását kell alkalmazni. A létrehozott f(double,double) függvény esetében az aktuális argumentum double típusa pontosan megegyezik a formális paraméter típusával. Mivel a pontos egyezés jobb, mint egy szabványos konverzió (egyetlen konverzió sem mindig jobb, mint egy konverzió), az f(double,double) a legmegfelelőbb függvény ehhez a híváshoz.
Ha a harmadik lépésben nem lehet megtalálni a beállított függvények közül az egyetlen legjobbat, vagyis nincs olyan beállított függvény, amely jobban illeszkedne, mint az összes többi, akkor a hívást kétértelműnek tekintjük, pl. téves.
(A 9.4. szakasz részletesebben tárgyalja a túlterhelés-feloldás összes lépését. A feloldási folyamatot egy túlterhelt osztálytagfüggvény és egy túlterhelt operátor hívásakor is alkalmazzák. A 15.10. szakasz az osztálytagfüggvényekre vonatkozó túlterhelés-feloldási szabályokat tárgyalja, a 15.11. szabályok (túlterhelt operátorok. A túlterhelési felbontásnak figyelembe kell vennie a sablonokból származó funkciókat is. A 10.8. szakasz tárgyalja, hogy a sablonok hogyan befolyásolják ezt a felbontást.)

9.5. gyakorlat

Mi történik a funkció túlterhelés-feloldási folyamatának utolsó (harmadik) lépésében?

9.3. Argumentumtípus-konverziók A

A függvénytúlterhelés-feloldási folyamat második lépésében a fordító azonosítja és rangsorolja azokat a konverziókat, amelyeket a hívott függvény minden tényleges argumentumára alkalmazni kell, hogy azt a jól bevált függvények megfelelő formális paraméterének típusába öntse. funkciókat. A rangsor három lehetséges kimenetel egyikét adhatja:

  • pontos egyezés. A tényleges argumentum típusa pontosan megegyezik a formális paraméter típusával. Például, ha a túlterhelt print() függvények halmaza a következőkkel rendelkezik: void print(unsigned int); void print(const char*); voidprint(char);
  • akkor a következő három hívás mindegyike pontos egyezést ad:
    unsigned int a;
print("a"); // megfelel a print(char); print("a"); // megfelel a print(const char*); nyomtat(a); // egyezik print(unsigned int);
  • típuskonverzióval való egyeztetés. Az aktuális argumentum típusa nem egyezik a formális paraméter típusával, de átalakítható arra: void ff(char); ff(0); // Az int típusú argumentum a char típusba kerül
  • megfelelés hiánya. A tényleges argumentum típusa nem adható át a függvénydeklaráció formális paraméterének típusához, mert a szükséges konverzió nem létezik. A print() függvény következő két hívása nem egyezik:
  • // a print() függvények a fentiek szerint vannak deklarálva int *ip; osztály SmallInt ( /* ... */ ); SmallInt si; print(ip); // hiba: nincs egyezés
    nyomtat(si); // hiba: nincs egyezés
  • A pontos egyezés megállapításához a tényleges argumentum típusának nem kell egyeznie a formális paraméter típusával. Néhány triviális transzformáció alkalmazható az argumentumra, nevezetesen:

    • l-érték átalakítása r-értékké;
    • tömb átalakítása mutatóvá;
    • függvény konvertálása mutatóvá;
    • specifikáló konverziók.

    (Az alábbiakban részletesebben tárgyaljuk.) A típusátalakítással való megfelelés kategóriája a legösszetettebb. Többféle ilyen szereposztást érdemes figyelembe venni: típusbővítmények (promóciók), szabványos konverziók és felhasználó által meghatározott konverziók. (A típusbővítésekkel és a szabványos konverziókkal ebben a fejezetben foglalkozunk. A felhasználó által definiált konverziókat később, az osztályok részletes tárgyalása után mutatjuk be; ezeket a konverter hajtja végre, egy olyan tagfüggvény, amely lehetővé teszi a saját „standard” halmazának meghatározását. ” transzformációkat egy osztályban. A 15. fejezetben megvizsgáljuk az ilyen konvertereket, és azt, hogy ezek hogyan befolyásolják a funkció túlterhelési felbontását.)
    Amikor egy adott híváshoz a legjobb jól bevált függvényt választjuk, a fordító azt a függvényt keresi, amelynél a tényleges argumentumokra alkalmazott transzformációk a „legjobbak”. A típuskonverziók rangsorolása a következőképpen történik: a pontos egyezés jobb, mint a típusbővítmény, a típusbővítmény jobb, mint a normál konverzió, ez pedig jobb, mint a felhasználó által meghatározott konverzió. A 9.4. szakaszban visszatérünk a rangsoroláshoz, de egyelőre egyszerű példák Mutatjuk, hogyan segít a legmegfelelőbb funkció kiválasztásában.

    9.3.1. További információ a Pontos egyezésről

    A legegyszerűbb eset akkor fordul elő, ha a tényleges argumentumok típusai megegyeznek a formális paraméterek típusaival. Például az alábbiakban két túlterhelt max() függvény látható. Ekkor a max() függvény mindegyik hívása pontosan megegyezik az egyik deklarációval:

    int max(int, int); double max(double, double); int i1; void calc(double d1) (
    max(56, i1); // pontosan megfelel a max(int, int);
    max(d1, 66,9); // pontosan megegyezik a max(double, double);
    }

    Egy felsorolt ​​típus csak a benne meghatározottakkal egyezik meg pontosan. felsorolási elemek, valamint az ilyen típusúnak nyilvánított objektumok:

    Enum Tokenek ( INLINE = 128; VIRTUÁLIS = 129; ); Tokenek curTok = INLINE; enum Stat( Sikertelen, Megfelelt); extern void ff(Tokenek);
    extern void ff(Stat);
    extern void ff(int); int main() (
    ff(pass); // pontosan megegyezik az ff(Stat)
    ff(0); // pontosan megegyezik az ff(int)
    ff(curTok); // pontosan megegyezik az ff(Tokens)
    // ...
    }

    Fentebb már említettük, hogy a tényleges argumentum pontosan megegyezhet a formális paraméterrel, még akkor is, ha valamilyen triviális konverzióra van szükség a típusuk leadásához, amelyek közül az első az l-érték átalakítása r-értékké. Az l-érték olyan objektum, amely megfelel a következő feltételeknek:

    • megkaphatja az objektum címét;
    • megkaphatja egy objektum értékét;
    • ez az érték könnyen módosítható (kivéve, ha az objektumdeklarációban van const specifikáció).

    Ezzel szemben az r-érték olyan kifejezés, amelynek értéke kiértékelődik, vagy olyan kifejezés, amely egy ideiglenes objektumot jelöl, amelyhez nem lehet címet szerezni, és amelynek értéke nem módosítható. Íme egy egyszerű példa:

    intcalc(int); int main() ( int lval, res; lval = 5; // lvalue: lval; rvalue: 5
    res = calc(lval);
    // lvalue:res
    // rvalue: ideiglenes objektum az érték tárolására,
    // a calc() függvény adja vissza
    visszatérés 0;
    }

    Az első hozzárendelési utasításban az lval változó az l-érték, a literál 5 pedig az r-érték. A második hozzárendelési utasításban a res az l-érték, és az ideiglenes objektum, amely a calc() függvény által visszaadott eredményt tárolja, az r-érték.
    Bizonyos helyzetekben, olyan környezetben, ahol egy érték várható, egy l-érték kifejezés használható:

    intobj1; intobj2; int main() (
    // ...
    belső = obj1 + obj2;
    visszatérés 0;
    }

    Itt obj1 és obj2 l-értékek. A main() függvényben történő összeadás végrehajtásához azonban értékeiket az obj1 és obj2 változókból nyerik ki. Az l-érték kifejezéssel reprezentált objektum értékének kinyerését az l-érték r-értékké való konvertálásának nevezzük.
    Ha a függvény érték által átadott argumentumot vár, ha az argumentum l-érték, akkor a rendszer r-értékké alakítja:

    #beleértve stringcolor("lila"); voidprint(karakterlánc); int main() (
    nyomat(szín); // pontos egyezés: értékkonverzió
    // rértékben
    visszatérés 0;
    }

    Mivel a print(color) hívás argumentumát érték adja át, az l-értéket r-értékké alakítja a rendszer, hogy kivonja a szín értékét, és átadja azt a print(string) prototípus függvénynek. Mindazonáltal annak ellenére, hogy megtörtént egy ilyen öntvény, a tényleges színargumentumról azt feltételezzük, hogy pontosan megegyezik a print(string) deklarációval.
    Függvények hívásakor nem mindig szükséges ilyen átalakítást alkalmazni az argumentumokra. A hivatkozás egy l-érték; ha a függvénynek van referencia paramétere, akkor a függvény meghívásakor l-értéket kap. Ezért a leírt transzformációt nem alkalmazzuk arra a tényleges argumentumra, amelynek a formális referenciaparaméter megfelel. Tegyük fel például, hogy a következő függvény deklarálva van:

    #beleértve void print(list &);

    Az alábbi hívásban li a lista objektumot képviselő l-érték , átadva a print() függvénynek:

    Lista li(20); int main() (
    // ...
    print(li); // pontos egyezés: nincs konverzió lvalue értékről
    // rvalue
    visszatérés 0;
    }

    Az li egy referenciaparaméterrel való egyeztetése pontos egyezésnek számít.
    A második konverzió, amely még mindig pontos egyezést rögzít, egy tömb mutatóvá alakítása. Ahogy a 7.3. szakaszban megjegyeztük, a függvényparaméter soha nem tömbtípus, hanem az első elemére mutató mutatóvá alakul át. Hasonlóképpen, egy tényleges tömb típusú argumentum az NT-ből (ahol N a tömb elemeinek száma, T pedig az egyes elemek típusa) mindig egy T-re mutató mutatóba kerül. -mutató átalakítás. Ennek ellenére a tényleges argumentum pontosan megegyezik a "pointer to T" típusú formális paraméterrel. Például:

    int ai; void putValues(int*); int main() (
    // ...
    putValues(ai); // pontos egyezés: konvertálja a tömböt a következőre
    // mutató
    visszatérés 0;
    }

    A putValues() függvény meghívása előtt a tömb mutatóvá alakul, ami azt eredményezi, hogy a tényleges ai argumentum (három egész számból álló tömb) egy int mutatóba kerül. Bár a putValues() függvény formális paramétere egy mutató, és a tényleges argumentum híváskor átalakul, pontos megfelelés jön létre közöttük.
    Pontos egyezés megállapításánál lehetőség van a függvény mutatóvá alakítására is. (A 7.9. fejezetben említettük.) A tömbparaméterekhez hasonlóan a függvényparaméter is függvénymutatóvá válik. A tényleges "function" típusú argumentum szintén automatikusan átkerül a függvénymutató típusára. Az aktuális argumentum ilyen típusú konvertálását függvény-mutató konverziónak nevezik. A transzformáció végrehajtása közben a tényleges argumentum a formális paraméterrel pontosan megegyezőnek tekinthető. Például:

    Int lexicoCompare(const string &, const string &); typedef int (*PFI)(const string &, const string &);
    void sort(karakterlánc *, karakterlánc *, PFI); string as; int main()
    {
    // ...
    sort (mint,
    mint + mérete(mint)/mérete(mint -1),
    lexicoCompare // pontos egyezés
    // függvény konvertálása mutatóvá
    ); visszatérés 0;
    }

    A sort() hívás előtt egy függvény-mutató konverzió kerül alkalmazásra, amely a lexicoCompare argumentumot "function" típusból "pointer to function" típusba dobja. Bár a függvény formális argumentuma egy mutató, a tényleges argumentum pedig a függvény neve, és ezért a függvényt mutatóvá alakították, a tényleges argumentum a sort() függvénynek pontosan a harmadik formális paramétere. .
    A fentiek közül az utolsó a specifikációk átalakítása. Csak a mutatókra vonatkozik, és a const vagy a volatile specifikátorok (vagy mindkettő) hozzáadása az adott mutatót megszólító típushoz:

    Int a = (4454, 7864, 92, 421, 938); int*pi = a; bool is_equal(const int * , const int *); void func(int *parm) ( // pontos egyezés a pi és parm között: a specifikációk átalakítása
    if (egyenlő(pi, parm))
    // ... return 0;
    }

    Az is_equal() függvény meghívása előtt a tényleges pi és parm argumentumok „pointer to int” típusról „pointer to const int” típusra konvertálódnak. Ez a transzformáció abból áll, hogy a megcímzett típushoz adunk egy const specifikátort, ezért a specifikációs transzformációk kategóriájába tartozik. Annak ellenére, hogy a függvény két mutatót vár egy const int-re, és a tényleges argumentumok egy int-re mutatnak, feltételezzük, hogy az is_equal() függvény formális és tényleges paraméterei pontosan egyeznek.
    A specifikus konverzió csak arra a típusra vonatkozik, amelyet a mutató megcímez. Nem használatos, ha a formális paraméternek van const vagy volatile specifikációja, de a tényleges argumentumnak nincs.

    extern void takeCI(const int); int main() (
    int ii = ...;
    vegye CI(ii); // a specifikátor konverzió nincs alkalmazva
    visszatérés 0;
    }

    Bár a takeCI() függvény formális paramétere const int típusú, és int típusú ii argumentummal hívják meg, nincs specifikáló konverzió: pontos egyezés van a tényleges argumentum és a formális paraméter között.
    A fentiek mindegyike igaz arra az esetre is, amikor az argumentum egy mutató, és a const vagy a volatile specifikációk erre a mutatóra hivatkoznak:

    extern void init(int *const); extern int *pi; int main() (
    // ...
    init(pi); // a specifikátor konverzió nincs alkalmazva
    visszatérés 0;
    }

    Az init() függvény formális paraméterében szereplő const specifikátor magára a mutatóra vonatkozik, nem a címzett típusra. Ezért a fordító nem veszi figyelembe ezt a specifikációt az aktuális argumentumra alkalmazandó konverziók elemzésekor. A pi argumentumra nem alkalmazunk specifikáló konverziót: ez az argumentum és a formális paraméter pontosan megegyezőnek tekinthető.
    Az első három transzformációt (l-érték-r-érték, tömb-mutató és függvény-mutató) gyakran l-érték transzformációnak nevezik. (A 9.4. fejezetben látni fogjuk, hogy bár mind az l-értékű transzformációk, mind a specifikáló transzformációk a pontos illeszkedést nem sértő transzformációk kategóriájába tartoznak, ennek fokát magasabbnak tekintjük abban az esetben, ha csak az első transzformációra van szükség. A következő részben , erről egy kicsit részletesebben fogunk beszélni.)
    A pontos egyezés kikényszeríthető explicit típusú öntéssel. Például, ha két funkció túlterhelt:

    extern void ff(int); extern void ff(void *);

    Ff(0xffbc); // ff(int) hívása

    pontosan megegyezik az ff(int) értékkel, még akkor is, ha a literális 0xffbc hexadecimális állandóként van írva. A programozó kényszerítheti a fordítót az ff(void *) függvény meghívására egy cast művelet explicit végrehajtásával:

    Ff(reinterpret_cast (0xffbc)); // ff(void*) hívása

    Ha egy ilyen öntvényt alkalmazunk az aktuális argumentumra, akkor az átveszi azt a típust, amelyre átalakítja. Az explicit típuskonverziók segítik a túlterhelés-feloldási folyamat szabályozását. Például, ha a túlterhelési felbontás kétértelmű eredményt ad (a tényleges argumentumok két vagy több jól bevált függvénynek egyformán jól egyeznek), akkor egy explicit cast használható a kétértelműség feloldására, és arra kényszeríti a fordítót, hogy válasszon egy adott függvényt.

    9.3.2. További információ a típuskiterjesztésről

    A típusbővítmény a következő konverziók egyike:

    • a char, unsigned char vagy short típusú tényleges argumentum kibővül az int típusra. Az unsigned short típusú tényleges argumentum kibővül az int típusra, ha az int gépmérete nagyobb, mint a short, és egyébként az unsigned int típusra;
    • a float típusú argumentum double típusúra bővül;
    • a felsorolt ​​típusú argumentum a következő típusok közül az elsőre bővül, amely képes az összes enum tagértéket reprezentálni: int, unsigned int, long, unsigned long;
    • A bool argumentum int típusúra bővül.

    Hasonló kiterjesztést alkalmazunk, ha a tényleges argumentum típusa az imént felsorolt ​​típusok egyike, a formális paraméter pedig a megfelelő kiterjesztett típus:

    extern void manip(int); int main() (
    manip("a"); // típusú char int-re bővül
    visszatérés 0;
    }

    A karakterliterál char típusú. Int-re bővül. Mivel a kiterjesztett típus megfelel a manip() függvény formális paraméterének típusának, azt mondjuk, hogy meghívásához az argumentumtípus kiterjesztése szükséges.
    Tekintsük a következő példát:

    extern void print(unsigned int); extern void print(int); extern void print(char); unsigned char uc;
    print(uc); // print(int); uc csak típuskiterjesztésre van szüksége

    Egy olyan hardverplatformon, ahol az előjel nélküli char egy bájt memóriát, az int pedig négy bájtot foglal el, a kiterjesztés az előjel nélküli karaktert int-vé alakítja, mivel az összes előjel nélküli char értéket képviselheti. Egy ilyen géparchitektúra esetén a példában bemutatott sok túlterhelt függvény közül a print(int) biztosítja a legjobb illeszkedést az előjel nélküli char argumentumhoz. A másik két funkció esetében az illesztéshez szabványos öntvény szükséges.
    A következő példa egy tényleges felsorolt ​​típusú argumentum kiterjesztését szemlélteti:

    Enum Stat(Fail, Pass); extern void ff(int);
    külső void ff(char); int main() (
    // helyes: enum tag A Pass kibővül az int típusra
    ff(pass); // ff(int)
    ff(0); // ff(int)
    }

    Néha az enum bővítése meglepetéseket hoz. A fordítók gyakran az elemeinek értéke alapján választják ki az enum ábrázolását. Tegyük fel, hogy a fenti architektúra (egy bájt a char-hoz és négy bájt az int-hez) a következőhöz hasonló enumot határoz meg:

    e1 szám ( a1, b1, c1 );

    Mivel csak három a1, b1 és c1 elem van 0, 1 és 2 értékkel - és mivel ezek az értékek mindegyike char típussal ábrázolható, a fordító általában a char-ot választja az e1 típus képviseletére. Tekintsük azonban az e2 felsorolást a következő elemekkel:

    Enum e2 ( a2, b2, c2=0x80000000 );

    Mivel az egyik konstans értéke 0x80000000, a fordítónak úgy kell döntenie, hogy az e2-t olyan típussal reprezentálja, amely elegendő a 0x80000000 érték tárolására, azaz unsigned int.
    Tehát bár az e1 és az e2 is enumok, ábrázolásaik eltérőek. Emiatt az e1 és e2 különböző típusokra bővül:

    #beleértve string format(int);
    karakterlánc formátum (unsigned int); int main() (
    formátum(a1); // hívásformátum(int)
    formátum(a2); // hívásformátum (unsigned int)
    visszatérés 0;
    }

    A format() első alkalommal meghívásra kerül, a tényleges argumentum kibővül int típusúra, mivel a char az e1-et jelenti, ezért a túlterhelt format(int) függvény meghívásra kerül. A második híváskor a tényleges e2 argumentum típusa unsigned int, és az argumentum unsigned int-re bővül, ami a túlterhelt formátum(unsigned int) függvény meghívását okozza. Ezért ügyeljen arra, hogy két enum viselkedése a túlterhelés-feloldási folyamattal kapcsolatban eltérő lehet, és az elemek értékétől függ, amelyek meghatározzák a típusbővítést.

    9.3.3. További információ a normál átalakításról

    Ötféle szabványos transzformáció létezik, nevezetesen:

    1. integer típusú konverziók: egy egész szám típusból vagy felsorolásból bármilyen más egész szám típusba adás (kivéve a transzformációkat, amelyeket fentebb típuskiterjesztésként soroltunk be);
    2. lebegőpontos típusú konverziók: bármilyen lebegőpontos típusból bármely más lebegőpontos típusba történő casting (kivéve a transzformációkat, amelyeket fentebb típusbővítésként soroltunk be);
    3. konverziók egész típusú és lebegőpontos típusok között: casting bármilyen lebegőpontos típusból tetszőleges egész típusba vagy fordítva;
    4. mutatókonverziók: a 0 egész érték mutatótípusba öntése vagy bármilyen típusú mutató átalakítása void* típusúvá;
    5. konverzió bool típusba: casting bármely egész típusból, lebegőpontos típusból, felsorolt ​​típusból vagy mutatótípusból bool típusba.

    Íme néhány példa:

    extern void print(void*); külső üres nyomtatás (dupla); int main() (
    int i;
    print(i); // egyezik print(double);
    // átesem a standard konverzión int-ről double-ra
    print(&i); // egyezik print(void*);
    // &i szabványos átalakításon megy keresztül
    // int*-ből érvénytelen*
    visszatérés 0;
    }

    Az 1., 2. és 3. csoportba tartozó konverziók potenciálisan veszélyesek, mert előfordulhat, hogy a céltípus nem képviseli a forrástípus összes értékét. Például a lebegő elemek nem képviselhetik megfelelően az összes int értéket. Ez az oka annak, hogy az ezekbe a csoportokba tartozó transzformációk standard transzformációk, és nem típuskiterjesztések közé tartoznak.

    int i; voidcalc(úszó); int main() ( calc(i); // az egész típusú és a // lebegőpontos típus közötti szabványos konverzió potenciálisan veszélyes // az i return 0 értékétől függően; )

    A calc() függvény meghívásakor az int integer típusúról a lebegőpontos típusú lebegőpontosra történő szabványos konverzió kerül alkalmazásra. Az i változó értékétől függően előfordulhat, hogy nem lehet lebegőként tárolni a pontosság elvesztése nélkül.
    Feltételezzük, hogy minden szabványos változtatás ugyanannyi munkát igényel. Például a char-ból előjel nélküli karakterré konvertálásnak nincs nagyobb prioritása, mint a char-ból duplává történő átalakításnak. A típusok közelségét nem veszik figyelembe. Ha két létrehozott függvény az aktuális argumentum szabványos transzformációját igényli, hogy megfeleljen, akkor a hívást kétértelműnek tekinti, és a fordító hibának jelöli meg. Például két túlterhelt funkció esetén:

    extern void manip(hosszú); extern void manip(float);

    akkor a következő hívás nem egyértelmű:

    int main() ( manip(3.14); // hiba: kétértelműség // a manip(float) nem jobb, mint a manip(int) return 0; )

    A 3.14 konstans dupla típusú. Egyik-másik szabványos transzformáció segítségével a túlterhelt függvények bármelyikével létrehozható a megfelelés. Mivel két transzformáció vezet a célhoz, a hívás kétértelműnek tekinthető. Egyik átalakulás sem élvez elsőbbséget a másikkal szemben. A programozó a kétértelműséget explicit típusú öntéssel is feloldhatja:

    Manip(static_cast (3,14)); // manip(hosszú)

    vagy egy utótag használatával, amely jelzi, hogy a konstans float típusú:

    Manip (3,14F)); // manip(float)

    Íme néhány további példa a kétértelmű hívásokra, amelyek hibaként vannak megjelölve, mert több túlterhelt függvénynek felelnek meg:

    extern void farith(unsigned int); extern void farith(float); int main() (
    // a következő hívások mindegyike kétértelmű
    farith("a"); // az argumentum char típusú
    farith(0); // argumentum int típusú
    farith(2ul); // az argumentum unsigned long típusú
    farith(3,14159); // az argumentum double típusú
    farith(igaz); // az argumentum bool típusú
    }

    A szabványos mutatókonverziók néha ellentétesek az intuitív hatásokkal. Konkrétan, a 0 értéket a rendszer bármely típusú mutatóra leadja; az így kapott mutatót nullnak nevezzük. A 0 érték egy egész típusú konstans kifejezésként ábrázolható:

    void set(int*); int main() (
    // mutatókonverzió 0-ról int*-ra, argumentumokra alkalmazva
    // mindkét hívásban
    set(0L);
    set(0x00);
    visszatérés 0;
    }

    A 0L konstans kifejezés (hosszú int típus 0 értéke) és a 0x00 konstans kifejezés (0 hexadecimális egész szám) egész típusú, ezért int* típusú nullmutatóvá alakítható.
    De mivel az enumok nem egész típusúak, a 0-val egyenlő elem nem kerül mutatótípusba:

    Enum EN (zr = 0); set(zr); // hiba: a zr nem konvertálható int* típusúvá

    A set() függvényhívás hibás, mert nincs konverzió a felsorolási elem zr értéke és az int* típusú formális paraméter között, pedig zr 0.
    Vegye figyelembe, hogy a 0 konstans kifejezés int típusú. A mutatótípusra való átküldéshez szabványos átalakítás szükséges. Ha van egy int típusú formális paraméterrel rendelkező függvény a túlterhelt függvények halmazában, akkor a túlterhelés megengedett a javára abban az esetben, ha a tényleges argumentum 0:

    Void print(int); voidprint(void*); void set(const char*);
    void set(char *); int main()(
    nyomtat(0); // a print(int);
    set(0); // kétértelműség
    visszatérés 0;
    }

    A print(int) hívás pontos egyezést jelent, míg a print(void*) híváshoz a 0 értéket kell egy mutatótípusba önteni. Mivel egy egyezés jobb, mint egy transzformáció, ennek a hívásnak a megoldásához az ember választ nyomtatási funkció(int). A set() hívása kétértelmű, mivel a 0 megfelel mindkét túlterhelt függvény formális paramétereinek a szabványos transzformáció alkalmazásával. Mivel mindkét funkció egyformán jó, a kétértelműség rögzítve van.
    Az utolsó lehetséges mutatókonverzió lehetővé teszi, hogy bármilyen típusú mutatót öntsön a void* típusba, mivel a void* egy általános mutató bármely adattípusra. Íme néhány példa:

    #beleértve extern void reset(void *); void func(int *pi, string *ps) (
    // ...
    reset(pi); // mutató átalakítása: int* érvénytelen*
    /// ...
    reset(ps); // mutató átalakítása: string* érvénytelen*
    }

    Csak az adattípusokra mutató mutatókat lehet érvényteleníteni* a szabványos konverzióval, a függvényekre mutató mutatókat így nem lehet:

    Typedef int(*PFV)(); külső PFV tesztesetek; // függvényekre mutatók tömbje extern void reset(void *); int main() (
    // ...
    reset(textcase); // hiba: nincs szabványos átalakítás
    // int(*)() és void* között
    visszatérés 0;
    }

    9.3.4. Linkek

    Egy függvény tényleges argumentuma vagy formális paramétere lehet hivatkozás. Hogyan érinti ez a típuskonverziós szabályokat?
    Fontolja meg, mi történik, ha a hivatkozás tényleges érv. Típusa soha nem referenciatípus. A referencia argumentumot l-értékként kezeli, amelynek típusa megegyezik a megfelelő objektum típusával:

    int i; int& ri = i; voidprint(int); int main() (
    print(i); // argumentum egy int típusú érték
    print(ri); // azonos
    visszatérés 0;
    }

    A tényleges argumentum mindkét hívásban int típusú. A hivatkozás átadása a második hívásban nem befolyásolja magának az argumentumnak a típusát.
    A fordító által figyelembe vett szabványos típusú konverziók és kiterjesztések megegyeznek, ha a tényleges argumentum a T típusra való hivatkozás, és ha maga is az adott típusú. Például:

    int i; int& ri = i; voidcalc(double); int main() (
    calc(i); // szabványos konverzió egész típusok között
    // és lebegőpontos típus
    calc(ri); // azonos
    visszatérés 0;
    }

    És hogyan befolyásolja a formális referenciaparaméter az aktuális argumentumra alkalmazott transzformációkat? Az összehasonlítás a következő eredményeket adja:

    • az aktuális argumentum referenciaparaméter-inicializálónak alkalmas. Ebben az esetben azt mondjuk, hogy pontos egyezés van köztük: void swap(int &, int &); void manip(int i1, int i2) (
      // ...
      csere(i1, i2); // helyes: call swap(int &, int &)
      // ...
      visszatérés 0;
      }
    • az aktuális argumentum nem tudja inicializálni a referenciaparamétert. Ilyen helyzetben nincs pontos egyezés, és az argumentum nem használható a függvény meghívására. Például:
    • int obj; void fred(double &); int main() ( frd(obj); // hiba: a paraméternek const double & return 0 típusúnak kell lennie; )
    • Az frd() függvény meghívása hiba. A tényleges argumentum int típusú, és double típusúvá kell alakítani, hogy megfeleljen a formális referenciaparaméternek. Ennek az átalakításnak az eredménye egy ideiglenes változó. Mivel egy hivatkozásnak nincs const specifikációja, az ilyen változók nem használhatók inicializálására.
      Íme egy másik példa, ahol nincs egyezés a formális referenciaparaméter és a tényleges argumentum között:
    • B osztály; void takeB(B&); BgiveB(); int main() (
      venniB(adniB()); // hiba: a paraméternek const B & típusúnak kell lennie
      visszatérés 0;
      }
    • A takeB() függvény meghívása hiba. A tényleges argumentum a visszatérési érték, azaz. egy ideiglenes változó, amely nem használható hivatkozás inicializálására a const specifikátor nélkül.
      Mindkét esetben azt látjuk, hogy ha a formális referenciaparaméterben szerepel a const specifikátor, akkor pontos egyezés állapítható meg közötte és a tényleges argumentum között.

    Vegye figyelembe, hogy mind az l-érték-r-érték konverzió, mind a referencia inicializálás pontos egyezésnek minősül. Ebben a példában az első függvényhívás hibát eredményez:

    Void print(int); voidprint(int&); intiobj;
    int &ri = iobj; int main() (
    print(iobj); // hiba: kétértelműség
    print(ri); // hiba: kétértelműség
    nyomtatás(86); // helyes: call print(int)
    visszatérés 0;
    }

    Az iobj objektum egy argumentum, amely mindkét print() függvényre leképezhető, azaz a hívás kétértelmű. Ugyanez vonatkozik a következő sorra is, ahol az ri hivatkozás mindkét print() függvénynek megfelelő objektumot jelöli. A harmadik felhívással viszont minden rendben van. Számára a print(int&) nem jól bevált. Az egész konstans egy r-érték, így nem inicializálhat referenciaparamétert. Az egyetlen beépített függvény a print(86) meghívására a print(int), ezért ezt túlterhelési felbontásban választják.
    Röviden, ha a formális argumentum egy hivatkozás, akkor a tényleges argumentum akkor pontos egyezés, ha képes inicializálni a hivatkozást, és másként nem.

    9.6. gyakorlat

    Nevezzen meg két triviális transzformációt, amelyek megengedettek a pontos egyezés megállapítása során.

    9.7. gyakorlat

    Mi a rangja az egyes argumentumkonverzióknak a következő függvényhívásokban:

    (a) void print(int *, int); int arr; print(arr, 6); // függvényhívás (b) void manip(int, int); manip("a", "z"); // függvényhívás (c) int calc(int, int); dupla dobj; double = calc(55.4, dobj) // függvényhívás (d) void set(const int *); int*pi; set(pi); // függvényhívás

    9.8. gyakorlat

    Az alábbi hívások közül melyik a hibás, mert nincs konverzió az aktuális argumentum típusa és a formális paraméter között:

    (a) enum Stat( Fail, Pass ); érvénytelen teszt (Stat); szöveg(0); // függvényhívás (b) void reset(void *); reset(0); // függvényhívás (c) void set(void *); int*pi; set(pi); // függvényhívás (d) #include lista opera(); void print(oper()); // függvényhívás (e) void print(const int); intiobj; print(iobj); // függvényhívás

    9.4. A funkció túlterhelési felbontásának részletei

    A 9.2. szakaszban már említettük, hogy a funkció túlterhelés-feloldási folyamata három lépésből áll:

    1. Állítsa be a jelölt függvények készletét egy adott hívás feloldásához, valamint az aktuális argumentumlista tulajdonságait.
    2. A jelöltek halmazából válassza ki a beállított függvényeket - azokat, amelyek a tényleges argumentumok adott listájával hívhatók, figyelembe véve számukat és típusukat.
    3. Válassza ki a hívásnak leginkább megfelelő függvényt a tényleges argumentumokra alkalmazandó transzformációk rangsorolásával, hogy azok megfeleljenek a létrehozott függvény formális paramétereinek.

    Készen állunk arra, hogy részletesebben megvizsgáljuk ezeket a lépéseket.

    9.4.1. Jelölt funkciók

    A jelölt függvény olyan függvény, amelynek neve megegyezik a meghívottéval. A jelölteket kétféleképpen választják ki:

    • a függvény deklarációja a hívás pontján látható. A következő példában
      void f(); void f(int); void f(double, double = 3,4); void f(char*, char*); int main() (
      f(5,6); // négy jelölt van a felhívás megoldására
      visszatérés 0;
      }
    • mind a négy f() függvény teljesíti ezt a feltételt. Ezért a jelöltek halmaza négy elemet tartalmaz;
    • ha az aktuális argumentum típusa egy névtérben van deklarálva, akkor ennek a térnek a hívott függvényével azonos nevű tagfüggvényei hozzáadódnak a jelölt halmazhoz: namespace NS ( class C ( /* ... */ ); void takeC( C&); ) // A cobj típus az NS névtérben deklarált C osztály
      NS::Cobj; int main() (
      // a takeC() függvények egyike sem látható a hívóponton
      takeC(cobj); // helyes: NS::takeC(C&) meghívva,
      // mert az argumentum NS::C típusú, tehát
      // figyelembe veszi a takeC() függvényt,
      // az NS névtérben deklarálva
      visszatérés 0;
      }

    Így a jelöltek halmaza a szakszervezet függvénykészlet, látható a hívási pontnál, és a tényleges argumentumtípusokkal azonos névtérben deklarált függvénykészlet.
    A hívás pillanatában látható túlterhelt függvények halmazának azonosításakor a korábban már tárgyalt szabályok érvényesek.
    A beágyazott hatókörben deklarált függvény inkább elrejti, semmint túlterheli a külső hatókör azonos nevű függvényét. Ilyen helyzetben csak a beágyazott hatókörön belüli függvények lesznek jelöltek, pl. azokat, amelyek híváskor nincsenek elrejtve. A következő példában a hívóponton látható jelölt függvények a format(double) és a format(char*):

    Char* formátum(int); void g() ( char *formátum(double); char* formátum(char*); formátum(3); // formátum (double) hívása)
    }

    Mivel a globális hatókörben deklarált format(int) rejtett, nem szerepel a jelölt függvények halmazában.
    A jelöltek bemutatása a hívás helyén látható nyilatkozatokkal lehetséges:

    Névtér libs_R_us ( int max(int, int); double max(double, double); ) char max(char, char); void func()
    {
    // a névtér függvényei láthatatlanok
    // mindhárom hívás a max(char, char) globális függvény javára van feloldva
    max(87, 65);
    max(35,5; 76,6);
    max("J", "L");
    }

    A libs_R_us névtérben definiált max() függvények láthatatlanok a hívási pontban. Az egyetlen látható a max() függvény a globális hatókörből; csak ez szerepel a jelölt függvények halmazában, és mindhárom func () hívásnál meghívásra kerül. A use deklaráció segítségével megjeleníthetjük a max() függvényeket a libs_R_us névtérből. Hová kell tenni a használati nyilatkozatot? Ha belefoglalja a globális hatókörbe:

    Char max(char, char); a libs_R_us::max; // use-declaration

    majd a libs_R_us max() függvényei hozzáadódnak a túlterhelt függvények halmazához, amely már tartalmazza a globális hatókörben deklarált max() függvényt. Most már mindhárom függvény látható a func()-ban, és jelöltté válik. Ebben a helyzetben a func() hívások megoldása a következőképpen történik:

    Void func() ( max(87, 65); // meghívva: libs_R_us::max(int, int) max("J", "L"); // hívva::max(char, char) )

    De mi történik, ha a func() függvény helyi hatókörébe beszúrunk egy use deklarációt, amint az ebben a példában látható?

    void func() ( // a-deklaráció használata a libs_R_us::max használatával; // ugyanazok a függvényhívások, mint fent
    }

    A max() függvények közül melyik fog szerepelni a jelölthalmazban? Emlékezzünk arra, hogy a deklarációk használata egymásba van ágyazva. Ha a helyi hatályban van ilyen nyilatkozat globális funkció max(char, char) rejtett, így csak a

    Libs_R_us::max(int, int); libs_R_us::max(double, double);

    Ők a jelöltek. Most a func() hívások a következőképpen vannak megoldva:

    Void func() ( // using-declaration // A max(char, char) globális függvény el van rejtve a libs_R_us::max használatával; max(87, 65); // libs_R_us::max(int, int)
    max(35,5; 76,6); // A libs_R_us::max(double, double) meghívásra kerül
    max("J", "L"); // a libs_R_us::max(int, int) meghívása
    }

    A használó direktívák a függvényjelölt halmaz összetételét is befolyásolják. Tegyük fel, hogy úgy döntünk, hogy a libs_R_us névtér max() függvényeit a func()-ban láthatóvá tesszük. Ha a következő direktívát a globális hatókörbe helyezzük, akkor a jelölt függvények halmaza a max(char, char) globális függvényből, valamint a libs_R_us-ban deklarált max(int, int) és max(double, double) függvényekből áll majd:

    Névtér libs_R_us ( int max(int, int); double max(double, double); ) char max(char, char);
    névtér használatával libs_R_us; // void func() direktíva használatával
    {
    max(87, 65); // a libs_R_us::max(int, int) meghívása
    max(35,5; 76,6); // A libs_R_us::max(double, double) meghívásra kerül
    }

    Mi történik, ha a use direktívát a helyi hatókörbe helyezi, mint a következő példában?

    Void func() ( // a-directive használata névtér használatával libs_R_us; // ugyanazok a függvényhívások, mint fent
    }

    A max() függvények közül melyik lesz a jelöltek között? Emlékezzünk vissza, hogy a használó direktíva láthatóvá teszi a névtér tagjait, mintha azon a területen kívül lennének deklarálva, azon a ponton, ahol egy ilyen direktíva el van helyezve. Példánkban a libs_R_us tagjai a func() függvény lokális hatókörében láthatók, mintha téren kívülre lettek volna deklarálva - a globális hatókörben. Ebből következik, hogy a func()-on belül látható túlterhelt függvények halmaza megegyezik az előzővel, azaz. magába foglalja

    Max(char, char); libs_R_us::max(int, int); libs_R_us::max(double, double);

    A use-direktíva megjelenik a lokális vagy globális hatókörben, a func() függvény hívásainak felbontását ez nem érinti:

    Void func() ( a libs_R_us névtér használatával; max(87, 65); // a libs_R_us::max(int, int) meghívása
    max(35,5; 76,6); // A libs_R_us::max(double, double) meghívásra kerül
    max("J", "L"); // hívva::max(int, int)
    }

    Tehát a jelöltek halmaza a hívási ponton látható függvényekből áll, beleértve azokat, amelyeket a use-declarations és a use-directivek vezetnek be, valamint a tényleges argumentumtípusokhoz tartozó névterekben deklarált függvényeket. Például:

    Névtér basicLib ( int print(int); double print(double); ) névtér matrixLib ( osztálymátrix ( /* ... */ ); void print(const maxtrix &); ) void display() ( basicLib::print használatával matrixLib::matrix mObj;
    print(mObj); // maxtrixLib hívása::print(const maxtrix &) print(87); // A basicLib::print(const maxtrix &) meghívásra kerül
    }

    A print(mObj) jelöltek a basicLib::print(int) és basicLib::print(double) függvények display()-en belüli use-declarations, mivel ezek láthatók a hívóponton. Mivel a függvény tényleges argumentuma matrixLib::matrix típusú, a matrixLib névtérben deklarált print() függvény is jelölt lenne. Melyek a print(87) jelölt függvényei? Csak basicLib::print(int) és basicLib::print(double) látható a hívóponton. Mivel az argumentum int típusú, a többi jelölt keresése során a rendszer nem veszi figyelembe a további névtereket.

    9.4.2. Kialakult funkciók

    A jól bevált funkció az egyik jelölt. Formális paramétereinek listája vagy ugyanannyi elemet tartalmaz, mint a hívott függvény tényleges argumentumainak listája, vagy több. Ez utóbbi esetben azért további beállítások alapértelmezett értékek vannak megadva, különben a függvény nem hívható meg a megadott számú argumentummal. Ahhoz, hogy egy függvényt stabilnak lehessen tekinteni, minden tényleges argumentumból át kell konvertálni a megfelelő formális paraméter típusát. (Az ilyen átalakításokat a 9.3. pont tárgyalja.)
    A következő példában az f(5.6) függvény hívásának két beállított függvénye van: f(int) és f(double).

    void f(); void f(int); void f(double); void f(char*, char*); int main() (
    f(5,6); // 2 beállított függvény: f(int) és f(double)
    visszatérés 0;
    }

    Az f(int) függvény azért maradt fenn, mert csak egy formális paramétere van, ami megfelel a hívás tényleges argumentumainak. Ezenkívül létezik egy double típusú argumentum szabványos átalakítása int-re. Az f(double) függvény is megmaradt; van egy double típusú paramétere is, és ez pontosan megegyezik a tényleges argumentummal. Az f() és f(char*, char*) jelölt függvények kikerülnek a túlélő függvények listájából, mert nem hívhatók meg egyetlen argumentummal.
    A következő példában a format(3) egyetlen beállított függvénye a format(double). Bár a format(char*) jelölt egyetlen argumentummal is meghívható, a tényleges int argumentum típusáról nem történik átalakítás a formális paraméter char* típusára, ezért a függvény nem tekinthető jól bevált.

    Char* formátum(int); void g() ( // a globális függvényformátum(int) rejtett char* format(double); char* formátum(char*); format(3); // egyetlen beállított függvény van: format(double) )

    A következő példában mindhárom jelölt függvény képes lesz meghívni a max() függvényt a func()-on belül. Mindegyik két argumentummal hívható meg. Mivel a tényleges argumentumok int típusúak, pontosan megfelelnek a libs_R_us::max(int, int) függvény formális paramétereinek, és a libs_R_us::max(double, double) függvény paramétereinek típusaira önthetők. egész számok lebegőpontokká konvertálásával, valamint libs_R_us::max(char, char) függvényparaméterek típusaira egész típusú konverzióval.


    a libs_R_us::max; char max(char, char);
    void func()
    {
    // mindhárom max() függvény jól bevált
    max(87, 65); // a libs_R_us::max(int, int) használatával hívjuk meg
    }

    Ne feledje, hogy a több paraméterrel rendelkező jelölt függvény azonnal megszűnik, amint azt tapasztalja, hogy az egyik tényleges argumentum nem adható át a megfelelő formális paraméter típusára, még akkor is, ha az összes többi argumentum esetében létezik ilyen konverzió. A következő példában a min(char *, int) függvény ki van zárva a túlélők halmazából, mivel az első int argumentum típusát nem lehet átalakítani a megfelelő char * paraméter típusára. És ez annak ellenére történik, hogy a második argumentum pontosan megegyezik a második paraméterrel.

    extern double min(double, double); extern double min(char*, int); void func()
    {
    // egy jelölt függvény min(double, double)
    min (87, 65); // hívás min(double, double)
    }

    Ha a jelöltek halmazából kizárva az összes nem megfelelő paraméterszámú függvényt és azokat, amelyek paramétereihez nem történt megfelelő transzformáció, nincsenek állandóak, akkor a függvényhívás feldolgozása fordítási hibával zárul. Ebben az esetben azt mondják, hogy nem találtak egyezést.

    Érvénytelen nyomtatás (unsigned int); voidprint(char*); voidprint(char); int*ip;
    osztály SmallInt ( /* ... */ );
    SmallInt si; int main() (
    print(ip); // hiba: nincs beállított függvény: nem található egyezés
    nyomtat(si); // hiba: nincs beállított függvény: nem található egyezés
    visszatérés 0;
    }

    9.4.3. Legjobb bevált funkció

    A legjobbnak azok a megállapított függvények tekinthetők, amelyek formális paraméterei leginkább megfelelnek az aktuális argumentumok típusainak. Minden ilyen függvény esetében az egyes argumentumokra alkalmazott típuskonverziókat rangsorolják, hogy meghatározzák, mennyire egyezik a paraméterrel. (A 6.2. szakasz leírja a támogatott típuskonverziókat.) A legjobban bevált függvény egy olyan függvény, amely egyszerre teljesít két feltételt:

    • az argumentumokra alkalmazott transzformációk nem rosszabbak, mint bármely más jól bevált függvény meghívásához szükséges transzformációk;
    • legalább egy argumentum esetében az alkalmazott transzformáció jobb, mint bármely más jól bevált függvény ugyanazon argumentumánál.

    Előfordulhat, hogy ahhoz, hogy a tényleges argumentumot a megfelelő formális paraméter típusára öntsük, több konverziót kell végrehajtani. Tehát a következő példában

    Int arr; void putValues(const int *); int main() (
    putValues(arr); // 2 átalakítás szükséges
    // tömb mutató + specifikáló konverzió
    visszatérés 0;
    }

    az arr argumentum „három int tömb” típusból „pointer to const int” típusba történő átadásához egy konverziós sorozat kerül alkalmazásra:

    1. Tömb mutató konverzió, amely három intből álló tömböt alakít át int mutatóvá.
    2. Specifikus konverzió, amely az int mutatót const int mutatóvá alakítja.

    Ezért helyesebb lenne azt mondani, hogy ahhoz, hogy a tényleges argumentumot a beállított függvény formális paraméterének típusára öntsük, konverziók sorozatára van szükség. Mivel nem egy, hanem több transzformációt alkalmazunk, a függvénytúlterhelés-feloldási folyamat harmadik lépése tulajdonképpen a transzformációk sorozatait rangsorolja.
    Egy ilyen sorozat rangját a benne foglalt transzformációk közül a legrosszabb rangnak tekintjük. A 9.2. szakaszban leírtak szerint a típuskonverziók rangsorolása a következő: a pontos egyezés jobb, mint a típusbővítmény, a típusbővítmény pedig jobb, mint a normál konverzió. Az előző példában mindkét változtatásnak pontos egyezési rangja van. Ezért az egész sorozatnak ugyanaz a rangja.
    Egy ilyen gyűjtemény több átalakításból áll, amelyeket a bemutatott sorrendben alkalmaznak:

    l-érték konverzió -> típusbővítés vagy szabványos konverzió -> specifikáló konverzió

    Az l-érték konverzió kifejezés a 9.2. szakaszban tárgyalt pontos egyezésű átalakítások közül az első háromra vonatkozik: l-érték-r-érték konverzióra, tömb-mutató konverzióra és függvény-mutató konverzióra. A transzformációk sorozata nulla vagy egy l-érték konverzióból áll, ezt követi a nulla vagy egy típuskiterjesztés vagy szabványos konverzió, végül pedig a nulla vagy egy specifikációs konverzió. Mindegyik fajtából csak egy transzformáció alkalmazható tényleges argumentum leadására egy formális paraméter típusára.

    A leírt sorozatot standard transzformációk sorozatának nevezzük. A felhasználó által definiált konverziók sorozata is tartozik egy tag-átalakító funkcióhoz. (A konvertereket és a felhasználó által meghatározott konverziók sorozatait a 15. fejezet tárgyalja.)

    Melyek azok a sorozatok, amelyekben a tényleges argumentumok megváltoznak a következő példában?

    Névtér libs_R_us ( int max(int, int); double max(double, double); ) // using-declaration
    a libs_R_us::max; void func()
    {
    char c1, c2;
    max(c1, c2); // a libs_R_us::max(int, int) meghívása
    }

    A max() függvényhívás argumentumai char típusúak. Az argumentumtranszformációk sorrendje a libs_R_us::max(int,int) függvény meghívásakor a következő:

    1a. Mivel az argumentumokat érték adja át, az l-érték r-értékké alakítása kivonja a c1 és c2 argumentumok értékeit.

    2a. Az argumentumok típusbővítéssel konvertálódnak char-ból int-be.
    Az argumentumtranszformációk sorrendje a libs_R_us::max(double,double) függvény meghívásakor a következő:
    1b. Az l-érték r-értékké való konvertálásával a c1 és c2 argumentumok értékei kivonásra kerülnek.

    2b. Az integer és a float típusok közötti szabványos konverzió a char típustól a double típusig vetíti az argumentumokat.

    Az első sorozat rangja típusbővítés (a legrosszabb változás alkalmazott), míg a másodiké egy szabványos konverzió. Mivel a típuskiterjesztés jobb, mint a típuskonverzió, a libs_R_us::max(int,int) függvényt választottuk a legjobban ehhez a híváshoz.
    Ha az argumentumtranszformációk rangsoroló sorozatai egyetlen jól bevált függvényt sem tárnak fel, akkor a hívás kétértelműnek minősül. Ebben a példában a calc() mindkét hívása a következő sorrendet igényli:

    1. Konvertálja az l-értéket r-értékké az i és j argumentumértékek kinyeréséhez.
    2. Szabványos konverzió a tényleges argumentumok megfelelő formális paraméterekbe való öntéséhez.

    Mivel nem lehet megmondani, hogy ezek közül a sorozatok közül melyik jobb a másiknál, a hívás kétértelmű:

    Int i, j; extern long calc(hosszú, hosszú); extern double calc(double, double); void jj() ( // hiba: kétértelműség, nincs legjobb egyezés
    calc(i,j);
    }

    A specifikus konverzió (const vagy volatile specifikátor hozzáadása a mutatót megszólító típushoz) pontos egyezési ranggal rendelkezik. Ha azonban két transzformációs sorozat csak annyiban tér el, hogy az egyiknek a végén van egy további specifikáló transzformációja, akkor az anélküli szekvenciát tekintjük jobbnak. Például:

    void reset(int *); void reset(const int *); int*pi; int main() (
    reset(pi); // a specifikációk konvertálása nélkül jobb:
    // válassza ki a reset(int *)
    visszatérés 0;
    }

    Az első reset(int*)-jelölt függvény tényleges argumentumára alkalmazott szabványos konverziók sorozata pontos egyezés, csak az l-értékről az r-értékre kell lépni az argumentum értékének kinyeréséhez. A második jelölt függvény reset(const int *) esetében szintén az l-érték r-érték transzformációt alkalmazzuk, de ezt követi a specifikátor átalakítás is, amely a kapott értéket mutatóból intbe mutatóba const int-be adja. Mindkét sorozat pontosan egyezik, de nincs kétértelműség. Mivel a második szekvencia abban különbözik az elsőtől, hogy a végén egy specifikáló transzformáció található, az ilyen transzformáció nélküli szekvencia tekinthető a legjobbnak. Ezért a reset(int*) a legjobb álló funkció.
    Íme egy másik példa, ahol az öntési specifikációk befolyásolják, hogy melyik sorozatot választjuk:

    int kivonat(void*);
    int kivonat(const void *);

    int main() (
    kivonat(pi); // kivonat kiválasztása(void *)
    visszatérés 0;
    }

    Itt két beállított függvény hívható meg: extract(void*) és extract(const void*). Az extrakció(void*) függvény transzformációinak sorozata abból áll, hogy az l-értéket r-értékké alakítjuk a kivonathoz érvértékek, majd a szokásos mutatókonverzió következik: pointer to int to a pointer to void. Az extraktum(const void*) függvény esetében ez a szekvencia a specifikációk további konverziójával tér el az eredménytípus mutatóból void-ba mutatóból const void-ba való átadásával. Mivel a szekvenciák csak ebben a transzformációban különböznek egymástól, az elsőt választottuk megfelelőbbnek, és ezért az extra (const void*) függvény lesz a legjobban álló.
    A const és a volatile specifikációk szintén befolyásolják a referenciaparaméterek inicializálásának rangsorát. Ha két ilyen inicializálás csak a const és a volatile specifikátor hozzáadásával tér el, akkor az extra specifikátor nélküli inicializálás jobbnak tekinthető a túlterhelési felbontásban:

    #beleértve void manip(vektor &); void manip(const vektor &); vektor f();
    külső vektor vec; int main() (
    manip(vec); // select manip(vector &)
    manip(f()); // select manip(const vector &)
    visszatérés 0;
    }

    Az első hívásban a referencia inicializálása bármely függvényhíváshoz pontosan megegyezik. De ez a kihívás továbbra sem lesz kétértelmű. Mivel mindkét inicializálás ugyanaz, kivéve egy további const specifikáció jelenlétét a második esetben, az ilyen specifikáció nélküli inicializálást jobbnak tartják, így a túlterhelés a jól bevált manip(vektor javára lesz megoldva &).
    A második híváshoz csak egy jól bevált függvény manip(const vektor &). Mivel a tényleges argumentum egy ideiglenes változó, amely tartalmazza az f() által visszaadott eredményt, az ilyen argumentum egy r-érték, amely nem használható a manip(vector) nem állandó formális referenciaparaméterének inicializálására &). Ezért az egyetlen jól bevált manip(const vektor &).
    Természetesen a függvényeknek több tényleges argumentuma is lehet. Az állandóak közül a legjobbat az összes argumentum transzformációs sorrendjének figyelembevételével kell kiválasztani. Vegyünk egy példát:

    extern int ff(char*, int); extern int ff(int, int); int main() ( ff(0, "a"); // ff(int, int)
    visszatérés 0;
    }

    Az ff() függvény, amely két int argumentumot tartalmaz, a legjobb állapotú függvénynek lett kiválasztva a következő okok miatt:

    1. az első érve jobb. A 0 pontosan megegyezik egy formális int paraméterrel, míg a szabványos mutatókonverzió szükséges a char * típusú paraméterhez;
    2. második érve is azonos rangú. A char típusú "a" argumentumhoz a két függvény bármelyikének második formális paraméterével való megfelelés megállapításához egy konverziós sorozatot kell alkalmazni, amelynek típuskiterjesztés rangja van.

    Íme egy másik példa:

    int compute(const int&, short); int compute(int&, double); külső intiobj;
    int main() (
    compute(iobj, "c"); // számítás(int&, double)
    visszatérés 0;
    }

    Mindkét compute(const int&, short) és compute(int&, double) függvény megmaradt. A másodikat választották a legjobbnak a következő okok miatt:

    1. az első érve jobb. Az első létrehozott függvény referencia-inicializálása rosszabb, mert ehhez egy const specifikátort kell hozzáadni, amelyre nincs szükség a második függvényhez;
    2. második érve is azonos rangú. A char típusú "c" argumentumhoz a két függvény bármelyikének második formális paraméterével való megfelelés megállapításához standard transzformáció rangú transzformációsorozatot kell alkalmazni.

    9.4.4. Érvek alapértelmezett értékekkel

    Az alapértelmezett értékekkel rendelkező argumentumok sok jól bevált funkciót kiterjeszthetnek. A maradék függvények, amelyeket a tényleges argumentumok adott listájával hívunk meg. De egy ilyen függvénynek több formális paramétere is lehet, mint a ténylegesen megadott argumentum, abban az esetben, ha minden paraméterhez van valamilyen alapértelmezett érték, amely nincs megadva:

    extern void ff(int); extern void ff(hosszú, int = 0); int main() (
    ff(2L); // egyezik: ff(long, 0); ff(0; 0); // egyezik: ff(long, int);
    ff(0); // egyezik az ff(int);
    ff(3,14); // hiba: kétértelműség
    }

    Az első és a harmadik hívásnál az ff() függvény ki van jelölve, annak ellenére, hogy csak egy argumentumot adtak át. Ennek oka a következő okok:

    1. van egy alapértelmezett érték a második formális paraméterhez;
    2. a long típusú első paraméter pontosan megfelel az első hívás tényleges argumentumának, és a harmadik hívás argumentumának típusára önthető egy szabványos konverzió rangú szekvenciával.

    Az utolsó hívás kétértelmű, mert mindkét beállított függvény kiválasztható a standard transzformáció alkalmazásával az első argumentumra. Az ff(int) függvény csak azért nem előnyös, mert egy paramétere van.

    9.9. gyakorlat

    Magyarázza el, mi történik, ha megoldja a túlterhelést a compute() meghívására a main() függvényen belül. Milyen jellemzői vannak a jelölteknek? Melyikük áll majd meg az első lépés után? Milyen transzformációs sorozatot kell alkalmazni az aktuális argumentumra, hogy az megfeleljen az egyes létrehozott függvények formális paramétereinek? Melyik funkció lesz a legjobb állás?

    Névtér primerLib ( void compute(); void compute(const void *); ) primerLib::compute;
    void compute(int);
    void compute(double, double = 3,4);
    void compute(char*, char* = 0); int main() (
    számítás(0);
    visszatérés 0;
    }

    Mi történik, ha a use deklaráció a main()-ba kerül a compute() meghívása előtt? Válaszoljon ugyanazokra a kérdésekre.

    Amikor függvényeket határoz meg a programjaiban, meg kell adnia a függvény visszatérési típusát, valamint a paraméterek számát és típusát. A múltban (ha C nyelven programozott), amikor volt egy add_values ​​nevű függvénye, amely két egész számon működött, és hasonló függvényt akart használni három egész érték hozzáadásához, akkor létre kellett volna hoznia egy függvényt más név. Használhatja például az add_two_values ​​​​és a add_three_values. Hasonlóképpen, ha egy hasonló függvényt szeretne használni lebegőértékek hozzáadásához, akkor egy másik, más nevű függvényre lesz szüksége. A függvénykettőzés elkerülése érdekében a C++ lehetővé teszi több, azonos nevű függvény definiálását. A fordítás során a C++ figyelembe veszi az egyes függvények által használt argumentumok számát, majd pontosan a kívánt függvényt hívja meg. Ha a fordító több funkció közül választhat, azt túlterhelésnek nevezzük. Ebből az oktatóanyagból megtudhatja, hogyan kell használni a túlterhelt függvényeket. A lecke végére a következő alapfogalmakat sajátítja el:

    A funkció túlterhelése lehetővé teszi, hogy ugyanazt a nevet használja több funkcióhoz különböző típusok paramétereket.

    A függvények túlterheléséhez egyszerűen definiáljon két azonos nevű és visszatérési típusú függvényt, amelyek a paraméterek számában vagy típusában különböznek.

    A függvénytúlterhelés a C++ nyelv olyan jellemzője, amely nem található meg a C nyelvben. Mint látni fogja, a funkciótúlterhelés meglehetősen kényelmes, és javíthatja a programok olvashatóságát.

    ELSŐ BEVEZETÉS A FUNKCIÓ TÚLTERHELÉSÉHEZ

    A függvénytúlterhelés lehetővé teszi a programok számára, hogy több függvényt definiáljanak azonos névvel és visszatérési típussal. Például a következő program túlterheli az add_values ​​nevű függvényt. Az első függvénydefiníció két int értéket ad hozzá. A második függvénydefiníció hozzáadja a három értéket. A fordítás során a C++ helyesen határozza meg a használandó függvényt:

    #beleértve

    int add_values(int a,int b)

    {
    return(a+b);
    )

    int add_values ​​(int a, int b, int c)

    (
    return(a+b+c);
    )

    {
    cout<< «200 + 801 = » << add_values(200, 801) << endl;
    cout<< «100 + 201 + 700 = » << add_values(100, 201, 700) << endl;
    }

    Amint látható, a program két függvényt definiál add_values ​​néven.Az első függvény két int értéket ad hozzá, míg a második három értéket. Nem kell konkrétan semmit tennie, hogy figyelmeztesse a fordítót a túlterhelésre, csak használja. A fordító a program által biztosított opciók alapján kitalálja, hogy melyik függvényt használja.

    Hasonlóképpen, a következő MSG_OVR.CPP program túlterheli a show_message függvényt. Az első show_message nevű függvény egy szabványos üzenetet jelenít meg, nem adnak át neki paramétereket. A második a neki átadott üzenetet, a harmadik pedig két üzenetet ad ki:

    #beleértve

    void show_message(void)

    {
    cout<< «Стандартное сообщение: » << «Учимся программировать на C++» << endl;
    }

    void show_message(char *message)

    {
    cout<< message << endl;
    }

    void show_message(char *első, char *második)

    {
    cout<< first << endl;
    cout<< second << endl;
    }

    {
    show_message();
    show_message("Tanulj meg programozni C++ nyelven!");
    show_message("Nincsenek előítéletek a C++-ban!", "A túlterhelés menő!");
    }

    AMIKOR TÚLTERHELÉS SZÜKSÉGES

    A túlterhelés egyik legáltalánosabb felhasználási esete, hogy egy függvényt használunk, hogy különböző paraméterekkel meghatározott eredményt állítsunk elő. Tegyük fel például, hogy a programjában van egy day_of_week nevű függvény, amely a hét aktuális napját adja vissza (0 a vasárnap, 1 a hétfő, ..., 6 a szombat). A program túlterhelheti ezt a függvényt, így helyesen adja vissza a hét napját, ha Julian-napot ad meg paraméterként, vagy ha adott egy napot, hónapot és évet:

    int_week_nap(int julian_day)

    {
    // Operátorok
    }

    int_week_nap (int hónap, int nap, int év)

    {
    // Operátorok
    }

    Miközben a következő leckékben az objektum-orientált programozást vizsgálja C++ nyelven, a függvények túlterhelését fogja használni programjai teljesítményének növelésére.

    A funkciók túlterhelése javítja a program olvashatóságát

    A C++ függvény túlterhelése lehetővé teszi, hogy a programok több, azonos nevű függvényt definiáljanak. A túlterhelt függvényeknek azonos típusú* értékeket kell visszaadniuk, de a paraméterek számában és típusában eltérhetnek. A funkciótúlterhelés megjelenése előtt a C++-ban a C programozóknak több, majdnem azonos nevű függvényt kellett létrehozniuk. Sajnos az ilyen funkciókat használni kívánó programozóknak emlékezniük kellett arra, hogy melyik paraméterkombináció melyik függvénynek felel meg. Másrészt a függvénytúlterhelés leegyszerűsíti a programozók dolgát azáltal, hogy csak egy függvény nevére kell emlékezni.* A túlterhelt függvényeknek nem kell azonos típusú értékeket visszaadniuk, mivel a fordító egyedileg azonosítja a függvényt a neve és a függvénye alapján. érvek halmaza. A fordító számára az azonos nevű, de eltérő argumentumtípusú függvények különböző függvények, így a visszatérési típus az egyes függvények kiváltsága. - kb.ford.

    AMIT TUDNOD KELL

    A függvénytúlterhelés lehetővé teszi több definíció megadását ugyanahhoz a funkcióhoz. A fordítás során a C++ az átadott paraméterek száma és típusa alapján határozza meg, hogy melyik függvényt használja. Ebből az oktatóanyagból megtanulta, hogy könnyű túlterhelni a funkciókat. A 14. leckében megtudhatja, hogyan teszik egyszerűvé a C++ hivatkozások a függvényeken belüli paraméterek megváltoztatását. Mielőtt azonban továbblépne a 14. leckére, győződjön meg arról, hogy megtanulta a következő alapfogalmakat:

    1. A funkció túlterhelése több „nézetet” biztosít ugyanarról a funkcióról a programon belül.
    2. A függvények túlterheléséhez egyszerűen definiáljon több, azonos nevű és visszatérési típusú függvényt, amelyek csak a paraméterek számában és típusában különböznek egymástól.
    3. A fordítás során a C++ az átadott paraméterek száma és típusa alapján határozza meg, hogy melyik függvényt hívja meg.
    4. A funkciók túlterhelése leegyszerűsíti a programozást, mivel lehetővé teszi a programozóknak, hogy csak egy függvénynévvel dolgozzanak.

    Megjegyzés: Az előadás tárgyalja a beépített és túlterhelt függvények fogalmait, deklarálását és használatát a C++ programokban, a helyettesítés és a funkciótúlterhelés végrehajtásának mechanizmusait, javaslatokat tesz a programok hatékonyságának növelésére túlterheléssel vagy funkcióhelyettesítéssel.

    Az előadás célja: tanulja meg az inline (beágyazott) függvényeket és a függvénytúlterheléseket, tanuljon meg programokat fejleszteni függvénytúlterheléssel C ++ nyelven.

    Inline funkciók

    Egy függvény hívása, értékek átadása neki, érték visszaadása - ezek a műveletek elég sok CPU-időt vesznek igénybe. Általában egy függvény definiálásakor a fordító csak egy cellablokkot foglal le a memóriában az utasítások tárolására. A függvény meghívása után a programvezérlés átkerül ezekre az operátorokra, majd a függvényből visszatérve a program végrehajtása a függvényhívást követő sorról folytatódik.

    Ismételt hívások esetén a program minden alkalommal ugyanazt a parancskészletet dolgozza fel anélkül, hogy minden egyes hívásról külön másolatot készítene.

    Minden egyes ugrás a függvény utasításokat tartalmazó memóriaterületre lelassítja a program végrehajtását. Ha a függvény kicsi, időt takaríthat meg több hívásnál, ha utasítja a fordítót, hogy a függvénykódot közvetlenül a programba illessze be a hívás helyén. Az ilyen függvényeket ún helyettesítve. Ebben az esetben, ha a hatékonyságról beszélünk, mindenekelőtt a program végrehajtásának sebességéről van szó.

    Inline vagy Inline Functions olyan függvények, amelyek kódját a fordító közvetlenül a hívás helyére illeszti be, ahelyett, hogy a vezérlést a függvény egyetlen példányára adnák át.

    Ha a függvény inline, akkor a fordító nem a memóriában hozza létre az adott függvényt, hanem közvetlenül a hívás helyén a programkódba másolja a karakterláncait. Ez egyenértékű azzal, hogy függvényhívások helyett megfelelő mondatokat írunk a programba. Tehát a specifikáló Sorban függvényre határozza meg az ún belső kötés, ami abban rejlik, hogy a fordító a függvény meghívása helyett a kódja parancsait helyettesíti. A soron belüli függvények akkor használatosak, ha a függvénytörzs több utasításból áll.

    Ez a megközelítés lehetővé teszi a programvégrehajtás sebességének növelését, mivel a parancsok ki vannak zárva a programból. mikroprocesszor Az argumentumok átadásához és a függvény meghívásához szükséges.

    Például:

    /*függvény visszaadja a távolságot a ponttól koordinátákkal (x1,y1) a pontig koordinátákkal (x2,y2)*/ inline float Line(float x1,float y1,float x2, float y2) ( return sqrt(pow(x1-x2) ,2)+pow(y1-y2,2)); )

    Meg kell azonban jegyezni, hogy az inline függvények használata nem mindig vezet pozitív hatáshoz. Ha egy ilyen függvény többször meghívásra kerül a programkódban, akkor in összeállítási idő ennek a függvénynek annyi példánya kerül be a programba, ahány hívás van rá. Jelentősen megnő a programkód mérete, aminek következtében előfordulhat, hogy a program időbeni végrehajtásának hatékonyságában a várt növekedés elmarad.

    1. példa.

    #include "stdafx.h" #include névtér használata std; inline int Kocka(int x); int _tmain(int argc, _TCHAR* argv)( int x=2; float y=3; double z=4; cout<

    Felsoroljuk azokat az okokat, amelyek miatt a soron belüli specifikátorral rendelkező függvényt a rendszer normál nem soros függvényként kezeli:

    • az inline függvény rekurzív;
    • függvények, amelyek hívása a definíciója elé kerül;
    • függvények, amelyeket egy kifejezésben többször hívnak meg;
    • hurkokat, kapcsolókat, ill ugrás operátorok;
    • olyan függvények, amelyek túl nagyok a helyettesítéshez.

    A helyettesítés végrehajtására vonatkozó korlátozások többnyire végrehajtásfüggőek. Ha egy specifikátorral rendelkező függvényhez Sorban a fordító nem tudja végrehajtani a helyettesítést a kontextus miatt, amelyben a hívást elhelyezik, ekkor a függvény statikusnak minősül és kiadódik figyelmeztető üzenet.

    A beépített függvények másik jellemzője, hogy nem lehet megváltoztatni azokat anélkül, hogy a program minden részét újrafordítanák, amelyben ezek a függvények meghívásra kerülnek.

    Funkció túlterhelés

    A függvények programban történő definiálásakor meg kell adni a függvény által visszaadott érték típusát, valamint a paraméterek számát és mindegyik típusát. Ha a C++ nyelvnek volt egy add_values ​​nevű függvénye, amely két egész értékkel működött, és a programnak hasonló függvényt kellett használnia három egész érték átadásához, akkor egy másik nevű függvényt kellene létrehozni. Például: add_two_values és add_three_values . Hasonlóképpen, ha egy hasonló függvényt szeretne használni lebegő értékekkel, akkor egy másik függvényre van szüksége más néven. A függvénykettőzés elkerülése érdekében a C++ lehetővé teszi több, azonos nevű függvény definiálását. A fordítás során a C++ figyelembe veszi az egyes függvények által használt argumentumok számát, majd pontosan a kívánt függvényt hívja meg. Ha a fordító több funkció közül választhat, azt túlterhelésnek nevezzük.

    Funkció túlterhelés több, azonos nevű, de különböző paraméterekkel rendelkező függvény létrehozása. A különböző paraméterek azt jelentik, hogy minek kellene másnak lennie érvek száma funkciók és/vagy azok típusú. Vagyis a függvénytúlterhelés lehetővé teszi több függvény definiálását azonos névvel és visszatérési típussal.

    Funkciótúlterhelésnek is nevezik függvény polimorfizmus. A "poli" sokat jelent, a "morf" - egy forma, vagyis a polimorf függvény olyan függvény, amelyet különféle formák különböztetnek meg.

    A függvénypolimorfizmus alatt egy függvény több, különböző értékkel rendelkező túlterhelt változatának meglétét értjük a programban. A paraméterek számának vagy típusának megváltoztatásával két vagy több függvénynek azonos nevet adhat. Ebben az esetben nem lesz félreértés, mivel a kívánt funkciót a használt paraméterek egybeesése határozza meg. Ez lehetővé teszi olyan függvény létrehozását, amely képes egész számokkal, lebegőpontokkal vagy más típusú értékekkel működni anélkül, hogy külön neveket kellene létrehoznia az egyes függvényeknek.

    Így a használatnak köszönhetően túlterhelt funkciók, nem kell aggódnia amiatt, hogy a megfelelő függvényt hívja meg a programban, amely megfelel az átadott változók típusának. Ha túlterhelt függvényt hívunk meg, a fordító automatikusan meghatározza, hogy a függvény melyik verzióját kell használni.

    Például a következő program túlterheli az add_values ​​​​nevű függvényt. Az első függvénydefiníció két int típusú értéket ad hozzá. A második függvénydefiníció három int típusú értéket ad hozzá. A fordítás során a C++ helyesen határozza meg a használandó függvényt:

    #include "stdafx.h" #include névtér használata std; int add_values(int a,int b); int add_values ​​(int a, int b, int c); int _tmain(int argc, _TCHAR* argv)( cout<< "200+801=" << add_values(200,801) << "\n"; cout << "100+201+700=" << add_values(100,201,700) << "\n"; system("pause"); return 0; } int add_values(int a,int b) { return(a + b); } int add_values (int a, int b, int c) { return(a + b + c); }

    Így a program két add_values ​​nevű függvényt határoz meg. Az első függvény két értéket, míg a második három azonos int típusú értéket ad hozzá. A C++ fordító a program által biztosított opciók alapján határozza meg, hogy melyik függvényt használja.

    Funkciótúlterhelés használata

    A túlterhelés egyik legáltalánosabb felhasználási esete, hogy egy függvényt használunk, hogy különböző paraméterekkel meghatározott eredményt állítsunk elő. Tegyük fel például, hogy a programjában van egy day_of_week nevű függvény, amely a hét aktuális napját adja vissza (0 a vasárnap, 1 a hétfő, ... , 6 a szombat). Egy program túlterhelheti ezt a függvényt, így helyesen adja vissza a hét napját, ha paraméterként Julian-napot, vagy ha napot, hónapot és évet ad meg.

    int_hét_napja(int julian_day) ( // operátorok ) int_nap_of_week(int hónap, int nap, int év) ( // operátorok )

    Használata túlterhelt funkciók gyakran számos hibát követnek el. Például, ha a függvények csak a visszatérési típusban különböznek, de az argumentumtípusokban nem, akkor ezeknek a függvényeknek nem lehet ugyanaz a neve. A következő túlterhelési opció szintén érvénytelen:

    int függvény_neve(int argumentum_neve); int függvény_neve(int argumentum_neve); /* érvénytelen név túlterhelés: az argumentumok száma és típusa azonos */

    A funkció túlterhelésének előnyei:

    • a funkciók túlterhelése javul olvashatóság programok;
    • A C++ függvény túlterhelése lehetővé teszi a programok számára, hogy több, azonos nevű függvényt definiáljanak;
    • túlterhelt funkciók azonos típusú visszatérési értékek, de a paraméterek számában és típusában eltérhetnek;
    • A függvénytúlterhelés leegyszerűsíti a programozók dolgát azáltal, hogy megköveteli, hogy csak egy függvénynévre emlékezzenek, de akkor tudniuk kell, hogy melyik paraméterkombináció melyik függvénynek felel meg.

    2. példa.

    /*A túlterhelt függvényeknek ugyanaz a neve, de eltérő paraméterlisták és visszatérési értékek*/ #include "stdafx.h" #include névtér használata std; int átlag(int első_szám, int második_szám, int harmadik_szám); int átlag(int első_szám, int második_szám); int _tmain(int argc, _TCHAR* argv)(// fő függvény int szám_A = 5, szám_B = 3, szám_C = 10; cout<< "Целочисленное среднее чисел " << number_A << " и "; cout << number_B << " равно "; cout << average(number_A, number_B) << ".\n\n"; cout << "Целочисленное среднее чисел " << number_A << ", "; cout << number_B << " и " << number_C << " равно "; cout << average(number_A, number_B, number_C) << ".\n"; system("PAUSE"); return 0; }// конец главной функции /*функция для вычисления целочисленного среднего значения 3-х целых чисел*/ int average(int first_number, int second_number, int third_number) { return((first_number + second_number + third_number)/3); } // конец функции /*функция для вычисления целочисленного среднего значения 2-х целых чисел*/ int average(int first_number, int second_number) { return((first_number + second_number)/2); } // конец функции



    Hogyan érhető el a funkció túlterhelése C-ben? (tíz)

    Van mód a függvény túlterhelésére C-ben? Olyan egyszerű funkciókat nézek, amiket túl lehet terhelni, pl

    foo (int a) foo (char b) foo (float c , int d)

    Azt hiszem, nincs közvetlen út; Megkerülő megoldásokat keresek, ha vannak.

    Remélem, az alábbi kód segít megérteni a funkció túlterhelését

    #beleértve #beleértve int fun(int a, ...); int main(int argc, char *argv)( fun(1,10); fun(2"questionbank"); return 0; ) int fun(int a, ...)( va_list vl; va_start(vl,a ); if(a==1) printf("%d",va_arg(vl,int)); else printf("\n%s",va_arg(vl,char *)); )

    Úgy értem, úgy érted – nem, nem teheted.

    A va_arg függvényt így deklarálhatjuk

    void my_func(char* formátum, ...);

    De át kell adnia néhány információt a változók számáról és típusairól az első argumentumban – például a printf() .

    Igen tetszik.

    Itt adsz egy példát:

    void printA(int a)( printf("Hello world from printA: %d\n",a; ) void printB(const char *buff)( printf("Hello world from printB: %s\n",buff) ; ) #define Max_ITEMS() 6, 5, 4, 3, 2, 1, 0 #define __VA_ARG_N(_1, _2, _3, _4, _5, _6, N, ...) N #define _Num_ARGS_(... ) __VA_ARG_N(__VA_ARGS__) #define NUM_ARGS(...) (_Num_ARGS_(_0, ## __VA_ARGS__, Max_ITEMS()) - 1) #define CHECK_ARGS_MAX_LIMIT(t) if(NUM_ARGS(args) #define_CHMINKKARG_t) if(NUM_ARGS(args) #define print(x , args ...) \ CHECK_ARGS_MIN_LIMIT(1) printf("hiba");fflush(stdout); \ CHECK_ARGS_MAX_LIMIT(4) printf("hiba");fflush(stdout) ; \ (( \ if (__beépített_típusok_kompatibilis_p ((x), int)) \ printA(x, ##args); \ else \ printB (x,##args); \ )) int main(int argc, char* * argv) ( int a=0; print(a); print("hello"); return (EXIT_SUCCESS); )

    0-t és hello-t ad ki a printA és a printB-ből.

    Ha a fordítóprogramod gcc, és nem bánod, hogy minden alkalommal manuálisan frissíted, amikor új túlterhelést adsz hozzá, akkor csinálhatsz egy makromasszát, és a hívó fél szemszögéből a kívánt eredményt kapod, nem olyan szép írni... de ez így van lehetséges

    nézd meg a __builtin_types_compatibilis_p-t, majd használd olyan makrók meghatározására, amelyek

    #define foo(a) \ ((__builtin_types_compatibilis_p(int, a)?foo(a):(__builtin_types_compatible_p(float, a)?foo(a):)

    de igen, csúnya, csak nem

    EDIT: A C1X támogatást kap a típuskifejezésekhez, amelyek így néznek ki:

    #define cbrt(X) _Generic((X), long double: cbrtl, \ default: cbrt, \ float: cbrtf)(X)

    Mint már említettük, a túlterhelést abban az értelemben, ahogyan te érted, a C nem támogatja. A probléma megoldásának szokásos idióma az, hogy a függvény egy címkézett uniót vesz fel. Ezt a struct paraméterrel valósítjuk meg, ahol maga a struktúra valamilyen típusú típusjelzőből, például egy enumból és különböző értéktípusok uniójából áll. Példa:

    #beleértve typedef enum ( T_INT, T_FLOAT, T_CHAR, ) my_type; typedef struct ( my_type type; union ( int a; float b; char c; ) my_union; ) my_struct; void set_overload (my_struct *whatever) ( switch (bármi->type) ( T_INT eset: whatever->my_union.a = 1; break; case T_FLOAT: whatever->my_union.b = 2.0; break; case T_CHAR: whatever-> my_union.c = "3"; ) ) void printf_overload (my_struct *whatever) ( switch (bármilyen->type) ( case T_INT: printf("%d\n", whatever->my_union.a); break; case T_FLOAT : printf("%f\n", whatever->my_union.b); break; case T_CHAR: printf("%c\n", whatever->my_union.c); break; ) ) int main (int argc, char* argv) ( my_struct s; s.type=T_INT; set_overload(&s); printf_overload(&s); s.type=T_FLOAT; set_overload(&s); printf_overload(&s); s.type=T_CHAR; set_overload(&s) ; printf_overload(&s); )

    Nem használhatod a C++-t, és nem használhatod az összes többi C++ funkciót ezen kívül?

    Ha eddig nem volt szigorú C, akkor inkább variadic függvényeket javaslok.

    A következő megközelítés hasonló a a2800276, de néhány C99 makróval:

    // szükségünk van a `size_t` #include // argumentumtípusok a sum_arg_types enum elfogadásához ( SUM_LONG, SUM_ULONG, SUM_DOUBLE ); // egy argumentum tárolására szolgáló struktúra struct sum_arg ( enum sum_arg_types type; union ( long as_long; unioned long as_ulong; double as_double; ) érték; ); // egy tömb méretének meghatározása #define count(ARRAY) ((Sizeof (ARRAY))/(sizeof *(ARRAY))) // így lesz a függvényünk neve #define sum(...) _sum( count(sum_args(__VA_ARGS__)), sum_args(__VA_ARGS__)) // `struct sum_arg` tömb létrehozása #define sum_args(...) ((struct sum_arg )( __VA_ARGS__ )) // inicializálók létrehozása a sum argumentumokhoz #define (ÉRTÉK) ( SUM_LONG, ( .as_long = (VALUE) ) ) #define sum_ulong(VALUE) ( SUM_ULONG, ( .as_ulong = (VALUE) ) ) #define sum_double(VALUE) ( SUM_DOUBLE, ( .as_double = (ÉRTÉK) ) ) // polimorf függvényünk long double _sum(size_t count, struct sum_arg * args) ( long double value = 0; for(size_t i = 0; i< count; ++i) { switch(args[i].type) { case SUM_LONG: value += args[i].value.as_long; break; case SUM_ULONG: value += args[i].value.as_ulong; break; case SUM_DOUBLE: value += args[i].value.as_double; break; } } return value; } // let"s see if it works #include int main() ( unsigned long foo = -1; long double value = sum(sum_long(42), sum_ulong(foo), sum_double(1e10)); printf("%Le\n", érték); return 0; )

    Egyelőre az _Általános, mivel a kérdés _Általános, szabványos C (kiterjesztés nélkül) kapott támogatja a funkciók túlterhelését (és nem operátorok), köszönhetően a _Generic szónak _Generic a C11-ben. (a GCC a 4.9-es verzió óta támogatja)

    (A túlterhelés nem igazán "beépült" a kérdésben bemutatott módon, de könnyen elpusztítható valami, ami így működik.)

    A Generic egy fordítási idejű operátor, amely ugyanabba a családba tartozik, mint a sizeof és az _Alignof. Ezt a szabvány 6.5.1.1. szakasza írja le. Két fő paraméter kell hozzá: egy kifejezés (amely nem kerül kiértékelésre futás közben) és egy típus/kifejezés asszociációk listája, ami kicsit olyan, mint egy kapcsolóblokk. A _Generic lekéri a kifejezés általános típusát, majd „átvált” rá, hogy kiválassza a listából a típusának megfelelő végső eredménykifejezést:

    Generic(1, float: 2.0, char *: "2", int: 2, alapértelmezett: get_two_object());

    A fenti kifejezés 2-re értékeli ki - a vezérlőkifejezés típusa int , tehát az int-hez társított kifejezést választja ki értékként. Ezek egyike sem marad futásidőben. (Az alapértelmezett záradék kötelező: ha nem adja meg, és a típus nem egyezik, akkor fordítási hibát okoz.)

    A függvénytúlterhelésnél hasznos technika, hogy a C előfeldolgozó beillesztheti, és kiválaszthatja az eredménykifejezést a vezérlő makrónak átadott argumentumok típusa alapján. Tehát (példa a C szabványból):

    #define cbrt(X) _Általános((X), \ long double: cbrtl, \ default: cbrt, \ float: cbrtf \)(X)

    Ez a makró úgy valósítja meg a cbrt túlterhelt műveletet, hogy átadja az argumentum típusát a makrónak, kiválasztja a megfelelő implementációs függvényt, majd átadja az eredeti makrót ennek a függvénynek.

    Tehát az eredeti példa megvalósításához a következőket tehetjük:

    Foo_int (int a) foo_char (char b) foo_float_int (float c , int d) #define foo(_1, ...) _Generic((_1), \ int: foo_int, \ char: foo_char, \ float: _Generic(() ELSŐ(__VA_ARGS__,)), \int: foo_float_int))(_1, __VA_ARGS__) #define FIRST(A, ...) A

    Ebben az esetben használhatjuk az alapértelmezett: kötést a harmadik esetre, de ez nem mutatja be, hogyan lehet kiterjeszteni az elvet több argumentumra. A végeredmény az, hogy használhatja a foo(...)-ot a kódjában anélkül, hogy (sokat) aggódna az argumentumok típusa miatt.

    Bonyolultabb helyzetekben, például olyan függvényeknél, amelyek több argumentumot vagy változó számokat túlterhelnek, segédprogrammakrókat használhat a statikus küldési struktúrák automatikus generálására:

    void print_ii(int a, int b) ( printf("int, int\n"); ) void print_di(double a, int b) ( printf("double, int\n"); ) void print_iii(int a, int b, int c) ( printf("int, int, int\n"); ) void print_default(void) ( printf("ismeretlen argumentumok\n"); ) #define print(...) OVERLOAD(print, (__VA_ARGS__), \ (print_ii, (int, int)), \ (print_di, (double, int)), \ (print_iii, (int, int, int)) \) #define OVERLOAD_ARG_TYPES (int, double) #define OVERLOAD_FUNCTIONS (print) #include "activate-overloads.h" int main(void) ( print(44, 47); // kiírja az "int, int" parancsot print(4.4, 47); // kiírja a "double, int" print (1, 2, 3); // kiírja az "int, int, int" parancsot print(""); // kiírja az "ismeretlen argumentumokat" )

    (végrehajtás itt). Némi erőfeszítéssel lecsökkentheti a hőcserélőt, hogy a beépített túlterhelés-támogatással rendelkező nyelvhez nagyon hasonlítson.

    Félretéve már lehetett túlterhelni összeg argumentumokat (nem pedig típust) a C99-ben.

    Vegye figyelembe, hogy a C kiértékelésének módja megmozgathat. Ez a foo_int választja ki, ha például literális karaktert próbál átadni neki, és szüksége van némi foo_int-re, ha azt szeretné, hogy a túlterhelések támogassák a string literálokat. Összességében azonban nagyon klassz.

    Leushenko válasza nagyon klassz: csak a foo példa nem fordítja le a GCC-t, ami a foo(7) -n meghiúsul, leüti az ELSŐ makrót és a tényleges függvényhívást ((_1, __VA_ARGS__) , maradva egy extra vesszővel. problémákba ütközik, ha további túlterheléseket akarunk biztosítani, például a foo(double) .

    Ezért úgy döntöttem, hogy részletesebben válaszolok erre a kérdésre, beleértve az üres túlterhelés engedélyezését (foo(void) - ami némi gondot okozott...).

    Az ötlet most a következő: definiáljon egynél több általánost különböző makrókban, és válassza ki a megfelelőt az argumentumok számának megfelelően!

    Az érvek száma meglehetősen egyszerű, a következő válasz alapján:

    #define foo(...) SELECT(__VA_ARGS__)(__VA_ARGS__) #define SELECT(...) CONCAT(SELECT_, NARG(__VA_ARGS__))(__VA_ARGS__) #define CONCAT(X, Y) CONCAT_(X, Y) # definiálja a CONCAT_(X, Y) X ## Y

    Ez jó, a SELECT_1 vagy a SELECT_2 mellett döntünk (vagy több argumentum mellett, ha akarod/szükséged van rájuk), tehát csak a megfelelő definíciókra van szükségünk:

    #define SELECT_0() foo_void #define SELECT_1(_1) _Általános ((_1), \ int: foo_int, \ char: foo_char, \ double: foo_double \) #define SELECT_2(_1, _2) _Általános((_1), \ double : _Általános((_2), \ int: foo_double_int \) \)

    Először is, egy üres makróhívás (foo()) továbbra is létrehoz egy tokent, de az üres. Tehát a számláló makró valójában 1-et ad vissza 0 helyett, még akkor is, ha a makrót üresnek nevezik. "Könnyen" megoldhatjuk ezt a problémát, ha __VA_ARGS__ vesszővel a __VA_ARGS__ után feltételesen, attól függően, hogy a lista üres-e vagy sem:

    #define NARG(...) ARG4_(__VA_ARGS__ VESZSŐ(__VA_ARGS__) 4, 3, 2, 1, 0)

    azt nézett könnyű, de a COMMA makró elég nehéz; szerencsére ezzel a témával már Jens Gustedt blogja is foglalkozik (köszönöm, Jens). A fő trükk az, hogy a függvénymakrók nem bővülnek ki, hacsak nem követik zárójelek, további magyarázatért lásd Jens blogját... Csak egy kicsit módosítanunk kell a makrókat az igényeinknek megfelelően (rövidebb neveket és kevesebb érvet használok a rövidség kedvéért) .

    #define ARGN(...) ARGN_(__VA_ARGS__) #define ARGN_(_0, _1, _2, _3, N, ...) N #define HAS_COMMA(...) ARGN(__VA_ARGS__, 1, 1, 1, 0 ) #define SET_COMMA(...) , #define COMMA(...) SELECT_COMMA \ (\ HAS_COMMA(__VA_ARGS__), \ HAS_COMMA(__VA_ARGS__ ()), \ HAS_COMMA(SET_COMMA __VA_ARGS__), \_VA_COMMA_(_SET_COMMA)_(_) \) #define SELECT_COMMA(_0, _1, _2, _3) SELECT_COMMA_(_0, _1, _2, _3) #define SELECT_COMMA_(_0, _1, _2, _3) COMMA_ ## _0 ## _1 ## _3 #2 ## define COMMA_0000 , #define COMMA_0001 #define COMMA_0010 , // ... (az összes többi vesszővel együtt) #define COMMA_1111 ,

    És most jól vagyunk...

    Teljes kód egy blokkban:

    /* * demo.c * * Létrehozva: 2017-09-14 * Szerző: sboehler */ #include void foo_void(void) ( puts("void"); ) void foo_int(int c) ( printf("int: %d\n", c); ) void foo_char(char c) ( printf("char: %c \n", c); ) void foo_double(double c) ( printf("double: %.2f\n", c); ) void foo_double_int(double c, int d) ( printf("double: %.2f, int: %d\n", c, d); ) #define foo(...) SELECT(__VA_ARGS__)(__VA_ARGS__) #define SELECT(...) CONCAT(SELECT_, NARG(__VA_ARGS__))(__VA_ARGS__) # define CONCAT(X, Y) CONCAT_(X, Y) #define CONCAT_(X, Y) X ## Y #define SELECT_0() foo_void #define SELECT_1(_1) _Generic ((_1), \ int: foo_int, \ char : foo_char, \ double: foo_double \) #define SELECT_2(_1, _2) _Generic((_1), \ double: _Generic((_2), \ int: foo_double_int \) \) #define ARGN(...) ARGN_( __VA_ARGS__) #define ARGN_(_0, _1, _2, N, ...) N #define NARG(...) ARGN(__VA_ARGS__ COMMA(__VA_ARGS__) 3, 2, 1, 0) #define HAS_COMMA(...) ARGN(__VA_ARGS__, 1, 1, 0) #define SET_COMMA(...) , #define VESZSŐ(...) SELECT_COMMA \ (\ HAS_COMMA(__VA_ARGS__), \ HAS_COMMA(__VA_ARGS__ ()), \ HAS_C OMMA(SET_COMMA __VA_ARGS__), \ HAS_COMMA(SET_COMMA __VA_ARGS__ ()) \) #define SELECT_COMMA(_0, _1, _2, _3) SELECT_COMMA_(_0, _1, _2, _3) #MA_1, SELECT3,_COM COMMA_ ## _0 ## _1 ## _2 ## _3 COMMA_1001 , #define COMMA_1010 , #define COMMA_1011 , #define COMMA_1100 , #define COMMA_1101 , #define COMMA_1101 , #define COMMA_1101 , #define COMMA_1101 , #define COMMA_1111 mainc arint ** in ( foo(); foo(7); foo(10,12); foo(12,10; 7); foo((char)"s"); visszatérés 0; )

    A C++ lehetővé teszi egynél több definíció megadását funkciókat név ill operátor ugyanazon a területen, mint túlterhelés funkcióés kezelői túlterhelések illetőleg.

    A túlterhelt deklaráció egy olyan deklaráció, amely ugyanazon a néven van deklarálva, mint egy, ugyanabban a hatókörben korábban deklarált deklaráció, azzal a különbséggel, hogy mindkét deklaráció eltérő argumentumokkal és nyilvánvalóan eltérő definícióval (implementációval) rendelkezik.

    Ha túlterhelt telefont hív funkció vagy operátor, a fordító úgy határozza meg a legmegfelelőbb definíciót, hogy összehasonlítja a függvény vagy operátor meghívásához használt argumentumtípusokat a definíciókban megadott paramétertípusokkal. A legmegfelelőbb túlterhelt függvény vagy operátor kiválasztásának folyamatát nevezzük túlterhelési felbontás .

    Funkció túlterhelés C++ nyelven

    Ugyanazon függvénynévhez több definíció is tartozhat ugyanabban a hatókörben. A függvénydefinícióknak különbözniük kell egymástól az argumentumlistában szereplő argumentumok típusa és/vagy száma tekintetében. Nem lehet túlterhelni azokat a függvénydeklarációkat, amelyek csak a visszatérési típusukban különböznek egymástól.

    Alább látható egy példa, ahol ugyanaz a funkció nyomtatás() különböző típusú adatok nyomtatására szolgál -

    #beleértve névtér használata std; class printData ( public: void print(int i) ( cout<< "Printing int: " << i << endl; } void print(double f) { cout << "Printing float: " << f << endl; } void print(char* c) { cout << "Printing character: " << c << endl; } }; int main(void) { printData pd; // Call print to print integer pd.print(5); // Call print to print float pd.print(500.263); // Call print to print character pd.print("Hello C++"); return 0; }

    Nyomtatási belső: 5 Nyomtatási lebegés: 500.263 Nyomtatási karakter: Hello C++

    Kezelői túlterhelés C++ nyelven

    A C++-ban elérhető legtöbb beépített operátor felülírható vagy túlterhelhető. Így a programozó a felhasználó által meghatározott típusú operátorokat is használhatja.

    A túlterhelt operátorok speciális nevű függvények: az "operátor" kulcsszó, majd a definiálandó operátor szimbóluma. Mint minden más függvénynek, a túlterhelt operátornak is van visszatérési típusa és paraméterlistája.

    Box operátor+(const Box&);

    használható hozzáfűzés operátort deklarál kiegészítéseket két Box objektumot, és visszaadja a végső Box objektumot. A legtöbb túlterhelt operátor közönséges nem tag függvényként vagy osztálytag függvényként definiálható. Abban az esetben, ha a fenti függvényt nem osztálytag függvényként definiáljuk, akkor minden operandushoz két argumentumot kell átadnunk az alábbiak szerint:

    Box operátor+(const Box&, const Box&);

    Az alábbiakban egy példa mutatja be az operátor fogalmát, amikor egy tagfüggvénnyel töltik be. Itt egy objektumot adunk át argumentumként, amelynek tulajdonságait ezzel az objektummal lehet elérni, az objektumot, amely ezt az operátort hívja, a következővel lehet elérni ez kezelő az alábbiak szerint -

    #beleértve névtér használata std; class Box ( public: double getVolume(void) ( return hossz * szélesség * magasság; ) void setLength(double len) ( long = len; ) void setBreadth(double bre) ( szélesség = bre; ) void setHeight(double hei) ( magasság = hei; ) // Túlterhelés + operátor két Box objektum hozzáadásához. Box operator+(const Box& b) ( Box box; box.length = this->length + b.length; box.breadth = this->breadth + b .breadth; box.height = this->height + b.height; return box; ) private: dupla hosszúság; // Doboz hossza dupla szélesség; // Doboz szélessége dupla magasság; // Doboz magassága ) ; // A program fő funkciója int main() ( Box1; // Box1 típusú Box Box2 deklarálása; // Box2 Box3 típusú doboz deklarálása; // Box 3. doboz deklarálása Dupla térfogat = 0,0; // Tárolás egy doboz térfogata itt // box 1 specifikáció Box1.setLength(6.0); Box1.setBreadth(7.0); Box1.setHeight(5.0); // 2. doboz specifikáció Box2.setLength(12.0); ;Box2.setHeight(10.0 ); // az 1. doboz térfogata kötet = Box1.getVolume(); cout<< "Volume of Box1: " << volume <

    Amikor a fenti kódot lefordítják és végrehajtják, a következő kimenetet produkálja:

    1. doboz térfogata: 210 2. doboz térfogata: 1560 3. doboz térfogata: 5400

    Túlterhelhető / Nem túlterhelhető Operátorok

    Az alábbiakban felsoroljuk azokat az operátorokat, amelyek túlterhelhetők.