Dzisiaj o typach składowania parametrów funkcji, nazywanych również atrybutami. Sporo zastanawiałem się,
czy opisy umieścić tutaj, jednak stwierdziłem, że chyba łatwiej i czytelniej będzie w postaci
komentarzy w kodzie. Mam nadzieję, że taka forma będzie wam odpowiadać.
Omówimy sobie krótko na przykładach trzy typy składowania in, out,
oraz ref (lub jak ktoś woli inout (h3r3tic mnie dziś uświadomił, że inout nie jest aktualnie zalecany i że prawdopodobnie zostanie usunięty, lub zmieni się jego semantyka, także lepiej nie używać). Zastanawiałem się też od którego z nich
zacząć, zdecydowałem się na in. Jeżeli ktoś nie miał do czynienia z programowaniem wcześniej to jego zachowanie,
może wydawać się dość dziwne, w rzeczywistości jest jednak jasne i proste :).
- import tango.io.Stdout;
- // atrybut 'in' mówi, że przekazanie nastąpi przez wartość.
- // zmiany na obiekcie, nie będą widoczne w metodzie wywołującej
- // jednakże...
- void test_int(in int x) {
- x = 70;
- Stdout ("int in test_int ") (x).newline;
- }
- // ... z powodu 'płytkiego kopiowania', zmiany na elementach
- // przekazywanego obiektu (czyli wiersz z zamianą znaku) będą
- // widoczne w metodzie wywołującej
- void test_func(in char[] a) {
- a ~= " zonk"; // ponieważ w D ciągi znaków są obiektami
- // ta zmiana powoduje zmianę rozmiaru,
- // i doklejenie ciągu 'zonk' (lokalnie!)
- // jednakże nie niszczy obiektu, który
- // otrzymaliśmy przy wywołaniu funkcji
- a[0] = 'X';
- a[1] = 'X';
- a = "blah!".dup; // niszczymy (lokalnie) orginalny obiekt,
- a[2] = 'X'; // więc te podmiany liter, nie będą
- a[3] = 'X'; // widoczne w metodzie wywołującej
- Stdout ("string in func ") (a).newline;
- }
- void main() {
- char[] t = "zażółć gęślą jaźń".dup;
- int val=15;
- test_func(t);
- Stdout("string in main ") (t).newline;
- test_int(val);
- Stdout ("int in main ") (val).newline;
- }
Podając do rebuilda opcję -exec spowodujem odpalenie programu zaraz po kompilacj, tak więc:
- rebuild -clean -exec test_in.d
Krótkie wyjaśnienie do powyższego kodu. W funkcji main, zaraz za ciągiem znaków widać słówko .dup. Powoduje ono utworzenie kopii
stringa. Jest tak dlatego, że w linuxie literały stringów, są w pamięci read-only (jedno zdanie można znaleźć tutaj),
więc próba modyfikacji elementów takiego stringa, zaowocuje kochanym SIGSEGV. Stąd potrzebujemy wcześniej skopiować ten string :).
Czas na ref, czyli przekazywanie przez referencję.
- import tango.io.Stdout;
- // atrybut 'ref' mówi,
- // że przekazanie nastąpi przez referencje, tak więc
- // wszystkie zmiany których dokonamy na przekazywanym
- // obiekcie będą widoczne w metodzie wywołującej.
- void test_int(ref int x) {
- x = 70;
- Stdout ("int in test_int ") (x).newline;
- }
- void test_func(ref char[] a) {
- a ~= " zonk";
- a[0] = 'X';
- Stdout ("string in func ") (a).newline;
- }
- void main() {
- char[] t = "zażółć gęślą jaźń".dup;
- int val=15;
- test_func(t);
- Stdout("string in main ") (t).newline;
- test_int(val);
- Stdout ("int in main ") (val).newline;
- }
Chyba nie trzeba nic tłumaczyć :)
Przejdźmy do out. Out jak się można domyślić oznacza, że parametr jest tylko parametrem wyjściowym, to znaczy że dana funkcja czy metoda
ma w nosie wartość jaką tam podamy. Jest przy tym jedna ważna cecha, ale to w komentarzach.
- import tango.io.Stdout;
- // najciekawszy atrybut czyli 'out', przekazywanie następuje
- // przez referencje, jednakże, zanim obiekt trafi do metody
- // następuje jego inicjalizacja do wartości domyślnej.
- void test_int(out int x) {
- // zmianna typu int jest domyślnie inicjalizowana 0
- Stdout ("int in test_int ") (x).newline;
- }
- void test_func(out char[] a) {
- a ~= " zonk"; // ponieważ na początku zmienna a
- // jest inicjalizowana na pusty ciag,
- // to doklejenie spowoduje tak sklejenie
- // "" razem z " zonk"
- Stdout ("string in func ") (a).newline;
- }
- void main() {
- char[] t = "zażółć gęślą jaźń".dup;
- int val=15;
- test_func(t);
- Stdout("string in main ") (t).newline;
- test_int(val);
- Stdout ("int in main ") (val).newline;
- }
Najpierw kawałek kodu, a poniżej opis, jakie zostawiam zadanie ;)
- import tango.io.Stdout;
- void test_a() { Stdout ("To jest test_AAA").newline; }
- void test_X() { Stdout ("To jest test_XXX").newline; }
- void test_change(in void function() x) {
- Stdout("wywolanie pierwsze w change").newline;
- x();
- x = &test_X;
- Stdout("wywolanie drugie w change").newline;
- x();
- }
- void main() {
- void function() a = &test_a;
- test_change(a);
- Stdout("wywolanie w main ").newline;
- a();
- }
Małe wyjaśnienie. W main widać deklarację zmiennej a typu void function(),
czyli wskaźnik na funkcję typu void, nie przyjmującą żadnych parametrów. Stąd przed test_a pojawia się &,
czyli pobranie adresu funkcji test_a.
Podobnie funkcja test_change przyjmuje parametr typu wskaźnik na funkcję (typu void, która nie przyjmuje parametrów).
Zadanie jest bardzo skomplikowane, mianowicie potestować, jak ten program będzie się zachowywał, gdy podmienimy atrybut pierwszego parametru funkcji
test_change na inoutref oraz out.
Po zrobieniu zadania można zerknąć na ostatni przykład.
- module baker;
- import tango.io.Stdout;
- void baker(inout int b)
- {
- assert(b < 0x10000, " RANY! b jest większe od 65535! co my teraz zrobimy?! ");
- b = 666;
- }
- int main()
- {
- int x = 0x5ac4;
- Stdout.format ("{:x}", x).newline;
- baker(x);
- Stdout.format ("{:x}", x).newline;
- x = 0x12345;
- baker(x);
- Stdout.format ("{:x}", x).newline;
- return 0;
- }
Jedynym nowym elementem, który się tu pojawia jest assert. assert podobnie jak w C sprawdza,
czy podany warunek jest prawdziwy. Jeśli nie jest, 'rzuca' on wyjątkiem (który można oczywiście przechwycić).
assert(0); można używać, do oznaczania fragmentów kodu, do których program nie powinien dotrzeć. Pierwszą
myślą jest, "jak to możliwe, jak może być miejsce, gdzie program ma nie dotrzeć?", jednak chociażby
w przypadku budowania bardziej skomplikowanych parserów, jest to dość przydatne. Więcej na temat
Contract Programming w którymś z kolejnych odcinków
Do powyższego przykładu, proszę spróbować zmienić inout w funkcji baker, na out
i sprawdzić (albo jeszcze lepiej przewidzieć) jaki będzie wynik działania.
Omówiłem tylko trzy podstawowe typy składowania, jest ich jednak więcej i mają różne ciekawe zastosowania. Zaciekawionych odsyłam do dokumentacji:
invariant, const oraz final (const w D jest typem składowania, a nie atrybutem dla typu, więcej na ten temat oraz porównanie do C++ tutaj
lazy (warto wspomnieć, że typ składowania lazy został zasugerowany przez Tomasza 'h3r3tic'a Stachowiaka z Team0xf).
Podane w linkach typy składowania, pewnie pojawią się kiedyś w przykładach ;)
gim.org.pl is down






