Какое расширение обычно имеют файлы с текстом программ в visual c

Обновлено: 03.07.2024

Чем программы, код которых написан в файлах с расширением .с отличаются от программ, код которых написан в в файлах с расширением .срр?

некоторые компиляторы понимают это так: какое расширение - так и компилить. С/С++

Не совсем понятно.
В visual C++ и просто в С работают файлы с расширением как .с так и .срр?
Получается, что разница будет только для компилятора? Или не только?

Получается, что разница будет только для компилятора?
Чем программы, код которых написан в файлах с расширением .с отличаются от программ, код которых написан в в файлах с расширением .срр?
разумеется *.c воспринимается компилятором как прога на C
*.cpp воспринимается компилятором как прога на C++
Поскольку большинство C прог одновременно являются корректными прогами на C++, и C-шные заголовочные файлы обычно доступны для использования в C++ (хотя стандартные C-шные заголовки не обязаны быть доступны с теми же именами эти C-шные проги компилируются C++ компилятором.
Большинство C++ прог наоборот не будут компилироваться C компилятором.

бывают проги на с, которые не компилятся при изменении расширения на срр

если на заборе написать $%?, забор все равно останется забором
в таких случаях надо самому определиться, чем компилить: gcc или g++

1) У файлов нет никаких "расширений". термин некорректный, есть суффиксы у имен файлов.
2) Как и везде, суффикс обычно подсказывает, что же в том файле. Проги на си имеют суффикс "c", на с++ — обычно "C", "cpp", "cxx".

Некоторые проги на С++ тоже имеют суффикс "c". Например: "test.cc". Так что нефиг придираться к вполне устоявшимся терминам

бывают проги на с, которые не компилятся при изменении расширения на срр
разумеется
именно потому я написал "большинство", а не "все"
Проги на си имеют суффикс "c", на с++ — обычно "C", "cpp", "cxx".
Проги на си имеют суффикс ".c", на с++ — обычно ".C", ".cpp", ".cxx", ".cc".

Для всех сред, которые хотят что-нибудь понять, исходя из суффикса файла.
---
. Я работаю антинаучным аферистом.

Погодь, если я не ошибаюсь, любая прога на C одновременно является корректной прогой на C++, т.ч. должна компилится при обоих суффиксах.

> Погодь, если я не ошибаюсь, любая прога на C одновременно является корректной прогой на C++, т.ч. должна компилится при обоих суффиксах.
в общем случае - верно, но есть исключения.
например, такая программа на C должна компилиться, а не C++ - нет (т.к. используется ключевое слово)

В С есть пространства имен?

когда последний раз я этим интересовался - не было

угу, тормознул, не хотелось семантику printf-а вспоминать.

>> Погодь, если я не ошибаюсь, любая прога на C одновременно является корректной прогой на C++, т.ч. должна компилится при обоих суффиксах.
> в общем случае - верно, но есть исключения.
есть и другие исключения, в обычном С не так часто требуется явное приведение типов. Например, код

корректен в C, но ошибочен в C++, и с расширением cpp компилироваться не будет. И такое встречается во вполне реальных программах.

По мере того, как программы становятся больше (и используют больше файлов), становится всё более утомительным давать предварительные объявления каждой функции, которую вы хотите использовать, и которая определена в другом файле. Было бы неплохо, если бы вы могли поместить все свои предварительные объявления в одно место, а затем импортировать их, когда они вам понадобятся?

Исходные файлы кода C++ (с расширением .cpp ) – это не единственные файлы, которые обычно встречаются в программах на C++. Другой тип файлов – это заголовочный файл (иногда просто заголовок). Заголовочные файлы обычно имеют расширение .h , но иногда вы можете встретить их с расширением .hpp или вообще без расширения. Основная цель заголовочного файла – распространять объявления в исходные файлы кода.

Ключевой момент

Заголовочные файлы позволяют нам размещать объявления в одном месте, а затем импортировать их туда, где они нам нужны. Это может сэкономить много времени при наборе текста в проектах из нескольких файлов.

Использование заголовочных файлов стандартной библиотеки

Рассмотрим следующую программу:

Эта программа печатает « Hello, world! » в консоль с помощью std::cout . Однако эта программа никогда не предоставляла определение или объявление для std::cout , поэтому как компилятор узнает, что такое std::cout ?

Ключевой момент

Когда дело доходит до функций и переменных, стоит помнить, что заголовочные файлы обычно содержат только объявления функций и переменных, а не их определения (в противном случае может произойти нарушение правила одного определения). std::cout объявлен в заголовке iostream, но определен как часть стандартной библиотеки C++, которая автоматически подключается к вашей программе на этапе линкера.

Рисунок 1 Диаграмма процесса сборки

Рисунок 1 – Диаграмма процесса сборки

Лучшая практика

Заголовочные файлы обычно не должны содержать определений функций и переменных, чтобы не нарушать правило одного определения. Исключение сделано для символьных констант (которые мы рассмотрим в уроке «4.14 – const, constexpr и символьные константы»).

Написание собственных заголовочных файлов

А теперь вернемся к примеру, который мы обсуждали в предыдущем уроке. Когда мы закончили, у нас было два файла, add.cpp и main.cpp , которые выглядели так:

(Если вы воссоздаете этот пример с нуля, не забудьте добавить add.cpp в свой проект, чтобы он компилировался).

В этом примере мы использовали предварительное объявление, чтобы при компиляции main.cpp компилятор знал, что такое идентификатор add . Как упоминалось ранее, добавление предварительных объявлений для каждой функции, которую вы хотите использовать, и которая находится в другом файле, вручную может быстро стать утомительным.

Давайте напишем заголовочный файл, чтобы избавиться от этого бремени. Написать заголовочный файл на удивление легко, поскольку файлы заголовков состоят только из двух частей:

  1. защита заголовка, о которой мы поговорим более подробно в следующем уроке («2.11 – Защита заголовков»);
  2. фактическое содержимое файла заголовка, которое должно быть предварительными объявлениями для всех идентификаторов, которые мы хотим, чтобы другие файлы могли видеть.

Добавление заголовочного файла в проект работает аналогично добавлению исходного файла (рассматривается в уроке «2.7 – Программы с несколькими файлами исходного кода»). Если вы используете IDE, выполните такие же действия и при появлении запроса выберите Файл заголовка (или C/C++ header) вместо Файла С++ (или C/C++ source). Если вы используете командную строку, просто создайте новый файл в своем любимом редакторе.

Лучшая практика

При именовании файлов заголовков используйте расширение .h .

Заголовочные файлы часто идут в паре с файлами исходного кода, при этом заголовочный файл предоставляет предварительные объявления для соответствующего исходного файла. Поскольку наш заголовочный файл будет содержать предварительное объявление для функций, определенных в add.cpp , мы назовем наш новый заголовочный файл add.h .

Лучшая практика

Если заголовочный файл идет в паре с файлом исходного кода (например, add.h с add.cpp ), они оба должны иметь одинаковое базовое имя ( add ).

Вот наш завершенный заголовочный файл:

Следовательно, наша программа будет правильно компилироваться и компоноваться.

Рисунок 2 Диаграмма процесса сборки

Рисунок 2 – Диаграмма процесса сборки

Включение заголовочного файла в соответствующий исходный файл

Позже вы увидите, что большинство исходных файлов включают свой соответствующий заголовочный файл, даже если он им не нужен. Зачем?

Включение заголовочного файла в исходный файл увеличивает прямую совместимость. Очень вероятно, что в будущем вы добавите больше функций или измените существующие таким образом, что им нужно будет знать о существовании друг друга.

Когда мы углубимся в изучение стандартной библиотеки, вы будете включать множество заголовочных файлов библиотек. Если вам потребовалось включение в заголовочном файле, оно, вероятно, понадобилось вам для объявления функции. Это означает, что вам также потребуется такое же включение в исходный файл. Это приведет к тому, что в исходном файле у вас будет копия включений заголовочного файла. Включив заголовочный файл в исходный файл, исходный файл получит доступ ко всему, к чему имел доступ заголовочный файл.

При разработке библиотеки включение заголовочного файла в исходный файл может даже помочь в раннем обнаружении ошибок.

Лучшая практика

При написании исходного файла включите в него соответствующий заголовочный файл (если он существует), даже если он вам пока не нужен.

Поиск и устранение проблем

Если вы получаете ошибку компилятора, указывающую, что add.h не найден, убедитесь, что файл действительно называется add.h . В зависимости от того, как вы его создали и назвали, возможно, файл может иметь имя вроде add (без расширения), add.h.txt или add.hpp . Также убедитесь, что он находится в том же каталоге, что и остальные исходные файлы.

Угловые скобки и двойные кавычки

Вам, наверное, интересно, почему мы используем угловые скобки для iostream и двойные кавычки для add.h . Возможно, что заголовочные файлы с таким же именем могут существовать в нескольких каталогах. Использование угловых скобок и двойных кавычек помогает компилятору понять, где ему следует искать заголовочные файлы.

Когда мы используем угловые скобки, мы сообщаем препроцессору, что это заголовочный файл, который мы не писали сами. Компилятор будет искать заголовок только в каталогах, указанных в каталогах включаемых файлов (include directories). Каталоги включаемых файлов настраиваются как часть вашего проекта / настроек IDE / настроек компилятора и обычно по умолчанию используются для каталогов, содержащих заголовочные файлы, которые поставляются с вашим компилятором и/или ОС. Компилятор не будет искать заголовочный файл в каталоге исходного кода вашего проекта.

Когда мы используем двойные кавычки, мы сообщаем препроцессору, что это заголовочный файл, который написали мы. Компилятор сначала будет искать этот заголовочный файл в текущем каталоге. Если он не сможет найти там подходящий заголовочный файл, он будет искать его в каталогах включаемых файлов.

Правило

Используйте двойные кавычки, чтобы включать заголовочные файлы, которые написали вы или которые, как ожидается, будут найдены в текущем каталоге. Угловые скобки используйте, чтобы включать заголовочные файлы, которые поставляются с вашим компилятором, ОС или сторонними библиотеками, которые вы установили в другом месте своей системы.

Почему у iostream нет расширения .h ?

Другой часто задаваемый вопрос: «Почему iostream (или любой другой заголовочный файл стандартной библиотеки) не имеет расширения .h ?». Ответ заключается в том, что iostream.h – это другой заголовочный файл, отличающийся от iostream ! Для объяснения требуется небольшой урок истории.

Когда C++ был только создан, все файлы в стандартной библиотеке оканчивались расширением .h . Жизнь была последовательной, и это было хорошо. Исходные версии cout и cin были объявлены в iostream.h . Когда комитет ANSI стандартизировал язык, они решили переместить все функции стандартной библиотеки в пространство имен std , чтобы избежать конфликтов имен с пользовательскими идентификаторами. Однако это представляло проблему: если бы они переместили всю функциональность в пространство имен std , ни одна из старых программ (включая iostream.h ) больше не работала бы!

Кроме того, многие библиотеки, унаследованные от C, которые всё еще используются в C++, получили префикс c (например, stdlib.h стал cstdlib ). Функциональные возможности этих библиотек также были перенесены в пространство имен std , чтобы избежать конфликтов имен.

Лучшая практика

При включении заголовочного файла из стандартной библиотеки используйте версию без расширения (без .h ), если она существует. Пользовательские заголовочные файлы по-прежнему должны использовать расширение .h .

Включение заголовочных файлов из других каталогов

Другой распространенный вопрос связан с тем, как включать заголовочные файлы из других каталогов.

Хотя это будет компилироваться (при условии, что файлы существуют в этих относительных каталогах), обратная сторона этого подхода состоит в том, что он требует от вас отражения структуры каталогов в вашем коде. Если вы когда-нибудь обновите структуру каталогов, ваш код больше не будет работать.

Лучший способ – сообщить вашему компилятору или IDE, что у вас есть куча заголовочных файлов в каком-то другом месте, чтобы он смотрел туда, когда не может найти их в текущем каталоге. Обычно это можно сделать, установив путь включения (include path) или каталог поиска (search directory) в настройках проекта в IDE.

Для пользователей Visual Studio

Кликните правой кнопкой мыши на своем проекте в обозревателе решений и выберите Свойства (Properties), затем вкладку Каталоги VC++.(VC++ Directories). Здесь вы увидите строку с названием «Включаемые каталоги» (Include Directories). Добавьте каталоги, в которых компилятор должен искать дополнительные заголовочные файлы.

Для пользователей Code::Blocks

В Code:: Blocks перейдите в меню Project (Проект) и выберите Build Options (Параметры сборки), затем вкладку Search directories (Каталоги поиска). Добавьте каталоги, в которых компилятор должен искать дополнительные заголовочные файлы.

Для пользователей GCC/G++

Используя g++, вы можете использовать параметр -I , чтобы указать альтернативный каталог для включения.

Хороший момент в этом подходе заключается в том, что если вы когда-нибудь измените структуру каталогов, вам нужно будет изменить только одну настройку компилятора или IDE, а не каждый файл кода.

Заголовочные файлы могут включать другие заголовочные файлы

Содержимое этих транзитивных включений доступно для использования в вашем файле исходного кода. Однако не следует полагаться на содержимое заголовков, которые включены транзитивно. Реализация заголовочных файлов может со временем меняться или отличаться в разных системах. Если это произойдет, ваш код может компилироваться только на определенных системах или может компилироваться сейчас, но перестать в будущем. Этого легко избежать, явно включив все заголовочные файлы, необходимые для содержимого вашего файла исходного кода.

Лучшая практика

К сожалению, нет простого способа определить, полагается ли ваш файл кода случайно на содержимое заголовочного файла, который был включен другим заголовочным файлом.

Вопрос: Я не включил <someheader.h> , и моя программа всё равно работала! Почему?

Это один из наиболее часто задаваемых вопросов. Ответ: скорее всего, он работает, потому что вы включили какой-то другой заголовок (например, <iostream> ), который сам включает <someheader.h> . Несмотря на то, что ваша программа будет компилироваться, в соответствии с приведенными выше рекомендациями вам не следует полагаться на это. То, что компилируется у вас, может не компилироваться на машине друга.

Лучшая практика

Рекомендации по использованию заголовочных файлов

Вот еще несколько рекомендаций по созданию и использованию заголовочных файлов.

Когда мы пишем программу на C/C++ в одном файле, проблем обычно не возникает. Они ждут того момента, когда исходный текст необходимо разбить на несколько файлов. В этой статье я постараюсь рассказать, как это сделать правильно.

Термины

Пара слов о терминах. Ниже даны определения терминов так, как они используются в данной статье. В некоторых случаях эти определения имеют более узкий смысл, чем общепринятые. Это сделано намеренно, дабы не утонуть в деталях и лишних уточнениях.

Исходный код — программа, написанная на языке программирования, в текстовом формате. А также текстовый файл, содержащий исходный код.

Компилятор — программа, выполняющая компиляцию (неожиданно! не правда ли?). На данный момент среди начинающих наиболее популярными компиляторами C/C++ являются GNU g++ (и его порты под различные ОС) и MS Visual Studio C++ различных версий. Подробнее см. в Википедии статьи: Компиляторы, Компиляторы C++.

Компиляция — преобразование исходного кода в объектный модуль.

Объектный модуль — двоичный файл, который содержит в себе особым образом подготовленный исполняемый код, который может быть объединён с другими объектными файлами при помощи редактора связей (компоновщика) для получения готового исполняемого модуля, либо библиотеки. (подробности)

Компоновщик (редактор связей, линкер, сборщик) — это программа, которая производит компоновку («линковку», «сборку»): принимает на вход один или несколько объектных модулей и собирает по ним исполнимый модуль. (подробности)

Исполняемый модуль (исполняемый файл) — файл, который может быть запущен на исполнение процессором под управлением операционной системы. (подробности)

Препроцессор — программа для обработки текста. Может существовать как отдельная программа, так и быть интегрированной в компилятор. В любом случае, входные и выходные данные для препроцессора имеют текстовый формат. Препроцессор преобразует текст в соответствии с директивами препроцессора. Если текст не содержит директив препроцессора, то текст остаётся без изменений. Подробнее см. в Википедии: Препроцессор и Препроцессор Си.

IDE (англ. Integrated Development Environment) — интегрированная среда разработки. Программа (или комплекс программ), предназначенных для упрощения написания исходного кода, отладки, управления проектом, установки параметров компилятора, линкера, отладчика. Важно не путать IDE и компилятор. Как правило, компилятор самодостаточен. В состав IDE компилятор может не входить. С другой стороны с некоторыми IDE могут быть использованы различные компиляторы. (подробности)

Объявление — описание некой сущности: сигнатура функции, определение типа, описание внешней переменной, шаблон и т.п. Объявление уведомляет компилятор о её существовании и свойствах.

Определение — реализация некой сущности: переменная, функция, метод класса и т.п. При обработке определения компилятор генерирует информацию для объектного модуля: исполняемый код, резервирование памяти под переменную и т.д.

От исходного кода к исполняемому модулю

Создание исполняемого файла издавна производилось в три этапа: (1) обработка исходного кода препроцессором, (2) компиляция в объектный код и (3) компоновка объектных модулей, включая модули из объектных библиотек, в исполняемый файл. Это классическая схема для компилируемых языков. (Сейчас уже используются и другие схемы.)

Часто компиляцией программы называют весь процесс преобразования исходного кода в исполняемы модуль. Что неправильно. Обратите внимание, что в IDE этот процесс называется построение (build) проекта.

IDE обычно скрывают три отдельных этапа создания исполняемого модуля. Они проявляются только в тех случаях, когда на этапе препроцессинга или компоновки обнаруживаются ошибки.

Итак, допустим, у нас есть программа на C++ «Hello, World!»:

Затем результат работы препроцессора передаётся компилятору. Компилятор производит весь положенный комплекс работ: от синтаксического разбора и поиска ошибок до создания объектного файла (понятно, что если имеются синтаксические ошибки, то объектный файл не создаётся). В объектном файле обычно имеется таблица внешних ссылок — некая таблица, в которой, в частности, перечислены имена подпрограмм, которые используются в объектном модуле, но код которых отсутствует в данном объектном модуле. Эти подпрограммы внешние по отношению к модулю.

Исходный код, который может быть откомпилирован, называется единицей компиляции. Наша программа содержит одну единицу компиляции.

Что бы получить нормальный исполняемый модуль, необходимо «разрешить» внешние ссылки. Т.е. добавить в исполняемый модуль код отсутствующих подпрограмм и настроить соответствующим образом все ссылки на этот код. Этим занимается компоновщик. Он анализирует таблицу внешних ссылок объектного модуля, ищет в объектных библиотеках недостающие модули, копирует их в исполняемый модуль и настраивает ссылки. После этого исполняемый модуль готов.

Библиотека (объектная библиотека) — это набор откомпилированных подпрограмм, собранных в единый файл определённой структуры. Подключение библиотеки происходит на этапе компоновки исполняемого файла из объектных файлов (т.е. из тех файлов, которые получаются в результате компиляции исходного текста программы).

Необходимые объектные библиотеки входят в комплект поставки компилятора. В комплект поставки библиотек (любых) входит набор заголовочных файлов, которые содержат объявления, необходимые компилятору.

Если исходный код программы разделён на несколько файлов, то процесс компиляции и сборки происходит аналогично. Сначала все единицы компиляции по отдельности компилируются, а затем компоновщик собирает полученные объектные модули (с подключением библиотек) в исполняемый файл. Собственно, этот процесс и называется раздельной компиляцией.

Разделение текста программы на модули

Разделение исходного текста программы на несколько файлов становится необходимым по многим причинам:

  1. С большим текстом просто неудобно работать.
  2. Разделение программы на отдельные модули, которые решают конкретные подзадачи.
  3. Разделение программы на отдельные модули, с целью повторного использования этих модулей в других программах.
  4. Разделение интерфейса и реализации.

Я намеренно использовал слово «модуль», поскольку модулем может быть как класс, так и набор функций — вопрос используемой технологии программирования.

Как только мы решаем разделить исходный текст программы на несколько файлов, возникают две проблемы:

  1. Необходимо от простой компиляции программы перейти к раздельной. Для этого надо внести соответствующие изменения либо в последовательность действий при построении приложения вручную, либо внести изменения в командные или make-файлы, автоматизирующие процесс построения, либо внести изменения в проект IDE.
  2. Необходимо решить каким образом разбить текст программы на отдельные файлы.

Первая проблема — чисто техническая. Она решается чтением руководств по компилятору и/или линкеру, утилите make или IDE. В самом худшем случае просто придётся проштудировать все эти руководства. Поэтому на решении этой проблемы мы останавливаться не будем.

Вторая проблема — требует гораздо более творческого подхода. Хотя и здесь существуют определённые рекомендации, несоблюдение которых приводит либо к невозможности собрать проект, либо к трудностям в дальнейшем развитии проекта.

Во-первых, нужно определить какие части программы выделить в отдельные модули. Что бы это получилось просто и естественно, программа должна быть правильно спроектирована. Как правильно спроектировать программу? — на эту тему написано много больших и правильных книг. Обязательно поищите и почитайте книги по методологии программирования — это очень полезно. А в качестве краткой рекомендации можно сказать: вся программа должна состоять из слабо связанных фрагментов. Тогда каждый такой фрагмент может быть естественным образом преобразован в отдельный модуль (единицу компиляции). Обратите внимание, что под «фрагментом» подразумевается не просто произвольный кусок кода, а функция, или группа логически связанных функций, или класс, или несколько тесно взаимодействующих классов.

Во-вторых, нужно определить интерфейсы для модулей. Здесь есть вполне чёткие правила.

Интерфейс и реализация

Когда часть программы выделяется в модуль (единицу компиляции), остальной части программы (а если быть точным, то компилятору, который будет обрабатывать остальную часть программы) надо каким-то образом объяснить что имеется в этом модуле. Для этого служат заголовочные файлы.

Таким образом, модуль состоит из двух файлов: заголовочного (интерфейс) и файла реализации.

Заголовочный файл, как правило, имеет расширение .h или .hpp, а файл реализации — .cpp для программ на C++ и .c, для программ на языке C. (Хотя в STL включаемые файлы вообще без расширений, но, по сути, они являются заголовочными файлами.)

Заголовочный файл должен содержать все объявления, которые должны быть видны снаружи. Объявления, которые не должны быть видны снаружи, делаются в файле реализации.

Что может быть в заголовочном файле

Правило 1. Заголовочный файл может содержать только объявления. Заголовочный файл не должен содержать определения.

То есть, при обработке содержимого заголовочного файла компилятор не должен генерировать информацию для объектного модуля.

Единственным «исключением» из этого правила является определение метода в объявлении класса. Но по стандарту языка, если метод определён в объявлении класса, то для этого метода используется инлайновая подстановка. Поэтому, такое объявление не порождает исполняемого кода — код будет генерироваться компилятором только при вызове этого метода.

Аналогичная ситуация и с объявлением переменных-членов класса: код будет порождаться при создании экземпляра этого класса.

Правило 2. Заголовочный файл должен иметь механизм защиты от повторного включения.

Защита от повторного включения реализуется директивами препроцессора:

Заголовочный файл сам по себе не является единицей компиляции.

Что может быть в файле реализации

Файл реализации может содержать как определения, так и объявления. Объявления, сделанные в файле реализации, будут лексически локальны для этого файла. Т.е. будут действовать только для этой единицы компиляции.

Правило 3. В файле реализации должна быть директива включения соответствующего заголовочного файла.

Понятно, что объявления, которые видны снаружи модуля, должны быть также доступны и внутри.

Правило также гарантирует соответствие между описанием и реализацией. При несовпадении, допустим, сигнатуры функции в объявлении и определении компилятор выдаст ошибку.

Правило 4. В файле реализации не должно быть объявлений, дублирующих объявления в соответствующем заголовочном файле.

При выполнении Правила 3, нарушение Правила 4 приведёт к ошибкам компиляции.

Практический пример

Допустим, у нас имеется следующая программа:

main.cpp

Эта программа не является образцом для подражания, поскольку некоторые моменты идеологически неправильны, но, во-первых, ситуации бывают разные, а во-вторых, для демонстрации эта программа подходит очень неплохо.

Итак, что у нас имеется?

  1. глобальная константа cint , которая используется и в классе, и в main ;
  2. глобальная переменная global_var , которая используется в функциях func1 , func2 и main ;
  3. глобальная переменная module_var , которая используется только в функциях func1 и func2 ;
  4. функции func1 и func2 ;
  5. класс CClass ;
  6. функция main .

Вроде вырисовываются три единицы компиляции: (1) функция main , (2) класс CClass и (3) функции func1 и func2 с глобальной переменной module_var , которая используется только в них.

Не совсем понятно, что делать с глобальной константой cint и глобальной переменной global_var . Первая тяготеет к классу CClass , вторая — к функциям func1 и func2 . Однако предположим, что планируется и эту константу, и эту переменную использовать ещё в каких-то, пока не написанных, модулях программы. Поэтому прибавится ещё одна единица компиляции.

Теперь пробуем разделить программу на модули.

Сначала, как наиболее связанные сущности (используются во многих местах программы), выносим глобальную константу cint и глобальную переменную global_var в отдельную единицу компиляции.

globals.h

globals.cpp

Обратите внимание, что глобальная переменная в заголовочном файле имеет спецификатор extern . При этом получается объявление переменной, а не её определение. Такое описание означает, что где-то существует переменная с таким именем и указанным типом. А определение этой переменной (с инициализацией) помещено в файл реализации. Константа описана в заголовочном файле.

С объявлением констант в заголовочном файле существует одна тонкость. Если константа тривиального типа, то её можно объявить в заголовочном файле. В противном случае она должна быть определена в файле реализации, а в заголовочном файле должно быть её объявление (аналогично, как для переменной). «Тривиальность» типа зависит от стандарта (см. описание того стандарта, который используется для написания программы).

Также обратите внимание (1) на защиту от повторного включения заголовочного файла и (2) на включение заголовочного файла в файле реализации.

Затем выносим в отдельный модуль функции func1 и func2 с глобальной переменной module_var . Получаем ещё два файла:

funcs.h

funcs.cpp

Поскольку переменная module_var используется только этими двумя функциями, её объявление в заголовочном файле отсутствует. Из этого модуля «на экспорт» идут только две функции.

Наконец выносим в отдельный модуль класс CClass :

CClass.h

CClass.cpp

Обратите внимание на следующие моменты.

(1) Из объявления класса убрали определения тел функций (методов). Это сделано по идеологическим причинам: интерфейс и реализация должны быть разделены (для возможности изменения реализации без изменения интерфейса). Если впоследствии будет необходимость сделать какие-то методы инлайновыми, это всегда можно сделать с помощью спецификатора.

(2) Класс имеет статический член класса. Т.е. для всех экземпляров класса эта переменная будет общей. Её инициализация выполняется не в конструкторе, а в глобальной области модуля.

Классы практически всегда выделяются в отдельные единицы компиляции.

В файле main.cpp оставляем только функцию main . И добавляем необходимые директивы включения заголовочных файлов.

main.cpp

Последний шаг: необходимо изменить «проект» построения программы так, что бы он отражал изменившуюся структуру файлов исходного кода. Детали этого шага зависят от используемой технологии построения программы и используемого ПО. Но в любом случае сначала должны быть откомпилированы четыре единицы компиляции (четыре cpp-файла), а затем полученные объектные файлы должны быть обработаны компоновщиком для получения исполняемого файла.

Типичные ошибки

Ошибка 1. Определение в заголовочном файле.

Эта ошибка в некоторых случаях может себя не проявлять. Например, когда заголовочный файл с этой ошибкой включается только один раз. Но как только этот заголовочный файл будет включён более одного раза, получим либо ошибку компиляции «многократное определение символа . », либо ошибку компоновщика аналогичного содержания, если второе включение было сделано в другой единице компиляции.

Ошибка 2. Отсутствие защиты от повторного включения заголовочного файла.

Тоже проявляет себя при определённых обстоятельствах. Может вызывать ошибку компиляции «многократное определение символа . ».

Ошибка 3. Несовпадение объявления в заголовочном файле и определения в файле реализации.

Обычно возникает в процессе редактирования исходного кода, когда в файл реализации вносятся изменения, а про заголовочный файл забывают.

Если необходимый заголовочный файл не включён, то все сущности, которые в нём объявлены, останутся неизвестными компилятору. Вызывает ошибку компиляции «не определён символ . ».

Ошибка 5. Отсутствие необходимого модуля в проекте построения программы.

Ошибка 6. Зависимость от порядка включения заголовочных файлов.

Не совсем ошибка, но таких ситуаций следует избегать. Обычно сигнализирует либо об ошибках в проектировании программы, либо об ошибках при разделении исходного кода на модули.

Заключение

В рамках небольшой статьи невозможно рассмотреть все случаи, возникающие при раздельной компиляции. Бывают ситуации, когда разделение программы или большого модуля на более мелкие кажется невозможным. Обычно это бывает, когда программа плохо спроектирована (в данном случае, части кода имеют сильные взаимные связи). Конечно, можно приложить дополнительные усилия и всё-таки разделить код на модули (или оставить как есть), но эту мозговую энергию лучше потратить более эффективно: на изменение структуры программы. Это принесёт в дальнейшем гораздо большие дивиденды, чем просто силовое решение.

Теперь в поле редактора Name (в нижней части окна) следует задать имя нового файла и указать расширение ".с". Например, main.c. Имя файла может быть достаточно произвольным, но имеется негласное соглашение, что имя файла должно отражать его назначение и логически описывать исходный код, который в нем содержится. В проекте, состоящем из нескольких файлов, имеет смысл выделить файл , содержащий главную функцию программы, с которой она начнет выполняться. В данном пособии такому файлу мы будем задавать имя main.c, где расширение .с указывает на то, что этот файл содержит исходный код на языке С, и он будет транслироваться соответствующим компилятором. Программам на языке С принято давать расширение .с. После задания имени файла в поле редактора Name, получим форму, показанную на рис. 1.10.

Задание имени файла, подключаемому к проекту


Рис. 1.10. Задание имени файла, подключаемому к проекту

Затем следует нажать кнопку Add. Вид среды Visual Studio после добавления первого файла к проекту показан на рис. 1.11. Добавленный файл отображается в дереве Solution Explorer под узлом Source Files (файлы с исходным кодом), и для него автоматически открывается редактор.

Подключение файла проекта

На рис. 1.11 в левой панели в папке Solution Explorer отображаются файлы, включенные в проект в папках. Приведем описание.

Папка Source Files предназначена для файлов с исходным кодом. В этой папке отображаются файлы с расширением .с.

Папка Header Files содержит заголовочные файлы с расширением .h.

Папка Resource Files содержит файлы ресурсов , например изображения и т. д.

Следующий шаг состоит в настройке проекта. Для этого в меню Project главного меню следует выбрать hello Properties (или с помощью последовательного нажатия клавиш Alt+F7). Пример обращения к этому пункту меню показан на рис. 1.12.

Обращение к странице свойств проекта

После того как произойдет открытие окна свойств проекта, следует обратиться (с левой стороны) к Configuration Properties. Появится ниспадающий список , который показан на рис. 1.13. Выполнить обращение к узлу General , и через него в правой панели выбрать Character Set, где установить свойство Use Multi-byte Character Set. Настройка Character Set (набор символов) позволяет выбрать, какая кодировка символов – ANSI или UNICODE – будет использована при компиляции программы. Для совместимости со стандартом C89 мы выбираем Use Multi-Byte Character Set. Это позволяет использовать многие привычные функции, например, функции по выводу информации на консоль .

Меню списка свойств проекта

После сделанного выбора, показанного на рис. 1.13, следует нажать кнопку Применить. Затем следует выбрать узел С/С++ и в ниспадающем меню выбрать пункт Code Generation, через который следует обратиться в правой части панели к закладке Enable C++ Exceptions, для которой установить No (запрещение исключений С++). Результат установки выбранного свойства показан на рис. 1.14. После произведенного выбора нажать кнопку Применить.

Далее в ниспадающем меню узла С/С++ необходимо выбрать пункт Language и через него обратиться в правую часть панели, где установить следующие свойства: свойство Disable Language Extensions (дополнительные языковые расширения фирмы Microsoft) в Yes (/Za), свойство Treat wchar_t as Built-in Type (рассматривать тип wchar_t как встроенный тип) установить в No (/Zc:wchar_t–), свойство Force Conformance in For Loop Scope (соответствие стандарту определения локальных переменных в операторе цикла for ) установить в Yes(/Zc:forScope) , свойство Enable Run-Time Type Info (разрешить информацию о типах во время выполнения ) установить в No (/GR–), свойство Open MP Support (разрешить расширение Open MP – используется при написании программ для многопроцессорных систем) установить в No(/openmp–).

Результат выполнения этих действий показан на рис. 1.15.

После выполнения указанных действий следует нажать клавишу Применить. Далее в ниспадающем списке узла С/С++ следует выбрать пункт Advanced и в правой панели изменить свойство Compile As в свойство компиляции языка С, т.е. Compile as C Code (/TC). Результат установки компилятора языка С показан на рис. 1.16.

Результат выбора режима компиляции языка С


Рис. 1.16. Результат выбора режима компиляции языка С

После нажатия клавиш Применить и ОК сначала откроется подготовленный проект с пустым полем редактора кода, в котором можно начать писать программы. В этом редакторе наберем программу, выводящую традиционное приветствие "Hello World". Для компиляции созданной программы можно обратиться в меню Build, или, например, набрать клавиши Ctr +F7. В случае успешной компиляции получим следующую экранную форму, показанную на рис. 1.17.

Успешно откомпилированная первая программа на языке С


Рис. 1.17. Успешно откомпилированная первая программа на языке С

Для приведенного кода программы запуск на ее исполнение из окна редактора в Visual Studio 2010 можно нажать клавишу F5. рис. 1.18 показан результат исполнения первой программы.

Читайте также: