-
Následující část programu přečte textový soubor a řádky textu uloží v operační
paměti. Předpokládáme, že soubor obsahuje maximálně 1000 řádků textu (řádky
textu nebudou delší než 99 znaků) a pro uložení řádku rezervujeme pouze
potřebný počet slabik. Na závěr programu jsou použité dynamické proměnné
uvolněny.
char radek[100];
char *radky[1000];
FILE *soubor;
int i, n = 0;
if ((soubor = fopen("SOUBOR.TXT",
"rt")) == NULL) {
cout <<
"Soubor nelze otevřít" << endl;
return 1;
}
while (fgets(radek,
sizeof(radek), soubor) != NULL) {
radky[n] =
new char[strlen(radek) + 1];
strcpy(radky[n++],
radek);
}
for (i = 0; i <
n; i++) delete[] radky[i];
Změňte tento program tak, aby bylo možno specifikovat čtený soubor
a aby obsah souboru uložený v paměti byl vypsán na obrazovku.
-
V předchozím zadání jsme pro uložení ukazatelů na jednotlivé řádky textu
použili statické pole (velikost pole je v našem případě omezena na 1000
řádků). Abychom toto omezení obešli použijeme spojový seznam s prvky
struct prvek {
prvek *dalsi;
char radek[100];
}
Položka dalsi ukazuje na další prvek seznamu (další řádek textu)
a položka radek obsahuje vlastní text řádku. Abychom se spojovým
seznamem mohli pracovat, musíme mít statickou proměnnou, která ukazuje
na začátek seznamu. Jelikož nově přečtené řádky přidáváme na konec seznamu
je vhodné mít i ukazatel na konec seznamu.
Následuje část programu vytvářející spojový seznam řádků a část uvolňující
seznam.
char radek[100];
struct prvek {
prvek *dalsi;
char radek[100];
} *zacatek = NULL,
*konec, *pom;
FILE *soubor;
if ((soubor = fopen("SOUBOR.TXT",
"rt")) == NULL) {
cout <<
"Soubor nelze otevřít" << endl;
return 1;
}
while (fgets(radek,
sizeof(radek), soubor) != NULL) {
pom = (prvek
*)new char[strlen(radek)+sizeof(prvek *) + 1];
strcpy(pom->radek,
radek);
if (zacatek
== NULL) zacatek = pom;
else konec->dalsi = pom;
konec = pom;
konec->dalsi
= NULL;
}
while (zacatek->dalsi
!= NULL) {
pom = zacatek;
zacatek =
zacatek->dalsi;
delete[] pom;
}
Doplňte tento program o výpis v paměti uloženého obsahu souboru.
-
C++ umožňuje pracovat s funkcemi, které mají stejné jméno ale různé parametry.
Funkce, které sdílejí společné jméno, se nazývají překryté funkce. Předpokládejme,
že máme funkci nasob, která násobí dva celočíselné parametry a vrací
výsledek násobení. Můžeme ale také potřebovat podobnou funkci, která násobí
dvě reálná čísla. V C bylo nutno používat různé funkce:
int nasobInt(int
cislo1, int cislo2);
float nasobFloat(float
cislo1, float cislo2);
short nasobShort(short
cislo1, short cislo2);
V C++ ale můžeme pracovat s překrývajícími se funkcemi. Naše funkce
tedy budou mít tyto prototypy:
int nasob(int cislo1,
int cislo2);
float nasob(float
cislo1, float cislo2);
short nasob(short
cislo1, short cislo2);
Všechny funkce mají stejné jméno. Překladač pak určí volanou funkci
na základě předaných parametrů. Např.
float x = 1.5;
float y = 17.2;
float vysledek =
nasob(x, y);
Překladač vidí dva parametry typu float, které jsou předávány
funkci nasob a volá funkci nasob s dvěma parametry typu float.
Pokud parametry budou typu int, pak bude použita verze funkce s
parametry typu int.
Volaná funkce je tedy určena seznamem parametrů (každá deklarace překrývající
funkce musí mít jiný seznam parametrů; tj. počet parametrů a typy parametrů).
Nelze vytvořit překryté funkce, které se budou lišit pouze vrácenou hodnotou.
Překladač rozlišuje překryté funkce pomocí procesu nazvaného komolení
jmen. Komolení jmen spočívá v zakódování počtu a typu parametrů v interním
jméně funkce, které je udržováno překladačem.
-
Používání překrytých funkcí vyžaduje použití správných datových typů při
volání funkce. Při převodu datového typu používáme přetypování, tj. převod
jednoho datového typu na jiný. Např.
float x = (float)10
* 5.5;
V tomto případě přetypování říká překladači "udělej z čísla 10 typ
float". Druhý operand je automaticky interpretován jako typ float,
protože obsahuje desetinnou tečku. Podívejte se ještě na následující příklad:
int cele = 5;
float realne = 10.5;
float vysledek =
nasob(cele, realne);
V tomto případě překladač generuje chybu, protože nemůže nalézt funkci
nasob
s prvním parametem typu int a druhým parametrem typu
float.
Podobnou chybu bude generovat i v případě příkazu:
int vysledek = nasob(10,
10);
Zde překladač nedokáže určit jakého typu jsou použité číselné konstanty.
V této situaci máme dvě možnosti. Konstanty před voláním funkce přiřadíme
do proměnných nebo provedeme při volání přetypování. První možnost ukazuje
následující kód:
int x = 10;
int y = 10;
int vysledek = nasob(x,
y);
a druhou možnost kód:
int vysledek = nasob((int)10,
(int)10);
Nyní překladač ví, že má tyto konstanty brát jako typ int. Přetypování
lze také použít k dočasné změně jednoho datového typu na jiný. Např.
int cele = 5;
float realne = 10.5;
float vysledek =
nasob((float)cele, realne);
V tomto případě je první parametr převeden na typ float a překladač
je již schopen najít požadovanou funkci.
-
Funkce implementující implicitní parametry může vypadat takto:
// deklarace - parametr
"poprve" má implicitní hodnotu false
void Redraw(bool
poprve = false);
// definice
void Redraw(bool
poprve)
{
if (poprve){
// kód pro první kreslení
}
// kód kreslení
}
Tuto funkci můžeme volat s nebo bez parametru. Pokud je při volání
funkce parametr uveden, pak funkce se chová jako normální funkce. Pokud
při volání funkce parametr není uveden, pak je automaticky použita implicitní
hodnota parametru. Pro náš příklad jsou následující dva řádky kódu identické:
Redraw();
Redraw(false);
Když parametr má implicitní hodnotu, pak při volání funkce může být
vynechán. V jedné funkci mohou být také implicitní i neimplicitní parametry:
int PlaySound(char*
name, bool loop = false, int loops = 10);
// volání funkce
int vysl;
vysl = PlaySound("chime.wav");
vysl = PlaySound("ding.wav",
true);
vysl = PlaySound("bell.wave",
true, 5);
Implicitní parametry jsou z mnoha důvodů užitečné. Pokud při volání
některé funkce používáme často stejné parametry, pak použití implicitních
parametrů nám usnadní práci.
Implicitní parametry musíme uvádět na konci seznamu parametrů. Následující
deklarace funkce je chybná:
int mojeFunkce(int
x, int y = 10, int t = 5, int z);
Implicitní parametry musíme přesunout na konec seznamu parametrů:
int mojeFunkce(int
x, int z, int y = 10, int t = 5);
-
Metody (členské funkce třídy) se používají stejně jako normální funkce.
Mohou být překryté, mohou mít implicitní parametry atd. Metody mohou být
volány pouze prostřednictvím objektů třídy, do které metoda patří. K volání
metody používáme přímý nebo nepřímý selektor složky. Např. máme třídu nazvanou
Letadlo,
kterou budeme používat v simulačním programu letadla. Tato třída pravděpodobně
bude mít možnost získat současnou rychlost daného letadla pomocí metody
nazvané ziskejRychlost. Následující příklad ukazuje, jak voláme
tuto metodu objektu Letadlo:
Letadlo sokol;
// vytvoření instance třídy
int rychlost = sokol.ziskejRychlost();
cout << "Současná
rychlost letadla je " << rychlost << endl;
Tento kód používá k volání funkce ziskejRychlost operátor přímého
selektoru složky. Metody definujeme podobně jako normální funkce, je zde
zapotřebí navíc uvést jméno třídy a operátor ::. Např. definice
naší metody může vypadat takto:
int Letadlo::získejRychlost()
{
return rychlost;
// rychlost je datová složka třídy
}
Operátor :: říká překladači, že funkce získejRychlost
je metodou třídy Letadlo. Více o třídách a metodách se dozvíme později.
-
Normálně strojový kód funkce se v přeloženém proveditelném kódu vyskytuje
pouze jednou. Při volání funkce je proveden skok na vstupní bod funkce
a při ukončení funkce se opět vrátíme na místo volání funkce. Existují
také vložené (inline) funkce, kde kód funkce je vložen do proveditelného
kódu programu na místo volání funkce. Vložené funkce se deklarují podobně
jako normální funkce, ale jejich definice začíná klíčovým slovem inline.
Pokaždé, když překladač ve zdrojovém kódu nalezne volání vložené funkce,
pak umístí separátní kopii kódu funkce na toto místo do přeloženého programu.
Vložené funkce pracují rychle, protože není zapotřebí provádět volání funkce.
Je vhodné, aby vložené funkce byly velmi malé. Delší funkce není vhodné
používat jako vložené, neboť výsledkem je zvětšení přeloženého spustitelného
programu.
Vložené funkce jsou obvykle metody. Často definici vložené funkce umisťujeme
do hlavičkového souboru deklarace třídy. Metoda ziskejRychlost z
předchozího zadání je krátká a je tedy možno z ní udělat vloženou funkci.
To provedeme takto:
inline int Letadlo::získejRychlost()
{
return rychlost;
// rychlost je datová složka třídy
}
Nové pojmy:
-
Překryté funkce tvoří dvě nebo více funkcí se stejným jménem, ale
s různými seznamy parametrů.
-
Přetypování říká překladači, že má dočasně brát jeden datový typ,
jako jiný datový typ.
-
Funkce v C++ mohou mít implicitní parametry, které poskytují implicitní
hodnoty pro parametry, když hodnota parametru při volání funkce není zadána.
-
Třídy mohou obsahovat své vlastní funkce. Tyto funkce nazýváme metody
(členské funkce třídy - jsou složkami třídy).
-
Vložené funkce umisťují přeložený kód do přeloženého programu na
místo volání funkce.
Kontrolní otázky:
-
Musíme vždy zrušit objekt, který byl vytvořen dynamicky pomocí operátoru
new?
-
Kdy vytváříme objekty v zásobníku a kdy v hromadě?
-
Co to jsou překryté funkce?
-
Kdy používáme vložené funkce?
-
Co je to ukazatel?
-
Co dělá dereference ukazatele?
-
Co je vrácená hodnota operátoru new?
-
Lze instance tříd a struktur předávat funkcím odkazem nebo hodnotou?
-
Co dělá klíčové slovo const?
-
Vytvářejí následující deklarace překryté funkce?
void mojeFunkce(int
x);
int mojeFunkce(int
x);
-
Je lepší používat odkazy nebo ukazatele?
-
Co je metoda?
-
Jak se liší zacházení překladače s vloženou funkcí od normální funkce?
-
Co je chybné na následující ukázce?
char* buff = new
char[200];
// a později
delete buff;
Řešení