C++ static关键字详解

news2025/2/27 4:29:07

背景

前段时间初步整理了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.coloradmin.cn/o/1823638.html

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

相关文章

supOS工业操作系统的由来

作为“世界制造工厂”&#xff0c;我国拥有最庞大、最完整的工业企业集群与产业链&#xff0c;其中既有众多全球性制造巨头&#xff0c;又有数以百万计的中小型工厂。但这些企业的制造工厂在推进数字化、网络化、智能化进程时普遍受阻&#xff1a;1&#xff09;系统软件定制程度…

mybatis之特殊SQL的执行

1.1模糊查询 尝试&#xff1a; //模糊查询用户 List<User> getUserByLike(Param("mohu") String mohu);<select id"getUserByLike" resultType"user">select * from user where username like %#{mohu}% </select>Test publ…

本地密码记录工具-KeePass

文章目录 软件界面软件下载KeePass配置KeePass修改中文创建数据库配置数据库锁定配置账户密码为不同应用配置账号密码插件安装及使用 数据库同步 在此之前&#xff0c;没有使用过类似的账户密码记录工具&#xff0c;甚至完全没有接触过&#xff0c;由于Edge浏览器自带保存密码并…

视频监控汇聚平台:系统日志介绍及在运维中的实际应用

目录 一、系统日志的重要性 &#xff08;一&#xff09;安全保障 &#xff08;二&#xff09;故障排查 &#xff08;三&#xff09;运营管理 &#xff08;四&#xff09;事件回溯与分析 二、产品说明 &#xff08;一&#xff09;产品介绍 &#xff08;二&#xff09;接…

把纸质文件扫描成word电子版的3种方法!

在数字化日益盛行的今天&#xff0c;纸质文件转化为电子版的需求愈发强烈。不论是出于环保的考虑&#xff0c;还是为了提高工作效率&#xff0c;将纸质文件扫描成Word电子版都是一项必备技能。那么&#xff0c;如何将纸质文件轻松转化为Word电子版呢&#xff1f;本文将为您揭秘…

【Windows10】查看WIFI密码

操作步骤 电脑上查看已连接Wi-Fi的密码的步骤如下: 连接需要查看密码的Wi-Fi。右键点击任务栏上的 [网络] 图标&#xff0c;选择 [开启"网络和Internet"设置]。在 高级网络设置 项目中&#xff0c;点选 [网络和共享中心]。开启网络和共享中心的窗口后&#xff0c;点…

One能聊天接入百度千帆大模型 —— 文心一言

One能聊天介绍&#xff1a;基于ChatGPT实现的微信小程序&#xff0c;适配H5和WEB端。包含前后端&#xff0c;支持打字效果输出流式输出&#xff0c;支持AI聊天次数限制&#xff0c;支持分享增加次数等功能One能聊天开源地址&#xff1a;https://github.com/oldinaction/ChatGPT…

【算法专题--链表】相交链表--高频面试题(图文详解,小白一看就会!!)

目录 一、前言 二、题目描述 三、解题方法 ⭐双指针 --- 数学思维 ⭐双指针 --- 按链表长度计算 &#x1f95d; 判断相交 &#x1f347; 求出交点 &#x1f34d;实现步骤 四、总结与提炼 五、共勉 一、前言 相交链表这道题&#xff0c;可以说是--链表专题--&#xf…

docker-compose部署FastDFS分布式文件系统

文章目录 一、技术选型二、fastDFS组成部分三、docker-compose文件四、客户端nginx配置五、存储器spring Boot集成参考文献 一、技术选型 还有一个更好的google FS&#xff08;但是他不开源&#xff0c;我也没找到社区版一类的可以不要钱使用的&#xff09;。 最后考虑到我们存…

通信设备的网卡

一、网卡的作用 将计算机或者路由器连接到传输介质上的接口&#xff0c;传输介质可以是有线也可以是无线的。 &#xff08;1&#xff09;计算机的网卡 现在的计算机大多有两个网卡&#xff0c;一个是有线网卡一个无线网卡&#xff0c;比如以我们的台式电脑为例 台式电脑千兆网…

推进现代化的财务计划,打造可持续的企业发展

现阶段一个重大问题是&#xff0c;大多数企业无法保持决策与规划水平的一致性&#xff0c;财务团队的现状难以支持复杂环境下的新型决策&#xff0c;从而造成了劳动力和资源的错误匹配。由于财务功能的过度扩展&#xff0c;企业难以持续发展。财务团队不得不为了企业的存亡不断…

【代码】数据类型之复合数据类型

Hello&#xff01;大家好&#xff0c;我是学霸小羊&#xff0c;今天讲讲数据类型之复合数据类型。 上一个博客讲了讲基本数据类型&#xff0c;今天讲讲复合数据类型&#xff0c;没学过基本数据类型的建议先去学一学哈。 【代码】数据类型之基本数据类型https://blog.csdn.net…

AI论文速读 | 2024[SIGIR]基于大语言模型的下一个兴趣点推荐

论文标题&#xff1a;Large Language Models for Next Point-of-Interest Recommendation 作者&#xff1a;Peibo Li ; Maarten de Rijke ; Hao Xue &#xff08;薛昊&#xff09;; Shuang Ao ; Yang Song ; Flora D. Salim 机构&#xff1a;新南威尔士大学(UNSW)&#xff0c…

MAC M1系统编译ffmpeg-gl-transition

MAC M1系统编译ffmpeg-gl-transition 1. 本人系统2. 编译&#x1f4b0;系统准备2.1. 下载【ffmpeg-gl-transition】到用户家目录下&#xff0c;并解压2.2 下载ffmpeg源码2.3. brew安装GLEW glfw32.4 复制vf_gltransition.c文件到ffmpeg2.5 修改ffmpeg源码文件2.6 设置库目录和…

这4款国产软件,因为太良心好用,甚至被误认为是外国人开发的

说起国产软件&#xff0c;大家总是容易给它们打上“流氓、要钱、广告满天飞”的标签&#xff0c;其实&#xff0c;有些小众的软件超级好用&#xff0c;功能强大又不耍流氓&#xff0c;真心不该被一棍子打死。 1、sunlight studio Sunlight Studio是一个开源、免费、无广告的硬…

【成品设计】基于物联网的停车管理系统设计与实现

《基于物联网的停车管理系统设计与实现》 整体功能&#xff1a; 本次课题中&#xff0c;主要设计的是一款基于物联网技术的校园停车的管理系统&#xff0c;该系统能更方便得让管理员对停车场进行管理&#xff0c;同时也能够满足和方便用户使用。针对此种现象&#xff0c;就需…

【面试干货】抽象类和接口的区别

【面试干货】抽象类和接口的区别 1、抽象类1.1、什么是抽象类&#xff1f;1.2、示例代码 2、接口2.1、什么是接口&#xff1f;2.2、示例代码 3、比较和总结3.1、使用场景3.2、关键区别3.3、代码示例比较 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&am…

大规模装箱问题:蜣螂优化算法DBO求解二维装箱问题(MATLAB代码)

一、问题描述 装载率:所有选择的箱子的总面积与夹板面积之比 假设一共有300个箱子&#xff0c;如何设计算法&#xff0c;使得选择部分箱子放入80*80的甲板上&#xff0c;让甲板的装载率越大&#xff0c;要求箱子间不得重叠。 二、蜣螂优化算法求解二维装箱问题 蜣螂优化算法…

八 、VS的调试技巧

--- 24.4.20 目录 1、什么是Bug&#xff1f; 2、什么是调试&#xff08;Debug&#xff09;&#xff1f; 3、Debug和Release 4、VS基础调试快捷键 4.1、环境准备 4.2、调试快捷键 5、监视和内存观察 5.1、监视窗口 5.2、内存窗口 6、调试举例1 7、调试举例2 8、调试…

AI宣传文案软件有哪些?5款AI软件推荐

AI宣传文案软件有哪些&#xff1f;AI宣传文案软件在现代营销和创意产业中扮演着越来越重要的角色&#xff0c;它们凭借先进的自然语言处理、机器学习和深度学习技术&#xff0c;不仅解放了创作者的双手&#xff0c;还大大提升了文案的生成效率和质量。这些软件能够精准捕捉用户…