函数里改了变量,外面为什么纹丝不动?——指针初探,Day11学习记录 1. 从“传值传址”的困惑说起Day09 学函数的时候我遇到了一个让人抓狂的问题。写了一个change函数想把外部变量改成 100结果外面的变量纹丝不动。代码长这样#includestdio.hvoidchange(intx){x100;// 我把 x 改成了 100}intmain(){inta10;change(a);printf(a %d\n,a);// 输出 10...说好的 100 呢return0;}为什么改不了因为 C 语言默认是值传递。调用change(a)的时候只是把a的值10复制了一份给形参x。x和a是两个独立存在的变量你是在改副本x原物a根本不受影响。就像一个同学借了你的笔记去复印他在复印件上乱涂乱画你的原版笔记自然完好无损。那怎么才能真的改到外面的a呢答案不传值传地址。让我直接找到a住在哪个“房间”进房间改它。这就是今天的主角——指针。2. 指针到底是什么2.1 内存编号——变量的“门牌号”程序运行的时候每个变量都住在内存的某个“房间”里每个房间都有一个独一无二的编号——地址。指针 地址内存单元的编号指针变量 专门用来存放地址的变量类比一下概念类比变量房间变量值房间里的东西地址指针门牌号指针变量写着门牌号的小纸条解引用按门牌号找到房间进去拿/放东西这张图把刚才的类比翻译成了实实在在的内存关系内存空间变量 a门牌号0x7ffd1234指针变量 pp 记录了 a 的门牌号a 里存的是10p 里存的是0x7ffd12342.2 三个关键操作指针就三板斧会了就能用定义指针变量int *p;——声明p是一个专门存放int类型变量地址的变量取地址p a;——把变量a的地址交给p解引用*p 100;——顺着p里存的地址找到a的房间直接把里面的值改成 100inta10;// 住进房间放了值 10int*p;// p 是一张空白小纸条准备记门牌号pa;// 在纸条上写下 a 的门牌号*p100;// 按门牌号找过去把房间里的东西改成 100// 现在 a 的值变成了 1003. 用指针解决“函数改不了外部变量”的问题3.1 重写 change 函数理解了指针的三板斧我们就能改写change函数。不再传值的副本而是把a 的地址直接告诉函数#includestdio.h// x 现在是一个指针变量接收的是地址voidchange(int*x){*x100;// 按地址找到源变量直接修改}intmain(){inta10;change(a);// 把 a 的地址传进去printf(a %d\n,a);// 这次真的输出 100return0;}执行流程change(a)把a的门牌号比如0x7ffd1234传进去形参x拿到了这个门牌号*x 100按门牌号直接进入a的房间把 10 改成了 100main里的a实实在在被改变了。3.2 一个常见的错误❌错误写法voidchange(int*x){x100;// 这是把指针变量本身改成 100编译器会警告}为什么错x存的是地址x 100是把地址值改成了 100这既不是有效的内存地址也跟a毫无关系。要改a的值必须加*——*x 100。✅一句总结想通过函数改外部变量传地址进去用*解引用修改。4. 指针的核心用途在被调函数中修改主调函数的变量指针最实用的场景之一就是让一个函数同时带回多个结果。普通的return只能返回一个值但用指针可以让函数把多个计算结果“写”进外部变量。问题写一个函数同时计算两个数的和与差不用返回值带出结果。#includestdio.h// sum 和 diff 都是指针指向外部的存储位置voidcalc(inta,intb,int*sum,int*diff){*sumab;// 把和写入 sum 指向的变量*diffa-b;// 把差写入 diff 指向的变量}intmain(){inta10,b3;inthe,cha;// 准备两个变量来接收结果calc(a,b,he,cha);// 把 he 和 cha 的地址传进去printf(和 %d, 差 %d\n,he,cha);// 输出和 13, 差 7return0;}执行要点calc不靠return而是通过指针直接把结果塞进main里的he和cha调用时一定要传he和cha地址不是传值这在嵌入式开发里极其常见一个函数需要同时返回状态码 实际数据时就用这种模式5. 预处理指令简介补充知识点预处理是在编译之前由预处理器完成的工作可以理解为代码正式编译前先做一波文本替换。今天先认识最重要的三种后续用到再展开。5.1 宏定义 ——#define不带参宏简单的文本替换#definePI3.14159#defineMAX_SIZE100floatareaPI*r*r;// 预编译后变成3.14159 * r * r注意宏定义末尾不加分号加上分号会一起被替换进去导致语法错误。带参宏像函数但不是函数括号是关键#defineSQUARE(x)((x)*(x))// 参数和整体都加括号才安全intresultSQUARE(12);// 替换后变成((1 2) * (1 2)) 9正确忘记加括号就会踩坑#define SQUARE(x) x*x会把SQUARE(12)展开成12*12 5完全不是预期结果。所以带参宏必须加括号这是最重要的安全习惯。5.2 文件包含与条件编译#include …用于系统头文件#include …用于自己写的头文件记住系统尖括号自己双引号即可。条件编译#ifdef / #ifndef / #endif在调试时很有用开发阶段定义一个宏如DEBUG用#ifdef DEBUG包裹调试打印发布时去掉那一行宏定义所有调试代码自动消失不用手动删除。这三种预处理指令今天先简单认识后面介绍头文件和多文件编译时还会深入讨论。6. 每日一练用指针实现一个函数同时计算两个数的和与差不通过返回值带出结果。参考代码见第 4 节示例。完成后再试试这个升级版写一个函数getMinMax(int arr[], int len, int *min, int *max)找出数组中的最小值和最大值通过指针返回。在main中定义长度为 10 的数组并测试。提示数组传参会退化为指针可以同时修改min和max指向的变量。升级版参考答案#includestdio.h// 找出数组中的最小值和最大值通过指针参数返回voidgetMinMax(intarr[],intlen,int*min,int*max){*minarr[0];// 先假设第一个元素同时是最小和最大*maxarr[0];for(inti1;ilen;i){if(arr[i]*min){*minarr[i];// 找到更小的更新 min 指向的变量}if(arr[i]*max){*maxarr[i];// 找到更大的更新 max 指向的变量}}}intmain(){intarr[10]{1,3,-2,5,9,0,4,7,8,2};intmin,max;getMinMax(arr,10,min,max);// 传数组和两个变量的地址printf(数组中的最小值 %d\n,min);printf(数组中的最大值 %d\n,max);return0;}运行输出数组中的最小值 -2 数组中的最大值 9要点回顾min和max在调用前是普通int变量把它们的地址传给getMinMax函数就能通过*min/*max把结果“塞”进去。数组名arr在传参时自动变成指向首元素的指针和arr[0]效果一样。7. 今日避坑指南序号错误后果正确做法1change(a)而不是change(a)函数内部改不到外部变量想改外部变量就传变量名2函数里写x 100而不是*x 100改了指针本身外部变量纹丝不动解引用写*x 1003解引用未初始化的指针野指针程序崩溃或乱改内存int *p a;先初始化再用4#define SQUARE(x) x*x不加括号运算符优先级导致结果错误#define SQUARE(x) ((x)*(x))5宏定义末尾加分号#define PI 3.14;替换后多余分号导致编译错误宏定义不加分号6搞混和*int *p a;写成int *p *a;类型不匹配编译器报错定义时*是声明指针取地址用8. 今日学习感悟Day11 搞定指针和预处理有种“终于看透 C 语言底层”的感觉。以前觉得指针是个神秘的东西什么*p、a看得头晕。今天画了个“门牌号”的比喻之后突然就通了——指针无非就是记下变量住在哪里方便我直接上门改东西。预处理也不难就是编译前先做的文本替换#define加括号这件事今天是真的踩过坑才记住了。Day11 学习记录明天继续。