C语言指针常量与常量指针:从内存权限到实战应用

发布时间:2026/10/26 19:21:50

C语言指针常量与常量指针:从内存权限到实战应用 你是不是也曾在学习C语言时被“指针常量”和“常量指针”这两个概念绕得晕头转向明明代码里都用了const和*编译却总报错或者运行时行为与预期不符。这几乎是每个C语言初学者都会遇到的“拦路虎”也是面试官最爱问的经典问题之一。很多人会死记硬背“const在*前是常量指针const在*后是指针常量”的口诀。但口诀只是表象不理解背后的内存访问权限和设计哲学一旦遇到多层指针、函数参数传递或者与数组、字符串结合的场景立刻又会陷入混乱。这篇文章要解决的正是这个核心痛点。我将带你彻底厘清这两个概念的本质区别核心判断是区分的关键不在于const和*的书写顺序而在于const这个“锁”究竟锁住了谁——是锁住了指针变量本身让它不能再指向别处还是锁住了指针所指向的那块内存让内存里的数据不可修改。理解了这一点你就能从“死记硬背”升级到“一眼看穿”。读完本文你将能清晰理解指针常量和常量指针的内存模型与访问权限。掌握一套比口诀更可靠的“右左法则”分析方法。知道在函数参数、数组、字符串等实际场景中如何正确选择和使用它们。避开常见的编译陷阱和运行时错误。1. 为什么你总是分不清从内存的视角重新理解在深入语法之前我们先抛开术语从最根本的内存操作来思考。指针的本质是一个变量它的值是另一个变量的内存地址。对指针的操作无非两种改变指针本身的值即让指针指向另一个内存地址。通过指针改变它所指向的内存地址里的值即解引用操作。const关键字的作用就是“施加限制”。那么当const遇到指针时它限制的到底是上述的“操作1”还是“操作2”这就是所有混淆的根源。如果你想让一个指针一旦指向某个地址后就再也不能指向其他地方那么你需要限制的是“操作1”。这时指针变量本身是常量。如果你想让一块内存区域的数据通过某个指针访问时是只读的不能被修改那么你需要限制的是“操作2”。这时指针所指向的数据是常量。很多教材和教程一上来就抛出int const *p和int * const p这样的复杂声明却很少解释它们分别对应上述哪种需求导致学习者只能机械记忆。接下来我们将从需求出发推导出语法让你真正掌握。2. 核心概念拆解常量指针 vs. 指针常量我们先用最直白的命名和定义来建立概念。2.1 常量指针指针指向的数据是常量核心指针可以改变指向指向不同的地址但不能通过这个指针修改它当前所指向地址里的数据。通俗理解我指针可以换着房间内存地址看但我承诺无论我看哪个房间我都只看不动里面的东西数据。房间里的东西本身可能不是常量别人用其他方式或许能改但“我”不能通过“我”这个视角去改。C语言中的术语通常称为“指向常量的指针”pointer to const。内存模型指针变量p ------- | 地址A | --- 指向 --- [ 数据 ] (const通过p不可修改) ------- | | p b; // 合法指针本身的值可以变 v ------- | 地址B | --- 指向 --- [ 其他数据 ] -------关键权限✅ 可以修改指针的值p other_var;。❌ 不能通过该指针修改指向的数据*p 100;会编译错误。2.2 指针常量指针本身是常量核心指针一旦初始化指向某个地址后就不能再指向其他地址。但是可以通过这个指针修改它所指向地址里的数据前提是那块内存本身不是常量。通俗理解我指针被焊死在这个房间门口固定地址我只能看这个房间。但我可以进去把房间里的家具数据换掉。C语言中的术语通常称为“常量指针”constant pointer。内存模型指针常量p (const) ------- | 地址A | --- 指向 --- [ 数据 ] (非const通过p可以修改) ------- | | p b; // 非法指针本身的值不可变 X关键权限❌ 不能修改指针的值p other_var;会编译错误。✅ 可以通过该指针修改指向的数据*p 100;合法。2.3 对比表格为了更清晰我们将两者的核心区别总结如下特性常量指针指向常量的指针指针常量常量指针核心限制对象指向的数据通过该指针不可修改指针变量本身不可再指向他处指针值地址可以改变不可改变必须在定义时初始化解引用修改数据不可修改编译错误可以修改如果内存可写通俗比喻“承诺不破坏”的观察者“固定位置”的修改器常见用途函数参数防止函数内部意外修改外部数据固定访问某个硬件寄存器、数组首地址等3. 语法声明从“右左法则”到一眼看穿知道了概念我们来看C语言中具体的声明语法。这里介绍比死记硬背更可靠的“右左法则”。右左法则从标识符变量名开始先看右边再看左边交替进行直到解释完整个声明。3.1 常量指针的声明我们想声明一个“指向整型常量的指针”p。找到标识符p。向右看是*说明p是一个指针。向左看是const int说明这个指针指向的是const int整型常量。所以const int *p;或等价的int const *p;都表示p是一个指针它指向一个整型常量。const int *p;const修饰int强调指向的是常量整数。int const *p;const在*左边同样修饰int。这两种写法完全等价都表示常量指针。示例与验证#include stdio.h int main() { int a 10; int b 20; const int *p a; // p是指向常量的指针 // 1. 可以改变指针的指向 printf(最初 p 指向 a, *p %d\n, *p); // 输出 10 p b; // 合法改变指向 printf(现在 p 指向 b, *p %d\n, *p); // 输出 20 // 2. 不能通过p修改指向的数据 // *p 30; // 编译错误assignment of read-only location *p // 3. 但原始变量本身可以被修改通过其他途径 a 100; // 注意此时p已指向b所以a的修改与p无关 // 如果p还指向a那么*p读取到的值会是100但依然不能通过*p去修改a printf(a被修改为 %d但p指向b所以*p仍是 %d\n, a, *p); return 0; }3.2 指针常量的声明我们想声明一个“指向整型的常量指针”p。找到标识符p。向右看什么都没有遇到分号;所以向左看。向左看是*说明p是一个指针。继续向左看不*左边是const它修饰的是p本身吗这里用右左法则有点绕。更直接的理解是const直接修饰p。实际上声明int * const p;中const在*的右侧它修饰的就是指针变量p本身使得p是一个常量。所以int * const p a;表示p是一个常量指针它指向一个整数并且必须在定义时初始化。示例与验证#include stdio.h int main() { int a 10; int b 20; int * const p a; // p是一个指针常量必须初始化 // 1. 不能改变指针的指向 // p b; // 编译错误assignment of read-only variable p printf(p 固定指向 a, *p %d\n, *p); // 输出 10 // 2. 可以通过p修改指向的数据 *p 30; // 合法修改a的值 printf(通过 *p 修改后a %d, *p %d\n, a, *p); // 输出 30 30 // 3. 虽然p不能指向b但b的值可以变a的值也可以通过其他方式变 a 100; printf(a被直接修改为 %d, *p %d\n, a, *p); // 输出 100 100 return 0; }3.3 双重限制指向常量的常量指针当然你也可以两者都限制指针本身是常量且不能通过它修改数据。声明为const int * const p a;。第一个const修饰int表示指向常量。第二个const修饰p表示指针是常量。#include stdio.h int main() { int a 10; const int * const p a; // p是一个指向整型常量的常量指针 // p b; // 错误指针本身是常量 // *p 20; // 错误指向的数据是常量通过p printf(只能读取: a %d, *p %d\n, a, *p); // 只能读 a 20; // 但是a本身可以被修改通过其他方式 printf(a改变后: a %d, *p %d\n, a, *p); // 输出 20 20 return 0; }4. 实战场景为什么以及如何使用理解了语法关键是要用在正确的地方。错误的使用会导致编译错误或逻辑错误正确的使用能提升代码的健壮性和可读性。4.1 场景一函数参数传递——使用常量指针保护数据这是常量指针最经典的应用场景。当你编写一个函数它只需要读取参数指向的数据而不应该修改它时就应该使用常量指针作为参数。反面教材void printString(char *str) { while (*str) { putchar(*str); str; // 移动指针没问题 // 但如果函数内部有 *str X; 就能修改外部数据这可能是调用者不希望的。 } }调用者传入一个字符串字面量printString(Hello);如果函数内部试图修改将导致未定义行为通常是程序崩溃。正确做法void printString(const char *str) { // 使用常量指针参数 while (*str) { putchar(*str); str; } // *str A; // 编译错误保护了数据 } int main() { printString(Hello); // 安全字符串字面量受到保护 char arr[] World; printString(arr); // 安全函数承诺不修改arr return 0; }好处安全性防止函数内部意外修改外部数据。明确意图函数原型清晰地告诉调用者“我不会修改你传进来的数据”。兼容性可以接受常量数据如字符串字面量和非常量数据的地址作为实参。4.2 场景二固定访问入口——使用指针常量当你需要定义一个固定的指针始终指向某个特定的内存区域时可以使用指针常量。这在嵌入式开发中很常见例如访问固定的硬件寄存器地址。// 假设0x40021000是一个只读的状态寄存器地址 #define STATUS_REG_ADDR ((volatile const unsigned int *)0x40021000) // 定义一个指针常量始终指向这个地址 volatile const unsigned int * const status_reg STATUS_REG_ADDR; void checkStatus() { unsigned int status *status_reg; // 读取寄存器状态 // status_reg (void*)0x...; // 错误不允许改变指向 // *status_reg 1; // 错误寄存器可能是只读的此处用const保护 if (status 0x01) { // 处理状态位 } }要点这里用了两个const。第一个const表示寄存器值只读第二个const表示指针status_reg本身是常量。volatile关键字告诉编译器该值可能被硬件改变不要做优化。4.3 场景三数组名与指针数组名在大多数表达式中会被转换为指向其首元素的指针常量。int arr[5] {1, 2, 3, 4, 5}; // arr 的类型可以看作是 int * const 指向int的常量指针 // arr some_other_int; // 错误数组名不能作为左值被赋值。 int *p arr; // 正确将数组首地址赋值给一个普通指针 p; // 正确p可以移动 // arr; // 错误数组名是常量指针。理解这一点就能明白为什么arr不能进行或重新赋值操作。4.4 综合示例字符串处理函数标准库函数strcpy和strcat的原型存在安全隐患因为它们不限制修改源字符串。更安全的版本如strncpy或者使用常量指针来声明只读参数。// 一个计算字符串长度的函数显然不应该修改字符串 size_t my_strlen(const char *str) { // 常量指针参数 size_t len 0; while (str[len] ! \0) { len; } return len; } // 一个查找字符的函数也不修改字符串 char *my_strchr(const char *str, int ch) { // 返回的指针也是指向常量的但函数内需要转换 while (*str ! \0) { if (*str ch) { // 需要强制类型转换去掉const因为返回类型是char* // 这提示调用者返回的指针可能指向原始字符串修改需谨慎。 // 更好的设计是返回 const char*但为了匹配标准库这里做转换。 return (char *)str; } str; } return NULL; }5. 常见误区与深度辨析5.1 误区一const int *p和int const *p有区别吗没有区别。在C语言中const关键字修饰的是它左边的类型如果左边没有类型则修饰右边。对于int const和const int都是“常量整型”。所以int const *p和const int *p完全等价都表示“指向常量整型的指针”。选择哪种写法更多是编码风格问题但团队内应保持一致。5.2 误区二指针常量指向的数据就一定是常量吗不一定。指针常量 (int * const p) 只保证p这个指针变量本身的值存储的地址不变。至于它指向的内存区域 (*p) 是否可写取决于那块内存原本的属性。int a 10; const int b 20; int * const p1 a; // 合法p1是常量指针指向非常量a // int * const p2 b; // 编译警告或错误不能用int*指向const int因为这样会丢掉const属性存在通过p2修改b的风险。 const int * const p3 b; // 合法双重const安全。规则可以将非常量数据的地址赋值给指向常量的指针承诺不修改但不能将常量数据的地址赋值给指向非常量的指针否则可能通过指针修改常量。5.3 误区三多层指针下的const当指针指向另一个指针时const的位置会带来更多组合。分析时依然坚持“右左法则”和“锁住谁”的原则。int a 10; int b 20; int *p1 a; int **p2 p1; // 情况1指向“指向常量的指针”的指针 const int * *pp1 p1; // pp1本身不是const它指向一个const int* // **pp1 30; // 错误*pp1是const int*不能修改其指向的数据 *pp1 b; // 正确可以改变pp1指向的指针即p1的值 // *(*pp1) 30; // 错误等价于**pp130 // 情况2指向“指针常量”的指针 int * const *pp2 p1; // pp2指向一个int * const // *pp2 b; // 错误*pp2是一个指针常量不能改变其指向 **pp2 30; // 正确可以通过*pp2即p1修改a的值a现在是30 // 情况3指针常量它指向一个指针 int ** const pp3 p1; // pp3本身是指针常量 // pp3 some_other_pointer; // 错误pp3是常量 *pp3 b; // 正确可以修改pp3指向的内容即改变p1的指向 **pp3 40; // 正确可以通过*pp3即p1修改b的值分析多层指针时从标识符开始由内向外一层层用“右左法则”解析即可。6. 最佳实践与工程建议函数参数优先使用常量指针对于只读输入参数一律使用const修饰指针所指向的类型。这能最大程度避免副作用并使函数接口更清晰、更安全。明确指针常量的初始化定义指针常量时必须立即初始化。这是语法要求也是良好习惯。使用typedef简化复杂声明对于复杂的指针类型尤其是多层指针或函数指针使用typedef可以大大提高可读性。typedef const char * CString; // 指向常量字符的指针常用于字符串 typedef int * const IntConstPtr; // 整型指针常量 void printAll(CString arr[], int len); // 声明变得清晰注意const在类型转换中的丢失使用强制类型转换 (cast) 去掉const属性是危险的除非你完全确定底层对象是可修改的。否则会导致未定义行为。理解const的传播在结构体中如果结构体变量被声明为const那么其所有成员包括指针成员都视为const。但对于指针成员const修饰的是指针本身还是指向的数据需要根据结构体定义来判断。7. 总结与进阶思考区分“指针常量”和“常量指针”的关键在于理解const修饰的主体。const直接修饰指针变量名通常出现在*右侧则指针本身是常量const修饰指针指向的数据类型出现在*左侧则通过该指针访问的数据是只读的。掌握这一对概念不仅是通过面试更是编写健壮、清晰C代码的基石。它关乎你对内存访问权限的精确控制。当你设计函数接口、操作硬件、管理资源时正确使用const可以让编译器帮你发现许多潜在的错误。要进一步深化理解建议结合void *指针思考const void *和void * const分别用在什么场景例如qsort的比较函数参数。探索C中的constC中的const更复杂有常量成员函数、mutable等概念但核心思想相通。在项目代码中刻意练习下次写C代码时有意识地思考每个指针参数是否应该加const每个指针变量是否应该被声明为常量。理解并善用const是你从C语言新手迈向熟练开发者的重要一步。它让你的代码意图更明确合作更顺畅bug更少出现。
延伸阅读

更多相关文章

2026/10/26 19:21:31

从晶体管到计算机:拆解逻辑门如何构建CPU与冯·诺依曼架构

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/26 14:30:06

浩鲸科技Java B卷笔试全解析:从HashMap到JVM的校招核心考点

浩鲸科技2020届Java B卷这套笔试题,我到现在印象还挺深的。一方面是它考察的范围很全面,从Java基础语法一路问到JVM底层和算法手写,基本把校招Java岗笔试的主流考点都覆盖了;另一方面,出题风格偏工程实用,不…

2026/10/25 4:24:59

自抗扰控制ADRC的C语言工程实现与调参实战

简介:本资源是一套可直接集成到嵌入式系统的自抗扰控制(ADRC)C语言实现方案,面向自动控制、电机驱动、无人机姿态调节等实时控制领域的开发者与工程师,解决传统PID在模型不确定、强扰动场景下鲁棒性不足的问题。代码完…

2026/10/26 19:20:37

Element UI表格合计行事件绑定实战与优化方案

1. 项目概述:el-table合计行事件绑定实战在Element UI的表格组件开发中,合计行(summary row)是一个高频使用的功能特性,但官方文档对合计行的事件处理并未详细说明。最近在电商后台订单统计模块开发时,我需…

2026/10/26 19:20:37

PLC工程师学AI:工业时序数据驱动的智能控制实战

1. 这不是未来,是正在发生的现场作业变革“懂 AI 的 PLC 工程师,正在拉开行业差距”——这句话最近在自动化圈子里传得特别快,不是因为某个厂商的广告词,而是我上个月在苏州一家汽车零部件厂调试产线时,亲眼看见的现实…

2026/10/26 19:20:37

中国气候区划工程落地:从静态地图到动态知识图谱

简介:本资源是依据国家标准GB/T 17297-1998数字化构建的中国气候区划GIS数据集,面向地理信息、气象、生态、农业及城市规划等领域的科研人员、高校师生与政策研究者,用于支撑气候空间分析、区域差异评估与可持续发展决策。压缩包共16个文件&a…

2026/10/26 19:20:37

2026年学术写作AI工具评测与实战指南

1. 2026年学术写作工具全景扫描三年前我刚开始博士研究时,面对市面上突然涌现的数十款AI写作工具完全无从下手。如今作为用过28款工具的"踩坑专业户",我想分享这份结合300小时实测的深度评测。不同于那些拿厂商赞助的软文,这里只有…

2026/10/26 19:20:37

工业设备智能运维:基于ML.NET的预测性维护实战

1. 项目概述:工业设备智能运维的AI解决方案这个项目本质上是在工业自动化领域构建了一套端到端的预测性维护系统。作为在工业上位机开发领域摸爬滚打多年的老手,我见过太多工厂因为设备突发故障导致停产的情况。传统的人工巡检和定期维护就像"盲人摸…

2026/10/26 19:15:34

YOLOv8检测框绘制:PIL/numpy输入与RGB/BGR坐标还原

做视觉项目时,最容易被低估的往往不是模型推理,而是“把结果画回去”这一步。标题里这个需求很具体:YOLOv8 接受一个 numpy 数组(cv2 风格)或 PIL 图像,在输入的 RGB 图像上绘制检测结果。听起来只是调个画…

2026/10/26 0:20:20

全国计算机二级C语言真题PDF高效刷题指南

简介:这份PDF是全国计算机二级C语言考试的试题与答案解析,专为备考二级C语言的考生准备。内容涵盖C语言特点与设计理念、程序基本组成单位、main函数执行流程、函数嵌套调用规则、标识符命名规范、整型实型字符型数据类型、八进制十六进制数合法形式、运…

2026/10/26 1:53:47

requests+BeautifulSoup:爬虫抓取h1标题的入门实战指南

简介:一份以实战为导向的Python爬虫入门资源,面向具备基本编程基础、希望快速掌握网络数据抓取的初学者,可解决从零编写简单爬虫的常见困惑。实例以requests库发送HTTP请求、BeautifulSoup解析HTML文档,完整演示从网页中提取 标题…

2026/10/26 0:20:17

递推最小二乘参数辨识MATLAB编程:二阶系统与渐消记忆实现

简介:这是一篇面向控制系统、信号处理与机器学习等领域学习者的最小二乘参数辨识程序说明文档。资源共包含一个docx格式文档,压缩包大小七十八KB,内容围绕递推最小二乘算法和渐消记忆最小二乘算法展开:先构造一个极点位于单位圆内…

2026/10/26 0:00:48

Nested Learning:解决AI灾难性遗忘的分层记忆架构

1. 为什么AI需要"记忆"能力?在传统深度学习模型中,我们常遇到一个根本性问题:模型在处理新任务时,往往会完全遗忘之前学到的知识。这种现象被业界称为"灾难性遗忘"(Catastrophic Forgetting)。想象一下&#…

2026/10/26 0:00:48

企业级大模型应用:OCR预处理、Dify编排与DeepSeek落地实战

1. 这不是“用大模型”,而是重构工作流的起点“大模型的应用和工具”——这八个字现在被贴在无数技术分享、招聘JD、内部立项书的标题栏里,但绝大多数人点进去后看到的,是零散的API调用示例、几个开源UI截图、或者一句“接入LLM就能降本增效”…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑