当前位置: 首页 > news >正文

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进行编译。
运行结果:编译出错,出错信息如下:
在这里插入图片描述
可以看到是链接期间的错误,多重定义。

我们分析下为什么会出现这个错误。

  1. 首先明白程序编译的过程。 将源代码编译为可执行文件有四个阶段:预处理——>编译——>汇编——>链接。预处理大概做的是:define定义的替换,include文件的拷贝(比如说:main.cpp中include了test.h,编译器会将test.h的内容拷贝到main.cpp中)等等;编译大概做的是:将C++代码编译为汇编代码;汇编大概做的是:将汇编代码编译为机器码;链接大概做的是:将所有cpp文件编译的机器码合并为一个可执行文件。大概来说就是这样,详细的大家可以自行百度。
  2. 这里我们先关注预处理阶段。它会将test.h的内容拷贝到main.cpp中,那么此时相当于已经在main.cpp中有了int a = 5这个定义,有了a这个变量(如果没有a这个变量,编译器会直接报错找不到a)。然后对于test.cpp,它也include了test.h,所以test.cpp中也有了int a = 5这个定义,有了a这个变量。
  3. 接下来两个cpp分别进行编译,汇编。
  4. 到了链接阶段(上面的错误就发生在链接阶段),它将main.cpp的机器码和test.cpp的机器码合并到一起,但maina的定义,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大概有以下几类:

  1. static 全局变量
  2. static 局部变量
  3. static 成员变量
  4. static 函数
  5. 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声明的全局变量只在该文件内可见,对其它文件是不可见的。

隐藏这个特点会带来什么好处呢?

  1. 利用这一特点可以在不同的文件中定义同名变量,而不必担心命名冲突。

明白这一点后,我们在来看一些问题:

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_atest.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还有更多的问题,接下来遇到再更新。

参考链接

  1. https://blog.csdn.net/u011718663/article/details/118218407
  2. https://www.cnblogs.com/honernan/p/14478366.html

相关文章:

C++之static关键字

文章目录 前提正文多重定义extern关键字使用staticstatic 全局变量(在.cpp文件中定义)static变量存放在哪里static变量可不可以放在.h文件中 static 函数static局部变量static 成员变量static 成员函数 总结参考链接 前提 好吧&#xff0c;八股&#xff0c;我又回来了。这次想…...

嵌入式学习——硬件(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&#xff08;JS&#xff09;逆向工程是一种技术&#xff0c;用于分析和理解JS代码的功能和行为&#xff0c;尤其是在源代码不可用或被混淆的情况下。逆向JS代码可以帮助开发者理解第三方库的工作机制&#xff0c;或者在调试和优化过程中定位问题。 要点一&#xff1…...

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

Go语言--复合类型之指针与数组

分类 指针 指针是一个代表着某个内存地址的值。这个内存地址往往是在内存中存储的另一个变量的值的起始位置。Go 语言对指针的支持介于 Java 语言和 C/C语言之间,它既没有想 Java 语言那样取消了代码对指针的直接操作的能力,也避免了 C/C语言中由于对指针的滥用而造成的安全和…...

docker 环境下failed to start lsb故障解决

背景&#xff1a;从深信服超融合迁移虚拟机到VMWARE集群后&#xff0c;迁移后的虚拟机 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对象 当事件发生时&#xff0c;浏览器会创建一个event对象&#xff0c;并将其作为参数传递给事件处理函数。这个对象包含了事件的详细信息&#xff0c;比如&#xff1a; type&#xff1a;事件的类型&#xff08;如 click&#xff09;target&#xff1a…...

【C++】开源:地图投影和坐标转换proj库配置使用

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

WordPress主题大前端DUX v8.7源码下载

全新&#xff1a;用户注册流程&#xff0c;验证邮箱&#xff0c;设置密码 新增&#xff1a;列表显示小视频和横幅视频 新增&#xff1a;文章内容中的外链全部增加 nofollow 新增&#xff1a;客服功能中的链接添加 nofollow 优化&#xff1a;产品分类的价格显示...

【论文阅读】自动驾驶光流任务 DeFlow: Decoder of Scene Flow Network in Autonomous Driving

再一次轮到讲自己的paper&#xff01;耶&#xff0c;宣传一下自己的工作&#xff0c;顺便完成中文博客的解读 方便大家讨论。 Title Picture Reference and pictures paper: https://arxiv.org/abs/2401.16122 code: https://github.com/KTH-RPL/DeFlow b站视频: https://www.b…...

调和均值

文章目录 调和均值的定义和公式调和均值的几何解释调和均值的应用调和均值与算术平均和几何平均的比较示例 调和均值的定义和公式 调和均值是一种特殊的平均数&#xff0c;适用于处理涉及比率或速度的数据。对于一组正数 x 1 , x 2 , … , x n x_1, x_2, \ldots, x_n x1​,x2…...

DP学习——模板模式

学而时习之&#xff0c;温故而知新。 字面理解 模板&#xff1f;啥叫模板&#xff1f;模板就是固定死了&#xff0c;就是一套流程/步骤上层写死了。固定死了的流程或者步骤就是模板。然后我们要重写或者改写的是写死的这套流程中的节点。俗称“套模板”。 使用场合&#xff…...

AOP在业务中的简单使用

背景 业务组有一些给开发用的后门接口&#xff0c;为了做到调用溯源&#xff0c;业务组最近需要记录所有接口的访问记录&#xff0c;暂时只需要记录接口的响应结果&#xff0c;如果调用失败&#xff0c;则记录异常信息。由于后门接口较多以及只是业务组内部轻度使用&#xff0…...

C# 用户权限界面的测试内容

测试用户权限界面的主要目标是确保权限管理功能按照设计工作&#xff0c;同时保证用户界面响应正确&#xff0c;不会出现意外的行为或安全漏洞。以下是C#中用户权限界面测试的一些关键内容&#xff1a; 1. 功能性测试 权限分配与撤销&#xff1a;测试权限的分配和撤销功能&am…...

PyCharm

一、介绍 PyCharm 是 JetBrains 公司开发的一款功能强大的 Python 集成开发环境&#xff08;IDE&#xff09;。它专为 Python 开发设计&#xff0c;提供了一系列强大的工具和功能&#xff0c;帮助开发者更高效地编写、调试和维护 Python 代码。以下是对 PyCharm 的详细介绍&am…...

【嵌入式开发 Linux 常用命令系列 1.5 -- grep 过滤特定类型文件】

请阅读【嵌入式开发学习必备专栏 】 文章目录 grep 过滤特定类型文件 grep 过滤特定类型文件 在Linux中使用grep搜索字符串时&#xff0c;如果你想排除特定类型的文件&#xff0c;比如 .map 和 .py 文件&#xff0c;可以使用grep的--exclude选项。这个选项允许你定义一个或多个…...

学习笔记——动态路由——OSPF(邻接/邻居)

十、OSPF的邻接/邻居 1、OSPF路由器之间的关系 (1)基本介绍 在OSPF网络中&#xff0c;为了交换链路状态信息和路由信息&#xff0c;邻居设备之间首先要建立邻接关系&#xff0c;邻居(Neighbors)关系和邻接(Adjacencies)关系是两个不同的概念。 OSPF路由器的两种关系&#x…...

k8s 答疑

1 如何修复容器中的 top 指令以及 /proc 文件系统中的信息呢? 这段自问自答的内容解释了如何通过使用 lxcfs 来修复 Docker 容器中 top 指令和 /proc 文件系统中的信息。让我们分步骤来详细说明: 背景信息 在容器化环境中,通常会遇到一个问题,即容器中的一些命令(如 to…...

[终端安全]-2 移动终端之硬件安全(SE)

本文主要介绍针对安全芯片的攻击和防护方案。 1 芯片攻击 1&#xff09;故障注入攻击 故障注入攻击&#xff08;Fault Injection Attack, FIA&#xff09;是一种通过人为引入故障&#xff0c;诱发系统或芯片在异常情况下产生错误结果&#xff0c;从而泄露机密信息或破坏系统…...

浅谈 React Hooks

React Hooks 是 React 16.8 引入的一组 API&#xff0c;用于在函数组件中使用 state 和其他 React 特性&#xff08;例如生命周期方法、context 等&#xff09;。Hooks 通过简洁的函数接口&#xff0c;解决了状态与 UI 的高度解耦&#xff0c;通过函数式编程范式实现更灵活 Rea…...

基于FPGA的PID算法学习———实现PID比例控制算法

基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容&#xff1a;参考网站&#xff1a; PID算法控制 PID即&#xff1a;Proportional&#xff08;比例&#xff09;、Integral&#xff08;积分&…...

简易版抽奖活动的设计技术方案

1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

C++.OpenGL (10/64)基础光照(Basic Lighting)

基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...

土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等

&#x1f50d; 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术&#xff0c;可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势&#xff0c;还能有效评价重大生态工程…...

大模型多显卡多服务器并行计算方法与实践指南

一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...

【Java_EE】Spring MVC

目录 Spring Web MVC ​编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 ​编辑参数重命名 RequestParam ​编辑​编辑传递集合 RequestParam 传递JSON数据 ​编辑RequestBody ​…...

select、poll、epoll 与 Reactor 模式

在高并发网络编程领域&#xff0c;高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表&#xff0c;以及基于它们实现的 Reactor 模式&#xff0c;为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。​ 一、I…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?

uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件&#xff0c;用于在原生应用中加载 HTML 页面&#xff1a; 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...