C++ :.cpp【头文件(进行函数、变量、宏、结构体、类声明)】、.cpp【源文件(变量定义、函数实现)】、.hpp(头文件;将.cpp的实现代码混入.h头文件中,定义与实现都包含在同一个文件)

一、*.cpp文件、*.h文件

通常,在一个C++程序中,只包含两类文件——.cpp文件和.h文件。其中:

  • .cpp文件被称作C++源文件,里面放的都是C++的源代码;
  • .h文件则被称作C++头文件,里面放的也是C++的源代码。

目前业界的常用格式如下:

  • implementation file
    • *.cpp
    • *.cc
    • *.cc
    • *.c
  • header file
    • *.hpp
    • *.h++
    • *.hh
    • *.hxx
    • *.h

一句话: 建议 源文件使用 .cpp, 头文件使用 .h

关于 implementation file 并没有什么说的, 使用 .cpp/.cc 都是可以的. 但是 header file 需要注意.

c 的头文件格式是 .h, 认为 h 代表 header, 于是有很多人也喜欢在 c++ 用 .h 作为头文件扩展名。

其实扩展名并不影响编译结果, 对于编译器来说扩展名是不重要的 (甚至使用 .txt 也可以)。但是如果在一个 c 与 c++ 混合使用的大型项目中, 你很难立刻分辨出这是一个 cpp 的 header file 或者是一个 c 的header file; 此外, 在 vim 或者 vscode的语法提示插件看来, .h 就是 c 语言的, 那么当你在 c 文件写了 cpp 的某些语法当然会提示不正确 (当然肯定还是可以编译通过的)

1、implementation file 与 header file 写什么内容

理论上来说 implementation file 与 header file 里的内容, 只要是 c++ 语言所支持的, 无论写什么都可以的, 比如你在 header file 中写函数体, 只要在任何一个 implementation file 包含此 header file 就可以将这个函数编译成 object 文件的一部分 (编译是以 implementation file 为单位的, 如果不在任何 implementation file 中包含此 header file 的话, 这段代码就形同虚设), 你可以在 implementation file 中进行函数声明, 变量声明, 结构体声明, 这也不成问题!!!

那为何一定要分成 header file 与 implementation file 呢? 为何一般都在:

  • 在 header file 中进行函数, 变量声明, 宏声明, 结构体声明呢?
  • 在 implementation file 中去进行变量定义, 函数实现呢?

原因如下:

  1. 如果在 header file 中实现一个函数体, 那么如果在多个 implementation file 中引用它, 而且又同时编译多个 implementation file, 将其生成的 object file 连接成一个可执行文件, 在每个引用此 header file 的 implementation file 所生成的 object file 中, 都有一份这个函数的代码, 如果这段函数又没有定义成局部函数, 那么在连接时, 就会发现多个相同的函数, 就会报错.
  2. 如果在 header file 中定义全局变量, 并且将此全局变量赋初值, 那么在多个引用此 header file 的 implementation file 中同样存在相同变量名的拷贝, 关键是此变量被赋了初值, 所以编译器就会将此变量放入 DATA 段, 最终在连接阶段, 会在 DATA 段 中存在多个相同的变量, 它无法将这些变量统一成一个变量, 也就是仅为此变量分配一个空间, 而不是多份空间, 假定这个变量在 header file 中没有赋初值, 编译器就会将之放入 BSS 段, 连接器会对 BSS 段 的多个同名变量仅分配一个存储空间.
  3. 如果在 implementation file 中声明宏, 结构体, 函数等, 那么如果要在另一个 implementation file 中引用相应的宏, 结构体, 就必须再做一次重复的工作, 如果我改了一个 implementation file 中的一个声明, 那么又忘了改其它 implementation file 中的声明, 这不就出了大问题了, 如果把这些公共的东西放在一个头文件中, 想用它的 implementation file 就只需要引用一个就 OK 了!
  4. 在 header file 中声明结构体, 函数等, 当你需要将你的代码封装成一个库, 让别人来用你的代码, 你又不想公布源码, 那么人家如何利用你的库中的各个函数呢? ?
    1. 一种方法是公布源码, 别人想怎么用就怎么用;
    2. 另一种是提供 header file, 别人从 header file 中看你的函数原型, 这样人家才知道如何调用你写的函数, 就如同你调用 printf 函数一样, 里面的参数是怎样的? 你是怎么知道的? 还不是看人家的头文件中的相关声明!

二、.hpp文件

.hpp文件的实质就是将.cpp的实现代码混入.h头文件中,定义与实现都包含在同一个文件,则该类的调用者只需要include该hpp文件即可,无需再将cpp加入到project中进行编译。而实现代码将直接编译到调用者的obj文件中,不再生成单独的obj,采用hpp将大幅减少调用project中的cpp文件数与编译次数,也不用再发布烦人的lib与dll,因此非常适合用来编写公用的开源库

  1. 是Header Plus Plus 的简写。
  2. 与*.h类似,hpp是C++程序头文件
  3. L 专用的头文件,已预编译。
  4. 是一般模板类的头文件。
  5. 一般来说,*.h里面只有声明,没有实现,而*.hpp里声明实现都有,后者可以减 少.cpp的数量。
  6. *.h里面可以有using namespace std,而*.hpp里则无。

三、*.hpp要注意的问题

1、不可包含全局对象和全局函数

由于hpp本质上是作为.h被调用者include,所以当hpp文件中存在全局对象或者全局函数时,若该hpp被多个调用者include,将在链接时导致符号重定义错误。要避免这种情况,需要去除全局对象将全局函数封装为类的静态方法

2、类之间不可以循环调用

在.h和.cpp场景中,当两个类或者多个类之间有循环调用关系时候,只要预先在头文件中做被调用类的声明即可,如下:

class B;
class A{
public:
	void someMethod(B b);
};
class B{
public:
	void someMethod(A a);
};

*.hpp 场景中,由于定义与实现都已经存在于一个文件,调用者必需明确知道被调用者的所有定义,而不能等到cpp中去编译。

因此hpp中必须整理类之间调用关系,不可产生循环调用。同理,对于当两个类A和B分别定义在各自的hpp文件中,形如以下的循环调用也将导致编译错误:

//a.hpp
#include "b.hpp"
class A{
public:
void someMethod(B b);
};


//b.hpp
#include "a.hpp"
class B{
public:
void someMethod(A a);
}

3、不可使用静态成员

静态成员的使用限制在于如果类含有静态成员,则在hpp中必需加入静态成员初始化代码,当该hpp被多个文档include时,将产生符号重定义错误。

唯 一的例外是const static整型成员,因为在vs2003中,该类型允许在定义时初始化,如:

class A{
public:
const static int intValue = 123;
};

由于静态成员的使用是很常见的场景,无法强制清除,因此可以考虑以下几种方式(以下示例均为同一类中方法)

3.1 类中仅有一个静态成员时,且仅有一个调用者时,可以通过局域静态变量模拟

//方法模拟获取静态成员

 someType getMember()
 {
 static someType value(xxx);//作用域内静态变量

 return value;
 }

3.2 类中有多个方法需要调用静态成员,而且可能存在多个静态成员时,可以将每个静态成员封装一个模拟方法,供其他方法调用。

someType getMemberA()
{
    static someType value(xxx);//作用域内静态变量

    return value;
}
someType getMemberB()
{
    static someType value(xxx);//作用域内静态变量

    return value;
}
void accessMemberA()
{
    someType member = getMemberA();//获取静态成员

};

//获取两个静态成员

void accessStaticMember()
{
    someType a = getMemberA();//获取静态成员
    someType b = getMemberB();
};

3.3 第二种方法对于大部分情况是通用的,但是当所需的静态成员过多时,编写封装方法的工作量将非常巨大,在此种情况下,建议使用Singleton模式,将被调用类定义成普通类,然后使用Singleton将其变为全局唯一的对象进行调用。

如原h+cpp下的定义如下:

class A{
public:
    type getMember(){
        return member;
    }
static type member;//静态成员
}

采用singleton方式,实现代码可能如下(singleton实现请自行查阅相关文档)

//实际实现类

class Aprovider{
public:
    type getMember(){
        return member;
    }
type member;//变为普通成员

}

//提供给调用者的接口类
class A{
public:
    type getMember(){
       return Singleton<AProvider>::getInstance()->getMember();
    }
}

C++头文件的作用_Vic_Hao的博客-CSDN博客_c++头文件的作用

c++中的.hpp文件-vivieu-ChinaUnix博客

C++ 之头文件声明定义 - 知乎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值