上一篇下一篇
C 语言实现矩阵乘法


const 关键字的作用(和 #define 比呢)?

constant:[adj] 固定的、不变的,[n] 常数、常量

被 const 定义后本质上还是变量,只是无法修改。在必须应用常量的情况下是无法使用的,所以叫做 常变量 。

总结在最后面!

在 C 语言中,const 是一个 类型限定符 ,用于声明某个对象为 只读(read-only)。这意味着:该对象的值在初始化后不能被程序直接修改。如果尝试修改,编译器会报错(除非通过强制类型转换绕过,但这是未定义行为,应避免)。


1)const 修饰变量

① 局部变量

void func(void) {
    const int MAX_RETRY = 5;
    // MAX_RETRY = 10; // 编译错误:不能修改 const 变量
}

用 const 修饰的局部变量依旧存储在栈上,但内容只读,并拥有明确类型(如 const int)。

② 全局变量

在 C 语言中,全局定义的 const 变量(如 const double PI = 3.1415926;)默认具有外部链接,与普通全局变量一样,可以被其他源文件通过 extern 声明正常访问。(注意:这一点与 C++ 完全不同,C++ 中 const 全局变量默认内部链接。)若希望将 const 变量局限在当前 .c 文件内,必须显式加上 static。

示例:

// constants.h
extern const double PI;       // 声明(告诉编译器 PI 在别处定义)

// constants.c
const double PI = 3.1415926; // 定义(外部链接)

// main.c
#include "constants.h"
double r = PI * 2;            // 正确使用

2)const 与指针的组合(重点难点)

const 与指针结合时,位置决定含义:

  • const 在 * 左边是指针常量,内容不能改,但指针可以改;
  • const 在 * 右边是常量指针,指针不能改,但内容可以改;
  • * 两边都有 const,内容和指针都不能改。
声明含义能否修改指针?能否修改指向的内容?
const int *p 或 int const *p指针常量(指向常量的指针)✔️ 可以(p = &x;)❌ 不可以(*p = 10; 错误)
int *const p = &x;常量指针❌ 不可以✔️ 可以(*p = 10;)
const int *const p = &x;指向常量的常量指针❌ 不可以❌ 不可以

应用示例:

const char *str = "Hello";     // 推荐写法:字符串字面量是只读的
// str[0] = 'h';               // × 运行时可能崩溃(写只读内存)
str = "World";                 // √ 指针可变

char buffer[10];
char *const ptr = buffer;      // 指针固定指向 buffer
ptr[0] = 'A';                  // √ 内容可变
// ptr = another_buffer;       // × 指针不可变

3)const 修饰函数

这是 const 最重要的应用场景之一:表达“只读”契约 。

① 修饰函数参数(函数参数保护)

  • 指针参数加 const,变成指针常量参数,不会修改内容:

    指针参数就是形如 void func(const int* x)

    void print_array(const int arr[], size_t n) {
        for (size_t i = 0; i < n; ++i)
            printf("%d ", arr[i]);   // √ 只读访问
        // arr[0] = 999;             // × 编译错误
    }
    

    等价于 const int *arr,表明函数不会修改传入的内容,调用者可安全传入常量数组或普通数组。

  • 字符串处理函数的标准做法:

    size_t my_strlen(const char *s);        // 不修改 s
    int my_strcmp(const char *s1, const char *s2); // 不修改 s1, s2
    

    所有标准库如 strlen, strcpy, printf 等对只读参数都使用 const。

  • 值参数(非指针参数),加 const 对实参没啥影响,但形参在函数内部不会被修改:

    值参数就是形如 void func(const int x) ,入口参数不是指针类型的。

    • 对于传入的变量(实参):C 函数参数默认是 “按值传递” ,当你调用一个函数时,实参的值会被 复制 到形参中,这个传入的变量(实参)本来就不会被修改,加不加 const 都一样。

    • 对于形参来说:形参是函数内部的一个对实参的局部变量副本,给值参数加 const 的效果,等于在函数内部给局部变量加 const ,表示在函数体内,不能修改这个值。

② 修饰函数返回值

  • 修饰返回的指针 —— 有意义:

    const char* get_version(void) {
        return "v1.2.3"; // 字符串字面量是只读的
    }
    

    上面示例返回 const char* 表示:调用者不得修改返回的字符串(其他类型类似)。

    但不要返回局部变量的 const 指针:

    const char* bad_func(void) {
        char buf[10] = "temp";
        return buf; // ❌ 返回栈地址,函数返回后无效
    }
    
  • 修饰返回的基本类型 —— 无意义:

    const int getValue(void) { return 42; }
    int x = getValue();
    

    函数返回的是临时值(右值,表示“临时值、计算结果”,如字面量 42,字符串字面量天生就是 const ),无法被赋值,const 多余。C 标准允许,但毫无作用,现代编译器会忽略。

4)const 修饰数组和结构体

① 常量数组(查找表、配置表)

const uint8_t CRC_TABLE[256] = {0x00, 0x07, 0x0E};
  • 编译器通常将其放入 .rodata 段(只读数据段),节省 RAM。适用于嵌入式系统中资源受限场景。

② 结构体成员为 const(较少用,但合法)

struct DeviceConfig {
    const int baud_rate;
    const char *const name;
};

// 初始化必须在定义时完成(C99 支持复合字面量)
struct DeviceConfig dev = { .baud_rate = 115200, .name = "UART0" };
// dev.baud_rate = 9600; // × 错误

5)const 与 #define 的区别

const 是有类型、有作用域、可取地址的运行时常量(本质是常变量,在 C 中不是编译时常量),而 #define 是无类型、无作用域、纯文本替换的预处理宏,在必须使用编译时常量的地方,const 不可用。

具体如下:

特性const 变量#define 宏
类型有明确类型(如 const int)无类型,纯文本替换
作用域遵循 C 作用域规则(块、文件)全局有效(从定义到 #undef)
存储占用内存(除非被优化掉)不占用内存,预处理阶段替换
取地址可以(&PI 合法)不可以(宏不是对象)
调试调试器可查看符号和值调试器看不到宏(已被替换)
安全性编译器检查类型和修改无任何检查,易出错
初始化运行时或编译时初始化必须是常量表达式
数组大小C 标准中 不能 用于定义数组大小(除非是 VLA 或 C23)可以: #define N 10 int arr[N];

示例对比:

  • 使用 #define(传统方式):

    #define MAX_SIZE 100
    #define PI 3.14159
    int buffer[MAX_SIZE]; // √ 合法(常量表达式)
    
  • 使用 const(更安全,但有限制):

    const int MAX_SIZE = 100;
    // int buffer[MAX_SIZE]; // × C89/C99 中错误!const 不是“编译时常量”
    

关键点⚠️:在 C 语言中,const 变量 不是编译时常量,因此不能用于:数组长度(除非是变长数组 VLA,C99+)、case 标签、位字段宽度等需要常量表达式的地方。

解决方案:

  • 对于需要编译时常量的场景(如数组大小),仍需用 #define 或 enum。

  • 对于运行时常量或需要类型安全的常量,优先用 const。

推荐实践:

// 数组大小、开关选项 → 用 #define 或 enum
#define BUFFER_SIZE 256
enum { MAX_USERS = 10 };

// 数学常量、配置值 → 用 const
const double EARTH_RADIUS_KM = 6371.0;
const char VERSION[] = "1.0.0";

6)总结

const 的核心作用:

  • 声明只读对象,借助类型系统告诉编译器和使用者:该数据在初始化后不应被修改。

主要应用场景:

  1. 保护函数参数(尤其指针):防止函数意外篡改传入的数据,强化只读契约。
  2. 定义类型安全的常量:替代无类型、无作用域的 #define,调试友好,且可取地址。
    • 常量指针和指针常量重点了解一下。
  3. 返回只读指针:对外暴露内部数据时,禁止调用者修改,增强封装。

const 的本质与限制:

  • const 变量是有类型、有作用域、可寻址的运行时常量(常变量),不是 C 语言的编译时常量。
  • 因此它不能用于数组长度(C89/C99,VLA 除外)、case 标签、位域宽度等需要整数常量表达式的位置,这些场景仍需 #define 或 enum。

与 #define 的关键区别:

  • #define 是编译前的文本替换,无类型、无作用域,可形成编译时常量;缺点是不安全、调试不可见。
  • const 提供类型安全和作用域检查,适合运行时常量、配置参数、只读数据表。

注意事项:

  • 强行通过类型转换去掉 const 并修改,在 C 标准中属于未定义行为,可能崩溃或静默失败,应绝对避免。

推荐原则:

  • 编译时常量(数组大小、条件编译)用 #define 或 enum。
  • 运行时常量、配置值、接口保护优先使用 const。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐