C++之static关键字

news2024/11/29 12:43:10

文章目录

  • 前提
  • 正文
    • 多重定义
    • 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 加2
    std::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 加2
    std::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 function
        func1(); // error: cannot call member function 'void Base::func1()' without object
        std::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

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

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

相关文章

【wordpress教程】wordpress博客网站添加非法关键词拦截

有的网站经常被恶意搜索&#xff0c;站长们不胜其烦。那我们如何屏蔽恶意搜索关键词呢&#xff1f;下面就随小编一起来解决这个问题吧。 后台设置预览图&#xff1a; 设置教程&#xff1a; 1、把以下代码添加至当前主题的 functions.php 文件中&#xff1a; add_action(admi…

I2C接口+高度集成的电源管理芯片(PMIC)-iML1942

电源管理芯片 - iML1942是一个高度集成的电源管理IC为TFT液晶面板。它具有完整的I2C接口来编程各种参数。该设备包括一个针对AVDD的电流模式升压调节器&#xff0c;一个针对VBK1的同步升压转换器。VGL可选的反相转换器或负电荷泵调节器&#xff0c;VSS1负线性调节器&#xff0c…

基于python的数据分解-趋势-季节性-波动变化

系列文章目录 前言 时间序列数据的分解&#xff0c;一般分为趋势项&#xff0c;季节变化项和随机波动项。可以基于加法或者乘法模型。季节变化呈现出周期变化&#xff0c;因此也叫季节效应(周期&#xff09;。 一、数据分解步骤 &#xff08;1&#xff09;估计时间序列的长期…

相关向量机RVM算法介绍继承sklearn-SVM-API实现回归预测算例

一、相关向量机RVM与支持向量机SVM对比 1、相关向量机&#xff08;RVM&#xff09; ①定义与原理 相关向量机&#xff08;Relevance Vector Machine, RVM&#xff09;是一种基于概率模型的机器学习算法&#xff0c;主要用于分类和回归分析。基于稀疏贝叶斯学习框架&#xff…

杰理科技AD142A语音芯片,语音玩具方案—云信通讯

语音玩具产品市场的需求量比较大&#xff0c;从前简单的发光玩具&#xff0c;到各种动作的电子玩具&#xff0c;再到如今的语音录音灯光动作玩具&#xff0c;可见玩具行业也是在不断地演变。 杰理语音芯片AD142A4的优势主要是支持录音、录变音、语音播放&#xff0c;广泛应用于…

kubernetes集群部署:node节点部署和CRI-O运行时安装(三)

关于CRI-O Kubernetes最初使用Docker作为默认的容器运行时。然而&#xff0c;随着Kubernetes的发展和OCI标准的确立&#xff0c;社区开始寻找更专门化的解决方案&#xff0c;以减少复杂性和提高性能。CRI-O的主要目标是提供一个轻量级的容器运行时&#xff0c;它可以直接运行O…

给我的 IM 系统加上监控两件套:【Prometheus + Grafana】

监控是一个系统必不可少的组成部分&#xff0c;实时&#xff0c;准确的监控&#xff0c;将会大大有助于我们排查问题。而当今微服务系统的话有一个监控组合很火那就是 Prometheus Grafana&#xff0c;嘿你别说 这俩兄弟配合的相当完美&#xff0c;Prometheus负责数据采集&…

手写实现一个ORM框架

手写实现一个ORM框架 什么是ORM框架、ORM框架的作用效果演示框架设计代码细节SqlBuilderSqlExecutorStatementHandlerParameterHandlerResultSetHandler逆序生成实体类 大家好&#xff0c;本人最近写了一个ORM框架&#xff0c;想在这里分享给大家&#xff0c;让大家来学习学习。…

10、matlab中字符、数字、矩阵、字符串和元胞合并为字符串并将字符串以不同格式写入读出excel

1、前言 在 MATLAB 中&#xff0c;可以使用不同的数据类型&#xff08;字符、数字、矩阵、字符串和元胞&#xff09;合并为字符串&#xff0c;然后将字符串以不同格式写入 Excel 文件。 以下是一个示例代码&#xff0c;展示如何将不同数据类型合并为字符串&#xff0c;并以不…

【设计模式】装饰器模式(定义 | 特点 | Demo入门讲解)

文章目录 定义装饰模式的结构 快速入门 | Demo顶层抽象DataSource数据源接⼝具体实现类引入BASE**64**编码装饰器客户端Client 定义 所谓装饰器模式其实就是在原有的功能上做一个增强&#xff01;&#xff01; 换句话说&#xff1a;以前你妈妈揍你的时候用的巴掌&#xff0c;但…

Android 简单快速实现 下弧形刻度尺(滑动事件)

效果图&#xff1a; 直接上代码&#xff1a; package com.my.view;import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Pai…

SQL注入【1】——通用漏洞/SQL注入/mysql跨库/ACCESS偏移

一、知识点: 1、脚本代码与数据库前置知识 2、Access数据库注入-简易&偏移 3、MYSQL数据库注入-简易:权限跨库 二、前置知识: &#xff08;一&#xff09;SQL注入漏洞产生原理分析 SQL注入产生条件&#xff1a;根本条件&#xff1a;可控变量、特定函数。 脚本代码在实现…

一气之下,关闭成都400多人的游戏公司

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 最近&#xff0c;多益网络宣布关闭成都公司&#xff0c;在未来三年内&#xff0c;关闭成都所有的相关公司。原因竟然是输掉了劳动仲裁&#xff0c;赔偿员工38万多&#xff0c;然后一气之下要退出成都&#xff0c;…

windows下编译ffmpeg 最详细教程

1 Ffmpeg下载地址&#xff1a;FFmpeg 使用命令下载 git clone https://git.ffmpeg.org/ffmpeg.git ffmpeg 下载完成后会发现如下目录&#xff1a; 2 msys2下载地址&#xff1a;MSYS2 解压好后&#xff0c;选择一个非空路径安装&#xff0c;安装好后路径如下&#xff1a; 为…

基于STM32的通用红外遥控器设计: 解码、学习与发射(代码示例)

摘要&#xff1a; 本文将带你使用STM32打造一款功能强大的万能红外遥控器&#xff0c;它可以学习和复制多种红外信号&#xff0c;并通过OLED屏幕和按键实现便捷操作。我们将深入探讨红外通信原理、STM32编程、OLED显示和EEPROM数据存储等关键技术&#xff0c;并提供完整的代码示…

【Qt】day3 自定义控件、框架、定时器、QPainter、QFile

文章目录 自定义控件封装自定义框架定时器第一种方式第二种方式 &#xff08;推荐&#xff09; 事件分发器QPainter基本操作高级设置抗锯齿移动坐标原点 画家画资源图片&#xff0c;并实现手动移动 作业QPaintDevice绘图设备QPixmapQimageQPicture QFile文件读写操作QFileInfo文…

FPGA_GTX:简要版

1. GTX介绍 Xilinx FPGA的GT意思是Gigabyte Transceiver。通常称呼为Serdes、高速收发器。GT在xilinx不同系列有着不同的产品&#xff0c;从7系列到UltraScale系列分别有GTP、GTX、GTZ、GTH、GTY和GTM。不同GT整体结构上类似&#xff0c;为了支持越来越高的line rate&#xff…

virtualbox窗口和win10窗口的切换

1、问题&#xff1a; 从windows切换到虚拟机可以用快捷键 ALTTAB&#xff0c;但是从虚拟机到windows使用 ALTTAB 无法成功切换 2、解决方法&#xff1a; 注意&#xff1a;发现设置为ctrlAlt会导致打开终端快捷键&#xff08;CtrlAltT&#xff09;失效&#xff0c;建议这里设置…

Lua语言入门

目录 Lua语言1 搭建Lua开发环境1.1 安装Lua解释器WindowsLinux 1.2 IntelliJ安装Lua插件在线安装本地安装 2 Lua语法2.1 数据类型2.2 变量全局变量局部变量命名规范局部变量作用域 2.3 注释单行注释多行注释 2.4 赋值2.5 操作符数学操作符比较操作符逻辑操作符连接操作符取长度…

HACCP体系认证:守护食品安全的黄金标准

在食品生产过程中&#xff0c;食品安全始终是重中之重。为了确保食品的安全性和质量&#xff0c;越来越多的企业开始采用HACCP&#xff08;危害分析关键控制点&#xff09;体系认证。这个体系不仅能帮助企业预防食品安全问题&#xff0c;还能显著提升产品质量和市场竞争力。 HA…