Форум программистов, компьютерный форум, киберфорум
С++ для начинающих
Войти
Регистрация
Восстановить пароль
Карта форума Темы раздела Блоги Сообщество Поиск Заказать работу  
 
 
Рейтинг 4.84/68: Рейтинг темы: голосов - 68, средняя оценка - 4.84
74 / 37 / 3
Регистрация: 23.09.2012
Сообщений: 408
1

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

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

Author24 — интернет-сервис помощи студентам
Есть 2 .cpp файла: a.cpp и b.cpp. Когда пишу:

C++
1
2
/* a.cpp */
#include "b.cpp"
Выдает ошибку. Как правильно из одного cpp подключить другой?
0
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
06.10.2012, 00:02
Ответы с готовыми решениями:

Перенос данных из одного cpp в другой
Здравствуйте. Допустим, есть два cpp файла, в первом прописано событие "щелчок по кнопке" void...

Передать значения переменных из одного cpp в другой
Здравствуйте. У меня возникла необходимость в чужом проекте передать значения переменных из одного...

Как правильно подключить два винчестера(один sata, другой ide)?
Здравствуйте. Подскажите, как правильно подключить два винчестера(один sata, другой ide)? На sata...

Как правильно перенести сайт с одного хостинга на другой?
На старом хостинге экспортирую БД. потом с помощью ftp скачиваю все файлы сайта. потом на новом...

34
Эксперт С++
5055 / 3115 / 271
Регистрация: 11.11.2009
Сообщений: 7,044
07.10.2012, 21:50 21
Лучший ответ Сообщение было отмечено как решение

Решение

Author24 — интернет-сервис помощи студентам
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
74 / 37 / 3
Регистрация: 23.09.2012
Сообщений: 408
07.10.2012, 21:53  [ТС] 22
alsav22, хм, вы правы. Попробовал - почти все прояснилось. Единственное:

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

Так же, не понимаю, разве двойное включение <iostream> не сделает хуже?
ведь оба файла компилируются отдельно. И получается в обоих .o будет код из iostream
0
Эксперт С++
5055 / 3115 / 271
Регистрация: 11.11.2009
Сообщений: 7,044
07.10.2012, 22:06 25
Цитата Сообщение от Kgfq Посмотреть сообщение
Ведь есть в хедере изменить хоть 1 несущественный символ, компилировать придется заново
Теоретически да. Практически же реализацию редактировать придётся куда чаще, чем объявление. Хедер будет редактироваться только в связи с какими-либо значительными изменениями (введение нового пользовательского типа, новой функции, изменение тела класса и т.д.), который в любом случае повлекут за собой перекомпиляцию реализации, в которой также необходимо отслеживать эти изменения и поддерживать их в актуальном состоянии.
0
74 / 37 / 3
Регистрация: 23.09.2012
Сообщений: 408
07.10.2012, 22:09  [ТС] 26
silent_1991, а как линкер поймет, из какого файла брать реализацию?
0
Эксперт С++
5055 / 3115 / 271
Регистрация: 11.11.2009
Сообщений: 7,044
07.10.2012, 22:11 27
Kgfq, ну ведь на момент вызова у него есть вся необходимая информация, все файлы с реализацией ему предоставлены. Здесь в ход вступает такая вещь, как таблица символов, которая является, по сути, индексом всего содержимого объектного файла. Если линкёр находит в таблице символов символ, соответствующий разрешаемой им в данный момент заглушке, он берёт из этой таблицы смещение и подставляет его вместо заглушки.
Возможно, я несколько не точен в деталях реализации, но концепция такая.
0
5498 / 4893 / 831
Регистрация: 04.06.2011
Сообщений: 13,587
07.10.2012, 22:13 28
Цитата Сообщение от Kgfq Посмотреть сообщение
Ведь есть в хедере изменить хоть 1 несущественный символ, компилировать придется заново
Какая разница, в данном случае, что куда подключено? Или вы хотите вносить изменения в программу, и чтобы потом ничего не перекомпилировалось? И как вы себе это представляете?
0
74 / 37 / 3
Регистрация: 23.09.2012
Сообщений: 408
07.10.2012, 22:22  [ТС] 29
alsav22, как я себе представлял это - реализация должна абстрагироваться от остального кода.
Какая разница, какие классы есть в коде, когда этой функции нужно подать определенное значение и она должна его как-то изменить.
0
Эксперт С++
5055 / 3115 / 271
Регистрация: 11.11.2009
Сообщений: 7,044
07.10.2012, 22:32 30
Kgfq, так она и абстрагирована. Возьмите мой пример. В main.cpp абсолютно ничего неизвестно о реализации func. Всё это дело склеивается в единый монолит на самой последней стадии - стадии линковки.
0
5498 / 4893 / 831
Регистрация: 04.06.2011
Сообщений: 13,587
07.10.2012, 22:50 31
Цитата Сообщение от Kgfq Посмотреть сообщение
alsav22, как я себе представлял это - реализация должна абстрагироваться от остального кода.
И хотите .cpp с реализацией подключать к другим файлам. Где логика?
0
74 / 37 / 3
Регистрация: 23.09.2012
Сообщений: 408
07.10.2012, 23:05  [ТС] 32
silent_1991,
alsav22,

Спасибо за подробные объяснения, я разобрался. Единственное, еще вопрос, когда стоит начинать задумываться о разделении прототипа и реализации?
0
5498 / 4893 / 831
Регистрация: 04.06.2011
Сообщений: 13,587
07.10.2012, 23:14 33
Цитата Сообщение от Kgfq Посмотреть сообщение
когда стоит начинать задумываться о разделении прототипа и реализации?
К правильному, желательно, сразу привыкать. Неверное, есть смысл помещать прототипы отдельно, в заголовочном файле, когда много функций и они используются во многих файлах.

Добавлено через 4 минуты
Стоит сказать ещё о шаблонах. Прототип шаблона и реализация должны быть в одном файле.
1
Эксперт С++
5055 / 3115 / 271
Регистрация: 11.11.2009
Сообщений: 7,044
08.10.2012, 01:12 34
Kgfq, задумываться стоит сразу. Ещё почитайте о системах сборки, например, make. Именно с ними все прелести раздельной компиляции показываются наружу.
На счёт шаблонов: иными словами, шаблон - единственный случай, когда подключается не объявление, а полностью реализация. Такова особенность шаблонов.
0
36 / 36 / 0
Регистрация: 09.08.2012
Сообщений: 132
08.10.2012, 04:50 35
Цитата Сообщение от Kgfq Посмотреть сообщение
silent_1991, хм, за теорию спасибо, стало яснее. Не могли бы вы сделать проект с maim.cpp, func.h и func.cpp, что бы я наглядно увидел?
func.h
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#pragma once
 
// в header пишутся только объявления
 
extern int GLobalInt;
 
void Foo(int i);
 
class FooClass
{
public:
    FooClass();
 
    void Init(int i);
 
private:
    int m_i;
};
func.cpp
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "func.h"
 
int GLobalInt = 0;
 
void Foo(int i)
{
   i += 10;
   printf("%d", i);
}
 
FooClass::FooClass()
{
   m_i = 0;
}
 
void FooClass::Init(int i)
{
   m_i = i;
   printf("%d", m_i);
}
main.cpp
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "func.h"
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
   GLobalInt = 10;
   printf("%d", GLobalInt);
 
   Foo(GLobalInt);
 
   FooClass fc;
   fc.Init(GLobalInt);
 
   system("PAUSE");
   return 0;
}
0
08.10.2012, 04:50
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
08.10.2012, 04:50
Помогаю со студенческими работами здесь

Как правильно передать метод делегата из одного класса в другой?
Добрый день! Я написал класс который рисует спираль Архимеда в pictureBox. За основу для для...

Как ПРАВИЛЬНО перенести windows 7 с одного компьютера на другой через образ?
Здравствуйте, форумчане! Можете подсказать мне что я делаю не так? Когда всякие авторы сборок...

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

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

Как подключить cpp файл к проекту?
есть cpp файл date где описаны класс и его методы подключаю к main с помощью #include &quot;date.cpp&quot;...

Как подключить библиотеку OpenGL к файлу .cpp
Здравствуйте, только начал работать с OpenGL, хочу сделать одну небольшую 2d игру и мне хочется...


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

Или воспользуйтесь поиском по форуму:
35
Ответ Создать тему
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru