C++ static关键字详解

背景

前段时间初步整理了C++中static的相关知识点,以此做个记录。

在C++中,static关键字是常见的修饰符。从大方向上static分为两类:
1.类或结构体外的static
2.类或结构体内的static
因此,本文内容的划分如下:
在这里插入图片描述
接下来会结合 静态全局变量/函数静态成员变量/函数静态局部变量 这三个部分,分别通过代码说明static的特点,以及它们的使用场景,最后拿出几个问题进行讨论(代码可以自己实践,为了突出重点,本文不放入程序运行结果截图)。

一、静态全局变量/函数

1、主要特点

静态全局变量/函数的主要特点就在于内部链接

那什么是“内部链接”属性呢?
“内部链接”就是一个名称对编译单元来说是局部的,在链接的时候其他编译单元无法链接到它。
(编译单元:源代码文件及其所包含的头文件的总和,经过预处理之后生成的文件。可以简单理解为一个.cpp文件)
通俗上说,就是静态全局变量/函数的的可见性被限制在定义它的文件中,程序中的其他文件无法访问。

用代码举例:
在Test1.cpp中我们定义一个全局变量value,并在MyClass.cpp中定义同名变量并打印它。

//Test1.cpp
#include <iostream>
using namespace std;
int value = 5;
//MyClass.cpp
#include <iostream>
using namespace std;int value = 10;int main()
{cout << value << endl;return 0;
}

以上代码会报重复定义的问题,这是因为我们不能定义同名的全局变量,因为全局变量的作用域是整个程序。
但如果我们在Test1.cpp中用static修饰value,这时候再运行,控制台会打印10。因为此时Test1.cpp中的value只在Test1.cpp可见,有点类似在类中定义了一个私有变量。(另一种解决全局变量命名冲突的方式,是将MyClass.cpp中的value定义更改为extern声明,即:extern int value;,意味着这里的value实际指向的是Test1.cpp中的value)
函数也是类似,可以用以上方式写个Function函数去验证。

2、静态全局变量的使用场景

静态全局变量是一种在文件范围内可见的变量。可以在以下场景中使用:
1、作用域控制。(如何限制全局变量的作用域,使其只在定义它的文件内可见?)
2、数据隐藏,实现模块私有数据。(如何在模块中存储数据而不影响其他模块?)
3、避免命名冲突。(如何避免在大型项目中全局变量名称冲突?)

二、静态成员变量/函数

1、主要特点

1.静态成员变量

静态成员变量的主要特点就是内存共享
也就是说如果有一个类,类中有一个静态成员变量,当我们不断创建这个类的实例,实际上它们的这个静态成员变量指向相同的内存。当其中一个类实例改变了这个变量的值,其他类实例的这个变量也会更改。
进一步讲,静态成员变量是属于类的,不是类实例的。
所以在讨论到生命周期的时候,普通成员变量会在对象创建时被初始化,在对象销毁时被销毁,与对象的生命周期相同;静态成员变量不依赖对象的创建,它在程序启动时被初始化,在程序结束时被销毁,与程序的生命周期相同。

通过代码举例:

//MyClass.cpp
#include <iostream>
using namespace std;class Entity {
public:int x, y;
};int main()
{Entity e1;e1.x = 2;e1.y = 3;Entity e2;e2.x = 5;e2.y = 6;return 0;
}

这是一个普通的类Entity,有两个成员变量x和y,我们创建两个实例,分别对x、y赋值,如果分别打印x、y的数值,那么就如代码所写的,分别是2、3以及5、6。因为每个实例都有自己的x和y,互相不受影响。
当我们在x,y前增加static,那么它们就变成了静态成员变量。
这里有一个地方需要注意!
如果我们在这时直接运行代码,可以发现报错了:静态成员变量x、y未定义。这是因为我们在类内这样写:

class Entity {
public:static int x, y;
};

这实际上只是声明,我们还需要在类外对静态成员变量进行定义。(静态成员变量是类内声明,类外定义。至于为什么要在类外定义,后续会说明。)

class Entity {
public:static int x, y;
};int Entity::x;
int Entity::y;//定义的方式是: 变量类型 作用域::变量名;
//可以赋初值,也可以不用。

这时运行代码,打印出的两个实例的x、y都是5、6,因为静态成员变量x、y是所有对象共享的。
由于静态成员变量属于类,不属于类实例,我们可以不创建实例,直接通过类名去访问变量:

int main()
{Entity::x = 5;Entity::y = 6;return 0;
}

以上是静态成员变量的内容,我们通过代码说明了它的共享内存的特点。

2.静态成员函数

静态成员函数的主要特点在于:静态成员函数不能直接访问非静态成员变量

原因是普通的成员函数实际上是通过获取当前类的实例去访问变量的,也就是说有一个隐含的this指针,指向调用该函数的对象实例,但问题在于静态成员函数没有this指针,也就无法知道是哪个对象在访问变量。

普通成员函数在编译时的真实样子:

//假设Entity类有一个打印函数Print
void Print(Entity e)
{cout << e.x << e.y << endl;
}
//可以看到这里会有一个隐藏参数Entity e,通过这个对象e,我们可以访问到这个类的成员变量。
//但是静态成员函数没有这个隐藏参数(因为静态成员函数不属于类实例),因此无法直接访问到成员变量。

这里还有一个地方主要注意:为什么要强调“直接访问”呢,因为我们实际上可以通过在静态成员函数中创建对象的方式去间接访问到非静态成员变量,如下所示:

class Entity {
public:int x, y;
};class A {static void Test() {Entity e;e.x = 5;e.y = 6;}
};

2、静态成员变量的使用场景

静态成员变量是属于类的,而不是属于类的任何一个对象。可以在以下场景中使用:
1、数据共享。(如何让一个类的所有对象共享一个变量?)
2、类级别的常量。(如何在类内部定义一个常量,使其可供所有对象使用?)
3、计数器或唯一ID生成器。(如何为每个对象生成一个唯一的ID?)
4、配置或状态存储。(如何在类中存储一些全局配置或状态信息?)

三、静态局部变量

1、主要特点

静态局部变量与前面的两种static又有些区别,当我们定义一个静态局部变量时,我们需要考虑两个部分,一个是作用域,另一个生命周期。静态局部变量的作用域是局部(函数、if语句等等),但它的生命周期是整个程序。
生存周期延长到整个程序的执行周期,这意味着在函数调用结束后静态局部变量不会被销毁,而是保留其值供下一次调用使用

用代码举例:

#include <iostream>
using namespace std;void Function()
{int i = 0;i++;cout << i << endl;
}int main()
{Function();Function();Function();return 0;
}

我们在函数中定义了一个普通的局部变量,程序运行结果是 1 1 1。每次调用函数时,i总会被初始化为0,然后自增一次,最后打印,所以每次调用,这个i值最后总为1。
如果我们使用static修饰这个i,程序运行结果时 1 2 3。这是因为当第一次调用函数时,i会被初始化为0,而之后的每次调用,i的值不会再初始化,而是保留值进行下一次操作。
这里程序运行的效果和我们定义一个全局变量是相同的,但是由于程序的任何位置都可以访问全局变量,并对其进行更改,因此这极大增加了程序的可操作性。就如下代码所示,我们可以在函数调用之余对i值进行更改:

#include <iostream>
using namespace std;int i = 0;
void Function()
{i++;cout << i << endl;
}int main()
{Function();Function();i = 10;Function();return 0;
}

当我们不希望其他人直接访问i,而只能通过函数调用的形式去访问,那么我们就可以将i值放入函数中,为了达到可以输出 1 2 3 这样的结果,就使用static将其变为静态的局部变量。

同样的,另一个常见的例子就是单例类(只存在一个实例的类):

#include <iostream>
using namespace std;class Singleton {
public:static Singleton& Get() {static Singleton instance;return instance;}void Print() {}
};int main()
{Singleton::Get().Print();return 0;
}

可以看到我们定义了一个单例类,命名为Singleton。当我们第一次调用Get()函数,静态局部变量instance会被初始化一次,而之后Get()的每次调用,只会得到已经存在的instance,那么这样我们就能保证这个类只有一个实例。

2、静态局部变量的使用场景

静态局部变量的存在是为了提供一种既具有局部作用域、又具有全局生命周期的变量。可以在以下场景中使用:
1、单例模式。(如何确保一个类只有一个实例,并提供一个全局访问点?)
2、函数级别的状态保持。(如何在函数调用之间保持状态,而不使用全局变量?)
3、避免重复初始化。(如何避免在函数多次调用时反复初始化资源? 如何在函数内缓存计算结果,以提高性能?)

四、Q&A

问:

为什么不能在类的内部定义以及初始化static成员变量?

答:

因为如果在类的内部定义并初始化静态成员变量,意味着每个对象都包含该静态成员,这些同名的静态成员变量实际上是不同的变量,不是共享的。
我们将其声明在类的外部定义,可以确保静态成员变量在整个程序中只有一个实例,确保全局唯一性和正确的内存分配。
(需要注意的是,有个例外的情况:静态常量整型变量可以在类内定义,如:const static int value = 1; 除了int,还有char、bool、long long、枚举(enum 和enum class都可以)等。
静态常量整型成员可以在类内初始化,这是因为整型常量在编译时就可以确定其值,而且这些值通常很小,可以直接嵌入到代码中。这样做可以提高效率,因为不需要在运行时进行初始化。)

问:

为什么静态成员函数不能声明为const(const修饰成员函数)?

答:

const修饰的成员函数被用来表示函数不会修改该函数访问的目标对象的数据成员。
但是静态成员函数不与特定的类对象实例相关联,它不能访问对象的非静态成员变量。
所以,用const表示不修改对象状态的特性在静态成员函数中是多余的。

问:

为什么静态成员函数不能直接访问非静态成员变量?

答:

两个原因:
1.
静态成员函数只属于类本身,随着类的加载而存在,不属于任何对象,是独立存在的。
非静态成员只有在实例化对象之后才存在,静态成员函数产生在前,非静态成员产生在后,所以不能访问。
2.
静态成员函数没有this指针,无法直接访问类的非静态成员。

以上是所有内容,欢迎一起讨论!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/27492.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Day07-06_13【CT】LeetCode手撕—1. 两数之和

目录 题目1-思路2- 实现⭐1. 两数之和——题解思路 3- ACM实现 题目 原题连接&#xff1a;1. 两数之和 1-思路 哈希表 利用哈希表存储 key 数组元素值 ——> value 数组下标遍历数组 2- 实现 ⭐1. 两数之和——题解思路 class Solution {public int[] twoSum(int[] nums…

Linux实验八:流式套接字编程

目录 一、实验目的二、实验内容三、实验环境四、参考代码五、实验步骤步骤1. 编辑源代码blockserver.c和blockclient.c步骤2. 编译源代码blockserver.c和blockclient.c步骤3. 运行可执行程序blockserver和blockclient 六、实验结果七、实验总结 一、实验目的 1、深入理解 TCP/…

嵌入式操作系统_3.操作系统内核架构

内核是操作系统的核心部分&#xff0c;它管理着系统的各种资源。内核可以看成连接应用程序和硬件的一座桥梁&#xff0c;是直接运行在硬件上的最基础的软件实体。目前从内核架构来划分&#xff0c;可分为宏内核&#xff08;Monolithic Kernel&#xff09;和微内核&#xff08;M…

网络编程(一)基本概念、TCP协议

文章目录 一、概念&#xff08;一&#xff09;网络发展阶段1. ARPAnet阶段2. TCP/IP两个协议阶段3. 网络体系结构和OSI开放系统互联模型4. TCP/IP协议簇体系结构&#xff08;1&#xff09; 应用层&#xff1a;&#xff08;2&#xff09;传输层&#xff1a;&#xff08;3&#x…

【STM32】GPIO输出(江科大)

一、GPIO简介 1.GPIO&#xff1a;通用输入输出口 2.可配置为8种输入输出模式 3.引脚电平&#xff1a;0-3.3V&#xff08;输出最大3.3V&#xff09;&#xff0c;部分引脚可容忍5V&#xff08;输入&#xff0c;有FT&#xff09; 4.输出模式下&#xff0c;可控制端口输出高低电平…

linux系统宝塔服务器temp文件夹里总是被上传病毒php脚本

目录 简介 上传过程 修复上传漏洞 tmp文件夹总是被上传病毒文件如下图: 简介 服务器时不时的会发送短信说你服务器有病毒, 找到了这个tmp文件, 删除了之后又有了。 确实是有很多人就这么无聊, 每天都攻击你的服务器。 找了很久的原因, 网上也提供了一大堆方法,…

通过语言大模型类学习python,卡哪问哪(一)

代码语法学习&#xff0c;代码解析 import matplotlib.pyplot as plt import numpy as np import PIL import tensorflow as tffrom tensorflow import keras from tensorflow.keras import layers from tensorflow.keras.models import Sequential 一、语法解析 &#xff08;…

themleaf 页面弹层取值

themleaf 页面弹层取值 创作背景themleaf页面事件onbluronclick 页面参数提交 创作背景 个人在日常开发中&#xff0c;遇到了一个需求页面&#xff0c;页面交互较多&#xff0c;用到的事件也很丰富&#xff0c;特此记录&#xff0c;方便后续查找也方便有需要的开发者采用&…

10 SpringBoot 静态资源访问

我们在开发Web项目的时候&#xff0c;往往会有很多静态资源&#xff0c;如html、图片、css等。那如何向前端返回静态资源呢&#xff1f; 以前做过web开发的同学应该知道&#xff0c;我们以前创建的web工程下面会有一个webapp的目录&#xff0c;我们只要把静态资源放在该目录下…

信号与系统实验MATLAB-实验2-连续时间系统的时域分析

实验二 连续时间系统的时域分析 一、实验目的 1、掌握连续时间信号卷积及其MATLAB实现方法&#xff1b; 2、掌握连续系统的冲激响应、阶跃响应及其MATLAB实现方法&#xff1b; 3、掌握利用MATLAB求LTI系统响应的方法&#xff1b; 4、掌握利用MATLAB求函数卷积和解微分方程…

学习grdecl文件格式之后的事情

学习了grdecl文件格式&#xff0c;搞地质的专业人士都知道&#xff0c;这是专门用在地质上的油藏软件&#xff08;个人感觉就是斯伦贝谢的Petrel的&#xff09;的一种文件格式&#xff0c;正好自己也在学习三维的开发&#xff0c;顺手写了一个简单的读取grdecl算法&#xff0c;…

Vue3【十九】自定义Hooks钩子 将数据和方法分组

Vue3【十九】自定义Hooks钩子 将数据和方法分组 Vue3【十九】自定义Hooks钩子 将数据和方法分组 每个分组都可以放置 各种生命周期钩子 分组和可以使用计算属性等 案例截图 目录结构 代码 person.vue <template><div class"person"><h2>Vue3自定…

Linux指令学习(4)

目录 0.普通用户和root用户之间的切换 1.head/tail指令 2.管道 3.date命令 4.三个查找相关的指令 5.文件过滤grep 6.打包和压缩 5.zip/unzip指令 0.普通用户和root用户之间的切换 &#xff08;1&#xff09;这个我们之前不是经常使用这个root用户吗&#xff0c;现在随着…

数据结构笔记39-48

碎碎念&#xff1a;想了很久&#xff0c;不知道数据结构这个科目最终该以什么笔记方式呈现出来&#xff0c;是纸质版还是电子版&#xff1f;后来想了又想&#xff0c;还是电子版吧&#xff1f;毕竟和计算机有关~&#xff08;啊哈哈哈哈哈哈哈&#xff09; 概率论已经更新完了&…

【Python入门与进阶】Jupyter Notebook配置与优化

目录 1.Jupyter Notebook简介 2.Jupyter Notebook的安装 2.1 命令行安装 2.2 可视化界面安装 3.Jupyter Notebook的使用 3.1 启动 Jupyter Notebook 3.2 Jupyter Notebook 界面介绍 3.3 创建新的 Notebook 3.4 编写和运行代码单元 3.5 使用 Markdown 编写文档 3.6 保…

快慢指针在字符串中的应用-443. 压缩字符串

题目链接及描述 443. 压缩字符串 - 力扣&#xff08;LeetCode&#xff09; 题目分析 这个题目总体不算太难&#xff0c;如果之前接触过双指针&#xff08;快慢指针&#xff09;的话&#xff0c;比较好做。题目可以理解为计算数组中对应各个连续字符出现的次数&#xff0c;并将…

SAPUI5基础知识5 - 控件(control)的使用

1. 背景 在SAPUI5中&#xff0c;控件&#xff08;Control&#xff09;是构建用户界面的基本元素。控件是一个可重用的组件&#xff0c;它可以与用户进行交互或显示信息。 每个控件都有自己的特性&#xff0c;例如属性&#xff08;Properties&#xff09;、聚合&#xff08;Agg…

btrace:binder_transaction+eBPF+Golang实现通用的Android APP动态行为追踪工具

一、简介&#xff1a; 在进行Android恶意APP检测时&#xff0c;需要进行自动化的行为分析&#xff0c;一般至少包括行为采集和行为分析两个模块。其中&#xff0c;行为分析有基于规则、基于机器学习、基于深度学习甚至基于大模型的方案&#xff0c;各有各的优缺点&#xff0c;不…

kettle实时增量同步mysql数据

** 本文主要介绍运用kettle实时增量同步mysql数据 ** Debezium介绍 官网地址&#xff1a;https://debezium.io/documentation/ Debezium是一个开源项目&#xff0c;为捕获数据更改(Capture Data Change,CDC)提供了一个低延迟的流式处理平台&#xff0c;通过安装配置Debeziu…