Многофайловая программа это что

Урок №20. Многофайловые программы

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

Многофайловые проекты в Visual Studio

В Visual Studio щелкните правой кнопкой мыши по имени вашего проекта в «Обозревателе решений» , затем «Добавить» > «Создать элемент. » :

Во всплывающем диалоговом окне выберите тип файла, укажите его имя, расположение, а затем нажмите «Добавить» :

Также вы можете добавлять файлы к вашему проекту через «Проект» > «Добавить новый элемент. » :

Многофайловые проекты в Code::Blocks

В Code::Blocks перейдите в «File» > «New» > «File. » :

Затем выберите «C/C++ source» и нажмите «Go» :

Язык Си с нуля — Урок 47 — Многофайловые проекты. Создание и подключение. Заголовочные файлы.

Затем «Next» (этого окна может и не быть):

Затем «C++» и опять «Next» :

Затем укажите имя нового файла (не забудьте расширение .cpp) и его расположение (нажмите на троеточие и выберите путь). Убедитесь, что поставлены все три галочки (они отвечают за конфигурации сборки ). Затем нажмите «Finish» :

Готово! Файл добавлен.

Многофайловые проекты в GCC/G++

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

g++ main.cpp add.cpp -o main

(где main.cpp и add.cpp — это имена файлов с кодом, а main — это имя файла-результата)

Пример многофайловой программы

Рассмотрим следующую программу, которая состоит из двух файлов.

int add ( int x , int y )

return x + y ;

Попробуйте запустить эту программу. Она не скомпилируется, вы получите следующую ошибку:

add: идентификатор не найден

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

Тем не менее, в данном случае, мы хотим, чтобы main.cpp знал (и использовал) функцию аdd(), которая находится в add.cpp. Для предоставления доступа main.cpp к функциям add.cpp, нам нужно использовать предварительное объявление :

int add ( int x , int y ) ; // это нужно для того, чтобы main.cpp знал, что функция add() определена в другом месте

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

Что-то пошло не так!

Есть много вещей, которые могут пойти не так, особенно, если вы это делаете в первый раз. Главное — не паниковать:

Пункт №1: Если вы получили ошибку от компилятора, что функция add() не определена в main(), то, скорее всего, вы забыли записать предварительное объявление функции add() в main.cpp.

Основы ООП C++. Урок 13. Многофайловые программы

Пункт №2: Если вы получили следующую ошибку от линкера:

то возможных решений есть несколько:

a) Cкорее всего, add.cpp некорректно добавлен в ваш проект. Если вы используете Visual Studio или Code::Blocks, то вы должны увидеть add.cpp в «Обозревателе решений» в списке файлов вашего проекта или в панели проекта IDE. Если добавленного файла нет, то щелкните правой кнопкой мыши по вашему проекту и добавьте файл, как это показано выше, а затем повторите попытку компиляции вашего проекта.

б) Вполне возможно, что вы добавили add.cpp к другому проекту.

в) Вполне возможно, что добавленный файл не подключен к компиляции/линкингу. Щелкните правой кнопкой мыши по имени вашего добавленного файла и выберите «Свойства» :

Источник: dzen.ru

Многофайловые сборки

XYZ School

— это коллекция взаимосвязанных модулей, которые развертываются и снабжаются версией в виде цельной логической единицы. В IDE-среде Visual Studio никакого отдельного шаблона проекта для создания многофайловой сборки на C# не предусмотрено. Для создания такового пока что должен использоваться компилятор командной строки (csc.exe).

Рассмотрим этот процесс на примере, создав многофайловую сборку по имени Car. В главном модуле этой сборки (car.dll) будет содержаться единственный тип класса SportCar и соответствующий манифест, который указывает на наличие дополнительного файла *.netmodule по имени auto.netmodule, содержащего еще один класс Auto. Хотя физически оба класса размещаются в отдельных двоичных файлах, пространство имен у них будет одно — Car. И, наконец, оба класса будут создаваться на C# (хотя, конечно же, вполне допустимо использовать другие языки). Для начала откроем простой текстовый редактор и создадим следующее определение для класса Auto, после чего сохраним его в файле с именем auto.cs:

using System; namespace Car < public class Auto < public void AutoInfo() < Console.WriteLine(«Информация об автомобиле: «); >> >

Чтобы скомпилировать этот класс в .NET-модуль, откройте в Visual Studio 2010 окно командной строки, перейдите в папку, где был сохранен файл auto.cs, и введите следующую команду (опция module флага /t указывает, что должен быть создан файл *.netmodule, а не *.dll или *.ехе):

csc.exe /t:module auto.cs

В папке с файлом auto.cs появится новый файл по имени auto.netmodule. Теперь давайте создадим новый файл sportcar.cs со следующим определением класса:

using System; namespace Car < public class SportCar < public void InfoSportCar() < Console.WriteLine(«Audi R8»); >> >

Поскольку было решено, что главный модуль в этой многофайловой сборке будет называться car.dll, осталось только скомпилировать sportcar.cs с использованием соответствующих опций /t: library и /out:. Чтобы включить информацию о двоичном файле auto.netmodule в манифест сборки, также потребуется добавить соответствующий флаг /addmodule. Полностью необходимая команда выглядит следующим образом:

csc /t:library /addmodule:auto.netmodule /out:car.dll sportcar.cs

После выполнения этой команды в каталоге должен появиться главный модуль car.dll, а также второстепенный двоичный файл auto. netmodule.

Теперь откроем файл auto.netmodule в утилите ildasm.exe. Сразу же можно будет заметить, что в нем содержится манифест уровня модуля, единственной задачей которого является перечисление всех внешних сборок, которые упоминаются в кодовой базе. Поскольку в классе Auto был практически добавлен только вызов Console.WriteLine(), обнаружится следующая ключевая информация:

Манифест уровня модуля

Далее откроем в ildasm.exe файл главного модуля car.dll и изучим содержимое манифеста уровня всей сборки. В нем важно обратить внимание, что маркер .file используется для представления информации о других ассоциируемых с многофайловой сборкой модулях (в данном случае — auto.netmodule), а маркеры .class extern — для перечисления имен внешних типов, которые упоминаются как используемые во второстепенном модуле (в данном случае — Auto). Ниже приведена соответствующая информация:

Манифест уровня всей сборки

Использование многофайловой сборки

Пользователям многофайловой сборки нет никакого дела до того, состоит ли сборка, на которую они ссылаются, из многочисленных модулей. Чтобы не усложнять пример, построим новое клиентское приложение на языке C# в командной строке. Для начала создадим новый файл по имени Client.cs со следующим определением модуля и сохраним его в месте, где находится многофайловая сборка:

using System; using Car; class Program < static void Main() < SportCar obj = new SportCar(); obj.InfoSportCar(); // Здесь будет загружаться модуль auto.netmodule Auto obj1 = new Auto(); obj1.AutoInfo(); Console.ReadLine(); >>

Чтобы скомпилировать эту исполняемую сборку в командной строке, запустим компилятор csc.ехе с помощью следующей команды:

csc /r:car.dll Client.cs

Обратите внимание, что при добавлении ссылки на многофайловую сборку компилятору необходимо указать только имя главного модуля (второстепенные модули *.netmodule будут загружаться CLR-средой по требованию, когда возникнет необходимость в их использовании клиентским кодом). Сами по себе модули *.netmodule не обладают никаким индивидуальным номером версии и потому не могут напрямую загружаться CLR-средой. Отдельные модули *.netmodule могут загружаться только главным модулем (например, файлом, в котором содержится манифест сборки).

Источник: professorweb.ru

Многофайловые программы

Запуск gcc позволяет обработать файл с исходным кодом препроцессором и далее скомпилировать его. Однако при этом сам инструмент gcc не компилирует файл исходного кода в конечный исполняемый файл. Он компилирует его в объектный файл, после чего вызывает так называемый линковщик, или компоновщик. Но зачем надо сначала получать объектный файл, а потом из него уже исполняемый? Для программ, состоящих из одного файла, такой необходимости нет. Хотя при желании здесь также можно отказаться от компоновки, если выполнить команду gcc с ключом -c:

gcc -c hello.c

В результате получится файл с расширением *.o. Чтобы получить из объектного файла исполняемый, надо использовать ключ -o:

gcc -o hello hello.o

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

Компиляция программы, состоящей из нескольких файлов исходного кода

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

#include void l2r(char **c, int n) { int i, j; for(i=0; in; i++, c++) { for (j=0; ji; j++) printf(«t»); printf («%sn»,*c); } } void r2l(char **c, int n) { int j; for(; n>0; n—, c++) { for (j=1; jn; j++) printf(«t»); printf («%sn»,*c); } }
#include #define N 5 int main () { char strs[N][10]; char *p[N]; int i; for(i=0; iN; i++) { scanf(«%s», strs[i]); p[i] = } l2r(p, N); r2l(p, N); }

Читайте также:
Smart life что это за программа

В теле функции main() заполняется массив, состоящий из строк, а также массив указателей на эти строки. Далее в функции l2r() и r2l() передаются ссылки на первый элемент массива указателей и значение символической константы N. Эти функции осуществляют вывод элементов массива строк с отступами.

Чтобы получить исполняемый файл этой программы, надо сначала получить объектные файлы из исходных:

gcc -c superprint.c gcc -c main.c

Тоже самое можно сделать за один вызов gcc:

gcc -c superprint.c main.c

Или даже вот так, если в каталоге находятся только файлы текущего проекта:

gcc -c *.c

В любом случае в каталоге появятся два объектных файла: superprint.o и main.o. Далее их можно скомпилировать в один исполняемый файл так:

gcc -o myprint main.o superprint.o
gcc -o myprint *.o

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

gcc -o main.o superprint.o

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

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

Задумаемся, каким образом в представленной выше программе функция main() «узнает» о существовании функций l2r() и r2l() . Ведь в исходном коде файла main.c нигде не указано, что мы подключаем файл superprint.c, содержащий эти функции. Действительно, если попытаться получить из main.c отдельный исполняемый файл, т.е. скомпилировать программу без superprint.c:

gcc main.c

, то ничего не получиться. Компилятор сообщит об ошибке вызова неопределенных идентификаторов. Получить из файла superprint.c отдельный исполняемый файл вообще невозможно, т.к. там отсутствует функция main() . А вот получить из этих файлов отдельные объектные файлы можно.

Представим, что одни объектные файлы как бы «выставляют наружу» имена определенных в них функций и глобальных переменных, а другие — вызовы этих имен из тел других функций. Дальше объектные файлы «ожидают», что имена будут связаны с их вызовами. Связывание происходит при компиляции исполняемого файла из объектных.

Создание заголовочных файлов

Продолжим разбирать приведенную выше программу. Что будет, если в функции main() осуществить неправильный вызов функций l2r() и r2l() ? Например, указать неверное количество параметров. В таком случае создание объектных файлов пройдет без ошибок, и скорее всего удастся получить исполняемый файл; но вот работать программа будет неправильно. Такое возможно потому, что ничего не контролирует соответствие вызовов прототипам (объявлениям) функций.

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

… void l2r (char **c, int n); void r2l (char **c, int n); main () …

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

А теперь представим, что программа у нас несколько больше и содержит десяток файлов исходного кода. Файл aa.c требует функций из файла bb.c, dd.c, ee.c. В свою очередь dd.c вызывает функции из ee.c и ff.c, а эти два последних файла активно пользуются неким файлом stars.c и одной из функций в bb.c. Программист замучится сверять, что чего вызывает откуда и куда, где и какие объявления надо прописывать.

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

Итак, более грамотно будет не добавлять объявления функций в файл main.c, а создать заголовочный файл, например, myprint.h и поместить туда прототипы функций l2r() и r2l() . А в файле main.c следует прописать директиву препроцессора:

#include «myprint.h»

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

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

Однако стоит ли так делать? В функции r2l() второй параметр изменяется в процессе ее выполнения, что делать с константой будет невозможно. Придется переписывать тело функции. Кроме того, вдруг в последствии нам захочется использовать файл superprint.c в другом проекте, где будут свои порядки, и константы N в заголовочном файле не найдется.

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

Особенности использования глобальных переменных

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

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

  • Если в файле aa.c объявлена переменная за пределами любой функции (например, так: intcount ), то она является глобальной для всех файлов проекта. Чтобы получить значение этой переменной в файле aa.c достаточно просто указать ее имя (если в функции нет локальной переменной с тем же именем). Чтобы получить значение из других файлов, надо указать, что имеется в виду глобальная переменная, а не локальная. Делается это с помощью ключевого слова extern (например, extern count ).
  • Бывают ситуации, когда в одном файле для нескольких содержащихся в нем функций нужна глобальная переменная. Но эта переменная не должна быть доступна функциям, содержащимся в других файлах. В таком случае глобальная переменная объявляется с ключевым словом static (например, static int count ). Тем самым мы как бы скрываем глобальную переменную.

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

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

Источник: younglinux.info

2.7 – Программы с несколькими файлами исходного кода

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

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

Когда вы добавляете в проект новые файлы исходного кода, давайте им расширение .cpp .

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

В Visual Studio кликните правой кнопкой мыши на папке Исходные файлы (Source Files) в окне Обозревателя решений (Solution Explorer) и выберите Добавить (Add) → Создать элемент… (New Item…).

Рисунок 1 Добавление нового файла в проект в Visual Studio

Убедитесь, что у вас выбран Файл C++ (.cpp). Дайте новому файлу имя, и он будет добавлен в ваш проект.

Рисунок 2 Создание нового файла в проекте в Visual Studio

Примечание. Если вы создаете новый файл из меню Файл (File), а не из своего проекта в обозревателе решений, новый файл не будет добавлен в ваш проект автоматически. Вам придется добавить его в проект вручную. Для этого кликните правой кнопкой мыши на папке Исходные файлы (Source Files) в окне Обозревателя решений (Solution Explorer) и выберите Добавить (Add) → Существующий элемент (Existing Item), а затем выберите свой файл.

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

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

В Code::Blocks перейдите в меню File (Файл) и выберите New (Создать) → File… (Файл…).

Рисунок 3 Создание нового файла в Code::Blocks

В диалоговом окне New from template (Создать из шаблона) выберите C/C++ source (Исходный файл C/C++) и нажмите Go (Перейти).

Читайте также:
Для чего нужна программа Horizon Xbox 360

Рисунок 4 Создание нового исходного файла C/C++ в Code::Blocks

На этом этапе вы можете увидеть или не увидеть приветствие в диалоговом окне мастера создания исходного файла C/C++. Если да, щелкните Next (Далее).

Рисунок 5 Диалоговое окно мастера создания исходного файла C/C++

На следующей странице мастера выберите C++ и нажмите Next (Далее).

Рисунок 6 Выбор языка при создании нового исходного файла в Code::Blocks

Теперь дайте новому файлу имя (не забудьте расширение .cpp ) и выберите все конфигурации сборки. Наконец, выберите Finish (Готово).

Рисунок 7 Указание имени файла и выбор конфигураций сборки

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

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

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

g++ main.cpp add.cpp -o main

где main.cpp и add.cpp – это имена ваших исходных файлов, а main – имя выходного файла.

Пример с несколькими файлами

В уроке «2.6 – Предварительные объявления и определения» мы рассмотрели программу с одним исходным файлом, которая не компилируется:

#include int main() < std::cout int add(int x, int y)

Когда компилятор достигает вызова функции add в строке 5 в функции main , он не знает, что такое add , потому что мы определили add только в строке 9! Нашим решением было либо переупорядочить функции (поместив сначала add ), либо использовать для add предварительное объявление.

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

int add(int x, int y)
#include int main() < std::cout

Ваш компилятор может решить сначала скомпилировать либо add.cpp , либо main.cpp . В любом случае main.cpp не скомпилируется, что приведет к той же ошибке компилятора, что и в предыдущем примере:

main.cpp(5) : error C3861: ‘add’: identifier not found

Причина точно такая же: когда компилятор достигает строки 5 файла main.cpp , он не знает, что такое идентификатор add .

Помните, что компилятор компилирует каждый файл отдельно. Он не знает о содержимом других исходных файлов и не запоминает что-либо, что он видел из ранее скомпилированных исходных файлов. Таким образом, даже если компилятор, возможно, видел определение функции add ранее (если он сначала скомпилировал add.cpp ), он этого не запомнил.

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

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

Лучшее решение здесь – использовать предварительное объявление:

main.cpp (с предварительным объявлением):

#include // необходимо, чтобы main.cpp знал, что add() — это функция, объявленная в другом месте int add(int x, int y); int main()

add.cpp (остается прежним):

int add(int x, int y)

Теперь, когда компилятор компилирует main.cpp , он будет знать, что такое идентификатор add , и будет удовлетворен. Линкер соединит вызов функции add в main.cpp с определением функции add в add.cpp .

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

Попробуйте скомпилировать add.cpp и main.cpp с предварительным объявлением. Если вы получили ошибку линкера, убедитесь, что вы правильно добавили add.cpp в свой проект или строку компиляции.

Что-то пошло не так!

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

1. Если вы получаете ошибку компилятора о том, что add не определена в main , вы, вероятно, забыли предварительное объявление для функции add в main.cpp .

2. Если вы получаете сообщение об ошибке компоновщика о том, что add не определена, например

2а. … наиболее вероятная причина в том, что add.cpp неправильно добавлен в ваш проект. При компиляции вы должны увидеть в списке компиляции и main.cpp , и add.cpp . Если вы видите только main.cpp , значит add.cpp определенно не компилируется. Если вы используете Visual Studio или Code::Blocks, вы должны увидеть add.cpp в списке в обозревателе решений / на панели проекта в левой части IDE. Если его не видно, кликните на проекте правой кнопкой мыши и добавьте этот файл, а затем попробуйте скомпилировать снова. Если вы компилируете из командной строки, не забудьте включить main.cpp и add.cpp в свою команду компиляции.

2b. … возможно, вы добавили add.cpp не в тот проект.

2c. … возможно, что файл не компилируется или не линкуется. Проверьте свойства файла и убедитесь, что файл настроен для компиляции/линковки. В Code::Blocks компиляция и линковка – это отдельные флажки, которые следует установить. В Visual Studio есть параметр «исключить из сборки» (exclude from build), для которого следует установить значение «нет» или оставить пустым.

3. Не пишите #include «add.cpp» в main.cpp . Это заставит компилятор вставить содержимое add.cpp непосредственно в main.cpp вместо того, чтобы рассматривать их как отдельные файлы.

Резюме

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

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

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

Небольшой тест

Вопрос 1

Разделите следующую программу на два файла ( main.cpp и input.cpp ). main.cpp должен содержать функцию main , а input.cpp должен содержать функцию getInteger .

Не забывайте, что вам понадобится предварительное объявление в main.cpp для функции getInteger() .

#include int getInteger() < std::cout ; std::cin >> x; return x; > int main() < int x< getInteger() >; int y< getInteger() >; std::cout
#include // нам нужен iostream, поскольку мы используем его в этом файле int getInteger() < std::cout ; std::cin >> x; return x; >
#include // здесь нам тоже нужен iostream, поскольку мы используем его и в этом файле int getInteger(); // предварительное объявление функции getInteger int main() < int x< getInteger() >; int y< getInteger() >; std::cout

Источник: radioprog.ru

Лекция 19 Многофайловые программы

1. Создание многофайловой программы. Создание и добавление головного файла в проект

При построении алгоритмов решения больших задач приходиться строить основной и вспомогательные алгоритмы. Вспомогательные алгоритмы описывают решение подзадач основной задачи. Решение основной задачи сводится к последовательному решению всех подзадач. При решении больших по объему прикладных задач на компьютере написание основной программы сводится к вызовам функций, которые позволяют решать подзадачи. Если описывать функции и основную программу в одном файле, то это затруднит её понимание при проверке. Эту проблему в языке С++ позволяет решить многофайловая программа. Для реализации многофайловой программы в программный проект добавляют головные файлы, в которых описываются функции, затем при помощи директивы препроцессора #include эти файлы подключаются в основной программе, где описывается главная функция main(). Реализация многофайловой программы:

  1. Создать проект программы.
  2. Выбрать пункт меню ProjectAdd toProjectNew (для создания нового головного файла) и ProjectAdd toProjectFiles… (для подключения уже созданного головного файла). Среда Visual C++ 6.0 будет выглядеть следующим образом (рис.10).
  3. Появится диалоговое окно, в котором будет предложено выбрать тип подключаемого файла (C/C++HeaderFile) и, если это нужно, набрать имя создаваемого головного файла в поле Filename. После этого нужно нажать на кнопку (рис.11).
  4. Созданный головной файл будет находиться в той же папке, где и располагаются файлы проекта. В среде программирования файл можно найти на вкладке FileView, раскрыв папку HeaderFiles (рис.12):
  5. Открыв созданный головной файл, в окне редактора можно набрать текст функции. Для возможности использования созданной функции в своей программе необходимо в файле главной функции .cрр (на вкладке FileView окна рабочего пространства открыть папку SourceFiles) при помощи директивы #includе подключить созданный головной файл (рис.13).

Пример 1. Дан одномерный массив целых чисел. Вычислить сумму максимального и первого элементов массива. Ход выполнения работы

  1. Основной алгоритм решения задачи (декомпозиция) выглядит следующим образом:
      1. ввод элементов массива;
      2. нахождение максимального элемента;
      3. вычисление суммы;
      4. вывод элементов массива на печать.

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

      • Результатом работы первой функции должен быть сформированный массив. Этого можно достичь двумя способами: тип результата работы функции можно сделать указателем на тип элементов массива (1 вариант) или сделать формальным параметром указатель на тип элементов массива (2 вариант). Если в первом случае в теле функции будет необходимо использовать оператор return и будет возвращаться указатель на начало массива в ОП, то во втором случае этого не надо будет делать, поскольку в качестве фактического параметра будет передаваться адрес массива в ОП. При передаче указателя – фактического параметра в функцию произойдет заполнение ячеек ОП, которые будут отведены под массив. При этом их адреса не будут изменены. Еще одним параметром функции должна быть переменная, значение которой определяет количество вводимых переменных.
      • Результатом работы функции нахождения максимального элемента будет найденный элемент. Следовательно, в теле функции обязательно должен быть оператор return, который вернет найденное значение. Формальными параметрами функции будут указатель на начало массива и переменная, хранящая количество элементов массива.
      • Функция вывода элементов массива на печать не возвращает никакого результата, т.е. в теле функции оператор return отсутствует. Следовательно, формальными параметрами этой функции будут указатель на начало массива и переменная, хранящая количество элементов массива.
      1. Создать проект (консольное приложение) с именем massive. В проекте создать и подключить два головных файла с именами vvod.h и comp.h. В первом файле описать функцию ввода и функцию вывода элементов одномерного массива, во втором – функцию, которая будет искать максимальный элемент.
      2. Записать тексты функций в головные файлы. Для этого открыть в окне рабочего пространства вкладку FileView, затем открыть папку HeaderFiles. В этой папке открыть файл vvod.h и записать в него тексты первых двух функций. В той же папке открыть файл comp.h и записать в него текст функции. Тексты программ, записанных в каждом файле:
      Читайте также:
      Livetex что это за программа

      файлvvod.h: //функция ввода элементов массива: 1-ый вариант int*vv(inta[],intn) //в качестве аргументов выступают одномерный //массив и количество его элементов < inti;//локальная переменная – номер элемента массива. Область //видимости переменной – тело функции for(i=0;i<=n-1;i++) < cout<<«a[«<cin>>a[i]; > returna; //возвращаем адрес на начало массива в ОП > //функция ввода элементов массива: 2-ой вариант void vv(int *a, int n) < inti;//локальная переменная – номер элемента массива. //Область видимости – тело функции for(i=0;i<=n-1;i++) < cout<<«a»<cin>>*(a+i); > > //функция вывода элементов массива voidviv(int*a,intn) < inti;//локальная переменная – номер элемента массива. //Область видимости – тело функции for(i=0;i<=n-1;i++) < cout<<«a»<cout файлcomp.h: //функция поиска максимального элемента массива int max(int *a, int n) < inti;//локальная переменная – номер элемента массива. //Область видимости – тело функции intmax_a;//локальная переменная – максимальный элемент массива. //Область видимости – тело функции max_a=*a; for(i=1;i<=n-1;i++) < if(max_a<*(a+i)) max_a=*(a+i); >return max_a; >

      1. Записать текст главной функции. Для этого открыть в окне рабочего пространства вкладку FileView и папку SourceFiles. В этой папке открыть файл massive.cpp и записать в него текст главной функции:

      #include «stdio.h» #include «stdlib.h» #include «iostream.h» #include «iomanip.h» #include «vvod.h» #include «comp.h» int main() < int n, *p, s; coutcin>>n; p=(int*)malloc(n*sizeof(int)); //введем элементы массива при помощи нашей функции 2-ой //вариант vv(p,n); // при втором варианте описания этой функции был бы вызов: // p=vv(p,n); //вычислим сумму s=*p+max(p,n); //выведем значение суммы на экран cout //выведем элементы массива viv(p, n); free(p); return1; >Примечания:

      • При написании программ с использованием функций и массивов нужно учитывать тот факт, что если массив (одномерный или двухмерный, целочисленный или символьный и т.д.) должен быть изменен в результате работы функции, то в качестве формального параметра выступает указатель на начало массива в ОП. Если же массив не изменяется в процессе выполнения функции, то в качестве формального параметра можно определить указатель на начало массива в ОП, но при этом надо следить за тем, чтобы элементы массива не изменялись в процессе выполнения операторов тела функции.
      • Как видно из примера имена фактических и формальных параметров не совпадают, что не влияет на правильность работы программы.
      1. Откомпилировать файл massive.cpp. Запустить программу на выполнение.

      Пример 2. Каждого студента можно описать при помощи характеристик: ФИО, курс, специальность, предмет1, предмет2, предмет3. Написать программу (с использованием файлов и функций), определяющую количество студентов:

      1. сдавших сессию на «отлично»;
      2. не сдавших хотя бы 1 экзамен.

      Ход выполнения работы

      1. Опишем основной алгоритм решения задачи (декомпозиция). Переменная var обозначает вариант работы:

      var=-1 пока var!=0 ввод var выбор var случай 1: // создание и запись в файл вызов функции создания и записи в файл выход случай 2: //открытие файла для добавления вызов функции добавления данных в файл выход случай 3: // открытие файл в режиме чтения //определение характеристик вызов функции вычисления искомых величин печать характеристик выход все_выбор все_цикл Определим входные параметры и результат работы каждой функции.

      1. Функция создания и записи в файл. Функционально результат ее работы будет зависеть от имени файла, указателя на поток (файл) и режима открытия файла. Следовательно, эти величины и будут формальными параметрами этой функции. Данная функция будет возвращать результат – целое число 0 в случае неудачной попытки открытия файла и число 1, если функция отработала нормально.
      2. Функция добавления данных в файл. Функционально результат ее работы будет зависеть от имени файла, указателя на поток (файл) и режима открытия файла. Следовательно, эти величины и будут формальными параметрами этой функции. Данная функция будет возвращать результат – целое число 0 в случае неудачной попытки открытия файла и число 1, если функция отработала нормально. Из п.п.a), b) следует, что можно написать одну функцию для реализации действий, описанных в этих двух пунктах.
      3. Функция вычисления искомых величин. Функционально результат ее работы будет зависеть от имени файла, указателя на поток (файл) и режима открытия файла. Следовательно, эти величины и будут формальными параметрами этой функции. Данная функция будет возвращать результат – целое число 0 в случае неудачной попытки открытия файла и число 1, если функция отработала нормально. В список формальных параметров этой функции добавим еще два параметра: указатели на искомые величины (будем называть их характеристиками) – значения, расположенные по переданным адресам, будут изменены в процессе выполнения функции.

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

      1. Создать проект (консольное приложение) с именем student. В проекте создать и подключить один головной файл с именем my_function.h. Открыть этот файл в окне рабочего пространства и описать в нем все функции. Тексты функций:

      файлmy_function.h: //описание структурного типа typedef struct < char fio[30]; int kurs; char spec[30]; int hist; int math; int phis; >stud; //функция записи или добавления данных в файл int write(char *name, FILE *pf, char *rezh) < stud *st; int i; long int n; coutcin>>n; st=(stud*)malloc(n*sizeof(stud)); //заполнение массива структур for(i=0;i <=n-1;i++) < cout<<«fio justify»>cin>>((st+i)–>fio); cout cin>>((st+i)–>kurs); cout cin>>((st+i)–>spec); cout cin>>((st+i)–>hist); cout cin>>((st+i)–>math); cout cin>>((st+i)–>phis); > //файл pfс именемnameоткрывается в режимеrezh if((pf=fopen(name,rezh))==NULL) < printf(«файл не открытn»); return 0; >//запись в файл fwrite(st, sizeof(stud), n, pf); fclose(pf); free(st); return 1; > //функция вычисления искомых величин int read(char *name, FILE *pf, char *rezh, int *c_5, int *c_2) < stud *st; int i; long int n; //локальные переменные cnt5 иcnt2 нужны для вычисления //характеристик int cnt5,cnt2; // файл pfс именемnameоткрывается в режимеrezh if((pf=fopen(name,rezh))==NULL) < printf(«файл не открытn»);return0; >//определение длины файла в байтах fseek(pf, 0,SEEK_END); n=ftell(pf); //определение количества записей в файле n=n/sizeof(stud); st=(stud*)malloc(n*sizeof(stud)); //возвращаем указатель в начало файла rewind(pf); //заполнение массива структур fread(st,sizeof(stud),n,pf); cnt5=cnt2=0; for (i=0;i<=n-1;i++) < //определяем количество отличников if((st+i)–>hist==5(st+i)–>math==5(st+i)–>phis==5) cnt5++; //определяем количество студентов, не сдавших хотя бы один //экзамен if((st+i)–>hist==2||(st+i)–>math==2||(st+i)–>phis==2) cnt2++; > fclose(pf); free(st); //возвращение найденных характеристик в главную программу //через указатели *c_5=cnt5; *c_2=cnt2; return1; >

      1. Записать текст главной функции. Открыть в окне рабочего пространства вкладку FileView и папку SourceFiles. В этой папке открыть файл student.cpp и записать в него текст главной функции:

      #include «stdio.h» #include «stdlib.h» #include «iostream.h» #include «iomanip.h» #include «my_function.h» int main() < FILE *pf; int var, count_5, count_2, i; charname_file[25]; //вводится имя файла, с которым предстоит работать coutcin>>name_file; var=-1; while(var!=0) < coutcin>>var; switch(var) < case 1: //записываем данные в файл i=write(name_file, pf, «w»); break; case 2: coutcin>>name_file; //дозаписываем данные в файл i=write(name_file, pf, «a»); break; case 3: cout cin>>name_file; //определяем характеристики i=read(name_file, pf, «r», count_2); if(i!=0) < coutcout > break; > > return1; >Примечания:

      1. В списке формальных параметров функции read() присутствуют два параметра-указателя. Это сделано для того, чтобы можно было при помощи функции возвращать не одну, а две величины (характеристики). Таким образом, если в результате работы функции должно быть получено более одной результирующей величины, то одну из этих величин можно возвращать при помощи оператора return, а другие добавлять в список формальных параметров как указатели на искомые переменные. В этом случае напрямую использовать эти параметры в операторах тела функции (например, в выражениях) не рекомендуется. Целесообразнее использовать локальные переменные для вычисления характеристик (в нашем примере это cnt2 и cnt5). Затем, полученные значения этих переменных записать по адресам ОП, переданным в функцию через указатели – фактические параметры.
      2. Очевидно, что с использованием функций текст главной функции main() значительно упростился и стал более понятен при чтении. Намного проще становится отладка программы, поскольку можно выявить неправильную работу всей программы, исследуя ее по частям (функциям).
      1. Откомпилировать файл massive.cpp. Запустить программу на выполнение.

      Источник: studfile.net

      Рейтинг
      ( Пока оценок нет )
      Загрузка ...
      EFT-Soft.ru