See also ebooksgratis.com: no banners, no cookies, totally FREE.

CLASSICISTRANIERI HOME PAGE - YOUTUBE CHANNEL
Privacy Policy Cookie Policy Terms and Conditions
Referencja (informatyka) - Wikipedia, wolna encyklopedia

Referencja (informatyka)

Z Wikipedii

Referencja w informatyce oznacza dane, które zawierają informację o położeniu innych danych. Określenie "X jest referencją do Y" oznacza, że na obiekcie X można wykonać każdą operację, którą potencjalnie można wykonać na obiekcie Y. W tym wypadku operacje na X w rzeczywistości są operacjami na Y. W odróżnieniu do wskaźnika, użycie referencji nie wymaga specjalnej składni. Referencje występują w większości języków programowania, jednak szczególne znaczenie mają w języku C++.

Spis treści

[edytuj] Referencja w C++

W języku programowania C++ referencja jest pochodnym typem danych i podobnie jak wskaźnik umożliwia pośrednie odwoływanie się do zmiennych lub obiektów.

Z punktu widzenia programisty, referencja jest alternatywną nazwą innej, istniejącej już zmiennej lub obiektu. Stosowana jest głównie do

  1. przekazywania argumentów do funkcji
  2. przekazywania wartości z funkcji (szczególnie operatorów)
  3. przechowywania w obiekcie informacji o położeniu innych obiektów
  4. skracania zapisu długich wyrażeń

W trzech pierwszych zastosowaniach referencja z zasady implementowana jest jako stały wskaźnik, co w praktyce oznacza, że tak naprawdę funkcja otrzymuje jako argument lub zwraca jako wartość pewien adres. Fakt ten jest jednak zupełnie transparentny dla programisty, gdyż referencji używa się tak, jak zwykłych zmiennych i obiektów, a nie jak wskaźników. W czwartym z powyższych zastosowań, a także podczas opracowywania tzw. funkcji wklejanych (inline), kompilator może tak zoptymalizować referencję, że w programie nie będzie reprezentowana przez żaden obiekt.

Referencje wymagają inicjalizacji.

[edytuj] Referencje i stałe referencje

W języku C++ rozróżnia się dwa typy referencji: "zwyczajne" i stałe. Zwyczajne referencje umożliwiają zarówno odczyt jak i modyfikację obiektu, do którego się odwołują, natomiast stałe referencje umożliwiają wyłącznie odczyt stanu obiektu. Zwyczajne referencje wymagają podania konkretnego obiektu, do którego się mają odnosić, natomiast stałe referencje mogą się odnosić zarówno do obiektów, jak i wyrażeń. Utworzenie referencji do wyrażenia, które oczywiście nie ma własnego adresu, realizowane jest w ten sposób, że najpierw konstruowany jest tymczasowy obiekt inicjalizowany wartością danego wyrażenia, a następnie referencji przypisuje się adres tego tymczasowego obiektu. Właściwość ta ma kapitalne znaczenie przy definiowaniu argumentów funkcji (a szczególnie funkcji generowanych z szablonów): jako faktycznych argumentów funkcji pobierających argument(y) przez stałą referencję można użyć zarówno nazw zmiennych (np. f(x)), jak i wyrażeń (np. f(x+1)).

Zaleca się, by obiekty klas zdefiniowanych przez użytkownika przekazywać do lub z funkcji poprzez referencję lub stałą referencję zależnie od tego, czy funkcja ma mieć prawo do modyfikowania argumentu/wartości, czy nie. Przekazywanie argumentów przez wartość należy ograniczyć do niewielkich obiektów, np. prostych typów wbudowanych, jak int, double czy int*.

[edytuj] Składnia

Deklaracja referencji składa się z nazwy typu, operatora & i nazwy zmiennej referencyjnej, np.

int i;
...
int & q = i; // q jest referencją do zmiennej i

Deklaracja stałej referencji zawiera dodatkowo słowo kluczowe const pisane bezpośrednio przed lub tuż za nazwą typu, np.

const int & i_ref1 = i; // i_ref1 jest stałą referencją do zmiennej i typu int
int const & i_ref2 = i; // i_ref2 jest stałą referencją do zmiennej i typu int

Obie konwencje są równoważne.

[edytuj] Referencje a wskaźniki

Jak już wspomniano, referencje implementowane są przy pomocy stałych wskaźników. Analogicznie stałe referencje implementowane są przy pomocy stałych wskaźników na stałe. Ponieważ w przeciwieństwie do wskaźników, referencje muszą być inicjalizowane adresem obiektu, są one dużo bardziej bezpieczne od wskaźników. Nie oznacza to jednak, że referencje gwarantują 100% bezpieczeństwo. Wystarczy, że w programie utworzymy niezainicjowany wskaźnik:

int* p;

Ze zględu na brak inicjalizacji zmiennej p, wyrażenie *p nie reprezentuje żadnego obiektu. Tym niemniej może ono przypadkowo zostać przekazane do funkcji przyjmującej argument przez referencję.

f(*p);  // f jest funkcją o sygnaturze void f(int & x);

W tym wypadku funkcja otrzyma referencję do zmiennej znajdującej się pod nieważnym adresem p. Jakiekolwiek użycie takiego argumentu może zakończyć się katastrofą. Wina nie leży jednak po stronie referencji, lecz po stronie wskaźników.


[edytuj] Przykłady

[edytuj] Użycie referencji w argumentach funkcji

Poniższy kod zawiera typową (nieco uproszczoną) implementację standardowego szablonu funkcji swap, która zamienia wartości swoich argumentów.

template <typename T>
  inline void
  swap(T& a, T& b)
  {
    const T tmp = a;
    a = b;
    b = tmp;
  }

Ponieważ funkcja modyfikuje argumenty, są jej one przekazywane przez referencję.

[edytuj] Użycie referencji w wartości funkcji

Podstawowym miejscem zastosowania typów referencyjnych w wartościach funkcji jest implementacja operatorów. Oto przykład (szablonu) operatora strumieniowego, który wyświetla zawartość dowolnego standardowego wektora:

template <typename T>
  std::ostream & 
  operator<<(std::ostream & out, std::vector<T> const& v)
  {
     out << '(';
     for (size_t i = 0; i + 1 < v.size(); ++i)
        out << v[i] << ", ";
 
     if (!v.empty())
        out << v.back();
 
     out << ')';
 
     return out;          
  }

Dzięki przekazaniu jako wartości operatora << referencji do strumienia, operatora tego można użyć w ciągu, np.

std::cout << v << ", ";    // v jest typu std::vector<T>

Ponieważ operator<< jest lewostronnie łączny, w instrukcji tej najpierw zostanie opracowane wyrażenie std::cout << v. Jego wartość, czyli (referencja do) std::cout, zostanie następnie użyta jako lewy argument drugiego operatora<<. W sumie więc powyższa instrukcja równoważna jest dwóm instrukcjom

std::cout << v;
std::cout << ", ";

Referencja w powyższym przykładzie jest konieczna, gdyż strumieni nie można kopiować, w związku z czym nie można ich przekazywać z funkcji przez wartość.

[edytuj] Referencje jako składowe klas lub struktur

Referencje mogą być używane jako składowe klas lub struktur. Oto dość ogólny przykład:

class X
{
...
};
 
class X_iterator
{
   X const& _x;
public:
   X_iterator (X const& x) 
   : _x(x)
   { ... }
...
};

W powyższym kodzie zdefiniowano dwie klasy: X i X_iterator, przy czym druga z nich posiada składową referencyjną _x. Ponieważ każda referencja wymaga inicjalizacji, niezbędny do tego parametr przekazywany jest w preambule konstruktora (wyrażenie _x(x)) (zobacz Lista inicjalizacyjna konstruktora). Zastosowanie składowej referencyjnej wiąże się z następującymi faktami:

  • Każdy obiekt klasy X_iterator ma dostęp do oryginału pewnego obiektu klasy X.
  • Zastosowanie stałej referencji gwarantuje, że obiekty klasy X_iterator nie będą zmieniać stanu przypisanych im obiektów klasy X.
  • Każdy konstruktor klasy X_iterator musi inicjalizować (w swojej preambule) składową referencyjną _x.
  • Inicjalizacja składowych referencyjnych odbywa sie przed wywołaniem ciała konstruktora.

[edytuj] Zastosowanie referencji do upraszczania notacji i unikania zbędnych obliczeń

Rozpatrzmy następujący fragment kodu:

std::map<std::string, std::vector<int> > mapa;  
   ...
std::vector<int> & w = mapa["ala"];

Referencja udostępnia element mapa["ala"] poprzez alternatywną "nazwę" w. W związku z tym wyrażenie mapa["ala"].resize(100) jest równoważne wyrażeniu w.resize(100), z tym że to drugie jest prostsze (w więc czytelniejsze) oraz bardziej efektywne, gdyż nie wymaga dość kosztownego obliczania adresu wyrażenia mapa["ala"].

[edytuj] Zastosowanie stałych referencji w argumentach i wartości funkcji

Oto typowa implementacja szablonu max obliczającego większy z dwóch obiektów a, b dowolnego typu T

template <typename T>
  inline const T&
    max(const T& a, const T& b)
    {
      if (a < b)
        return b;
      return a;
    }

W powyższym przykładzie:

  • Stałą referencję zastosowano zarówno do przekazania argumentów do funkcji, jak i jej wartości do punktu jej wywołania;
  • Dzięki zastosowaniu stałej referencji, funkcji max można używać nawet tak, jakby argumenty były do niej przekazywane przez wartość:
int n = 9, k = 10;
...
int m = max(n, 100 - k);

W przykładzie tym pierwszy argument (obiekt n) zostanie przekazany do funkcji max przez (stałą) referencję, natomiast drugi argument (wyrażenie 100 - k) zostanie wpierw skopiowany (jakby był przekazywany przez wartość) i dopiero adres tej kopii zostanie przekazany do funkcji.

  • Gdyby argumenty były do funkcji przekazywane przez wartość, funkcja niepotrzebie tworzyłaby ich lokalne kopie, co mogłoby być szczególnie niekorzystne dla tych typów T, które posiadają nietrywialne, złożone konstruktory kopiujące
  • Gdyby argumenty formalne przekazywano przez zwykłą referencję, faktycznymi argumentami funkcji nie mogłyby być ani stałe obiekty, ani jakiekolwiek wyrażenia.
  • W wypadku zastosowania agresywnej optymalizacji, dzięki zastosowaniu atrybutu inline wyrażenia typu max(n, 100 - k) przestaną być interpretowane jak wywołania funkcji, a sposób przekazywania "argumentów" straci jakiekolwiek znaczenie. Dzięki temu wartości wyrażeń typu max(n, 100) będą obliczane z największą możliwą prędkością, bez żadnego narzutu związanego z przekazaniem literału przez referencję.

[edytuj] Zobacz też

[edytuj] Referencja w innych językach programowania

W pewnych językach programowania referencje w ogóle nie występują. Na przykład w języku C argumenty i wartości funkcji przekazywane są wyłącznie przez wartość.

Istnieją też języki, np. Fortran 77, w których co prawda nie ma typów referencyjnych, ale referencje wykorzystywane są (niejawnie) do przekazywania argumentów i wartości funkcji. Własność ta w niektórych starszych implementacjach fortranu (np. Lahey 77) prowadziła do nieoczekiwanego efektu: programy mogły modyfikować wartości literałów (sic!).

W innych językach programowania, takich jak Python czy Java, referencja ma semantykę bliższą wskaźnikom z C++. Każde przypisanie do referencji powoduje, że wskazuje ona na nowo podany obiekt. Pustą referencję (nie wskazującą na żaden obiekt) oznacza się specjalnym słowem kluczowym (null w Javie, nil w Smalltalku) lub specjalnym obiektem pustym (ang. null object; w Pythonie jest to None). Od wskaźnika tak rozumiana referencja różni się tym, że nie może wskazywać na błędny, przypadkowy adres w pamięci. Zawsze jest albo pusta, albo wskazuje na konkretny obiekt. Eliminuje to całą kategorię błędów wynikających z próby interpretacji przypadkowego fragmentu pamięci jako obszaru zawierającego konkretne, użyteczne dane.


aa - ab - af - ak - als - am - an - ang - ar - arc - as - ast - av - ay - az - ba - bar - bat_smg - bcl - be - be_x_old - bg - bh - bi - bm - bn - bo - bpy - br - bs - bug - bxr - ca - cbk_zam - cdo - ce - ceb - ch - cho - chr - chy - co - cr - crh - cs - csb - cu - cv - cy - da - de - diq - dsb - dv - dz - ee - el - eml - en - eo - es - et - eu - ext - fa - ff - fi - fiu_vro - fj - fo - fr - frp - fur - fy - ga - gan - gd - gl - glk - gn - got - gu - gv - ha - hak - haw - he - hi - hif - ho - hr - hsb - ht - hu - hy - hz - ia - id - ie - ig - ii - ik - ilo - io - is - it - iu - ja - jbo - jv - ka - kaa - kab - kg - ki - kj - kk - kl - km - kn - ko - kr - ks - ksh - ku - kv - kw - ky - la - lad - lb - lbe - lg - li - lij - lmo - ln - lo - lt - lv - map_bms - mdf - mg - mh - mi - mk - ml - mn - mo - mr - mt - mus - my - myv - mzn - na - nah - nap - nds - nds_nl - ne - new - ng - nl - nn - no - nov - nrm - nv - ny - oc - om - or - os - pa - pag - pam - pap - pdc - pi - pih - pl - pms - ps - pt - qu - quality - rm - rmy - rn - ro - roa_rup - roa_tara - ru - rw - sa - sah - sc - scn - sco - sd - se - sg - sh - si - simple - sk - sl - sm - sn - so - sr - srn - ss - st - stq - su - sv - sw - szl - ta - te - tet - tg - th - ti - tk - tl - tlh - tn - to - tpi - tr - ts - tt - tum - tw - ty - udm - ug - uk - ur - uz - ve - vec - vi - vls - vo - wa - war - wo - wuu - xal - xh - yi - yo - za - zea - zh - zh_classical - zh_min_nan - zh_yue - zu -