定义和声明之间有什么区别?

1037
两者的意义都让我无法理解。

19
说实话,我在学习时很难区分哪个对应哪个名称,所以我觉得这些名称并不明显。我能理解意思,只是不知道该把哪个名称与其对应的意思联系起来。 - David Thornley
2
我们已经详细地讨论过这个问题:https://dev59.com/_nRB5IYBdhLWcg3wSVYI。 - dmckee --- ex-moderator kitten
1
一个更有趣的问题是“声明和原型之间的区别”:https://dev59.com/SW035IYBdhLWcg3wVuh1 - Mooing Duck
1
这是一篇不错的文章,解释了extern关键字和内存分配与声明/定义的关系:http://www.dreamincode.net/forums/topic/171468-declarations-vs-definitions/ - Griffin
1
这可能会有所帮助:http://www.cprogramming.com/declare_vs_define.html - barlop
可能是什么是 C++ 的定义,声明和赋值?的重复问题。 - TylerH
27个回答

1041

声明引入标识符并描述其类型,无论是类型、对象还是函数。声明是编译器接受对该标识符引用所需的内容。以下是声明:

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

定义实际上是将此标识符实例化/实现。这是链接器需要的内容,以便将对这些实体的引用链接起来。这些是与上述声明相对应的定义:

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

定义可以替代声明。

标识符可以被声明任意多次。因此,以下在 C 和 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);
然而,它必须被定义一次。如果您忘记为已声明和引用的内容定义,则链接器不知道要链接到哪个引用,并抱怨缺少符号。如果您定义了多次某些东西,则链接器不知道要将引用链接到哪个定义,并抱怨有重复的符号。

由于在C++中什么是类声明与类定义的争论一直存在(在回答和评论其他问题时),我在这里摘录了C++标准的一句话。
在C++03的3.1/2处,它说:

除非[...]是类名声明[...],否则声明就是定义。

然后,3.1/3给出了一些例子。其中:

[Example: [...]
struct S { int a; int b; }; // 定义了S、S::a和S::b [...]
struct S; // 声明S
—end example

总之:C++标准认为struct x;是一个声明struct x {};是一个定义。(换句话说,“前向声明”是一个错误的名称,因为在C++中没有其他形式的类声明。)

感谢litb(Johannes Schaub)在他的一个回答中挖掘出了实际的章节和篇章。


4
@Brian:“extern int i;” 表示 i 是一个整型变量,但不知道它的具体位置。不用担心这点。而“int i;”则表示 i 是一个整型变量,在此处确定了它的地址和作用域。 - David Thornley
14
@Brian:你错了。extern int i 是一个声明,因为它只是引入或指定了变量 i。在每个编译单元中,你可以有尽可能多的 extern int i。 然而,int i 是一个定义。它表示这个翻译单元中整数所占用的空间,并通知链接器将所有对 i 的引用链接到这个实体上。如果你有多于或少于一个这样的定义,链接器就会报错。 - sbi
5
在C和C++中,文件/全局作用域或函数作用域中的int i;都是定义。在C中,这是因为它分配了存储空间,在C++中也一样,因为它没有extern说明符或链接规范。这两种情况实际上是相同的,正如sbi所说:在这两种情况下,此声明指定了该作用域内所有对“i”的引用必须链接到的对象。 - Steve Jessop
5
注意,你不能在作用域中重新声明成员:struct A { double f(int, double); double f(int, double); };当然是无效的。但其他地方是允许的。有些情况下可以声明,但不能定义:void f() { void g(); }是有效的,但以下不是:void f() { void g() { } }; 在涉及模板时,定义和声明有微妙的规则-请注意!对于好的回答加1。 - Johannes Schaub - litb
2
“定义可以用于声明的位置。”这可能是不正确的。只有在不会引入重复定义的情况下才合法。 - Thomson
显示剩余25条评论

189

根据C++标准第3.1节:

声明将名称引入翻译单元或重新声明由先前声明引入的名称。声明指定这些名称的解释和属性。

下一个段落指出(强调是我的),除非声明一个函数而不指定函数体,否则声明就是定义

void sqrt(double);  // declares sqrt

在类定义中声明静态成员:

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

它声明了一个类名:

class Y;

...它包含了没有初始化器或函数体的extern关键字:

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

...或者是typedefusing语句。

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

了解声明和定义的区别之所以重要,有一个非常重要的原因:C++中的一次定义规则(One Definition Rule)。引用C++标准3.2.1节:

任何翻译单元都不得包含多于一个任何变量、函数、类类型、枚举类型或模板的定义。


1
在类定义中声明静态成员,即使静态成员被初始化了,这仍然是正确的吗?我们可以使用示例 struct x {static int b = 3; }; 吗? - RJFalconer
2
@RJFalconer 没错,初始化并不一定会将声明转换为定义(与人们的预期相反;我当然觉得这很令人惊讶)。你对示例的修改实际上是非法的,除非 b 也被声明为 const。请参见 https://dev59.com/U3A65IYBdhLWcg3w-jym#3536513 和 http://www.daniweb.com/software-development/cpp/threads/140739/taking-an-address。 - Kyle Strand
2
这对我很有趣。根据你的回答,在C++中,声明也是定义(有例外情况),而在C标准中,它从另一个角度表述(C99,第6.7节,“声明”):“标识符的“定义”是指为该标识符声明的声明:[后跟不同情况的标准]”。不同的看法,我想。 :) - Victor Zamanian
1
声明是为了让编译器接受一个名称(告诉编译器该名称是合法的,该名称是有意引入而不是打字错误)。 定义是将名称及其内容关联起来的过程。链接器使用定义将名称引用链接到名称的内容。 - Gab是好人

165

声明: "某个地方存在一个foo。"

定义: "...在这里它就是!"


4
声明是为了让编译器接受一个名称(告诉编译器该名称是合法的,该名称是有意引入而不是打字错误)。 定义是将名称和其内容关联起来的地方。链接器使用定义将名称引用链接到名称的内容。 - Gab是好人

54

在C++(也有一些在C中)中存在一些有趣的边缘情况。请考虑:

T t;

这可以是定义或声明,具体取决于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".

在C++中,当使用模板时,还存在另一种特殊情况。

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

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

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

上一个声明并不是定义。这是对 X<bool> 的静态成员进行显式特化的声明。它告诉编译器:“如果要实例化 X<bool>::member,则不要从主模板实例化成员的定义,而是使用在其他地方找到的定义。” 要将其定义,必须提供初始化程序。

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

37

声明

声明告诉编译器程序元素或名称的存在。一个声明会将一个或多个名称引入程序中。在程序中,声明可以出现多次。因此,每个编译单元都可以为类、结构体、枚举类型和其他用户定义的类型进行声明。

定义

定义指明名称描述的代码或数据。在使用名称之前必须先进行声明。


嗯,你不是可以在每个编译单元中甚至定义类和枚举吗?至少我把类的定义放在头文件中,并在各处引用它们。嗯,class foo {}; 就是一个类的定义,对吧? - sbi
1
是的。然而,“class foo;”是一个声明。它告诉编译器foo是一个类。“class foo {};”是一个定义。它告诉编译器foo是什么样的类。 - David Thornley
1
例外情况是类成员名称可以在声明之前使用。 - Johannes Schaub - litb
1
是的,这就是我想说的。因此,您可以执行以下操作:struct foo {void b() {f(); } void f();},即使未声明,f也可见。以下也有效:struct foo {void b(int = bar()); typedef int bar; };。它在“所有函数主体、默认参数、构造函数ctor-initializers”中的声明之前可见。但不在返回类型中 :( - Johannes Schaub - litb
1
@litb:在它声明之前是不可见的,只是标识符的使用被移动到声明后面。是的,我知道,在许多情况下效果是相同的。但并非所有情况都是如此,这就是为什么我认为我们应该使用精确的解释。-- 哎呀,等等。在默认参数中它是可见的?好吧,那肯定会破坏我的理解。该死!<撅嘴> - sbi
显示剩余2条评论

25

来自C99标准的第6.7(5)条:

一个声明指明了一组标识符的解释和属性。对于一个标识符的定义是指这个标识符的声明:

  • 对于一个对象而言,使存储为该对象保留;
  • 对于一个函数而言,它包括函数体;
  • 对于一个枚举常量或typedef名称而言,是该标识符的唯一声明。

来自C++标准的3.1(2)条:

除非声明一个没有指定函数体的函数、包含extern修饰符或链接说明符并且没有初始化程序或函数体、在类声明中声明一个静态数据成员、是一个类名声明、typedef声明、using声明或using指令,否则声明就是一个定义

然后有一些例子。

有趣的是(或者不是,但我稍微感到惊讶),typedef int myint;在C99中是一个定义,但在C++中只是一个声明。


标准库的ODR定义对类定义有何规定?它们必须被重复。 - sbi
我怀疑这样做的原因是因为typedef仅声明名称,而不会产生其他东西(例如类型、对象或其他)。因此它只是一个声明,就像using-declaration一样。 - Johannes Schaub - litb
2
@sbi:ODR规定:“(1)任何翻译单元中都不得包含任何...类类型的多个定义”,“(5)在程序中可以有一个类类型的多个定义...,前提是每个定义出现在不同的翻译单元中”,然后还有一些额外的要求,这些要求相当于“这些定义是相同的”。 - Steve Jessop
@sbi:它涉及两个方面。第1条款是关于单个翻译单位中不能复制的内容。第5条列出了一些可以在程序中复制的内容,前提是它们位于不同的单位中。还有其他奇妙的条款太长了,无法放在这个边距^H注释中。 - Steve Jessop
1
@SteveJessop:根据C11标准更新您的答案,因为您知道C11也允许重复的typedef。 - Destructor
显示剩余8条评论

23

来自wiki.answers.com:

在C语言中,“声明”表示你正在告诉编译器有关程序中任何变量、用户定义类型或函数的类型、大小以及在函数声明的情况下,其参数类型和大小。在声明时,并没有为变量保留内存空间。然而,如果创建了这种类型的变量,编译器会知道要保留多少空间。

例如,以下都是声明:

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

与声明相反,定义意味着除了所有声明的内容外,还会在内存中保留空间。可以说“定义=声明+空间预留”。以下是定义的示例:

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

请查看Answers


3
这也是错误的(虽然比其他的更接近):struct foo {}; 是一种定义,而不是声明。foo的声明应该是 struct foo;。编译器从中无法知道为foo对象预留多少空间。 - sbi
1
@Marcin:sbi说“编译器知道为创建该类型的变量需要保留多少空间”并不总是正确的。struct foo;是一个声明,但它并没有告诉编译器foo的大小。我想补充说明的是struct _tagExample { int a; int b; };是一个定义。因此,在这种情况下称其为声明是具有误导性的。当然,它确实是一个声明,因为所有定义都是声明,但你似乎在暗示它不是一个定义,它是_tagExample的定义。 - Steve Jessop
1
@Marcin Gil:这意味着“答案”维基不总是准确的。我必须因错误信息而将其投票否决。 - David Thornley
1
我们了解到adatapost引用的内容是正确的,但并没有(在我看来)真正回答问题。Marcin引用的内容是错误的。引用标准是正确的并且回答了问题,但很难理解。 - Steve Jessop
1
@onebyone:确实是一个非常好的总结!(然而,我曾经作为学生学到并试图灌输给我的学生的是:盲目复制可能会导致灾难。:^> - sbi
显示剩余4条评论

17

C++11更新

由于我没有看到与C ++ 11相关的答案,这里有一个。

除非它声明了以下内容,否则声明就是定义

  • 不透明枚举 - enum X:int;
  • 模板参数 - template<typename T> class MyArray;中的T
  • 参数声明 - int add(int x, int y);中的xy
  • 别名声明 - using IntVector = std :: vector<int>;
  • 静态断言声明 - static_assert(sizeof(int) == 4, "Yikes!")
  • 属性声明(实现定义)
  • 空声明;

从上述列表继承的其他条款:

  • 函数声明 - int add(int x, int y);中的add
  • 包含声明或链接说明符的extern说明符 - extern int a;extern"C"{...};
  • 类中的静态数据成员 - class C { static int x; };中的x
  • 类/结构体声明 - struct Point;
  • typedef声明 - typedef int Int;
  • using声明 - using std :: cout;
  • using指令 - using namespace NS;

模板声明是一个声明。如果其声明定义了函数、类或静态数据成员,则模板声明也是定义。

我在理解它们之间微妙差别方面有帮助的标准示例:

// 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

9

定义:

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

定义将变量与类型关联并分配内存,而声明仅指定类型但不分配内存。在您希望在定义之前引用变量时,声明更加有用。

*不要混淆定义和初始化。两者是不同的,初始化为变量赋值。请参见上面的示例。

以下是一些定义的示例。

int a;
float b;
double c;

现在是函数声明:
int fun(int a,int b); 

请注意函数末尾的分号,这样它就只是一个声明。编译器知道在程序的某个地方将用该原型定义函数。现在,如果编译器接收到类似以下的函数调用:
int b=fun(x,y,z);

编译器将会抛出一个错误,提示不存在该函数。这是因为它没有为那个函数声明原型。
注意两个程序之间的区别。 程序1
#include <stdio.h>
void print(int a)
{
     printf("%d",a);
}
main()
{
    print(5);
}

在这里,print函数被声明和定义了。由于函数调用在定义之后进行,现在请看下一个程序。 程序2
 #include <stdio.h>
 void print(int a); // In this case this is essential
 main()
 {
    print(5);
 }
 void print(int a)
 {
     printf("%d",a);
 }

这是必要的,因为函数调用在定义之前,编译器必须知道是否存在这样的函数。因此,我们声明将通知编译器的函数。

定义:

定义函数的这个部分叫做定义。它说明了函数内部要做什么。

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

3
int a; //declaration; a=10; //definition 这完全是错误的。当谈论自动存储期对象时(在函数定义内声明且没有使用其他存储类指示符(如extern)声明的对象),这些都是定义 - Joey Pabalinas
需要理解的主要区别是,声明是在说“某个地方存在一个具有这些特征(类型等)的东西”,而定义则是在说“我正在声明一个具有这些特征的东西,并且我也在这里实例化它。”由于你无法像那样前向声明自动存储期对象,因此它们始终是定义。 - Joey Pabalinas
除了一些我总是忘记的奇怪的typedef角落案例外,一个经验法则是所有定义都是声明。想想看;当你实例化某个东西时,你还需要告诉编译器这个东西存在以及它的特性,对吧? - Joey Pabalinas
根据您的第一条评论更新了答案。然而,我不同意这个评论:"当您实例化某些东西时,还需要告诉编译器该事物的存在"。我们并不总是在实例化时指定lhs的类型。例如:a=10。我们没有指定任何关于a的“特征”。 - SRIDHARAN

6
为了理解名词,让我们先关注动词。
声明 - 正式宣布; 宣告
定义 - 清晰完整地展示或描述(某人或某物)
因此,当你声明某件事时,你只是告诉别人它是什么。
// declaration
int sum(int, int);

这行代码声明了一个名为sum的C函数,它接受两个int类型的参数并返回一个int类型的值。但是你现在还不能使用它。
当你提供它的实际工作方式时,那就是它的定义。
// definition
int sum(int x, int y)
{
    return x + y;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接