1 、引子
       在平常的 C/C++ 开发中,几乎所有的人都已经习惯了把类和函数分离放置,一个 .h 的头文件里放声明,对应的 .c 或者 .cpp 中放实现。从开始接触,到熟练使用,几乎已经形成了下意识的流程。尽管这样的做法无可厚非,而且在不少情况下是相对合理甚至必须的,但我还是要给大家介绍 一下把实现全部放置到头文件中的方式,给出可供大家使用的另一个选择。同时针对这一做法,也顺便说一下其优缺点以及需要注意的情况。
       我是一个很喜欢简洁的人,多年以来甚至养成了这样的癖好,如果一个功能是能够用一条语句实现的,那就不要用两条语句。在我看来,如果给别人提供一份可以复 用的代码的话,最优雅的状态莫过于仅仅提供一个头文件就全部搞定。之所以不太喜欢引入源文件,最重要的原因是源文件往往会带来工程文件的变化;而且,在使 用过程中也会增加一些额外的操作,例如,在一个组织良好的工程里,头文件和源文件很有可能是位于不同的目录,这样就会多带来一次文件复制操作。

2 、正文
     2.1 顾虑
         我遇到有不少人不使用头文件来包含实现,往往是出于以下几种顾虑:
         1、 暴露了实现细节
         2、 头文件被包含到不同的源文件中,会导致链接冲突
         3、 头文件被包含到不同的源文件中,会导致有多份实现被编译出来,增大可执行体的体积
       如果有顾虑 1 ,那很显然应该在第一时间抛弃完全在头文件中实现的念头。不过我遇到的情形里,通常后两种顾虑占据了绝对的比例。而这种顾虑,通常是由于对 C/C++ 没有足够的了解导致的。
      有顾虑 2 的,经常会是一些有 C 语言开发经验的程序员。他们所担心的也往往是出现的全局函数的情况。例如有以下头文件 c_function.h (清晰起见,防卫宏之类的代码没有列出):
[cpp]
int integer_add(const int a, const int b) 

         return a + b; 

      如果在同一工程中,有 a.c (或者是 .cpp )和 b.c 两个(或两个以上)源文件包含了此头文件,则在链接时期就会发生冲突,因为在两个源文件编译得到的目标文件中都有一份 integer_add 的函数实现,导致链接器不知道对于调用了此函数的调用者,应该使用哪一个副本。

2.2 着手
       解决的办法有两个,各自为两个关键字,一个是 inline ,另一个是 static 。使用这两个关键字的任意一个来修饰 integer_add 函数,都会消除上述的冲突问题,然而本质却大不相同。
       如果使用 inline ,则意味着编译器会在调用此函数的地方把函数的目标代码直接插入,而不是放置一个真正的函数调用,实际作用就是这个函数事实上已经不再存在,而是像宏一样 被就地展开了。使用 inline 的副作用,首先在于毋庸置疑地,代码的体积变大了;其次则是,这个关键字严格算起来并不是 C 语言的关键字,使用它多少会带来一些移植性方面的风险,尽管主流的 C 语言编译器都可以支持 inline 。对于 GCC , inline 功能关键字就是 inline 本身,而对于微软的编译器,应该是 __inline (注意有两个前导下划线)。而且,根据惯例, inline 通常都是对编译器的某种暗示而非强制要求,编译器有权力在你不知情的情况下把它实现为非 inline 的状态(可能的原因有,函数太大或者复杂度过高)。这样的后果是什么,不好意思,我没有测试过。
       如果是使用 static ,那么至少结果是可预料的。所有包含此头文件的源文件中都会存在此函数的一份副本。虽然代码也有一定程度的膨胀,但好就好在互相不冲突,因为 static 关键字保证了该函数的可见度为单个源文件之内。
以上的讨论虽然看起来主要聚焦在 C 语言上,但由于 C++ 是 C 语言的超集,并且在这些方面并没有做太多的修改,因此讨论结果同样也适用于 C++ 。

2.3 继续
        对于 C 语言来讲,上面的改进几乎已经走到了尽头,没有继续发展的余地。然而对于 C++ 则不同,我们还可以进一步把它做得更漂亮。
首先,我们做以下的改动:
[cpp] 
class Integer 

public: 
         int add(int a, int b) 
         { 
                   return a + b; 
         } 
}; 
       这样的形式,几乎连 C++ 的初学者都能看出来,确实不会再发生链接冲突的问题了。不过也有一个问题,我们如果要计算两个整数的和的话,需要这样写:
       Integer op;
       op.add(i, j);
       而这显然不是一种可接受的状态,之前很简单的一条函数语句的调用,现在却必须定义一个类的对象实例。于是我们再次求助于 static ( inline 是不适用的,因为它不能去掉定义对象实例这一步,而且事实上,把实现写到类定义之内的函数缺省就是 inline 的)。现在,类就像这个样子:
[cpp]
class Integer 

public: 
         static int add(int a, int b) 
         { 
                   return a + b; 
         } 
}; 
       调用方式也相应地简化为:
       Integer::add(i, j);
       尤其需要注意的就是这里, C++ 类中的 static 函数和全局 static 函数的行为是有差异的,它编译之后仅产生一份实现代码,并不会由于被多个源文件包含而产生多份副本 。
       这距离我们的终极目标已经不远了(我们的终极目标是: add(i, j) 就可以搞定)。于是我们再次高举起宏这杆大旗,在头文件里添加以下定义:
       #define integer_add         Integer::add(后注:突然想到,似乎定义 const 函数指针也可以达到相同的目的)
      上面解决的其实仅仅是 C++ 中全局函数的头文件复用问题,那么类呢?类的情况要复杂一些。如果是 static 方法,那么正好是和上述我们对全局函数的变通实现是一致的;如果是 inline 的方法(不管有没有 inline 关键字),则其状态几乎理论上等同于前面所述的 inline 全局函数的情况。那么还有最后的一种情况, virtual函数。对于 virtual 函数,我们等到的是一个好消息:它总是生成一份代码(甚至你显式使用 inline 关键字修饰) 。这里面有个玄机: virtual 函数的地址会被写到类的 v-table 里,是要能够在运行期被调用的(其核心在于,其调用者以及调用时机在编译时是不明确的),所以绝对不能生成为全部就地展开的形式。以此可以做一个推论:所 有会被求址的成员函数,都会生成一份函数实体,而不能单纯地去符合内联的修饰关键字。

3 、后记
      当然,把实现全部放在头文件中并不是万金油,不是放之四海而皆准的准则,正如本文开头所说,这仅仅是一种选择,只不过你之前没有想到过可以这么做,而现在知道了。它最适合的场合是一些规模较小的工具类的实现。

函数实现不放在头文件的原因,及何时可以放头文件的情况相关推荐

  1. python open找不到文件的原因_python – logger找不到文件

    我输入此输入命令时收到此错误: $python3.4 cron_e2e.py -f test_web_events -E ctg-clickstream testbrad 错误: $python3.4 ...

  2. 函数实现不放在头文件的原因,及何时可以放头文件的情况【转】

    1 .引子 在平常的 C/C++ 开发中,几乎所有的人都已经习惯了把类和函数分离放置,一个 .h 的头文件里放声明,对应的 .c 或者 .cpp 中放实现.从开始接触,到熟练使用,几乎已经形成了下意识 ...

  3. 头文件(include)、源文件(src)、main函数(自定义文件夹)三者在三个不同文件夹的实现,exe独立生成文件夹(bin)

    最近想要实现头文件.源文件.main函数三者在三个不同文件夹的实现,查找了很多博客,对tasks.json,  launch.json,  c_cpp_properties.json相关配置优良进一步 ...

  4. application.properties引用其他文件_金橙智能 | C语言头文件组织与包含原则,你知道吗?...

    点击蓝字 关注我们 同学们知道C语言中头文件的组织与包含原则吗?如果你还不知道,那么小橙来给你讲解一下,希望同学可以认真学习. 头文件的作用 程序执行在预处理阶段,编译器会把源文件包含的头文件中的内容 ...

  5. c语言文件操作步骤是,文件操作的正确流程,C语言文件操作的函数

    引言 操作文件的正确操作流程为: 打开文件->读写文件->关闭文件 在对文件进行读写操作之前,需要先打开文件,操作完成之后就要关闭文件!所谓的打开文件,就是需要获取文件的信息,例如文件名. ...

  6. c语言中用来指示文件缓冲区中具体读写位置,C语言文件读写操作中缓冲区问题和setbuf函数详解...

    清除和设置文件缓冲区 (1).清除文件缓冲区函数: int fflush(FILE *stream); int flushall(); fflush()函数将清除由stream指向的文件缓冲区里的内容 ...

  7. php数组使用json_encode函数中文被编码成null的原因和解决办法

    大写的囧,提客户处理问题,前端的APP一直在叽叽咂咂,说收到的值是null,弄了半天原来是这个问题,记录下吧 json格式在开发中用的十分广泛.在php中json_encode函数可以直接将数组转成 ...

  8. 单片机c语言怎样添加自定义头文件,单片机C语言编程与或|头文件常见问题

    一.常见问题 1.头文件reg51.h和reg52.h其实是一样的,大家两个都可以用. 2.main()前面的void可加可不加,反正都是无返回值函数. 3.不是每一个程序都要用到死循环while(1 ...

  9. linux函数删除某文件,Linux环境下用C++删除指定文件

    Linux环境下用C++删除指定文件 "Talk is cheap, show me the code!" #include #include #include #include ...

最新文章

  1. 《计算机网络应用基础》模拟试卷(六),《计算机与网络应用基础知识1》模拟试卷...
  2. 计算机网络实验ip数据报转发,计算机网络实验报告三网际协议IP.doc
  3. 大咖说:出道十五载,认知五迭代
  4. Pycharm运行Scrapy报错:no active project Unknown command: crawl Use “scrapy“ to see available commands
  5. java开发后台技术_java开发后台的技术
  6. Linux 文件目录特殊权限设定(SUID,SGID,SBIT)
  7. quartz 任务调试 建表 sql 语句、create table语句
  8. Enterprise Library 2.0 插件介绍:Avanade Integration Pack
  9. (二)Netty之IO模型
  10. Apache 服务器配置详解
  11. 混合现实平台 Mesh、云服务能力翻倍扩容、福利 150 万人……Ignite China 精彩盘点...
  12. JavaScript 代码简洁之道
  13. contenttype类型_HTTP请求中,几种常见的ContentType类型解析
  14. java不解压获取压缩包(zip,rar)文件列表或文本文件内容
  15. python强行终止程序_python终止程序的方法
  16. “嫦娥一号”可以证明美国当年登月是否属实
  17. 泰康人寿java开发待遇怎么样_宋晓伟-泰康人寿-5年Java开发高级工程师-猿急送
  18. 黑马程序员《JavaWeb程序设计案例教程》_课后习题答案
  19. Windows下安装 gmpy2
  20. 实验二 单隐层神经网络

热门文章

  1. shiro的会话管理:介绍
  2. php开发用框架优缺点,剖析PHP开发中主流PHP框架的优缺点
  3. C语言烧写C51单片机的线,51单片机烧写程序过程以及详细说明【图文】
  4. 加载torchvision中预训练好的模型并修改默认下载路径
  5. Dubbo本地开发技巧
  6. css 宽高自适应的div 元素 如何居中 垂直居中
  7. 偏见为什么是数据科学领域的一个大问题
  8. linux系统结构与文件管理命令
  9. LDAP常用命令解析
  10. PowerShell尝试登录SQL Server