当前位置: 首页 > news >正文

第12讲:深入理解指针(2)——指针的“安全锁”与“传址魔法”

🧭 第12讲:深入理解指针(2)——指针的“安全锁”与“传址魔法” 🔐✨

指针进阶:学会保护、规避风险、高效传参!


📚 目录

  1. const 修饰指针:给指针上“安全锁” 🔒
  2. 野指针:指针界的“野狗” 🐕
  3. assert 断言:程序员的“调试哨兵” 🛡️
  4. 指针的使用与传址调用:让函数真正“改变世界” 🔄
  5. 学习收获总结 ✅

const 修饰指针:给指针上“安全锁” 🔒

🔐 const 修饰变量:只读的“保护罩”

变量默认可修改,但 const 可以让它“只读”。

int m = 0;
m = 20; // ✅ 合法const int n = 0;
n = 20; // ❌ 编译错误!n 被 const 保护

📌 本质const语法层面的限制,防止直接修改变量。

⚠️ 但“绕路”修改是可能的(不推荐):

const int n = 0;
int *p = &n;  // 警告:类型不匹配
*p = 20;      // ❌ 虽可能成功,但破坏了 const 的初衷

🎯 问题:如果 nconst 修饰,就不该被修改。
解决方案:用 const 修饰指针,防止通过指针篡改!


🧩 const 修饰指针变量:位置决定权限

const* 左右,意义大不同!

指针声明含义可修改?
int *p普通指针指向内容 ✅,指针本身 ✅
const int *pint const *p指向常量的指针指向内容 ❌,指针本身 ✅
int * const p常量指针指向内容 ✅,指针本身 ❌
const int * const p指向常量的常量指针全部 ❌
✅ 代码验证
// 1. const 在 * 左边:内容不可改
const int *p1 = &n;
// *p1 = 10; // ❌ 错误:不能通过 p1 修改内容
p1 = &m;     // ✅ 正确:可以改变 p1 指向// 2. const 在 * 右边:指针本身不可改
int * const p2 = &n;
*p2 = 10;    // ✅ 正确:可以通过 p2 修改内容
// p2 = &m;   // ❌ 错误:不能改变 p2 的指向// 3. 两边都有 const:完全锁定
const int * const p3 = &n;
// *p3 = 10;  // ❌
// p3 = &m;   // ❌

📌 记忆口诀

“左定内容,右定指针”
const* 左 → 内容不能变
const* 右 → 指针不能变


野指针:指针界的“野狗” 🐕

⚠️ 什么是野指针?

野指针:指向无效或未知内存地址的指针。
访问野指针可能导致程序崩溃、数据损坏!


🚨野指针三大成因

❌ 成因1:指针未初始化

局部指针变量未初始化时,值是随机的垃圾值

int *p;     // 野指针!p 的值是随机的
*p = 100;   // ❌ 危险!写入未知地址,程序崩溃

❌ 成因2:指针越界访问

指针访问了数组范围之外的内存。

int arr[10] = {0};
int *p = &arr[0];
for (int i = 0; i <= 11; i++) {*(p++) = i; // ❌ 当 i=10,11 时,p 越界 → 野指针
}

❌ 成因3:指向已释放的空间

函数返回局部变量的地址,局部变量在函数结束后被销毁。

int* test() {int n = 100;return &n; // ❌ 危险!n 是局部变量,函数结束后空间释放
}int main() {int *p = test();printf("%d\n", *p); // ❌ 野指针访问,结果未定义return 0;
}

✅如何规避野指针?

🛡️ 指针初始化
  • 明确指向 → 直接赋地址
  • 不明确 → 赋值为 NULL
int num = 10;
int *p1 = &num;  // ✅
int *p2 = NULL;  // ✅ 安全的“空值”

📌 NULL 是一个宏,值为 0,表示“空地址”,不可读写。

#define NULL ((void*)0) // 标准定义

🛡️ 小心越界
  • 指针只能访问申请过的内存范围
  • 循环条件要精确,避免 <= 误用。

🛡️指针不再使用时置 NULL,使用前检查有效性 ✅

当指针完成使命后,及时将其置为 NULL,这是一种极佳的编程习惯。

🌳 为什么? 约定俗成的规则是:绝不访问 NULL 指针
这样做相当于把“野狗”拴在树上,使其不再危害系统。

int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];// 使用指针遍历数组
for (int i = 0; i < 10; i++) {*(p++) = i;
}// 使用完毕,及时“拴住野狗”
p = NULL; // ✅ 野指针被“管理”起来

🐕 使用前检查:绕开“拴住的野狗”

即使野狗被拴住,我们也要绕着走,不能去挑逗它。
对于指针,使用前必须检查是否为 NULL

// 下次使用前,先检查
if (p != NULL) {// 安全使用 p
} else {p = &arr[0]; // 重新赋有效地址if (p != NULL) {// 再次确认后使用}
}

📌 核心原则

  1. 用完即置 NULL → 防止后续误用
  2. 使用前必检查 → 确保指针有效

🎯 比喻升华

  • 野指针 = 野狗 → 放任不管,危害系统
  • p = NULL = 拴狗绳 → 将风险“管理”起来
  • if (p != NULL) = 观察狗是否被拴住 → 安全第一

🛡️ 避免返回局部变量地址
  • 局部变量生命周期仅限函数内。
  • 如需返回数据,考虑:
    • 静态变量(static
    • 动态内存(malloc
    • 传入指针参数

assert 断言:程序员的“调试哨兵” 🛡️

🚨 什么是 assert

assert<assert.h> 中的宏,用于运行时断言检查

#include <assert.h>
assert(p != NULL); // 如果 p 为 NULL,程序终止并报错
assert() 的工作原理

assert() 接受一个表达式作为参数:

条件结果
表达式为真(非零)assert() 不产生任何作用,程序继续运行
表达式为假(为零)assert() 触发,程序终止,并在标准错误流 stderr 中输出一条错误信息,内容包括: • 失败的表达式 • 文件名 • 行号

📌 示例:若 p == NULL,则报错:

Assertion failed: p != NULL, file example.c, line 10

🌟 assert() 的优势

  1. 自动定位错误:无需手动打印,自动显示文件名和行号,快速定位问题源头。
  2. 可开关控制:无需修改代码即可全局启用/禁用。
  3. 提高代码健壮性:在开发阶段捕获逻辑错误,防止程序带病运行。
🔧 如何关闭 assert()

#include <assert.h> 前定义宏 NDEBUG

#define NDEBUG
#include <assert.h>
// 所有 assert() 被编译器忽略

🔄 灵活切换

  • 出现问题?→ 注释掉 #define NDEBUG → 重新编译 → assert() 重新启用
  • 确认无误?→ 取消注释 → 重新编译 → assert() 被禁用

⚠️ assert() 的缺点

  • 增加运行时间:每次执行都引入额外的检查,影响性能。

🎯 最佳实践:Debug 与 Release 版本

版本assert() 状态说明
Debug(调试版)✅ 启用帮助程序员快速排查问题
Release(发布版)✅ 禁用通常在编译时自动优化掉,不影响用户程序效率

📌 集成开发环境(如 VS)的行为
在 Release 模式下,默认会定义 NDEBUG,从而自动移除所有 assert() 检查,确保发布版本的高性能。


指针的使用与传址调用:让函数真正“改变世界” 🔄

🧩 strlen 模拟实现

目标:统计字符串 \0 之前的字符个数。

#include <assert.h>int my_strlen(const char *str) {assert(str != NULL); // 断言指针非空int count = 0;while (*str != '\0') {count++;str++;}return count;
}int main() {int len = my_strlen("abcdef");printf("长度: %d\n", len); // 输出 6return 0;
}

📌 关键点

  • 参数用 const char*:保证函数内不修改字符串
  • 使用 assert:防止空指针传入
  • 指针遍历:str++ 逐个字符移动

🔄 传值调用 vs 传址调用

❌ 传值调用:失败的交换
void Swap1(int x, int y) {int tmp = x;x = y;y = tmp; // ❌ 只交换了形参,不影响实参
}int main() {int a = 10, b = 20;Swap1(a, b); // a, b 的值没变return 0;
}

📌 传值调用特点

  • 实参 → 形参:值拷贝
  • 形参是独立副本,修改不影响实参

✅ 传址调用:成功的交换
void Swap2(int *px, int *py) {int tmp = *px;*px = *py;*py = tmp; // ✅ 通过地址修改主函数变量
}int main() {int a = 10, b = 20;printf("交换前: a=%d, b=%d\n", a, b);Swap2(&a, &b); // 传地址printf("交换后: a=%d, b=%d\n", a, b);return 0;
}

✅ 输出:

交换前: a=10, b=20
交换后: a=20, b=10

📌 传址调用特点

  • 传递变量的地址
  • 函数内通过 *指针 间接访问和修改主函数变量
  • 实现了函数对外部数据的真正修改

🎯 何时使用传址调用?

场景调用方式
仅读取数据、计算✅ 传值调用
需要修改主函数变量✅ 传址调用
传递大型结构体(避免拷贝开销)✅ 传址调用

🌟 核心价值
传址调用让函数与主调函数建立真实联系,实现数据的双向交互。


✅ 学习收获总结

技能掌握情况
✅ 理解 const 修饰指针的四种形式及权限✔️
✅ 掌握野指针的三大成因及规避方法✔️
✅ 熟练使用 assert 进行调试断言✔️
✅ 理解传值与传址调用的本质区别✔️
✅ 能编写安全、高效的指针函数✔️

🎯 指针是C语言的“双刃剑”
用得好,代码高效灵活;用不好,程序崩溃难调。
你已掌握安全与传参的核心,继续前行,成为指针大师!💪🔥

💬 需要本讲的 assert 使用场景清单 / 野指针检测工具 / 传址调用练习题?欢迎继续提问,我为你准备!

http://www.dtcms.com/a/470530.html

相关文章:

  • 小企业网站制作wordpress 搭建个人博客
  • 企石镇做网站中国建筑装饰网饶明富
  • 深入洞察:从巴菲特投资哲学萃取最佳实践
  • 设计网站的功能有哪些微营销工具
  • 我的世界做皮肤的网站西安市高新区建设局网站
  • 车载360环视平台:米尔RK3576开发板支持12路低延迟推流
  • 松下机械手焊机气体流量调节
  • x64dbg破解学习(浅尝)
  • RRC状态机:移动通信网络中的连接灵魂
  • LibreCAD 编译详细步骤指南
  • 2025年100道最新软件测试面试题,常见面试题及答案汇总
  • (15)100天python从入门到拿捏《面向对象编程》
  • Spring AI Alibaba 与 Ollama 集成初探:从环境搭建到首次调用
  • 营销型网站的标准网站flash导入页
  • 汉中专业网站建设开发怎么上国外网站
  • Windows上离线安装 PostgreSQL
  • MySQL——表的操作
  • langchain官网翻译:Build a Question/Answering system over SQL data
  • 我的HarmonyOS百宝箱
  • 广州十大室内设计公司排名网站推广seo教程
  • h5网站用什么软件做网站域名备案证书下载
  • 南京本地网站有哪些做网页需要什么
  • 机器学习破解生命之谜:内在无序蛋白质设计迎来革命性突破
  • Springboot之常用注解
  • 从 0 到 1 搭建 Python 语言 Web UI自动化测试学习系列 14--二次开发--封装公共方法 2
  • 做平面的公司网站DNF做钓鱼网站
  • SpringBoot 集成 LangChain4j RAG Redis
  • 【QT】customPlot 设置图例透明背景和文字颜色
  • AT指令解析:ring_buffer、信号量、互斥量等基础知识
  • 任务网站(做任务学技能的)潍坊市建设局网站