2011-01-21 37 views
130

tại: http://www.learncpp.com/cpp-tutorial/19-header-files/Tuyên bố về phía trước trong C++ là gì?

Sau đây là đề cập:

add.cpp:

int add(int x, int y) 
{ 
    return x + y; 
} 

main.cpp:

#include <iostream> 

int add(int x, int y); // forward declaration using function prototype 

int main() 
{ 
    using namespace std; 
    cout << "The sum of 3 and 4 is " << add(3, 4) << endl; 
    return 0; 
} 

Chúng tôi sử dụng một bản tuyên bố về phía trước sao cho trình biên dịch sẽ biết "add" là gì khi biên dịch main.cpp. Như đã đề cập trước đó, việc viết các khai báo chuyển tiếp cho mọi hàm bạn muốn sử dụng mà sống trong một tệp khác có thể trở nên tẻ nhạt một cách nhanh chóng.

Bạn có thể giải thích "tờ khai chuyển tiếp" hơn nữa không? Vấn đề là gì nếu chúng ta sử dụng nó trong hàm main()?

+0

A "về phía trước tuyên bố" thực sự chỉ là một tuyên bố. Xem (kết thúc) câu trả lời này: http://stackoverflow.com/questions/1410563/what-is-the-difference-between-a-definition-and-a-declaration/1410632#1410632 – sbi

Trả lời

266

Tại sao phía trước-khai báo là cần thiết trong C++

Trình biên dịch muốn đảm bảo bạn chưa thực hiện lỗi chính tả hoặc thông qua sai số lập luận cho hàm. Vì vậy, nó khẳng định rằng nó lần đầu tiên nhìn thấy một tuyên bố của 'thêm' (hoặc bất kỳ loại nào khác, các lớp hoặc chức năng) trước khi nó được sử dụng. Điều này thực sự chỉ cho phép trình biên dịch thực hiện một công việc tốt hơn để xác nhận mã, và cho phép nó dọn dẹp các đầu lỏng lẻo để nó có thể tạo ra một tệp đối tượng tìm kiếm gọn gàng. Nếu bạn không phải chuyển tiếp khai báo mọi thứ, trình biên dịch sẽ tạo ra một tệp đối tượng có thể chứa thông tin về tất cả các dự đoán có thể có về chức năng 'thêm' có thể là gì. Và trình liên kết sẽ phải chứa logic rất thông minh để thử và tìm ra 'thêm' bạn thực sự muốn gọi, khi hàm 'thêm' có thể tồn tại trong một tệp đối tượng khác mà trình liên kết đang kết hợp với một trình sử dụng thêm để tạo một dll hoặc exe. Có thể người liên kết có thể bị thêm sai. Giả sử bạn muốn sử dụng int add (int a, float b), nhưng vô tình quên ghi nó, nhưng linker tìm thấy một int đã có sẵn (int a, int b) và nghĩ rằng đó là một trong những quyền và được sử dụng để thay thế. Mã của bạn sẽ biên dịch, nhưng sẽ không làm những gì bạn mong đợi.

Vì vậy, chỉ để giữ cho mọi thứ rõ ràng và tránh đoán, trình biên dịch khẳng định bạn khai báo mọi thứ trước khi nó được sử dụng.

Sự khác nhau giữa khai báo và định nghĩa

Là một sang một bên, điều quan trọng là phải biết sự khác biệt giữa một tuyên bố và một định nghĩa. Một khai báo chỉ cung cấp đủ mã để hiển thị cái gì đó trông giống như vậy, vì vậy đối với một hàm, đây là kiểu trả về, quy ước gọi, tên phương thức, các đối số và các kiểu của chúng. Nhưng mã cho phương pháp này là không cần thiết. Đối với một định nghĩa, bạn cần khai báo và sau đó cũng là mã cho hàm đó.

Làm thế nào phía trước-tờ khai có thể làm giảm đáng kể xây dựng lần

Bạn có thể nhận tờ khai của một hàm thành cpp hiện tại của bạn hoặc file .h bởi # includ'ing header đã có chứa một tuyên bố của chức năng. Tuy nhiên, điều này có thể làm chậm biên dịch của bạn, đặc biệt nếu bạn #bao gồm một tiêu đề vào một .h thay vì .cpp của chương trình của bạn, vì mọi thứ mà #includes .h bạn đang viết sẽ kết thúC# include'ing tất cả các tiêu đề bạn đã viết #includes cho quá. Đột nhiên, trình biên dịch có #included các trang và các trang mã mà nó cần biên dịch ngay cả khi bạn chỉ muốn sử dụng một hoặc hai hàm. Để tránh điều này, bạn có thể sử dụng một tuyên bố chuyển tiếp và chỉ cần gõ vào khai báo của hàm mình ở đầu tệp. Nếu bạn chỉ sử dụng một vài hàm, điều này thực sự có thể làm cho việc biên dịch của bạn nhanh hơn so với luôn luôn # bao gồm tiêu đề. Đối với các dự án thực sự lớn, sự khác biệt có thể là một giờ hoặc nhiều hơn thời gian biên dịch mua xuống một vài phút.

Nghỉ tài liệu tham khảo cyclic nơi hai định nghĩa đều sử dụng nhau

Bên cạnh đó, về phía trước-tờ khai có thể giúp bạn phá vỡ chu kỳ. Đây là nơi hai chức năng cố gắng sử dụng lẫn nhau. Khi điều này xảy ra (và đó là điều hoàn toàn hợp lệ để làm), bạn có thể #include một tệp tiêu đề, nhưng tệp tiêu đề đó cố gắng #include tệp tiêu đề bạn hiện đang viết .... sau đó #includes tiêu đề khác , bao gồm # bạn đang viết. Bạn đang mắc kẹt trong một tình huống gà và trứng với mỗi tập tin tiêu đề cố gắng để lại # bao gồm khác. Để giải quyết vấn đề này, bạn có thể chuyển tiếp khai báo các phần bạn cần trong một trong các tệp và để #include ra khỏi tệp đó.

Ví dụ:

file Car.h

#include "Wheel.h" // Include Wheel's definition so it can be used in Car. 
#include <vector> 

class Car 
{ 
    std::vector<Wheel> wheels; 
}; 

file Wheel.h

Hmm ... việc kê khai của xe là cần thiết ở đây là bánh xe có một con trỏ đến một chiếc xe, nhưng Car.h không thể được bao gồm ở đây vì nó sẽ dẫn đến một lỗi trình biên dịch. Nếu Car.h được bao gồm, sau đó sẽ cố gắng bao gồm Wheel.h trong đó bao gồm Car.h mà sẽ bao gồm Wheel.h và điều này sẽ đi mãi mãi, vì vậy thay vào đó trình biên dịch đưa ra một lỗi. Giải pháp là để chuyển tiếp khai báo xe thay vì:

class Car;  // forward declaration 

class Wheel 
{ 
    Car* car; 
}; 

Nếu lớp Wheel đã phương pháp mà cần phải gọi các phương thức của xe, các phương pháp đó có thể được quy định tại Wheel.cpp và Wheel.cpp giờ đây có thể bao gồm Car.h mà không gây ra chu kỳ.

+3

tuyên bố về phía trước cũng cần thiết khi một chức năng thân thiện với hai hoặc nhiều lớp – Barun

+0

Hey Scott, về điểm của bạn về thời gian xây dựng: Bạn có nói đó là một thực tiễn phổ biến/tốt nhất để luôn chuyển tiếp khai báo và bao gồm tiêu đề khi cần thiết trong tệp .cpp ? Từ đọc câu trả lời của bạn nó sẽ xuất hiện nó nên được như vậy, nhưng tôi tự hỏi nếu có bất kỳ cảnh báo? – Zepee

+4

@Zepee Đó là một sự cân bằng. Để xây dựng nhanh chóng, tôi sẽ nói đó là thực hành tốt và tôi khuyên bạn nên thử nó. Tuy nhiên, nó có thể mất một số nỗ lực và thêm dòng mã có thể cần phải được duy trì và cập nhật nếu tên loại vv vẫn đang được thay đổi (mặc dù công cụ đang nhận được tốt hơn đổi tên công cụ tự động). Vì vậy, có một sự cân bằng. Tôi đã nhìn thấy các cơ sở mã mà không ai làm phiền. Nếu bạn thấy mình lặp đi lặp lại cùng một định nghĩa chuyển tiếp, bạn luôn có thể đặt chúng vào một tệp tiêu đề riêng biệt và bao gồm, chẳng hạn như: http://stackoverflow.com/questions/4300696/what-is-the-iosfwd-header –

10

Vì C++ được phân tích cú pháp từ trên xuống, trình biên dịch cần biết về mọi thứ trước khi chúng được sử dụng. Vì vậy, khi bạn tham khảo:

int add(int x, int y) 

trong chức năng chính trình biên dịch cần biết nó tồn tại. Để chứng minh điều này, hãy thử di chuyển nó xuống bên dưới chức năng chính và bạn sẽ gặp lỗi trình biên dịch.

Vì vậy, một 'Chuyển tiếp tờ khai' chỉ là những gì nó nói trên tin. Nó tuyên bố một cái gì đó trước khi sử dụng nó.

Nói chung, bạn sẽ bao gồm các khai báo chuyển tiếp trong tệp tiêu đề và sau đó bao gồm tệp tiêu đề đó theo cùng cách mà iostream được bao gồm.

0

Một vấn đề là, trình biên dịch không biết, loại giá trị nào được phân phối bởi hàm của bạn; giả định rằng hàm trả về một số int trong trường hợp này, nhưng điều này có thể đúng vì nó có thể sai. Một vấn đề khác là, trình biên dịch không biết, loại đối số nào mà hàm của bạn mong đợi, và không thể cảnh báo bạn, nếu bạn đang truyền các giá trị của kiểu sai. Có các quy tắc "khuyến mãi" đặc biệt áp dụng khi chuyển, cho biết các giá trị dấu phẩy động đến một hàm không khai báo (trình biên dịch phải mở rộng chúng thành kiểu gấp đôi), thường là không, chức năng thực sự mong đợi, dẫn đến khó tìm lỗi trong thời gian chạy.

1

Khi trình biên dịch thấy add(3, 4), cần biết điều đó có nghĩa là gì. Với khai báo về phía trước bạn về cơ bản nói với trình biên dịch rằng add là một hàm lấy hai int và trả về một int. Đây là thông tin quan trọng cho trình biên dịch becaus nó cần phải đặt 4 và 5 trong đại diện chính xác vào ngăn xếp và cần phải biết những gì loại điều trở lại bằng cách thêm là.

Lúc đó, trình biên dịch là không lo lắng về việc thực hiện thực tế của add, tức là nó ở đâu (hoặc nếu có dù chỉ một) và nếu nó biên dịch. Điều đó được đưa vào xem sau, sau khi biên dịch các tệp nguồn khi trình liên kết được gọi.

1
int add(int x, int y); // forward declaration using function prototype 

bạn có thể giải thích "tuyên bố về phía trước" thêm nhiều hơn? Vấn đề là gì nếu chúng ta sử dụng nó trong hàm main()?

Giống như #include"add.h". Nếu bạn biết, bộ tiền xử lý mở rộng tệp mà bạn đề cập trong #include, trong tệp .cpp nơi bạn viết chỉ thị #include. Điều đó có nghĩa, nếu bạn viết #include"add.h", bạn sẽ nhận được điều tương tự, nó giống như khi bạn thực hiện "tuyên bố chuyển tiếp".

Tôi giả định rằng add.h có dòng này:

int add(int x, int y); 
0

một phụ lục nhanh chóng liên quan đến: thường bạn đặt những tài liệu tham khảo về phía trước vào một tập tin tiêu đề thuộc .c (pp) nộp nơi hàm/biến vv được thực hiện. trong ví dụ của bạn nó sẽ trông như thế này: add.h:

extern int add(int a, int b); 

các bang extern từ khóa đó hàm được thực sự tuyên bố trong một tập tin bên ngoài (cũng có thể là một thư viện vv). main.c của bạn sẽ trông như thế này:

 
#include 
#include "add.h" 

int main() 
{ 
. 
. 
. 

+0

Nhưng, đừng chúng tôi chỉ đặt các khai báo trong tệp tiêu đề? Tôi nghĩ rằng đây là lý do tại sao chức năng được định nghĩa trong "add.cpp", và do đó sử dụng các khai báo chuyển tiếp? Cảm ơn. – Simplicity

23

Trình biên dịch tìm kiếm mỗi biểu tượng đang được sử dụng trong đơn vị dịch hiện tại đã được khai báo trước đó hoặc không có trong đơn vị hiện tại. Nó chỉ là vấn đề về phong cách cung cấp tất cả các chữ ký phương thức ở đầu tệp nguồn trong khi các định nghĩa được cung cấp sau này. Việc sử dụng quan trọng của nó là khi bạn sử dụng một con trỏ đến một lớp như biến thành viên của một lớp khác.

//foo.h 
class bar; // This is useful 
class foo 
{ 
    bar* obj; // Pointer or even a reference. 
}; 

// foo.cpp 
#include "bar.h" 
#include "foo.h" 

Vì vậy, hãy sử dụng các khai báo chuyển tiếp trong các lớp học khi có thể. Nếu chương trình của bạn chỉ có các hàm (với các tệp tiêu đề ho), thì việc cung cấp các nguyên mẫu ngay từ đầu chỉ là vấn đề về phong cách. Trường hợp này sẽ xảy ra nếu tập tin tiêu đề xuất hiện trong một chương trình bình thường với tiêu đề chỉ có chức năng.

7

Thuật ngữ "về phía trước khai" trong C++ được chủ yếu chỉ được sử dụng cho các tờ khai lớp. Xem (phần cuối) this answer vì lý do "tuyên bố chuyển tiếp" của một lớp thực sự chỉ là một khai báo lớp đơn giản đơn giản với tên lạ mắt.

Nói cách khác, sự "chuyển tiếp" chỉ thêm dằn đến hạn, như bất kỳ tuyên bố thể được xem như là mong trong chừng mực nó tuyên bố một số định danh trước nó được sử dụng.

(Như những gì là một tuyên bố như trái ngược với một nét, một lần nữa nhìn thấy What is the difference between a definition and a declaration?)

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