C++之static关键字
文章目录
- 前提
- 正文
- 多重定义
- extern关键字
- 使用static
- static 全局变量(在.cpp文件中定义)
- static变量存放在哪里
- static变量可不可以放在.h文件中
- static 函数
- static局部变量
- static 成员变量
- static 成员函数
- 总结
- 参考链接
前提
好吧,八股,我又回来了。这次想探究下static
关键字,因为用到过,同时对下面的有些问题,还不是很清楚:
- static变量都有什么
- static关键字的作用
正文
在开始研究static
之前,我想先引导几个问题,这几个问题对接下来理解static
有一定的作用。
多重定义
有test.h, test.cpp, main.cpp
三个文件如下:
/*=====test.h=====*/
#ifndef TEST_H_
#define TEST_H_int a = 5; /*=======这是重点======*/
void print();#endif
/*=====test.cpp=====*/
#include"test.h"
#include<iostream>void print()
{std::cout<<"hello, world\n";
}
/*=====main.cpp=====*/
#include <iostream>
#include "test.h"int main()
{print();std::cout<<"a: "<<a<<std::endl;
}
程序说明:在test.h
定义了一个int
的变量a
,我们想在main.cpp
中输出这个a
,使用gcc
进行编译。
运行结果:编译出错,出错信息如下:
可以看到是链接期间的错误,多重定义。
我们分析下为什么会出现这个错误。
- 首先明白程序编译的过程。 将源代码编译为可执行文件有四个阶段:预处理——>编译——>汇编——>链接。预处理大概做的是:
define
定义的替换,include
文件的拷贝(比如说:main.cpp
中include了test.h
,编译器会将test.h
的内容拷贝到main.cpp
中)等等;编译大概做的是:将C++代码编译为汇编代码;汇编大概做的是:将汇编代码编译为机器码;链接大概做的是:将所有cpp文件编译的机器码合并为一个可执行文件。大概来说就是这样,详细的大家可以自行百度。 - 这里我们先关注预处理阶段。它会将
test.h
的内容拷贝到main.cpp
中,那么此时相当于已经在main.cpp
中有了int a = 5
这个定义,有了a
这个变量(如果没有a
这个变量,编译器会直接报错找不到a
)。然后对于test.cpp
,它也include了test.h
,所以test.cpp
中也有了int a = 5
这个定义,有了a
这个变量。 - 接下来两个cpp分别进行编译,汇编。
- 到了链接阶段(上面的错误就发生在链接阶段),它将
main.cpp
的机器码和test.cpp
的机器码合并到一起,但main
中a
的定义,test
中有a
的定义,所以就会出现多重定义的这个错误。
举这个例子是想给大家说明程序编译的过程。接下来我们使用extern
关键字改写这个程序。
extern关键字
/*=====test.h=====*/
#ifndef TEST_H_
#define TEST_H_
void print();#endif
/*=====test.cpp=====*/
#include"test.h"
#include<iostream>int a = 5;
void print()
{a = a+1;std::cout<<"a: "<<a<<std::endl;
}
/*=====main.cpp=====*/
#include <iostream>
#include "test.h"
extern int a;
int main()
{a = a+1;std::cout<<"a: "<<a<<std::endl;print();
}
程序说明:在test.cpp
中定义一个全局变量a
,然后在main.cpp
中想使用这个变量a
,这里我们借助了extern
这个关键字,表明:这只是a
的声明,它在其它地方定义(在test.cpp
中定义)。
运行结果:如下所示。
按照上面的分析过程,自已分析下。
使用static
我们见到的static大概有以下几类:
- static 全局变量
- static 局部变量
- static 成员变量
- static 函数
- static 成员函数
static 全局变量(在.cpp文件中定义)
改写代码如下:
/*=====test.h=====*/
#ifndef TEST_H_
#define TEST_H_void print();
#endif
/*=====test.cpp=====*/
#include"test.h"
#include<iostream>
static int a = 5;
void print()
{a = a+1;std::cout<<"a: "<<a<<std::endl;
}
/*=====main.cpp=====*/
#include <iostream>
#include "test.h"
// extern int a; # 重点关注
int main()
{print();std::cout<<a<<std::endl;
}
程序说明:在test.cpp
中定义了一个static
变量a
,然后print()
函数使用了这个变量,最后在main.cpp
中调用print()
函数。
运行结果:
这里重点关注main.cpp
中注释的这行代码:extern int a
。从上面可知,extern
的作用是告诉编译器我的这个a
只是声明,你链接的时候在其它地方去找。
而一但我们使用static
来定义这个变量时,该变量就只在test.cpp
中可见(也就是对其它的cpp文件隐藏),这时如果我们在main.cpp
中使用extern
,编译器还是找不到变量a
的定义。运行结果如下:
所以这就是static
的第一个作用:隐藏。static
声明的全局变量只在该文件内可见,对其它文件是不可见的。
隐藏这个特点会带来什么好处呢?
- 利用这一特点可以在不同的文件中定义同名变量,而不必担心命名冲突。
明白这一点后,我们在来看一些小问题:
static变量存放在哪里
先来看看现代操作系统中一个进程的内存空间布局:
Text section: 存放二进制指令。
Data section: 存放非0初始化的静态数据和全局变量。
BSS (Block Started by Symbol): 存放0初始化的静态数据和全局变量,程序中没有初始化的静态数据会被初始为0并存放到这里。
Heap: 存放动态分配的数据。
Stack: 存放局部变量,函数参数,指针等。
来吧,让我们继续。C++的内存分布(有的人说C++没有标准的内存分布)和上面的基本一致,只不过显式指出了有一个**只读数据区(.rodata)**用来存放全局常量数据(const),如下:
回归正题,我们采用一些方法来确认static
全局变量存放在**.bss section**(未初始化)和**.data section**(初始化)。
先来确定初始化的static
变量存放在.data
中。
有以下代码:
static int variable_a=10; // 全局static变量,已初始化为10
int main()
{}
这里要借助objdump这个命令。objdump 是一个功能强大的命令行工具,用于显示二进制文件(如可执行文件、目标文件和共享库)的各种信息。它是GNU Binutils软件包的一部分,广泛用于调试、分析和逆向工程。通过 objdump,用户可以查看文件头、段表、符号表、反汇编代码等详细信息。
编译程序,然后使用objdump -t
显示符号表,如下:
可以看到初始化的static
变量存放在.data
中。
再来确定未初始化的static
变量存放在.bss
中。修改代码如下:
#include<stdio.h>
static int variable_a;
int main()
{printf("variable_a: %d\n", variable_a); // 验证未初始化的static变量会被编译器初始化为0,全局变量也一样
}
结果如下:
然后使用objdump
查看符号表:
可以看到未初始化的static
变量存放在.bss
中。
再来确定初始化为0的static
变量也存放在.bss
中。
修改代码如下:
#include<stdio.h>
static int variable_a=0;
int main()
{printf("variable_a: %d\n", variable_a); // 验证未初始化的static变量会被编译器初始化为0
}
如下:
可以看到初始化为0的static
变量存放在.bss
中。
static变量可不可以放在.h文件中
代码如下 :
/*=====test.h=====*/
#ifndef TEST_H_
#define TEST_H_static int variable_a = 5; // 重点关注:将static变量定义在.h中
void func();#endif
/*=====test.cpp=====*/
#include"test.h"
#include<iostream>void func()
{variable_a += 5; // // test.cpp 中的static int variable_a 加2std::cout<<"test.cpp: variable_a = " << variable_a <<std::endl;
}
/*=====main.cpp=====*/
#include<iostream>
#include"test.h"
int main()
{variable_a += 2; // main.cpp 中的static int variable_a 加2std::cout << "main.cpp: variable_a = " << variable_a << std::endl;func();
}
程序说明:将static
变量定义在test.h
中,然后在main.cpp
中包含test.h
。
运行结果:
结果说明了:main.cpp
中的variable_a
和test.h
中的variable_a
是两个独立的变量。因为它们都是static
变量,而static
变量有隐藏的特性,所以它们互相对对方隐藏。
static 函数
static
函数和static
全局变量一样,都有隐藏的作用,所以允许同名函数。代码如下:
/*=====test.h=====*/
#ifndef TEST_H_
#define TEST_H_static void print();
void use_print();#endif
/*=====test.cpp=====*/
#include"test.h"
#include<iostream>void print()
{std::cout<<"hello static, test.cpp"<<std::endl;
}void use_print()
{print();
}
/*=====main.cpp=====*/
#include<iostream>
#include"test.h"void print()
{std::cout<<"hello static, main.cpp"<<std::endl;
}int main()
{print();use_print();
}
程序说明:在test.cpp
中定义了一个static
函数print()
,同时在main.cpp
中定义了一个同名函数print()
。按照我们的分析,static
函数只在test.cpp
中可见,所以使用main.cpp
包含test.h
后,main.cpp
是看不见test.h
中的print()
函数的。可以编译运行,结果如下 :
static局部变量
static
修饰局部变量时,使得该变量不会因为函数运行结束而丢失,使其生命周期为整个源程序。把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域, 限制了它的使用范围。因此static 这个说明符在不同的地方所起的作用是不同的。
代码如下:
/*==========main.cpp==========*/
#include<iostream>int fun()
{static int variable_a = 10; // //在第一次进入这个函数的时候,variable_a被初始化为10。接着每次调用fun(),variable自加1;在static发明前,要达到同样的功能,则只能使用全局变量: variable_a++;return variable_a;
}int main()
{for(int i=0; i<10; i++){std::cout <<"variable_a: " << fun() << std::endl;}
}
结果:
static 成员变量
代码如下:
/*==========main.cpp===========*/
#include<iostream>class Base{
public:Base() = default;virtual ~Base() = default;
public:static int variable; /* 类内定义 */
};int Base::variable = 10; /* 类外初始化 */int main()
{std::cout<<"Base::variable = "<<Base::variable<<std::endl;
}
这段代码肯定是可以编译运行的,在这里我们要思考的问题是:为什么类的静态成员变量要类内定义,类外初始化?
有一个理论答案:**因为静态成员属于整个类,而不属于某个对象,如果在类内初始化,会导致每个对象都包含该静态成员,这是矛盾的。**如果你在类内初始化,编译会报错,信息如下:
这里埋个坑,可不可以从代码层面分析为什么不能这么做?
static 成员函数
**static
成员函数和static
成员变量属于类,不属于类的某个对象。**所以static
成员函数只能调用static
成员函数和static
成员变量。
代码如下:
/*=========main.cpp============*/
#include<iostream>class Base{
public:Base() = default;virtual ~Base() = default;
public:static int variable_a; /* 类内定义 */int a; /* 普通成员变量 */void func1(){std::cout<<"this func1 is a member function"<<std::endl;}static void func(){ a = 4; // error: invalid use of member 'Base::a' in static member functionfunc1(); // error: cannot call member function 'void Base::func1()' without objectstd::cout<<"variable_a: " << variable_a <<std::endl;}
};int Base::variable_a = 10; /* 类外初始化 */int main()
{Base base;base.func();Base::func();
}
程序说明:在static
成员函数中调用普通成员函数和普通成员变量。
结果:程序编译出错,如下:
类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致它仅能访问类的静态数据和静态成员函数。
总结
关于static
关键字暂时总结这么多,当然关于static
还有更多的问题,接下来遇到再更新。
参考链接
- https://blog.csdn.net/u011718663/article/details/118218407
- https://www.cnblogs.com/honernan/p/14478366.html
相关文章:

C++之static关键字
文章目录 前提正文多重定义extern关键字使用staticstatic 全局变量(在.cpp文件中定义)static变量存放在哪里static变量可不可以放在.h文件中 static 函数static局部变量static 成员变量static 成员函数 总结参考链接 前提 好吧,八股,我又回来了。这次想…...
嵌入式学习——硬件(Linux内核驱动编程platform、内核定时器、DS18B20)——day61
1. platform驱动框架 1.1 设备device #include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h>void led_release(struct device *dev) {printk("leds has been released\n"); }static struct resource led_resou…...
js逆向抠js要点解析与案例分享
JavaScript(JS)逆向工程是一种技术,用于分析和理解JS代码的功能和行为,尤其是在源代码不可用或被混淆的情况下。逆向JS代码可以帮助开发者理解第三方库的工作机制,或者在调试和优化过程中定位问题。 要点一࿱…...

mac安装docker
1、首先打开docker官网 https://docs.docker.com/engine/install/ 2、下载好后安装到app应用 3、安装好环境变量 #docker echo export PATH"/usr/local/Cellar/docker/20.10.11/bin:$PATH" >> .bash_profile...

PDF压缩工具选哪个?6款免费PDF压缩工具分享
PDF文件已经成为一种常见的文档格式。然而,PDF文件的体积有时可能非常庞大,尤其是在包含大量图像或复杂格式的情况下。选择一个高效的PDF压缩工具就显得尤为重要。小编今天给大家整理了2024年6款市面上反响不错的PDF压缩文件工具。轻松帮助你找到最适合自…...

Go语言--复合类型之指针与数组
分类 指针 指针是一个代表着某个内存地址的值。这个内存地址往往是在内存中存储的另一个变量的值的起始位置。Go 语言对指针的支持介于 Java 语言和 C/C语言之间,它既没有想 Java 语言那样取消了代码对指针的直接操作的能力,也避免了 C/C语言中由于对指针的滥用而造成的安全和…...
docker 环境下failed to start lsb故障解决
背景:从深信服超融合迁移虚拟机到VMWARE集群后,迁移后的虚拟机 centos 7 运行systemctl start network ,报错 Restarting network (via systemctl): Job for network.service failed. See systemctl status network.service and journalctl -xn for d…...

Vue3学习(二)
回顾 DOM原生事件event对象 当事件发生时,浏览器会创建一个event对象,并将其作为参数传递给事件处理函数。这个对象包含了事件的详细信息,比如: type:事件的类型(如 click)target:…...

【C++】开源:地图投影和坐标转换proj库配置使用
😏★,:.☆( ̄▽ ̄)/$:.★ 😏 这篇文章主要介绍地图投影和坐标转换proj库配置使用。 无专精则不能成,无涉猎则不能通。——梁启超 欢迎来到我的博客,一起学习,共同进步。 喜欢的朋友可以关注一下&a…...

WordPress主题大前端DUX v8.7源码下载
全新:用户注册流程,验证邮箱,设置密码 新增:列表显示小视频和横幅视频 新增:文章内容中的外链全部增加 nofollow 新增:客服功能中的链接添加 nofollow 优化:产品分类的价格显示...

【论文阅读】自动驾驶光流任务 DeFlow: Decoder of Scene Flow Network in Autonomous Driving
再一次轮到讲自己的paper!耶,宣传一下自己的工作,顺便完成中文博客的解读 方便大家讨论。 Title Picture Reference and pictures paper: https://arxiv.org/abs/2401.16122 code: https://github.com/KTH-RPL/DeFlow b站视频: https://www.b…...
调和均值
文章目录 调和均值的定义和公式调和均值的几何解释调和均值的应用调和均值与算术平均和几何平均的比较示例 调和均值的定义和公式 调和均值是一种特殊的平均数,适用于处理涉及比率或速度的数据。对于一组正数 x 1 , x 2 , … , x n x_1, x_2, \ldots, x_n x1,x2…...
DP学习——模板模式
学而时习之,温故而知新。 字面理解 模板?啥叫模板?模板就是固定死了,就是一套流程/步骤上层写死了。固定死了的流程或者步骤就是模板。然后我们要重写或者改写的是写死的这套流程中的节点。俗称“套模板”。 使用场合ÿ…...
AOP在业务中的简单使用
背景 业务组有一些给开发用的后门接口,为了做到调用溯源,业务组最近需要记录所有接口的访问记录,暂时只需要记录接口的响应结果,如果调用失败,则记录异常信息。由于后门接口较多以及只是业务组内部轻度使用࿰…...
C# 用户权限界面的测试内容
测试用户权限界面的主要目标是确保权限管理功能按照设计工作,同时保证用户界面响应正确,不会出现意外的行为或安全漏洞。以下是C#中用户权限界面测试的一些关键内容: 1. 功能性测试 权限分配与撤销:测试权限的分配和撤销功能&am…...

PyCharm
一、介绍 PyCharm 是 JetBrains 公司开发的一款功能强大的 Python 集成开发环境(IDE)。它专为 Python 开发设计,提供了一系列强大的工具和功能,帮助开发者更高效地编写、调试和维护 Python 代码。以下是对 PyCharm 的详细介绍&am…...
【嵌入式开发 Linux 常用命令系列 1.5 -- grep 过滤特定类型文件】
请阅读【嵌入式开发学习必备专栏 】 文章目录 grep 过滤特定类型文件 grep 过滤特定类型文件 在Linux中使用grep搜索字符串时,如果你想排除特定类型的文件,比如 .map 和 .py 文件,可以使用grep的--exclude选项。这个选项允许你定义一个或多个…...

学习笔记——动态路由——OSPF(邻接/邻居)
十、OSPF的邻接/邻居 1、OSPF路由器之间的关系 (1)基本介绍 在OSPF网络中,为了交换链路状态信息和路由信息,邻居设备之间首先要建立邻接关系,邻居(Neighbors)关系和邻接(Adjacencies)关系是两个不同的概念。 OSPF路由器的两种关系&#x…...
k8s 答疑
1 如何修复容器中的 top 指令以及 /proc 文件系统中的信息呢? 这段自问自答的内容解释了如何通过使用 lxcfs 来修复 Docker 容器中 top 指令和 /proc 文件系统中的信息。让我们分步骤来详细说明: 背景信息 在容器化环境中,通常会遇到一个问题,即容器中的一些命令(如 to…...
[终端安全]-2 移动终端之硬件安全(SE)
本文主要介绍针对安全芯片的攻击和防护方案。 1 芯片攻击 1)故障注入攻击 故障注入攻击(Fault Injection Attack, FIA)是一种通过人为引入故障,诱发系统或芯片在异常情况下产生错误结果,从而泄露机密信息或破坏系统…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...
【杂谈】-递归进化:人工智能的自我改进与监管挑战
递归进化:人工智能的自我改进与监管挑战 文章目录 递归进化:人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管?3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...

Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...

day36-多路IO复用
一、基本概念 (服务器多客户端模型) 定义:单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力 作用:应用程序通常需要处理来自多条事件流中的事件,比如我现在用的电脑,需要同时处理键盘鼠标…...

TSN交换机正在重构工业网络,PROFINET和EtherCAT会被取代吗?
在工业自动化持续演进的今天,通信网络的角色正变得愈发关键。 2025年6月6日,为期三天的华南国际工业博览会在深圳国际会展中心(宝安)圆满落幕。作为国内工业通信领域的技术型企业,光路科技(Fiberroad&…...

水泥厂自动化升级利器:Devicenet转Modbus rtu协议转换网关
在水泥厂的生产流程中,工业自动化网关起着至关重要的作用,尤其是JH-DVN-RTU疆鸿智能Devicenet转Modbus rtu协议转换网关,为水泥厂实现高效生产与精准控制提供了有力支持。 水泥厂设备众多,其中不少设备采用Devicenet协议。Devicen…...

第一篇:Liunx环境下搭建PaddlePaddle 3.0基础环境(Liunx Centos8.5安装Python3.10+pip3.10)
第一篇:Liunx环境下搭建PaddlePaddle 3.0基础环境(Liunx Centos8.5安装Python3.10pip3.10) 一:前言二:安装编译依赖二:安装Python3.10三:安装PIP3.10四:安装Paddlepaddle基础框架4.1…...

恶补电源:1.电桥
一、元器件的选择 搜索并选择电桥,再multisim中选择FWB,就有各种型号的电桥: 电桥是用来干嘛的呢? 它是一个由四个二极管搭成的“桥梁”形状的电路,用来把交流电(AC)变成直流电(DC)。…...

aardio 自动识别验证码输入
技术尝试 上周在发学习日志时有网友提议“在网页上识别验证码”,于是尝试整合图像识别与网页自动化技术,完成了这套模拟登录流程。核心思路是:截图验证码→OCR识别→自动填充表单→提交并验证结果。 代码在这里 import soImage; import we…...