Форум программистов, компьютерный форум CyberForum.ru
Наши страницы

С++ для начинающих

Войти
Регистрация
Восстановить пароль
 
 
Рейтинг: Рейтинг темы: голосов - 22, средняя оценка - 4.82
Kgfq
74 / 37 / 2
Регистрация: 23.09.2012
Сообщений: 408
#1

Как правильно из одного cpp подключить другой - C++

06.10.2012, 00:02. Просмотров 3401. Ответов 34
Метки нет (Все метки)

Есть 2 .cpp файла: a.cpp и b.cpp. Когда пишу:

C++
1
2
/* a.cpp */
#include "b.cpp"
Выдает ошибку. Как правильно из одного cpp подключить другой?
0
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
06.10.2012, 00:02
Здравствуйте! Я подобрал для вас темы с ответами на вопрос Как правильно из одного cpp подключить другой (C++):

Как подключить cpp файл к проекту? - C++
есть cpp файл date где описаны класс и его методы подключаю к main с помощью #include "date.cpp" выдает следующие ошибки: Ошибка 1 error...

Как подключить элементы из cpp файла? - C++
Доброе утро. У меня вопрос. Есть отдельно header(где прототип функции) и cpp файл, где содержится тело функции. При подключении к примеру в...

Как подключить cpp-файл к h-файлу? - C++
как связать файл Form1.h с главным файлом проекта 1.cpp ? что то вроде того не получается #include "1.cpp"

Как подключить файл ресурсов Dev-Cpp - C++
Ну могу подключить файл ресурсов на Dev-Cpp файл: #include <windows.h> #include "main.h" ID_MENU MENU BEGIN POPUP "&File"...

Как правильно подключить lib - C++
Добрый день. Пишу консольную программу на C++ (без использования Qt) с помощью редактора Qt Creator. Она работает, но в Qt уж очень...

Как правильно подключить модули? - C++
Задача: Даны натуральное число n, действительные числа x1,y1; x2,y2;... xn,yn;. Найти площадь n-угольника, вершины которого при некотором...

Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Kgfq
74 / 37 / 2
Регистрация: 23.09.2012
Сообщений: 408
07.10.2012, 18:44  [ТС] #16
defer, А смысл? Ведь мы подключаем func.h

Добавлено через 38 минут
в мэйне, имеется ввиду.
значит в func.h должно быть написано #include "func.cpp"?
0
defer
秘密
555 / 235 / 3
Регистрация: 29.11.2010
Сообщений: 783
07.10.2012, 19:40 #17
Цитата Сообщение от Kgfq Посмотреть сообщение
значит в func.h должно быть написано #include "func.cpp"?
нет, вот этого #include "func.cpp", не надо писать

#include "func.h" надо писать и в main.cpp и в func.cpp

а в начале func.h написать
C++
1
#pragma once
0
silent_1991
Эксперт С++
4964 / 3040 / 149
Регистрация: 11.11.2009
Сообщений: 7,027
Завершенные тесты: 1
07.10.2012, 21:05 #18
Kgfq, суть всего этого в следующем. При компиляции cpp-файла компилятору необходимо знать, какой вид имеют прототипы всех используемых в этом файле функций. Реализацию ему знать необязательно. Поэтому прототипы эти определяются в заголовочных файлах, которые и подключаются в файлы реализации. Когда же из каждого исходного файла получен объектный файл, в дело вступает редактор связей (линкёр). Вот он-то генерирует бинарный файл и редактирует адреса в местах вызовов функций, ища реализацию этих функций в объектных модулях, полученных на стадии трансляции, и заменяя подставленные компилятором заглушки на реальные адреса. Именно на этом факте основывается отсутствие необходимости перекомпилировать файл, который не изменялся с момента последней генерации объектного файла.
Включение же исходного файла посредством include сводит эту прелесть на нет, ведь сначала один исходный файл будет перенесён в другой, а потом этот сросшийся близнец будет откомпилирован целиком, что не уменьшит время компиляции, но наоборот увеличит его.
2
Kgfq
74 / 37 / 2
Регистрация: 23.09.2012
Сообщений: 408
07.10.2012, 21:11  [ТС] #19
silent_1991, хм, за теорию спасибо, стало яснее. Не могли бы вы сделать проект с maim.cpp, func.h и func.cpp, что бы я наглядно увидел?
0
alsav22
5419 / 4815 / 442
Регистрация: 04.06.2011
Сообщений: 13,587
07.10.2012, 21:44 #20
Вы лучше свой покажите. Многие вопросы отпадут в процессе работы. Например этот:
Цитата Сообщение от Kgfq Посмотреть сообщение
А смысл? Ведь мы подключаем func.h в мэйне
Если у вас в func.h будут прототипы, а в func.cpp из реализация, то без подключения в func.cpp func.h, у вас ничего не откомпилируется.
0
silent_1991
Эксперт С++
4964 / 3040 / 149
Регистрация: 11.11.2009
Сообщений: 7,027
Завершенные тесты: 1
07.10.2012, 21:50 #21
Лучший ответ Сообщение было отмечено автором темы, экспертом или модератором как ответ
func.h
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Следующая конструкция ifndef-define-endif имеет название "страж включения".
// Она нужна для предотвращения множественного включения одного и того же файла
// в другой файл. Суть работы следующая: при первом включении файла с помощью
// define символ FUNC_H (или любой другой, главное, чтобы он был по возможности
// уникальным и совпадал в ifndef и define) ещё не определён; препроцессор
// определяет этот символ и включает весь текст, который находится между ifndef
// и endif. При повторном включении файла символ FUNC_H будет ужё определён, и
// препроцессор уже не будет включать текст, находяцийся между ifndef и endif
#ifndef FUNC_H
#define FUNC_H
 
// Прототип функции. Говорит компилятору, что эту функцию можно вызвать с двумя
// аргументами типов double и char соответственно (или совместимых с ними), а
// также, возможно, присвоить её результат переменной типа int (или совместимого
// с ним)
int func(double d, char c);
 
#endif // FUNC_H
func.cpp
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// В данном случае подключение этого заголовочного файла не обязательно,
// поскольку ничего нового компилятору он не скажет. В общем случае, когда в
// заголовочном файле определяется больше информации (например, какие-то
// пользовательские типы и т.д.), которая используется в данном файле
// реализации, подключить этот файл необходимо. Я обычно всегда подключаю
// заголовочный файл в файле реалиации, это позволяет связать эти два файла в
// единый модуль (позволяет связать программисту, а не компилятору)
#include "func.h"
 
// Здесь всё стандартно, подключаем заголовочные файлы, функции из которых будем
// использовать в файле реализации
#include <iostream>
 
// Реализация функции, объявленной в заголовочном файле
int func(double d, char c)
{
    // Выводим на экран значения переданных в функцию аргументов
    std::cout << "double d = " << d << "; char c = " << c << std::endl;
    
    // Возвращаем из функции ASCII-код второго аргумента; приведение типа
    // здесь необязательно, но явное лучше, чем неявное
    return static_cast<int>(c);
}
main.cpp
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// Файл, в котором используется функция, объявленная в func.h и реализованная в
// func.cpp
#include <iostream>
 
// Смысл включения здесь этого файла в следующем: когда компилятор встрети вызов
// func, ему не обязательно знать, как эта функция реализована; нужно только
// знать, аргументы каких типов можно в неё передавать и значение какого типа
// она возвращает. В этом ему поможет прототип функции.
// По факту препроцессор, который вызывается ещё ДО компиляции, просто подставит
// вместо этого оператора всё, что в файле func.h располагается между ifndef
// и endif. Т.е. вместо
// 
// #include "func.h"
// 
// будет текстово подставлено
//
// #define FUNC_H
//
// int func(double d, char c);
// 
#include "func.h"
 
int main()
{
    double d = 3.14;
    char c = 'X';
    
    // Здесь происходит вызов функции func. При этом тело этой функции
    // компилятору видеть ненужно, ведь на стадии компиляции ещё нет никакого
    // вызова функции, есть только код, который этот вызов осуществляет. Здесь
    // компилятор проверит (на основании прототипа, который уже был текстово
    // включён в данный файл препроцессором), подходят ли типы аргументов,
    // передаваемых в функцию (если необходимо преобразование и компиилятор
    // может выполнить его автоматически, перед вызовом будет подставлен код,
    // выполняющий преобразование), а также можно ли результат функции присвоить
    // переменной того типа, которой он присваивается (опять же, если необходимо
    // преобразование - оно будет по возможности выполнено перед сохранение
    // результата в соответствующую переменную)
    int result = func(d, c);
    
    std::cout << result << std::endl;
    
    return 0;
}
Компиляция:
Bash
1
2
3
g++ -ansi -Wall -c -o func.o func.cpp
g++ -ansi -Wall -c -o main.o main.cpp
g++ -o main func.o main.o
Ключ -ansi говорит компилятору не делать никаких поблажек и строжайшим образом отыскивать все несоответствия стандарту. Ключ -Wall запрещает подавлять какие-либо, даже несущественные, предупреждения, и сообщать обо всём, что он посчитал хоть сколько нибудь неверным. Можно было бы обойтись и без них, но я так привык.
Клюс -c говорит, что необходимо остановится сразу после генерации объектного кода, а не вызывать линкёр и генерировать бинарный файл.
В первой строке отдельно компилируется файл func.cpp, на выходе будет объектный файл func.o. Здесь компилятору неважно, что эта функция где-то вызывается. Ему вообще не важно, вызывается она или нет. Его просят откомпилировать исходный файл в объектный, и он это делает.
Во второй строке отдельно компилируется файл main.cpp, на выходе объектный файл main.o. Компилятор при встрече вызова функции, определения которой не видит, вместо кода вызова этой функции (в котором фигурирует адрес, по которому она располагается в исполняемом файле) поставит заглушку, содержащую всю необходимую информацию, по которой линкёр сможет заглушку эту изменить на реальный адрес вызываемой функции (важно, чтобы он не ошибся и не подставил туда какой-то другой адрес, поэтому и нужна информативная заглушка). Всё, больше компилятору ничего не нужно, он не хочет видеть здесь тело функции, а возлагает поиск этого тела на линкёр.
В третьей строке, хотя и вызывается компилятор g++, на самом деле он сразу же вызовет линкёр (утилита ld), который сделает следующее (в неформальном виде, только для общего понимания процесса): отыщет все заглушки, поставленные компилятором, расшифрует информацию, которая в них хранится, найдёт в других объектных файлах реализации функций, соответствующих заглушкам, и заменит заглушки на код вызовов этих функций. В случае, если реализации какой-либо функции найдено не будет - линкёр выдаст ошибку о неразрешённой внешней ссылке. В случае множественного определения также будет выдана ошибка. Именно из-за всего, написанного выше, линкёру и нужно передавать ВСЕ объектные модули, которые ранее компилировались по-отдельности, ведь линкёр должен располагать всеми адресами всех используемых функций.
4
Kgfq
74 / 37 / 2
Регистрация: 23.09.2012
Сообщений: 408
07.10.2012, 21:53  [ТС] #22
alsav22, хм, вы правы. Попробовал - почти все прояснилось. Единственное:

Я правильно понимаю, что если есть некий заголовочный файл, в нем прототипы функций, то компилятор полезет в точно так же названный cpp файл за этими функциями?
0
silent_1991
Эксперт С++
4964 / 3040 / 149
Регистрация: 11.11.2009
Сообщений: 7,027
Завершенные тесты: 1
07.10.2012, 22:03 #23
Цитата Сообщение от Kgfq Посмотреть сообщение
что если есть некий заголовочный файл, в нем прототипы функций, то компилятор полезет в точно так же названный cpp файл за этими функциями?
Нет, так он делать не будет. Прочтите моё сообщение, там есть очень важный вывод (в явном виде, правда, не оформленный): во-первых, вызов компилятора заключается в неявном вызове кучи различных вспомогательных утилит (препроцессор, лексический анализатор, синтаксический анализатор, редактор связей и много чего ещё). Во-вторых, важно понимать, что все эти утилиты распределяют обязанности между собой. Компилятор не занимается линковкой, а линкёр - компиляцией. У каждого свои задачи, требующие своей входной информации.
0
Kgfq
74 / 37 / 2
Регистрация: 23.09.2012
Сообщений: 408
07.10.2012, 22:03  [ТС] #24
silent_1991, хм. Не совсем понимаю смысл создавать отдельный cpp, если потом в него включается .h. Ведь есть в хедере изменить хоть 1 несущественный символ, компилировать придется заново. Разве это не сводит всю прелесть на нет?

Так же, не понимаю, разве двойное включение <iostream> не сделает хуже?
ведь оба файла компилируются отдельно. И получается в обоих .o будет код из iostream
0
silent_1991
Эксперт С++
4964 / 3040 / 149
Регистрация: 11.11.2009
Сообщений: 7,027
Завершенные тесты: 1
07.10.2012, 22:06 #25
Цитата Сообщение от Kgfq Посмотреть сообщение
Ведь есть в хедере изменить хоть 1 несущественный символ, компилировать придется заново
Теоретически да. Практически же реализацию редактировать придётся куда чаще, чем объявление. Хедер будет редактироваться только в связи с какими-либо значительными изменениями (введение нового пользовательского типа, новой функции, изменение тела класса и т.д.), который в любом случае повлекут за собой перекомпиляцию реализации, в которой также необходимо отслеживать эти изменения и поддерживать их в актуальном состоянии.
0
Kgfq
74 / 37 / 2
Регистрация: 23.09.2012
Сообщений: 408
07.10.2012, 22:09  [ТС] #26
silent_1991, а как линкер поймет, из какого файла брать реализацию?
0
silent_1991
Эксперт С++
4964 / 3040 / 149
Регистрация: 11.11.2009
Сообщений: 7,027
Завершенные тесты: 1
07.10.2012, 22:11 #27
Kgfq, ну ведь на момент вызова у него есть вся необходимая информация, все файлы с реализацией ему предоставлены. Здесь в ход вступает такая вещь, как таблица символов, которая является, по сути, индексом всего содержимого объектного файла. Если линкёр находит в таблице символов символ, соответствующий разрешаемой им в данный момент заглушке, он берёт из этой таблицы смещение и подставляет его вместо заглушки.
Возможно, я несколько не точен в деталях реализации, но концепция такая.
0
alsav22
5419 / 4815 / 442
Регистрация: 04.06.2011
Сообщений: 13,587
07.10.2012, 22:13 #28
Цитата Сообщение от Kgfq Посмотреть сообщение
Ведь есть в хедере изменить хоть 1 несущественный символ, компилировать придется заново
Какая разница, в данном случае, что куда подключено? Или вы хотите вносить изменения в программу, и чтобы потом ничего не перекомпилировалось? И как вы себе это представляете?
0
Kgfq
74 / 37 / 2
Регистрация: 23.09.2012
Сообщений: 408
07.10.2012, 22:22  [ТС] #29
alsav22, как я себе представлял это - реализация должна абстрагироваться от остального кода.
Какая разница, какие классы есть в коде, когда этой функции нужно подать определенное значение и она должна его как-то изменить.
0
silent_1991
Эксперт С++
4964 / 3040 / 149
Регистрация: 11.11.2009
Сообщений: 7,027
Завершенные тесты: 1
07.10.2012, 22:32 #30
Kgfq, так она и абстрагирована. Возьмите мой пример. В main.cpp абсолютно ничего неизвестно о реализации func. Всё это дело склеивается в единый монолит на самой последней стадии - стадии линковки.
0
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
07.10.2012, 22:32
Привет! Вот еще темы с ответами:

Как правильно подключить данную библиотеку ? - C++
Доброго времени суток подскажите пожалуйста как правильно подключить данную библиотеку в C++Builder 10.1 Berlin скачка библиотеки...

Как правильно подключить библиотеку SDL - C++
Здравствуйте, подскажите, пожалуйста, как правильно подключить библиотеку SDL. Я пользуюсь компилятором Code::Blocks. При подключении...

Как правильно подключить curl библиотеку? - C++
Здравствуйте, я хотел в своем проекте использовать библиотеку curl, для этого я: 1. Скачал архив з оф. сайта. 2. Затем по инструкции в...

Как правильно подключить набор сертификатов в curl? - C++
Здравствуйте, есть следующий код: #include &lt;stdio.h&gt; #include &lt;curl/curl.h&gt; #include &lt;string&gt; int main(void) { CURL *curl;...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
Yandex
Объявления
07.10.2012, 22:32
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2017, vBulletin Solutions, Inc.
Рейтинг@Mail.ru