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指针,无法直接访问类的非静态成员。

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vue42-vc与vm的原型对象

一、普通函数与其对象的原型对象 显示原型属性&#xff0c;只有函数才有&#xff01;&#xff01;&#xff01; 实例对象只有隐式原型属性。 普通函数与其对象&#xff0c;指向同一个原型对象&#xff01;&#xff01;&#xff01; 这么写不推荐&#xff0c;建议直接如下格式&a…

k8s学习--helm的详细解释及安装和常用命令

文章目录 Helm简介什么是Helm主要组件核心概念chart结构总结 应用环境一、helm部署二、helm基础使用 Helm简介 什么是Helm Helm 是 Kubernetes 的一个包管理工具&#xff0c;它允许用户定义、安装和升级复杂的 Kubernetes 应用程序。Helm 通过使用 “Charts” 的概念来简化应…

byzer 笔记总结

1.总览&#xff08;简单了解&#xff09; 1.1 数据挖掘的定义 基于大数据技术&#xff0c;针对有价值是业务场景&#xff0c;对数据中台沉淀的大量数据进行探索&#xff0c;分析。寻找数据与数据之间潜藏的关系&#xff0c;转化为自动化的算法模型&#xff0c;从而获取有价值的…

【SpringBoot + Vue 尚庭公寓实战】地区信息管理接口实现(九)

【SpringBoot Vue 尚庭公寓实战】地区信息管理接口实现&#xff08;九&#xff09; 文章目录 【SpringBoot Vue 尚庭公寓实战】地区信息管理接口实现&#xff08;九&#xff09;1、业务说明2、数据逻辑模型3、接口实现3.1、查询省份信息列表3.2、根据省份ID查询城市信息列表3…

Hexapod C-887使用手册 -- 4,5,6

4 - 拆包 小心拆包C-887 根据合同和发货注意比较发货范围的内容&#xff1a; 检查危险符号的内容。如果任何零件损坏或缺失&#xff0c;立即联系客服部门。 保存所有包装材料&#xff0c;以防产品需要返厂。 5 - 安装 本章中 安装一般注意 安装PC软件 确保通风 接地C-…

增材制造引领模具创新之路

随着科技的快速发展和制造业的不断转型升级&#xff0c;增材制造&#xff08;也称为3D打印&#xff09;技术正逐渐展现出其在模具智造中的巨大潜力和优势。增材制造以其独特的加工方式和设计理念&#xff0c;为模具行业带来了革命性的变革&#xff0c;为传统制造业注入了新的活…

第二十三节:带你梳理Vue2:Vue插槽的认识和基本使用

前言: 通过上一节的学习,我们知道了如何将数据从父组件中传递到子组件中, 除了除了将数据作为props传入到组件中,Vue还允许传入HTML, Vue 实现了一套内容分发的 API&#xff0c;这套 API 的设计灵感源自 Web Components 规范草案&#xff0c;将 <slot> 元素作为承载分发…

2千泰中英泰语词汇对照含MP3真人读音

越来越多的人到泰国旅游&#xff0c;今天这一份数据就是服务于此&#xff0c;包含了2000条泰语的日常生活词汇&#xff0c;并且每条记录都含有真人发音MP3对应。 有分100个章节&#xff0c;每个章节共有20条记录&#xff0c;非常适合一章节一章节的学习&#xff0c;具体章节有&…

R可视化:R语言基础图形合集

R语言基础图形合集 欢迎大家关注全网生信学习者系列&#xff1a; WX公zhong号&#xff1a;生信学习者Xiao hong书&#xff1a;生信学习者知hu&#xff1a;生信学习者CDSN&#xff1a;生信学习者2 基础图形可视化 数据分析的图形可视化是了解数据分布、波动和相关性等属性必…

班子考核评价的重要性与实施方法

在组织管理领域&#xff0c;班子考核评价是一项至关重要的工作&#xff0c;它不仅关系到组织的发展方向和速度&#xff0c;更直接影响到组织的凝聚力和战斗力。一个科学、公正、有效的班子考核评价体系&#xff0c;能够准确反映班子的工作成效&#xff0c;激励班子成员积极作为…

刺客信条找不到emp.dll怎么解决?emp.dll缺失的解决方法解析

emp.dll 是一个动态链接库文件&#xff0c;它在Windows操作系统中扮演着重要的角色。这个文件包含了多个函数和接口&#xff0c;允许其他程序调用这些功能来实现对多媒体设备的控制和管理。根据搜索结果&#xff0c;emp.dll 主要负责以下功能&#xff1a; 多媒体设备管理&…

XP系统安装Node.js v8.6.0并搭建Vue2开发环境(项目兼容到Vista的IE9浏览器)

下载并安装Node.js v8.6.0 通常我们开发Vue2项目&#xff0c;是通过vue create命令建立Vue2工程&#xff0c;用npm run serve命令启动Vue2网站的。 vue命令是用JavaScript写的&#xff0c;不是用C语言写的&#xff0c;必须要Node.js环境才能运行&#xff0c;由Node.js自带的np…

驱动开发(三):内核层控制硬件层

驱动开发系列文章&#xff1a; 驱动开发&#xff08;一&#xff09;&#xff1a;驱动代码的基本框架 驱动开发&#xff08;二&#xff09;&#xff1a;创建字符设备驱动 驱动开发&#xff08;三&#xff09;&#xff1a;内核层控制硬件层​​​​​​​ ←本文 目录…

【Postman的接口测试工具介绍】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

【SpringBoot】深入分析 SpringApplication 源码:彻底理解 SpringBoot 启动流程

在黄昏的余晖里&#xff0c;梦境渐浓&#xff0c;如烟如雾。心随星辰&#xff0c;徜徉远方&#xff0c;岁月静好&#xff0c;愿如此刻般绵长。 文章目录 前言一、SpringBoot 应用二、SpringApplication2.1 SpringApplication 中的属性2.2 SpringApplication 的构造器2.3 Sprin…

第一个SpringBoot程序

第一个SpringBoot程序 目录介绍 当我们创建了一个SpringBoot项目之后&#xff0c;会出现如下的目录结构 SpringBoot项⽬有两个主要的⽬录&#xff1a; src/main/java: Java源代码 src/main/resources:为静态资源或配置⽂件&#xff1a; /static&#xff1a;静态资源⽂件夹,⽐…

【python】python指南(三):使用正则表达式re提取文本中的http链接

一、引言 对于算法工程师来说&#xff0c;语言从来都不是关键&#xff0c;关键是快速学习以及解决问题的能力。大学的时候参加ACM/ICPC一直使用的是C语言&#xff0c;实习的时候做一个算法策略后台用的是php&#xff0c;毕业后做策略算法开发&#xff0c;因为要用spark&#x…

【MySQL】InnoDB引擎(MVCC)

https://www.bilibili.com/video/BV1Kr4y1i7ru/?p141 https://blog.csdn.net/weixin_52574640/article/details/129961415 MVCC&#xff0c;全称Multo-Version Concurrentcy Control,多版本并发控制。指维护一个数据的多个版本&#xff0c;使得读写操作没有冲突&#xff0c;快…

Linux电话本的编写-shell脚本编写

该电话本可以实现以下功能 1.添加用户 2.查询用户 3.删除用户 4.展示用户 5.退出 代码展示&#xff1a; #!/bin/bash PHONEBOOKphonebook.txt function add_contact() { echo "Adding new contact..." read -p "Enter name: " name …

Dubbo3 服务原生支持 http 访问,兼具高性能与易用性

作者&#xff1a;刘军 作为一款 rpc 框架&#xff0c;Dubbo 的优势是后端服务的高性能的通信、面向接口的易用性&#xff0c;而它带来的弊端则是 rpc 接口的测试与前端流量接入成本较高&#xff0c;我们需要专门的工具或协议转换才能实现后端服务调用。这个现状在 Dubbo3 中得…