C/Powszechne praktyki: Różnice pomiędzy wersjami
Usunięta treść Dodana treść
szczegóły |
Pokolorowano kod |
||
Linia 6:
Ponieważ C nie jest językiem obiektowym, nie ma wbudowanego wsparcia dla konstruktorów i destruktorów. Często programiści bezpośrednio modyfikują tworzone obiekty i struktury. Jednakże prowadzi to do potencjalnych błędów, ponieważ operacje na obiekcie mogą się nie powieść lub zachować się nieprzewidywalnie, jeśli obiekt nie został prawidłowo zainicjalizowany. Lepszym podejściem jest stworzenie funkcji, która tworzy instancję obiektu, ewentualnie przyjmując pewne parametry:
<source lang="c">
struct string {
size_t size;
Linia 20 ⟶ 21:
return new_string;
}
</source>
Podobnie, bezpośrednie usuwanie obiektów może nie do końca się udać, prowadząc do wycieku zasobów. Lepiej jest użyć destruktora:
<source lang="c">
void free_string(struct string *s)
{
Linia 29 ⟶ 32:
free(s); /* usuwamy samą strukturę */
}
</source>
Często łączy się destruktory z [[#Zerowanie zwolnionych wskaźników|zerowaniem zwolnionych wskaźników]].
Linia 34 ⟶ 38:
Czasami dobrze jest ukryć definicję obiektu, żeby mieć pewność, że użytkownicy nie utworzą go ręcznie. Aby to zapewnić struktura jest definiowana w pliku źródłowym (lub prywatnym nagłówku niedostępnym dla użytkowników) zamiast w pliku nagłówkowym, a deklaracja wyprzedzająca jest umieszczona w pliku nagłówkowym:
<source lang="c">
struct string;
struct string *create_string(const char *initial);
void free_string(struct string *s);
</source>
== Zerowanie zwolnionych wskaźników ==
Linia 43 ⟶ 49:
Jednym z prostych rozwiązań tego problemu jest zapewnienie, że każdy wskaźnik jest zerowany natychmiast po zwolnieniu:
<source lang="c">
free(p);
p = NULL;
</source>
Inaczej niż w przypadku "wiszących wskaźników", na wielu nowoczesnych architekturach przy próbie użycia wyzerowanego wskaźnika pojawi się sprzętowy wyjątek. Dodatkowo, programy mogą zawierać sprawdzanie błędów dla zerowych wartości, ale nie dla "wiszących wskaźników". Aby zapewnić, że jest to wykonywane dla każdego wskaźnika, możemy użyć makra:
<source lang="c">
#define FREE(p) do { free(p); (p) = NULL; } while(0)
</source>
<small>(aby zobaczyć dlaczego makro jest napisane w ten sposób, zobacz [[#Konwencje pisania makr]])</small>
Linia 54 ⟶ 64:
Przy wykorzystaniu tej techniki destruktory powinny zerować wskaźnik, który przekazuje się do nich, więc argument musi być do nich przekazywany przez referencję. Na przykład, oto zaktualizowany destruktor z sekcji [[#Konstruktory i destruktory|Konstruktory i destruktory]]:
<source lang="c">
void free_string(struct string **s)
{
Linia 60 ⟶ 71:
FREE(*s); /* usuwamy strukturę */
}
</source>
Niestety, ten idiom nie jest wstanie pomóc w wypadku wskazywania przez inne wskaźniki zwolnionej pamięci. Z tego powodu niektórzy eksperci C uważają go za niebezpieczny, jako kreujący fałszywe poczucie bezpieczeństwa.
Linia 88 ⟶ 100:
Oprócz tego są także przesunięcia ('''<nowiki><<</nowiki>''' oraz '''<nowiki>>></nowiki>'''). Zastanówmy się teraz, jak je wykorzystać w praktyce. Załóżmy, że zajmujemy się jednobajtową zmienną.
<source lang="c">
unsigned char i = 2;
</source>
Z matematyki wiemy, że zapis binarny tej liczby wygląda tak (w ośmiobitowej zmiennej):
00000010. Jeśli teraz np. chcielibyśmy "zapalić" drugi bit od lewej (tj. bit, którego zapalenie niejako "doda" do liczby wartość 2<sup>6</sup>) powinniśmy użyć logicznego lub:
<source lang="c">
unsigned char i = 2;
i |= 64;
</source>
Gdzie 64=2<sup>6</sup>. Odczytywanie wykonuje się za pomocą tzw. maski bitowej. Polega to na:
Linia 101 ⟶ 117:
Do "wyłuskania" odpowiedniego bitu możemy posłużyć się operacją "i" - czyli operatorem '''&'''. Wygląda to analogicznie do posługiwania się operatorem "lub":
<source lang="c">
unsigned char i = 3; /* bitowo: ''00000011'' */
unsigned char temp = 0;
Linia 110 ⟶ 127:
printf ("bit zgaszony");
}
</source>
Jeśli nie władasz biegle kodem binarnym, tworzenie masek bitowych ułatwią ci przesunięcia bitowe. Aby uzyskać liczbę która ma zapalony bit o numerze <code>n</code> (bity są liczone od zera), przesuwamy bitowo w lewo jedynkę o <code>n</code> pozycji:
<source lang="c">
1 << n
</source>
Jeśli chcemy uzyskać liczbę, w której zapalone są bity na pozycjach <code>l, m, n</code> - używamy sumy logicznej ("lub"):
<source lang="c">
(1 << l) | (1 << m) | (1 << n)
</source>
Jeśli z kolei chcemy uzyskać liczbę gdzie zapalone są wszystkie bity poza <code>n</code>, odwracamy ją za pomocą operatora logicznej negacji <code>~</code>
<source lang="c">
~(1 << n)
</source>
Warto władać biegle operacjami na bitach, ale początkujący mogą (po uprzednim przeanalizowaniu) zdefiniować następujące makra i ich używać:
<source lang="c">
/* Sprawdzenie czy w liczbie k jest zapalony bit n */
#define IS_BIT_SET(k, n) ((k) & (1 << (n)))
Linia 133 ⟶ 158:
/* Zgaszenie bitu n w zmiennej k */
#define RESET_BIT(k, n) (k &= ~(1 << (n)))
</source>
== Skróty notacji ==
Istnieją pewne sposoby ograniczenia ilości niepotrzebnego kodu. Przykładem może być wykonywanie jednej operacji w razie wystąpienia jakiegoś warunku, np. zamiast pisać:
<source lang="c">
if (warunek) {
printf ("Warunek prawdziwy\n");
}
</source>
możesz skrócić notację do:
<source lang="c">
if (warunek)
printf ("Warunek prawdziwy\n");
</source>
Podobnie jest w przypadku pętli for:
<source lang="c">
for (;warunek;)
printf ("Wyświetlam się w pętli!\n");
</source>
Niestety ograniczeniem w tym wypadku jest to, że można w ten sposób zapisać tylko jedną instrukcję.
|