Part 1: static 关键字
static 关键字在 C 语言中主要有三种用法,它的核心作用是改变变量的生命周期和作用域。

(图片来源网络,侵删)
修饰局部变量(函数内部)
这是 static 最常见的用法之一。
作用:
- 生命周期延长:普通的局部变量(自动变量)存储在栈上,函数调用结束时会被销毁,而用
static修饰的局部变量存储在静态存储区(通常是数据段),它的生命周期和整个程序一样长,即使函数执行结束,它的值也不会被销毁,而是会保留上一次的值。 - 作用域不变:它的作用域仍然仅限于定义它的函数内部,外部无法访问。
特点:
- 初始化:
static局部变量如果没有被显式初始化,编译器会自动将其初始化为0(对于指针是NULL)。 - 只初始化一次:由于它只初始化一次,在程序运行期间,这个初始化代码只会执行一次。
示例:

(图片来源网络,侵删)
#include <stdio.h>
void counter() {
// 普通局部变量,每次调用都会重新初始化
int normal_var = 0;
normal_var++;
printf("Normal variable: %d\n", normal_var);
// static局部变量,只初始化一次,值会保留
static int static_var = 0;
static_var++;
printf("Static variable: %d\n", static_var);
}
int main() {
printf("--- Call 1 ---\n");
counter(); // Normal: 1, Static: 1
printf("--- Call 2 ---\n");
counter(); // Normal: 1 (重新开始), Static: 2 (保留上次的值)
printf("--- Call 3 ---\n");
counter(); // Normal: 1, Static: 3
return 0;
}
输出:
--- Call 1 ---
Normal variable: 1
Static variable: 1
--- Call 2 ---
Normal variable: 1
Static variable: 2
--- Call 3 ---
Normal variable: 1
Static variable: 3
应用场景:
- 需要记录函数调用次数的场景。
- 在递归函数中,需要保存上一次的计算结果。
修饰全局变量(函数外部)
作用:
- 作用域限制:普通的全局变量(外部变量)具有全局作用域,可以被项目中的任何一个源文件(通过
extern声明)访问,而用static修饰的全局变量,其作用域被限制在当前源文件(.c 文件)内,其他文件即使通过extern声明也无法访问它。 - 生命周期不变:它的生命周期仍然是整个程序运行期间。
特点:

(图片来源网络,侵删)
- 实现“文件级封装”:这是
static在多文件编程中最重要的作用,它可以帮助我们避免全局变量名在不同文件中的冲突,实现类似“私有”成员变量的效果,增强了代码的模块化和安全性。
示例:
假设我们有三个文件:main.c, utils.c, utils.h
utils.h
#ifndef UTILS_H #define UTILS_H void print_config(); // 我们不在这里声明 global_var,因为它是 utils.c 的私有变量 #endif
utils.c
#include <stdio.h>
#include "utils.h"
// 这是一个全局变量,但被 static 修饰,只能在本文件中使用
static int global_var = 100;
void print_config() {
printf("The global config value is: %d\n", global_var);
}
main.c
#include <stdio.h>
#include "utils.h"
// 如果试图在这里访问 global_var,编译器会报错
// int main() { printf("%d", global_var); } // Error: 'global_var' undeclared
int main() {
print_config(); // 正确调用,因为它在 utils.c 中定义
return 0;
}
在这个例子中,global_var 对于 main.c 是不可见的,从而避免了潜在的命名冲突。
修饰函数
作用:
- 作用域限制:和修饰全局变量类似,
static修饰函数会将该函数的作用域限制在当前源文件(.c 文件)内,这个函数被称为“内部函数”或“静态函数”。 - 生命周期不变:函数的生命周期仍然是整个程序运行期间。
特点:
- 避免函数名冲突:在大型项目中,可能会有不同开发者编写的同名函数,使用
static可以确保函数只在当前文件内可见,防止与其他文件中的函数发生冲突。
示例:
继续上面的例子,我们把 print_config 函数改为 static。
utils.c
#include <stdio.h>
#include "utils.h"
// 这是一个静态函数,只能在 utils.c 中调用
static void print_config() {
printf("This is a static function.\n");
}
// 提供一个公共接口给其他文件调用
void public_interface() {
print_config();
}
main.c
#include <stdio.h>
#include "utils.h"
// int main() { print_config(); } // Error: 'print_config' undeclared
int main() {
public_interface(); // 正确调用
return 0;
}
这里,main.c 无法直接调用 print_config,但可以通过 utils.c 提供的公共接口 public_interface 间接使用其功能,这是一种良好的封装实践。
Part 2: const 关键字
const 是 "constant" 的缩写,它的核心作用是声明一个“只读”的变量。
修饰局部变量
作用:
const修饰的变量是一个“只读变量”,意味着它的值在初始化后不能被修改。- 它仍然是一个变量,可能有自己的存储空间(在栈上或静态存储区,取决于其定义位置)。
const主要是一种编译期约束,编译器会检查并阻止对它的直接赋值操作。
示例:
#include <stdio.h>
int main() {
const int MAX_AGE = 100;
// MAX_AGE = 101; // 编译错误!不能给 const 变量赋值
printf("MAX_AGE is: %d\n", MAX_AGE);
return 0;
}
重要概念:const 与指针
const 和指针结合使用时,情况稍微复杂,const 的位置决定了它修饰的是指针本身还是指针指向的数据。
-
const int *ptr或int const *ptr- 含义:
ptr是一个指向const int类型的指针。 - 限制:不能通过
ptr去修改它所指向的值。 - 可以改变
ptr本身,让它指向另一个地址。
- 含义:
-
*`int const ptr`**
- 含义:
ptr是一个const指针,它指向一个int。 - 限制:不能修改
ptr本身,即它不能指向其他地址。 - 可以通过
ptr去修改它所指向的值。
- 含义:
-
*`const int const ptr`**
- 含义:
ptr是一个const指针,它指向一个const int。 - 限制:既不能修改
ptr本身,也不能通过ptr修改它所指向的值。
- 含义:
示例:
int a = 10, b = 20; // 1. 指向常量的指针 const int *ptr1 = &a; // *ptr1 = 15; // 错误!不能通过 ptr1 修改 a ptr1 = &b; // 正确!ptr1 可以指向 b // 2. 常量指针 int * const ptr2 = &a; *ptr2 = 15; // 正确!可以通过 ptr2 修改 a // ptr2 = &b; // 错误!ptr2 不能指向 b // 3. 指向常量的常量指针 const int * const ptr3 = &a; // *ptr3 = 15; // 错误!不能通过 ptr3 修改 a // ptr3 = &b; // 错误!ptr3 不能指向 b
修饰全局变量
作用:
- 和修饰局部变量一样,它声明了一个“只读”的全局变量。
- 由于是全局变量,它存储在静态存储区(通常是只读数据段
.rodata)。 - 它的值在程序运行期间是固定的,通常用于定义程序中不会改变的配置信息、魔法数字等。
示例:
// utils.c
#include <stdio.h>
// 定义一个全局只读变量
const float PI = 3.14159f;
void calculate_circle_area(float radius) {
// PI = 4.0f; // 编译错误!
float area = PI * radius * radius;
printf("Area: %.2f\n", area);
}
修饰函数参数
作用:
- 这是
const一个非常重要的应用,用于保护函数的参数不被意外修改。 - 当一个指针(无论是普通指针还是结构体指针)作为参数传递给函数时,如果用
const修饰,可以确保函数内部不会修改指针指向的数据。
优点:
- 安全性:防止函数意外修改传入的数据。
- 可读性:向函数的调用者明确表明,这个函数不会修改你传入的数据。
- 优化:编译器可能会对
const数据进行更好的优化。
示例:
#include <stdio.h>
#include <string.h>
// 好的实践:使用 const 保护输入参数
void print_string(const char *str) {
// str[0] = 'H'; // 编译错误!不能修改 str 指向的内容
printf("The string is: %s\n", str);
}
// 定义一个结构体
typedef struct {
int x;
int y;
} Point;
// 好的实践:使用 const 保护结构体指针
void print_point(const Point *p) {
// p->x = 100; // 编译错误!不能修改 p 指向的结构体内容
printf("Point is: (%d, %d)\n", p->x, p->y);
}
int main() {
const char *message = "Hello, World!";
print_string(message);
Point my_point = {10, 20};
print_point(&my_point);
return 0;
}
Part 3: static 与 const 的结合使用
static 和 const 可以结合使用,创造出兼具两者特性的变量。
最常见的用法: static const int MAX_SIZE = 1024;
const:这个变量的值是只读的,不能被修改。static:这个变量的作用域被限制在当前文件内,是文件私有的。
作用:
创建一个文件私有的、只读的常量,这比使用 #define 宏更安全,因为 const 变量有类型信息,编译器可以进行类型检查。
示例:
// utils.c
#include <stdio.h>
// 定义一个文件私有的、只读的常量
static const int BUFFER_SIZE = 256;
void process_data(const char *data) {
char buffer[BUFFER_SIZE]; // 使用这个常量
// ... 处理逻辑 ...
printf("Processing data with buffer size: %d\n", BUFFER_SIZE);
}
// main.c 无法看到 BUFFER_SIZE
总结与对比
| 特性 | static |
const |
|---|---|---|
| 核心作用 | 改变生命周期和作用域 | 声明“只读”变量 |
| 修饰局部变量 | 生命周期延长(全局生命周期) 作用域不变(函数内) 只初始化一次 |
值不可修改(只读) 生命周期和作用域不变 |
| 修饰全局变量 | 作用域限制(当前文件内) 生命周期不变(全局生命周期) |
值不可修改(只读) 作用域不变(全局) |
| 修饰函数 | 作用域限制(当前文件内) 成为“内部函数” |
无此用法 |
| 与指针结合 | 无直接关系,但可以和 const 结合,如 static const int * |
非常重要:const int * (指向常量)int * const (常量指针)const int * const (两者都是) |
| 主要应用场景 | 函数内的静态计数器 文件私有的全局变量/函数(封装、防命名冲突) |
定义真正的常量(替代 #define)保护函数参数不被修改 指向只读数据的指针 |
| 存储位置 | 静态存储区(数据段/BSS段) | 通常在静态存储区(.rodata 段,只读数据段)或栈上(取决于定义位置) |
简单记忆法则
static:“私有的” 和 “持久的”。- 修饰全局/函数:使其私有(文件作用域)。
- 修饰局部变量:使其持久(全局生命周期)。
const:“只读的”。- 修饰任何变量:使其值只读,不能被修改。
理解这两个关键字是迈向编写高质量、模块化 C 代码的重要一步。
