zoukankan      html  css  js  c++  java
  • const对象为什么可以在头文件中定义

    对于头文件中为什么可以定义const变量(或对象),以及推荐用const代替#define宏定义,之前一直概念不清晰,今天就总结一下。

    之前在网上查过,解释的都不太到位,或者角度不一样(从编译原理、强弱定义?),总之不能清晰理解,发现《C++ Primer》上基本上涵盖了所有平常遇到的C/C++问题,而且《C++ Primer》地位正宗,上面的解释可信度自然没得说。so,有问题,先找《C++ Primer》。

    1.const对象默认为文件的局部变量。《C++ Primer 4》p86,

    2.头文件用于声明而不是用于定义。《C++ Primer 4》p100,原因是:

    1.定义和声明的区别:定义只可以出现一次,而声明则可以出现多次。下面这些是定义(怎样判断的?),都不应该出现在头文件中:

    extern int ival = 10;

    double fica_rate;

    同一个程序中有两个以上文件含有上述任一个定义都会导致多重定义链接错误。编译时可能不会报错,因为编译时每个源文件都是单独编译生成目标文件,彼此是相互独立不可见的,只有在链接的时候整个工程才作为一个整体,链接主要解决模块间的相互引用问题,而在编译阶段生成目标文件是,会暂时搁置那些外部引用,外部引用是在链接时进行确定的。

    因为头文件包含在多个源文件中,所以不应该含有变量或函数的定义。但是……

    3.对于头文件不应该含有定义这一规则,有三个例外。头文件中可以定义类、值在编译时就已知道的const对象(数组的大小可以用const int型变量定义,这在C中是不行的),和inline函数。《C++ Primer 4》p100,在补充一个,模板"必须"定义在头文件中,包括模板类和模板函数。

    下面来解释原因:

    1.这些实体可以在多个源文件中定义,只要每个源文件中的定义是相同的。

    2.在头文件中定义这些实体,是因为编译器需要他们的定义(不只是声明)来产生代码(这句话对模板同样适用)。例如:为了产生能定义或使用类的对象的代码,编译器需要知道组成该类型的数据成员。同样还需要知道能够在这些对象上执行的操作。类定义提供所需要的信息。当然(但是)类定义内部的成员可以是声明,类定义和类内部成员的定义不是一回事哦,可以不同步进行,也”不建议“类定义时定义类内部函数。在头文件中定义const对象则需要更多解释。

    3.下面是本节的重点了(引自《C++ Primer 4》,满满的都是干货):

    const 变量默认时是定义该变量的文件的局部变量。正如我们现在所看到的,这样设置默认情况的原因在于允许 const 变量定义在头文件中。

    在 C++ 中,有些地方需要放置常量表达式。例如,枚举成员的初始化式必须是常量表达式。在以后的章节中将会看到其他需要常量表达式的
    例子。

    一般来说,常量表达式是编译器在编译时就能够计算出结果的表达式。当 const 整型变量通过常量表达式自我初始化时,这个 const 整型变量就可能是常量表达式。而 const 变量要成为常量表达式,初始化式必须为编译器可见。 为了能够让多个文件使用相同的常量值,const 变量和它的初始化式必须是每个文件都可见的。而要使初始化式可见,一般都把这样的 const 变量定义在头文件中。那样的话,无论该 const 变量何时使用,编译器都能够看见其初始化式。

    但是,C++ 中的任何变量都只能定义一次(第 2.3.5 节)。定义会分配存储空间,而所有对该变量的使用都关联到同一存储空间。因为 const 对象默认为定义它的文件的局部变量,所以把它们的定义放在头文件中是合法的。 

    这种行为有一个很重要的含义:当我们在头文件中定义了 const 变量后,每个包含该头文件的源文件都有了自己的 const 变量,其名称和值都一样。 

    当该 const 变量是用常量表达式初始化时,可以保证所有的变量都有相同的值。但是在实践中,大部分的编译器在编译时都会用相应的常量表达式替换这些 const 变量的任何使用。所以,在实践中不会有任何存储空间用于存储用常量表达式初始化的 const 变量。 
    如果 const 变量不是用常量表达式初始化,那么它就不应该在头文件中定义(若不初始化const变量,这个const变量有什么用呢?)。相反,和其他的变量一样,该 const 变量应该在一个源文件中定义并初始化。应在头文件中为它添加 extern 声明,以使其能被多个文件共享。


    下面再补充点关于编译和链接方面的知识:

    编译:将预处理生成的文件,经过词法、语法、语义分析及优化后编译成若个目标模块(linux中的 .o 或win32中的 .obj)。

    链接:由链接程序将编译后形成的一组目标模块以及他们所需要的库函数链接在一起,形成一个完整的载入模型。(win32中的可执行程序 .exe)

    编译可以理解为,将高级语言翻译成计算机可以理解的二进制代码,即机器语言。编译器需要的是语法正确,函数与变量的声明正确。

    链接时主要链接函数和全局变量,目标文件之间相互链接自己所需要的函数和全局变量,而函数可能来自其他目标文件或库文件。

    链接可分为:

    1. 静态链接
    2. 载入时动态链接
    3. 运行时动态链接

  • 相关阅读:
    iOS tableHeaderView有默认高度?
    flutter 自定义tabbar 给tabbar添加背景功能
    jar各个版本号的意义
    【转载】springboot + swagger
    分表需要解决的问题 & 基于MyBatis 的轻量分表落地方案
    解决Spring Boot中,通过filter打印post请求的 request body 问题
    SpringBoot自动配置xxxAutoConfiguration 的使用
    Shell
    Spring踩坑记录
    Spring中可复用工具类&&特性记录&&技巧
  • 原文地址:https://www.cnblogs.com/cnsec/p/3789785.html
Copyright © 2011-2022 走看看