单片机/C语言八股:(十四)const 关键字的作用(和 define 比呢?)

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

目 录

  • [const 关键字的作用(和 define 比呢?)](#const 关键字的作用(和 define 比呢?))
    • [1)const 修饰变量](#1)const 修饰变量)
      • [① 局部变量(函数内部)](#① 局部变量(函数内部))
      • [② 全局变量(文件作用域)](#② 全局变量(文件作用域))
    • [2)const 与指针的组合(重点难点)](#2)const 与指针的组合(重点难点))
    • [3)const 修饰函数](#3)const 修饰函数)
      • [① 修饰函数参数](#① 修饰函数参数)
      • [② 修饰函数返回值](#② 修饰函数返回值)
    • [4)const 修饰数组和结构体](#4)const 修饰数组和结构体)
      • [① 常量数组(查找表、配置表)](#① 常量数组(查找表、配置表))
      • [② 结构体成员为 const(较少用,但合法)](#② 结构体成员为 const(较少用,但合法))
    • [5)const 与 #define 的区别](#define 的区别)
    • 6)总结

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

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

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

总结在最后面!

在 C 语言中,const 是一个 类型限定符(type qualifier) ,用于声明某个对象为 只读(read-only)

这意味着:该对象的值在初始化后不能被程序直接修改。如果尝试修改,编译器会报错(除非通过强制类型转换绕过,但这是未定义行为,应避免)。

1)const 修饰变量

① 局部变量(函数内部)

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

用 const 修饰的局部变量依旧存储在栈上,但内容不可写,具有明确类型(比如int)。

② 全局变量(文件作用域)

c 复制代码
// file.c
const double PI = 3.1415926;

默认仅在本 .c 文件可用(相当于隐含 static),若需在多个文件共享,需配合 extern 声明。在 .c 文件中定义,在对应的 .h 文件中依旧要使用 extern

c 复制代码
// constants.h
extern const double 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 *pint const *p 指针常量(指向常量的指针) ✔️ 可以(p = &x; ❌ 不可以(*p = 10; 错误)
int *const p = &x; 常量指针 ❌ 不可以 ✔️ 可以(*p = 10;
const int *const p = &x; 指向常量的常量指针 ❌ 不可以 ❌ 不可以

应用示例:

c 复制代码
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)

    c 复制代码
    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,表明函数不会修改传入的内容,调用者可安全传入常量数组或普通数组。

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

    c 复制代码
    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 ,表示在函数体内,不能修改这个值。

② 修饰函数返回值

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

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

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

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

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

    c 复制代码
    const int getValue(void) { return 42; }
    int x = getValue();

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

4)const 修饰数组和结构体

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

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

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

c 复制代码
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(传统方式):

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

    c 复制代码
    const int MAX_SIZE = 100;
    // int buffer[MAX_SIZE]; // × C89/C99 中错误!const 不是"编译时常量"

关键点 ⚠️ :在 C 语言中,const 变量 不是编译时常量,因此不能用于:

数组长度(除非是变长数组 VLA,C99+)、case 标签、位字段宽度等需要常量表达式的地方。

解决方案✔️ :

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

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

推荐实践:

c 复制代码
// 数组大小、开关选项 → 用 #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)总结

在 C 语言中,const 的核心作用是声明只读对象,通过类型系统告诉编译器和程序员:该数据在初始化后不应被修改。

  • 它主要用于:

    • 修饰指针参数(防止函数意外修改传入的数据);

    • 定义具有类型安全的常量变量(替代部分宏);

    • 以及返回只读指针(保护内部数据不被外部篡改)。

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

  • 强制类型转换可绕过 const (但属未定义行为,可能崩溃或静默失败)。


相关推荐
进击的横打2 小时前
【车载开发系列】TAU定时器
单片机·嵌入式硬件
我是海飞2 小时前
TinyUSB 移植到 STM32F407实现Audio+Midi+Cdc复合设备
stm32·单片机·嵌入式硬件
smchaopiao2 小时前
使用C语言打印几何图形:从三角形到菱形
c语言·开发语言·算法
Book思议-2 小时前
【数据结构实战】双向链表尾插法
c语言·数据结构·链表
’长谷深风‘2 小时前
51单片机入门
c语言·单片机·嵌入式硬件·51单片机
张海森-1688202 小时前
cv608_aac_8k_16bit_mono编码较慢,所以存为MP4,音频数据会对不齐视频数据?
单片机
沐欣工作室_lvyiyi2 小时前
基于物联网的体温心率监测系统(论文+源码)
stm32·单片机·嵌入式硬件·物联网·体温心率
电子工程师成长日记-C512 小时前
51单片机蓝牙智能台灯
单片机·嵌入式硬件·51单片机
电子工程师成长日记-C512 小时前
51单片机无线病床呼叫系统
单片机·嵌入式硬件·51单片机