C++学了这么多年,你也许不知道为什么类定义要放在.h文件,类实现放在cpp文件。它们如何关联?
原文 http://blog.csdn.net/ithzhang/article/details/8119286
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文件。它们如何关联?的更多相关文章
- C++学了这么多年,你仍不知道的事
C++学了这么多年你知道为什么定义类时,类的定义放在.h文件中,而类的实现放在cpp文件中.它们为什么能够关联到一起呢?你知道什么东西可以放在.h文件中,什么不能.什么东西又可以放在cpp文件中.如果 ...
- 跟我一起学extjs5(16--各种Grid列的自己定义渲染)
跟我一起学extjs5(16--各种Grid列的自己定义渲染) Grid各列已经可以展示出来了.列的类型包含字符型,整型,浮点型,货币型,百分比型,日期型和布尔型,我自己定义了各种类型 ...
- (29)Spring boot 文件上传(多文件上传)【从零开始学Spring Boot】
文件上传主要分以下几个步骤: (1)新建maven java project: (2)在pom.xml加入相应依赖: (3)新建一个表单页面(这里使用thymleaf); (4)编写controlle ...
- 学了这么多年C语言,你真的知道全局变量,局部变量,静态变量,本地函数,外部函数是如何区分标识的吗?
动态库内容分析 文章目录 动态库内容分析 1. 动态库编译 1.1 第一个C文件:basic.c 1.2第二个C文件:demo.c 1.3第三个C文件:main.c 2.动态库编译 3.二进制内容分析 ...
- linux不知道文件在哪,想查找文件内的字符串
find . -name "*.*" |xargs grep "xxx"
- [转贴]从零开始学C++之STL(二):实现一个简单容器模板类Vec(模仿VC6.0 中 vector 的实现、vector 的容量capacity 增长问题)
首先,vector 在VC 2008 中的实现比较复杂,虽然vector 的声明跟VC6.0 是一致的,如下: C++ Code 1 2 template < class _Ty, cl ...
- 从零开始学C++之继承(二):继承与构造函数、派生类到基类的转换
一.不能自动继承的成员函数 构造函数 析构函数 =运算符 二.继承与构造函数 基类的构造函数不被继承,派生类中需要声明自己的构造函数. 声明构造函数时,只需要对本类中新增成员进行初始化,对继承来的基类 ...
- 阶段5 3.微服务项目【学成在线】_day04 页面静态化_18-页面静态化-模板管理-GridFS研究-取文件
需要创建mongoDB的配置类1 配置类里面主要创建.GridFSBucket这个对象.这个对象的作用就是用来打开一个下载流 在cms的微服务下,在config下创建MongoConfig.这个时候就 ...
- 阶段5 3.微服务项目【学成在线】_day04 页面静态化_17-页面静态化-模板管理-GridFS研究-存文件
将模板信息保存在cms_template里面 存储在fs.chunks这个集合中.这个集合里面存的是分块文件. fs.files存的是文件的基本信息 chunks存的是块信息 创建测试文件 在cms的 ...
随机推荐
- shell总结(0基础入门)
一.简介 shell是用户和操作系统交互的命令行解释器. shell有很多种: bash.csh.sh.ksh... 我们等了linux时看到的命令行就是一个bash. 二.第一个脚本: [root@ ...
- DataGridView控件添加数据时空白的可 错误情况
写一个小程序,将数据库中的两张表相关信息显示在DataGridView中.代码如下: //获取项目数据,添加到表中 SqlConnection con = new SqlConnection(Main ...
- hdu Can you find it
这道题也是道二分的题,主要有几个注意点: 1.两个数组的合并的问题,可以将a数组和b数组合并,这样可以降低时间复杂度. 2.二分查找的left和right的变化问题.之前这里一直wa...一定要是le ...
- PHP有两个不同的版本:4.x系列版本和5.x系列版本
在为用户提供动态内容方面,PHP和MySQL是一个强大的组合.这些年来,这两项产品已经跨越了它们最初的应用舞台,现在,一些世界上最繁忙的网站也在应用它们.虽然它们当初都是开源软件,只能在UNIX/Li ...
- MongoDB高可用模式部署
首先准备机器,我这里是在公司云平台创建了三台DB server,ip分别是10.199.144.84,10.199.144.89,10.199.144.90. 分别安装mongodb最新稳定版本: w ...
- linux网卡设置详解
centos7安装之后是需要在网卡配置文件中开始网络连接 onboot =yes 刚开始时网卡获取IP模式是dhcp 你会发现ifconfig不能用,猜测是废弃了,你要yum install net- ...
- php获得网站根目录的几个方法
php获得网站根目录的几个方法 电脑软硬件应用网 45IT.COM 时间:2015-01-08 12:54 作者:佚名 在php中我们要得到网站根目录可以用很多全局变量实现了,如可以利用__file_ ...
- ARM状态和THUMB状态
ARM处理器的工作状态 在ARM的体系结构中,可以工作在三种不同的状态,一是ARM状态,二是Thumb状态及Thumb-2状态,三是调试状态. <嵌入式系统开发与应用教程(第2版)>上介绍 ...
- Scripting Languages
Computer Science An Overview _J. Glenn Brookshear _11th Edition A subset of the imperative programmi ...
- java 操作数据库
package foo;import java.sql.*; public class JdbcDemo { private static Connection conn; private stati ...