static与const在C语言中到底有何区别与联系?

99ANYc3cd6
预计阅读时长 25 分钟
位置: 首页 C语言 正文

Part 1: static 关键字

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

c语言static与const
(图片来源网络,侵删)

修饰局部变量(函数内部)

这是 static 最常见的用法之一。

作用:

  • 生命周期延长:普通的局部变量(自动变量)存储在栈上,函数调用结束时会被销毁,而用 static 修饰的局部变量存储在静态存储区(通常是数据段),它的生命周期和整个程序一样长,即使函数执行结束,它的值也不会被销毁,而是会保留上一次的值。
  • 作用域不变:它的作用域仍然仅限于定义它的函数内部,外部无法访问。

特点:

  • 初始化static 局部变量如果没有被显式初始化,编译器会自动将其初始化为 0(对于指针是 NULL)。
  • 只初始化一次:由于它只初始化一次,在程序运行期间,这个初始化代码只会执行一次。

示例:

c语言static与const
(图片来源网络,侵删)
#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 声明也无法访问它。
  • 生命周期不变:它的生命周期仍然是整个程序运行期间。

特点:

c语言static与const
(图片来源网络,侵删)
  • 实现“文件级封装”:这是 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 *ptrint 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: staticconst 的结合使用

staticconst 可以结合使用,创造出兼具两者特性的变量。

最常见的用法: 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 代码的重要一步。

-- 展开阅读全文 --
头像
织梦自适应手机底部导航如何实现?
« 上一篇 01-13
dede免费模板适合摄影工作室用吗?
下一篇 » 01-13

相关文章

取消
微信二维码
支付宝二维码

目录[+]