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

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

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

相关文章

[图解]企业应用架构模式2024新译本讲解23-标识映射2

1 00:00:00,950 --> 00:00:02,890 好&#xff0c;我们往下走 2 00:00:04,140 --> 00:00:04,650 一样的 3 00:00:04,660 --> 00:00:07,170 这前面也见过了&#xff0c;定义一个对象数组 4 00:00:07,870 --> 00:00:12,820 数组的长度就是字段的数量&#xff0c;4个…

中值滤波法

中值滤波法 中值滤波法:连续采样N次(N取奇数),把N次采样值按大小排列,取中间值为本次有效值。 优点:能有效克服因偶然因素引起的波动干扰;对温度、液位的变化缓慢的被测参数有良好的滤波效果。 缺点:对流量、速度等快速变化的参数不宜。 #include <stdio.h> #i…

一.1.(3)半导体二极管基本电路的分析方法及常见应用电路

1.二极管基本电路的分析方法 先标正负极&#xff0c;再看是否理想二极管 将二极管视为断路&#xff0c;求两端电压 两端电压均大于导通电压&#xff0c;压差大的先导通&#xff08;由于电源不是完全的阶跃&#xff0c;而是有一个电压爬升的过程&#xff09; 2.常见应用电路 1.求…

【redis】redis知识点学习目录整理及简介

1、Redis概述 作者往期博文链接&#xff1a; 1、【redis】redis概述-CSDN博客 2、【redis】redis经典面试题20连问-CSDN博客 Redis定义&#xff1a;Redis是一个开源的、高性能的、基于内存运行的、非关系型的键值对NoSQL数据库。特点&#xff1a; 数据存储在内存中&#xf…

centos修改时间:系统时间、硬件时间

在CentOS上&#xff0c;修改时间可以通过以下步骤进行&#xff0c;涵盖系统时间和硬件时间&#xff08;RTC&#xff1a;Real-Time Clock&#xff09;。 系统时间是操作系统内核维护的时间硬件时间是系统主板上的时钟芯片维护的时间。 1. 修改系统时间 使用 date 命令来修改系…

c语言------------------分支结构

#语句 ## 空语句 c语言中最简单的语句就是空语句&#xff0c;其本身只包含一个分号。空语句本身不执行任何任务&#xff0c;但是有时也是有用的 ## 表达式语句 c语言中的语句本质上就是程序员的某些操作意图的体现。C语言中的单句是以分号结尾&#xff0c; 如&#xff1a…

日期选取限制日期范围antdesign vue

限制选取的日期范围 效果图 <a-date-pickerv-model"dateTime"format"YYYY-MM-DD":disabled-date"disabledDate"valueFormat"YYYY-MM-DD"placeholder"请选择日期"allowClear />methods:{//回放日期选取范围限制&…

网安小贴士(4)哈希函数

一、前言 哈希函数是密码学中的基础工具&#xff0c;哈希函数在密码学中扮演着至关重要的角色&#xff0c;广泛应用于确保数据的安全性和完整性。随着技术的发展&#xff0c;新的哈希算法和应用场景也在不断出现。 二、定义 哈希函数是一种数学函数&#xff0c;它接受一个输…

Intellj idea无法启动

个人电脑上安装的是2024.01版本的intellj idea作为开发工具&#xff0c;引入了javaagent作为工具包 但是在一次invaliad cache操作后&#xff0c;intellj idea就无法启动了&#xff0c;双击无响应。 重装了idea后也无效&#xff08;这个是有原因的&#xff0c;下面会讲&#…

C#——类及其方法和属性成员关键字权限

类及其方法和属性关键字权限 1.关键字static&#xff08;静态的&#xff09; 它可以修饰类、方法、属性、字段 静态类&#xff1a;不能实例化&#xff0c;直接调用&#xff0c;它的内部成员必须是静态的&#xff0c;不能包含实例构造函数&#xff0c;调用方式是&#xff08;…

springboot服务启动读取不到application.yml中的nacos.config信息

我的版本&#xff1a; 可以添加bootstrap.yml文件&#xff0c;在里面添加nacos.config的配置信息 也可以添加VM参数 -Dspring.cloud.nacos.discovery.server-addr -Dspring.cloud.nacos.config.server-addr -Dspring.cloud.nacos.config.namespace -Dspring.cloud.nacos.discov…

Apache tika 实现各种文档内容解析

Apache tika 实现各种文档内容解析 1、依赖 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation=&…

代码随想录——划分字母区间(Leetcode763)

题目链接 贪心 class Solution {public List<Integer> partitionLabels(String s) {int[] count new int[27];Arrays.fill(count,0);// 统计元素最后一次出现的位置for(int i 0; i < s.length(); i){count[s.charAt(i) - a] i;}List<Integer> res new Ar…

【基于R语言群体遗传学】-4-统计建模与算法(statistical tests and algorithm)

之前的三篇博客&#xff0c;我们对于哈代温伯格遗传比例有了一个全面的认识&#xff0c;没有看的朋友可以先看一下前面的博客&#xff1a; 群体遗传学_tRNA做科研的博客-CSDN博客 1.一些新名词 &#xff08;1&#xff09;Algorithm: A series of operations executed in a s…

QDataStream 详解

QDataStream是Qt框架中的一个重要类&#xff0c;它提供了方便的方式来读取和写入二进制数据流。以下是对QDataStream的详细解释&#xff1a; 一、基本概念 QDataStream&#xff1a;是Qt中用于序列化和反序列化数据的类。它可以将复杂的数据类型以二进制形式写入到文件、套接字…

【MySQL】锁(黑马课程)

【MySQL】锁 0. 锁的考察点1. 概述1. 锁的分类1.1 属性分类1.2 粒度分类 2. 全局锁2.1 全局锁操作2.2.1 备份问题 3. 表级锁3.1 附录 0. 锁的考察点 1. 概述 锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中&#xff0c;除传统的计算资源(CPU、RAM、I/O)的争…

MatLab三维图形绘制基础

三维图形绘制 三维曲线 plot3 螺旋图绘制 % %三维图像:螺旋图绘制 clear; clc; t [0:0.1:10*pi];% 向量 x sin(t) t.*cos(t);%t是向量&#xff0c;用点乘 y cos(t) - t.*sin(t); z t; plot3(x,y,z); grid on;plot3 绘制同型矩阵 %% % plot3绘制同型矩阵 t [0:0.1:10*…

OceanBase v4.2 特性解析:对Json与Xml的扩展支持

1. 背景 OceanBase的Oracle模式当前已实现对XMLType类型的支持&#xff0c;不仅包含了基本的构造、查询、更新以及格式转换功能&#xff0c;还支持使用Xpath查询从XML数据中提取特定值。在V 4.2.2 版本中&#xff0c;我们进一步扩展了Oracle模式下对XMLType的支持&#xff0c;…

Python统计实战:时间序列分析之二阶曲线预测和三阶曲线预测

为了解决特定问题而进行的学习是提高效率的最佳途径。这种方法能够使我们专注于最相关的知识和技能&#xff0c;从而更快地掌握解决问题所需的能力。 &#xff08;以下练习题来源于《统计学—基于Python》。请在Q群455547227下载原始数据。&#xff09; 练习题 下表是某只股票…

每周算法:无向图的双连通分量

题目链接 冗余路径, Redundant Paths G 题目描述 为了从 F F F 个草场中的一个走到另一个&#xff0c;奶牛们有时不得不路过一些她们讨厌的可怕的树。 奶牛们已经厌倦了被迫走某一条路&#xff0c;所以她们想建一些新路&#xff0c;使每一对草场之间都会至少有两条相互分离…