深入理解C语言中static关键字的使用及其在程序模块化中的重要性
大家好,又见面了,我是你们的朋友全栈君。

概述
static 关键字在 c 语言中较为常用。若使用恰当,能够极大地提升程序的模块化特性。这种特性有利于程序的扩展和维护。
对于 c 语言的初学者来说,因为 static 的使用较为灵活,所以并不容易被掌握。本文对 static 在 c 语言中的应用进行了总结,以供参考使用。如果存在错误或遗漏之处,恳请不吝指出。
最后一节加入了一些内容,这些内容是 c++面向对象中 static 的使用特性,将其当作拓展阅读。
普通局部变量是很熟悉的变量。在任何一个函数内部定义的变量(如果没有加 static 修饰符)就属于这个范畴。编译器通常不会对普通局部变量进行初始化,这意味着它在初始时的值是不确定的,只有对其进行显式赋值才会确定其值。
普通局部变量存储于进程栈空间,使用完毕会立即释放。
静态局部变量由 static 修饰符来定义。当在声明静态局部变量时未赋予初值,编译器会将其初始化为 0。同时,静态局部变量被存储在进程的全局数据区。即便函数返回了,它的值依然会保持不变。
变量的内存空间在全局数据区被分配;编译器会自动对其进行初始化;它的作用域是局部作用域;当定义它的那个函数结束时,它的作用域也就结束了。
小程序体会一下静态局部变量的威力:
代码语言:javascript
代码运行次数:0
复制
Cloud Studio 代码运行
#include
void fn(void)
{
int n = 10;
printf("n=%d\n", n);
n++;
printf("n++=%d\n", n);
}
void fn_static(void)
{
static int n = 10;
printf("static n=%d\n", n);
n++;
printf("n++=%d\n", n);
}
int main(void)
{
fn();
printf 函数输出了一个由双引号括起来的字符串,这个字符串包含了一个破折号组成的横线,后面跟着一个换行符。
fn_static();
printf("--------------------\n");
fn();
printf("--------------------\n");
fn_static();
return 0;
}
运行结果如下:
代码语言:javascript
代码运行次数:0
复制
Cloud Studio 代码运行
-> % ./a.out
n=10
n++=11
--------------------
static n=10
n++=11
--------------------
n=10
n++=11
--------------------
static n=11
n++=12
可见,静态局部变量的效果与全局变量相近。然而,它位于函数体内部,这对程序的模块化极为有利。
2. 全局变量
全局变量定义于函数体之外,它会在全局数据区获得存储空间,并且编译器会自动为其进行初始化。
普通全局变量整个工程都能看见,其他文件在使用 extern 进行外部声明后就可以直接使用。这意味着其他文件不能再去定义一个与该普通全局变量名字相同的变量了,因为一旦这样做,编译器就会认为它们是同一个变量。
静态全局变量仅在当前文件内可见。其他文件无法对其进行访问。而其他文件是可以定义与静态全局变量同名的变量的。并且这两者之间不会相互产生影响。
定义不需要与其他文件共享的全局变量时,加上 static 关键字,这样能有效降低程序模块之间的耦合,还能避免不同文件同名变量产生冲突,并且不会出现误使用的情况。
函数
函数的使用方式和全局变量相近。在函数的返回类型前面加上 static ,这样就形成了静态函数。它具有以下特性:
非静态函数能够在另一个文件里直接进行引用,并且不需要使用 extern 进行声明。 非静态函数可以直接在其他文件中被引用,甚至无需通过 extern 来声明。 非静态函数在另一个文件中可以直接被引用,而且不必使用 extern 去声明。
下面的例子说明,使用 static 声明的函数存在这样的情况,即不能被另一个文件所引用,有两个文件可以作为此说明的例子。
代码语言:javascript
代码运行次数:0
复制
Cloud Studio 代码运行
/* file1.c */
#include
static void fun(void)
{
printf("hello from fun.\n");
}
int main(void)
{
fun();
fun1();
return 0;
}
/* file2.c */
#include
static void fun1(void)
{
printf 打印了 "hello" 并且打印了 "from static fun1." 。
}
在使用 gcc 对 file1.c 和 file2.c 进行编译时,错误报告呈现出如下内容:
代码语言:javascript
代码运行次数:0
复制
Cloud Studio 代码运行
/tmp/cc2VMzGR.o:在函数‘main’中:
在 static_fun.c 文件的 (.text+0x20) 位置,存在对‘fun1’的未定义引用。
collect2 出现错误,ld 返回了 1 个退出状态。
修改文件时,不使用 static 修饰符,这样该函数就可以在另一个文件中被引用。
代码语言:javascript
代码运行次数:0
复制
Cloud Studio 代码运行
/* file1.c */
#include
void fun(void)
{
printf("hello from fun.\n");
}
/* file2.c */
int main(void)
{
fun();
return 0;
}
使用 gcc 来编译 file1.c 和 file2.c,编译过程顺利,运行后得到的结果如下:
代码语言:javascript
代码运行次数:0
复制
Cloud Studio 代码运行
-> % ./a.out
hello from fun.
面向对象静态数据成员
在类内数据成员的声明之前加上 static 关键字,那么这个数据成员就成为了类内的静态数据成员。它具有以下特点:
同全局变量相比,使用静态数据成员有两个优势:
静态成员函数
静态成员函数具有以下特性:它与静态数据成员相似,属于整个类,而非某一个对象。
总结
static 是一个很有作用的关键字。若使用恰当,能让程序更加出色。当然,有些公司的编码规范明确要求,对于仅在本文件的函数,都需使用 static 关键字来进行声明,这是一种良好的编码风格。
在实际编码时无论如何都要注意自己的编码习惯,并且要尽量体现出语言本身的优雅以及编码者的编码素质。