C++学了这么多年你知道为什么定义类时,类的定义放在.h文件中,而类的实现放在cpp文件中。它们为什么能够关联到一起呢?你知道什么东西可以放在.h文件中,什么不能。什么东西又可以放在cpp文件中。如果你忘记了或是压根就不明白,那么读过此文你会清晰无比!!

首先谈下声明与定义的区别。

声明是将一个名称引入程序。定义提供了一个实体在程序中的唯一描述。声明和定义有时是同时存在的。

如int  a;

extern int b=1;

只有当extern中不存在初始化式是才是声明。其他情况既是定义也是声明。

但是在下列情况下,声明仅仅是声明:

1:仅仅提供函数原型。如void func(int,int);

2: extern int a;

3:class A;

4:typedef声明

5:在类中定义的静态数据成员的声明

如:

<span style="font-size: 18px;">class A
{
public:
static int a;//声明。
};</span>

  

下列情况下 ,定义仅仅是定义:

1:在类定义之外,定义并初始化一个静态数据成员。如 A::a=0;

2:在类外定义非内联成员函数。

声明仅仅是将一个符号引入到一个作用域。而定义提供了一个实体在程序中的唯一描述。在一个给定的定义域中重复声明一个符号是可以的,但是却不能重复定义,否则将会引起编译错误。但是在类中的成员函数和静态数据成员却是例外,虽然在类内它们都是声明,但是也不能有多个。

如:

明白了声明与定义的区别,还需要明白 内部链接、外部链接。只有明白了它们你才会知道开头提出的问题。

在编译时,编译器只检测程序语法和函数、变量是否被声明。如果函数未被声明,编译器会给出一个警告,但可以生成目标文件。而在链接程序时,链接器会在所有的目标文件中找寻函数的实现。如果找不到,那到就会报链接错误码(Linker Error)。在VC下,这种错误一般是:Link 2001错误,意思说是说,链接器未能找到函数的实现。

链接把不同编译单元产生的符号联系起来。有两种链接方式:内部链接和外部链接。

如果一个符号名对于它的编译单元来说是局部的,并且在链接时不可能与其他编译单元中的同样的名称相冲突,那个这个符号就是内部链接。内部链接意味着对此符号的访问仅限于当前的编译单元中,对其他编译单元都是不可见的。

static关键字作用在全局变量时,表示静态全局变量。但是作用域仅仅在当前文件作用域内。其他文件中即使使用extern声明也是无法使用的。const也类似。

带有static、const关键字和枚举类型的连接是内部的。

具有内部链接的符号无法作用于当前文件外部,要让其影响程序的其他部分,可以将其放在.h文件中。此时在所有包含此.h文件的源文件都有自己的定义且互不影响。

类的定义具有内部链接,由于它是定义,因此在同一编译单元中不能重复出现。如果需要在其他编译单元使用,类必须被定义在头文件且被其他文件包含。仅仅在其他文件中使用class a;声明是不行的,原因就是类的定义是内部链接,不会在目标文件导出符号。也就不会被其他单元解析它们的未定义符号。理解这一点很重要。

内联函数也具有内部链接。

在一个多文件的程序中,如果一个符号在链接时可以和其他编译单元交互,那么这个名称就有外部链接。外部链接意味着该定义不仅仅局限在单个编译单元中。它可以在.o文件中产生外部符号。可以被其他编译单元访问用来解析它们未定义的符号。因此它们在整个程序中必须是唯一的,否则将会导致重复定义。

非内联成员函数、非内联函数、非静态自由函数都具有外部链接。

内联函数之所有具有内部链接,因为编译器在可能的时候,会将所有 对函数的调用替换为函数体,不将任何符号写入.o文件。

判断一个符号是内部链接还是外部链接的一个很好的方法就是看该符号是否被写入.o文件。

前面说的是定义对链接方式的影响,接下来说下声明对链接方式的影响。

由于声明只对当前编译单元有用,因此声明并不将任何东西写入.o文件。

如extern int a;

int func();

这些声明本身不会影响到.o文件的内容。每一个都只是命名一个外部符号,使当前的编译单元在需要的时候可以访问相应的全局定义。

函数调用会导致一个未定义的符号被写入到.o文件。如果a在该文件中没有被使用,那么没有被写入到.o文件。而func函数有对此函数的调用。也就会将此符号写入目标文件。此后此.o文件与定义此符号的.o文件被连接在一起,前面未定义的符号被解析。

上述声明有可能导致该符号被写入目标文件中。但是以下声明并不会导致该符号写入到目标文件中。

如:

<span style="font-size: 18px;">typedef int Int;Class A;
struct s;
union point;</span>

  

它们的链接也是内部的。

类声明和类定义都是内部链接。只是为当前编译单元所用。

静态的类数据成员的定义具有外部链接。如

<span style="font-size: 18px;">class A
{
  static int a;//声明。具有内部链接。
};</span>

  

静态数据成员a仅仅是一个声明,但是它的定义A::a=0;却具有外部链接。

C++对类和枚举类型的处理方式是不一样的。比如:在不定义类时可以声明一个类。但是不能未经定义就声明一个枚举类型。

基于以上的分析,我们可以知道:将具有外部链接的定义放在头文件中几乎都是编程错误。因为如果该头文件中被多个源文件包含,那么就会存在多个定义,链接时就会出错。

在头文件中放置内部链接的定义却是合法的,但不推荐使用的。因为头文件被包含到多个源文件中时,不仅仅会污染全局命名空间,而且会在每个编译单元中有自己的实体存在。大量消耗内存空间,还会影响机器性能。

const和static修饰的全局变量仅仅在当前文件作用域内有效。它们具有内部链接属性。

下面列出一些应该或是不应该写入头文件的定义:

<span style="font-size: 18px;">//test.h
#ifndef TEST_H
#define TEST_H
int a; //a有外部链接,不能在头文件中定义。
extern int b=10;//同上。
const int c=2;//c具有内部链接,可以定在头文件中但应该避免。
static int d=3;//同上。
static void func(){} //同上。
void func2(){} //同a。
void func3();//可以。仅仅是声明。并不会导致符号名被写入目标文件。
class A
{
public:
static int e;//可以,具有内部链接。
int f;//可以,同上。
void func4();//声明,内部链接。同上。
};
A::e=10;//不可以在头文件中包含具有外部链接的定义。符号名别写入目标文件。
void A:func4()//不可以,类成员函数。外部连接。
{
//,......
}
#endif</span>

  

相信大家现在明白为什么只在类型声明成员函数,而不实现它是合法的了。也可以回答为什么类的定义可以放在.h文件中。而类的实现可以放在同名的cpp文件中。老师以前的介绍是说编译器会自动寻找同名的cpp文件。其实是因为由于cpp文件中存储的是成员函数的实现,而成员函数具有外部链接特性,会在目标文件产生符号。在此文件中此符号是定义过的。其他调用此成员函数的目标文件也会产生一个未定的符号。两目标文件连接后此符号就被解析。注意static数据成员应该放在cpp文件中。而不能放在.h文件。

有内部链接的定义可以定义在cpp文件中,并不会影响全局的符号空间 。但是在cpp文件作用域中要避免定义(并不禁止)没有声明为静态的数据和函数,因为它们具有外部链接。

如

<span style="font-size: 18px;"> int a;
void func()
{
......
}</span>

上述定义具有外部链接可能会与全局命名空间的其他符号名称存在潜在冲突。如果确实需要使用全局的变量或函数。可以为它们加上static关键字。使其作用域局限在当前文件内,具有内部链接也就不会对全局命名空间产生影响。因为内联函数和静态自由函数、枚举以及const类型的数据都具有内部链接,所以它们可以定义在cpp文件中,而不会影响全局命名空间。

typedef和宏定义不会将符号引入.o文件,它们也可以出现在cpp文件中,不会影响全局命名空间。

typedef 为一个已存在的类型创建一个别名。而不是创建一个新的类型。它不提供类型安全。如

<span style="font-size: 18px;">typedef int IntA;
typedef int InB;</span>  

在需要IntA的地方使用IntB是不会报错的。它们可以互相替换。因为此我们称它不提供类型安全。但是在定义函数类型时typedef经常使用,可以使定义更清晰。

标准c库提供一个assert宏,用以保证给定的表达式值非零。否则便会输出错误信息并终止程序执行。只有在程序中没有定义NDEBUG时,assert才会工作。一旦定义NDEBUG  ,assert语句将会被忽略 。注意与VC中的ASSERT相区别。ASSERT是vc提供的。当_DEBUG被定义时才会起作用。

在vc的DEBUG模式下_DEBUG会被定义。而在RELEASE模式下NDEBUG会被定义。

好了,相信大家都会明白开头提出的问题了。如果有不明白的,请务必留言哦。如有错误,也请不吝指正!!

以上内容参考自《Large Scale C++ software design》。

[转载]C++中声明与定义的区别的更多相关文章

  1. 在源文件(.c)和头文件(.h)中声明和定义的区别——C语言

    最近在看多文件编程的时候遇到的一个问题,本来以为理解了声明和定义的区别(然而并没有····),也算是重新认识了一次声明和定义,下面上代码 情形一:在源文件(.c)中 相信大部分读者对声明和定义的理解是 ...

  2. C\C++中声明与定义的区别

    声明和定义是完全同的概念,声明是告诉编译器"这个函数或者变量可以在哪找到,它的模样像什么".而定义则是告诉编译器,"在这里建立变量或函数",并且为它们分配内存空 ...

  3. C++中声明与定义的区别

    如果是指变量的声明和定义: 从编译原理上来说,声明是仅仅告诉编译器,有个某类型的变量会被使用,但是编译器并不会为它分配任何内存.而 定义就是分配了内存.对于下面的两句代码:void Func(){in ...

  4. C++中声明和定义的区别

    声明 这有一个与这个名字相关的东西,并且它是这个类型的,告诉编译器我要使用它,并期待它定义在某一个地方. 定义 定义是指提供所有必要的信息(占用内存大小),使其能够创建整个实体. 我们必须明白的: 一 ...

  5. PHP中变量声明和定义的区别

    先记录一下(不知道PHP是不是一样,但是C语言是这样的):把建立空间的声明称之为“定义”,而把不需要建立存储空间的声明称之为“声明”.声明的最终目的是为了提前使用,即在定义之前使用,如果不需要提前使用 ...

  6. C++ 变量的声明与定义的区别

    变量声明和定义的区别 我们在程序设计中,时时刻刻都用到变量的定义和变量的声明,可有些时候我们对这个概念不是很清楚,知道它是怎么用,但却不知是怎么一会事,下面我就简单的把他们的区别介绍如下:(望我的指点 ...

  7. C++之声明与定义的区别

    直接举例,在C++中,声明与定义的区别如下: extern int a;//若有extern关键字,则只是声明 int b;//若没有extern关键字,则为声明+定义 int a;//若之前已经声明 ...

  8. [转载]C++声明和定义的区别

    <C++Primer>第四版 2.3.5节中这么说到: ①变量定义:用于为变量分配存储空间,还可为变量指定初始值.程序中,变量有且仅有一个定义. ②变量声明:用于向程序表明变量的类型和名字 ...

  9. 【C++】C++中变量的声明与定义的区别

    声明(declaration):意味着告诉编译器关于变量名称.变量类型.变量大小.函数名称.结构名称.大小等等信息,并且在声明阶段不会给变量分配任何的内存. 定义(definition):定义就是在变 ...

随机推荐

  1. Lua参考手册

    英文原版: http://www.lua.org/manual/5.1/ 中文版下面2个地址都有:一样的 manual.luaer.cn lua在线手册 lua参考手册Lua参考手册的中文翻译(云风翻 ...

  2. (转载)PHP怎么获取MySQL执行sql语句的查询时间

    (转载自CSDN) 方法一: //计时开始 runtime(); //执行查询 mysql_query($sql); //计时结束. echo runtime(1); //计时函数 function ...

  3. FFT(快速傅里叶变换):HDU 5307 He is Flying

    aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAA8IAAAPeCAIAAABInTQaAAAgAElEQVR4nOy9fZReVXk3vP8ia+HqCy

  4. 【宽搜】BAPC2014 J Jury Jeopardy (Codeforces GYM 100526)

    题目链接: http://codeforces.com/gym/100526 http://acm.hunnu.edu.cn/online/?action=problem&type=show& ...

  5. QTP自传之对象库编程

    对象库编程是我们平时工作中使用最多的编程方式,在自动化脚本开发中起到举足轻重的作用,与描述性性编程相比,更直接和易于维护,今天就和大家简单的聊聊如何进行对象库编程. 既然是对象库编程,肯定要对已存在于 ...

  6. SRM 392(1-250pt)

    DIV1 250pt 题意:给两个各含有一个*号的字符串s1和s2,可以用一个任意字符串代替*号(注意是串,不是只能用单个字符代替,也可以为用空串代替),问能否将s1和s2变为相同的字符串.如果能输出 ...

  7. 在javaweb中通过servlet类和普通类读取资源文件

    javaweb有两种方式读取资源文件 在Servlet中读取,可以使用servletContext,servletContext可以拿到web所有的资源文件,然后随便读,但是这种方法不常用,尽量少在S ...

  8. mysql5.5 对触发器,函数,存储引擎,事件进行主从复制情况.(转)

       mysql5.5 对触发器,函数,存储引擎,事件进行主从复制情况. 转(http://blog.csdn.net/m582445672/article/details/7670802) 一.My ...

  9. winform程序中将控件置于最顶层或最底层的方法

    有时,我们可能动态的添加控件,并准备将其置于对顶层或最底层.实现的方法有两个: 一种方法是在WinForm窗体中使用Controls控件集的SetChildIndex方法,该方法将子控件设定为指定的索 ...

  10. Android抓包工具Fiddler抓取数据

    1.手机端设置 2.Fiddler设置监听 Tools/Fiddler options 是否允许监听到https(Fiddler默认只抓取http格式的),首次点击会弹出是否信任fiddler证书和安 ...