您的位置:首页 > 编程语言 > C语言/C++

C语言和C++在声明和定义之间的区别

2012-11-12 10:39 330 查看
声明是告诉编译器有一个变量或函数,并标明是什么类型的.

而定义,是明确告诉编译器,这个变量或函数的值和实现方式.

一般来讲,声明变量并不会占用存储空间,而定义变量会占用存储空间.声明函数只提供函数的格式,定义函数则提供函数的实现代码.

当然,很多时候,声明和定义是合并在一起的,这样的声明称为为定义声明.

举例来讲:

int test=1;//这是一个定义声明,term被分配了内存空间

extern int test;//这是一个声明而不是定义,它告诉编译器要使用先前定义的变量test.称为引用声明.

函数的定义和声明一般是分开的,比较清楚,也有将函数的定义和声明合并.

c++primer第四版中,53页的习题2.18中问到,

extern std::string name;是属于声明还是定义?

答案说的是声明。

不过我认为,string类中有默认构造函数,如果该语句在函数外,那么则会自动获得储存空间,将name定义为空字符串,不知各位有何高见?

我刚才做了一个实验:

//a.cpp

#include <string>

std::string i;

//b.cpp

#include <iostream>

#include <string>

extern std::string i;

int main ()

{

std::cout << i << std::endl;

}

运行结果为:

(上面是一空行:由于末尾用了endl)

如果没有a.cpp这个文件,运行出错

这是一对实验(有没有a.cpp文件)

可得出这样的结论:

a.cpp中std::string i;是定义(本来就是)

b.cpp中extern std::string i; 则是声明(你的问题)

先别急,这只是实验结果。我们还需要理论依据,那么下面就试着解释一下:

下面是另一个例子(也是第二个实验)

你应该知道内置类型(int等)如果作为全局变量时,定义的同时也就初始化了(一个内置类型有其对应的默认值;如果你将自定义类型也看成内置类型,那么就等于说,一个自定义类型有其对应的默认值(由默认构造函数完成))。如

//c.cpp

int i;

//d.cpp

#include <iostream>

extern int i;

int main ()

{

std::cout << i << std::endl;

}

那么在这里int i; 就是定义(并且初始化了i,初始值为0)

因而输出结果为

0

//这个实验的运行结果符合理论依据。

(我使用的编译器是visual studio 2008)

从这两个实验来看:(将两个“默认”等同起来)

int i;

std::string i;

都可以作为初始化i的定义性语句。

那么

extern int i;

extern std::string i;

也具有定义行为(这个很明显与extern的用法相悖)

在以上两个试验中:int 与 string 有“区别”吗?所有的代码都一样只不过把std::string 换成了 int而已!如果你能解释int的情况,那么我想std::string问题也就迎刃而解了!

于是我个人总结出一个结论:

不论是内置类型还是自定义类型,前面有extern关键字,如果没有显式地初始化参数(即就是自定义类型的默认构造函数被extern屏蔽了,不起初始化的作用),那么就按声明对待。

可能我的描述很混乱,希望你好好看看上面的例子,就明白了!

注意:从这里可以看出来默认构造函数的特殊性!

c++程序通常由许多文件组成,为了让多个文件访问相同的变量,c++区分了声明和定义。

变量的定义(definition)用于为变量分配存储空间,还可以为变量指定初始值。在程序中,变量有且仅有一个定义。

声明(declaration)用于向程序表明变量的类型和名字。定义也是声明:当定义变量的时候我们声明了它的类型和名字。可以通过使用extern声明变量名而不定义它。不定义变量的声明包括对象名、对象类型和对象类型前的关键字extern。

extern声明不是定义,也不分配存储空间。事实上它只是说明变量定义在程序的其他地方。程序中变量可以声明多次,但只能定义一次。

只有当声明也是定义时,声明才可以有初始化式,因为只有定义才分配存储空间。初始化式必须要有存储空间来进行初始化。如果声明有初始化式,那么它可被当作是定义,即使声明标记为extern。

任何在多文件中使用的变量都需要有与定义分离的声明。在这种情况下,一个文件含有变量的定义,使用该变量的其他文件则包含该变量的声明(而不是定义)。

说明:个人感觉好像就是extern存在声明和定义不是一回事,其它几个声明和定义就是一回事

  auto (自动类作用域在函数体内和分程序内,)

register (作用域在函数体内和分程序内)

static (内部静态类作用域在函数体内和分程序内,外部静态态作用域在定义它的类中,但其生命周期却一样)

  extern (作用域在整个程序内,包含该程序的各个文件)

    在其个文件中使用extern变量时,必须先声明,还有就是如果使用在前,定义在后时也要声明。

 在函数体外的一个变量如果不加存储内型,默认便为extern,在函数体内如果一个变量不加存储说明

   则默认为auto

{

int a = 5;//声明加定义加初始化

  int b ;//声明加定义

b = 4; //赋值

}

extern int a ;//声明

extern int i =1;//定义

附:如果一个变量被定义但没有被初始化,也没有被赋值,则存储类为静态的和extern的变量值为默认值

  (数值量为0,字符量为null),存储类为auto和register的为无效值,即值是随机的,此值不可使用,变量被赋值和有默认值称为有效值

定义与声明是C/C++基本的概念,看似很简单的东西,但区分不好往往很容易出错。C/C++中规定,使用一个对象(非面向对象中的对象,此处对象指广泛意义上的程序元素,下同)前必须先定义该对象。所谓定义,是指在程序中描述一个对象实体;声明是指将一个名称引入一个程序。大多数情况下可以不加区分的使用它们,因为这时候定义就是声明。

当涉及到头文件与源文件时,情况就变得有些复杂了。类比于对象的声明与定义,C/C++中头文件可认为是声明,而源文件用于定义,形式上有点类似于Java的接口和实现类。作为类库提供给第三方使用时,只需提供头文件和实现的Dll即可,达到了信息隐藏效果。由于头文件不能单独编译,即不能通过编译头文件产生符号表,因此在头文件中描述某种实现的过程是非法的。

先来看看那些情况只是声明而非定义:

1. 包含extern定义符的变量和函数;

2. 没有提供实现的函数(没有函数体);

3. 类中的静态数据成员;

4. teypedef声明;

关于声明和定义还有个区别就是在一个作用域中可以重复声明,但不能重复定义。

还是看例子吧,比较容易说明问题,在头文件(head.h)中:

#ifndef H1_H

#define H1_H

typedef char* NAME;

typedef char* NAME; //重复声明,没问题

extern int k;

//int ilk1; 此处为定义,非法。注,此句在VC6中是合法的,可能是VC2005现实C99标准

//extern int ilk2 = 4; 非法,

extern void outPut();//声明,合法

void outPut2(); //声明,合法

//void ilOutPut(){} 定义,非法

inline void put(){}//内敛,合法

static int i;

//static int ili2 = 4; 非法

class Radio{

public:

static int s_count;

void ilfoo();

/*void ilfoo();重复声明,非法。 注意,类中的声明不能重复,这是声明能重复的特殊情况*/

private:

int size() const;

char getchar() //合法

{

return 'c';

}

};

inline int Radio::size() const

{

return s_count;

}

extern Radio *ra;//合法,声明

//Radio ra; 非法, 可此句放到包含该头文件的源文件中

//void Radio::ilfoo(){}; 非法, 可此句放到包含该头文件的源文件中

//int Radio::s_count = 5; 非法, 可此句放到包含该头文件的源文件中

#endif

  “声明”向计算机介绍名字,它说,“这个名字是什么意思”。 

 “定义”为这个名字分配存储空间。

  无论涉及到变量时还是函数时含义都一样。无论在哪种情况下,编译器都在“定义”处分配存储空间。对于变量,编译器确定这个变量占多少存储单元,并在内存中产生存放它们的空间。对于函数,编译器产生代码,并为之分配存储空间。函数的存储空间中有一个由使用不带参数表或带地址操作符的函数名产生的指针。

  另外,定义也可以是声明。如果该编译器还没有看到过名字A,程序员定义int A,则编译器马上为这个名字分配存储地址。声明常常使用于e x t e r n关键字。如果我们只是声明变量而不是定义它,则要求使用e x t e r n。对于函数声明, e x t e r n是可选的,不带函数体的函数名连同参数表或返回值,自动地作为一个声明。

  函数原型包括关于参数类型和返回值的全部信息。int f(float,char);是一个函数原型,因为它不仅介绍f这个函数的名字,而且告诉编译器这个函数有什么样的参数和返回值,使得编译器能对参数和返回值做适当的处理。C + +要求必须写出函数原型,因为它增加了一个重要的安全层。

  下面是一些声明的例子。

//声明变量

extern int varible;

//声明函数

extern float foo(float,float);

float b;

float c;

//定义函数

float foo(float a,float b)

{

return a + b;

}

//定义整型变量

int varible;

//定义函数

int h(int x)

{

return x + 1;

}

void main(int argc, _TCHAR* argv[])

{

varible = 2;

b = 1.0;

c = 2.3;

foo(b,c);

h(varible);

}

  在函数声明时,参数名可给出也可不给出。而在定义时,它们是必需的。这在C语言中确实如此,但在C + +中并不一定。

  其实很简单,总结起来就是:声明不分配内存空间,定义则相反,定义可以是声明,但声明一定不是定义!

all but one of the following 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 nonstatic 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; } // definesN and N::d

namespace N1 = N; // defines N1

X anX; // defines anX

whereas these are just 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

在C++中,变量、对象、函数都需要声明,使用之前需要定义,本文针对初学者在此方面经常忽略但又很重要的问题给出了解释,并提供了此方面良好的编程风格。

由于大家都对局部变量的定义与使用十分熟悉,所以在此主要总结本人认为两点应该注意的问题:

1.头文件与声明的关系

2.符号常量与inline函数的声明

l 讲解

首先,头文件为所向extern 对象声明函数声明以及inline 函数定义提供了一个集中的位置

头文件提供了两个安全保证:第一,保证所有文件都包含同一个全局对象或函数的同一份声明;第二,如果需要修改声明,则只需改变一个头文件从而不至于再发生只修改了某一个特殊的文件中的声明。如果应用程序有很大的头文件,则使用预编译头文件而不是普通头文件可以大大降低应用程序的编译时间。

其次,头文件不应该含有非inline 函数或对象的定义。因为如果这些定义在同一程序的两个或多个文件中被包含就会产生重复定义的编译错误。

但是,常量和inline 函数却可以违反这条规则,因为常量和inline 函数可以被定义多次,而归根到底是因为编译器在编译期间要将常量和inline函数展开的原因。

. 函数

我觉得这是一种最容易理解的情况。函数的声明就是函数原型,函数的定义就是函数的具体实现。编译器在遇到一个函数调用语句时,必须知道这个函数的原型以进行相应的类型检查,因此必须把该函数的声明(当然定义也可以,因为在此时定义隐含了声明)放在前面。

只要是比ACM题目那种一百行左右代码再大一点的项目,一般都不会把所有代码写在一个文件里。注意编译器的执行过程是分两步的,首先把每个.cpp文件(或者.c, .cc等等)分别编译成目标文件(通常是.o, .obj等等),然后再用链接器把目标文件组装成一个可执行文件。注意“分别”两字,也就是说,编译器必须能够不依赖其他cpp文件就可以把当前的cpp文件编译成目标文件。

我们先不考虑类等等内容,假设现在我实现了一系列逻辑上有一定关联的工具函数,为了清楚起见我把它们放在同一个util.cpp文件里,而把使用这些工具函数的其他代码放在文件main.cpp里,那么要编译main.cpp,必须知道里面用到的工具函数的原型,所以必须在main.cpp里有一份该函数的声明(只需要声明就够了,编译器可以据此生成压栈、取返回值等等汇编代码,至于函数内部的实现代码是在util.o目标文件里,等链接的时候组装在一起就可以)

然而,当有多个cpp文件都要用到这些工具函数时,如果我们每次都要在cpp文件前面附加一堆函数声明的话,就显得有点太啰嗦了。于是头文件就派上用场了。这时惯常的做法是把util里(想提供给外界使用的)函数的声明放在.h头文件里,如果另外的cpp文件要用到某个函数,只要把对应的头文件include进来即可。注意一定不要把函数的定义放在头文件里,否则的话,如果这个头文件被包含进多个cpp文件里,在链接时就会出现函数重定义的错误。也就是说,函数可以声明多次,但只能定义一次(有一个例外下面会提到)。

另外一点是,如果不想某个文件里的函数被外面看到,应该用static修饰。应该只开放那些想公开的函数,并把想公开的函数的声明放到对应的头文件里。

这里再插一句,头文件只是为了方便程序员,编译器是看不到头文件的,把include的文件插入到相应的位置之类的事情都是预处理器完成的。头文件不必有对应的.cpp文件,不必是.h扩展名,实际上它可以是随便什么东西,只要你愿意就好,编译器根本不care。

关于头文件还有一点,如果我们不小心的话,经常会出现同一份头文件被包含了多次的情况(有时候可能是不可避免的)。比如我自己写了一个 roba.h,里面有一句#include <string>,又写了一个main.h,里面即include了roba.h,又include了<string>,这样 string就被包含了两次。这样就有可能出现重复定义(因为头文件里不止有函数声明,还有比如类的定义),这时我们可以用#ifndef这个东西控制这个头文件的内容只被包含一次。常见写法如下:

#ifndef _ROBA_H_ #define _ROBA_H_ //... //the body of roba.h //... #endif最后再说一下inline函数,这是一个特例,inline函数的定义应该写在头文件里。因为编译器要内联这个函数,就必须知道函数的实现,不然的话是无法单独编译出一个目标文件的。对inline函数不必担心重定义的问题,它可以看作局部于所在的源文件的。

2. 变量

这里主要是讨论全局变量的使用,对于局部变量(即在函数内部定义的,在栈上分配空间的变量)不予考虑。全局变量的情况与前一篇说的函数的情况类似,但是更迷惑人一些。全局变量的定义是在所有函数的外面,比如下面语句:

int roba;定义了一个叫做roba的int型全局变量。而全局变量的声明是这样:

extern int roba;全局变量与函数一样要求,定义只能一次,声明可以多次,在使用前编译器必须看到声明。如果多个源文件要共享一个全局变量,初学者常犯的错误有两种:一种是在多个文件中都写了int roba;,这样会导致链接时发生重复定义的错误;另一种是只在一个文件中写了int roba;,以为在其他的文件的文件中可以直接用,但实际上因为在其他文件中没有对这个全局变量的声明,所以会发生编译错误。正确的写法是,在某一个文件中用int roba的形式定义,而在其他用到该变量的文件中用extern int
roba的形式声明。

如果要把变量放到头文件里,从前面的讨论可以看出,一定不要把int roba;这样的定义放进去(因为这样的话如果这个头文件被包括进多个cpp文件就会产生重复定义),而应该写extern int roba;这样的声明,然后在某个cpp文件中定义int roba; 。

与函数类似,如果我们不想其他的文件看到某个全局变量,也可以用static修饰它,表示它的作用域局部于此文件内。(注意我说的不是static的局部变量)

下面又开始说特例了。对于这种写法:

extern int roba = 1;是被当作定义而不是声明的,尽管加了一个extern。

另外一个要注意的是const型的全局对象,它默认是局部于文件的,也就是说默认就是static的。这是因为C++允许(并且鼓励)这样的写法:

const int N = 100; int loli
;我们可以看出,编译器必须知道N的值以后,才能确定loli数组的大小并生成相应代码。所以如果像非const的全局变量一样,把const int N定义在别的文件里,编译器就没办法在编译时知道它的值,也就不能单独编译这个cpp文件,这就违反了上次说的那个要求。基于这个考虑,C++规定const全局对象是局部于文件的,也就是说我们可以安全地把 const int N = 100; 这样的东西放在头文件里,不会出现重定义。

我们也可以用extern const int N = 100;这样的写法强制使其全局可见,在其他的文件中用extern const int N;来进行声明,但我认为这样会产生不必要的混乱,对程序效率也并没有提升。可能有人会觉得如果在每个文件里声明一个局部于本文件的全局const对象会浪费空间,但实际上编译器基本都会把这个const优化掉的,产生的效果和古老的 #define N 100 宏定义是一样的。

变量和对象不加extern永远是定义,类中的除外.

函数只有函数头是声明,有函数体是定义.

类永远只是声明.类成员函数的函数体是定义.

class C

{

static int x;//这里的x是声明

static const int a;;//这里的a是声明

//非static变量在类定义对象时才分配内存.

C();//这里的函数是声明

};

int C::x;//这个才是定义

const int C::a=11;//这个才是定义

声明和定义之间的区别可能看起来微不足道,但事实上却是举足轻重的。

在C++语言中,变量必须且仅能定义一次,而且在使用变量之前必须定义或声明变量。

任何在多个文件中使用的变量都需要有与定义分离的声明。在这种情况下,一个文件含有变量的定义,使用该变量的其他文件则包含该变量的声明(而不是定义)。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: