2016-09-01 15 views
22

Książka, którą czytam, Introduction to Data Structures with Linked Lists (Presentation 21), zawiera 2 przykłady połączonych list. Oto pierwszy z nich:Nie rozumiem, dlaczego ta funkcja "zwraca wskaźnik z listy"

EnemySpaceShip* getNewEnemy() 
{ 
    EnemySpaceShip* p_ship = new EnemySpaceShip; 
    p_ship->x_coordinate = 0; 
    p_ship->y_coordinate = 0; 
    p_ship->weapon_power = 20; 
    p_ship->p_next_enemy = p_enemies; 
    p_enemies = p_ship; 
    return p_ship; 
} 

Drugi przykład połączonych listach jest to jedno:

EnemySpaceShip* addNewEnemyToList (EnemySpaceShip* p_list) 
{ 
    EnemySpaceShip* p_ship = new EnemySpaceShip; 
    p_ship->x_coordinate = 0; 
    p_ship->y_coordinate = 0; 
    p_ship->weapon_power = 20; 
    p_ship->p_next_enemy = p_list; 
    return p_ship; 
} 

Wtedy książka pisze tak:

Zauważ, że ta funkcja różni się od getNewEnemy ponieważ zwraca wskaźnik do listy, a nie nowego wroga.

Nie rozumiem, co rozumie przez "drugą funkcję zwraca wskaźnik do listy", a "pierwsza funkcja zwraca nowego wroga". Myślałem, że obaj stworzyli nowego wroga o nazwie p_ship (który jest zarówno wskaźnikiem, jak i nowym wrogiem) i zwrócili go. Co oznacza to stwierdzenie?

+1

Właściwie można powiedzieć, że obie funkcje zwracają nowo utworzony wróg lub obie funkcje zwracają listę, ponieważ obie funkcje mają kod do utrzymywania połączonej listy. Jedyną różnicą jest źródło nagłówka listy. Implementacja listy jest połączona z jednostką, więc błędem jest wywoływanie wyniku dowolnej funkcji, czystej listy lub "wroga". – Serge

+31

Wygląda na błąd. Ponieważ jest to również bardzo zły przykład pokazujący wszystkie najgorsze praktyki, których możesz używać w C++, prawdopodobnie poleciłbym poszukać bardziej nowoczesnej książki w C++. –

+4

@JanHudec Niestety jest więcej gorszych praktyk niż pokazano w tym przykładzie;) – user463035818

Odpowiedz

20

To jest ważna linia

p_ship->p_next_enemy = p_list; 

Zauważ, że p_ship posiada wskaźnik do p_next_enemy która sama jest EnemySpaceShip*. Dlatego jeśli nadal wywoływałbyś tę funkcję w kółko, skończyłbyś z połączoną listą. Możesz zacząć od pierwszego EnemySpaceShip* i przejść wszystkie z nich w pętli, np.

EnemySpaceShip* p_ship = p_first_ship; // assume this was known 
while (p_ship->p_next_enemy != nullptr) 
{ 
    p_ship = p_ship->p_next_enemy; 
    // p_ship has now advanced one element of your linked list 
} 

Ponadto, ze względu na postanowienia, że ​​statki te są dodawane, jeśli nazywa addNewEnemyToList kilka razy, bardzo ostatni raz nazwał to, że rzeczywiście się wskaźnik do pierwszej statku połączony lista. Dlatego autor mówi "zwraca wskaźnik do listy".

+8

Wszystko prawda. To bardzo dziwne sformułowanie. OP ma rację, że to, co jest zwracane, jest wskaźnikiem do nowego statku. W tym ostatnim przypadku statek jest częścią natrętnej, połączonej listy. (I dodanie do start wtf) –

+0

@LightnessRacesinOrbit: dobrze, pojedynczo połączona lista, to całkiem naturalne, aby dodać do początku i używać listy jako stosu do większości celów. –

+4

Pytanie nie polega tak bardzo na tworzeniu powiązanych list, co na uwadze autora książki, sugerującej, że istnieje coś jakościowo odmiennego w odniesieniu do wartości zwracanych przez obie funkcje. Jak pokazuje Vlad, nie ma. –

13

Nie sądzę, że zdanie ma sens.

Występuje tylko jedna różnica między funkcjami. W pierwszej funkcji lista statków jest globalna w stosunku do funkcji. Być może jest to element danych klasy, a funkcja jest funkcją składową klasy, która ma dostęp do elementów danych klasy. Lub rzeczywiście lista jest zadeklarowana w globalnej przestrzeni nazw.

W drugiej funkcji lista jest przekazywana do funkcji jako argument.

Obie funkcje zwracają wskaźnik do pierwszych węzłów list.

Jeśli usunięcie non-istotną kod z funkcji i wprowadź nazwy list identycznych wtedy dostaniesz

EnemySpaceShip* getNewEnemy() 
{ 
    EnemySpaceShip* p_ship = new EnemySpaceShip; 
    //... 
    p_ship->p_next_enemy = p_enemies; 
    p_enemies = p_ship; 
    return p_ship; 
} 


EnemySpaceShip* addNewEnemyToList (EnemySpaceShip* p_enemies) 
{ 
    EnemySpaceShip* p_ship = new EnemySpaceShip; 
    //... 
    p_ship->p_next_enemy = p_enemies; 
    return p_ship; 
} 

Jak widać funkcje różnią się tylko w jednym sprawozdaniu

p_enemies = p_ship; 

która jest obecna w pierwszej funkcji (ponieważ ma dostęp do samej oryginalnej listy) i jest nieobecna w drugiej funkcji, ponieważ funkcja ma tylko kopię nagłówka listy (zmienia się kopia nagłówka oryginalnej listy nie zmienia oryginalnej głowy, ponieważ Parametry ause są zmiennymi lokalnymi funkcji.

Można wezwać obie funkcje następujący sposób

p_enemies = getNewEnemy(); 

p_enemies = addNewEnemyToList(p_enemies); 

i jak p_enemies wynik będzie taka sama lista, do której węzeł został dodany.

Tylko w pierwszej funkcji lista również zmieniła się w funkcji; w drugiej funkcji musisz przypisać wskaźnik powrotu do listy, ponieważ w ramach tej funkcji sama lista nie jest zmieniana.

W ten sposób mogę stwierdzić, że zdanie tylko myli czytelników. Powinno to zostać jakoś przepisane, aby wyjaśnić, co autor ma zamiar powiedzieć. :) W książkach dla początkujących bardzo ważne jest, aby wszystkie zdania były jasne.

+1

Jedyny sposób, w jaki mogę zinterpretować zdanie, które ma dla mnie sens, jest to, że pierwsza funkcja pojęciowo zwraca nowy statek, a po stronie jest także ten globalny 'p_enemies', który mutuje.Druga funkcja konceptualnie wypycha nowy statek na stos statków dostarczonych jako dane wejściowe, niczego nie mutuje, a zatem * koniecznie * zwraca nowy stan stosu. Ponieważ jednak, jak sam podkreślasz, "lista" i "nowy statek" są dokładnie tym samym obiektem, jest to dość dziwaczna koncepcja, aby oczekiwać, że ktoś zrozumie jedno zdanie. –

+1

Więc jeśli celem tego przykładu było zilustrowanie różnicy między tymi dwoma, to przykład jest źle wybrany. Znacznie lepiej będzie lista, która używa zewnętrznych węzłów listy. Następnie czytelnik zobaczy, jak zwracany jest rzeczywisty wskaźnik, odzwierciedlając koncepcyjną różnicę między "funkcją, która zwraca nowy obiekt i jako efekt uboczny mutuje globalną strukturę danych" i "funkcją bez skutków ubocznych (inne niż alokacja pamięci), która działa na niezmiennej strukturze danych, a zatem zwraca strukturę danych, a nie obiekt, który właśnie do niej dodano ". –

+0

Myślę, że jest koncepcyjnie błędne przypisywanie wyniku getNewEnemy do wskaźnika o nazwie p_enemies ... zamiast tego, jest bardziej prawdopodobne, że getNewEnemy jest przeznaczony do pracy z abstrakcyjnym typem danych lub obiektem, a addNewEnemyToList jest przeznaczony do pracy w "proceduralnym". "styl ... –

0

można nawet powiedzieć, że getNewEnemy function() mogą być zastąpione przez addNewEnemyToList (null)

EnemySpaceShip* getNewEnemy() 
{ 
    p_enemies = addNewEnemyToList(NULL); 
    return p_enemies; 
} 

lub nawet usunięte, jeśli używasz:

p_enemies = addNewEnemyToList(NULL); 
p_enemies = addNewEnemyToList(p_enemies); 
2

Pierwszy getNewEnemy wykorzystuje pole p_enemies , który trzyma listę wrogów. Dodaje się do przodu listy, zmieniając p_enemies.

Drugi addNewEnemyToList używa parametru p_list, ale pozostawia niezmodyfikowany p_list (ponieważ jest to parametr wejściowy). Dlatego wynik powinien być z całą pewnością przypisany do p_list, aby ta lista wzrosła o jeden.

Można by oczekiwać, że:

p_enemies = addNewEnemyToList(p_enemies); 

Choć obie są wskaźnikami do nowego wroga, aby utrzymać listę addNewEnemyToList można powiedzieć, aby powrócić nową listę zbyt.

2

Zgadzam się z Vladem i zamierzałem po prostu głosować na tę odpowiedź, ale jeśli spojrzeć na obie metody z perspektywy blackboksa, nie sugeruj, gdzie zostanie dodany nowy wróg.

Nazwa pierwszej metody wskazuje, że nowo utworzony wróg jest tym, co zostanie zwrócone. Efektem ubocznym jest to, że jest on dodawany do listy p_enemies. To w moim umyśle byłoby naruszeniem zasady jednej odpowiedzialności, ale może to zostać złagodzone przez większą kontekst wokół tej metody. Jeśli metoda zostanie zaktualizowana w celu dodania nowego elementu do końca lub w oparciu o posortowaną kolejność, to nie będzie już zwracać nagłówka listy.

Druga metoda wyraźnie mówi, że dodaje nowego wroga do podanej listy. Z definicji nie wynika jasno, co jest zwracane, a to może być dezorientujące. Czy powinien zwrócić nowego wroga lub zaktualizowaną listę? Jeśli nie zwróci listy, czy dzwoniący może mieć pewność, że nowy element znajduje się na czele listy? Co się stanie, jeśli wymagania ulegną zmianie, a nowy element zostanie dodany na końcu listy? Nie jest to skuteczny sposób dodawania wrogów, ale jest to możliwe.

Wygląda na to, że książka może próbować wskazać, że druga funkcja ma zwrócić szefowi listy, a nie nowemu wrogowi. W tym przypadku fakt, że szef listy jest nowym wrogiem, jest przypadkowy.

0

Myślę, że autor oznaczałby, że pierwsza funkcja ma przypisać typ wskaźnika "wroga", druga funkcja ma na celu przypisać typ wskaźnika "listy wrogów".

Typ stosowany do implementacji tych wskaźników jest taki sam, ale ich znaczenie jest różne, a także ich wykorzystanie w przepływie logicznym programu.

Więc autor mówi: uwaga, w drugim przypadku otrzymujesz coś, co w programie masz zamiar używać jako listy, a nie jako element listy ...

Na przykład, w tym konkretnym przypadku druga funkcja powinna mieć przypisany parametr p_list przy wyjściu, w przeciwnym razie p_list nadal wskazuje poprzedni obiekt wroga.

również pod uwagę, że jest bardziej prawdopodobne, że getNewEnemy jest przeznaczony do pracy z abstrakcyjnego typu danych lub obiektu, a addNewEnemyToList jest przeznaczony do pracy w „proceduralny” stylu ..

Powiązane problemy