首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Static的魔法:让局部变量“记住“上一次的值

Static的魔法:让局部变量“记住“上一次的值

作者头像
码途随笔
发布2026-01-12 19:52:49
发布2026-01-12 19:52:49
1280
举报

前言

在C语言编程中,变量的生命周期和作用域是我们必须掌握的重要概念。你是否曾经遇到过这样的需求:希望函数中的局部变量能够“记住”上一次调用时的值,而不是每次都被重新初始化?这正是static关键字的魔力所在!本文将带你深入探索static的奥秘,理解它如何改变变量的存储特性,让局部变量跨越函数调用的界限,保持其值的持久性。无论你是C语言初学者还是有一定经验的开发者,掌握static的用法都将为你的编程技能增添重要的一笔

一、数组

1.1 数组的概念

数组的诞生是为了存放一组数据的(避免啰嗦复杂)

1.2 一维数组

部分初始化(可以调试验证) 初始化多个元素,所以用大括号

全部初始化

一维数组的类型

初始化的时候可以省略[]里面的大小,会自动根据后面的元素来确定个数,如果不初始化,必须要指定大小 验证方式:

一维数组存放数据就是对数据进行操作的,使用下标(类似于编号)来操作

1.3 求数组的元素个数

会自动计算元素个数,就算输入太多,超过元素个数后面的也会自动舍弃

1.4 一维数组的内存

将环境改成X86的环境,内存单位为一个字节,%p打印为地址的16进制

有图可以得到:数组是连续存放的 随着下标增长,地址由低(小)变高(大)

1.5 二维数组

二维数组初始跟一维数组一样,可以部分初始化或者全部初始化,初始化可以省略行不能省略列,编译器根据初始化的内容自动初始化行

二维数组存放

由图可以看出数组的存储方式是连续存放的

二维数组中每一行可以理解为一维数组,每一行一维数组名为arr[],从这里可以得到,为啥初始化时可以省略行,不能省略列,因为行取决于内容,不知道列有几个元素,每一行不知道从哪里开始放(比如:第二行放到第一行的后面)

1.6 变长数组

注意:当初始化为0的时候,里面有一个元素,后面输入不能超出1

在这里插入图片描述
在这里插入图片描述

注意:变长数组不能初始化 变长数组的意思是数组的大小可以用变量来指定,一旦数组创建好后,大小就无法改变 VS上不支持变长数组的语法 变长数组一定是在运行时确定的,一旦确定大小,不能改变

二、函数

2.1 库函数

查找库函数相关头文件:https://en.cppreference.com/w/c/header.html

2.2 学会自学库函数

网站:https://legacy.cplusplus.com/reference/clibrary/ 举例:sqrt 首先查找sqrt

在这里插入图片描述
在这里插入图片描述

由于有英文,哪里看不懂,用鼠标选中,右击,选择翻译,就可以看懂 函数原型和介绍:

功能:

在这里插入图片描述
在这里插入图片描述

头文件在旁边:

参数:

在这里插入图片描述
在这里插入图片描述

返回值:

在这里插入图片描述
在这里插入图片描述

有举例:

在这里插入图片描述
在这里插入图片描述

其他函数(相关的)链接:

在这里插入图片描述
在这里插入图片描述

实践:

代码语言:javascript
复制
#include<stdio.h>
#include<math.h>
int main()
{
	double a = 100;
	printf("%lf", sqrt(a));
	return 0;
}

读一般文档流程:

2.3 函数的特点

高内聚低耦合

补充:

2.3 扩展:sizeof求字符串数组下标和条件编译

代码语言:javascript
复制
#include<stdio.h>
int main()
{
	char arr[] = "hellow";//[h e l l o w \0]
	int right = sizeof(arr) / sizeof(arr[0]) - 2;
	printf("%d", right);
	return 0;
}

另一种注释

代码语言:javascript
复制
# if 0
#endif

2.4 自定义函数

格式:

==注意点:==当形参为void,就明确实参不能传参数,当形参里面的参数不写,传参数没关系(最好不要传参数) 形参与实参

2.5 return语句

当return后面跟表达式,先把表达式算出,再返回值 当函数返回类型为void,return后面可以不接任何表达式(场景:不返回任何值,但是想提前结束) 比如下面场景(函数的功能为打印1~n的值,所以不需要返回,参数用void):

代码语言:javascript
复制
#include<stdio.h>
void test(int n)
{
	if (n < 0)
	{
		return;
	}
	int i = 0;
	int sum = 0;
	for (int i = 0; i <= n; i++)
	{
		sum += i;
	}
	printf("%d", sum);
}
int main()
{
	int n;
	while (scanf("%d", &n) != EOF)
	{
		test(n);
	}
	return 0;
}

当return返回值与函数返回类型不一致,系统自动将返回的值隐式转化为函数的返回类型

想要提前返回,可以使用return语句 当函数里存在像if那样的分支语句,必须每一条路径都有return语句返回

错误原因:

2.6 数组做函数参数

代码语言:javascript
复制
#include<stdio.h>
void set_arr(int arr1[],int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		arr1[i] = -1;
	}
}
void print_arr(int arr1[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr1[i]);
	}
	printf("\n");
}
int main()
{
	int arr[10] = { 0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	print_arr(arr, sz);
	set_arr(arr,sz);
	print_arr(arr, sz);
	return 0;
}

数组在形参和实参中为同一个数组 验证:

注意:形参中传的是数组名,不要传数组的元素arr[10]

2.7 嵌套调用和链式访问

  • 嵌套调用
代码语言:javascript
复制
#include<stdio.h>
#include<stdbool.h>
_Bool is_leap(int y)
{
	if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0))
	{
		return true;
	}
	else
	{
		return false;
	}
}
int get_day(int y, int m)
{
	int days[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
	if (is_leap(y) && m == 2)
	{
		days[2] += 1;
	}
	int day = days[m];
	return day;
}
int main()
{
	int year = 0;
	int month = 0;
	scanf("%d%d", &year, &month);
	int day = get_day(year, month);
	printf("%d", day);
	return 0;
}

函数嵌套调用别人,调用自己叫递归 函数不能定义在函数里面,函数之间互相平等

  • 链式访问:
在这里插入图片描述
在这里插入图片描述

举例:printf("%d",strlen("abc")); 题目:下面输出结果

代码语言:javascript
复制
#include<stdio.h>
int main()
{
	printf("%d", printf("%d", printf("%d", 43)));
	return 0;
}

注意点:弄清printf()的返回值 在这个网站上搜索printf: https://legacy.cplusplus.com/reference/clibrary/

结果:

在这里插入图片描述
在这里插入图片描述

变式:

2.8 高内聚低耦合

高内聚: 自己做功能独立、干净 低耦合: 函数不要有太多的功能,既要有要 反例(不方便其他函数调用):函数不够独立

2.9 多文件编程的妙用

  • 单个文件编程 当函数的调用放在函数定义的前面时,没有声明会报错,函数声明就是告诉编译器函数名是什么,参数类型是什么,返回类型是什么。 ==函数必须先声明再使用,函数的定义也算一种特殊的一种声明
  • 多文件写法(以加法为例) 模块化 add.hadd.ctest.c,把add.c和add.h当成一整个模块(同一个模块,文件名字相同,后缀不同)
  • add.h
代码语言:javascript
复制
#pragma once
int Add(int x, int y);
  • add.c
代码语言:javascript
复制
int Add(int x, int y)
{
	return x + y;
}
  • test.c(用来调用)
代码语言:javascript
复制
#include<stdio.h>
#include"add.h"
int main()
{
	int a;
	int b;
	scanf("%d%d", &a, &b);
	int ret = Add(a, b);
	printf("%d", ret);
	return 0;
}

适当隐藏代码(以add为例) 程序员给公司add.h(只告诉你函数名和参数,所以具体实现不知道),给add.c编译后的产生的文件(不会暴露源代码) 下面是模拟过程实现: A程序员写的模块:

将配置类型改成静态库

将这两个文件给公司

模拟公司场景:(只有一个调用函数)

将程序员给的两个文件导进去

添加到项目中,引用头文件,运行,但是有报错

解决办法(导入静态库):

如果环境要求不行,改为X86或X64的

2.9 static和extern

补充: 作用域: 通常来说,一段程序代码中所用到的名字并不总是有效(可用)的,而限定这个名字的可用性的代码范围就是这个名字的作用域,作用域通常是在大括号里面

生命周期(与作用域协同的): 指的是变量的创建(申请内存)到变量的销毁(收回内存)之间的一个时间段(类比人的生命周期)

static和extern:

static修饰局部变量 出函数时,并没有销毁,保留了上一次的值,因为上一次创建了n,下一次就不创建了和赋值(变量的存储位置/存储类型发生变化,作用域没变,只能在作用域使用) 对比图

先介绍extern的作用,在引入static 当不同文件变量相互使用时 比如:

解决办法:extern专门用来声明外部符号的(其他文件的符号) 变量跨文件使用方法,满足全局变量的是整个工程(项目),前提要声明才能使用

static修饰全局变量

原因:

externstatic修饰函数 函数不像变量那样在不同文件必须声明,不声明也可以运行,但正常来说最好还是声明(没有警告)

同理,static修饰函数与static修饰全局变量一样有外部链接属性,被static修饰后,变成内部链接属性,只能在本文件内部使用,其他文件无法正常的链接使用了。本质不变

结语

通过本文的学习,我们揭开了static关键字的神秘面纱,了解了它如何让局部变量“记住”上一次的值,以及如何改变全局变量和函数的链接属性。static的巧妙运用不仅能让我们的代码更加灵活高效,还能提升程序的模块化和封装性。记住,static修饰局部变量改变了其生命周期但不改变作用域;修饰全局变量和函数则改变了它们的链接属性,使其仅限于文件内部使用。希望读者能够将这些知识融会贯通,在实际编程中灵活运用,写出更加优雅、高效的C语言代码。编程之路漫漫,让我们继续探索更多C语言的精妙之处

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-10-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 一、数组
    • 1.1 数组的概念
    • 1.2 一维数组
    • 1.3 求数组的元素个数
    • 1.4 一维数组的内存
    • 1.5 二维数组
    • 1.6 变长数组
  • 二、函数
    • 2.1 库函数
    • 2.2 学会自学库函数
    • 2.3 函数的特点
    • 2.3 扩展:sizeof求字符串数组下标和条件编译
    • 2.4 自定义函数
    • 2.5 return语句
    • 2.6 数组做函数参数
    • 2.7 嵌套调用和链式访问
    • 2.8 高内聚低耦合
    • 2.9 多文件编程的妙用
    • 2.9 static和extern
  • 结语
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档