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:
#include <conio.h>
#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
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;
}
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 !";
}
void Algorytm( std::string & bufor, short & zmienna, int & Wynik ) {
short pozycja, pozycja1;
std::string bufor1;
std::stringstream IntToStr;
if( bufor.find( 'ż' ) ) {
pozycja = bufor.find( 'ż' );
pozycja += 2;
bufor1.assign( bufor, pozycja, 10 );
if( bufor1.find( ' ' ) ) {
pozycja1 = bufor1.find( ' ' );
bufor1.assign( bufor1, 0, pozycja1 );
sscanf( bufor1.c_str(), "%hd", & zmienna );
Wynik = 2 * ++zmienna + 15;
IntToStr << zmienna;
IntToStr >> bufor1;
IntToStr.clear();
bufor.replace( pozycja, pozycja1, bufor1 );
if( bufor.find( "funkcja" ) ) {
pozycja = bufor.find( '2', bufor.find( "funkcja" ) );
bufor.replace( pozycja + 4, pozycja1, bufor1 );
}
if( bufor.find( "k to " ) ) {
pozycja = bufor.find( ' ', bufor.find( "k to " ) );
IntToStr << Wynik;
IntToStr >> bufor1;
bufor.replace(( pozycja + 4 ), bufor1.length(), bufor1 );
}
}
if( bufor.find( "!" ) )
if( bufor.find( "." ) ) {
}
} else
std::cout << "Brak szukanej litery!";
}
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 !";
}
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ę:
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.
#include <conio.h>
#include <iostream>
#include <string>
#include <windows.h>
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;
MS = Funkcja( MS );
MS1 = Funkcja( MS1 );
MS2 = Funkcja( MS2 );
MS3 = Funkcja( MS3 );
MS4 = Funkcja( MS4 );
MS5 = Funkcja( MS5 );
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 );
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 ];
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 );
}
return Narodowa;
}
void Wyswietl( Druzyny & Struktura ) {
using std::cout;
using std::ios_base;
ios_base::fmtflags ustawienia_domyslne;
ustawienia_domyslne = cout.setf( ios_base::fixed );
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 );
}
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.
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śćprzez wskaźnikprzez referencjePamię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.
#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.
#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 );
}
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 );
} 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
#include <conio.h>
#include <iostream>
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:
void FunkcjaPotegujaca( int x, int y = 2 );
void FunkcjaPotegujaca( int x = 1, int y );
void Funkcja( char z, int x = 1, int y = 2 );
void Funkcja( char z = 'z', int x = 1, int y = 2 );
void Funkcja( char z = 'z', int x = 1, int y );
Ć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ć.