ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 30.10.2023
Просмотров: 420
Скачиваний: 1
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
230 Глава 7
мы могли бы получить, избегая копирования данных во вторичный массив и обратно.
Таким образом, в этом сценарии второй подход, использующий метод qsort, оказывается победителем. Его проще реализовать, про- ще читать и, следовательно, легче сопровождать, кроме того, мы мо- жем ожидать того, что он окажется таким же, а, возможно, и более эффективным, чем первое решение. Самое лучшее, что мы можем сказать о первом подходе, — это то, что мы могли бы освоить навы- ки, которые можно было бы применить к решению других задач, тог- да как второй подход, в силу своей простоты, этого не предполагает.
Как правило, когда вы находитесь на этапе программирования, где пытаетесь освоить максимальное количество навыков, вам следует отдавать предпочтение компонентам более высокого уровня, таким как алгоритмы и шаблоны. Когда вы пытаетесь максимизировать свою эффективность как программиста (или ограничены во време- ни), вы должны отдавать предпочтение компонентам более низкого уровня, выбирая, по возможности, готовый код. Конечно, если вре- мя позволяет, попытка использовать несколько разных вариантов, как мы сделали это здесь, позволяет получить лучшее из всех под- ходов.
Упражнения
Опробуйте как можно больше компонентов. Как только вы научи- тесь осваивать новые компоненты, ваши способности как програм- миста быстро начнут расти.
7.1.
Жалоба, поступающая в адрес шаблона политики/стратегии, за- ключается в том, что она требует разоблачения некоторых вну- тренних данных класса, например типов. Измените программу для определения старосты, рассмотренную ранее в этой главе, так, чтобы все функции политики хранились в классе и выбира- лись путем передачи значения кода (например, нового нумеро- ванного типа) вместо передачи самой функции политики.
7.2.
Перепишите наши функции studentCollection из главы 4 (ad- dRecord и averageRecord) так, чтобы вместо непосредственной реализации связного списка вы использовали класс из библио- теки C++.
7.3.
Рассмотрим коллекцию объектов studentRecord. Мы хотим иметь возможность быстрого нахождения конкретной записи на основе номера ученика. Сохраните записи учеников в масси- ве, отсортируйте массив по номеру ученика, а также исследуйте и реализуйте алгоритм интерполяционного поиска.
7.4.
Решите задачу из пункта 7.3 путем реализации абстрактного типа данных, который позволяет хранить произвольное количество элементов и извлекать отдельные записи на основе значения ключа. Общим названием для структуры, которая может эффек- тивно хранить и извлекать элементы на основе значения ключа,
является таблица символов, а распространенными реализациями идеи таблицы символов — хеш-таблицы и двоичные деревья поиска.
1 ... 24 25 26 27 28 29 30 31 ... 34
7.5.
Решите задачу в пункте 7.3, используя класса из библиотеки C++.
7.6.
Предположим, вы работаете над проектом, в котором может по- требоваться дополнить конкретную запись studentRecord одним из следующих фрагментов данных: названием курсовой рабо- ты, годом поступления или значением bool, указывающим, про- слушивает ли студент данный курс без получения оценки. Вы не хотите включать все эти поля данных в базовый класс student-
Record
, зная, что они не будут использоваться в большинстве случаев. Вашей первой мыслью будет создание трех подклассов, каждый из которых имеет одно из полей данных с такими име- нами, как studentRecordTitle, studentRecordYear и studentRe- cordAudit
. Затем вам сообщают, что некоторые записи студен- тов будут содержать два из этих дополнительных полей данных или, возможно, все три. Создание подклассов для каждого воз- можного варианта нецелесообразно. Найдите шаблон дизайна, направлен ный на разгадывание этой загадки, и реализуйте ре- шение.
7.7.
Разработайте решение задачи, описанной в пункте 7.6, кото- рое не использует обнаруженный вами шаблон, но вместо этого решает проблему с помощью классов библиотеки C ++. Вместо того, чтобы сосредоточиваться на трех конкретных полях дан- ных, описанных в предыдущем вопросе, попробуйте реализо- вать общее решение: версию класса studentRecord, которая по- зволяет добавлять в конкретные объекты дополнительные поля данных. Так, например, если sr1 представляет собой запись stu- dentRecord
, вы можете сделать так, чтобы клиентский код вы- зывал sr1.addExtraField("Title", "Problems of Uncondition- al Branching")
, а затем sr1.retrieveField("Title") возвращал
«проблему безусловного ветвления».
7.8.
Придумайте собственную задачу. Возьмите задачу, которую вы уже решили, и решите ее снова, используя другой компонент.
Не забудьте проанализировать результаты, сравнив их со своим исход ным решением.
232 Глава 8
Пришло время подвести итог все- му, что мы узнали в предыдущих главах, чтобы завершить превраще- ние из новичка в программиста, умею- щего решать задачи. В предыдущих главах мы решали задачи, относящиеся к самым разным областям.
Я считаю, что эти области наиболее полезны для раз- вивающегося программиста, но, разумеется, процесс обучения ими не ограничивается, и многие задачи потребуют навыков, не описан- ных в этой книге. Итак, в данной главе мы собираемся объединить общие концепции решения задач и применить знания, которые ус- воили по ходу нашего путешествия, чтобы разработать мастер-план для решения любой задачи по программированию. Хотя мы могли бы назвать этот план общим, в некотором отношении он на самом деле очень конкретен: это будет ваш план, а не чей-то. Мы также рас-
" $ " "
8
Думайте как программист 233
смотрим множество способов, с помощью которых вы можете рас- ширить свои профессиональные знания и навыки.
Разработка собственного мастер-плана
В первой главе мы узнали о первом правиле решения задачи, кото- рое заключается в том, чтобы всегда иметь план. Если сформулиро- вать его точнее, то вы всегда должны придерживаться своего плана.
Составьте мастер-план, который максимизирует ваши сильные сто- роны и сводит к минимуму слабые, а затем примените его к каждой решаемой вами задаче.
За многие годы преподавания я видел учеников, обладающих разными способностями. Под этим я не просто имею в виду, что не- которые программисты обладают большими способностями, чем другие, хотя это, конечно, правда. Даже среди программистов с оди- наковыми способностями существует большое разнообразие. Я мно- го раз удивлялся тому, как еще недавно отстающий студент быстро овладевает особым навыком или как талантливый ученик испытыва- ет сложности с освоением новой области. Так же, как нет двух оди- наковых отпечатков пальцев, нет двух одинаковых мозгов, и темы, простые для одного человека, сложны для другого.
Представьте, что вы — футбольный тренер, планирующий страте- гию для следующей игры. Из-за травмы двух защитников вы не увере- ны, какой из них сможет начать игру. Оба защитника — высокопрофес- сиональные игроки, но, как у всех, у них есть свои сильные и слабые стороны. План игры, обеспечивающий лучшие шансы на победу для одного защитника, может оказаться провальным для другого.
При создании своего мастер-плана вы являетесь тренером, а ваш набор навыков — вашим игроком. Чтобы максимизировать свои шан- сы на успех, вам нужен план, который учитывает как ваши сильные, так и слабые стороны.
Использование своих сильных и слабых сторон
Ключевым шагом в составлении собственного мастер-плана является определение собственных сильных и слабых сторон. Это не сложно, но требует усилий и довольно честной самооценки. Чтобы извлечь пользу из своих ошибок, вы должны не только исправлять их в про- граммах, в которых они появляются, но и отмечать их, по крайней мере, мысленно или еще лучше, документировать. Так вы можете определить поведенческие паттерны, которые вы в противном слу- чае пропустили бы.
Я расскажу о недостатках в двух различных категориях — в ко- дировании и в дизайне. Недостатки кодирования — это области, где вы склонны повторять ошибки при написании кода. Например, мно- гие программисты часто пишут циклы, количество итераций в ко- торых оказывается на единицу меньше или больше чем нужно. Это
234 Глава 8
известно как ошибка заборного столба, получившая свое название от старой загадки о том, сколько заборных столбов необходимо для соз- дания 50-метрового забора с поперечинами длиной в 10 метров меж- ду столбами. Большинство людей сразу отвечают «пять», но если вы как следует подумаете, то получите «шесть», как показано на рис. 8.1.
Большинство недостатков кодирования связано с ситуациями, ког- да программист допускает семантические ошибки, создавая код слиш- ком быстро или без достаточной подготовки. Напротив, недостатки
в дизайне — это проблемы, которые обычно возникают на этапе реше- ния задач или проектирования. Например, вы можете обнаружить, что сталкиваетесь с проблемами на начальном этапе или на этапе интегра- ции ранее написанных подпрограмм в итоговое решение.
Рис. 8.1. Загадка заборного столба
Несмотря на то, что две эти категории несколько пересекают- ся, два вида недостатков, как правило, создают разные проблемы, и предотвращать их нужно разными способами.
Планирование с учетом недостатков кодирования
Вероятно, наиболее неприятная деятельность в программирова- нии — это многочасовое отслеживание семантической ошибки, ис- править которую очень просто, как только удается ее обнаружить.
Поскольку никто не совершенен, нет возможности полностью устра- нить подобные ситуации, но хороший программист сделает все воз- можное, чтобы избежать повторения одних и тех же ошибок.
Я знал программиста, который устал совершать, пожалуй, наибо- лее распространенную семантическую ошибку в программировании на языке С++, которая заключается в подмене оператора равенства
(==) оператором присваивания (=). Поскольку условные выражения в C++ являются целочисленными, а не строго логическими, утверж- дение, подобное следующему, синтаксически допустимо: if (number = 1) ag = true;
В данном случае целочисленное значение 1 присваивается пере- менной number, а затем значение 1 используется как результат услов- ного выражения, которое C++ оценивает как true. Конечно, про- граммист имел в виду следующее: if (number == 1) ag = true;
Думайте как программист 235
Устав от повторения такого типа ошибок, программист приучил себя всегда писать тесты равенства по-другому, указывая числовой литерал слева, например: if (1 == number) ag = true;
Благодаря этому, если программист по ошибке заменит опера- тор равенства, выражение 1 = number перестанет быть допусти- мым синтаксисом C++, что приведет к синтаксической ошибке, ко- торая будет обнаружена во время компиляции. Исходная ошибка представляет собой допустимый синтаксис, поэтому является все- го лишь семантической ошибкой, которая будет обнаружена во вре- мя компиляции или не будет обнаружена вообще. Поскольку я сам много раз совершал эту ошибку (и сходил с ума, пытаясь ее найти), я использовал этот метод, помещая числовой литерал в левой части оператора равенства. При этом я обнаружил кое-что любопытное.
Поскольку это противоречит моему обычному стилю, при написа- нии условных утверждений необходимость помещения числового литерала в левую часть заставляет меня на мгновение остановить- ся. Я думаю: «Мне нужно не забыть поместить литерал слева, чтобы суметь поймать себя в случае, если я по ошибке использую опера- тор присваивания». Как и следовало ожидать, благодаря этой мыс- ли я никогда не использовал оператор присваивания вместо опера- тора равенства. Теперь я больше не помещаю литерал в левую часть оператора равенства, но по-прежнему останавливаюсь и позволяю этой мысли пронестись в моей голове, что не позволяет мне ис- пользовать неправильный оператор.
Урок здесь заключается в том, что знания о своих недостатках на уровне кодирования часто достаточно для того, чтобы избежать их. Это хорошая новость. Плохая новость заключается в том, что вам все равно придется поработать, чтобы осознать свои слабые стороны как кодировщика. Ключевой метод заключается в том, что- бы спросить себя, почему вы допустили ту или иную ошибку, вместо того, чтобы просто исправить ее и двигаться дальше. Это позволит вам определить общий принцип, которому вы не последовали. На- пример, предположим, что вы написали следующую функцию для вычисления среднего значения положительных чисел в целочислен- ном массиве: double averagePositive(int array[ARRAYSIZE]) {
int total = 0;
int positiveCount = 0;
for (int i = 0; i < ARRAYSIZE; i++) {
if (array[i] > 0) {
total += array[i];
positiveCount++;
}
}
X return total / (double) positiveCount;
}