Szukaj…


Uwagi

W VFP operatory są pogrupowane w te:

  • Operatory numeryczne
  • Operatory logiczne
  • Operatorzy postaci
  • Operatory daty i godziny
  • Operatorzy relacyjni

Są też operatory zaimplementowane jako funkcje (takie jak operacje bitowe, porównywanie obiektów ...).

Przyjrzymy się każdemu z przykładów.

Operatory numeryczne

Operatory numeryczne są najłatwiejsze i prawie takie same jak w innych językach.

  • +, -, * i /. Dodawanie, odejmowanie, mnożenie i dzielenie operatorów (w VFP nie ma podziału na liczby całkowite, możesz przekonwertować wynik na liczbę całkowitą za pomocą funkcji INT (), CEILING () i FLOOR ()).
  • Operator modułu%.
  • ^ i **. Moc operatora (-ów). Oboje robią to samo.
  • (). Grupowanie operatorów.
  • Operatorzy mają pierwszeństwo. Kolejność jest następująca:
   ( )
   ^ (or **) 
   / and *
   - and +
    ? 10 / 5 + 2 && Outputs 4
    ? 2 + 10 / 5 && Outputs 4 as well. Division has precedence.
     
    * Both multiplication and division have same precedence
    * They would be interpreted from left to right.
    ? 4 * 5 / 2 + 5 && Outputs 15
    * Use parentheses whenever you are in doubt or want to be explicit
    ? ( (4 * 5) / 2 ) + 5 && Outputs 15. Explicit grouping of operations

    ? 4 * 5^2 && ^ has precedence, this is same as 4 * (5^2) = 100.
    ? (4 + 5)^2 && Using parentheses we say add 5 to 4 (9) and then square = 81. 

Operatory logiczne

Operatory logiczne w VFP w kolejności ich pierwszeństwa to:

Operator Opis
() Nawiasy, wyrażenia grupowe
NIE, ! Logicznie neguj wyrażenie. NIE lub! nie ma różnicy.
I Logicznie ORAZ wyrażenia
LUB Logicznie LUB wyrażenia
<>,! =, # Sprawdź nierówności. Tak samo jak logiczne wyłączne OR - XOR

Historycznie, NIE, AND, LUB są zapisywane jako .NOT., .AND., .OR. Nadal możesz ich używać, jeśli chcesz, ale AND, OR, NOT jest prostsze i czystsze.

Dla fałszu i prawdy musisz użyć .F. oraz T. odpowiednio literały. Nie możesz zamiast tego użyć F i T.

* Some logical variables
local llOld, llEmail  && any variable declaration implicitly initializes the variable as .F. - false
? m.llOld, m.llEmail && Prints .F. .F.

llOld   = .T.
llEmail = .F.

if ( m.llOld AND m.llEmail )
   ? 'Old AND should be emailed to'
endif
if ( m.llOld OR m.llEmail )
   ? 'Old OR should be emailed to'
endif
if ( m.llOld AND !m.llEmail ) && Same as (m.llOld AND NOT m.llEmail)
   ? 'Old BUT should NOT be emailed to'
endif

* Above code outputs
Old OR should be emailed to
Old BUT should NOT be emailed to

W VFP wyrażenia logiczne są oceniane w skrócie. Oznacza to, że jeśli pierwsza część kontroli spełnia cały wynik, reszta wyrażenia nie jest nawet interpretowana. Oto przykład:

? 1 = '2' && An obvious error. It would complain operator/operand type mismatch.

* However we could use such an expression in an if and get no error
* because it is not interpreted at all 
* (VFP is dynamic and there is no compile time check)

local llProcess
llProcess = .T.

if (m.llProcess OR (1='2'))
   ? 'Should do processing'
endif

* Would output

Should do processing

* without any error because m.llProcess true means
* the whole expression would be true, thus the expression after OR 
* is not interpreted at all.

Jedną z pułapek, które łapią początkujących, jest to, że czasami może być potrzebnych wiele kontroli, powiedzmy w zapytaniu SQL, które są powiązane z operatorami AND, OR. Gdy jest ich wiele, można zignorować fakt, że operatorzy mają pierwszeństwo (w kolejności (), NIE, ORAZ, LUB) i sądzić, że interpretacja będzie wykonywana od lewej do prawej w łańcuchu. Rozważ próbkę:

select * from myTable where !isCustomer AND debit > 5000 OR discount > 5

jaki jest cel tego zapytania? Jeśli wyrazimy to wyraźnie za pomocą nawiasów grupujących, powie:

((NOT isCustomer) AND debit > 5000) OR discount > 5

uproszczony wygląda jak „firstExpression” LUB (rabat> 5). Jakikolwiek był zamiar, z tego powodu LUB wybrałby:

wszystkie wiersze, które mają (rabat> 5) - a także te, w których jest to klient z ponad 5000 debetami.

Prawdopodobnie celem było „daj mi te, w których NIE jest to klient ORAZ (debet wynosi ponad 5000 LUB rabat powyżej 5)”. Od samego początku byłoby jasne, że użyjemy nawiasów:

select * from myTable where !isCustomer AND (debit > 5000 OR discount > 5)

Możesz użyć, ale nie warto, mieć nawiasów dla początkowego operatora NOT, gdy jego operand jest pojedynczym wyrażeniem, które jest wystarczająco czytelne z pierwszeństwem -! IsCustomer jest wyraźnie odczytywany jako (NOT isCustomer).

Operatory znaków

Są tylko 4 operatory znaków, w kolejności ich pierwszeństwa:

Operator Opis
() Nawiasy do grupowania. Uwaga: w dokumentacji VFP, którą posiadam, brakuje tej. Bez tego - operator jest prawie zawsze bezużyteczny.
+ Łączy (łączy) ciągi obok siebie.
- Łączy łańcuchy, przenosząc końcowe spacje z lewego łańcucha na koniec prawego łańcucha.
$ Sprawdza, czy pierwszy ciąg jest zawarty w drugim.

+ jest najprostszym i służy także do łączenia łańcuchów w wielu innych językach.

local firstName, lastName
firstName = "John"
lastName  = "Smith"

? m.firstName + " " + m.lastName

Wyjścia: John Smith

- jest trochę trudny i mało znany. Pobiera końcowe spacje od lewego ciągu, dołącza te spacje do ciągu po prawej stronie. Załóżmy, że masz tabelę z imionami i nazwiskami, a każda z nich ma 20 znaków. Chcemy połączyć imiona i nazwiska, aby utworzyć pełną nazwę, a także chcemy, aby wynikowy rozmiar został naprawiony (w tym przypadku 20 + 20 + 1 spacja = 41). Zróbmy, że masz także kolumnę środkowego imienia i chcemy, aby pełna nazwa wyglądała jak „lastName, firstName middleName_______”. Najłatwiej to zrobić za pomocą operatora - należy jednak zwrócić uwagę na sztuczkę polegającą na stosowaniu nawiasów w celu grupowania, aby uzyskać dokładnie to, co chcemy:

* Create a cursor for our sample and fill in a few names
Create Cursor Names (firstName c(20), midName c(20), lastName c(20))

Insert Into Names (firstName, midName, lastName) Values ('Cetin','', 'Basoz')
Insert Into Names (firstName, midName, lastName) Values ('John', 'M', 'Smith')
Insert Into Names (firstName, midName, lastName) Values ('John', 'F', 'Kennedy')
Insert Into Names (firstName, midName, lastName) Values ('Tom', '', 'Hanks')

* Select with tricky - operator
Select *, ;
    lastName - (', '+firstName-(' '+midName)) As FullName ;
    from Names ;
    INTO Cursor crsNames ;
    nofilter

Browse

A wyjście jest takie:

Imię midName nazwisko pełna nazwa
Cetyna Basoz Basoz, Cetin
Jan M. Kowal Smith, John M.
Jan fa Kennedy Kennedy, John F.
Tomek Hanks Hanks, Tom

W kolumnie fullName wszystkie końcowe spacje są ładnie wypychane do końca. Jeśli sprawdzisz strukturę, kolumna fullName ma szerokość 63 znaków (3 * 20 + 3 znaki, które dodaliśmy).

Zwróć uwagę na znaczenie grupowania nawiasów (spróbuj usunąć nawiasy lub ułożyć je w inny sposób).

Chociaż - operator może kusić się w takich przypadkach, istnieje druga strona medalu. Ten operator jest specyficzny dla VFP i dlatego SQL nie jest przenośny. Zamiast tego możesz osiągnąć ten sam wynik dzięki temu SQLowi zgodnemu z ANSI:

Select *, ;
    CAST(RTRIM(lastName) +', '+ RTRIM(firstName) +' '+ midName as char(63)) As FullName ;
    from Names ;
    INTO Cursor crsNames ;
    nofilter

Ostatni operator to $. Po prostu sprawdza, czy lewy ciąg jest częścią prawego.

local upcased, digits, hexDigits
upcased = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
digits  = '0123456789'
hexDigits  = m.digits + 'ABCDEF'

? 'A' $ m.upcased && .T.
? 'a' $ m.upcased && .F.
? '1' $ m.digits && .T.
? 'F' $ m.digits && .F.
? 'F' $ m.hexDigits && .T.

Ważne: W VFP, mimo że możesz pisać kod w każdym przypadku (górny, dolny lub mieszany), w ciągach zawsze rozróżniana jest wielkość liter. Na przykład: „Smith” i „Smith” to dwie odrębne wartości. Lub w tabeli, jeśli jest kolumna kraju, nie możesz znaleźć „USA”, jeśli przeszukasz ją za pomocą „usa”. To samo dotyczy operatora $, „GE” $ „Niemcy” jest fałszem.

Uwaga osobista: chociaż może ci się podobać $ za jego prostotę i może być często używany w kodach źródłowych Microsoft, IMHO ma bardzo małą wartość. Biorąc pod uwagę wiele tysięcy wierszy, które napisałem na swoim nośniku, myślę, że bardzo niewiele razy znajdę to w swoim własnym kodzie. Prawie zawsze istnieje lepsza alternatywa (szczególnie, gdy lewy operand nie jest pojedynczym znakiem i \ lub ważna jest wielkość liter).

Operatory daty i godziny

Zasadniczo istnieją dwa operatory wartości daty i godziny. + i - są przeciążone (prawdopodobnie termin C), aby wykonać matematykę data / data / godzina:

Operator Opis
+ Dodaje dni (datę) lub sekundy (data / godzina) do wartości data / data / godzina.
- Pobiera różnicę dwóch wartości daty / godziny. Odejmuje dni (datę) lub sekundy (datetime) od wartości datetime.

+ jest łatwiejszy. Ma dwa operandy, jeden jest wartością daty lub daty i godziny, a drugi jest liczbą (chociaż możesz użyć dowolnej liczby, jest to liczba całkowita dla wszystkich praktycznych celów).

Gdy jeden z operandów jest datą, wówczas operand numeryczny jest traktowany jako „dzień”:

? Date() + 10 && Get the date 10 days later
* VFP is leap year aware when doing date math
? Date(2016, 2, 28) + 1 && Add 1 day to Feb 28, 2016. Returns Feb 29, 2016.
? Date(2017, 2, 28) + 1 && Add 1 day to Feb 28, 2017. Returns Mar 1, 2017.

Gdy jednym z operandów jest data / godzina, wówczas operand numeryczny jest traktowany jako „drugi”:

Dziennie jest 24 * 60 * 60 = 86400 sekund

? Datetime() + 86400 && Add 1 day to current datetime. 

Dodaj 4 godziny i 15 minut do 1 stycznia 2016 14:20

? Datetime(2016, 1, 1, 14, 20, 0) + (4 * 3600 + 15 * 60) 

Wyjścia piątek, 1 stycznia 2016, 18:35:00.

Dzięki prostemu drukowaniu za pomocą? To, co widzisz na ekranie, zależy od ustawień daty. Na przykład, jeśli niczego nie zmieniłeś, ustawienia daty są w stylu amerykańskim (MDY), format 12-godzinny (AM / PM), a wiek pokazuje tylko dwie ostatnie cyfry.

Istnieje jeden specjalny symbol ^ dla daty i czasu danych, zmuszający ciąg interpretowany „ściśle” jako format rrrr / MM / dd [GG: mm: ss | gg: mm: ss tt]. Dlatego ^ może być również traktowane jako operator daty / daty i godziny. Na przykład rozważmy, że niektóre dane przychodzą ze źródła w formacie takim jak 201610082230 (rrrrMMddGGmm). Aby uzyskać tę wartość jako prawidłową datę i godzinę:

Local cSample, tSample
cSample = '201610082230'
tSample = Ctot(Transform(m.cSample, '@R ^9999/99/99 99:99'))
? Transform(m.tSample, '@YL')

Dane wyjściowe (w zależności od ustawienia długiej daty w systemie):

Sobota, 8 października 2016, 22.30

- służy do odejmowania. Jego argumenty to albo data / data / godzina, albo jeden to data / data / godzina, a drugi to wartość liczbowa.

Zacznijmy od prostszych operatorów data / data i cyfry (takich jak operator +):

Gdy jeden z operandów jest datą, wówczas operand numeryczny jest traktowany jako „dzień”:

? Date() - 10 && What was the date 10 days ago?
? Date(2016, 3, 1) - 1 && Returns Feb 29, 2016.
? Date(2017, 3, 1) - 1 && Returns Feb 28, 2017.

Gdy jednym z operandów jest data / godzina, wówczas operand numeryczny jest traktowany jako „drugi”:

? Datetime() - 86400 && Go back exactly one day

Uzyskaj 1 godzinę i 30 minut temu od „teraz”:

? Datetime() - (1 * 3600 + 30 * 60) 

Druga forma polega na uzyskaniu różnicy między dwiema wartościami data / data / godzina. Operandy to data lub data i godzina, nie można jednocześnie używać daty i godziny (wykonaj konwersję typów, jeśli to konieczne, VFP nie robi tego za Ciebie). Reguły są jak w + i -, operandy są datą, a różnica jest w dniach , operandy są datą, a różnica w sekundach .

Ile dni do sylwestra (na rok 2016)?

? Date(2016, 12, 31) - Date()

Ile sekund pozostało do północy?

? Dtot(Date()+1) - Datetime()

W ostatniej próbce użyliśmy funkcji Data / Data, DTOT - DateToTime, aby uzyskać jutrzejszą północ. Dostępnych jest wiele przydatnych funkcji daty / daty, pominęliśmy je wszystkie, ponieważ technicznie nie są uważane za operatory (chociaż działają one na datę / godziny). To samo dotyczy również innych operatorów.

Odejmowanie daty / godziny jest podpisane . To znaczy, jeśli użyjesz mniejszej daty / daty jako pierwszego operandu, wynik będzie ujemny. Możesz użyć funkcji abs (), jeśli chcesz uzyskać pozytywny wynik bez względu na kolejność dat / dat.

Operatorzy relacyjni

Ze wszystkich operatorów operatory relacyjne są najbardziej złożone, dlatego zostawiliśmy je do końca.

Operatory relacyjne są również znane jako operatory porównania, służą do porównywania rzeczy.

Wynik porównania to boolean false lub true.

Co ciekawe, jeśli zaznaczysz to w VFP, zobaczysz tylko krótką listę operacji i jeszcze kilka wierszy, jakby chodziło tylko o tych operatorów.

Cóż, złożoność wynika z faktu, że działają na każdym typie, niezależnie od tego, czy jest to wartość liczbowa, data, godzina, data, logika, czy ciąg, a nawet obiekty. Co więcej, zachowanie może wyglądać niezręcznie, nie dostaniesz tego, czego oczekujesz, chyba że wiesz, co wpływa na wyniki.

Zacznijmy od listy operatorów relacyjnych:

Operator Opis Najbardziej podstawowa próbka
> Lepszy niż ? 1> 2 i&.
< Mniej niż ? 1 <2 && .T.
> = Większe bądź równe ? 1> = 2 && .F.
<= Mniejszy lub równy ? 1 <= 2 && .T.
= Równy ? 1 = 1 && .T.
== Jest dokładnie równy (ma sens dla ciągów) ? „1” = „1” && .T.
! =, #, <> Nie równa się (wszyscy 3 operatorzy działają w ten sam sposób, wybierz swojego ulubionego) ? 1! = 1 && .F.

Chociaż można ich używać ze wszystkimi typami danych, między operandami powinna istnieć kompatybilność typów. Na przykład wystąpi błąd, jeśli spróbujesz porównać datę z liczbą całkowitą.

Można porównać datę i datę, chociaż są to różne typy, VFP dokonuje konwersji domyślnie dla Ciebie.

? Date() > DateTime() && .F. 
? Date() <= DateTime() && .T. 
? Date() < DateTime() && .T. if it is not midnight

Gdy operandy są numeryczne, wszystkie te operatory są proste i proste, działają tak, jak w wyrażeniach matematycznych.

Z logicznymi operandami, .F. jest uważany za mniejszy niż .T.

W przypadku obiektów porównujemy odniesienie do obiektu w pamięci. Dlatego najczęściej stosowanym porównaniem jest ustalenie, czy dwie zmienne obiektowe wskazują na ten sam obiekt. to znaczy:

local o1, o2
o1 = createobject('Label')
o2 = createobject('Label')
? m.o1 = m.o2 && is o1 and o2 the same object?
? m.o1 > m.o2 && this would work too but likely you would never use

* remember we are comparing their references in memory
* 
* They are different objects, but do they have any difference in their properties?
? CompObj(m.o1, m.o2) && .T. They are identical properties wise

Porównanie typu danych znaków, czyli porównywanie ciągów, jest najbardziej mylące w VFP. Nie działa jak w innych językach i / lub bazach danych i jest unikalny dla VFP (i może dla jakiegoś innego języka xBase).

Wiele lat temu widziałem nawet bardzo zaawansowanych członków społeczności, którzy nie byli jeszcze świadomi, jak operatorzy działają w VFP. Jest więc zrozumiałe, że niewielkie niuanse mogą łatwo wprowadzić w błąd początkujących.

Porównanie polega zasadniczo na byciu równym lub nie. Jeśli nie są równe, możemy pomyśleć o operatorach>, <,> =, <=, prawda? W przypadku ciągów jest mylące, gdy dwa ciągi są uważane za równe .

Ważne: w ciągach VFP rozróżniana jest wielkość liter. „A” i „a” to dwa różne ciągi. Nie dotyczy to wielu baz danych, w których domyślnie stosuje się sortowanie bez rozróżniania wielkości liter. Na przykład w postgreSQL lub MS SQL Server w tabeli utworzonej za pomocą sortowania bez rozróżniania wielkości liter (CI):

select * from myTable where Country = 'TURKEY'

select * from myTable where Country = 'Turkey'

dałby ten sam wynik. W VFP dostajesz tylko te, w których obudowa pasuje. Jednak VFP ma pewne wsparcie sortowania i sprawia, że porównanie bez rozróżniania wielkości liter. (Nie ufaj, patrz poniżej)

  • Jeśli dwa łańcuchy nie są równe, jak dotąd tak dobre, pod warunkiem, że nie zmieniłeś żadnych wartości domyślnych, wówczas są one porównywane na podstawie ich wartości ASCII .

    ? 'Basoz' < 'Cetin' && is true.
    ? 'basoz' < 'Cetin' && is false.
    ? 'Cetin' < 'David' && is true.
    ? 'Çetin' < 'David' && is false.
    

Domyślne zestawienie to „maszyna” i właśnie to dostajesz. Gdy zmienisz sortowanie na coś innego, otrzymasz porównanie na podstawie kolejności sortowania tego sortowania. Przy ustawieniu sortowania innym niż domyślny komputer sugerujesz także, że porównanie nie uwzględnia wielkości liter (NIE ufaj temu dla równości):

  set collate to 'GENERAL'
  ? 'Basoz' < 'Cetin'
  ? 'basoz' < 'Cetin'
  ? 'Cetin' < 'David'
  ? 'Çetin' < 'David'

Teraz wszystkie te wyrażenia są PRAWDA.

Osobista rada: zestawienia w VFP nigdy nie były wystarczająco wiarygodne. Sugeruję, aby nie używać zestawień i trzymać się domyślnego „MASZYNY”. Jeśli użyjesz sortowania, pamiętaj o sprawdzeniu go najpierw, gdy zauważysz coś nieoczekiwanego w odniesieniu do danych postaci. Widziałem i pokazałem, że w wielu przypadkach się nie udaje, ale potem przestałem próbować używać go dużo przed wersją VFP9, może być teraz spójne, naprawdę nie wiem.

Biorąc pod uwagę, że omawialiśmy przypadki nierówności za pomocą łańcuchów, trudnym jest przypadek równości. W VFP w zasadzie na porównanie wpływają dwa ustawienia:

  1. USTAW DOKŁADNIE (ustawienie domyślne to WYŁ. I powoduje regularne porównania - oprócz SQL)
  2. USTAW ANSI (Domyślnie jest WYŁĄCZONA, a porównania efektów dotyczą tylko SQL. SET EXACT nie ma wpływu na porównania w zapytaniach SQL.

Po ustawieniu WYKŁADZIE WYŁ. Odczytaj porównanie jako „czy łańcuch po prawej zaczyna się od łańcucha po lewej”? Są one porównywane do długości odpowiedniej struny.

? "Bobby" = "B" && Bobby starts with B, so TRUE
? "Bobby" = "Bob" && Bobby starts with Bob, so TRUE
? "Bobby" = "Bob " && Bobby starts with Bob but there is a trailing space there, FALSE
? "Bobby" = "bob" && would be true with collation set to GENERAL

Zauważ, że przy regularnym porównaniu „Bobby” = „B” ma wartość PRAWDA, ale „B” = „Bobby” ma wartość FAŁSZ. Innymi słowy, miejsce operandów jest ważne.

Przy SET EXACT ON ciągi muszą się w pełni zgadzać, ale ich końcowe spacje są ignorowane (ignorujemy tutaj zestawienie zestawień, co również powoduje rozróżnianie wielkości liter):

? "BOBBY" = "BOB" && FALSE 
? "BOBBY" = "BOBBY" && TRUE
? "BOBBY" = "BOBBY     " && TRUE 
? "BOBBY     " = "BOBBY" && TRUE

Teraz przy pomocy poleceń SQL SET EXACT nie ma żadnego efektu i działałoby tak, jak robi to SET EXACT OFF.

Select * from Customers where Country = 'U'

Wybrałby klientów z USA, Wielkiej Brytanii z dowolnego kraju zaczynającego się na „U”.

W SQL jednak z definicji zmiana kolejności operandów powinna dawać ten sam wynik. A zatem:

Select * from Customers where 'U' = Country

działałby również w ten sam sposób (zauważ różnicę w stosunku do poleceń innych niż SQL).

Jeśli chcesz sugerować dokładne dopasowania, jedną z opcji jest włączenie ANSI:

SET ANSI ON
Select * from Customers where Country = 'USA'

zwraca wszystkich tych klientów z USA. Zauważ, że końcowe spacje w polu kraju LUB na prawym wyrażeniu są ignorowane. Nie ma znaczenia, ile masz trailing po obu stronach. Otrzymujesz porównanie tak, jakby to było zrobione jak: RTRIM (Kraj) = RTRIM („USA”).

Chociaż nie jest wspomniany w Operatory w VFP, operatorem SQL jest LIKE. Korzystając z LIKE, uzyskujesz dokładne dopasowanie dopasowania niezależnie od ustawienia SET ANSI (używając sił LIKE i niejawnego przypadku ANSI ON - w końcu jest to operator ANSI). Uważaj jednak, istnieje niewielka różnica w zachowaniu. Nie ignorowałoby to spacji końcowych, chyba że całkowity rozmiar z przyczepami jest równy lub mniejszy niż rozmiar pola. Na przykład, jeśli pole Country to C (10), wówczas Country = „USA” lub Country = „USA__” będzie działać, ale Country = „USA___________” nie powiedzie się ( podkreślenia oznaczają spację, a ostatnia ma więcej niż 7 spacji końcowych).

Nareszcie jesteśmy do ostatniego operatora, ==. Oznacza to, że jest dokładnie równy i można go używać z łańcuchami. Jedną z zalet jest to, że użycie == oznacza zawsze, że chcesz mieć dokładne dopasowanie niezależnie od ustawień SET EXACT lub SET ANSI. Jednak uważaj ponownie, jego zachowanie jest inne, gdy jest to polecenie SQL lub zwykłe polecenie SQL.

Z SQL:

Select * from Customers where Country == 'USA'

bez względu na ustawienia ANSI i EXACT chcemy wszystkich klientów tylko z USA. Końcowe spacje po obu stronach są ignorowane.

Z nie-SQL:

? m.lcString1 == m.lcString2

byłoby prawdziwe tylko wtedy, gdy są dokładnie takie same, jeśli chodzi o ich obudowę i długość (końcowe spacje NIE są ignorowane). Nie ma to wpływu na ustawienia SET ANSI, EXACT lub COLLATE.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow