| Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате |
| Форум программистов > C/C++: Для новичков > Исходные файлы с описанием класса |
| Автор: efpies 18.1.2011, 16:23 |
| Здравствуйте. Очень ламерский вопрос, на самом деле. У меня в проекте несколько классов. Как было бы правильно их разбивать по файлам? Каждый класс в отдельный cpp-файл? Или в h-файл? Или в h-файле - прототипы и константы, а в cpp, который подключён к h, - описание? Как правильно? |
| Автор: KaraKum 18.1.2011, 16:32 | ||||
| Некоторые фанатики каждый класс описывают в отдельном .h-файле (называя файл так же как и класс но только маленькими буквами и разделяя слова пробелами - получается ужасно). Можно делить объявления классов по логическим признакам. Например классы отвечающие за графику - в одном файле; за логику - в другом. Общих нареканий в этой области нет (в отличии от, например, по оформлению кода (самая известная - GNU-style)). Код определения функций можно писать прямо в объявлении этих функций (в .h-файле) если подразумевается что файл будет подключаться к проекту только в одном месте (что как раз это подчёркивает) Не забывать что можно писать и "#include "definition.cpp"" - то есть вообще обойтись без header'ов Добавлено через 3 минуты и 43 секунды В остальном могут быть только какие-то частные нарекания. Например какие-то глобальные переменные (как же без них) можно определить в "globals.cpp":
И создать вот такой "externs.h", который можно подключать в любом другом файле где нужно использовать глобальные переменные:
Добавлено через 9 минут и 21 секунду Вообще, все эти проблемы решаются как сами собой разумеющиеся после должного проектирования программы (перед её написанием или в процессе). Для этого можно пользоваться средствами http://ru.wikipedia.org/wiki/Объектно-ориентированное_программирование, http://ru.wikipedia.org/wiki/Разработка_через_тестирование и общие принципы проектирования рисованием http://www.graphviz.org/, http://ru.wikipedia.org/wiki/UML-диаграмм... |
| Автор: mes 18.1.2011, 17:08 | ||
и нажить проблем |
| Автор: mes 18.1.2011, 17:09 |
далеко не только фанатики... |
| Автор: KaraKum 18.1.2011, 19:41 | ||
Скорее-всего я неясно высказался. Иногда бывает что какие-то структуры используются только внутри какой-то одной функции - такие структуры вполне логично объявлять внутри самой этой функции, однако некоторые ФАНАТИКИ выделят для этой структуры отдельный файл, назвав этот файл в её честь. |
| Автор: borisbn 18.1.2011, 21:06 |
| 2 TS: пишу с КПК в автобусе, поэтому коротко: KaraKum дал тебе ОЧЕНЬ плохие советы. Что про несколько классов в одном h-нике, что инклюд срр-шника, что про глобальные переменные. Чуть позже подробнее отпишусь |
| Автор: KaraKum 18.1.2011, 22:42 |
| Опаньки Ну вот - TS в чрезвычайном режиме спасли от моих смертельных советов Добавлено через 55 секунд Кстати, инклудить .cpp-файлы я не советовал - сказал лишь что это возможно. А вот объявление нескольких классов в одном хедере и глобальные переменные - это советовал (описал как это делать). |
| Автор: borisbn 19.1.2011, 10:43 | ||
у новичков часто строится такая цепочка "возможно-так можно делать-так делают-так делают все-так нужно делать". Возможно ещё создавать функции на 2000 строк, делать такие конструкции ( пару лет назад достался проект по наследству, так я этот кусок сохранил, чтобы показывать как не надо делать )
и т.п., однако сообщать новичкам, что это всё возможно IMHO не стОит. Теперь о нескольких классах в одном хедере. Если класс настолько маленький и простенький, что для него не нужен отдельный файл, то его и создавать не нужно, скорее всего можно обойтись "свободными" функциями ( исключения составляют лишь функторы, но с приходом стандарта 0x00 и в них не будет особой необходимости ). Если же класс сложнее, чем пару функций get/set и если он создан для того, чтобы повторно его использовать ( и не в одном проекте, а в многих последующих ), то его просто необходимо вынести в отдельный файл. Так ведь даже удобнее. А глобальные переменные ( не константы ) - вообще зло, т.к. сегодня тебе хватает производительности, а завтра - нет, и приходится распараллеливать программу, делать несколько потоков... далее, думаю, понятно. P.S. Кажется TS уже забыл о нас, и всё это превращается во флуд. Предлагаю засим закончить |
| Автор: efpies 19.1.2011, 11:09 | ||
| TS мониторит тему потихоньку) Спасибо за советы. Почему у меня возник такой вопрос: вот пишут, что
Ну хорошо, сейчас у меня каждый класс в отдельном h-файле (каждый строк на 200, потому всё в один файл пихать - это будет кровавое месиво, тем более каждый класс считай как модуль), который имеет имя такое, как класс. Насколько это верно с учётом этой цитаты?) Да и вообще при каком-либо изменении какого-либо класса нужно сначала сделать build, а только потом run (видимо, сказывается специфика h-файлов), что сказывается на отладке не лучшим образом. Вот мне и подумалось, а не в cpp-файлы ли помещать их описание и т.д.? Ну и соответственно, что будет являться "хорошим стилем". |
| Автор: borisbn 19.1.2011, 11:54 | ||||||||
| efpies, самая общепринятая практика следующая: в h-файлах класс описывается, а в cpp-файлах - определяется. Описание класса - есть интерфейс (договор, контракт) между тем, кто его создаёт и тем, кто его будет использовать. Определение класса - это уже не договор, а испольнение договора, причём только тем, кто его создаёт. При этом, если ты изменяешь договор между разработчиком класса и его потребителем, то тебе необходимо перекомпилировать и сам класс, и те исходники, которые его используют. Если же ты меняешь только ход исполнения договора, при этом контракт остаётся нетронутым, то достаточно только перекомпилировать исходник класса. А вообще без перекомпиляции при изменении в Си/Си++ никак. Это не скриптовый язык. Вот пример нормальной организации класса и его использования: SomeClass.h
SomeClass.cpp
SomeClassUsing.h (использование SomeClass)
SomeClassUsing.cpp (использование SomeClass)
|
| Автор: KaraKum 19.1.2011, 13:13 |
| http://www.fcenter.ru/forprint.shtml?online/articles/software/interview/7963 рисованные карандашом картинки могут быть интересны. |
| Автор: efpies 19.1.2011, 21:30 | ||
borisbn, KaraKum, спасибо за наглядные ответы!
Это понятно, просто либо компилятор сам перекомилирует, либо я это делаю насильно. Это я имел в виду. Возник такой вопрос: к примеру, у меня есть 2 h-файла и 2 соответствующих cpp-файла (то есть, как Вы описали). Что в конечном итоге включается в использующий файл? Ну я имею в виду, что если мы включим в main.cpp SomeClassUsing.h, он же не будет знать о существовании SomeClassUsing.cpp, поскольку в заголовочном файле он никак не затрагивается. Или компилятор будет знать "по умолчанию" о существовании cpp-файла, поскольку имена одинаковые? |
| Автор: KaraKum 19.1.2011, 22:59 |
| Я уже даже боюсь отвечать, ато теперь не из автобуса "по рации" сообщат о некомпетентности, а в квартиру постучат и конвоируют "Превращение исходного кода в исполняемый" выполняется в два этапа: компиляция и, затем, компоновка (есть ещё препроцессор, но его относят к компиляции). В процессе компиляции формируется никак не связанный набор функций (в том числе и main()). Процесс компиляции в местах вызовов функций лишь проверяет факт их описания (существования) и, конечно, вызываемые параметры. При компоновке на места вызовов функций располагаются уже настоящие адреса расположения этих функций (их исполняемые варианты, созданные в процессе компиляции) - если компоновщик не находит какую-либо скомпилированную функцию, то будет ругаться и откажется компилировать. Поэтому если не указать .cpp файлов, то процесс компиляции может завершиться успешно, но компоновка нет. |
| Автор: mes 19.1.2011, 23:06 | ||
каждый .cpp файл компилится отдельно, но вместе со всеми включениями.. для того чтоб знать какие вы файлы хотите скомпилить и с чем их собрать используют make-файл.. IDE позволяет использовать его неявно для пользователя.. |
| Автор: KuMa1104 20.1.2011, 00:57 | ||||
Всем здрасте)))
А можно в .cpp файле добавить новые функции члены класса?
У меня компилятор визжит на такое, это вообще возможно? Добавлено через 1 минуту и 40 секунд private я имею в виду конечно. |
| Автор: borisbn 20.1.2011, 09:07 |
Вот тебе и ответ. Нет. К сожалению. |
| Автор: KuMa1104 20.1.2011, 23:20 | ||
Да пожалуй действительно железный довод) |
| Автор: bsa 20.1.2011, 23:34 |
| KuMa1104, а почему нельзя и в объявление класса добавить? |
| Автор: mes 20.1.2011, 23:51 | ||
т.е. определение (definition) класса "происходит" в хидере.. |
| Автор: borisbn 21.1.2011, 00:16 | ||
перевод definition: определение четкость описание дефиниция толкование резкость точность ясность Т.о. вопрос только в переводе, а если прочитать полностью
а не выдёргивать, то получается вполне стройно |
| Автор: mes 21.1.2011, 00:21 | ||
нет, вопрос в устоявшихся терминах Добавлено @ 00:24
|
| Автор: borisbn 21.1.2011, 07:19 |
| mes, ОК, сдаюсь. Не силён в терминологии Скажи, пожалуйста, как в устоявшемся виде называют интерфейс класса, а как - реализацию его функций ? |
| Автор: mes 21.1.2011, 11:57 |
| class Class; // объявление класса class Class { void f(); // объявление (прототипа) функции-члена }; // определение класса void Class::f() {} // определение функции-члена |
| Автор: borisbn 21.1.2011, 15:15 | ||
| mes, я прекрасно понимаю, что о терминологии (тем более о переводе) можно спорить долго и, практически всегда, безрезультатно, однако... чесслово, всегда считал это предварительным объявлением (или предобъявлением) ... а по поводу http://citforum.ru/programming/cpp_march/cpp_065.shtml нашёл гениальную фразу
так что предлагаю ничью |
| Автор: mes 21.1.2011, 18:31 | ||||||
в данном случае спор не о словax "определение" и "описание".. а о том, что определение/описание класса не затрагивает определение функций-членов..
будет ли это объявление предварительным, зависит от того, известно ли уже это имя или нет..
сейчас почитаю.. |
| Автор: baldina 21.1.2011, 18:31 | ||||
| KaraKum, используешь "нарекание" с странном, не общеупотребительном значении. звучит забавно http://ru.wiktionary.org/wiki/%D0%BD%D0%B0%D1%80%D0%B5%D0%BA%D0%B0%D0%BD%D0%B8%D0%B5
о проектировании структуры кода, его организации и спрашивает ТС
efpies, в С/С++ используется т.н. раздельная компиляция, т.е. компилятор знает лишь о том файле, который компилирует в данный момент. содержимое включаемого файла в буквальном смысле подставляется в место директивы #include и компилируется результат. таким образом заголовочные файлы являются средством связи между файлами .сpp, при помощи них объявляются типы и переменные, определенные в другом файле. отдельный файл в C/C++ является не только единицей компиляции, но и средством структуризации программ (другие средства - функции, структуры и классы, пространства имен). К ним в общем случае применимы те же правила, что и к любым средствам структуризации: одна задача (тема, понятие) - один файл (класс, функция). интуитивно понять, как это лучше сделать, можно посмотрев на содержимое стандартных заголовков: cmath, ctime, vector, map, iostream, string. Если речь идет о классах, то один заголовок - один класс (плюс, возможно, классы, связанные с ним и только с ним непосредственно. например string и string_iterator). Конечно, в любом правиле бывают исключения, все должно быть по уму и без фанатизма. Для упрощения анализа и отладки, для сокращения времени компиляции число связей в проекте, в т.ч. связей между файлами, стараются уменьшить. пример borisbn вполне иллюстрирует основу такого подхода. Добавлено через 12 минут и 30 секунд mes, borisbn, не спорьте. в русскоязычной терминологии на эту тему небольшой бардак. объявление (declaration) - ввод идентификатора в текущую область видимости. при этом объявление может быть определением. а может и не быть, как, например, опережающее объявление. |
| Автор: mes 21.1.2011, 18:44 | ||||
о нет, такое читать не буду.. ну ну, пожалуйста.. не буду Вас разубеждать.. продолжайте читать Марченко.. Добавлено через 3 минуты и 12 секунд
как раз конфликта между "объявление" и "определение" нету.. есть спор какое из слов "определение" или "описание" подходит в качестве перевода для "definition" в некоторых случаях... |
| Автор: baldina 21.1.2011, 18:50 | ||
с этим borisbn вроде бы и не спорил. а насчет перевода... definition можно перевести как описание. если рядом нет declaration. |
| Автор: mes 21.1.2011, 19:06 | ||
Добавлено @ 19:08 да дело не в том как перевести это definition, a в том, к чему его применили |
| Автор: baldina 21.1.2011, 19:09 |
| ну ладно, ладно определение с реализацией попутали. С++ запутанный язык |
| Автор: efpies 22.1.2011, 02:40 | ||||
Всем спасибо за подсказки.
Правда просто объявить не получится, если SomeClassUsing должен наследовать SomeClass. Нужно подключать SomeClass.h |
| Автор: mes 22.1.2011, 12:15 | ||
для наследования нужно иметь полное определение наследуемого предка.. т.е. объявления недостаточно также полное определение нужно для создания/удаления, обращения к составляющим(членам), sizeof, и т.д.. объявления достаточно для использования ссылки или указателя.. |
| Автор: efpies 23.1.2011, 10:02 |
| Возникла такая проблема: некоторые методы считаются неописанными (unresolved external), их нужно описать именно в заголовочном файле. С чем это может быть связано? |
| Автор: efpies 23.1.2011, 10:18 |
| UPD: с inline-функциями такая штука |
| Автор: bsa 23.1.2011, 10:20 |
| efpies, почитай определение встраиваемых функций. Если лень, то в двух словах - тело inline функции должно быть в хидере. |
| Автор: baldina 24.1.2011, 12:23 |
efpies, тут главное не то, что тело inline функции в хидере, а что оно должно быть доступно в момент компиляции. в .h его помещают именно для этой цели. |
| Автор: semibug 24.1.2011, 15:00 |
| Хочу обратить внимание на фундамент - заголовочные файлы всего лишь программистское соглашение, компилятор (канонический) ничего про них не знает. Собственно компилирование начинается уже после обработки текста препроцессором, который поставит на место #include текст из соответствующего файла. Не важно какое у него будет расширение и имя, хоть cpp, хоть exe. Как то раз попалось даже такое извращение - вместо makefile с перечислением сырцов, компилировался один файл, содержащий #include всех остальных файлов проекта. Присоединяюсь к мнению гуру, один класс - один файл, и, imho, чем он меньше, тем лучше. |
| Автор: wildwolf 24.1.2011, 22:34 |
| я извиняюсь, ребят, помогите пожалуста.. сам изучал с++ .. а тут девушка с задачками подошла, не могу разобраться.. подскажите.. "Дано тело функции main(). Определить что будет выведено на экран в результате выполнения данного кода, и почему (дать пояснения к ответу). Код: int n=5; char * b="0"; printf("%u\n", open("f", O_RONLY)); close(fd); Примечание: Файла f не существует." и вторая- "Пояснить результаты работы программы #include<unistd.h> main(){ printf("один/n"); fork(); fork(); printf("два/n"); wait(); printf("три/n"); printf("четыре/n"); }" или хотя б подскажите где и скать и что за язык..спасибо заранее.. |
| Автор: baldina 25.1.2011, 10:28 |
| C++ изучал, говоришь? Добавлено через 11 минут и 42 секунды язык С. с небольшими (непринципиальными) исправлениями будет компилироваться как С++. первая программа выведет Очень Большое Число, соотв. максимальному беззнаковому целому на целевой системе. это потомучто open() возвращает int(-1), а спецификация формата вывода - беззнаковая. в 32-битной среде это 4294967295. вторая программа предназначена для исполнения в POSIX-совместимой среде. *nix например. вызов fork() порождает новый процесс (клон текущего) в точке вызова. wait () приостанавливает текущий процесс, пока один из порожденных процессов не завершится (или не ждет, если порожденных процессов нет). так что теперь можешь сам сообразить что она выведет. |
| Автор: borisbn 25.1.2011, 10:55 |
| baldina, "+1" надо бы http://forum.vingrad.ru/forum/topic-242693/view-all.html скопировать #inclyde опять же улыбнул |
| Автор: wildwolf 25.1.2011, 11:13 |
| спасиб огромное изучал, но только поверхносно.. "include" - опечатался=) |
| Автор: Ignat 25.1.2011, 11:23 |
Это, кстати, одна из моих любимых задачек. Выведет она не то, что ожидается =) Чтобы она вывела, что ожидается, нужно ставить flush() после каждого вывода. |
| Автор: wildwolf 25.1.2011, 11:29 |
| а что она выведет? если не секрет) |
| Автор: baldina 25.1.2011, 11:38 | ||
что-то мне подсказывает, что от девушки требуют "что ожидается"
интересно, результат приводится? |
| Автор: Ignat 25.1.2011, 11:41 | ||||
В зависимости от компилятора и платформы:
либо, если сброс буфера будет происходить после каждого вывода
На практике, со вторым поведением сталкивался, кажется, в цигвине. |
| Автор: wildwolf 25.1.2011, 11:44 |
| спасибо!=) |
| Автор: Ignat 25.1.2011, 11:47 |
За что? ни тот, ни другой ответ не гарантирован =) |
| Автор: wildwolf 25.1.2011, 11:50 |
| спасибо, что хоть чем-то помогли.. а там случайно не "один один один два три четыре" ? |
| Автор: Ignat 25.1.2011, 11:57 |
Ну это должна быть очень большая случайность, разве что сидеть на изготовку с kill -2, да и то снайпер должен быть знатный. |
| Автор: baldina 25.1.2011, 12:22 |
| wildwolf, Ignat, "один" выведется только один раз. в корневом процессе. |
| Автор: Ignat 25.1.2011, 12:29 |
baldina, на чем проверял? В своё время проверял на нескольких системах. Результат - процесс форкается вместе с буфферами, следовательно, "один" выведется 4 раза. |
| Автор: baldina 25.1.2011, 12:32 | ||
не успеет. сейчас очень быстрые процессоры ;-) Добавлено через 14 минут и 46 секунд не проверял. |
| Автор: baldina 25.1.2011, 12:48 | ||||
проверил
|
| Автор: Ignat 25.1.2011, 12:55 | ||
| Тоже проверил Linux 2.6.32-27-generic gcc 4.4.3
Таки каждый \n приводит к flush. Убираем LF и получаем 1234123412341234. Добавлено через 57 секунд Сыплю на голову мусор (пепла нет), про опустошение буффера десятым символом забыл напрочь. |
| Автор: wildwolf 27.1.2011, 21:43 |
| спасиб ребят, оч помогли!=) прокатило! |