Как вызвать функцию из другого файла c
Перейти к содержимому

Как вызвать функцию из другого файла c

  • автор:

Воспользоваться функцией из другого cpp/h файла

Во-первых, в .h -файлах нет никаких функций. Это просто кусок текста, вставляемый перед компиляцией вместо соответствующих директив #include . Соответственно, с точки зрения компилятора нет никакой разницы, включили ли вы заголовочный файл или просто взяли и скопировали его содержимое в свой .cpp руками.

То есть для решения вашей задачи нет разницы между заголовочным файлом с объявлением функции:

#pragma once void foo(int bar); 
#include "decl.h" int main() < foo(21); // Использование функции >
#include "decl.h" void foo(int bar) < // Реализация функции >

и повтором объявления в каждом .cpp -файле, которому нужна эта функция:

void foo(int bar); int main() < foo(21); // Использование функции >
void foo(int bar) < // Реализация функции >

Во-вторых, .cpp -файл — это «вещь в себе», чёрный ящик с двоичными данными и списком того, что этот ящик импортирует/экспортирует, и по каким байтовым смещениям в вышеуказанных данных это требуется/реализуется соответственно:

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

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

Отслеживать
ответ дан 19 окт 2017 в 21:25
Arhadthedev Arhadthedev
11.5k 8 8 золотых знаков 42 42 серебряных знака 70 70 бронзовых знаков

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

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

Такой файл может включаться в уже скомпилированном виде (как объектный файл или в составе библиотеки).

Отслеживать
ответ дан 15 окт 2017 в 13:12
220k 15 15 золотых знаков 120 120 серебряных знаков 233 233 бронзовых знака

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

как вызвать функцию из другого файла js

Для вызова функции из другого файла js необходимо сначала загрузить этот файл в текущий скрипт. Это можно сделать с помощью тега в html файле, где вы хотите вызвать функцию. Например, если у вас есть файл functions.js , который содержит функцию myFunction , то вам нужно добавить следующий тег в html файл:

 src="functions.js"> 

После этого вы можете вызвать функцию myFunction в текущем скрипте. Например:

myFunction(); 

Если вы хотите вызвать функцию из другого файла js внутри модуля (ES6 и выше), вы можете использовать ключевое слово import . Например, если у вас есть файл functions.js со следующим содержимым:

export const myFunction = () =>  console.log('Hello world!'); >; 

Тогда вы можете импортировать функцию myFunction в другом файле js следующим образом:

import  myFunction > from './functions.js'; myFunction(); 

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

Как вызвать функцию из другого файла c

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

Например, определим файл sum.cpp , который будет иметь следующий код:

int sum(int a, int b)

Это функция вычисления суммы чисел.

Добавим еще один файл — sum.h , который будет содержать объявление функции sum:

int sum(int, int);

И также определим главный файл, который назовем app.cpp :

#include #include "sum.h" // подключаем файл sum.h int main() < int result < sum(5, 4)>; std::cout #include "sum.h"

Файл sum.h еще называется заголовочным файлом (header file), так как содержит объявление, заголовок функции. ПРичем в данном случае предполагается что все файлы располагаются в одном каталоге:

Заголовочные файлы в языке программирования c++

Можно было бы и не подключать файл sum.h и вообще не создавать его, а объявление функции поместить непосредственно в файл app.cpp. Но при изменении функции может потребоваться изменить и ее объявление. И если функция sum используется в нескольких файлах с исходным кодом, то в каждом из этих файлов придется менять ее объявление. В данном же случае достаточно изменить объявление функции в одном файле — sum.h.

При компиляции через g++ необходимо передать все файлы через пробел компилятору:

g++ app.cpp sum.cpp -o app

То же самое верно и для компиляции через Clang::

clang++ app.cpp sum.cpp -o app.exe

На выходе будет сгенерирован единый файл app.

При работе в Visual Studio заголовочные файлы обычны помещаются в каталог «Headers»:

header files в языке программирования c++ в Visual Studio

А при компиляции все файлы автоматически компилируются в один.

Многофайловые программы на языке C. Объектный код и заголовочные файлы

Запуск 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 ; i  n; i++, c++) { for (j = 0; j  i; j++) printf("\t"); printf ("%s\n", *c); } } void r2l(char **c, int n) { int j; for(; n > 0; n--, c++) { for (j = 1; j  n; j++) printf("\t"); printf ("%s\n", *c); } }
#include #define N 5 int main() { char strs[N][10]; char *p[N]; int i; for(i = 0; i  N; i++) { scanf("%s", strs[i]); p[i] = &strs[i][0]; } l2r(p, N); r2l(p, N); }

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

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

Курс с решением задач:
pdf-версия

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *