Wskaźniki potrójne w C: czy to kwestia stylu?
Czuję, że potrójne wskaźniki w C są uważane za „złe”. Korzystam z nich od czasu do czasu.
Począwszy od podstaw,
jeden wskaźnik
ma dwa cele: stworzyć tablicę i pozwolić funkcji modyfikować jej zawartość (przekazywać przez referencję):
char *a;
a = malloc...
lub
void foo (char *c);//means I'm going to modify the parameter in foo.
{ *c = 'f'; }char a;
foo(&a);
Podwójnie
wskaźnik
może być tablicą 2D (lub tablicą tablic, ponieważ każda „kolumna” lub „wiersz” nie musi mieć tej samej długości). Osobiście lubię go używać, gdy muszę przekazać tablicę 1D:
void foo (char **c);//means I'm going to modify the elements of an array in foo.
{ (*c)[0] = 'f'; }char *a;
a = malloc...
foo(&a);
Mi pomaga opisać, co robi fu. Nie jest to jednak konieczne:
void foo (char *c);//am I modifying a char or just passing a char array?
{ c[0] = 'f'; }char *a;
a = malloc...
foo(a);
zadziała też.
Zgodnie z pierwszą odpowiedzią na
to pytanie
https://coderoad.ru/1106957/
jeśli
foozmienia rozmiar tablicy, wymagany jest podwójny wskaźnik.
Możesz wyraźnie zobaczyć, jak to potrwa
potrójny wskaźnik
(i nie tylko). W moim przypadku, gdybym przekazywał tablicę wskaźników (lub tablicę tablic), użyłbym tego. Oczywiście jest to wymagane, jeśli przejdziesz do funkcji, która zmienia rozmiar tablicy wielowymiarowej. Oczywiście tablice tablic nie są zbyt powszechne, ale są inne przypadki.
Więc jakie są te konwencje? Czy to naprawdę tylko kwestia stylu/czytelności w połączeniu z faktem, że wielu osobom trudno jest owinąć głowę wskazówkami?
Nie znaleziono powiązanych wyników
Zaproszony:
Aby odpowiedzieć na pytania, Zaloguj się lub Zarejestruj się
5 odpowiedzi
Anonimowy użytkownik
Potwierdzenie od:
Załóżmy, że masz tutaj małą deklarację funkcji:
Hmmm. Czy argument jest nieregularną tablicą 3-D, wskaźnikiem do tablicy nieregularnej 2-D lub wskaźnikiem do wskaźnika do tablicy (na przykład funkcja przydziela tablicę i przypisuje wskaźnik do int wewnątrz funkcji)
Porównajmy to z:
Możesz oczywiście użyć potrójnych wskaźników do liczb całkowitych, aby pracować z macierzami.
Ale to nie to, czym oni są
... Fakt, że są one tutaj zaimplementowane jako potrójne wskaźniki, nie ma
relacje
do użytkownika.
Złożone struktury danych powinny być
kapsułkowane
... To jedna z głównych idei programowania obiektowego. Nawet w C możesz do pewnego stopnia zastosować tę zasadę. Zawiń strukturę danych w strukturę (lub, bardzo często w C, używając „uchwytów”, czyli wskaźników do niekompletnego typu - ten idiom zostanie wyjaśniony w dalszej części odpowiedzi).
Załóżmy, że zaimplementowałeś macierze jako tablice postrzępione . W porównaniu z ciągłymi tablicami 2D, są gorsze, gdy je iterują (ponieważ nie należą do tego samego bloku ciągłej pamięci), ale można uzyskać do nich dostęp za pomocą notacji tablicowej, a każdy ciąg może mieć inny rozmiar.
Więc teraz problem polega na tym, że nie możesz teraz zmienić widoków, ponieważ użycie wskaźników jest zakodowane na stałe w kodzie niestandardowym, a teraz utkniesz z gorszą implementacją.
Nie stanowiłoby to nawet problemu, gdybyś zamknął go w strukturze.
po prostu zmienia się na
Metoda deskryptora działa w następujący sposób: w pliku nagłówkowym deklarujesz niedokończoną strukturę i wszystkie funkcje, które działają na wskaźniku do tej struktury:
w pliku źródłowym deklarujesz aktualną strukturę i definiujesz wszystkie funkcje:
reszta świata nie wie, co zawiera , ani nie zna jej rozmiaru. Oznacza to, że użytkownicy nie mogą bezpośrednio deklarować wartości, ale tylko za pomocą wskaźnika do funkcji i . Jednak fakt, że użytkownik nie zna rozmiaru, oznacza, że jest od niego niezależny - co oznacza, że możemy dowolnie usuwać lub dodawać elementy do .
Anonimowy użytkownik
Potwierdzenie od:
Potrzeba trzech poziomów pośrednich często wynika z nieporozumień dotyczących prawidłowego dynamicznego przydzielania tablic wielowymiarowych. Często jest źle rozumiany nawet w książkach o programowaniu, po części dlatego, że robienie tego dobrze było kłopotliwe przed standardem C99. Mój post Pytania i odpowiedzi
poprawnie rozprowadza tablice wielowymiarowe
https://coderoad.ru/42094465/
rozwiązując ten sam problem, a także ilustruje, jak wiele poziomów pośrednich sprawi, że kod będzie jeszcze trudniejszy do odczytania i utrzymania.
Chociaż, jak wyjaśnia ten post, są sytuacje, w których może mieć sens. Przykładem jest zmienna tabela ciągów o zmiennej długości. A kiedy pojawi się potrzeba , możesz wkrótce ulec pokusie, aby użyć , ponieważ musisz zwrócić swój za pomocą parametru funkcji.
Najczęściej taka potrzeba pojawia się w sytuacji, gdy projektujesz jakiś kompleks ADT. Na przykład, załóżmy, że kodujemy tablicę skrótów, w której każdy indeks jest połączoną listą połączoną, a każdy węzeł na połączonej liście jest tablicą. Zatem poprawnym rozwiązaniem jest przeprojektowanie programu tak, aby używał struktur zamiast kilku poziomów pośrednich. Tabela skrótów, połączona lista i tablica muszą być różnymi typami, samodzielnymi typami bez wzajemnej świadomości.
W ten sposób, używając odpowiedniego projektu, automatycznie unikniemy wielu gwiazd.
Ale tak jak w przypadku każdej zasady dobrej praktyki programistycznej, zawsze są wyjątki. Jest całkiem możliwe, że mamy taką sytuację:
ty
czy możesz
zaimplementuj powyższe jako ADT, ale mogą być również dobre powody, aby zachować prostotę i prostotę, używając
char * [n]
. Następnie masz dwie opcje dynamicznego rozpowszechniania tych informacji:lub
char** ptr_ptr = malloc( sizeof(char*[n]) );
Pierwsza jest bardziej poprawna formalnie, ale też uciążliwa. Ponieważ powinno być używane jak
(* arr_ptr) [i] = "string";
, podczas gdy alternatywa może być używana jakptr_ptr [i] = "string";
.Załóżmy teraz, że musimy umieścić wywołanie malloc wewnątrz funkcji,
i
typ zwracania jest zarezerwowany dla kodów błędów, jak to zwykle robi się w C API. Wtedy te dwie alternatywy będą wyglądać tak:
lub
Trudno się spierać i powiedzieć, że pierwsza opcja jest bardziej czytelna, a także wiąże się z uciążliwym dostępem wymaganym przez dzwoniącego. Trzygwiazdkowa alternatywa jest właściwie bardziej elegancka w tym bardzo szczególnym przypadku.
Dlatego nie powinniśmy dogmatycznie odrzucać trzech poziomów pośrednictwa. Ale wybór ich użycia musi być dobrze poinformowany, ze świadomością, że mogą tworzyć brzydki kod i że istnieją inne alternatywy.
Anonimowy użytkownik
Potwierdzenie od:
Wielokrotne pośrednictwo nie jest złym stylem ani czarną magią, a jeśli masz do czynienia z danymi wielowymiarowymi, będziesz miał do czynienia z wysokim poziomem pośrednictwa; jeśli naprawdę masz do czynienia ze wskaźnikiem do wskaźnika do wskaźnika do , napisz . Nie chowaj wskaźników za typedefami,
Jeśli
tylko użytkownik tego typu nie powinien zawracać sobie głowy jego wskaźnikiem. Na przykład, jeśli podasz typ jako „uchwyt”, który jest przekazywany do interfejsu API, na przykład:
to dokładnie z . Ale jeśli kiedykolwiek będzie wymagało dereferencji, np.
następnie
nie rób tego
... Jeśli twój użytkownik
musisz wiedzieć
że jest wskaźnikiem do <sup>
1
</sup>
Aby używać go poprawnie, ta informacja
nie
powinien chować się za typedef.
Powiem, że w moim doświadczeniu nie miałem do czynienia z wyższymi poziomami pośrednictwa; triple indirection było najwyższe i nie musiałem go używać więcej niż kilka razy. Jeśli regularnie napotykasz & > dane trójwymiarowe, zauważysz wysoki poziom pośredni, ale jeśli zrozumiesz, jak
praca
wyrażenia wskaźnikowe i pośrednie nie powinny stanowić problemu.
<sup>
1. Albo wskaźnik do wskaźnika do , albo wskaźnik do wskaźnika do wskaźnika do , lub cokolwiek innego.
</sup>
Anonimowy użytkownik
Potwierdzenie od:
tablice nie są wskaźnikami
.
Zaczynając od podstaw, pojedynczy wskaźnik ma dwa cele: stworzyć tablicę i pozwolić funkcji modyfikować jej zawartość (przekazywać przez referencję):
Kiedy deklarujesz wskaźnik, musisz go zainicjować, zanim użyjesz go w swoim programie. Można to zrobić, przekazując do niego adres zmiennej lub dynamicznie przydzielając pamięć.
W tym drugim przypadku wskaźnik może być używany jak tablice indeksowane (ale nie jest to tablica).
Podwójny wskaźnik może być tablicą 2D (lub tablicą tablic, ponieważ każda „kolumna” lub „wiersz” nie musi mieć takiej samej długości). Osobiście lubię go używać, gdy muszę przekazać tablicę 1D:
Znowu źle. Tablice nie są wskaźnikami i na odwrót. Wskaźnik do wskaźnika nie jest tablicą 2D.
Radziłbym przeczytać
sekcja c-faq 6. Tablice i wskaźniki
http://www.c-faq.com/aryptr/index.html
.
Anonimowy użytkownik
Potwierdzenie od:
Ale bardziej fundamentalnie, natknąłeś się tutaj na jeden z głównych stylistycznych zastrzeżeń do potrójnego wskaźnika:
Można wyraźnie zobaczyć, jak potrzebny byłby potrójny wskaźnik (a nawet więcej).
Problem jest tutaj i poza nim: kiedy dojdziesz do trzech poziomów, na czym się zatrzymasz? Oczywiście,
mogą
mają określoną liczbę poziomów pośrednich. Ale lepiej jest po prostu mieć znajomą granicę gdzieś, gdzie przejrzystość jest nadal dobra, ale elastyczność jest wystarczająca. Dwa to dobra liczba. „Programowanie trzech gwiazdek”, jak to się czasem nazywa, jest w najlepszym przypadku kontrowersyjne; jest to albo genialne, albo bolesne dla każdego, kto później potrzebuje obsługiwać kod.