深入C语言:探究static关键字的奥秘

news2024/10/7 18:28:11

文章目录

  • 一、链接属性
  • 二、static变量
    • 1、定义静态局部变量
    • 2、在函数内部使用静态变量
    • 3、函数中静态局部变量与递归
  • 三、static变量与全局变量的区别
    • 1、存储期与生命周期
    • 2、可见性与作用域
    • 3、使用场景
    • 4、静态与动态内存分配
  • 注意事项

当用于不同的上下文环境时, static关键字具有不同的意思。

当用于函数定义时,或用于代码块之外的变量声明时,static关键字用于修改标识符的链接属性(从 external 变为 internal ),但标识符的存储类型和作用域不受影响。用这种方式声明的函数或变量只能在声明它们的源文件中访问。

当用于代码块内部的局部变量的声明的时候,static用于修改变量的存储类型,从自动变量修改为静态变量,但变量的链接属性和作用域不受影响。用这种方式声明的变量在程序执行之前创建,并在程序的整个执行期间一直存在,而不是每次在代码块开始执行时创建,在代码块执行完毕之后销毁。

一、链接属性

链接属性一共有3种–external(外部)、internal(内部)和 none(无)。

  • 没有链接属性的标识符(none)总是被当作单独的个体,也就是说该标识符的多个声明被当作不同的独立实体。

  • 属于internal链接属性的标识符在同一个源文件内的所有声明中都指同一个实体,但位于不同源文件的多个声明则分属不同的实体。

  • 属于 external链接属性的标识符不论声明多少次,位于几个源文件都表示同一个实体。

关键字 externstatic用于在声明中修改标识符的链接属性。如果某个声明在正常情况下具有external链接属性,在它前面加上static关键字可以使它的属性变为internalstatic int a;那么变量a就将为这个源文件私有。其他源文件中,如果也链接到一个叫做变量 a 的变量,那么它所引用的是另一个不同的变量。类似的也可以把函数声明为 static

static int func(); 

这样可以防止被其他源文件调用。

extern 关键字的规则更为复杂。一般而言,它为一个标识符指定external链接属性,这样就可以访问在其他任何位置定义的这个实体。

具有 external属性的变量我们一般称之为全局变量,所有源文件中的所有函数均可以访问它。只要变量并非声明于代码块或函数定义内部,它在缺省的情况下的链接属性即为external。如果一个变量声明于代码块内部,在它前面添加extern关键字讲使它所引用的是全局变量而非局部变量。

具有 external链接属性的实体总是具有静态存储类型。全局变量在程序开始执行前创建,并在程序整个执行过程中始终存在。从属于函数的局部变量在函数开始执行时创建,在函数执行完毕后销毁,但用于执行函数的机器指令在程序的生命期内一直存在。

局部变量由函数内部使用,不能被其他函数通过名字引用。它在缺省情况下的存储类型为自动。这是基于两个原因:其一,当这些变量需要时才为它们分配存储,这样可以减少内存的总需求量;其二,在堆栈上为它们分配存储可以有效地实现递归。如果你觉得让变量的值在函数的多次调用中始终保持原先的值非常重要的话,那么可以修改它的存储类型,把它从自动变量改为静态变量。


二、static变量

在C语言中,当在函数内部定义static变量时,该变量具有静态存储期,这意味着该变量的生命周期将贯穿整个程序的执行过程,而不仅仅是局限于函数调用的局部范围。此外,静态局部变量只会被初始化一次,即在程序开始执行时,后续函数调用中不会再次初始化。由于这种特性,静态局部变量常用于在函数调用之间保持状态,如计数或累积数据。

1、定义静态局部变量

在函数内部,使用static关键字定义变量。

int main() {
    static int myStaticVar = 0; // 静态局部变量,只初始化一次
    // ... 函数的其他代码 ...
}

2、在函数内部使用静态变量

像使用普通局部变量一样使用静态局部变量,通过变量名进行访问和修改。可以在多次函数调用中保持状态,由于静态局部变量在函数调用之间保持其值,因此它们可用于跨函数调用维护状态。

void myFunction() {
    static int count = 0;
    count++; // 每次调用函数时,count增加1
    printf("Function has been called %d times.\n", count);
}

在这里插入图片描述

在上面的例子中,myFunction函数有一个静态局部变量count,它只会在程序开始时初始化一次。每次调用这个函数时,count都会递增,并且由于它是静态的,所以它的值会在函数调用之间保持。

需要注意的是:

  • 静态局部变量不会自动初始化为它们的默认值(比如int类型的0),除非显式地进行初始化。如果未初始化,它们将包含未定义的值。
  • 静态局部变量不会占用函数每次调用时的栈空间,因为它们的存储期是整个程序的执行期间,通常存放在程序的数据段中。
  • 在多线程环境中,静态局部变量可能不是线程安全的,因为所有线程共享同一份静态变量的存储。如果需要跨线程保持状态,应使用线程安全的机制,如互斥锁(mutex)或原子操作。

所有不加static的全局变量和函数具有全局可见性,可以在其他文件中使用;加了static之后只能在该文件所在的编译模块中使用。

在这里插入图片描述

一个函数被static修饰,使得这个函数只能在本源文件内使用,不能在其他源文件内使用。

在这里插入图片描述

在C语言中,static关键字在函数调用中的主要用法是用于声明函数内部的局部变量,使其具有静态存储期。这意味着这些局部变量不会在函数调用结束时被销毁,而是会保留其值,直到程序结束。这在需要跨函数调用保留状态时特别有用。(注意:main函数所在文件需声明 add函数。)

3、函数中静态局部变量与递归

在递归函数中,static局部变量也很有用,因为它们可以在递归调用之间保持其值。例如,下面是一个计算阶乘的递归函数:

unsigned long long factorial(int n) {
    static unsigned long long result = 1; // 静态局部变量用于累积结果
    if (n > 0) {
        result *= n;
        return factorial(n - 1); // 递归调用
    } else {
        return result; // 返回累积的结果
    }
}

在这个例子中,result是一个static局部变量,用于在递归调用之间累积阶乘的结果。如果没有static关键字,每次递归调用都会创建一个新的局部变量result,导致结果不正确。


三、static变量与全局变量的区别

1、存储期与生命周期

全局变量:全局变量在程序开始执行时分配内存,并在整个程序的执行期间都保持其存在。无论函数是否被调用,全局变量都占据内存空间,并在程序结束时释放其内存。

static变量:无论是全局还是局部,static变量都具有静态存储期。这意味着它们在程序开始执行时分配内存,并且在程序的整个执行期间都存在。对于static局部变量来说,这是一个关键区别,因为普通的局部变量只在函数被调用时存在,并在函数返回时释放其内存。

2、可见性与作用域

全局变量:全局变量的作用域是整个程序,这意味着它们可以在任何函数内部被访问和修改(除非被同名的局部变量覆盖)。同时,由于它们具有外部链接属性,它们可以在其他文件中通过extern声明被引用。

static全局变量:尽管它们在存储期上与全局变量相同,但static关键字改变了它们的可见性。static全局变量仅在其定义的文件中可见,即使其他文件包含了相应的头文件,也无法直接访问它们。这是因为static全局变量具有内部链接属性,使得它们的链接仅限于定义它们的文件。

static局部变量:这些变量在函数内部定义,其作用域仅限于该函数。但是,由于它们的静态存储期,它们的值在函数调用之间保持不变。这意味着每次函数被调用时,它都会看到上次调用时留下的static局部变量的值。

3、使用场景

全局变量:全局变量通常用于需要在多个函数或文件之间共享的数据。然而,过度使用全局变量可能导致代码难以理解和维护,因为它们可以被程序的任何部分修改。

static变量static全局变量通常用于限制变量的可见性,以避免命名冲突和提高代码模块的独立性。static局部变量则常用于需要在函数调用之间保持状态的场景,如计数器或累加器。

总结来说,static变量和全局变量在存储期、可见性、作用域、初始化以及使用场景方面存在显著的区别。选择使用哪种类型的变量取决于具体的应用需求和编程风格。在编写代码时,应该谨慎考虑变量的存储期和可见性,以确保程序的正确性和可维护性。

4、静态与动态内存分配

static并不直接涉及动态内存分配(如mallocfree),但理解静态和动态内存分配的不同是很重要的。静态变量在编译时分配内存,而动态内存分配在运行时进行。


注意事项

  • static局部变量只在定义它们的函数内部可见,它们不是全局变量。
  • static局部变量只会被初始化一次,即使函数被多次调用。
  • static变量在程序的整个执行期间都存在,这意味着它们会占用内存,直到程序结束。因此,过度使用static局部变量可能会导致内存浪费。

总结来说,static在函数调用中的主要用法是定义具有静态存储期的局部变量,以便在函数调用之间保留其值。这在需要跨函数调用保留状态或累积数据的场景中特别有用。

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

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

相关文章

005 高并发内存池_CentralCache设计

​🌈个人主页:Fan_558 🔥 系列专栏:高并发内存池 🌹关注我💪🏻带你学更多知识 文章目录 前言本文重点一、构建CentralCache结构二、运用慢开始反馈调节算法三、完成向CentralCache中心缓存申请四…

Netty经典32连问

文章目录 1、Netty是什么,它的主要特点是什么?2、Netty 应用场景了解么?3、Netty 核心组件有哪些?分别有什么作用?4、Netty的线程模型是怎样的?如何优化性能?5、EventloopGroup了解么?和 Event…

第十三届蓝桥杯大赛软件赛省赛CC++大学B组

第十三届蓝桥杯大赛软件赛省赛CC 大学 B 组 文章目录 第十三届蓝桥杯大赛软件赛省赛CC 大学 B 组1、九进制转十进制2、顺子日期3、刷题统计4、修建灌木5、x进制减法6、统计子矩阵7、积木画8、扫雷9、李白打酒加强版10、砍竹子 1、九进制转十进制 计算器计算即可。2999292。 2、…

RD55UP06-V 三菱iQ-R系列C语言功能模块

RD55UP06-V 三菱iQ-R系列C语言功能模块 RD55UP06-V用户手册,RD55UP06-V功能,RD55UP06-V系统配置 RD55UP06-V参数规格:10BASE-T/100BASE-TX/1000BASE-T 1通道;字节存储次序格式小端模式; 可使用SD存储卡插槽;工作RAM 1…

路由、插槽

路由 前端路由:Hash地址(url中#后面的部分)与组件之间的对应关系 页面效果:在浏览器中访问不同的Hash地址时,会显示不同的组件 SPA项目(单页面应用程序,就是Vue项目,最后所有模板都展示在一个html上) vue路由(vue-r…

VUE3——生命周期

Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名: beforeDestroy改名为 beforeUnmountdestroyed改名为 unmounted Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下: beforeCreate&g…

3D Gaussian Splatting Linux端部署指南(含Linux可视化)

3D Gaussian Splatting Linux端部署指南 目录 项目地址 部署记录 11. Linux端在线远程可视化训练进程 准备自己的数据 SIBR_remoteGaussian在线远程可视化 补充:sibr_3Dgaussian离线可视化训练好的模型 朋友浩哥说环境是最难配的,配好环境&#x…

Tinymce富文本编辑器二次开发电子病历时解决的bug

前言 本文是在Tinymce富文本编辑器添加自定义toolbar,二级菜单,自定义表单,签名的基础之上进行一些bug记录,功能添加,以及模版的应用和打印 项目描述 建立电子病历模版—录入(电子病历模版和电子病历打印…

运筹学基础(三):求解整数规划的切平面法(cutting plane method)

文章目录 算法思想一个例子参考文档 算法思想 先将整数规划问题松弛为线性规划问题,然后割掉线性规划问题可行域的一部分(只包含非整数解),使得线性规划问题的最优解在原整数规划问题的可行域某顶点上取得。 因此,割平…

Spring之BeanFactoryPostProcessor详解

目录 功能与作用 使用案例 spring提供的常见BeanFactoryPostProcessor 1.EventListenerMethodProcessor 2.BeanDefinitionRegistryPostProcessor 功能与作用 使用案例 spring提供的唯一BeanDefinitionRegistryPostProcessor 总结 功能与作用 参考BeanFactoryPostProce…

如何插入LinK3D、CSF、BALM来直接插入各个SLAM框架中

0. 简介 LinK3D、CSF、BALM这几个都是非常方便去插入到激光SLAM框架的。这里我们会分别从多个角度来介绍如何将每个框架插入到SLAM框架中 1. LinK3D:三维LiDAR点云的线性关键点表示 LinK3D的核心思想和基于我们的LinK3D的两个LiDAR扫描的匹配结果。绿色线是有效匹配。当前关…

C++ 中的 vector 的模拟实现【代码纯享】

文章目录 C 中的 vector 模拟实现1. vector 的基本概念2. vector 的基本操作3. vector 的模拟实现4.代码纯享5. 总结 C 中的 vector 模拟实现 在 C 中,vector 是一个非常重要的容器,它提供了动态数组的功能。在本篇博客中,我们将尝试模拟实现…

搭建电商网站外贸网站用API接口可以实现哪些功能(天猫API接口|京东API接口)

在电商领域,API接口可以实现多种功能,起到连接内外部系统及优化电商业务流程等多种作用,从而来提高电商企业的运营效率。 具体来看,API接口接入可以用来: 商品管理: API接口能够用来获取商品详情等&#…

OR- M406A固态继电器SSR光耦,对标替代TLP170A/ASSR-1218等

低工作电流 低导通电阻 高隔离电压 400V , 600V 输出耐受电压 工业温度范围:-40 to 85℃ 特征 高输入输出隔离电压 ( Viso 3,750Vrms ) 采用 400V 和 600V 负载电压系列 常开信号极点信号投射继电器 低工作电流 低…

Redis安装-Docker

安装redis的docker容器 1、创建redis挂载目录 mkdir -p /liuchaoxu/redis/{data,conf}2、复制配置文件 在 /liuchaoxu/redis/conf 目录中创建文件 redis.conf,文件从 redis-6.2.7.tar.gz 中解压获取 修改默认配置(从上至下依次): #bind 127.0.0.1 …

小明的背包-dp_python

用户登录 动态规划的思想是自底向上,先求局部最优解然后求全局最优解。 dp[i][j]代表的是当前状态物品的数量以及背包的容量。 N, V map(int,input().split()) dp [[0 for _ in range(V1)] for _ in range(N1)]for i in range(1,N1):v, w map(int,input().split(…

CSS样式-字体类型,文本对齐,外观修饰,文本缩进,文本行间距,外部引用css样式

字体类型和字体属性调整 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Css字体类型大小</title&…

浅述安防视频监控平台EasyCVR视频汇聚管理系统运维管理能力

智慧安防监控EasyCVR视频管理平台能在复杂的网络环境中&#xff0c;将前端设备统一集中接入与汇聚管理。国标GB28181协议视频监控/视频汇聚EasyCVR平台可以提供实时远程视频监控、视频录像、录像回放与存储、告警、语音对讲、云台控制、平台级联、磁盘阵列存储、视频集中存储、…

如何保证Redis的缓存和数据库中的数据的一致性?

Redis的缓存如何和数据库中的数据保持一致性&#xff1f; 我们都知道&#xff0c;Redis是一个基于内存的键值存储系统&#xff0c;数据完全存放在内存中&#xff0c;这使得它的读写速度远超传统的硬盘存储数据库。对于高访问频率、低修改率的数据&#xff0c;通过将它们缓存在…

4.6 offset指令,jmp short指令,far,dword ptr各种跳转指令

4.6 offset指令&#xff0c;jmp short指令&#xff0c;far&#xff0c;dword ptr各种跳转指令 可以修改IP&#xff0c;或同时修改CS和IP的指令统称为转移指令。概括的讲&#xff0c;转移指令就是可以控制CPU执行内存中某处代码的指令 1. 转移指令 1.1 8086CPU的转移行为有以…