Sự khác biệt giữa một định nghĩa và một tuyên bố là gì?


857

Ý nghĩa của cả hai đều trốn tránh tôi.


91
@Lasse: không đúng. Một định nghĩa cả định nghĩa và tuyên bố ;-)
Steve Jessop

13
Thành thật mà nói, tôi đã gặp rất nhiều khó khăn trong việc học, vì vậy tôi đã không tìm thấy tên rõ ràng. Tôi không có vấn đề với ý nghĩa, chỉ là tên để liên kết với ý nghĩa.
David Thornley

6
Tuy nhiên, đây không phải là một câu hỏi trùng lặp, vì câu hỏi này hỏi về C / C ++, trong khi câu hỏi khác hỏi về tất cả các ngôn ngữ, hoặc không, nói chung. Nó chỉ có các câu trả lời trùng lặp (vì trong câu hỏi khác đó, một số câu trả lời đã chọn bỏ qua tất cả ngôn ngữ ngoại trừ C và / hoặc C ++).
Steve Jessop

5
@DavidThornley Tôi sử dụng thủ thuật này: một định nghĩa đưa ra mô tả chính xác hơn về một biến hoặc hàm đã cho. Để nhớ điều này, tôi nhớ rằng phần giữa của từ "định nghĩa" có phần giống với từ "tốt hơn". :)
Marco Leogrande

4
Thật ngạc nhiên có bao nhiêu tào lao về câu hỏi này. Chỉ cần đi để cho thấy bao nhiêu ngôn ngữ này bị hiểu lầm, và những hiểu lầm đó được truyền bá thường xuyên như thế nào . Thật đáng buồn.
Các cuộc đua nhẹ nhàng trong quỹ đạo

Câu trả lời:


858

Một khai báo giới thiệu một định danh và mô tả loại của nó, có thể là một loại, đối tượng hoặc chức năng. Một khai báo là những gì trình biên dịch cần để chấp nhận các tham chiếu đến định danh đó. Đây là những tuyên bố:

extern int bar;
extern int g(int, int);
double f(int, double); // extern can be omitted for function declarations
class foo; // no extern allowed for type declarations

Một định nghĩa thực sự khởi tạo / thực hiện định danh này. Đó là những gì trình liên kết cần để liên kết các tham chiếu đến các thực thể đó. Đây là các định nghĩa tương ứng với các tuyên bố trên:

int bar;
int g(int lhs, int rhs) {return lhs*rhs;}
double f(int i, double d) {return i+d;}
class foo {};

Một định nghĩa có thể được sử dụng ở nơi khai báo.

Một định danh có thể được khai báo thường xuyên như bạn muốn. Do đó, những điều sau đây là hợp pháp trong C và C ++:

double f(int, double);
double f(int, double);
extern double f(int, double); // the same as the two above
extern double f(int, double);

Tuy nhiên, nó phải được xác định chính xác một lần. Nếu bạn quên định nghĩa một cái gì đó đã được khai báo và được tham chiếu ở đâu đó, thì trình liên kết sẽ không biết liên kết tham chiếu đến cái gì và phàn nàn về một biểu tượng bị thiếu. Nếu bạn xác định một cái gì đó nhiều hơn một lần, sau đó mối liên kết không biết các định nghĩa để tham khảo liên kết đến và phàn nàn về những biểu tượng trùng lặp.


Vì cuộc tranh luận về khai báo lớp là gì so với định nghĩa lớp trong C ++ sẽ tiếp tục (trong câu trả lời và nhận xét cho các câu hỏi khác), tôi sẽ dán một trích dẫn từ tiêu chuẩn C ++ tại đây.
Tại 3.1 / 2, C ++ 03 nói:

Một khai báo là một định nghĩa trừ khi nó [...] là một khai báo tên lớp [...].

3.1 / 3 sau đó đưa ra một vài ví dụ. Trong số đó:

[Thí dụ: [...]
cấu trúc S {int a; int b; }; // định nghĩa S, S :: a và S :: b [...]
cấu trúc S; // khai báo S
Ví dụ

Tóm lại: Tiêu chuẩn C ++ được coi struct x;là một tuyên bốstruct x {};một định nghĩa . (Nói cách khác, "khai báo chuyển tiếp" một cách viết sai , vì không có hình thức khai báo lớp nào khác trong C ++.)

Cảm ơn litb (Johannes Schaub) , người đã đào ra chương thực tế và câu thơ trong một trong những câu trả lời của mình.


2
@unknown: hoặc trình biên dịch của bạn bị hỏng do bạn đã sao chép sai mã sbi. Ví dụ: 6.7.2 (2) trong N1124: "Tất cả các khai báo đề cập đến cùng một đối tượng hoặc hàm sẽ có loại tương thích; nếu không, hành vi không được xác định."
Steve Jessop

4
@Brian: "extern int i;" nói rằng tôi là một int ở đâu đó, đừng lo lắng về nó. "int i;" có nghĩa là tôi là một int và địa chỉ và phạm vi của nó được xác định ở đây.
David Thornley

12
@Brian: Bạn sai rồi. extern int ilà một tuyên bố, vì nó chỉ giới thiệu / chỉ định i. Bạn có thể có bao nhiêu extern int itrong mỗi đơn vị biên dịch như bạn muốn. int ituy nhiên, là một định nghĩa. Nó biểu thị không gian cho số nguyên nằm trong đơn vị dịch thuật này và khuyên người liên kết liên kết tất cả các tham chiếu để ichống lại thực thể này. Nếu bạn có nhiều hơn hoặc ít hơn chính xác một trong những định nghĩa này, trình liên kết sẽ khiếu nại.
sbi

4
@Brian int i;trong tập tin / phạm vi toàn cầu hoặc phạm vi chức năng là một định nghĩa cả trong C và C ++. Trong C vì nó phân bổ lưu trữ và trong C ++ vì nó không có bộ xác định bên ngoài hoặc đặc tả liên kết. Các số tiền này cho cùng một thứ, đó là những gì sbi nói: trong cả hai trường hợp, khai báo này chỉ định đối tượng mà tất cả các tham chiếu đến "i" trong phạm vi đó phải được liên kết.
Steve Jessop

4
@unknown, hãy cẩn thận, bạn không thể xác định lại các thành viên trong phạm vi lớp : struct A { double f(int, double); double f(int, double); };không hợp lệ, tất nhiên. Nó được cho phép ở nơi khác mặc dù. Có một số nơi bạn có thể khai báo mọi thứ, nhưng cũng không xác định: void f() { void g(); }hợp lệ, nhưng không phải là sau : void f() { void g() { } };. Định nghĩa là gì và những gì một tuyên bố có các quy tắc tinh tế khi nói đến các mẫu - hãy cẩn thận! +1 cho một câu trả lời tốt mặc dù.
Julian Schaub - litb

168

Từ phần chuẩn C ++ 3.1:

Một tuyên bố giới thiệu tên vào một đơn vị dịch thuật hoặc xác định lại tên được giới thiệu bởi các tuyên bố trước đó. Một tuyên bố chỉ định việc giải thích và các thuộc tính của những tên này.

Đoạn tiếp theo (nhấn mạnh của tôi) rằng một tuyên bố là một định nghĩa trừ khi ...

... nó khai báo một hàm mà không chỉ định phần thân của hàm:

void sqrt(double);  // declares sqrt

... nó tuyên bố một thành viên tĩnh trong định nghĩa lớp:

struct X
{
    int a;         // defines a
    static int b;  // declares b
};

... nó tuyên bố một tên lớp:

class Y;

... Nó chứa externtừ khóa mà không có bộ khởi tạo hoặc thân hàm:

extern const int i = 0;  // defines i
extern int j;  // declares j
extern "C"
{
    void foo();  // declares foo
}

... Hoặc là một typedefhoặc một usingtuyên bố.

typedef long LONG_32;  // declares LONG_32
using namespace std;   // declares std

Bây giờ vì lý do lớn tại sao điều quan trọng là phải hiểu sự khác biệt giữa một tuyên bố và định nghĩa: Quy tắc Một Định nghĩa . Từ mục 3.2.1 của tiêu chuẩn C ++:

Không có đơn vị dịch thuật nào chứa nhiều hơn một định nghĩa về bất kỳ biến, hàm, loại lớp, kiểu liệt kê hoặc mẫu.


"Khai báo một thành viên tĩnh trong một định nghĩa lớp" - Điều này đúng ngay cả khi thành viên tĩnh được khởi tạo, đúng không? Chúng ta có thể làm ví dụ struct x {static int b = 3; };?
RJFalconer

@RJFalconer Bạn đã đúng; khởi tạo không nhất thiết phải biến một tuyên bố thành một định nghĩa (trái với những gì người ta có thể mong đợi; chắc chắn tôi thấy điều này đáng ngạc nhiên). Sửa đổi của bạn cho ví dụ là thực sự bất hợp pháp trừ khi bcũng được tuyên bố const. Xem stackoverflow.com/a/3536513/1858225daniweb.com/software-development/cpp/threads/140739/ Lỗi .
Kyle Strand

1
Đây là thú vị với tôi. Theo câu trả lời của bạn, dường như trong C ++, một tuyên bố cũng là một định nghĩa (có ngoại lệ), trong khi ở tiêu chuẩn C, nó được đặt theo quan điểm khác (C99, mục 6.7, Tuyên bố): " Định nghĩa về định danh là một tuyên bố cho định danh đó: [theo sau là tiêu chí cho các trường hợp khác nhau] ". Những cách khác nhau để xem xét nó, tôi cho rằng. :)
Victor Zamanian

Tuyên bố là để trình biên dịch chấp nhận một tên (để nói với trình biên dịch rằng tên đó là hợp pháp, tên được giới thiệu với ý định không phải là một lỗi đánh máy). Định nghĩa là nơi một tên và nội dung của nó được liên kết. Định nghĩa được sử dụng bởi trình liên kết để liên kết một tham chiếu tên đến nội dung của tên.
Gab

137

Tuyên bố: "Ở đâu đó, tồn tại một foo."

Định nghĩa: "... và đây rồi!"


3
Tuyên bố là để trình biên dịch chấp nhận một tên (để nói với trình biên dịch rằng tên đó là hợp pháp, tên được giới thiệu với ý định không phải là một lỗi đánh máy). Định nghĩa là nơi một tên và nội dung của nó được liên kết. Định nghĩa được sử dụng bởi trình liên kết để liên kết một tham chiếu tên đến nội dung của tên.
Gab 是

46

Có những trường hợp cạnh thú vị trong C ++ (một số trong C cũng vậy). Xem xét

T t;

Đó có thể là định nghĩa hoặc khai báo, tùy thuộc vào loại T:

typedef void T();
T t; // declaration of function "t"

struct X { 
  T t; // declaration of function "t".
};

typedef int T;
T t; // definition of object "t".

Trong C ++, khi sử dụng các mẫu, có một trường hợp cạnh khác.

template <typename T>
struct X { 
  static int member; // declaration
};

template<typename T>
int X<T>::member; // definition

template<>
int X<bool>::member; // declaration!

Tuyên bố cuối cùng không phải là một định nghĩa. Đó là tuyên bố về một chuyên môn rõ ràng của thành viên tĩnh của X<bool>. Nó nói với trình biên dịch: "Nếu nói đến việc khởi tạo X<bool>::member, thì đừng khởi tạo định nghĩa của thành viên từ mẫu chính, mà sử dụng định nghĩa được tìm thấy ở nơi khác". Để làm cho nó một định nghĩa, bạn phải cung cấp một bộ khởi tạo

template<>
int X<bool>::member = 1; // definition, belongs into a .cpp file.

35

Tờ khai

Các khai báo cho trình biên dịch biết rằng một phần tử hoặc tên chương trình tồn tại. Một tuyên bố giới thiệu một hoặc nhiều tên vào một chương trình. Tuyên bố có thể xảy ra nhiều lần trong một chương trình. Do đó, các lớp, cấu trúc, kiểu liệt kê và các kiểu do người dùng định nghĩa khác có thể được khai báo cho mỗi đơn vị biên dịch.

Định nghĩa

Định nghĩa xác định mã hoặc dữ liệu mà tên mô tả. Một tên phải được khai báo trước khi nó có thể được sử dụng.


Ừm, không phải là bạn thậm chí có thể định nghĩa các lớp và enum trong mỗi đơn vị biên dịch sao? Ít nhất tôi đặt các định nghĩa lớp vào các tiêu đề của mình và bao gồm tất cả chúng. Er, class foo {}; một định nghĩa lớp , phải không?
sbi

1
Đúng. Tuy nhiên, "lớp foo;" là một tuyên bố. Nó nói với trình biên dịch rằng foo là một lớp. "lớp foo {};" là một định nghĩa. Nó cho trình biên dịch biết chính xác loại foo của lớp là gì.
David Thornley

1
Ngoại lệ là tên thành viên lớp có thể được sử dụng trước khi chúng được khai báo.
Julian Schaub - litb

1
Vâng, đó là những gì tôi muốn nói. Vì vậy, bạn có thể làm như sau: struct foo {void b () {f (); } void f (); }, f hiển thị ngay cả khi chưa được khai báo. Các công việc sau đây cũng vậy: struct foo {void b (int = bar ()); typedef int bar; };. Nó hiển thị trước khi khai báo trong "tất cả các thân hàm, đối số mặc định, trình khởi tạo ctor-constructor". Không thuộc loại trả về :(
Johannes Schaub - litb

1
@litb: Không thể nhìn thấy trước khi khai báo, chỉ có điều là việc sử dụng mã định danh được di chuyển phía sau khai báo. Vâng, tôi biết, hiệu quả là giống nhau cho nhiều trường hợp. Nhưng không phải cho tất cả các trường hợp, đó là lý do tại sao tôi nghĩ chúng ta nên sử dụng lời giải thích chính xác. - Rất tiếc, chờ đợi. Nó được hiển thị trong các đối số mặc định? Vâng, điều đó chắc chắn tàn phá sự hiểu biết của tôi. Chết tiệt! <pouts>
sbi

22

Từ tiêu chuẩn C99, 6,7 (5):

Một khai báo chỉ định việc giải thích và các thuộc tính của một bộ định danh. Một định nghĩa của một định danh là một tuyên bố cho định danh đó:

  • đối với một đối tượng, làm cho việc lưu trữ được dành riêng cho đối tượng đó;
  • cho một chức năng, bao gồm cơ thể chức năng;
  • đối với một hằng số liệt kê hoặc tên typedef, là khai báo (duy nhất) của mã định danh.

Từ tiêu chuẩn C ++, 3.1 (2):

Một khai báo là một định nghĩa trừ khi nó khai báo một hàm mà không chỉ định thân của hàm, nó chứa hàm xác định bên ngoài hoặc một đặc tả liên kết và không phải là trình khởi tạo cũng như thân hàm, nó khai báo một thành viên dữ liệu tĩnh trong khai báo lớp, nó là một khai báo tên lớp, hoặc nó là một khai báo typedef, khai báo sử dụng hoặc chỉ thị sử dụng.

Sau đó là một số ví dụ.

Thật thú vị (hoặc không, nhưng tôi hơi ngạc nhiên về nó), typedef int myint;là một định nghĩa trong C99, nhưng chỉ là một tuyên bố trong C ++.


@onitherone: Về điều đó typedef, điều đó có nghĩa là nó có thể được lặp lại trong C ++, nhưng không phải trong C99?
sbi

Đó là điều làm tôi ngạc nhiên, và theo như một đơn vị dịch thuật, có sự khác biệt đó. Nhưng rõ ràng một typedef có thể được lặp lại trong C99 trong các đơn vị dịch thuật khác nhau. C không có "quy tắc một định nghĩa" rõ ràng như C ++, vì vậy các quy tắc mà nó chỉ cho phép. C ++ đã chọn thay đổi nó thành một khai báo, nhưng cũng là một quy tắc định nghĩa liệt kê những loại mà nó áp dụng cho, và typedefs không phải là một trong số chúng. Vì vậy, việc lặp lại sẽ được cho phép trong C ++ theo ODR như đã nói, ngay cả khi một typedef là một định nghĩa. Có vẻ kén chọn không cần thiết.
Steve Jessop

... nhưng tôi đoán rằng danh sách đó trong ODR thực sự liệt kê tất cả những điều có thể có định nghĩa về. Nếu vậy, thì danh sách này thực sự dư thừa, và chỉ ở đó để có ích.
Steve Jessop

Định nghĩa ODR của std nói gì về định nghĩa lớp? Chúng phải được lặp đi lặp lại.
sbi

2
@sbi: ODR nói "(1) Không có đơn vị dịch thuật nào chứa nhiều hơn một định nghĩa của bất kỳ ... loại lớp nào" và "(5) Có thể có nhiều hơn một định nghĩa về loại lớp ... trong một chương trình được cung cấp rằng mỗi định nghĩa xuất hiện trong một đơn vị dịch thuật khác nhau "và sau đó một số yêu cầu bổ sung tương đương với" các định nghĩa là như nhau ".
Steve Jessop

17

Từ wiki.answers.com:

Thuật ngữ khai báo có nghĩa (trong C) mà bạn đang nói với trình biên dịch về loại, kích thước và trong trường hợp khai báo hàm, loại và kích thước của các tham số của bất kỳ biến nào, hoặc loại hoặc hàm do người dùng xác định trong chương trình của bạn. Không có không gian được dành riêng trong bộ nhớ cho bất kỳ biến nào trong trường hợp khai báo. Tuy nhiên, trình biên dịch biết có bao nhiêu không gian để dự trữ trong trường hợp một biến loại này được tạo.

ví dụ, sau đây là tất cả các khai báo:

extern int a; 
struct _tagExample { int a; int b; }; 
int myFunc (int a, int b);

Mặt khác, định nghĩa có nghĩa là ngoài tất cả những điều khai báo, không gian cũng được dành riêng trong bộ nhớ. Bạn có thể nói "ĐỊNH NGH = A = KHAI THÁC + ĐỔI KHÔNG GIAN" sau đây là các ví dụ về định nghĩa:

int a; 
int b = 0; 
int myFunc (int a, int b) { return a + b; } 
struct _tagExample example; 

xem câu trả lời .


3
Điều này cũng vậy, là sai (mặc dù gần hơn nhiều so với những cái khác): struct foo {};là một định nghĩa , không phải là một tuyên bố. Một tuyên bố foosẽ được struct foo;. Từ đó, trình biên dịch không biết cần bao nhiêu dung lượng cho foocác đối tượng.
sbi

1
@Marcin: sbi đang nói rằng "trình biên dịch biết có bao nhiêu dung lượng dự trữ trong trường hợp một biến loại này được tạo ra" không phải lúc nào cũng đúng. struct foo;là một khai báo, nhưng nó không cho trình biên dịch biết kích thước của foo. Tôi muốn thêm đó struct _tagExample { int a; int b; };là một định nghĩa. Vì vậy, trong bối cảnh này, thật sai lầm khi gọi nó là một tuyên bố. Tất nhiên nó là một, vì tất cả các định nghĩa là khai báo, nhưng dường như bạn đang gợi ý rằng nó không phải là một định nghĩa. Đó là một định nghĩa, của _tagExample.
Steve Jessop

1
@Marcin Gil: Điều đó có nghĩa là wiki "Câu trả lời" không phải lúc nào cũng chính xác. Tôi phải downvote cho thông tin sai ở đây.
David Thornley

1
Chúng tôi biết rằng những gì adatapost trích dẫn là đúng nhưng không (IMO) thực sự trả lời câu hỏi. Những gì Marcin trích dẫn là sai. Trích dẫn các tiêu chuẩn là đúng và trả lời câu hỏi, nhưng rất khó để thực hiện đầu hoặc đuôi.
Steve Jessop

1
@David Thornley - không thành vấn đề :) Đây là những gì trang web này nói về. Chúng tôi chọn và xác minh thông tin.
Marcin Gil

13

Cập nhật C ++ 11

Vì tôi không thấy câu trả lời thích hợp cho C ++ 11 ở đây.

Một khai báo là một định nghĩa trừ khi nó khai báo a / n:

  • enum đục - enum X : int;
  • tham số mẫu - T intemplate<typename T> class MyArray;
  • khai báo tham số - xy trongint add(int x, int y);
  • tuyên bố bí danh - using IntVector = std::vector<int>;
  • tuyên bố khẳng định tĩnh - static_assert(sizeof(int) == 4, "Yikes!")
  • khai báo thuộc tính (xác định thực hiện)
  • khai báo trống ;

Các mệnh đề bổ sung được kế thừa từ C ++ 03 theo danh sách trên:

  • khai báo hàm - thêm vàoint add(int x, int y);
  • specifier bên ngoài có chứa khai báo hoặc một specifier liên kết - extern int a;hoặcextern "C" { ... };
  • thành viên dữ liệu tĩnh trong một lớp - x inclass C { static int x; };
  • khai báo lớp / struct - struct Point;
  • khai báo typedef - typedef int Int;
  • sử dụng khai báo - using std::cout;
  • sử dụng chỉ thị - using namespace NS;

Một khai báo mẫu là một khai báo. Một khai báo mẫu cũng là một định nghĩa nếu khai báo của nó định nghĩa một hàm, một lớp hoặc một thành viên dữ liệu tĩnh.

Các ví dụ từ tiêu chuẩn phân biệt giữa khai báo và định nghĩa mà tôi thấy hữu ích trong việc tìm hiểu các sắc thái giữa chúng:

// except one all these are definitions
int a;                                  // defines a
extern const int c = 1;                 // defines c
int f(int x) { return x + a; }          // defines f and defines x
struct S { int a; int b; };             // defines S, S::a, and S::b
struct X {                              // defines X
    int x;                              // defines non-static data member x
    static int y;                       // DECLARES static data member y
    X(): x(0) { }                       // defines a constructor of X
};
int X::y = 1;                           // defines X::y
enum { up , down };                     // defines up and down
namespace N { int d; }                  // defines N and N::d
namespace N1 = N;                       // defines N1
X anX;                                  // defines anX


// all these are declarations
extern int a;                           // declares a
extern const int c;                     // declares c
int f(int);                             // declares f
struct S;                               // declares S
typedef int Int;                        // declares Int
extern X anotherX;                      // declares anotherX
using N::d;                             // declares N::d


// specific to C++11 - these are not from the standard
enum X : int;                           // declares X with int as the underlying type
using IntVector = std::vector<int>;     // declares IntVector as an alias to std::vector<int>
static_assert(X::y == 1, "Oops!");      // declares a static_assert which can render the program ill-formed or have no effect like an empty declaration, depending on the result of expr
template <class T> class C;             // declares template class C
;                                       // declares nothing

6

Định nghĩa :

extern int a;      // Declaration 
int a;             // Definition
a = 10             // Initialization
int b = 10;        // Definition & Initialization

Định nghĩa liên kết biến với một loại và phân bổ bộ nhớ, trong khi khai báo chỉ xác định loại nhưng không phân bổ bộ nhớ. Khai báo hữu ích hơn khi bạn muốn tham chiếu biến trước khi định nghĩa.

* Đừng nhầm lẫn định nghĩa với khởi tạo. Cả hai đều khác nhau, khởi tạo mang lại giá trị cho biến. Xem ví dụ trên.

Sau đây là một số ví dụ về định nghĩa.

int a;
float b;
double c;

Bây giờ chức năng khai báo:

int fun(int a,int b); 

Lưu ý dấu chấm phẩy ở cuối hàm để nó nói nó chỉ là một khai báo. Trình biên dịch biết rằng ở đâu đó trong chương trình chức năng đó sẽ được xác định với nguyên mẫu đó. Bây giờ nếu trình biên dịch có một hàm gọi một cái gì đó như thế này

int b=fun(x,y,z);

Trình biên dịch sẽ đưa ra một lỗi nói rằng không có chức năng đó. Bởi vì nó không có bất kỳ nguyên mẫu nào cho chức năng đó.

Lưu ý sự khác biệt giữa hai chương trình.

Chương trình 1

#include <stdio.h>
void print(int a)
{
     printf("%d",a);
}
main()
{
    print(5);
}

Trong đó, chức năng in được khai báo và định nghĩa là tốt. Vì chức năng gọi đến sau định nghĩa. Bây giờ xem chương trình tiếp theo.

Chương trình 2

 #include <stdio.h>
 void print(int a); // In this case this is essential
 main()
 {
    print(5);
 }
 void print(int a)
 {
     printf("%d",a);
 }

Đó là điều cần thiết bởi vì hàm gọi trước định nghĩa để trình biên dịch phải biết liệu có bất kỳ hàm nào như vậy không. Vì vậy, chúng tôi khai báo hàm sẽ thông báo cho trình biên dịch.

Định nghĩa :

Phần xác định hàm này được gọi là Định nghĩa. Nó nói phải làm gì bên trong chức năng.

void print(int a)
{
    printf("%d",a);
}

2
int a; //declaration; a=10; //definitionĐiều này là hoàn toàn sai. Khi nói về các đối tượng thời lượng lưu trữ tự động (các đối tượng được khai báo bên trong một định nghĩa hàm không được khai báo với một công cụ xác định lớp lưu trữ khác như extern), đây luôn là các định nghĩa.
Joey Pabalinas

Sự khác biệt chính cần nắm bắt là một tuyên bố đang nói "một thứ tồn tại ở đâu đó có những đặc điểm này (loại v.v.)", trong khi một định nghĩa là "Tôi đang tuyên bố một điều với những đặc điểm này, và tôi cũng đang mô tả nó ở đây như là tốt." Vì bạn không thể chuyển tiếp khai báo các đối tượng thời lượng lưu trữ tự động như thế, chúng sẽ luôn là các định nghĩa.
Joey Pabalinas

Ngoại trừ có thể một số trường hợp góc typedef kỳ lạ mà tôi luôn quên, một nguyên tắc nhỏ là tất cả các định nghĩa đều là khai báo. Nghĩ về nó; Khi bạn đang khởi tạo một cái gì đó, bạn cũng cần nói với trình biên dịch rằng thứ đó tồn tại và những đặc điểm của nó là đúng?
Joey Pabalinas

Cập nhật câu trả lời theo nhận xét đầu tiên của bạn. tuy nhiên tôi không đồng ý với nhận xét này "khi bạn đang bắt đầu một cái gì đó, bạn cũng cần nói với trình biên dịch rằng thứ đó tồn tại". Chúng tôi không luôn chỉ định loại lhs khi bắt đầu. Vd: a = 10. Chúng tôi không chỉ định bất kỳ "đặc điểm" nào ở đây.
SRIDHARAN

4

định nghĩa có nghĩa là hàm thực sự được viết và khai báo nghĩa là hàm khai báo đơn giản cho vd

void  myfunction(); //this is simple declaration

void myfunction()
{
 some statement;    
}

đây là định nghĩa của hàm chức năng


1
Và những gì về các loại và các đối tượng?
sbi

4

Quy tắc của ngón tay cái:

  • Một khai báo cho trình biên dịch biết cách diễn giải dữ liệu của biến trong bộ nhớ. Điều này là cần thiết cho mọi truy cập.

  • Một định nghĩa dự trữ bộ nhớ để làm cho biến hiện có. Điều này phải xảy ra chính xác một lần trước khi truy cập đầu tiên.


2
Điều này chỉ giữ cho các đối tượng. Còn các loại và chức năng thì sao?
Các cuộc đua nhẹ nhàng trong quỹ đạo

4

Để hiểu các danh từ, hãy tập trung vào các động từ trước.

tuyên bố - công bố chính thức; tuyên bố

định nghĩa - để hiển thị hoặc mô tả (ai đó hoặc một cái gì đó) rõ ràng và đầy đủ

Vì vậy, khi bạn tuyên bố một cái gì đó, bạn chỉ cần nói nó là gì .

// declaration
int sum(int, int);

Dòng này khai báo một hàm C được gọi sumcó hai đối số kiểu intvà trả về một int. Tuy nhiên, bạn chưa thể sử dụng nó.

Khi bạn cung cấp cách nó thực sự hoạt động , đó là định nghĩa của nó.

// definition
int sum(int x, int y)
{
    return x + y;
}

3

Để hiểu sự khác biệt giữa khai báo và định nghĩa, chúng ta cần xem mã lắp ráp:

uint8_t   ui8 = 5;  |   movb    $0x5,-0x45(%rbp)
int         i = 5;  |   movl    $0x5,-0x3c(%rbp)
uint32_t ui32 = 5;  |   movl    $0x5,-0x38(%rbp)
uint64_t ui64 = 5;  |   movq    $0x5,-0x10(%rbp)
double   doub = 5;  |   movsd   0x328(%rip),%xmm0        # 0x400a20
                        movsd   %xmm0,-0x8(%rbp)

và đây chỉ là định nghĩa:

ui8 = 5;   |   movb    $0x5,-0x45(%rbp)
i = 5;     |   movl    $0x5,-0x3c(%rbp)
ui32 = 5;  |   movl    $0x5,-0x38(%rbp)
ui64 = 5;  |   movq    $0x5,-0x10(%rbp)
doub = 5;  |   movsd   0x328(%rip),%xmm0        # 0x400a20
               movsd   %xmm0,-0x8(%rbp)

Như bạn có thể thấy không có gì thay đổi.

Khai báo khác với định nghĩa vì nó cung cấp thông tin chỉ được sử dụng bởi trình biên dịch. Ví dụ uint8_t báo cho trình biên dịch sử dụng hàm asm Movb.

Xem đó:

uint def;                  |  no instructions
printf("some stuff...");   |  [...] callq   0x400450 <printf@plt>
def=5;                     |  movb    $0x5,-0x45(%rbp)

Tuyên bố không có một hướng dẫn tương đương vì nó không phải là một cái gì đó để được thực thi.

Hơn nữa khai báo cho trình biên dịch phạm vi của biến.

Chúng ta có thể nói rằng khai báo là một thông tin được sử dụng bởi trình biên dịch để thiết lập việc sử dụng đúng biến và trong bao lâu một số bộ nhớ thuộc về biến nhất định.


2

Bạn không thể nói theo các thuật ngữ chung nhất có thể, rằng một tuyên bố là một định danh trong đó không có lưu trữ được phân bổ và một định nghĩa thực sự phân bổ lưu trữ từ một định danh khai báo?

Một suy nghĩ thú vị - một mẫu không thể phân bổ lưu trữ cho đến khi lớp hoặc hàm được liên kết với thông tin loại. Vì vậy, định danh mẫu là một tuyên bố hoặc định nghĩa? Nó phải là một khai báo vì không có lưu trữ nào được phân bổ và bạn chỉ đơn giản là 'tạo mẫu' cho lớp hoặc hàm mẫu.


1
Định nghĩa của bạn không sai, nhưng "định nghĩa lưu trữ" luôn có vẻ khó xử khi nói đến định nghĩa hàm. Về mẫu: Đây template<class T> struct foo;là một khai báo mẫu , và đây cũng là một template<class T> void f();. Các định nghĩa mẫu phản ánh các định nghĩa lớp / hàm theo cùng một cách. (Lưu ý rằng tên mẫu không phải là tên loại hoặc tên hàm . Một nơi bạn có thể thấy đây là khi bạn không thể chuyển mẫu dưới dạng tham số loại của mẫu khác. Nếu bạn muốn truyền mẫu thay vì loại, bạn cần tham số mẫu mẫu. )
sbi

Đồng ý rằng 'định nghĩa lưu trữ' là khó xử, đặc biệt là về định nghĩa hàm. Khai báo là int foo () và định nghĩa là int foo () {// một số mã ở đây ..}. Tôi thường cần phải bọc bộ não nhỏ bé của mình bằng các khái niệm mà tôi quen thuộc - 'lưu trữ' là một cách như vậy để giữ nó thẳng với tôi ít nhất ... :)

2

Tìm câu trả lời tương tự ở đây: Câu hỏi phỏng vấn kỹ thuật trong C .

Một tuyên bố cung cấp một tên cho chương trình; một định nghĩa cung cấp một mô tả duy nhất về một thực thể (ví dụ như loại, thể hiện và chức năng) trong chương trình. Tuyên bố có thể được lặp đi lặp lại trong một phạm vi nhất định, nó giới thiệu một tên trong một phạm vi nhất định.

Một tuyên bố là một định nghĩa trừ khi:

  • Tuyên bố khai báo một chức năng mà không chỉ định cơ thể của nó,
  • Khai báo chứa một bộ xác định bên ngoài và không có phần khởi tạo hoặc phần thân hàm,
  • Khai báo là khai báo của một thành viên dữ liệu lớp tĩnh mà không có định nghĩa lớp,
  • Khai báo là một định nghĩa tên lớp,

Một định nghĩa là một tuyên bố trừ khi:

  • Định nghĩa định nghĩa một thành viên dữ liệu lớp tĩnh,
  • Định nghĩa định nghĩa một hàm thành viên không nội tuyến.

1

Điều này sẽ nghe có vẻ rất nhảm, nhưng đó là cách tốt nhất mà tôi có thể giữ các điều khoản trong đầu:

Tuyên bố: Hình ảnh Thomas Jefferson đưa ra một bài phát biểu ...

Định nghĩa: hình ảnh một từ điển, bạn đang tìm kiếm Foo và ý nghĩa thực sự của nó.


1

Một khai báo trình bày một tên biểu tượng cho trình biên dịch. Một định nghĩa là một tuyên bố phân bổ không gian cho biểu tượng.

int f(int x); // function declaration (I know f exists)

int f(int x) { return 2*x; } // declaration and definition

1

Theo hướng dẫn sử dụng thư viện GNU C ( http://www.gnu.org/software/libc/manual/html_node/Header-Files.html )

Trong C, một khai báo chỉ cung cấp thông tin rằng một hàm hoặc biến tồn tại và đưa ra kiểu của nó. Đối với một khai báo hàm, thông tin về các loại đối số của nó cũng có thể được cung cấp. Mục đích của khai báo là cho phép trình biên dịch xử lý chính xác các tham chiếu đến các biến và hàm được khai báo. Một định nghĩa, mặt khác, thực sự phân bổ lưu trữ cho một biến hoặc cho biết chức năng làm gì.


0

Khái niệm Tuyên bố và Định nghĩa sẽ hình thành một cạm bẫy khi bạn đang sử dụng lớp lưu trữ bên ngoài vì định nghĩa của bạn sẽ ở một số vị trí khác và bạn đang khai báo biến trong tệp mã địa phương (trang). Một điểm khác biệt giữa C và C ++ là trong C, các khai báo được thực hiện bình thường ở đầu một hàm hoặc trang mã. Trong C ++ thì không như vậy. Bạn có thể tuyên bố tại một nơi bạn chọn.


1
Điều này nhầm lẫn khai báo với định nghĩa và là hoàn toàn sai.
sbi

0

Ví dụ yêu thích của tôi là "int Num = 5" ở đây biến của bạn là 1. được định nghĩa là int 2. được khai báo là Num và 3. được khởi tạo với giá trị là năm. Chúng tôi

  • Xác định loại của một đối tượng, có thể được tích hợp sẵn hoặc một lớp hoặc cấu trúc.
  • Khai báo tên của một đối tượng, vì vậy bất cứ điều gì có tên đã được khai báo bao gồm Biến, Funtions, v.v.

Một lớp hoặc struct cho phép bạn thay đổi cách xác định các đối tượng khi nó được sử dụng sau này. Ví dụ

  • Người ta có thể khai báo một biến hoặc mảng không đồng nhất không được xác định cụ thể.
  • Sử dụng phần bù trong C ++, bạn có thể xác định một đối tượng không có tên khai báo.

Khi chúng ta học lập trình, hai thuật ngữ này thường bị nhầm lẫn bởi vì chúng ta thường làm cả hai cùng một lúc.


Tôi không hiểu tại sao rất nhiều người ủng hộ câu trả lời của sbi. Tôi đã đưa ra câu trả lời của bjhend, nó khá hay, ngắn gọn, chính xác và kịp thời hơn nhiều so với của tôi. Tôi rất buồn khi thấy rằng tôi là người đầu tiên làm như vậy trong 4 năm.
Jason K.

0

Các giai đoạn của một thế hệ thực thi:

(1) bộ xử lý trước -> (2) trình biên dịch / trình biên dịch -> (3) trình liên kết

Trong giai đoạn 2 (trình dịch / trình biên dịch), các câu lệnh khai báo trong mã của chúng tôi cho trình biên dịch biết rằng những thứ này chúng ta sẽ sử dụng trong tương lai và bạn có thể tìm thấy định nghĩa sau, nghĩa là:

người dịch chắc chắn rằng: cái gì là cái gì? có nghĩa là khai báo

và (3) giai đoạn (trình liên kết) cần định nghĩa để ràng buộc mọi thứ

Linker đảm bảo rằng: ở đâu là gì? có nghĩa là định nghĩa


0

Có một số định nghĩa rất rõ ràng được rắc khắp K & R (phiên bản 2); nó giúp đặt chúng ở một nơi và đọc chúng như một:

"Định nghĩa" chỉ nơi lưu trữ biến được tạo hoặc chỉ định lưu trữ; "Khai báo" chỉ những nơi mà bản chất của biến được nêu nhưng không có lưu trữ được phân bổ. [p. 33]

...

Điều quan trọng là phải phân biệt giữa khai báo một biến ngoài và định nghĩa của nó . Một khai báo công bố các thuộc tính của một biến (chủ yếu là kiểu của nó); một định nghĩa cũng khiến lưu trữ được đặt sang một bên. Nếu các dòng

int sp;
double val[MAXVAL]

xuất hiện bên ngoài bất kỳ chức năng nào, chúng xác định các biến bên ngoài spval , khiến cho việc lưu trữ được đặt sang một bên và cũng đóng vai trò là khai báo cho phần còn lại của tệp nguồn đó.

Mặt khác, các dòng

extern int sp;
extern double val[];

khai báo cho phần còn lại của tệp nguồn splà một intvà đó vallà mộtdouble mảng (có kích thước được xác định ở nơi khác), nhưng chúng không tạo ra các biến hoặc lưu trữ dự trữ cho chúng.

Chỉ có một định nghĩa về một biến ngoài trong số tất cả các tệp tạo nên chương trình nguồn. ... Kích thước mảng phải được chỉ định với định nghĩa, nhưng là tùy chọn vớiextern khai báo. [Trang. 80-81]

...

Tuyên bố xác định giải thích được đưa ra cho mỗi định danh; họ không nhất thiết phải lưu trữ lưu trữ liên quan đến định danh. Tuyên bố lưu trữ dự trữ được gọi là định nghĩa . [p. 210]


-1

Khai báo nghĩa là đặt tên và loại cho một biến (trong trường hợp khai báo biến), vd:

int i;

hoặc đặt tên, kiểu trả về và loại tham số cho một hàm không có phần thân (trong trường hợp khai báo hàm), ví dụ:

int max(int, int);

trong khi định nghĩa có nghĩa là gán giá trị cho một biến (trong trường hợp định nghĩa biến), vd:

i = 20;

hoặc cung cấp / thêm phần thân (chức năng) cho một hàm được gọi là định nghĩa hàm, ví dụ:

int max(int a, int b)
{
   if(a>b)   return a;
   return b;  
}

nhiều thời gian khai báo và định nghĩa có thể được thực hiện cùng nhau như:

int i=20;

và:

int max(int a, int b)
{
    if(a>b)   return a;
    return b;    
} 

Trong các trường hợp trên, chúng tôi xác định và khai báo biến ifunction max().


giá trị trung bình thực của định nghĩa nếu gán giá trị / phần thân cho một biến / hàm trong khi khai báo có nghĩa là cung cấp tên, loại cho một biến / hàm
Puneet Purohit

Bạn có thể định nghĩa một cái gì đó mà không gán cho nó một giá trị.
Các cuộc đua nhẹ nhàng trong quỹ đạo

1
Giống như thế này:int x;
Các cuộc đua nhẹ nhàng trong quỹ đạo

đó là một tuyên bố của biến x không phải là định nghĩa của nó
Puneet Purohit

2
Không, nó là cả hai. Bạn đang nhầm lẫn định nghĩa với khởi tạo.
Các cuộc đua nhẹ nhàng trong quỹ đạo
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.