Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология Образование Политология Производство Психология Стандартизация Технологии |
Использование typedef с указателями на функции
Конструкция void (*)(int&, int& ) весьма громоздка. Для ее упрощения можно воспользоваться ключевым словом typedef, объявив новый тип (назовем его VPF) указателей на функции, возвращающие void и принимающие две ссылки на значения типа int. Листинг 14.9 представляет собой переписанную версию листинга 14.8 с использованием этого подхода. Листинг 14.8. Использование оператора typedef для объявления типа указателей на функции 1: // Листинг 14.9. Использование typedef для 2: // объявления типа указателей на функции 3: #include < iostream.h> 4: 5: void Square (int&, int& ); 6: void Cube (int&, int& ); 7: void Swap (int&, int & ); 8: void GetVals(int&, int& ); 9: typedef void (*VPF) (int&, int& ); 10: void PrintVals(VPF, int&, int& ); 11: 12: int main() 13: { 14: int val0ne=1, valTwo=2; 15: int choice; 16: bool fQuit = false; 17: 18: VPF pFunc; 19: 20: while (fQuit == false) 21: { 22: cout < < " (0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: "; 23: cin > > choice; 24: switch (choice) 25: { 26: case 1: pFunc = GetVals; break; 27: case 2: pFunc = Square; break; 28: case 3: pFunc = Cube; break; 29: case 4: pFunc = Swap; break; 30: default: fQuit = true; break; 31: } 32: if (fQuit == true) 33: break; 34: PrintVals ( pFunc, valOne, valTwo); 35: } 36: return 0; 37: } 38: 39: void PrintVals( VPF pFunc, int& x, int& y) 40: { 41: cout < < " x: " < < x < < " y: " < < y < < endl; 42: pFunc(x, y); 43: cout < < " x: " < < x < < " y: " < < y < < endl; 44: } 45: 46: void Square (int & rX, int & rY) 47: { 48: rX *= rX; 49: rY *= rY; 50: } 51: 52: void Cube (int & rX, int & rY) 53: { 54: int tmp; 55: 56: tmp = rX; 57: rX *= rX; 58: rX = rX * tmp; 59: 60: tmp = rY; 61: rY *= rY; 62: rY = rY * tmp; 63: } 64: 65: void Swap(int & rX, int & rY) 66: { 67: int temp; 68: temp = rX; 69: rX = rY; 70: rY = temp; 71: } 72: 73: void GetVals (int & rValOne, int & rValTwo) 74: { 75: cout < < " New value for ValOne: "; 76: cin > > rValOne; 77: cout < < " New value for ValTwo: "; 78: cin > > rValTwo; 79: }
Результат: (0)Quit (1 )Change Values (2)Square (3)Cube (4)Swap: 1 x: 1 y: 2 New value for ValOne: 2 New value for ValTwo: 3 x: 2 y: 3 (0)Quit (1 )Change Values (2)Square (3)Cube (4)Swap: 3 x: 2 y: 3 x: 8 y: 27 (0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 2 x: 8 y: 27 x: 64 y: 729 (0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 4 x: 64 y: 729 x: 729 y: 64 (0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 0
Анализ: В строке 9 с помощью оператора typedef объявляется новый тип VPF как указатели на функции, возвращающие void и принимающие две ссылки на int. В строке 10 объявляется функция PrintVals(), которая принимает три параметра: VPF и две ссылки на integer. В строке 18 указатель Pfunc объявляется как принадлежащий TnnyVPF. После объявления типа VPF дальнейшее использование указателя pFunc и функции PrintVals() становится проще и понятнее. Информация, выводимая программой на экран, не изменилась.
Указатели на функции члены
До настоящего времени все создаваемые указатели на функции использовались для общих функций, не принадлежащих к какому-нибудь одному классу. Однако разрешается создавать указатели и на функции, являющиеся членами классов (методы). Для создания такого указателя используется тот же синтаксис, что и для указателя на обычную функцию, но с добавлением имени класса и оператора области видимости (:: ). Таким образом, объявление указателя pFunc на функции-члены класса Shape, принимающие два целочисленных параметра и возвращающие void, выглядит следующим образом: void (Shape:: *pFunc) (int, int); Указатели на функции-члены используются так же, как и рассмотренные ранее указатели простых функции. Единственное отличие состоит в том, что для вызова функции необходимо наличие объекта соответствующего класса, для которого вызываются функции. В листинге 14.10 показано использование указателя на метод класса. Листинг 14.10. Указатели на функции-члены 1: //Листинг 14.10. Указатели на виртуальные функции-члены 2: 3: #include < iostream.h> 4: 5: class Mammal 6: { 7: public: 8: Mammal(): itsAge(1) { } 9: virtual ~Mammal() { } 10: virtual void Speak() const = 0; 11: virtual void Move() const = 0; 12: protected: 13: int itsAge; 14: }; 15: 16: class Dog: public Mammal 17: { 18: public: 19: void Speak()const { cout < < " Woof! \n"; } 20: void Move() const { cout < < " Walking to heel...\n"; } 21: }; 22: 23: 24: class Cat: public Mammal 25: { 26: public: 27: void Speak()const { cout < < " Meow! \n"; } 28: void Move() const { cout < < " slinking...\n"; } 29: }; 30: 31: 32: class Horse: public Mammal 33: { 34: public: 35: void Speak()const { cout < < " Whinny! \n"; } 36: void Move() const 1 cout < < " Galloping...\n"; } 37: }; 38: 39: 40: int main() 41: { 42: void (Mammal:: *pFunc)() const =0; 43: Mammal* ptr =0; 44: int Animal; 45: int Method; 46: bool fQuit = false; 47: 48: while (fQuit == false) 49: { 50: cout < < " (0)Quit (1)dog (2)cat (3)horse 51: cin > > Animal; 52: switch (Animal) 53: { 54: case 1: ptr = new Dog; break; 55: case 2: ptr = new Cat; break; 56: case 3: ptr = new Horse; break; 57: default: fQuit = true; break; 58: } 59: if (fQuit) 60: break; 61: 62: cout < < " (1)Speak (2)Move: "; 63: cin > > Method; 64: switch (Method) 65: { 66: case 1: pFunc = Mammal:: Speak; break; 67: default: pFunc = Mammal:: Move; break; 68: } 69: 70: (ptr-> *pFunc)(); 71: delete ptr; 72: } 73: return 0; 74: }
Результат: (0)Quit (1)dog (2)cat (3)horse: 1 (1)Speak (2)Move: 1 Woof! (0)Quit (1)dog (2)cat (3)horse: 2 (1)Speak (2)Move: 1 Meow! (0)Quit (1)dog (2)cat (3)horse: 3 (1)Speak (2)Move: 2 Galloping (0)Quit (1)dog (2)cat (3)horse: 0
Анализ: В строках 4—14 объявляется тип абстрактных данных Mammal с двумя чистыми виртуальными методами Speak() и Move(). От класса Mammal производятся подклассы Dog, Cat и Horse, в каждом из которых замещаются соответствующим образом функции Speak() и Move(). В процессе выполнения тела функции main() пользователю предлагается выбрать животное, после чего в области динамического обмена создается новый подкласс выбранного животного, адрес которого присваивается в строках 54—56 указателю ptr. Затем пользователь выбирает метод, который связывается с указателем pFunc. В строке 70 выбранный метод вызывается для созданного объекта посредством предоставления доступа к объекту с помощью указателя ptr и к функции с помощью указателя pFunc. Наконец, строкой 71 для указателя ptr вызывается функция delete, которая очищает область памяти, занятую созданным ранее объектом. Заметьте, что нет смысла вызывать delete для pFunc, поскольку последний является указателем на код, а не на объект в области памяти. Хотя даже при попытке сделать это вы получите сообщение об ошибке компиляции. Массивы указателейна функции-члены
Аналогично указателям на обычные функции, указатели на функции-члены могут храниться в массиве. Для инициализации такого массива можно использовать адреса различных функций-членов. В таком случае, чтобы вызвать для объекта тот или иной метод, достаточно просто указать массив и индекс смещения. Именно такой подход применяется в листинге 14.11. Листинг 14.11. Массив указателей на функции-члены 1: // Листинг 14.11. Массивы указателей на функции-члены 2: 3: #include < iostream.h> 4: 5: class Dog 6: { 7: public: 8: void Speak()const { cout < < " Woof! \n"; } 9: void Move() const { cout < < " Walking to heel...\n"; } 10: void Eat() const { cout < < " Gobbling food...\n"; } 11: void Growl() const { cout < < " Grrrrr\n"; } 12: void Whimper() const { cout < < " Whining noises...\n"; } 13: void RollOver() const { cout < < " Rolling over...\n"; } 14: void PlayDead() const { cout < < " Is this the end of Little Caesar? \n"; 15: }; 16: 17: typedef void (Dog:: *PDF)()const; 18: int main() 19: { 20: const int MaxFuncs = 7; 21: PDF DogFunctions[MaxFuncs] = 22: { Dog:: Speak, 23: Dog:: Move, 24: Dog:: Eat, 25: Dog:: Growl, 26: Dog:: Whimper, 27: Dog:: RollOver, 28: Dog:: PlayDead }; 29: 30: Dog* pDog =0; 31: int Method; 32: bool fQuit = false; 33: 34: while (! fQuit) 35: { 36: cout < < " (0)Quit (1)Speak (2)Move (3)Eat (4)Growl"; 37: cout < < " (5)Whimper (6)Roll Over (7)Play Dead: "; 38: cin > > Method; 39: if (Method == 0) 40: { 41: fQuit = true; 42: } 43: else 44: { 45: pDog = new Dog; 46: (pDog-> *DogFunctions[Method-1])(); 47: delete pDog; 48: } 49: } 50: return 0; 51: }
Результат: (0)Quit (1)Speak (2)Move (3)Eat (4)Growl (5)Whimper (6)Roll 0ver (7)Play Dead: 1 Woof! (0)Quit (1)Speak (2)Move (3)Eat (4)Growl (5)Whimper (6)Roll 0ver (7)Play Dead: 4 Grrr (0)Quit (1)Speak (2)Move (3)Eat (4)Growl (5)Whimper (6)Roll 0ver (7)Play Dead: 7 Is this the end of Little Caesar? (0)Quit (1)Speak (2)Move (3)Eat (4)Growl (5)Whimper (6)Roll 0ver (7)Play Dead: 0
Анализ: В строках 5—15 создается класс Dog, содержащий семь функций-членов, характеризующихся одинаковыми сигнатурой и типом возврата. В строке 17 с помощью typedef объявляется тип PDF константных указателей на функции-члены Dog, которые не принимают и не возвращают никаких значений. В строках 21-28 объявляется массив DogFunctions, предназначенный для хранения указателей на семь функций-членов. В строках 36 и 37 пользователю предлагается выбрать метод. Выбор любого элемента, кроме Quit, приводит к созданию объекта класса Dog, после чего из массива вызывается соответствующий метод (строка 46). Ниже представлена еще одна строка, которая может немного смутить ваших знакомых программистов, работающих с C++: (pDog-> *-DogFunctions[Method-1])(); Это выражение, безусловно, немного экзотично, но с его помощью можно создать таблицу функций-членов, что сделает код программы проще и читабельнее.
Рекомендуется: Используйте указатели на функции- члены для вызова методов в объектах класса. Используйте typedef, чтобы упростить объявление указателя на функцию-член.
Не рекомендуется: Не злоупотребляйте созданием указателей на функции-члены, если беэ них можно обойтись.
Резюме
Сегодня вы познакомились с созданием статических переменных-членов класса, которые, в отличие от обычных переменных-членов, принадлежат всему классу, а не отдельному объекту. Если статическая переменная-член объявлена как public, то обратиться к ней можно просто по имени, даже не используя объектов класса, которому принадлежит эта переменная. Статические переменные-члены можно использовать в качестве счетчиков объектов класса. Поскольку они не являются частями объектов, при их объявлении не происходит автоматическое резервирование памяти, как при объявлении обычных переменных-членов. Поэтому в программе за пределами объявления класса обязательно должна быть строка, в которой происходит определение и инициализация статической переменной-члена. Статические функции-члены также принадлежат всему классу, подобно статическим переменным-членам. Вызвать статическую функцию-член класса можно даже в том случае, если не было создано ни одного объекта этого класса. Сами же эти функции могут использоваться для открытия доступа к статическим переменным-членам. Поскольку статические переменные-члены не имеют указателя this, они не могут использовать обычные переменные-члены. Из-за отсутствия указателя this статические функции-члены не могут объявляться как const. Дело в том, что при объявлении функции-члена со спецификатором const устанавливается, что указатель this этой функции является константным. Кроме того, вы узнали, как объявлять и использовать указатели на обычные функции и на функции-члены, а также познакомились с созданием массивов этих указателей и с передачей указателей на функции в другие функции. Как указатели на функции, так и указатели на функции-члены могут использоваться для создания таблиц функций, что облегчает управление их вызовом в процессе выполнения программы. Это придает программе гибкость и делает программный код более читабельным.
Вопросы и ответы
Зачем использовать статические данные, если есть глобальные? Область видимости статических данных ограничивается классом. Обращаться к статической переменной-члену следует из объектов класса, либо из внешнего кода программы, явно указав имя класса (в случае, если статическая переменная-член описана как public), либо с помощью открытой статической функции-члена этого класса. Статические переменные-члены относятся к типу данных того класса, которому они принадлежат. Ограничение доступа к членам класса, вызванное строгим контролем за типом данных в C++, делает использование статических переменных-членов более безопасным по сравнению с глобальными данными. Зачем использовать статические функции-члены, если можно воспользоваться глобальными функциями? Статические функции-члены принадлежат классу и могут вызываться только с помощью объектов класса или с явным указанием имени класса, например: ClassName:: FunctionName(). Насколько часто в программах используются указатели на функции и указатели на функции-члены? Такие указатели используются достаточно редко. Это дело вкуса программиста. Даже в сложных и мощных программах без них можно вполне обойтись.
Коллоквиум
В этом разделе предлагаются вопросы для самоконтроля и укрепления полученных знаний и приводится несколько упражнений, которые помогут закрепить ваши практические навыки. Попытайтесь самостоятельно ответить на вопросы теста и выполнить задания, а потом сверьте полученные результаты с ответами в приложении Г. Не приступайте к изучению материала следующей главы, если для вас остались неясными хотя бы некоторые из предложенных ниже вопросов. Контрольные вопросы
1. Могут ли статические переменные-члены быть закрытыми? 2. Объявите статическую переменную-член. 3. Объявите статическую функцию. 4. Объявите указатель на функцию, принимающую параметр типа int и возвращающую значение типа long. 5. Измените указатель, созданный в задании 4, на указатель на функцию-член класса Car. 6. Объявите массив из десяти указателей, созданных в задании 5.
Упражнения
1. Напишите короткую программу, объявляющую класс с одной обычной переменной-членом и одной статической переменной-членом. Создайте конструктор, выполняющий инициализацию переменной-члена и приращение статической переменной-члена. Затем опишите деструктор, который уменьшает на единицу значение статической переменной. 2. Используя программный блок из упражнения 1, напишите короткую выполняемую программу, которая создает три объекта, а затем выводит значения их переменных-членов и статической переменной-члена класса. Затем последовательно удаляйте объекты и выводите на экран значение статической переменной-члена. 3. Измените программу упражнения 2 таким образом, чтобы доступ к статической переменной-члену осуществлялся с помощью статической функции-члена. Сделайте статическую переменную-член закрытой. 4. Создайте в программе упражнения 3 указатель на функцию-член для доступа к значению нестатической переменной-члена и воспользуйтесь им для вывода этих значений на печать. 5. Добавьте две дополнительные переменные-члена к классу из предыдущих заданий. Добавьте методы доступа, возвращающие значения всех этих переменных. Все функции-члены должны возвращать значения одинакового типа и иметь одинаковую сигнатуру. Для доступа к этим методам используйте указатель на функцию-член.
Подведение итогов
В этой главе вашему вниманию предлагается достаточно мощная программа, в которой используется большинство средств и подходов программирования, освоенных вами в течение двух недель. В этой программе используются связанные списки, виртуальные функции, чистые виртуальные функции, замещения функций, полиморфизм, открытое наследование, перегрузка функций, вечные циклы, указатели, ссылки и многие другие знакомые вам средства. Обратите внимание, что представленный здесь связанный список отличается от рассмотренных ранее. Язык C++ предоставляет множество способов достижения одной и той же цели. Цель данной программы состоит в создании функционального связанного списка. В узлах созданного списка можно хранить записи о деталях и агрегатах, что позволяет использовать его в реальных прикладных программах баз данных складов. Хотя здесь представлена не окончательная форма программы, она достаточно хорошо демонстрирует возможности создания совершенной структуры накопления и обработки данных. Листинг программы содержит 311 строк. Попробуйте самостоятельно проанализировать код, прежде чем прочтете анализ, приведенный после листинга. Итоги второй недели 1: // ********************************** 2: // 3: // Название: Подведение итогов 4: // 5: // Файл: Week2 6: // 7: // Описание: Демонстрация создания и использования связанного списка 8: // 9: // Классы: PART - содержит идентификационный 10: // номер детали и обеспечивает возможность 11: // добавлять другие данные 12: // PartNode - функционирует как узел в PartsList 13: // 14: // PartsList - реализует механизм связывания 15: // узлов в список 16: // 17: // ********************************** 18: 19: #include < iostream.h> 20: 21: 22: 23: // **************** Part ************ 24: 25: // Абстрактный базовый класс, общий для всех деталей 26: class Part 27: { 28: public: 29: Part(): itsPartNumber(1) { } 30: Part(int PartNumber): itsPartNumber(PartNumber) { } 31: virtual ~Part() { }; 32: int GetPartNumber() const { return itsPartNumber; } 33: virtual void Display() const =0; // должна быть замещена как private 34: private: 35: int itsPartNumber; 36: }; 37: 38: // выполнение чистой виртуальной функции в 39: // стандартном виде для всех производных классов 40: void Part:: Display() const 41: { 42: cout < < " \nНомер детали: " < < itsPartNumber < < endl; 43: } 44: 45: // ************* Автомобильные детали ********** 46: 47: class CarPart: public Part 48: { 49: public: 50: CarPart(): itsModelYear(94){ } 51: CarPart(int year, int partNumber); 52: virtual void Display() const 53: { 54: Part:: Display(); cout < < " Год создания: "; 55: cout < < itsModelYear < < endl; 56: } 57: private: 58: int itsModelYear; 59: }; 60: 61: CarPart:: CarPart(int year, int partNumber): 62: itsModelYear(year), 63: Part(partNumber) 64: { } 65: 66: 67: // ************* Авиационные детали ********** 68: 69: class AirPlanePart: public Part 70: { 71: public: 72: AirPlanePart(): itsEngineNumber(1){ }; 73: AirPlanePart(int EngineNumber, int PartNumber); 74: virtual void Display() const 75: { 76: Part:: Display(); cout < < " Номер двигателя: "; 77: cout < < itsEngineNumber < < endl; 78: } 79: private: 80: int itsEngineNumber; 81: }; 82: 83: AirPlanePart:: AirPlanePart(int EngineNumber, intPartNumber): 84: itsEngineNumber(EngineNumber), 85: Part(PartNumber) 86: { } 87: 88: // ************** Узлы списка деталей ********** 89: class PartNode 90: { 91: public: 92: PartNode (Part*); 93: ~PartNode(); 94: void SetNext(PartNode * node) { itsNext = node; } 95: PartNode * GetNext() const; 96: Part * GetPart() const; 97: private: 98: Part *itsPart; 99: PartNode * itsNext; 100: }; 101: 102: // Выполнение PartNode... 103: 104: PartNode:: PartNode(Part* pPart): 105: itsPart(pPart), 106: itsNext(0) 107: { } 108: 109: PartNode:: ~PartNode() 110: { 111: delete itsPart; 112: itsPart = 0; 113: delete itsNext; 114: itsNext = 0; 115: } 116: 117: //Возвращается NULL, если нет следующего узла PartNode 118: PartNode * PartNode:: GetNext() const 119: { 120: return itsNaxt; 121: } 122: 123: Part * PartNode:: GetPart() const 124: { 125: if (itsPart) 126: return itsPart; 127: else 128: return NULL; // ошибка 129: } 130: 131: // **************** Список деталей ************ 132: class PartsList 133: { 134: public: 135: PartsList(); 136: ~PartsList(); 137: // Необходимо, чтобы конструктор-копировщик и оператор соответствовали друг другу! 138: Part* Find(int & position, int PartNumber) const; 139: int GetCount() const { return itsCount; } 140: Part* GetFirst() const; 141: static PartsList& GetGlobalPartsList() 142: { 143: return GlobalPartsList; 144: } 145: void Insert(Part *); 146: void Iterate(void (Part:: *f)()const) const; 147: Part* operator[](int) const; 148: private: 149: PartNode * pHead; 150: int itsCount; 151: static PartsList GlobalPartsList; 152: }; 153: 154: PartsList PartsList:: GlobalPartsList; 155: 156: // Выполнение списка... 157: 158: PartsList:: PartsList(): 159: pHead(0), 160: itsCount(0) 161: { } 162: 163: PartsList:: ^PartsList() 164: { 165: delete pHead; 166: } 167: 168: Part* PartsList:: GetFirst() const 169: { 170: if (pHead) 171: return pHead-> GetPart(); 172: else 173: return NULL; // ловушка ошибок 174: } 175: 176: Part * PartsList:: operator[](int offSet) const 177: { 178: PartNode* pNode = pHead; 179: 180: if (! pHead) 181: return NULL; // ловушка ошибок 182: 183: if (offSet > itsCount) 184: return NULL; // ошибка 185: 186: for (int i=0; i< offSet; i++) 187: pNode = pNode-> GetNext(); 188: 189: return pNode-> GetPart(); 190: } 191: 192: Part* PartsList:: Find(int & position, int PartNumber) const 193: { 194: PartNode * pNode = 0; 195: for (pNode = pHead, position = 0; 196: pNode! =NULL; 197: pNode = pNode-> GetNext(), position++) 198: { 199: if (pNode-> GetPart()-> GetPartNumber() == PartNumber) 200: break; 201: } 202: if (pNode == NULL) 203: return NULL; 204: else 205: return pNode-> GetPart(); 206: } 207: 208: void PartsList:: Iterate(void (Part:: *func)()const) const 209: { 210: if (! pHead) 211: return; 212: PartNode* pNode = pHead; 213: do 214: (pNode-> GetPart()-> *func)(); 215: while (pNode = pNode-> GetNext()); 216: } 217: 218: void PartsList:: Insert(Part* pPart) 219: { 220: PartNode * pNode = new PartNode(pPart); 221: PartNode * pCurrent = pHead; 222: PartNode > > pNext = 0; 223: 224: int New = pPart-> GetPartNumber(); 225: int Next = 0; 226: itsCount++; 227: 228: if (! pHead) 229: { 230: pHead = pNode; 231: return; 232: } 233: 234: // Если это значение меньше головного узла, 235: // то текущий узел становится головным 236: if (pHead-> GetPart()-> GetPartNumber()-> New) 237: { 238: pNode-> SetNext(pHead); 239: pHead = pHode; 240: return; 241: } 242: 243: for (;; ) 244: { 245: // Если нет следующего, вставляется текущий 246: if (! pCurrent-> GetNext()) 247: { 248: pCurrent-> SetNext(pNode); 249: return; 250: } 251: 252: // Если текущий больше предыдущего, но меньше следующего, то вставляем 253: // здесь. Иначе присваиваем значение указателя Next 254: pNext = pCurrent-> GetNext(); 255: Next = pNext-> GetPart()-> GetPartNumber(); 256: if (Next > New) 257: { 258: pCurrent-> SetNext(pNode); 259: pNode-> SetNext(pNext); 260: return; 261: } 262: pCurrent = pNext; 263: } 264: } 265: 266: int main() 267: { 268: PartsList& pl = PartsList:: GetGlobalPartsList(); 269: Part * pPart = 0; 270: int PartNumber; 271: int value; 272: int Choice; 273: 274: while (1) 275: { 276: cout < < " (0)Quit (1)Car (2)Plane: "; 277: cin > > choice; 278: 279: if (! choice) 280: break; 281: 282: cout < < " New PartNumber?: "; 283: cin > > PartNumber; 284: 285: if (choice == 1) 286: { 287: cout < < " Model Year?: "; 288: cin > > value; 289: pPart = new CarPart(value, PartNumber); 290: } 291: else 292: { 293: cout < < " Engine Number?: "; 294: cin > > value; 295: pPart = new AirPlanePart(value, PartNumber); 296: } 297: 298: pl.Insert(pPart); 299: } 300: void (Part:: *pFunc)()const = Part:: Display; 301: pl.Iterate(pFunc); 302: return 0; 303: }
Результат: (0)Quit (1)Car (2)Plane: 1 New PartNumber?: 2837 Model Year? 90 (0)Quit (1)Car (2)Plane: 2 New PartNumber?: 378 Engine Number?: 4938 (0)Quit (1)Car (2)Plane: 1 New PartNumber?: 4499 Model Year?: 94 (0)Quit (1)Car (2)Plane: 1 New PartNumber?: 3000 Model Year? 93 (0)Quit (1)Car (2)Plane: 0 Part Number: 378 Engine No.: 4938 Part Number: 2837 Model Year: 90 Part Number: 3000 Model Year: 93 Part Number: 4499 Model Year: 94
Представленная программа создает связанный список объектов класса Part. Связанный список — это динамическая структура данных вроде массива, за исключением того, что в список можно добавлять произвольное число объектов указанного типа и удалять любой из введенных объектов. Данный связанный список разработан для хранения объектов класса Part, где Part — абстрактный тип данных, служащий базовым классом для любого объекта с заданной переменной-членом itsPartNumber. В программе от класса Part производятся два подкласса - CarPartHAirPlanePart. Класс Part описывается в строках 26—36, где задаются одна переменная-член и несколько методов доступа. Предполагается, что затем в объекты класса будет добавлена другая ценная информация и возможность контроля за числом созданных объектов в базе данных. Класс Part описывается как абстрактный тип данных, на что указывает чистая виртуальная функция Display(). Обратите внимание, что в строках 40-43 определяется выполнение чистой виртуальной функции Display(). Предполагается, что метод Display() будет замещаться в каждом производном классе, но в определении замещенного варианта допускается просто вызывать стандартный метод из базового класса. Два простых производных класса, CarPart и AirPlanePart, описываются в строках 47—59 и 69—87 соответственно. В каждом из них замещается метод Display() простым обращением к методу Display() базового класса. Класс PartNode выступает в качестве интерфейса между классами Part и PartList. Он содержит указатель на Part и указатель на следующий узел в списке. Методы этого класса только возвращают и устанавливают следующий узел в списке и возвращают соответствующий объект Part. За " интеллектуальность" связанного списка полностью отвечает класс PartsList, описываемый в строках 132—152. Этот класс содержит указатель на головной узел списка (pHead) и, с его помощью продвигаясь по списку, получает доступ ко всем другим методам. Продвижение по списку означает запрашивание текущего узла об адресе следующего вплоть до обнаружения узла, указатель которого на следующий узел равен NULL. Безусловно, в этом примере представлен упрощенный вид связанного списка. В реально используемой программе список должен обеспечивать еще больший доступ к первому и последнему узлам списка или создавать специальный объект итерации, с помощью которого клиенты смогут легко продвигаться по списку. В то же время класс PartsList предлагает ряд интересных методов, упорядоченных по алфавиту. Зачастую такой подход весьма эффективен, поскольку упрощает поиск нужных функций. Функция Find() принимает в качестве параметров PartNumber и значение int. Если найден раздел с указанным значением PartNumber, функция возвращает указатель на Part и порядковый номер этого раздела в списке. Если же раздел с номером PartNumber не обнаружен, функция возвращает значение NULL. Функция GetCount() проходит по всем узлам списка и возвращает количество объектов в списке. В PartsList это значение записывается в переменную-член itsCount, хотя его можно легко вычислить, последовательно продвигаясь no списку. Функция GetFirst() возвращает указатель на первый объект Part в списке или значение NULL, если список пустой. Функция GetGlobalPartsList() возвращает ссылку на статическую переменную-член GiobalPartsList. Описание статической переменной GlobaiPartsList является типичным решением для классов типа PartsList, хотя, безусловно, могут использоваться и другие имена. В законченном виде реализация этой идеи состоит в автоматическом изменении конструктора класса Part таким образом, чтобы каждому новому объекту класса присваивался номер с учетом текущего значения статической переменной GiobalPartsList. Функция Insert принимает значение указателя на объект Part, создает для него PartNode и добавляет объект Part в список в порядке возрастания номеров PartNumber. Функция Iterate принимает указатель на константную функцию-член класса Part без параметров, которая возвращает void. Эта функция вызывается для каждого объекта Part в списке. В описании класса Part таким характеристикам соответствует единственная функция Display(), замещенная во всех производных классах. Таким образом, будет вызываться вариант метода Display(), соответствующий типу объекта Part. Функция Operator[] позволяет получить прямой доступ к объекту Part по заданному смещению. Этот метод обеспечивает простейший способ определения границ списка: если список нулевой, или заданное смещение больше числа объектов в списке, возвращается значение NULL, сигнализирующее об ошибке. В реальной программе имело бы смысл все эти комментарии с описанием назначений функций привести в описании класса. Тело функции main() представлено в строках 266-303. В строке 268 описывается ссылка на PartsList и инициализируется значением GiobalPartsList. Обратите внимание, что GiobalPartsList инициализируется в строке 154. Эта строка необходима, поскольку описание статической переменной-члена не сопровождается ее автоматическим определением. Поэтому определение статической переменной-члена должно выполняться за пределами описания класса. В строках 274—299 пользователю предлагается указать, вводится ли деталь для машины или для самолета. В зависимости от выбора, запрашиваются дополнительные сведения и создается новый объект, который добавляется в список в строке 298. Выполнение метода Insert() класса PartList показано в строках 218—264. При вводе идентификационного номера первой детали — 2837 — создается объект CarPart, который передается в LinkedList:: Insert()c введенными номером детали и годом создания 90. В строке 220 создается новый объект PartNode, принимающий значения новой детали. Переменная New инициализируется номером детали. Переменная-член itsCount класса PartsList увеличивается на единицу в строке 226. В строке 228 проверяется равенство указателя pHead значению NULL. В данном случае возвращается значение TRUE, поскольку это первый узел списка и указатель pHead в нем нулевой. В результате в строке 230 указателю pHead присваивается адрес нового узла и функция возвращается. Пользователю предлагается ввести следующую деталь. В нашем примере вводится деталь от самолета с идентификационным номером 37 и номером двигателя 4938. Снова вызывается функция PartsList:: Insert() и pNode инициализируется новым узлом. Статическая переменная-член itsCount становится равной 2 и вновь проверяется pHead. Поскольку теперь pHead не равен нулю, то значение указателя больше не изменяется. В строке 236 номер детали, указанный в головном узле, на который ссылается pHead (в нашем случае это 2837), сравнивается с номером новой детали — 378. Поскольку последний номер меньше, условное выражение в строке 236 возвращает TRUE и головным узлом в списке становится новый объект. Строкой 238 указателю pNode присваивается адрес того узла, на который ссылался указатель pHead. Обратите внимание, что в следующий узел списка передается не новый объект, а тот, который был введен ранее. В строке 239 указателю pHead присваивается адрес нового узла. На третьем цикле пользователь вводит деталь для автомобиля под номером 4499 с годом выпуска 94. Происходит очередное приращение счетчика и сравнивается номер текущего объекта с объектом головного узла. В этот раз новый введенный идентификационный номер детали оказывается больше номера объекта, определяемого в pHead, поэтому запускается цикл for в строке 243. Значение идентификационного номера головного узла равно 378. Второй узел содержит объект со значением 2837. Текущее значение — 4499. Исходно указатель pCurrent связывается с головным узлом. Поэтому при обращении к переменной next объекта, на который указывает pCurrent, возвращается адрес второго узла. Следовательно, условное выражение в строке 246 возвратит False. Указатель pCurrent устанавливается на следующий узел, и цикл повторяется. Теперь проверка в строке 246 приводит к положительному результату. Если следующего элемента нет, то новый узел вставляется в конец списка. На четвертом цикле вводится номер детали 3000. Дальнейшее выполнение программы напоминает предыдущий этап, однако в этом случае текущий узел имеет номер 2837, а значение следующего узла равно 4499. Проверка в строке 256 возвращает TRUE, и новый узел вставляется между двумя существующими. Когда пользователь вводит 0, условное выражение в строке 279 возвращает TRUE и цикл while(1) прерывается. В строке 300 функция-член Display() присваивается указателю на функции-члены pFunc. В профессиональной программе присвоение должно проходить динамически, основываясь на выборе пользователем. Указатель функции-члена передается методу Iterate класса PartsList. В строке 208 метод Iterate() проверяет, не является ли список пустым. Затем в строках 213—215 последовательно с помощью указателя функции-члена вызываются из списка все объекты Part. В итоге для объекта Part вызывается соответствующий вариант метода Display(), в результате чего для разных объектов выводится разная информация.
Неделя №3
Основные вопросы
Итак, две недели изучения C++ уже позади. Сейчас вы наверняка свободно ориентируетесь в некоторых достаточно сложных аспектах объектно-ориентированного программирования, включая инкапсуляцию и полиморфизм.
Что дальше
Последняя неделя начинается с изучения дополнительных возможностей наследования. Затем на занятии 16 вы изучите потоки, а на занятии 17 познакомитесь с одним замечательным дополнением стандартов C++ — пространствами имен. Занятие 18 посвящено анализу основ объектно-ориентированного программирования. В этот день внимание будет сконцентрировано не столько на синтаксисе языка, сколько на изучении концепций объектно-ориентированного программирования. На занятии 19 вы познакомитесь с использованием шаблонов, а на занятии 20 узнаете о методах отслеживания исключительных ситуаций и ошибок. Наконец, на последнем занятии будут раскрыты некоторые хитрости и секреты программирования на C++, что сделает вас настоящим гуру в этой области.
Популярное:
|
Последнее изменение этой страницы: 2017-03-08; Просмотров: 586; Нарушение авторского права страницы