Khai báo chuyển tiếp trong C ++ là gì?


215

Tại: http://www.learncpp.com/cpp-tutorial/19-header-files/

Sau đây được đề cập:

add.cpp:

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

chính.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 khai báo chuyển tiếp để trình biên dịch sẽ biết " add" là gì khi biên dịch main.cpp. Như đã đề cập trước đây, việc viết các khai báo chuyển tiếp cho mọi chức năng bạn muốn sử dụng mà sống trong một tệp khác có thể nhanh chóng trở nên tẻ nhạt.

Bạn có thể giải thích " tuyên bố chuyển tiếp " hơn nữa? Vấn đề là gì nếu chúng ta sử dụng nó trong main()chức năng?


1
Một "tuyên bố chuyển tiếp" thực sự chỉ là một tuyên bố. Xem (phần cuối) câu trả lời này: stackoverflow.com/questions/1410563/iêu
sbi

Câu trả lời:


381

Tại sao khai báo chuyển tiếp là cần thiết trong C ++

Trình biên dịch muốn đảm bảo bạn chưa mắc lỗi chính tả hoặc chuyển sai số lượng đối số cho hàm. Vì vậy, nó khẳng định rằng lần đầu tiên nó nhìn thấy một tuyên bố 'thêm' (hoặc bất kỳ loại, lớp hoặc hàm nào khác) 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 công việc xác thực mã tốt hơn và cho phép nó dọn dẹp các đầu lỏng để nó có thể tạo ra một tệp đối tượng trông 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 phải chứa thông tin về tất cả các dự đoán có thể 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 'add' mà bạn thực sự muốn gọi, khi chức năng 'add' có thể nằm 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 tệp sử dụng add để sản xuất một dll hoặc exe. Có thể là trình liên kết có thể nhận được 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 viết nó, nhưng trình liên kết đã tìm thấy một int add đã có sẵn (int a, int b) và nghĩ rằng đó là một cái đúng và sử dụng nó 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, vv, 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 biệt giữa khai báo và định nghĩa

Bên cạnh đó, đ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ị một cái gì đó trông như thế nào, vì vậy đối với một hàm, đây là kiểu trả về, gọi quy ước, tên phương thức, đối số và kiểu của chúng. Nhưng mã cho phương thức không bắt buộc. Đối với một định nghĩa, bạn cần khai báo và sau đó là mã cho hàm.

Làm thế nào khai báo chuyển tiếp có thể giảm đáng kể thời gian xây dựng

Bạn có thể lấy khai báo của hàm vào tệp .cpp hoặc .h hiện tại của mình bằng cách # bao gồm tiêu đề đã chứa khai báo của hàm. Tuy nhiên, điều này có thể làm chậm quá trình biên dịch của bạn, đặc biệt nếu bạn # bao gồm một tiêu đề thành .h thay vì .cpp của chương trình của bạn, vì mọi thứ bao gồm .h bạn đang viết sẽ kết thúc # bao gồm tất cả các tiêu đề bạn đã viết #incoles cho quá. Đột nhiên, trình biên dịch 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 khai báo chuyển tiếp và chỉ cần tự khai báo hàm ở đầu tệp. Nếu bạn chỉ sử dụng một vài chức năng, điều này thực sự có thể làm cho trình biên dịch của bạn nhanh hơn so với việc luôn luôn bao gồm tiêu đề. Đối với các dự án thực sự lớn,

Phá vỡ các tham chiếu theo chu kỳ trong đó hai định nghĩa đều sử dụng lẫn nhau

Ngoài ra, khai báo chuyển tiếp có thể giúp bạn phá vỡ các chu kỳ. Đây là nơi hai chức năng cả hai cố gắng sử dụng lẫn nhau. Khi điều này xảy ra (và đó là một điều hoàn toàn hợp lệ để làm), bạn có thể #inc loại trừ một tệp tiêu đề, nhưng tệp tiêu đề đó cố gắng #inc loại trừ tệp tiêu đề bạn hiện đang viết .... sau đó loại bỏ tiêu đề khác , trong đó bao gồm những gì bạn đang viết. Bạn đang bị mắc kẹt trong tình huống gà và trứng với mỗi tệp tiêu đề đang cố gắng loại bỏ lại cái 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à loại bỏ #incolee khỏi tệp đó.

Ví dụ:

Hồ sơ xe.h

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

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

Hồ sơ bánh xe.h

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

class Car;     // forward declaration

class Wheel
{
    Car* car;
};

Nếu lớp Wheel có các phương thức cần gọi các phương thức của ô tô, các phương thức đó có thể được định nghĩa trong Wheel.cpp và Wheel.cpp hiện có thể bao gồm Car.h mà không gây ra chu kỳ.


4
khai báo chuyển tiếp cũng là 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

1
Xin chào Scott, về quan điểm của bạn về thời gian xây dựng: Bạn có nói rằng đó là cách thông thường / tốt nhất để luôn chuyển tiếp khai báo và bao gồm các tiêu đề khi cần trong tệp .cpp? Từ việc đọc câu trả lời của bạn, nó sẽ xuất hiện, nhưng tôi tự hỏi liệu có bất kỳ cảnh báo nào không?
Zepee 11/2/2015

5
@Zepee Đó là một sự cân bằng. Để xây dựng nhanh, tôi sẽ nói rằng đó là một thực hành tốt và tôi khuyên bạn nên thử nó. Tuy nhiên, có thể cần một số nỗ lực và các dòng mã bổ sung có thể cần đượ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ác công cụ đang trở nên tốt hơn trong việc đổi tên công cụ tự động). Thế là có sự đánh đổi. Tôi đã thấy các cơ sở mã nơi không ai bận tâm. Nếu bạn thấy mình 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 nó, đại loại như: stackoverflow.com/questions/4300696/what-is-the-iosfwd-header
Scott Langham

khai báo chuyển tiếp là bắt buộc khi các tệp tiêu đề liên quan đến nhau: ví dụ stackoverflow.com/questions/396084/iêu
Nicholas Hamilton

1
Tôi có thể thấy điều này cho phép các nhà phát triển khác trong nhóm của tôi trở thành những công dân thực sự tồi của codebase. Nếu bạn không yêu cầu một nhận xét với tuyên bố chuyển tiếp, như // From Car.hsau đó bạn có thể tạo ra một số tình huống đầy lông đang cố gắng tìm một định nghĩa xuống đường, đảm bảo.
Dagroom

25

Trình biên dịch tìm kiếm từng ký hiệu đang được sử dụng trong đơn vị dịch hiện tại được khai báo trước đó hoặc không trong đơn vị hiện tại. Đây chỉ là vấn đề về phong cách cung cấp tất cả chữ ký phương thức ở đầu tệp nguồn trong khi các định nghĩa được cung cấp sau. Việc sử dụng đáng kể của nó là khi bạn sử dụng một con trỏ tới một lớp làm 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, sử dụng khai báo chuyển tiếp trong các lớp khi có thể. Nếu chương trình của bạn chỉ có các chức năng (với các tệp tiêu đề ho), thì việc cung cấp các nguyên mẫu ở đầu chỉ là vấn đề về kiểu dáng. Dù sao thì đây cũng là trường hợp xảy ra nếu tệp tiêu đề có mặt trong một chương trình bình thường với tiêu đề chỉ có chức năng.


12

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ề những 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 hàm main 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 ' Tuyên bố chuyển tiếp ' chỉ là những gì nó nói trên tin. Đó là 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 một tệp tiêu đề và sau đó bao gồm tệp tiêu đề đó giống như cách bao gồm iostream .


12

Thuật ngữ " khai báo chuyển tiếp " trong C ++ hầu hết chỉ được sử dụng cho khai báo lớp . Xem (phần cuối) câu trả lời này để biết lý do tại sao "khai báo 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 với một tên lạ mắt.

Nói cách khác, "chuyển tiếp" chỉ thêm dằn cho thuật ngữ, vì bất kỳ tuyên bố nào cũng có thể được xem là chuyển tiếp cho đến khi nó tuyên bố một số định danh trước khi nó được sử dụng.

(Đối với khai báo trái ngược với định nghĩa là gì , hãy xem lại Sự khác biệt giữa định nghĩa và khai báo là gì? )


2

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

Tại thời điểm đó, trình biên dịch không lo lắng về thực tế thực hiện 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 đó xuất hiện 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ố chuyển tiếp" hơn nữa? Vấn đề là gì nếu chúng ta sử dụng nó trong hàm main ()?

Nó giống như #include"add.h". Nếu bạn biết, bộ tiền xử lý sẽ mở rộng tệp mà bạn đề cập đến #include, trong tệp .cpp nơi bạn viết lệnh #include. Điều đó có nghĩa là, 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ả sử rằng add.hcó dòng này:

int add(int x, int y); 

1

một phụ lục nhanh liên quan đến: thông thường bạn đặt các tham chiếu chuyển tiếp đó vào một tệp tiêu đề thuộc về tệp .c (pp) trong đó hàm / biến, v.v. trong ví dụ của bạn nó sẽ trông như thế này: add.h:

extern int add (int a, int b);

từ khóa extern nói rằng hàm thực sự được khai báo trong một tệp bên ngoài (cũng có thể là một thư viện, v.v.). main.c của bạn sẽ trông như thế này:

#incolee 
#inc loại "add.h"

int chính ()
{
.
.
.


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

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; là giả định, rằng hàm trả về một inttrong trường hợp này, nhưng điều này có thể đúng như 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 chuyển các giá trị của loại sai. Có các quy tắc "khuyến mãi" đặc biệt, áp dụng khi chuyển, nói các giá trị dấu phẩy động cho hàm không được khai báo (trình biên dịch phải mở rộng chúng thành kiểu kép), thường là không, điều mà hàm thực sự mong đợi, dẫn đến khó tìm lỗi trong thời gian chạy.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.