【C 语言八股文】系列文章目录

第一章 static 关键字 - 内存 + 用法 + 面试题,一篇吃透

第二章 volatile 关键字 - 底层原理 + 实战场景 + 面试真题,一篇搞懂

第三章 const 关键字 - 底层本质 + 易混场景 + 面试真题,一篇吃透



前言

const是 C 语言中最基础也最易踩坑的关键字之一 —— 面试中面试官不会只问 “const 是什么”,而是会深挖 “const 修饰指针的几种写法区别”“const 和 #define 的对比”“函数参数加 const 的意义” 等实战问题。很多初学者只会用const定义 “常量”,但分不清const int *p和int *const p的区别,甚至滥用const导致代码逻辑错误。这篇文章会从const的底层本质出发,拆解所有高频用法,梳理面试必考点和避坑指南,帮你彻底搞定这个基础且重要的八股文考点。


一、核心定义 & 本质

核心定义:

const(常量的)是 C 语言的类型限定符(type qualifier),用于修饰变量、指针、函数参数等,表明被修饰的对象只读,程序代码无法修改其值(编译器会对修改操作报错)。

本质:

  1. 编译期约束:const的限制仅作用于编译阶段,编译器会检查是否有代码试图修改const对象,一旦发现则直接报错;运行期const对象仍存储在内存中(非只读内存区,可通过指针绕过限制)。
  2. 并非 “常量”:const修饰的变量是 “只读变量”,而非真正的常量(如宏定义)—— 它有内存地址,不能用于数组长度、case 标签等编译期常量场景(C99 前)。

二、核心用法(分点 + 代码示例)

用法 1:修饰普通变量(只读变量)

作用:限制变量值不能被直接修改,增强代码可读性和安全性(明确表示变量值不应被修改)。

代码如下(示例):

#include <stdio.h>

int main() 
{
    // 定义只读变量:编译期禁止直接修改
    const int MAX_NUM = 100; 
    
    // 错误示例:直接修改const变量,编译器报错(error: assignment of read-only variable ‘MAX_NUM’)
    // MAX_NUM = 200; 
    
    // 注意:可通过指针绕过(仅演示,实际开发禁止这么做)
    int *p = (int *)&MAX_NUM;
    *p = 200; // 运行期可修改(未定义行为,部分编译器/平台可能保护)
    printf("MAX_NUM = %d\n", MAX_NUM); // 输出:200(或100,取决于编译器优化)
    
    return 0;
}

关键说明:const变量本质是 “只读” 而非 “常量”——C99 标准前,const int n = 5; int arr[n];会编译报错(数组长度需编译期常量),而#define N 5; int arr[N];合法。

用法 2:修饰指针(核心高频场景)

作用:根据修饰位置不同,限制 “指针指向的值” 或 “指针本身” 不可修改,是面试必考的核心场景。

代码如下(示例):

#include <stdio.h>

int main() {
    int a = 10, b = 20;
    
    // 场景1:const int *p → 限制指向的值不可改
    const int *p1 = &a;
    // *p1 = 100; // 报错:不能修改指向的值
    p1 = &b; // 合法:指针本身可改
    
    // 场景2:int *const p → 限制指针本身不可改
    int *const p2 = &a;
    *p2 = 100; // 合法:指向的值可改
    // p2 = &b; // 报错:不能修改指针本身
    
    // 场景3:const int *const p → 两者都不可改
    const int *const p3 = &a;
    // *p3 = 100; // 报错
    // p3 = &b;   // 报错
    
    printf("a = %d, b = %d\n", a, b); // 输出:a = 100, b = 20
    return 0;
}

记忆技巧:const 靠近谁,谁就只读——const在左边,指向的值只读;const在右边,指针本身只读。

用法 3:修饰函数参数

作用:限制函数内部不能修改传入的参数值,保护实参不被意外修改,同时明确函数的设计意图(该参数仅作输入)。

代码示例:

#include <stdio.h>
#include <string.h>

// 场景1:修饰普通参数(值传递)
// 注:值传递的参数本身是副本,加const仅限制函数内修改副本,对实参无影响,主要提升可读性
void print_num(const int num) 
{
    // num = 100; // 报错:不能修改const参数
    printf("num = %d\n", num);
}

// 场景2:修饰指针参数(地址传递,核心场景)
// 限制函数内不能修改传入的字符串内容,避免意外篡改
void str_copy(char *dest, const char *src) 
{
    // src[0] = 'A'; // 报错:不能修改const指针指向的值
    strcpy(dest, src); // 合法:仅读取src的值
}

int main() 
{
    int n = 5;
    print_num(n); // 输出:num = 5
    
    char dest[20];
    const char *src = "hello const";
    str_copy(dest, src);
    printf("dest = %s\n", dest); // 输出:dest = hello const
    return 0;
}

关键说明:函数参数加const的核心价值在指针 / 引用传递场景(如字符串、数组、结构体),能有效防止函数内部意外修改实参内容。

用法 4:修饰函数返回值(进阶场景)

作用:限制函数返回值不能被直接修改,常见于返回指针的函数,防止返回的只读数据被篡改。

代码示例:

#include <stdio.h>

// 返回const指针:调用者不能修改返回的字符串内容
const char *get_version() 
{
    static const char version[] = "v1.0.0";
    return version;
}

int main() 
{
    const char *ver = get_version();
    printf("version = %s\n", ver); // 输出:version = v1.0.0
    
    // ver[0] = 'V'; // 报错:不能修改const返回值
    return 0;
}

三、面试高频考点(问法 + 标准答案)

基础题

问法 1:const的作用是什么?const修饰的变量是常量吗?

答:
① const的核心作用是在编译期约束被修饰对象只读,禁止代码直接修改其值,增强代码安全性和可读性;
② 不是真正的常量:const变量有内存地址,是 “只读变量”,C99 前不能用于数组长度、case 标签等编译期常量场景,而宏定义(#define)是真正的编译期常量。

问法 2:const int *p、int *const p、const int *const p的区别?

答:
① const int *p:指针指向的 int 值只读,指针本身可指向其他地址;
② int *const p:指针本身只读(不能指向其他地址),指向的 int 值可修改;
③ const int *const p:指针指向的 int 值和指针本身都只读。
【记忆技巧:const 靠近谁,谁就只读】

进阶题

问法 1:const和#define的区别?

答:
在这里插入图片描述

问法 2:如何绕过const的限制修改只读变量?为什么不建议这么做?

答:
① 方法:通过指针强制类型转换,如const int a=10; int p=(int)&a; *p=20;;
② 风险:这是 C 语言的 “未定义行为”—— 部分编译器(如嵌入式编译器)会将const变量存储在只读内存区(ROM),修改会导致程序崩溃;即使能修改,也违反代码设计意图,引发逻辑错误。

坑题

问法 1:以下代码是否合法?为什么?

const int n = 5;
int arr[n]; // C89标准下报错,C99标准下合法
switch(1) 
{
    case n: printf("ok"); break; // 所有标准都报错
}

答:
① int arr[n];:C89 要求数组长度是编译期常量,const变量不是常量,因此报错;C99 支持变长数组(VLA),因此合法;
② case n::case 标签必须是编译期常量表达式,const变量不满足,因此所有标准下都报错。

问法 2:函数参数传递时,值传递的参数加const有意义吗?

答:
有,但仅提升代码可读性—— 值传递的参数是实参的副本,函数内修改副本不会影响实参,加const只是禁止函数内修改副本,无实际功能约束,但能明确告诉阅读者:“该参数仅作输入,函数不会修改其值”。

四、常见误区 & 避坑

误区 1:认为const变量绝对不能被修改

错误认知:const变量的值永远不能改,是 “绝对常量”。
纠正:const仅在编译期限制直接修改,运行期可通过指针强制转换绕过(未定义行为);且const变量的存储位置取决于编译器 —— 若存储在只读内存区(如嵌入式 ROM),修改会崩溃;若存储在普通数据区,修改可能成功,但违反设计意图。

误区 2:混淆const修饰指针的写法

错误示例:把const int *p理解为 “指针本身只读”。
纠正:记住核心规则 ——const 在左边,值只读;const 在右边,指针只读。可通过口诀记忆:“左定值,右定址”(左边限制值,右边限制地址)。

误区 3:滥用const修饰值传递参数

错误认知:所有函数参数都加const更 “安全”。
纠正:值传递参数加const仅提升可读性,无实际功能价值;核心应在指针 / 结构体指针等地址传递场景加const,避免篡改实参。

五、总结 & 记忆口诀

核心总结

  1. const的核心是编译期只读约束,修饰的是 “只读变量” 而非 “常量”;
  2. 修饰指针的核心规则:左定值,右定址(const 在左,值只读;const 在右,指针只读);
  3. 核心使用场景:保护函数参数(指针)、明确只读变量、提升代码可读性。

记忆口诀

const 修饰只读量,编译检查不越界;
左定值来右定址,指针写法别搞混;
宏是常量 const 不是,参数加它更安全。

六、拓展思考

  • const在 C 和 C++ 中的差异:C++ 中const变量默认是编译期常量(可用于数组长度、case 标签),且支持修饰类成员函数(表示该函数不修改类成员);C 语言中const仅为只读变量,无类相关用法。
  • const的优化价值:编译器可对const变量做额外优化(如缓存到寄存器),但前提是变量值确实不会被修改(未通过指针绕过)。

更多推荐