Супер-модератор
![]() ![]() |
||||||||||||||||||||||||||||||||||||||||||||||
1 | ||||||||||||||||||||||||||||||||||||||||||||||
Как не надо писать программы22.09.2014, 21:10. Просмотров 16775. Ответов 4
Метки нет Все метки)
(
При написании графических программ большинство проблем проявляется более остро, поэтому я хочу рассказать о том, чего не надо делать, когда пишется программа с графическим интерфейсом, и приведу основные способы исправления часто встречающихся ошибок. Все фрагменты кода, которые я здесь рассматриваю, взяты мной из вопросов на разных форумах по программированию, я ничего не придумал (иногда в вопросах новичков встречается такое, что мне бы фантазии не хватило придумать это)
Итак: Когда вы пишете графическую программу, вы не должны ... ... рассчитывать на то, что размер экрана постоянен, и равен 640 пикселей по горизонтали на 480 по вертикали. Простейший код - заставка: приветствие пользователя. В центре экрана должна появиться надпись 'Hello'. Что делают новички?
Написав код правильно:
... использовать "магические числа", полагаясь на то, что вы всегда будете использовать для вывода текста один и тот же шрифт, и работать в одном и том же разрешении. Этот пункт является по сути продолжением предыдущего пункта. Итак, была поставлена задача вывести несколько строк, и обвести их прямоугольником (что-то подобное всплывающему меню в Windows-приложениях). Вот так эта задача была решена:
после чего программист, считая, что здесь прокола не будет, использует эту процедуру в своей программе "по полной". И в какой-то момент сталкивается с проблемой: Шрифт был изменен с дефолтного на SmallFont, отображение уже не такое, как прежде, и придется искать неправильную константу и исправлять ее (скорее всего - методом подбора). А этого опять же можно было избежать, сразу ориентируясь на работу с разными шрифтами:
(smallfont, 4) (gothicfont, 5) ... использовать числовые значения вместо именованных констант. В общем-то, этого правила стоило бы придерживаться не только при программировании графики, но и вообще при любом программировании, особенно начинающему программисту. Однако, как показывают вопросы, задаваемые на форумах, этого правила новички как раз и не придерживаются. А зря. Мне, например, гораздо сложнее понять, что такое:
Ну, и еще одно замечание. Не перемешивайте логику и интерфейс. Я уже устал говорить об этом, но, похоже, меня не слышат. Если вы делаете графическое меню, то функция отображения должна только отображать его, ни в коем случае не выполняя действия, "подключенные" к этому пункту меню. Потому, что если вам потом захочется сделать другое меню (или текстовое, или тоже графическое, но, скажем, трехмерное), то при разделенных логике/интерфейсе достаточно будет написать новую функцию, отображающую менюшки, а все остальное останется без изменения. В большинстве же проектов, которые пишут студенты начальных курсов ВУЗов, проще переписать всю программу заново, чем чуть-чуть изменить ее внешний вид (потому что все переплетено настолько вычурно, что только потяни за одну ниточку, все запутается окончательно).
11
|
|
22.09.2014, 21:10 | |
Как писать программы? Подскажите, пожалуйста, как писать данные программы, чтобы я понял принцип Как писать программы с неравенствами? Литература: как писать программы в паскале |
|
Супер-модератор
![]() ![]() |
||||||||||||||||||||||||||||||||||||
22.09.2014, 21:16 [ТС] | 2 | |||||||||||||||||||||||||||||||||||
(продолжаем...)
Турбо Паскаль и нехватка памяти / ошибки №48, №49 В связи с возросшим количеством вопросов на темы "Программе не хватает памяти, что делать", "Ошибка компиляции №48 (или №49), помогите", "Программа вылетает с переполнением стека, куда копать?", хотелось бы привести основные моменты, на которые стоит обратить внимание при написании программ. Особенно рекомендуется к прочтению новичкам в написании сложных, развернутых проектов, в частности таких, которые собираются из нескольких уже работающих по отдельности фрагментов. (все написанное ниже относится только к программированию в Турбо Паскале, многое из этого в более современных компиляторах уже неактуально, хотя кое что может и пригодиться) Ошибка №48 - Code segment too large (сегмент кода слишком большой). Возникает в случае, когда размер кода, получаемого при компиляции программы, превышает 65520 байт (то есть, размер одного сегмента). С этой ошибкой бороться относительно легко, и путей борьбы здесь несколько.
Как пример (из реальной программы, по которой был задан подобный вопрос):
Отвлечемся ненадолго от ошибки №48, и посмотрим ... Ошибка №49: Data segment too large (слишком большой сегмент данных) Это происходит в случае, когда общий размер данных (глобальных данных, нужно заметить, потому что локальные размещаются не в сегменте данных) всех модулей превышает отведённый для них размер в 65520 байт. Простым разбиением на модули эту ошибку побороть нельзя: дело в том, что программа на Паскале может содержать несколько сегментов кода, и только один (общий для всех модулей) сегмент данных. Так что, если каждый из ваших модулей по отдельности компилируется - это ещё ничего не значит, при линковке exe-файла все ещё может "всплыть" сообщение об ошибке №49. Например: Первый модуль:
Но и с этой ошибкой можно бороться. И тоже не одним способом: Способ первый - проверяем, действительно ли нужны массивы типа LongInt. Как пример: нужно хранить в массиве рост 10000 школьников с точностью до сантиметра. И зачем тут longint? Достаточно типа byte (поправьте меня, если я ошибаюсь, но рост более 255 сантиметров для школьника - глупо), экономия четырехкратная. После изменения базового типа на byte даже только в одном из модулей мы избавляемся от "ошибки №49". Особое внимание обратите на строковые типы: по умолчанию для string-а резервируется 256 байт. Это не всегда нужно, можно существенно уменьшить размер данных ограничением длины строки через string[длина]. То же самое касается вещественных типов: вместо использования real (6 байт) используем single (4 байта). Во-первых, экономия памяти в случае использования массивов, во вторых - увеличение быстродействия программы (тип real вообще очень медленный, поскольку он является "чужеродным" для сопроцессора, и его конвертация в сопроцессорный тип занимает кучу времени). Способ второй - используем динамическое выделение памяти вместо статического, в результате данные хранятся не в сегменте данных, а в "куче", следовательно, опять избавляемся от ошибки №49: Модуль №1
5
|
Супер-модератор
![]() ![]() |
|||||||||||
22.09.2014, 21:19 [ТС] | 3 | ||||||||||
RunTime error 202: Stack overflow error (или "Программа вылетает с переполнением стека") Причиной этого является то, что в стеке закончилось место для размещения локальных переменных подпрограммы. То есть, если есть программа:
(Также эта ошибка очень часто возникает при разработке - неправильной, естественно - рекурсивных подпрограмм. Об этом - чуть ниже) И опять же есть несколько способов борьбы и с этой ошибкой:
Что касается рекурсии:
Ну, и еще несколько общих рекомендаций при разработке программ:
6
|
Супер-модератор
![]() ![]() |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
22.09.2014, 21:22 [ТС] | 4 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
(заканчиваем)
Паскаль - это язык с очень мощной системой типов. Если правильно использовать этот факт, то можно ловить некоторые ошибки в программе еще на этапе компиляции. Например: (неправильное использование)
(правильное решение)
Повторяю: Паскаль - это язык с очень мощной системой типов. Пользуйтесь этим. Не экономьте на введении новых типов в свою программу. Простое описание типа не увеличивает размер exe-файла программы, какого бы большого размера не был описываемый тип. Т.е., не экономьте подобным образом (описанием типа [1 .. 1], я имею в виду):
Решается это очень просто:
Еще один пример, когда описание нового типа помогает справиться с задачей быстрее/проще, чем было без него. Всем хорошо известная задача: поменять в матрице вторую и четвертую строку. "Ну, чего тут сложного," - говорит новичок, и лихо пишет программу:
Ну, и еще кое-что. Я постоянно говорю об этом на форумах, но все время встречаю ошибки, связанные с нежеланием придерживаться этого простого совета: объявляйте переменную как можно ближе к тому месту, где она должна использоваться. Особенно это касается счетчиков цикла. Вот программа, иллюстрирующая ошибку:
Вот так надо было писать программку:
И еще одно уточнение, касающееся этой программы: заметили в 14-ой строке, что я не написал:
Повторяю еще раз: Паскаль - это язык с очень мощной системой типов, и это накладывает свои требования: даже две переменных (казалось бы одинакового типа)
8
|
Супер-модератор
![]() ![]() |
||||||||||||||||||||||||||||||||||||||||||||||
22.09.2014, 21:24 [ТС] | 5 | |||||||||||||||||||||||||||||||||||||||||||||
Решил дополнить список того, как делать не надо.
То, о чем будет сказано ниже - относится в основном к новому компилятору - FPC... Не используйте в качестве параметров функционального (процедурного) типа функции с неправильной сигнатурой. Объясню на примере. Вы все, конечно, слышали, что для передачи в подпрограмму своей функции, надо предварить ее имя операцией взятия адреса? Далеко ходить не буду, открываем один из примеров, идущих с FPC, называется winhello.pp, находится он в папке \FPC\{версия}\demo\win32, и создается в этом примере простейшее Win32 приложение. Вот одна из процедур этой программы:
Еще примеры? Пожалуйста, открываем DRKB, смотрим пример использования функции EnumWindows. Я его немного упростил, вот в таком виде программа компилируется FPC:
Кто-нибудь задумывался, почему, собственно, надо добавлять этот вездесущий "@"? Посмотрите в любой книге по Турбо-Паскалю тему "Процедурные типы". Вы там хоть один такой символ при передаче функции как параметра в другую функцию видели? Что же произошло, что теперь компиляторы требуют везде брать адрес, интересно? А ничего не произошло. Все дело - в том, что этот значок никому не нужен. Вы этим самым оказываете и себе и компилятору медвежью услугу - вместо того, чтобы описать Callback функцию правильно, вы берете адрес этой функции, который совместим с любым другим указателем. Вспомните, что
Что же надо сделать, чтобы исправить приведенные выше примеры? Очень немного. 1. Оконная функция. Смотрим в MSDN (если не помним наизусть) сигнатуру оконной функции:
2. Функция перечисления окон. Аналогично: смотрим описание EnumWindows, чтобы узнать, какая функция ожидается первым параметром. А вот такая:
То же самое касается и излюбленных хаков: запуска функций через RunDll32. Очень много в интернете советов подобного рода: Интерфейс программ Windows Rundll и Rundll32 Вот что:
И после этого люди еще удивляются, что "обратная смена невозможна"? Удивляться-то надо не этому, а тому, что оно вообще хоть как-то работает. Опять же, отсюда и эти самые "работает только на Win98, или только на Win95". Запуск функции с подходящими сигнатурами поддерживается на всех версиях Windows. А вот поддерживать то, что изначально не предполагалось - извините, никто не обязан. P.S. Я говорю об этом каждый раз: Паскаль - очень мощный язык, но используйте его правильно, не пытайтесь "объегорить" ни компилятор ни ОС. Выйдет себе дороже. Я надеюсь, хоть кто-то задумается над тем, что я тут написал, и перестанет бездумно добавлять операторы в программу для того, чтобы ее откомпилировать с наименьшими затратами усилий... Лучше потерять чуть больше времени, но разобраться в причинах, по которым компилятор отказывается выполнять свою работу, и исправить именно сами причины, а не закрывать глаза на следствия. Желающие скачать этот топик отдельным PDF-файлом могут это сделать, в аттаче точная копия топика.
13
|
22.09.2014, 21:24 | |
Заказываю контрольные, курсовые, дипломные и любые другие студенческие работы здесь. Надо ли в конце программы до end писать readln Помогите писать на С++ через шаблоны. Консуле я писал, но надо писать исползуя шаблоны Как создать программу которая бы выводила рандомно примеры и надо было правильно писать ответы как писать программы Искать еще темы с ответами Или воспользуйтесь поиском по форуму: |