« Funkcje raz jeszcze, lekcja »
Dokument omawia referencje, polimorfizm funkcji i inne własności funkcji. (lekcja)
Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Zarejestruj się!
Autor: 'Dante'
Kurs C++

Funkcje raz jeszcze

[lekcja] Dokument omawia referencje, polimorfizm funkcji i inne własności funkcji.

Referencje

Referencje inaczej możemy nazwać linkami do zmiennych. Daje nam to przede wszystkim możliwość pracowania na zmiennych(oryginałach), a nie na ich kopiach. Oto przykład:
C/C++
//Funkcje z parametrami referencyjnymi-------
#include <conio.h>
#include <iostream>
#include <string>
#include <fstream>
#include <sstream> //nowa biblioteka
void OdczytPliku( const std::string & nazwapliku, std::string & bufor );
void Algorytm( std::string & bufor, short & zmienna, int & Wynik );
void ZapisPliku( const std::string & nazwapliku, const std::string & bufor );
std::string PL( std::string & znak );

int main()
{
    using std::string;
    using std::cout;
    const string NAZWAPLIKU = "mojplik.txt";
    string danepliku;
    short x = 0;
    int wynik = 0;
   
    OdczytPliku( NAZWAPLIKU, danepliku );
    Algorytm( danepliku, x, wynik );
    ZapisPliku( NAZWAPLIKU, danepliku );
    cout << PL( danepliku );
   
    getch();
    return 0;
}
//odczytuje dana z pliku
void OdczytPliku( const std::string & nazwapliku, std::string & bufor ) {
    using std::fstream;
    fstream plik;
    std::string bufor1;
   
    plik.open( nazwapliku.c_str(), std::ios::in );
    if( plik.is_open() ) {
        while( !plik.eof() ) {
            getline( plik, bufor1 );
            bufor1 += "\n";
            bufor += bufor1;
        }
    } else
         std::cout << "Brak mojego pliku !";
   
}
//funkcja pobiera dane z wcześniej wczytanego pliku i modyfikuje je wg prostego wzoru
void Algorytm( std::string & bufor, short & zmienna, int & Wynik ) {
    short pozycja, pozycja1;
    std::string bufor1;
    std::stringstream IntToStr;
   
    if( bufor.find( 'ż' ) ) {
        //zapisuje pozycje literki ż
        pozycja = bufor.find( 'ż' );
        // przesuwamy pozycje o 2
        pozycja += 2;
        //wycinamy z całego buforu 10 pozycji od pozycji
        bufor1.assign( bufor, pozycja, 10 );
        if( bufor1.find( ' ' ) ) {
            //zapisuje pozycje spacji
            pozycja1 = bufor1.find( ' ' );
            //następnie zostawiamy tylko liczbę
            bufor1.assign( bufor1, 0, pozycja1 );
            //zamieniamy stringa na inta
            sscanf( bufor1.c_str(), "%hd", & zmienna );
            //wykonujemy działanie
            Wynik = 2 * ++zmienna + 15;
            //zamieniamy inta na stringa
            IntToStr << zmienna;
            IntToStr >> bufor1;
            IntToStr.clear(); // zerowanie IntToStr
            //zastępujemy liczbą jaka była w buforze liczbą o 1 większą
            bufor.replace( pozycja, pozycja1, bufor1 );
            //tan if ma słyżyć też zastąpienu tylko drugiego wystąpienia liczby czyli w wyrażeniu 2 * liczba + 15
            if( bufor.find( "funkcja" ) ) {
                pozycja = bufor.find( '2', bufor.find( "funkcja" ) );
                bufor.replace( pozycja + 4, pozycja1, bufor1 );
            }
            //pozostaje jeszcze zmienić wynik na wyżej obliczony
            if( bufor.find( "k to " ) ) {
                pozycja = bufor.find( ' ', bufor.find( "k to " ) );
                //ponownie IntToString
                IntToStr << Wynik;
                IntToStr >> bufor1;
                bufor.replace(( pozycja + 4 ), bufor1.length(), bufor1 );
            }
            //       std::cout << bufor;
           
        }
        if( bufor.find( "!" ) )
        if( bufor.find( "." ) ) {
        }
    } else
         std::cout << "Brak szukanej litery!";
   
}
//zapisuje dane do pliku mojplik
void ZapisPliku( const std::string & nazwapliku, const std::string & bufor ) {
    using std::fstream;
    fstream plik;
   
    plik.open( nazwapliku.c_str(), std::ios::out );
    if( plik.is_open() ) {
        plik << bufor;
    } else
         std::cout << "Brak mojego pliku !";
   
}
//poprawnie wyswietla polskie znaki w konsoli
std::string PL( std::string & znak ) {
   
    for( unsigned i = 0; i < znak.length(); i++ ) {
        switch( znak[ i ] ) {
        case 'ą':
            znak[ i ] = static_cast < char >( 165 );
            break;
           
        case 'ć':
            znak[ i ] = static_cast < char >( 134 );
            break;
           
        case 'ę':
            znak[ i ] = static_cast < char >( 169 );
            break;
           
        case 'ł':
            znak[ i ] = static_cast < char >( 136 );
            break;
           
        case 'ń':
            znak[ i ] = static_cast < char >( 228 );
            break;
           
        case 'ó':
            znak[ i ] = static_cast < char >( 162 );
            break;
           
        case 'ś':
            znak[ i ] = static_cast < char >( 152 );
            break;
           
        case 'ź':
            znak[ i ] = static_cast < char >( 171 );
            break;
           
        case 'ż':
            znak[ i ] = static_cast < char >( 190 );
            break;
           
        case 'Ą':
            znak[ i ] = static_cast < char >( 164 );
            break;
           
        case 'Ć':
            znak[ i ] = static_cast < char >( 143 );
            break;
           
        case 'Ę':
            znak[ i ] = static_cast < char >( 168 );
            break;
           
        case 'Ł':
            znak[ i ] = static_cast < char >( 157 );
            break;
           
        case 'Ń':
            znak[ i ] = static_cast < char >( 227 );
            break;
           
        case 'Ó':
            znak[ i ] = static_cast < char >( 224 );
            break;
           
        case 'Ś':
            znak[ i ] = static_cast < char >( 151 );
            break;
           
        case 'Ź':
            znak[ i ] = static_cast < char >( 141 );
            break;
           
        case 'Ż':
            znak[ i ] = static_cast < char >( 189 );
            break;
        }
    }
    return znak;
}
W pliku mojplik.txt, powinny znajdować się następujące dane:
---*---*---*---*---*---*---*--
Program został uruchomiony.
To już 0 raz.
Za każdym razem wykonuje się
funkcja 2 * 0 + 15!
Obecny wynik to 15.
*----*----*----*----*----*----
Plik wrzucamy do folderu z naszym projektem i kompilujemy.
Jak widać pracujemy na czterech danych, string danepliku; short x = 0; int wynik = 0;.
Jeśli chcesz wiedzieć jak dokładnie się zmieniają zmienne referencyjne podczas działania funkcji, lub nie widzisz na czym polega referencja, zmodyfikuj Sobie trochę kod wprowadzając do niego małą modyfikację:
C/C++
cout << "Nasze zmienne:"
<< "\ndane z pliku - " << danepliku
<< "\n\nx = " << x
<< "\nwynik = " << wynik
<< "\n\n";
OdczytPliku( NAZWAPLIKU, danepliku );
cout << "Nasze zmienne:"
<< "\ndane z pliku - " << danepliku
<< "\n\nx = " << x
<< "\nwynik = " << wynik
<< "\n\n";
Algorytm( danepliku, x, wynik );
cout << "Nasze zmienne:"
<< "\ndane z pliku - " << danepliku
<< "\n\nx = " << x
<< "\nwynik = " << wynik
<< "\n\n";
ZapisPliku( NAZWAPLIKU, danepliku );
znak = "\n\n   To już nasz wynik działania programu\n";
cout << PL( znak );
cout << PL( danepliku );
Reasumując, gdy chcemy pracować na oryginalnych zmiennych, a nie ich kopiach, wtedy należy się zastanowić czy lepiej nie użyć referencji. Program powinien być dla Ciebie w miarę czytelny i zrozumiały. Wspomnę tylko, iż użyto nowej biblioteki <sstream>, dzięki której możemy łatwo int zamienić na string. Jak pewnie zauważyłeś dodałem jeszcze funkcje, która umożliwia używanie polskiej czcionki w konsoli.

Referencje a obiekty

Zobaczmy teraz jak referencja radzi sobie z obiektami. Oto tabela, którą należy wprowadzić do programu
Źródło: onet.pl z dnia 13.08.2009.
 
C/C++
//Funkcje i obiekty-------
#include <conio.h>
#include <iostream>
#include <string>
#include <windows.h> //nowa biblioteka
struct Druzyny {
    std::string nazwa;
    short pozycja;
    short punkty;
    short spotkania;
};
std::string PL( std::string znak );
const Druzyny & Funkcja( Druzyny & Narodowa );
void Wyswietl( Druzyny & Struktura );

int main()
{
    using std::string;
    using std::cout;
    using std::ios_base;
    string znak;
    Druzyny MS, MS1, MS2, MS3, MS4, MS5;
    //wprwadzanie danych
    MS = Funkcja( MS );
    MS1 = Funkcja( MS1 );
    MS2 = Funkcja( MS2 );
    MS3 = Funkcja( MS3 );
    MS4 = Funkcja( MS4 );
    MS5 = Funkcja( MS5 );
    //utworzony pierwszy wiersz tabeli
    cout.width( 1 );
    cout << "| Lp.|";
    cout.width( 20 );
    cout.setf( ios_base::left, ios_base::adjustfield );
    cout << PL( "Nazwa Drużyny" ) << "|";
    cout.width( 1 );
    cout << " M |";
    cout.width( 1 );
    cout << " Punkty\n";
    cout << "---------------------------------------\n";
    //Wyswietl((MS = Funkcja(MS))); - też prawidłowo
    //wyświetlanie danych
    Wyswietl( MS );
    Wyswietl( MS1 );
    Wyswietl( MS2 );
    Wyswietl( MS3 );
    Wyswietl( MS4 );
    Wyswietl( MS5 );
    cout << "---------------------------------------\n";
   
    getch();
    return 0;
}
//
const Druzyny & Funkcja( Druzyny & Narodowa ) {
    using std::cout;
    using std::cin;
    char znak[ 50 ];
    //  using std::windows;
    cout << PL( "Podaj pozycję drużyny - " );
    ( cin >> Narodowa.pozycja ).get();
    cout << PL( "Podaj nazwę drużyny - " );
    std::getline( cin, Narodowa.nazwa );
    cout << PL( "Podaj liczbę punktów drużyny - " );
    cin >> Narodowa.punkty;
    cout << PL( "Podaj liczbę rozegranych spotkań przez drużynę - " );
    cin >> Narodowa.spotkania;
   
    if( cin )
         cout << PL( "Dziękuję !!!\n\n" );
    else {
        cout << PL( "\n\nBłąd przy wprowadzaniu danych!!! Program zostanie zamknięty" );
        Sleep( 4000 );
        exit( 0 ); //zamknięcie aplikacji
    }
    return Narodowa;
}
//Funkcja wyświetlająca zawartość struktury
void Wyswietl( Druzyny & Struktura ) {
    using std::cout;
    using std::ios_base;
    ios_base::fmtflags ustawienia_domyslne;
    ustawienia_domyslne = cout.setf( ios_base::fixed );
    //cout.precision(0);
    cout.setf( ios_base::left, ios_base::adjustfield );
    cout.width( 1 );
    cout << "| " << Struktura.pozycja << ". |";
    cout.width( 20 );
    cout << Struktura.nazwa << "|";
    cout.setf( ios_base::internal, ios_base::adjustfield );
    cout.width( 1 );
    cout << " " << Struktura.spotkania << " ";
    cout.width( 1 );
    cout << "| " << Struktura.punkty << " |\n";
    cout.setf( ustawienia_domyslne );
}
//poprawnie wyswietla polskie znaki w konsoli
std::string PL( std::string znak ) {
   
    for( unsigned i = 0; i < znak.length(); i++ ) {
        switch( znak[ i ] ) {
        case 'ą':
            znak[ i ] = static_cast < char >( 165 );
            break;
           
        case 'ć':
            znak[ i ] = static_cast < char >( 134 );
            break;
           
        case 'ę':
            znak[ i ] = static_cast < char >( 169 );
            break;
           
        case 'ł':
            znak[ i ] = static_cast < char >( 136 );
            break;
           
        case 'ń':
            znak[ i ] = static_cast < char >( 228 );
            break;
           
        case 'ó':
            znak[ i ] = static_cast < char >( 162 );
            break;
           
        case 'ś':
            znak[ i ] = static_cast < char >( 152 );
            break;
           
        case 'ź':
            znak[ i ] = static_cast < char >( 171 );
            break;
           
        case 'ż':
            znak[ i ] = static_cast < char >( 190 );
            break;
           
        case 'Ą':
            znak[ i ] = static_cast < char >( 164 );
            break;
           
        case 'Ć':
            znak[ i ] = static_cast < char >( 143 );
            break;
           
        case 'Ę':
            znak[ i ] = static_cast < char >( 168 );
            break;
           
        case 'Ł':
            znak[ i ] = static_cast < char >( 157 );
            break;
           
        case 'Ń':
            znak[ i ] = static_cast < char >( 227 );
            break;
           
        case 'Ó':
            znak[ i ] = static_cast < char >( 224 );
            break;
           
        case 'Ś':
            znak[ i ] = static_cast < char >( 151 );
            break;
           
        case 'Ź':
            znak[ i ] = static_cast < char >( 141 );
            break;
           
        case 'Ż':
            znak[ i ] = static_cast < char >( 189 );
            break;
        }
    }
    return znak;
}
Jak widać programik buduje podobną tabelkę jak przedstawiona wyżej. Mamy tutaj do czynienia z dwoma funkcjami const Druzyny &Funkcja(Druzyny &Narodowa); i
void Wyswietl(Druzyny &Struktura);
, które pobierają przez referencje obiekty struktury Druzyny. Referencyjne pobieranie i używanie struktur jest analogiczne ze zwykłymi zmiennymi szczególnie, jeśli dobrze zrozumiałeś poprzednie przykłady nie wnosi nic nowego. Jednak zapis const Druzyny &Funkcja może zastanawiać? Nie chodzi tutaj o to, iż funkcja jest stała, lub że struktura jest stała. Zapis ten zabezpiecza by zwracana wartość nie mogła być modyfikowana w chwili zwracania MS1 = Funkcja(MS1);. Nie jest to wymagane zabieg, a czasami wręcz warto unikać jego zapisu.
W funkcji void Wyswietl(Druzyny &Struktura); użyty został ciekawy metoda we/wy.
C/C++
ios_base::fmtflags ustawienia_domyslne;
ustawienia_domyslne = cout.setf( ios_base::fixed );
Ten zapis powoduje zapisanie domyślnych wartości dla wyświetlania i nie tylko metodą cout. Niektóre ciekawe metody obsługi strumienia są opisane tutaj. Gdy wykonamy jakiekolwiek modyfikacje strumienia i chcemy przywrócić wartości domyślne sprzed zmian wystarczy użyć cout.setf(ustawienia_domyslne);. Oczywiście zadziała to tylko gdy będziemy mieli wcześniej utworzone ustawienia_domyslne.

Referencje - konkluzje

Skąd wiedzieć jakich użyć metod przekazywania parametrów(wartość, wskaźnik, referencje)?
przez wartość
  • jeśli nie musimy modyfikować wartości pobieranych zmiennych
  • gdy mamy do czynienia z prostymi typami danych, małymi strukturami
przez wskaźnik
  • gdy działamy na tablicy pozostaje użyć wskaźnika const
  • gdy pracujemy z wbudowanymi typami danych
przez referencje
  • gdy pracujemy na strukturach
  • gdy pracujemy z egzemplarzami klas
  • wszędzie tam gdzie nie musimy użyć wskaźnika, a nie możemy zastosować przekazywania przez wartość
Pamiętaj jednak, że to ty decydujesz co użyjesz. Czasami różnych powodów może się zdarzyć, że nie będziesz mógł postępować zgodnie z prezentowanymi rozwiązaniami.

Polimorfizm funkcji

Jeśli chcemy mieć koniecznie dwie lub więcej funkcji o tej samej nazwie możemy to uczynić pod warunkiem, że lista wszystkich parametrów każdej funkcji będzie różna. Różnica musi być w typie co najmniej jednej zmiennej lub w ilości parametrów, która umożliwi kompilatorowi jednoznacznie określić w momencie wywołania funkcji o którą programiście chodzi. Polimorfizm pozwala funkcji przybrać różne formy, jednak w programowaniu spotkasz nazwę przeciążenie funkcji. Poniżej zamieszczam przykład przeciążania funkcji.
C/C++
#include <iostream>
#include <conio.h>
using namespace std;
int PotegaCzwarta( int n )
{
    return( n * n * n * n );
}
int PotegaCzwarta( int a, int b )
{
    return( PotegaCzwarta( a + b ) );
}
int main()
{
    long long liczba;
    cout << "Podaj liczbe: ";
    cin >> liczba;
    cout << "Liczba " << liczba << " podniesiona do potegi 4 to " << PotegaCzwarta( liczba ) << endl;
    cout << "Liczba (" << liczba << "+3) podniesiona do potegi 4 to " << PotegaCzwarta( liczba, 3 ) << endl;
    getch();
    return( 0 );
}

Inne własności funkcji

O funkcji można by jeszcze wiele napisać. Na koniec podam dwa przykłady zastosowania funkcji i innych rozwiązań, które nam jeszcze oferuje.

Funkcja inline

Zamierzeniem wprowadzenia tego rozwiązania było przyśpieszanie działania programu. Jednak czasami może się okazać, że wcale tak nie jest. Jednak przyśpieszenie, które możemy uzyskać jest okupione zwiększeniem zapotrzebowania na pamięć. Zobaczmy jak można przerobić przykład 18.4 Przekazywanie funkcji przez wskaźnik.
C/C++
//Funkcje inline
#include <conio.h>
#include <iostream>
inline float Kwadrat( float x ) { return x * x; }
inline float Szescian( float x ) { return 6 *( x * x ); }
inline void WyswietlWynik( float bok, float( * wskaznik )( float ) ) {
    using std::cout;
    cout << "Wynik pole = " <<( * wskaznik )( bok );
}
//void WyswietlWynik(float, float (*wskaznik) (float));

int main()
{
    using std::cout;
    using std::cin;
    char znak;
    float bok;
   
    cout << "Czego pole oblczamy K - kwadrat, S - szescianu, wybierz?\n";
    cin.get( znak ).get();
   
    if( znak == 'K' ) {
        cout << "\nPodaj bok kwadrata\n";
        cin >> bok;
        WyswietlWynik( bok, Kwadrat );
        //WyswietlWynik(bok, *Kwadrat);
    } else if( znak == 'S' ) {
        cout << "\nPodaj bok szescianu\n";
        cin >> bok;
        WyswietlWynik( bok, Szescian );
    } else
         cout << "\nPodano bledny parametr!\n";
   
    getch();
    return 0;
}
Jak pisałem trzeba uważać ze stosowaniem funkcji inline, ponieważ może się to obrócić przeciwko nam. Najczęściej stosuje ją się dla jedno-linijkowego kodu, mogą one również być alternatywą dla mark z standardowego C.

Parametry domyślne funkcji

C/C++
//Funkcje a parametry domyślne-------
#include <conio.h>
#include <iostream>
//funkcja z parametrem domyślnym
void FunkcjaPotegujaca( int x, int y = 2 );
int main()
{
    using std::cout;
    int a, b;
   
    cout << "Dwa do potegi 10.\n";
    FunkcjaPotegujaca( 2, 10 );
    cout << "A tak dziala funkcja z domyslnym parametrem.\n"
    << "Dwa do potegi 2.\n";
    FunkcjaPotegujaca( 2 );
    cout << "Podaj liczbe i potege do ktorej chcesz ja podniesc\n";
    std::cin >> a >> b;
    FunkcjaPotegujaca( a, b );
    getch();
    return 0;
}
void FunkcjaPotegujaca( int x, int y ) {
    using std::cout;
    int wynik = x;
    for( int i = y; i > 1; i-- )
         wynik *= x;
   
    if( y == 1 )
         cout << "Wynik = " << x;
    else if( y == 0 )
         cout << "Wynik = 1";
    else if( y < 0 )
         cout << "Ja nie znam potegi ujemnej!";
    else
         cout << "Wynik = " << wynik;
   
    cout << "\n\n";
}
Parametr domyślny jest ciekawą alternatywa by móc sterować danymi przekazywanymi do funkcji. Ważne by pamiętać o tym, iż parametr domyślny określany jest w prototypie funkcji.
Kolejną ważną sprawą jest to, że ustawienia parametrów domyślnych wykonuje się od prawej do lewej. Czyli:
C/C++
void FunkcjaPotegujaca( int x, int y = 2 ); //poprawnie
void FunkcjaPotegujaca( int x = 1, int y ); //niepoprawnie
void Funkcja( char z, int x = 1, int y = 2 ); //poprawnie
void Funkcja( char z = 'z', int x = 1, int y = 2 ); //poprawnie
void Funkcja( char z = 'z', int x = 1, int y ); //niepoprawnie

Ćwiczenia

1. Napisz program, który wyświetli całą tabelę ligi Polskiej żużlowców. Tabela ma wyglądać jak tabela, z której będziesz przepisywać dane. Wszystkie dane mają być pobrane z pliku tekstowego o nazwie zuzel.txt. W jaki sposób zapiszesz dane w pliku nie jest istotne. Istotne jest to by utworzyć dwie funkcje jedna, która odczyta dane z pliku, a druga która te dane wyświetli. Użyj struktury do przechowywania wszystkich informacji o drużynie.  Napisz dwa wariant tego programu, jeden który będzie używać referencji do struktury, a drugi który będzie używać wskaźników.
 
2. Napisz program modyfikujący następujące dane z panelu logowania w DDT.
 Za każdym razie gdy uruchomisz program, ma on zwiększać liczbę zarejestrowanych użytkowników o 2. Natomiast liczba wszystkich ma być zmniejszana o liczbę zalogowanych.
Czyli po 10 uruchomieniach liczba zarejestrowanych powinna wynosić 2682, a liczba wszystkich 94978. Oczywiście by tego dokonać należy zapisać dane w pliku. Spróbuj również stworzyć ramkę podobną do tej ze strony. Mam nadzieje, iż nie muszę dodawać byś wykorzystał do tego funkcje, sam określ ich liczbę oraz co będą wykonywać.
Poprzedni dokumentNastępny dokument
Dynamiczne zarządzanie pamięcią new i deletePrzestrzenie nazw