原文  http://blog.csdn.net/ithzhang/article/details/8119286

主题 C++ 

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 :在类中定义的静态数据成员的声明

如:

class A
{
public:
static int a;//声明。
}; 

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

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

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

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

如:

class A
{
public:
static int a;
static int a;
void func(int ,int);
void func(int ,int);
}; 

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

在编译时,编译器只检测程序语法和函数、变量是否被声明。如果函数未被声明,编译器会给出一个警告,但可以生成目标文件。而在链接程序时,链接器会在所有的目标文件中找寻函数的实现。如果找不到,那到就会报链接错误码( 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 文件被连接在一起,前面未定义的符号被解析。

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

如:

typedef int Int;Class A;
struct s;
union point; 

它们的链接也是内部的。

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

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

class A
{
static int a;
};

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

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

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

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

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

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

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

 

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

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

int a;
void func()
{
......
}

上述定义具有外部链接可能会与全局命名空间的其他符号名称存在潜在冲突。因为内联函数和静态自由函数、枚举以及 const 类型的数据都具有内部链接,所以它们可以定义在 cpp 文件中,而不会影响全局命名空间。

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

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

typedef int IntA;
typedef int InB;

在需要 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++学了这么多年,你也许不知道为什么类定义要放在.h文件,类实现放在cpp文件。它们如何关联?的更多相关文章

  1. C++学了这么多年,你仍不知道的事

    C++学了这么多年你知道为什么定义类时,类的定义放在.h文件中,而类的实现放在cpp文件中.它们为什么能够关联到一起呢?你知道什么东西可以放在.h文件中,什么不能.什么东西又可以放在cpp文件中.如果 ...

  2. 跟我一起学extjs5(16--各种Grid列的自己定义渲染)

    跟我一起学extjs5(16--各种Grid列的自己定义渲染)         Grid各列已经可以展示出来了.列的类型包含字符型,整型,浮点型,货币型,百分比型,日期型和布尔型,我自己定义了各种类型 ...

  3. (29)Spring boot 文件上传(多文件上传)【从零开始学Spring Boot】

    文件上传主要分以下几个步骤: (1)新建maven java project: (2)在pom.xml加入相应依赖: (3)新建一个表单页面(这里使用thymleaf); (4)编写controlle ...

  4. 学了这么多年C语言,你真的知道全局变量,局部变量,静态变量,本地函数,外部函数是如何区分标识的吗?

    动态库内容分析 文章目录 动态库内容分析 1. 动态库编译 1.1 第一个C文件:basic.c 1.2第二个C文件:demo.c 1.3第三个C文件:main.c 2.动态库编译 3.二进制内容分析 ...

  5. linux不知道文件在哪,想查找文件内的字符串

    find . -name "*.*" |xargs grep "xxx"

  6. [转贴]从零开始学C++之STL(二):实现一个简单容器模板类Vec(模仿VC6.0 中 vector 的实现、vector 的容量capacity 增长问题)

    首先,vector 在VC 2008 中的实现比较复杂,虽然vector 的声明跟VC6.0 是一致的,如下:  C++ Code  1 2   template < class _Ty, cl ...

  7. 从零开始学C++之继承(二):继承与构造函数、派生类到基类的转换

    一.不能自动继承的成员函数 构造函数 析构函数 =运算符 二.继承与构造函数 基类的构造函数不被继承,派生类中需要声明自己的构造函数. 声明构造函数时,只需要对本类中新增成员进行初始化,对继承来的基类 ...

  8. 阶段5 3.微服务项目【学成在线】_day04 页面静态化_18-页面静态化-模板管理-GridFS研究-取文件

    需要创建mongoDB的配置类1 配置类里面主要创建.GridFSBucket这个对象.这个对象的作用就是用来打开一个下载流 在cms的微服务下,在config下创建MongoConfig.这个时候就 ...

  9. 阶段5 3.微服务项目【学成在线】_day04 页面静态化_17-页面静态化-模板管理-GridFS研究-存文件

    将模板信息保存在cms_template里面 存储在fs.chunks这个集合中.这个集合里面存的是分块文件. fs.files存的是文件的基本信息 chunks存的是块信息 创建测试文件 在cms的 ...

随机推荐

  1. 获取文本文件的第N行内容

    在PowerShell中,可以通过Get-Content这个cmdlet来获取文本文件的内容.Get-Content将一个文本文件读取到一个数组中,每一个数组元素就是文件的一行内容.比如一个文本文件内 ...

  2. ASP.NET MVC4系列验证机制、伙伴类共享源数据信息(数据注解和验证)

    一,mvc前后台验证 自定义属性标签MyRegularExpression using System; using System.Collections.Generic; using System.C ...

  3. PHP OO 编程笔记

    1. 类中的方法不是全局方法,可以和外部的普通方法重名,例如: <?php function time(); 则会报错:不能重新声明方法 Fatal error: Cannot redeclar ...

  4. spring mvc超强的json支持,你自己根本不需要额外的配置。spring mvc都给你配置好了!!!

    SpringMVC层跟JSon结合,几乎不需要做什么配置,代码实现也相当简洁.再也不用为了组装协议而劳烦辛苦了! 2.一.Spring注解@ResponseBody,@RequestBody和Http ...

  5. [源码]随机获取虾米音乐song_id API文件

    [源码]随机获取虾米音乐song_id API文件 January 11, 2015 注意:此API请放置于国内主机使用,如香港.北京等等,否则会提示:虾米音乐在您所处的国家或地区暂时无法使用 < ...

  6. 时间函数 date strtotime

    date_default_timezone_set('Asia/Shanghai');echo strtotime('Today');echo strtotime(date('Y-m-d')); 获取 ...

  7. mysql控制台命令

    * mysql 链接服务器 mysql -h localhost  -u root -p show processlist; update mysql.user set authentication_ ...

  8. Raft

    http://thesecretlivesofdata.com/raft/ https://github.com/coreos/etcd   1 Introduction Consensus algo ...

  9. quanpailie quanbianli

    #include<stdio.h>char data[5]={'a','b','c','d','e'};int vist[5]={0};char step[5]={0};char bu[5 ...

  10. centos6.5 扩容

    #查看挂载点: df -h #显示: 文件系统 容量 已用 可用 已用%% 挂载点 /dev/mapper/vg_dc01-lv_root 47G 12G 34G % / tmpfs 504M 88K ...