• 【学习笔记】C++ 中 static 关键字的作用


    前言

    在 C/C++ 中,关键字 static不同的应用场景下,有不同的作用,这里总结一下,避免在使用时弄混。

    我按照以下的逻辑来分类 static 的作用场景

    1. static 作用在变量上
      1. static 作用在全局变量上:限制全局变量在本文件上
      2. static 作用在局部变量上:即使离开变量作用域,也保存变量值,比如用作计数器
      3. static 作用在成员变量上 :对象间共享该变量
    2. static 作用在函数上
      1. static 作用在函数上:函数可见性限制在本文件中(即使函数被声明在头文件,引用该头文件的其他 cpp 文件也无法使用该函数)
      2. static 作用在成员函数
    3. static 作用在模板上

    static 作用在变量上

    static 作用在全局变量

    对于普通的全局变量来说,同一项目中的其他文件也可访问相同的全局变量,若为了限制全局变量在本文件中,则需要在这个全局变量上加一个 static,这样该变量就只能在本文件可见。

    我们先来演示一下,如何在一个文件中使用另一个文件的全局变量。

    假设有 a.cppb.cpp,我们在 b 中定义一个全局变量 staticValue,然后在 a 中打印出来。

    // b.cpp
    // 全局变量
    int staticValue = 10;
    
    void staticMain() {
    	// ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    // a.cpp
    #include 
    using namespace std;
    
    // 必须通过 extern 关键字在整个项目搜索 staticValue
    extern int staticValue;
    
    int main()
    {
    	cout << "在 a.cpp 中可见:" << staticValue << endl; // 在 a.cpp 中可见:10
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    显然我们成功在 a 文件中使用到了 b 文件内定义的全局变量。

    现在,我希望将 staticValue 的全局可见性,限制在 b 文件内,不让其他文件也可以访问,于是我在 b 的 staticValue 前加上 static 关键字

    // b.cpp
    // 全局变量
    static int staticValue = 10;
    
    void staticMain() {
    	// ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    将代码如上修改后,当我们再次运行时,程序会在链接阶段报错,报错信息如下,根据描述可见在 aobj 文件(汇编后生成的对象文件)里没有解析到 staticValue 这个变量,达到了我们限制全局变量在本文件内的目的
    在这里插入图片描述

    static 作用在局部变量上

    static 作用在局部变量上,即使离开变量作用域,也保存变量值。具体的效果就是延长了变量的生命周期,比如用作计数器。效果如下面的代码所示

    可以看到,num 的值是逐渐累加的过程

    #include 
    
    void count();
    
    int main(void)
    {
        int i=0;
        for (i = 0;i <= 5;i++)
        {
        	count();
        }
        return 0;
    }
    void count()
    {
        /*声明一个静态局部变量*/
        static int num = 0;
        num++;
        printf("%d\n",num); // 1 2 3 4 5 6
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    从编译器的内存分布上讲,被 static 修饰的变量会被保存在数据区中,而普通的变量是保存在栈区或者堆区中

    static 作用在成员变量上

    在这种情况下,static 标记的变量可以在多个对象之间共享该变量,在底层上,因为 static 标记的变量并非存在于对象的内存空间,而是存在于数据区中(这涉及到 C 语言的内存布局)。

    具体的效果如下面代码所示,我们声明了 3 个学生对象,分别是 zhangsan,lisi 和 wangwu,并在定义他们的时候给他们传入了各自的初始分数,最终求得三个人的总分。

    #include 
    using namespace std;
    
    class student {
    private:
    	static int sumScore;
    	int myScore;
    public:
    	student(int m):myScore(m){
    		sumScore += myScore;
    	}
    
    	int getSumScore() {
    		return sumScore;
    	}
    };
    
    int student::sumScore = 0; // 静态成员变量必须在类外被初始化
    
    int main()
    {
    	student zhangsan(10);
    	student lisi(20);
    	student wangwu(30);
    
    	cout << wangwu.getSumScore() << endl; // 60
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    关于 static 在修饰成员变量时的注意事项

    1. static 修饰的成员变量属于类,在对象间共享,因此某个对象修改了该变量,对其他对象也是可见的
    2. 静态成员变量的初始化必须要在类外,如上面的代码所示
    3. 静态成员变量的内存分配是发生在类外初始化的时候,而不是类或者对象创建的时候
    4. 静态成员变量可以通过类,也可以通过对象来访问,但必须遵守访问可见性(publicprivateprotected

    static 作用在函数上

    static 作用在函数上

    它起到的效果和 static 作用在全局变量上类似,将函数的可见性限制在本文件内。

    这里我们直接举被 static 修饰的函数的例子。

    假设在 b.cpp 中我们定义了一个 bMain() 的函数,在 b.h 的头文件中进行声明,在 a.cpp 中引入这个头文件,并调用 bMain(),那么是可以正常调用的。

    现在,我们在 b.cpp 中,在 bMain() 前加上一个 static 关键字,那么 a.cpp 就不能在调用了。

    // b.cpp
    #include 
    using namespace std;
    
    static void bMain() {
    	cout << "bMain" << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    // b.hpp
    void bMain();
    
    • 1
    • 2
    // a.cpp
    #include "b.hpp"
    
    int main()
    {
    	bMain();
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    执行上述代码,我们可以看到和 “static 作用在全局变量上” 小节类似的报错问题
    在这里插入图片描述

    static 作用在成员函数上

    静态成员函数具有和静态成员变量类似的性质和作用,一般可以用这种方式来确定类创建了多少个对象

    1. 静态成员函数在对象之间共享,或者说它独立于对象之外
    2. 可以通过对象来调用静态成员函数,也可以通过类名来调用静态成员函数
    3. 静态成员函数可以访问静态成员变量或其他静态成员函数,但不能使用当前对象指针
    #include 
    using namespace std;
    
    class student {
    private:
    	static int sumObj;
    public:
    	student() {
    		sumObj += 1;
    	}
    	static int getObjNum() {
    		return sumObj;
    	}
    };
    
    int student::sumObj = 0; // 静态成员变量必须在类外被初始化
    
    int main()
    {
    	student zhangsan;
    	student lisi;
    	student wangwu;
    
    	cout << "student 有几个对象? " << student::getObjNum() << endl; // 3
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    可以看到,最终我们输出了 3 个对象

    static 作用在模板上

    这里包括了函数模板和类模板,它们的逻辑都是一样的

    1. 描述:当是相同的模板实例时,static 修饰的变量在模板间共享;当是不同的模板实例时,static 修饰的变量独立存在于各个实例中。下面是一个例子
    #include 
    using namespace std;
    
    template <typename T>
    void fun()
    {
      static int i = 1;
      cout << ++i << endl;
      return;
    }
    
    int main()
    {
      // 前两个是相同的模板函数
      fun<int>();  // 2
      fun<int>();  // 3
    
      // 这是不同的模板函数
      fun<double>(); // 2
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    类模板也是一样的道理

    #include  
    using namespace std;
      
    template<class T> class Test
    {
    	public:
    		static int count;
    		Test(){};
    		~Test(){};
    		void add()
    		{
    			count++;
    			cout << count << endl;
    		}
    };
    
    template<class T>
    int Test<T>::count = 0;
    
    int main()
    {
    	Test<int> a;
    	Test<int> b;
    	Test<double> c;
    	
    	a.add(); // 1
    	b.add(); // 2
    	c.add(); // 1
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    【注】函数(类)模板是指的在文件中定义的,程序员自己写的模板部分。模板(类)函数是指在实例化时,编译器为我们生成的函数

    参考

    1. 《Primer C++》
    2. Templates and Static variables in C++
  • 相关阅读:
    [Linux嵌入式开发]Linux常用命令
    Nanoframework 操作单片机蓝牙配置WIFI的案例
    如何使用ArcGIS Pro提取河网水系
    CentOS yum update
    关于docker无法正常下载镜像的问题
    千万级用户的大型网站,如何进行服务器压力预估?
    Real-Time Rendering——7.9 Irregular Z-Buffer Shadows不规则Z缓冲阴影
    jQuery--引入jq&jq简单方法
    2022年10月27日下午工作日志
    玻色量子“天工量子大脑”亮相中关村论坛,大放异彩
  • 原文地址:https://blog.csdn.net/qq_34902437/article/details/132698527