第15节 指针作为函数参数的工程实战用法

承接上文:上一节我们理清了指针与数组底层内存布局,弄懂数组传入函数会退化为指针。本节聚焦指针最常用的工程场景:指针函数参数,对比值传递与地址传递,搞懂为什么指针可以在函数内部修改外部变量,同时讲解指针传数组的标准写法、空指针安全校验、常见踩坑点,建立工程化编码习惯。

学习说明

很多新手疑惑:函数内部修改形参,为什么外部原始变量不会变化?本质是值传递会拷贝一份独立副本。 想要在函数内直接修改外部原始变量,有两种主流方案:指针传参(本节)、引用传参(后续章节)。 指针传参是C语言兼容、C++工程里高频使用的写法,核心原理:传递变量的内存地址,函数拿到地址,直接操作原始内存,无需拷贝原始数据。

本节学习目标

  1. 理解值传递的副本机制,明白值传递无法修改外部变量的根本原因
  2. 掌握指针做函数参数:地址传递原理,能在函数内修改外部变量
  3. 掌握数组通过指针传入函数的标准工程写法,理解数组退化
  4. 学会指针参数的安全校验:判断空指针,规避崩溃
  5. 区分指针传参的两大使用场景:修改外部变量、批量传递数组
  6. 识别指针传参常见坑:野指针、空指针解引用、越界访问

一、回顾:值传递,只能修改副本

值传递调用函数时,会拷贝一份变量的值给形参,形参和实参是两块独立内存,修改副本不会影响外部原始变量。

复制代码
#include <iostream>
using namespace std;

void change(int num)
{
    num = 100; // 修改的只是副本,外部变量不受影响
    cout << "函数内 num = " << num << endl;
}

int main()
{
    int a = 10;
    change(a);
    cout << "函数调用后 a = " << a << endl; // a仍然是10
    return 0;
}

运行输出:

复制代码
函数内 num = 100
函数调用后 a = 10

核心结论:值传递传递的是完整数据副本,形参和实参相互独立,无法修改外部实参。

值传递内存示意图

复制代码
值传递(独立副本,互不干扰)
main:    a = 10  [0x2000]  原始变量
change:  num = 100 [0x3000]  拷贝副本
修改num,仅修改0x3000内存,原始a完全不受影响

二、地址传递:指针作为函数参数,修改外部变量

不再传递变量的值,而是传递变量的内存地址 。函数形参接收这个地址(指针变量),通过解引用*操作,直接读写外部原始变量的内存。

复制代码
#include <iostream>
using namespace std;

void change(int* p)
{
    // 安全校验:先判断指针是否为空,防止空指针解引用崩溃
    if(p == nullptr)
    {
        cout << "空指针,不执行操作" << endl;
        return;
    }
    *p = 100; // 解引用,修改p指向的原始变量
    cout << "函数内 *p = " << *p << endl;
}

int main()
{
    int a = 10;
    change(&a); // 传入变量a的地址
    cout << "函数调用后 a = " << a << endl; // a被修改为100
    return 0;
}

运行输出:

复制代码
函数内 *p = 100
函数调用后 a = 100

原理总结(修正严谨表述)

  1. 实参:传入&a(变量a的内存地址)
  2. 形参:int* p,指针变量接收地址
  3. *p:根据地址找到外部变量a,直接修改原始内存
  4. 重要补充 :地址传递同样存在拷贝,仅拷贝8字节地址值,不会拷贝原始大数据,节省内存、支持修改原值

地址传递内存示意图

复制代码
地址传递(指向同一块内存)
main:    a = 10  [0x2000]  原始变量
change:  p = 0x2000         指针指向原始内存
*p = 100 直接修改 0x2000 内存,外部a同步变化

三、工程场景1:指针传参实现交换两个变量

值传递无法完成变量交换,仅地址传递可以实现。先展示值传递失败案例,再展示指针成功案例,对比更直观。

1. 值传递:交换失败(错误示范)

复制代码
#include <iostream>
using namespace std;

// 值传递:仅交换副本,外部无变化
void swapFail(int a, int b)
{
    int temp = a;
    a = b;
    b = temp;
}

int main()
{
    int x = 10, y = 20;
    swapFail(x, y);
    cout << "值传递交换后:x=" << x << ", y=" << y << endl; // 数值不变
    return 0;
}

2. 指针地址传递:交换成功(工程标准写法)

复制代码
#include <iostream>
using namespace std;

void swapNum(int* p1, int* p2)
{
    if(p1 == nullptr || p2 == nullptr)
    {
        cout << "存在空指针,终止交换" << endl;
        return;
    }
    int temp = *p1;
    *p1 = *p2;
    *p2 = temp;
}

int main()
{
    int x = 10, y = 20;
    cout << "交换前:x=" << x << ", y=" << y << endl;
    swapNum(&x, &y);
    cout << "交换后:x=" << x << ", y=" << y << endl;
    return 0;
}

四、工程场景2:指针接收数组(最常用)

数组传入函数自动退化为首元素指针,丢失长度信息。工程标准写法:指针形参 + 长度参数,强制增加空指针判断。

复制代码
#include <iostream>
using namespace std;

// 形参 int* arr 等价于 int arr[]
void printArray(int* arr, int len)
{
    // 工程强制:空指针校验 + 合法性校验
    if(arr == nullptr || len <= 0)
    {
        cout << "数组指针为空或者长度非法" << endl;
        return;
    }
    for(int i = 0; i < len; i++)
    {
        cout << arr[i] << " ";
    }
    cout << endl;
}

// 修改数组所有元素,统一翻倍
void doubleArray(int* arr, int len)
{
    if(arr == nullptr || len <= 0)
    {
        return;
    }
    for(int i = 0; i < len; i++)
    {
        arr[i] *= 2;
    }
}

int main()
{
    int arr[] = {1,2,3,4,5};
    int len = sizeof(arr)/sizeof(arr[0]);
    printArray(arr, len);
    doubleArray(arr, len);
    printArray(arr, len);
    return 0;
}

工程规范:只要函数接收指针类型参数,必须优先做空指针判断,避免空指针解引用导致程序崩溃,是工业级代码基础规范。


五、值传递 vs 地址传递对比表(精准修正)

传递方式 传递内容 内存拷贝 能否修改外部变量 适用场景
值传递 变量本身数据 拷贝整个数据 不能 只读场景、小数据类型、无需修改原值
地址传递(指针传参) 变量/数组的内存地址 仅拷贝8字节地址 可以 修改外部变量、传递大数组、避免大数据拷贝

六、指针函数参数高频坑点(修复逻辑矛盾)

  1. 忘记空指针校验 传入nullptr未做判断,直接解引用,直接触发程序崩溃。所有指针参数必须前置判空。

  2. 数组越界访问 传入非法长度,指针访问数组以外内存,产生未定义行为,大概率闪退、数据错乱。

  3. 返回局部变量地址(野指针)【修正标题与内容矛盾】 函数返回局部变量地址,局部变量随函数结束销毁,内存被回收,地址失效,形成野指针。

    // 错误示例,绝对禁止
    int* badFunc()
    {
    int num = 100;
    return # // num是局部变量,函数结束内存回收,返回无效野指针
    }

  4. 混淆修改指针本身 VS 修改指针指向内容

  • *p = xxx修改指针指向的数据,会改变外部原始变量(核心常用)
  • p = xxx仅修改指针变量自身存储的地址,不会影响外部实参

七、本节核心规则总结

  1. 值传递:拷贝完整数据副本,形参实参相互独立,无法修改外部原值。
  2. 地址传递:拷贝地址而非数据,通过*p解引用直接操作原始内存,可修改外部变量。
  3. 数组传参自动退化为首元素指针,丢失长度属性,必须手动传入数组长度。
  4. 工程规范:所有指针参数,第一步必须做空指针校验,防御程序崩溃。
  5. 修改*p是改数据,修改p是改地址,二者效果完全不同。
  6. 严禁返回局部变量地址,杜绝野指针隐患。

思考题(新增铺垫预习题)

  1. 值传递为什么无法在函数内部修改外部实参?

  2. 使用指针作为函数参数时,为什么推荐增加空指针判断?

  3. 写一个函数,利用指针参数,计算传入数组的最大值。

  4. 辨析:*p = 50p = nullptr,分别做了什么操作?会不会影响外部变量?

  5. 预习题(铺垫指针的指针):运行以下代码是否会崩溃?为什么?

    #include
    using namespace std;

    void tryAlloc(int* p) {
    p = new int(100);
    }

    int main() {
    int* p = nullptr;
    tryAlloc(p);
    cout << *p << endl;
    return 0;
    }

下期预告

下一节学习引用&详解:引用与指针的工程选型区别

相关推荐
SEO_juper1 小时前
2026年用Python分析网站访问日志:看清Googlebot和AI爬虫怎么爬你的站(附完整代码)
开发语言·前端·seo·独立站·谷歌优化
j7~1 小时前
【C++微服务项目开发脚手架】(准备篇)从 0 到跑通:Docker 容器开发环境 + 宿主机用户 + Trae 远程连接 全链路踩坑实战
linux·c++·学习·xshell·docker容器·trae
唠玖馆1 小时前
C++11
c++
邪修king2 小时前
Re:Linux 系统篇(二十九):动静态库Chapter2:动态库深度辨析 —— 核心本质、制作流程、双阶段查找模型与排错指南
android·java·linux·开发语言
泡海椒2 小时前
告别 iText 繁杂配置:jquick-pdf 极简 PDF 生成实战(零基础上手)
java·开发语言·pdf
云泽8084 小时前
从零搭建 WSL2 + Ubuntu C/C++ 开发环境:原理、实践与底层架构解析
linux·c++·windows·ubuntu·wsl2
初願致夕霞10 小时前
C++11:类型推导与完美转发复盘笔记
c++
小猪写代码10 小时前
C++中什么是类,什么是对象
c++
君顾113 小时前
上海24小时自助健身房系统开发实战指南:从架构设计到落地部署
java·开发语言·健身房