Skip to content

C++中static、extern、include、inline、constexpr

1. 全局变量

  • 变量有定义和声明之分,对于一个变量来说,声明可以多次,但是定义只能由一次。
  • 不允许在函数外部给变量赋值,全局变量在函数外只能定义时初始化(只能 int a = 0;或者int a;C++默认全局变量初始值为0。不能先声明再赋值,比如 int a;a=5;这会报错)

2. static

  • static是静态的意思。用static声明的变量都存在静态区,静态区包括静态全局变量、静态局部变量(作用域只在局部,全局不可见)、全局变量、类内静态成员变量。静态区的所有变量,初始化的时候自动初始化为 0。

  • static有两个作用:一是限定作用域,二是只初始化一次。

2.1 静态局部变量

在修饰局部变量的时候,static 修饰的静态局部变量只执行初始化一次,并且作用域仍然在局部,外部不可见,static延长了局部变量的生命周期,直到程序运行结束以后才释放。

2.2 静态全局变量

  • 通常情况下,静态全局变量的声明和定义放在源文件中,并且不能使用extern关键字将静态全局变量导出,因此静态全局变量只能在定义此变量的文件中调用(也就是说同一个名字的静态全局变量,可以在多个源文件中定义不同的值,互不影响,因为他们的作用域只限于文件本身)。

  • 普通全局变量的作用域是整个工程,在头文件中使用extern关键字声明普通全局变量,并在源文件中定义,其他文件中只要使用#include包含声明普通全局变量的头文件,就可以在当前文件中使用普通全局变量。

  • 如果在头文件中声明静态全局变量,静态全局变量在声明的同时会被初始化,如果静态全局没有显示地初始化为默认值,相当于在头文件中同时完成声明和定义,而普通全局变量不能直接定义在头文件中。

2.3 静态普通函数

  • static 修饰一个函数,则这个函数的只能在定义此函数的文件中调用,不能被其他文件调用。(也就是说同一个名字的静态函数,可以在多个源文件中定义不同的实现,互不影响,因为他们的作用域只限于文件本身)
  • 也就是include函数头文件后,必须要在本文件中实现函数的定义,而不能链接阶段去别的文件寻找函数的定义。 比如
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    //a.h
    static int func(int a);
    
    //a.cc
    #include "a.h"
    int func(int a){
        return 10;
    }
    
    //b.cc
    #include "a.h"
    int main(){
     func();//无法调用,必须要在b.cc中重新定义func。去掉a.h中的static就可以调用,因为函数声明默认是extern的。
    }
    

2.4 类中的静态成员变量

被 static 修饰的变量属于类变量,可以通过类名.变量名直接引用,而不需要 new 出一个类来,也就是不需要通过具体的对象进行访问。

  • 静态数据成员可以实现多个对象之间的数据共享,它是类的所有对象的共享成员,它在内存中只占一份空间,如果改变它的值,则各对象中这个数据成员的值都被改变。

  • 静态数据成员是在程序开始运行时被分配空间(分配到静态区),到程序结束之后才释放,只要类中指定了静态数据成员,即使不定义对象,也会为静态数据成员分配空间。

  • 静态数据成员可以被初始化,但是只能在类体外进行初始化,若未对静态数据成员赋初值,则编译器会自动为其初始化为 0。

  • 静态成员变量使用前必须先在类外初始化(如 int MyClass::m_nNumber = 0;),否则会在 linker 时出错。但是如果是const static变量,就可以在类内初始化,因为所有类都有这个变量且不可变。(C++17之前)。C++17 之后,inline可以直接修饰变量(之前只可以修饰函数),可以在类内通过static inline 直接进行声明与初始化,也可以在类内部进行声明,在类外(但是在头文件中)通过inline 进行初始化。其实就是新标准通过inline能够保证类内静态变量只初始化一次,全局共享一份数据,而之前的标准是不允许inline修饰类的静态成员变量的;在c++17开始,constexpr包含inline含义。

    1
    2
    3
    4
    5
    6
    7
    class MyClass
    {
        static inline std::string name = "asda"; // OK since C++17
        ...
    };
    
    inline MyClass myGlobalObj; // OK even if included/defined by multiple CPP files
    

  • 静态数据成员既可以通过对象名引用,也可以通过类名引用

2.5 类中的静态成员函数

被 static 修饰的方法属于类方法,可以通过类名.方法名直接引用,而不需要 new 出一个类来。 - 类的对象可以使用静态成员函数和非静态成员函数。但是静态成员函数不能调用非静态成员变量(因为非静态成员变量还没有分配内存,实例化后才分配内存) - 类的非静态成员函数可以调用用静态成员函数,但反之不能。 - 非静态成员函数有 this 指针,而静态成员函数没有 this 指针 - 总结:非静态可以调用静态,但是静态不可以调用非静态。

3 extern的用法:

  • extern:告诉编译器,这个变量是外部的,在链接阶段,在别的源文件中寻找其定义。 例如:extern int a;显式的说明了a的存储空间是在程序的其他地方分配的,在文件中其他位置或者其他文件中寻找a这个变量。
  • 对于函数来说,不需要显式的指定extern,默认函数就是extern的,所以include函数的头文件后,根据函数的声明可以在函数的源文件中找到其定义。
  • 对于变量来说,需要显示指定extern。如果需要用到其他源文件中的全局变量,那么需要在对应的头文件中添加extern声明。

用法 - 一个c文件需要调用另一个c文件里的变量或者函数,而不能从.h文件中调用变量。 - extern int a = 5与int a = 5意义是一样的,都是定义。而extern int a;是声明。但会产生一条警告(这样extern就失去了原有的意义,是不推荐的用法)。 - 对于函数而言,和引用变量是一样的,如果需要调用其他.c文件中的函数,在文件中的函数声明前加extern即可,不加extern而直接声明函数也可以,因为声明全局函数默认前面带有extern

3.1 建议用法

例如:在a.h里写extern int a;(声明),a.c文件中定义int a = 5,如果要在其他文件里调用a这个变量,直接#include,然后直接使用a这个变量即可。

3.2 extern和include的区别

include相当于把include .h文件直接带入到本源文件里,比如在b.c文件里include “a.h”,就相当于把a.h文件里所有定义的变量和函数全部拷贝了一份放入了b.c里,一个项目里,一个.h文件可能会被多个.c源文件包含,这样编译的时候就会报重复定义的错误。而且尽量不要在.h里定义变量,这是一个不好的习惯。

4 inline

inline和constexpr都可以多次定义(每个编译单元(cpp文件)定义一次)。因此可以将定义写在头文件中,使用时include该头文件即可,不会报重复定义的错误。

inline表示内联。表示函数或变量全局只有一份,可以有多次定义。 - 以inline修饰的函数,在编译时会在调用内联函数的地方展开,没有函数压栈的开销,提高程序运行效率。inline只是对编译器的一个建议,编译器会自动去优化。inline必须和函数定义放在一起才有作用,放在声明前不起作用。因此,对于内联函数,不能像普通函数那样,直接在.h文件里面声明下,源文件里面定义下,然后其他C文件就可以调用 - 单独定义inline函数最好和static连用。如果inline函数在两个不同的文件中出现,也就是说一个.h被两个不同的文件包含,则会出现重复定义,编译失败。而加上static的前缀后,就解决了这个问题。static可以限制作用域在当前函数定义的文件。即使有多个定义也不冲突。

  • static修饰的全局函数或者变量每个编译单元都有自己的一份实例(限制定义文件为作用域)。
  • 而inline修饰的全局函数或变量都是共用一份。
  • 对于全局函数/变量而言static inline修饰与单独static修饰的效果一致。
  • 对于类内变量而言,static inline修饰的效果就跟inline的效果一致,全局共享一份;

5 constexpr

常量表达式,通常用来定义全局常量,在一个头文件中定义好constexpr常量,可以被多个源文件引入(也就是说每个编译单元(源文件)都可以定义一次inline或constexpr修饰的变量,通常是include含有inline和constexpr的头文件),而不报错。

  • 内联函数和constexpr函数可以在程序中多次定义,但是多个定义必须完全一致,因此内联函数和constexpr函数通常定义在头文件中。
  • 在C++17中constexpr含有inline的声明。
  • constexpr用来定义全局常量更方便,只需要单独开一个.h文件专门放全局常量即可,其他Cpp文件使用时只需要include该头文件。