2016-09-01 3 views
22

Книга, которую я читаю, Introduction to Data Structures with Linked Lists (Presentation 21), имеет 2 примера связанных списков. Вот первый один:Я не понимаю, почему эта функция «возвращает указатель из списка»

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; 
} 

Второй пример связанных списков это одна:

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; 
} 

Тогда книга пишет об этом:

Обратите внимание, что эта функция отличается от getNewEnemy, потому что он возвращает указатель на список, а не новый враг.

То, что я не понимаю, означает, что он означает, что «вторая функция возвращает указатель на список» и «первая функция возвращает нового врага». Я думал, что они оба создали нового врага под названием p_ship (который является как указателем, так и новым врагом) и вернул его. Что подразумевается под этим заявлением?

+1

На самом деле можно сказать, что обе функции возвращают вновь созданного врага или что обе функции возвращают список, поскольку обе функции имеют код для поддержания связанного списка. Единственное отличие - это источник главы списка. Реализация списка объединена с сущностью, поэтому ошибочно назвать результат любой функции чистым списком или «врагом». – Serge

+31

Это похоже на ошибку. Так как это также очень плохой пример, показывающий все методы _worst_, которые вы можете использовать в C++, я бы, вероятно, рекомендовал искать более современную книгу на C++. –

+4

@JanHudec, к сожалению, существует более худший опыт, чем показано в этом примере;) – user463035818

ответ

20

Это важная строка

p_ship->p_next_enemy = p_list; 

Обратите внимание, что p_ship имеет указатель на p_next_enemy, который сам по себе является EnemySpaceShip*. Поэтому, если вы продолжаете называть эту функцию снова и снова, вы получите связанный список. Вы можете начать с первых EnemySpaceShip* и пройти все их в петле, например.

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 
} 

Кроме того, в связи с тем, чтобы эти корабли будут добавлены, если вы назвали addNewEnemyToList несколько раз, в самый последний раз, когда вы назвали это вы на самом деле получить указатель на первый корабль в связанный список. Вот почему автор говорит, что «он возвращает указатель на список».

+8

Все верно. Однако это очень странная формулировка. OP прав, что возвращаемое является указателем на новый корабль. Просто в последнем случае корабль является частью интрузивного связанного списка. (И добавление к началу wtf) –

+0

@LightnessRacesinOrbit: ну, односвязный список, довольно естественно добавить в начало и использовать список как стек для большинства целей. –

+4

Вопрос, похоже, не столько в построении связанных списков, сколько в замечании автора книги, в котором говорится о том, что что-то качественно отличается от возвращаемых значений двух функций. Как показывает @Vlad, его нет. –

13

Я не думаю, что предложение имеет смысл.

Существует только одна разница между функциями. В первой функции список кораблей является глобальным относительно функции. Возможно, это член данных класса, и функция является функцией-членом класса, имеющего доступ к членам данных класса. Или действительно, этот список объявляется в глобальном пространстве имен.

Во второй функции список передается функции как аргумент.

Обе функции возвращают указатель на первые узлы списков.

Если для удаления не важный код из функций и сделать имена списков одинаковых, то вы получите

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; 
} 

Как вы видите, функции отличаются только в одном операторе

p_enemies = p_ship; 

который присутствует в первой функции (поскольку он имеет доступ к самому исходному списку) и отсутствует во второй функции, потому что функция имеет только копию главы списка (изменение копии главы исходного списка делает не изменить оригинальную голову сам ause parameters - локальные переменные функций).

можно назвать обе функции следующим образом

p_enemies = getNewEnemy(); 

p_enemies = addNewEnemyToList(p_enemies); 

и, как результат p_enemies будет тот же список, к которому был добавлен узел.

Только в первой функции список также изменяется в пределах функции; во второй функции вам нужно назначить обратный указатель на список, потому что внутри функции сам список не изменяется.

Таким образом, я могу заключить, что предложение только путает читателей. Его следует переписать как-то, чтобы четко указать, что автор собирается сказать. :) В книгах для новичков очень важно, чтобы все предложения были ясными.

+1

Единственный способ, которым я могу интерпретировать предложение, имеющее для меня смысл, состоит в том, что первая функция концептуально возвращает новый корабль, а на стороне есть и этот глобальный 'p_enemies', который он мутирует.Вторая функция концептуально подталкивает новый корабль к стеку кораблей, предоставляемых в качестве входных данных, ничего не мутирует, и поэтому * обязательно * возвращает новое состояние стека. Но поскольку, как вы отмечаете, «список» и «новый корабль» - это точно такой же объект, это довольно сложная концепция, чтобы ожидать, что кто-то поймет одно предложение. –

+1

Итак, если целью примера было проиллюстрировать разницу между ними, то пример плохо выбран. Гораздо лучше будет список, в котором используются внешние узлы списка. Затем читатель увидит, как возвращенный фактический указатель отличается, что отражает концептуальную разницу между «функцией, возвращающей новый объект и как побочный эффект, мутирует глобальную структуру данных» и «функцией без побочных эффектов (кроме выделение памяти), которое работает с неизменной структурой данных и, следовательно, возвращает структуру данных, а не объект, который она только что добавила к ней ». –

+0

Я думаю, что концептуально неправильно присваивать результат getNewEnemy указателю с именем p_enemies ... вместо этого более вероятно, что getNewEnemy предназначен для работы с абстрактным типом данных или объектом, а addNewEnemyToList предназначен для работы в "процедурной " стиль... –

0

Можно даже сказать, что функция getNewEnemy() может быть заменен addNewEnemyToList (NULL)

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

или даже удален, если вы используете:

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

Первый getNewEnemy использует поле p_enemies , который содержит список врагов. Он добавляет себя в начало списка, изменяя p_enemies.

Второй addNewEnemyToList использует параметр p_list, но он оставляет p_list немодифицированным (так как это входной параметр). Следовательно, результат должен быть назначен на p_list, чтобы этот список увеличивался на единицу.

Можно было бы ожидать:

p_enemies = addNewEnemyToList(p_enemies); 

Хотя оба являются указателями на новый враг, чтобы сохранить список, addNewEnemyToList можно сказать, чтобы вернуть новый список тоже.

2

Я согласен с Владом и собираюсь просто проголосовать за этот ответ, но если вы посмотрите на два метода с точки зрения черного ящика, то не подразумевайте, где будет добавлен новый противник.

Имя первого метода указывает, что вновь созданный враг - это то, что будет возвращено. Побочным эффектом является то, что он добавляется в список p_enemies. Это, на мой взгляд, было бы нарушением принципа единой ответственности, но это могло бы быть смягчено с большим контекстом вокруг этого метода. Если метод обновляется, чтобы добавить новый элемент в конец или на основе отсортированного порядка, он больше не будет возвращать заголовок списка.

Второй метод явно говорит, что он добавляет нового врага в прошедший в списке. Из определения не ясно, что возвращается, и это может ввести в заблуждение. Должен ли он вернуть нового врага или обновленный список? Если он не возвращает список, может ли вызывающий быть уверенным, что новый элемент находится во главе списка? Что делать, если требования изменяются, и новый элемент должен быть добавлен в конец списка? Неэффективный способ добавить врагов, но это возможно.

Кажется, что в книге может быть указано, что вторая функция должна вернуть голову списка, а не новый враг. В этом случае совпадением является тот факт, что глава списка - новый враг.

0

Я думаю, что автор будет означать, что первая функция предназначена для назначения типа «вражеского» указателя, вторая функция предназначена для указания типа указателя «список врагов».

Тип, используемый для реализации этих указателей, одинаков, но их значение различно, а также их использование в потоке логики программы.

Так автор говорит: внимание, во втором случае вы получаете то, что в программе, которую вы собираетесь использовать в качестве списка, а не в качестве элемента списка ...

К примеру, в данном конкретном случай второй функции должен был бы назначить параметр p_list при его выходе, в противном случае p_list продолжает указывать предыдущий объект противника.

считают также, что более вероятно, что getNewEnemy предназначен для работы с абстрактным типом данных или объекта, и addNewEnemyToList предназначен для работы в «процедурной» стиле ..

Смежные вопросы