Стивен Прата - Язык программирования C. Лекции и упражнения (6-е изд.) 2015
- Название:Язык программирования C. Лекции и упражнения (6-е изд.) 2015
- Автор:
- Жанр:
- Издательство:Вильямс
- Год:0101
- ISBN:нет данных
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Стивен Прата - Язык программирования C. Лекции и упражнения (6-е изд.) 2015 краткое содержание
Язык программирования C. Лекции и упражнения (6-е изд.) 2015 - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
List movies;
устанавливает movies как указатель, подходящий для ссылки на связный список.
Является ли этот способ определения типа List единственным? Нет. Например, для отслеживания количества записей можно было бы задействовать переменную:
typedef struct list {
Node * head; /* указатель на заголовок списка */
int size; /* количество записей в списке */
} List; /* альтернативное определение списка */
Можно было бы добавить второй указатель, предназначенный для отслеживания конца списка. Позже вы увидите соответствующий пример. Пока давайте ограничимся первым определением типа List. Важно помнить, что объявление
List movies;
следует рассматривать как определение списка, а не установку указателя на узел или структуры.
Расширенное представление данных 733
Точное представление данных списка movies является деталью реализации, которая не должна быть видна на уровне интерфейса.
Например, при запуске программа должна инициализировать указатель на заголовок значением NULL, но не следует применять код вроде такого:
movies = NULL;
А почему? По той причине, что впоследствии может оказаться, что реализация типа List в виде структуры подходит больше, и тогда потребуется следующая инициализация:
movies.next = NULL; movies.size = 0;
Никто из тех, кто использует тип List, не должен беспокоиться о подобных нюансах. Вместо этого должна быть возможность записывать приблизительно такой код:
InitializeList(movies);
Программистам требуется знать только о том, что для инициализации списка они должны применять функцию InitializeList(). Они не обязаны знать точную реализацию данных для переменной List. Это является примером сокрытия данных — искусства маскировки подробностей представления данных от более высоких уровней программирования.
Для предоставления руководства пользователю прототип функции можно сопровождать следующими строками:
/* операция: инициализация списка */
/* предусловия: plist указывает на список list */
/* постусловия: список инициализирован пустым содержимым */
void InitializeList(List * plist);
Есть три момента, на которые вы должны обратить внимание. Во-первых, комментарии описывают предусловия, т.е. условия, которые должны быть удовлетворены до вызова функции. Например, здесь необходим список, предназначенный для инициализации. Во-вторых, комментарии описывают постусловия — условия, которые должны быть удовлетворены после выполнения функции. Наконец, в-третьих, в качестве своего аргумента функция использует указатель на список, а не сам список, поэтому вызов функции будет иметь такой вид:
InitializeList(smovies);
Причина заключается в том, что в С аргументы передаются по значению. Таким образом, единственный способ позволить функции С изменять значения из вызывающей программы предусматривает применение указателя на эту переменную. Как видите, здесь ограничения языка приводят к некоторому отличию интерфейса от его абстрактного описания.
Принятый в языке С метод объединения информации о типе и функциях в единый пакет предполагает помещение определений для типа и прототипов функций (в том числе комментариев с пред- и постусловиями) в заголовочный файл. Этот файл должен предоставлять всю информацию, в которой нуждается программист для использования типа. Заголовочный файл для простого типа list показан в листинге 17.3. В нем конкретная структура определена как относящаяся к типу Item, после чего тип Node определен в терминах Item и тип List — в терминах Node. Затем в функциях, представляющих операции над списком, типы Item и List применяются для аргументов. Если функции необходимо модифицировать аргумент, она использует указатель
734 Глава 17 на соответствующий тип, а не сам тип напрямую. В файле имена функций начинаются с прописных букв для их обозначения как части интерфейсного пакета. Кроме того, для защиты от множественного включения файла применяется прием с #ifndef, который обсуждался в главе 16. Если ваш компилятор не поддерживает тип bool из стандарта С99, можете заменить в заголовочном файле строку
#include /* функциональная возможность С99 */
такой строкой:
Листинг 17.3. Заголовочный файл для интерфейса list.li
Расширенное представление данных 735
Список модифицируют только функции InitializeList() , Addltem() и EmptyTheList, поэтому формально только они требуют аргумента типа указателя. Однако если бы пользователю пришлось помнить о необходимости передачи аргумента List одним функциям и его адреса другим, то это могло бы приводить к путанице. Таким образом, для упрощения задачи пользователя во всех функциях используются аргументы типа указателей.
Один из прототипов в заголовочном файле несколько сложнее остальных:
/* операция: применение функции к каждому элементу списка */ /* предусловия: plist указывает на инициализированный список */ /* pfun указывает на функцию, которая принимает */ /* аргумент Item и не имеет возвращаемого значения */ /* постусловия: функция, указанная pfun, выполняется один */ /* раз для каждого элемента в списке */ void Traverse (const List *plist, void (* pfun)(Item item) );
Аргумент pfun представляет собой указатель на функцию. В этом случае он является указателем на функцию, которая принимает значение item в качестве аргумента и не имеет возвращаемого значения. Возможно, вы помните из главы 14, что указатель на функцию можно передавать в виде аргумента другой функции, которая сможет вызывать эту указанную функцию. Так, например, pfun может указывать на функцию, отображающую элемент. Функция Traverse() будет применять эту функцию к каждому элементу списка, в результате отображая весь список.
Использование интерфейса
Мы заявляем, что этот интерфейс можно использовать для написания программы, не располагая никакими дополнительными деталями — например, ничего не зная о том, как реализованы функции интерфейса. Давайте прямо сейчас напишем новую версию программы вывода информации о фильмах еще до создания вспомогательных функций. Поскольку интерфейс определен в терминах типов List и Item, программа должна быть создана с применением этих же типов. Ниже показан один из возможных планов, представленный с помощью псевдокода:
736 глава 17
Создать переменную List.
Создать переменную Item.
Инициализировать список пустым содержимым.
Пока список не заполнен и есть входные данные:
Прочитать входные данные и поместить их в переменную Item.
Добавить элемент в конец списка.
Посетить каждый элемент списка и отобразить его.
Программа, приведенная в листинге 17.4, следует этому базовому плану; кроме того, в нее добавлен код для проверки ошибок. Взгляните, как в ней используется интерфейс, описанный в файле list.h (листинг 17.3). Обратите также внимание, что листинг содержит код функции showmovies(), которая соответствует прототипу, требуемому функцией Traverse(). Поэтому программа может передавать указатель showmovies в функцию Traverse(), чтобы та могла применять функцию showmovies() к каждому элементу списка. (Вспомните, что имя функции является указателем на эту функцию.)
Читать дальшеИнтервал:
Закладка: