2010-06-23 30 views
6

Tôi đã làm việc với C++ trong một vài tuần, nhưng cơ chế đằng sau các tệp tiêu đề (hoặc trình liên kết mà tôi cho là?) Làm tôi bối rối. Tôi đã có thói quen tạo một tệp "main.h" để nhóm các tệp tiêu đề khác của tôi và giữ cho tệp main.cpp gọn gàng, nhưng đôi khi các tệp tiêu đề đó không thể tìm thấy tệp tiêu đề khác (mặc dù nó được khai báo trong "main.h"). Tôi có lẽ không giải thích nó rất tốt vì vậy đây là một phiên bản rút gọn của những gì tôi đang cố gắng để làm:Ai đó có thể giúp làm rõ cách hoạt động của tệp tiêu đề?

//main.cpp 

#include "main.h" 
int main() { 
    return 0; 
} 

-

//main.h 

#include "player.h" 
#include "health.h" 
#include "custvector.h" 

-

//player.h 

#include "main.h" 
class Player { 
    private: 
     Vector playerPos; 
    public: 
     Health playerHealth; 
}; 

-

//custvector.h 

struct Vector { 
    int X; 
    int Y; 
    int Z; 
}; 

-

//health.h 
class Health { 
    private: 
     int curHealth; 
     int maxHealth; 
    public: 
     int getHealth() const; 
     void setHealth(int inH); 
     void modHealth(int inHM); 
}; 

Tôi sẽ không bao gồm health.cpp vì nó hơi dài (nhưng không hoạt động), nó có #include "health.h".

Dù sao, trình biên dịch (Code :: Blocks) phàn nàn rằng "player.h" không thể tìm thấy loại 'Health' hoặc 'Vector'. Tôi nghĩ rằng nếu tôi sử dụng #include "main.h" vào "player.h" nó sẽ có thể tìm thấy các định nghĩa cho HealthVector cảm giác chúng được bao gồm trong "main.h". Tôi nghĩ rằng họ sẽ sắp xếp đường hầm theo cách của họ mặc dù (player.h -> main.h -> health.h). Nhưng điều đó không hiệu quả lắm. Có một số loại biểu đồ hoặc video có thể làm rõ cách thức này nên được thiết lập không? Google không giúp được gì nhiều (cũng không phải sách của tôi).

+1

Nó không trả lời câu hỏi của bạn, nhưng bạn nên thay đổi cấu trúc Vector thành một tên khác. Nó sẽ phát triển khó hiểu khi bạn bắt đầu sử dụng std :: vector, và một điểm trong không gian 3D không thực sự là một vectơ. – reuscam

+0

Cảm ơn, tôi sẽ làm điều đó. Và bạn nói đúng, nó nên là Point hoặc một cái gì đó. –

+0

Thực ra, một véc-tơ được xác định chỉ bằng một điểm. – Spidey

Trả lời

8

Các câu trả lời khác ở đây đã giải thích hiệu quả cách thức tệp tiêu đề và công việc tiền xử lý. Vấn đề lớn nhất bạn có là phụ thuộc vòng tròn, mà từ kinh nghiệm, tôi biết có thể là một nỗi đau hoàng gia. Ngoài ra, khi điều đó bắt đầu xảy ra, trình biên dịch bắt đầu hoạt động theo những cách rất kỳ quặc và ném các thông báo lỗi không phải là siêu hữu ích. Phương pháp tôi đã được dạy bởi một C++ guru ở trường đại học là để bắt đầu mỗi tập tin (một tập tin header ví dụ) với

//very beginning of the file 
#ifndef HEADER_FILE_H //use a name that is unique though!! 
#define HEADER_FILE_H 
... 
//code goes here 
... 
#endif 
//very end of the file 

này sử dụng chỉ thị tiền xử lý để tự động ngăn chặn phụ thuộc vòng tròn. Về cơ bản, tôi luôn luôn sử dụng một phiên bản tất cả các chữ hoa của tên tập tin. custom-vector.h trở thành

#ifndef CUSTOM_VECTOR_H 
#define CUSTOM_VECTOR_H 

này cho phép bạn bao gồm các file willie-nillie mà không cần tạo phụ thuộc vòng tròn bởi vì nếu một tập tin được bao gồm nhiều lần, biến tiền xử lý của nó đã được xác định, do đó tiền xử lý skips tập tin. Nó cũng làm cho nó dễ dàng hơn sau này để làm việc với mã bởi vì bạn không cần phải sàng lọc thông qua các tập tin tiêu đề cũ của bạn để đảm bảo rằng bạn chưa bao gồm một cái gì đó. Tôi sẽ lặp lại một lần nữa mặc dù, hãy chắc chắn rằng các tên biến mà bạn sử dụng trong các câu lệnh #define của bạn là duy nhất cho bạn nếu không bạn có thể gặp phải các vấn đề mà một thứ không được bao gồm đúng ;-).

Chúc may mắn!

+0

Hy vọng bạn không nhớ: Tôi đã chỉnh sửa ví dụ CUSTOM_VECTOR_CPP của bạn . Các tệp '* .c' và' * .cpp' không cần bao gồm các bảo vệ vì bạn không # include chúng. –

+0

Vì vậy, ví dụ, nếu tôi tạo ra một lớp Enemy cần Vector và Health, và lớp Player đã bao gồm Vector và Health, tôi sẽ không cần phải đưa chúng vào Enemy? –

+0

@John Không sao cả!Cuộc gọi tốt, tôi đã quen với lập trình mẫu, nơi bạn phải bao gồm các tệp * .cpp trong tệp * .h. Tôi vừa nhận được thói quen làm việc đó :-) –

3

Bạn có phụ thuộc vòng tròn. Player bao gồm main.h, nhưng main.h bao gồm player.h. Giải quyết vấn đề này bằng cách xóa một phụ thuộc này hoặc phụ thuộc khác. \

Trình phát.h phải bao gồm health.h và custvector.h và tại thời điểm này, tôi không nghĩ main.h cần bất kỳ bao gồm. Cuối cùng nó có thể cần player.h.

+1

#ifndef ... #endif là một cách khác, hơi dễ dàng hơn để đi cũng như –

+0

Sẽ không có vấn đề gì nếu một thứ khác cần sử dụng health.h? Sẽ không bao gồm nó vào chương trình hai lần? –

+1

Cách để tránh bao gồm tệp tiêu đề nhiều lần trong C hoặc C++ là sử dụng trình bảo vệ bao gồm. Mỗi tệp tiêu đề bắt đầu bằng hai dòng '#ifndef UNIQUE_STRING' và' #define UNIQUE_STRING' (thay thế UNIQUE_STRING bằng một số tên mà bạn không '# define' ở nơi khác). Tập tin sau đó kết thúc bằng '# endif'. http://en.wikipedia.org/wiki/Include_guard –

2

bao gồm công việc rất đơn giản, chúng chỉ cần xử lý tiền tố để thêm nội dung của tệp vào nơi bao gồm được đặt. ý tưởng cơ bản là bao gồm các tiêu đề mà bạn phụ thuộc vào. trong player.h bạn nên bao gồm custvector.hHealth.h. Chỉ trong chính player.h, bởi vì tất cả những gì cần thiết sẽ được thực hiện với người chơi. và bạn không cần phải bao gồm main.h trong tất cả player.h.

cũng tốt để đảm bảo rằng tiêu đề chỉ được bao gồm một lần. trong câu hỏi chung giải pháp này được đưa ra How to prevent multiple definitions in C? trong trường hợp của Visual Studio bạn có thể sử dụng #pragma once, nếu Borland C++ đó cũng là một thủ thuật nhưng tôi quên nó.

+0

Tôi đoán đó là những gì tôi đã quan tâm, tôi không muốn thêm nó hai lần nếu tôi quyết định làm một cái gì đó giống như một lớp Enemy cũng cần Vector và Sức khỏe. Tôi nghĩ nếu tôi đặt tất cả các định nghĩa vào main.h, nó sẽ loại bỏ vấn đề đó. –

+0

@Karl Menke 'main.h' không phải là nơi tốt để đặt nó, bởi vì nó cũng sẽ chứa mọi thứ cho main.cpp. bạn nên tạo một cái gì đó như 'common.h' và di chuyển các tham chiếu phổ biến ở đó. sau đó sử dụng nó. – Andrey

10

Cách tốt nhất để nghĩ về tệp tiêu đề của bạn là "tự động sao chép và dán".

Một cách hay để suy nghĩ về nó (mặc dù không thực sự được thực hiện như thế nào) là khi bạn biên dịch tệp C hoặc tệp C++, bộ tiền xử lý sẽ chạy trước. Mỗi khi gặp một câu lệnh #include, nó sẽ thực sự dán nội dung của tệp đó thay vì câu lệnh #include. Điều này được thực hiện cho đến khi không có thêm bao gồm. Bộ đệm cuối cùng được chuyển tới trình biên dịch.

này giới thiệu một số phức tạp:

Thứ nhất, nếu A.H bao gồm B.H và B.H bao gồm A.h, bạn đã có một vấn đề. Bởi vì mỗi khi bạn muốn dán A, bạn sẽ cần B và nó sẽ có nội bộ A! Đó là một đệ quy. Vì lý do này, các tệp tiêu đề sử dụng #ifndef, để đảm bảo rằng cùng một phần không được đọc nhiều lần. Điều này có thể xảy ra trong mã của bạn.

Thứ hai, trình biên dịch C của bạn đọc tệp sau khi tất cả các tệp tiêu đề đã được "làm phẳng", vì vậy bạn cần phải xem xét khi tranh luận về những gì được khai báo trước cái gì.

+0

Đó là lời giải thích tôi thích hơn và cách tôi mô tả rõ nhất cách tôi hiểu vấn đề này. Nó đơn giản, trình biên dịch có văn bản và biên dịch nó. Bộ tiền xử lý nhận văn bản và xử lý trước nó. Bao gồm chỉ là một chỉ thị tiền xử lý để NHẬP một số văn bản từ một tệp bên ngoài vào tệp hiện tại. – Spidey

+0

Cảm ơn, điều đó giúp ích. –

+0

+1 Đơn giản và thú vị .. :) – liaK

1

Có loại biểu đồ hoặc video nào có thể làm rõ cách thiết lập này không?

Hãy thử câu trả lời của tôi cho câu hỏi cho câu hỏi "Clean up your #include statements?".

+0

Cảm ơn, tôi sẽ xem xét điều đó. –

+0

@Karl Menke - Có một [tại đây] (http://stackoverflow.com/questions/1598207/odd-circular-dependency-issue/1598257#1598257) – ChrisW

1

Bạn muốn sắp xếp #includes (và thư viện của bạn) cho một số DAG (đồ thị theo hướng, tuần hoàn). Đó là cách phức tạp nói "tránh chu kỳ giữa các tập tin tiêu đề":

Nếu B bao gồm A, A không nên bao gồm B.

Vì vậy, sử dụng "một bậc thầy lớn main.h" không phải là cách tiếp cận đúng, bởi vì nó khó khăn # chỉ bao gồm các phụ thuộc trực tiếp.

Mỗi tệp .cpp phải bao gồm tệp .h của riêng nó. Tệp .h đó chỉ nên bao gồm những thứ mà bản thân nó yêu cầu biên dịch.

Thường không có main.h, bởi vì main.cpp không ai cần định nghĩa chính.

Ngoài ra, bạn sẽ muốn include guards để bảo vệ bạn khỏi nhiều tính năng bao gồm.

Ví dụ

//player.h 
#ifndef PLAYER_H_ 
#define PLAYER_H_ 
#include "vector.h" // Because we use Vector 
#include "health.h" // Because we use Health 
class Player { 
    private: 
     Vector playerPos; 
    public: 
     Health playerHealth; 
}; 
#endif 

-

//vector.h 
#ifndef VECTOR_H_ 
#define VECTOR_H_ 
struct Vector { 
    int X; 
    int Y; 
    int Z; 
}; 
#endif 

-

//health.h 
#ifndef HEALTH_H_ 
#define HEALTH_H_ 
class Health { 
    private: 
     int curHealth; 
     int maxHealth; 
    public: 
     int getHealth() const; 
     void setHealth(int inH); 
     void modHealth(int inHM); 
}; 
#endif 

Thời gian duy nhất bạn muốn tổng hợp một loạt các #include s vào một tiêu đề duy nhất là khi bạn đang cung cấp nó như là một tiện lợi cho một thư viện rất lớn.

Trong ví dụ hiện tại của bạn, bạn đang đi quá ít - mỗi lớp không cần tệp tiêu đề của riêng nó. Nó có thể tất cả đi trong main.cpp.

Trình tiền xử lý c theo nghĩa đen chèn tệp từ #include vào tệp có chứa tệp đó (trừ khi nó đã được chèn vào, đó là lý do tại sao bạn cần có bộ bảo vệ). Nó cho phép bạn sử dụng các lớp được định nghĩa trong các tệp đó bởi vì bây giờ bạn có quyền truy cập vào định nghĩa của chúng.

Các vấn đề liên quan