news 2026/9/28 11:09:19

11.初级指针

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
11.初级指针

第一章 数据在计算机内存中真实的存储形式

1.1 变量存储的底层逻辑

我们写代码定义变量,本质就是向操作系统申请一块内存空间,给这块空间起一个变量名,再把我们写的十进制数字放进内存里。 计算机只能识别 0 和 1 组成的二进制,我们写的十进制数字,必须先转为二进制格式,才能存入内存。

十进制转二进制通用方法:除 2 取余法

  1. 数字不停除以 2,每一次运算记录下余数(余数只有 0 或者 1)
  2. 一直计算,直到除法得到的结果(商)等于 0 就停止计算
  3. 把所有记录的余数,从最后一个写到第一个,拼接起来就是对应二进制数

日常计算嫌麻烦,可以打开电脑自带计算器,切换成程序员模式,直接完成二进制、八进制、十进制、十六进制互相转换。

举例:十进制数字 10,对应二进制是1010int类型固定占用 4 字节内存,1 字节 = 8 个二进制位(bit),4 字节就是 32bit。需要把二进制补齐 32 位存放,最终内存存放格式:00000000 00000000 00000000 00001010

long long类型固定占用 8 字节(64bit),数字 123456 补齐 64 位二进制后存入内存。

1.2 数组在内存的存储规则

数组是一片连续无空隙的内存空间,相同类型的元素挨个排列存放。 示例:int arr [3];数组一共 3 个 int 元素,单个 int 占 4 字节,总占用内存:3 × 4 = 12 字节 逐个赋值:arr [0]=1,arr [1]=2,arr [2]=3

  • arr [0] 占用:第 1~4 字节
  • arr [1] 占用:第 5~8 字节
  • arr [2] 占用:第 9~12 字节

每个 int 数值都会补齐 32 位二进制,对应写入自己所属的 4 字节空间。

1.3 内存查看为什么显示十六进制,不直接显示二进制

1 个十六进制字符刚好对应 4 位二进制,两个十六进制字符就能完整代表 1 字节内存。 二进制字符串太长,人工查看、对比、查找地址极易出错,十六进制写法更简洁,所以调试内存时统一展示十六进制数据。

例子:二进制00001111 00010101 10101100 11110000,简写为十六进制:0F15ACF0

1.4 VS 软件断点查看内存的实操步骤

  1. 在代码行左侧灰色区域点击,出现红色圆点代表打上断点;程序运行到断点位置会暂停,方便观察静态内存数据
  2. 顶部菜单栏选择【调试】→【窗口】→【内存】,打开任意内存窗口
  3. 在内存地址输入框填写&变量名,按下回车,就能看到该变量内存原始的十六进制数据

例:int a=10,断点查看内存,4 字节整体数据为00 00 00 0A

1.5 进制快速对照表(0~15)

表格

十进制数值补齐 4 位二进制对应十六进制
000000
100011
200102
300113
401004
501015
601106
701117
810008
910019
101010A
111011B
121100C
131101D
141110E
151111F

第二章 大小端存储(内存字节排布规则)

2.1 大小端出现的现象

我们手动换算的数值十六进制,和 VS 断点查到的内存字节顺序经常是颠倒的,Windows 系统默认使用小端存储模式。 大端模式多用在网络传输、单片机、ARM 架构设备中。

2.2 大小端标准定义

我们以数值int num = 0x12345678举例,4 字节拆分:高字节0x12、次高字节0x34、次低字节0x56、低字节0x78

✅小端存储规则:低字节放在内存低地址,高字节放在内存高地址(内存里字节顺序倒着放) 假设变量起始地址为0x00010000

表格

内存地址存放字节字节属性
0x00010000(最低地址)78低字节
0x0001000156次低字节
0x0001000234次高字节
0x00010003(最高地址)12高字节

内存窗口展示顺序:78 56 34 12

✅大端存储规则:高字节放在内存低地址,低字节放在内存高地址(字节顺序和我们书写数字完全一致)

表格

内存地址存放字节字节属性
0x00010000(最低地址)12高字节
0x0001000134次高字节
0x0001000256次低字节
0x00010003(最高地址)78低字节

内存窗口展示顺序:12 34 56 78

2.3 CPU 架构和指针大小关系

X86(32 位系统)、X64(64 位系统):英特尔、AMD 电脑 CPU 通用架构,Windows、Linux 系统都是小端存储 手机骁龙、麒麟芯片属于 ARM 架构

架构对指针的唯一影响:X86 环境下指针固定占 4 字节,X64 环境下指针固定占 8 字节,本讲义全部以 X86(32 位)讲解。

2.4 代码自测本机是大端还是小端(可直接复制运行)

#include <stdio.h> void check_endian() { int num = 0x1234; char *p = (char*)&num; //强制转换,截取最低1字节查看 if(*p == 0x34) printf("当前电脑为小端存储\n"); else printf("当前电脑为大端存储\n"); } int main() { check_endian(); return 0; }

第三章 地址、取地址符 & 与指针基础入门

3.1 什么是内存地址

计算机给每一个字节的内存,都分配了独一无二的编号,这个编号就叫做内存地址(用十六进制展示)。 一块连续的内存,地址编号从小到大依次递增;只要拿到一块内存第一个字节的地址,结合数据类型占用的字节数量,就能精准锁定整片内存。

定义int a=10;,变量 a 占用连续 4 个字节,四个字节各自都有独立地址,&a获取的是这 4 个字节里第一个字节的地址编号。 格式示例(0x代表后面数字是十六进制):

0x00000001 → 存放 0A 0x00000002 → 存放 00 0x00000003 → 存放 00 0x00000004 → 存放 00

3.2 取地址符 & 用法

书写格式:& + 变量名作用:取出变量对应内存首字节的地址编号 打印地址需要使用格式化符号%p

3.3 指针的本质

指针本质就是一块独立的内存空间,专门用来存放其他变量的内存首地址。 指针书写格式:数据类型 + * + 指针名(一般习惯用 p 命名指针) 指针自身也占用内存:X86 为 4 字节,X64 为 8 字节

基础代码示例

int a = 11; int* p = &a; printf("a的地址:%p\n", &a); printf("指针p存储的地址:%p\n", p);

两段打印结果完全一致,&a的类型本身就是int*。 指针 p 自己有专属的内存地址,p 的内存里保存着变量 a 的首地址数据,数据存放同样遵循小端规则。

3.4 二级指针(指针的指针)

一级指针存放普通变量的地址,二级指针存放一级指针变量自身的地址。

int a = 10; int *p = &a; //一级指针p,存储变量a的地址 int **pp = &p; //二级指针pp,存储一级指针p自身的地址

第四章 解引用符号 * 与指针实际应用

4.1 解引用 * 的底层规则

格式:*指针变量名作用:顺着指针保存的地址,向后读取【指针对应类型大小】的字节数据,转换成对应类型的值;也可以通过地址修改原始内存里的数据。

4.2 解引用两大核心用法

读取原始变量的数据

int a = 10; int* p = &a; printf("%d", *p); //顺着地址读取a内存的数据,输出10 //只打印p,只能看到一串十六进制地址,拿不到真实数值

修改原始变量内存数据(地址传递的核心原理)

int a = 10; int* p = &a; *p = 999; //通过地址直接覆盖a所在内存的值 printf("%d", a); //输出结果999

原理:函数内部使用指针解引用,操作的是变量原始内存,所以可以修改外部变量的值。

4.3 不同类型指针作用对照表

表格

指针类型写法指向内容X86 下解引用读取字节数日常使用场景
字符指针char *pchar 变量、字符数组1 字节查看原始内存字节、判断大小端
短整型指针short *pshort 变量2 字节操作短整数数据
整型指针int *pint 变量4 字节数组遍历、函数地址传参(最常用)
长长整型指针long long *plong long 变量8 字节存储超大数值
单精度浮点指针float *pfloat 浮点数据4 字节处理小数
双精度浮点指针double *pdouble 高精度小数8 字节高精度浮点运算
无类型指针void *p任意类型地址不能直接解引用通用地址容器、万能函数参数

补充:void* 可以保存任意类型地址,但不能直接解引用,也不能直接做指针加减运算;使用前必须强制类型转换。

第五章 const 修饰指针(笔试必考)+ 指针运算规则

5.1 const 修饰指针三种写法区分(口诀:const 修饰谁,谁就不能修改)

  1. const int *p等价int const *p限制:不能通过*p修改指针指向的数据,但是指针 p 可以更换指向别的变量 使用场景:函数接收数组 / 变量,只读取数据,禁止修改原始内容
#include <stdio.h> int main() { int a = 10, b = 20; const int *p = &a; //*p = 99; 编译报错,无法修改数据 p = &b; //合法,指针可以更换指向 printf("%d", *p); return 0; }
  1. int *const p限制:指针初始化绑定地址后,永远不能修改指向;但是可以用*p修改指向的数据 使用场景:固定指针指向一块内存,防止代码误改动指针地址
int main() { int a = 10; int *const p = &a; *p = 99; //合法,修改数据没问题 //p = NULL; 编译报错,指针地址被锁定 printf("%d", a); return 0; }
  1. const int *const p限制:指针指向不能改、指向的数据也不能改,双向只读 使用场景:读取常量字符串这类只读内存数据
int main() { int a = 10; const int *const p = &a; //*p = 99; 报错 //p = NULL; 报错 return 0; }

5.2 指针合法运算与绝对禁忌

✅合法运算

  1. 指针 ± 整数:p+n、p-n,实际移动字节 = 数字 × 指针指向类型的字节大小(数组遍历的底层原理)

⚠️注意:只能在同一块连续内存(数组)内加减,越界访问属于未定义行为。

  1. 相同类型的指针可以相减:两个指针相减,得到两个地址中间相隔的元素个数,结果为整型数字
int arr[10] = {1,2,3,4,5}; int *p1 = &arr[0]; int *p2 = &arr[4]; printf("%d", p2 - p1); //输出4,中间间隔4个int元素
  1. 指针可以使用>、<、==、!=做比较运算,判断地址高低

❌绝对禁止的运算指针和指针相加、指针相乘、指针相除,地址只是一串编号,这类运算没有任何实际意义,编译器会直接报错。

第六章 空指针、野指针、悬空指针(新手最常踩坑 BUG)

把指针理解成记录房间门牌号的小本子

  1. 空指针 NULL:主动给指针赋值 NULL,NULL 本质是系统保护的 0 号无效地址。 规则:0 地址系统禁止读写数据,不会随意篡改内存;使用指针前先判断if(p != NULL)再操作,是最安全的指针初始化方式。
  2. 野指针:定义指针的时候,没有手动赋值,指针内存里自动填充了随机杂乱的内存地址。 危害:程序莫名闪退、隐性 bug 极难排查;解决方案:所有指针定义立刻赋值 NULL 清零。
int *p; //未初始化,野指针 p = NULL; //修复野指针问题
  1. 悬空指针:指针原本保存了合法内存地址,但是这块内存被销毁 / 释放了,指针本子上还留着失效的门牌号。 产生场景:函数运行结束,函数内部局部变量自动销毁;malloc 申请的内存调用 free 释放。

⚠️局部变量函数结束后内存直接回收,这块内存随时会被覆盖,读取结果不确定(未定义行为) 危害:读取到随机垃圾数据、程序崩溃;解决方案:内存销毁 / 释放后,马上执行p = NULL。

int* getAddr() { int a = 10; //函数执行完毕,a的内存直接销毁 return &a; } int main() { int *p = getAddr(); //p变成悬空指针 printf("%d",*p); //读取垃圾值,运行不稳定 return 0; }

三类指针极简区分

  • 空指针:人为主动设置无效地址,全程可控、安全
  • 野指针:忘记初始化产生随机地址,被动生成,高危错误
  • 悬空指针:地址曾经合法,内存销毁后地址失效,属于代码书写疏漏

第七章 值传递与地址传递(指针最核心使用价值)

7.1 值传递

传递的是变量真实的数值,函数内部会复制一份一模一样的数据运行,函数修改的只是副本数据,不会影响外部原始变量。 案例:交换数字写法错误示范

void swap_val(int x, int y) { int tmp = x; x = y; y = tmp; } int main() { int a=1,b=2; swap_val(a,b); printf("a=%d,b=%d",a,b); //输出a=1 b=2,数值不会交换 return 0; }

类比:把作文复印一份交给老师修改,复印件的改动不会修改你手里的原稿。

7.2 地址传递

传递的是变量的内存地址,函数内部通过解引用直接操作原始内存,外部变量的数据会被同步修改。 案例:正确的数字交换写法

void swap_addr(int *x, int *y) { int tmp = *x; *x = *y; *y = tmp; } int main() { int a=1,b=2; swap_addr(&a,&b); printf("a=%d,b=%d",a,b); //输出a=2 b=1,交换成功 return 0; }

类比:直接把作业本的家庭地址给老师,老师上门修改原本的作业本,原稿直接被改动。

总结

值传递:复制数据 → 修改副本 → 外部数据不变 地址传递:传递地址 → 操作原内存 → 外部数据被修改

第八章 数组和指针的底层关系、数组名三大特性

8.1 数组名 ≠ 普通指针变量(最容易混淆的知识点)

数组名是地址常量数组对应的内存位置固定不变,数组名只是这片连续内存的别名标签,不能被赋值、不能自增自减

int arr[5] = {1,2,3,4,5}; //arr = NULL; 编译报错,常量无法修改 //arr++; 编译报错

普通指针是地址变量指针自己独占一块内存存储地址数值,可以随意修改指向、自增自减

int *p = arr; p = NULL; //合法 p++; //合法

核心两点区分 ①sizeof(数组名):计算整个数组全部的总字节;sizeof(指针)固定 4/8 字节,只算指针本身大小 ② 数组名不能赋值、自增;指针变量完全支持修改指向、自增运算

8.2 数组名的三种状态(重中之重,所有数组计算题的核心)

定义通用格式:int arr [N];

  1. 数组名放在sizeof()内部:数组保留完整数组类型,sizeof计算数组整体总字节数 例:char arr [100],sizeof(arr)结果 = 100 字节
  2. 数组名前面添加&取地址:获取整个数组的整体地址,类型变为数组指针int(*)[N]
  3. 其余所有普通场景:数组名自动退化,等价于数组首元素的地址&arr[0],类型转为普通一级指针int*;arr+1只会跳过 1 个 int 大小(4 字节)

补充知识点:数组下标[]底层本质 编译器不存在[]下标运算符,所有arr[i]都会被编译为*(arr+i);加法满足交换律,*(arr+i)等价*(i+arr),所以写法i[arr]语法合法。

⚠️日常开发只用arr[i]书写即可,i[arr]只用来理解底层原理,工程禁止使用。

int arr[5] = {10,20,30}; printf("%d %d", arr[1], 1[arr]); //两处输出都是20

8.3 数组当做函数参数传递:数组退化 + 长度丢失

函数形参书写int arr[]和int *arr完全等价,编译器全部识别为指针 数组传入函数后,数组会退化为普通指针,函数内部使用sizeof(arr)只能拿到指针大小(4 字节),无法计算原始数组真实长度

标准解决方案:调用函数时,把数组长度作为第二个参数手动传入

void test(int arr[], int len) { printf("%d\n", sizeof(arr)); //固定输出4 for(int i = 0; i < len; i++) { printf("%d ", arr[i]); } } int main() { int arr[5] = {1,2,3,4,5}; //在main函数内部计算真实数组长度 int len = sizeof(arr)/sizeof(arr[0]); test(arr, len); return 0; }

8.4 指针数组 和 数组指针 区分方法

优先级规则:括号()> 方括号[]> 星号*

  • 指针数组:本质是数组,数组里面存放的元素全是指针;*紧贴[]示例:int* arr [10]
  • 数组指针:本质是指针,专门用来存放一整个数组的地址;被括号包裹,*紧贴变量名 示例:int (*p)[10]

第九章 配套习题 + 逐题解析(一维数组→二维数组→指针数组)

9.1 一维数组 sizeof、地址偏移计算题(习题 1~ 习题 3)

习题 1:long long arr [10] = {1,2,3,4,5,6,7,8,9,10};习题 2:char arr [5] = {97,98,99,100,101};习题 3:char arr [16] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};

解析统一逻辑:

  • sizeof(arr)→ 数组整体字节 = 单个元素字节 × 元素个数
  • sizeof(&arr)→ 数组指针,固定 4 字节(X86)
  • sizeof(*arr)→ 解引用后为数组第一个元素,等于对应数据类型本身字节
  • arr+1:按照元素类型字节偏移地址;&arr+1:一次性跳过整个数组的总字节
  • *arr获取的是数组第一个元素真实数值,数值做 + 1 就是普通数字加法,不再是指针运算
  • 不同类型指针接收同一数组首地址,指针 + N 的偏移字节 = N × 指针对应类型字节大小,结合小端存储读取对应数值

9.2 二维数组相关计算习题(习题 5、习题 6、习题 7)

二维数组本质:数组里面的每一个元素,又是一个一维数组 二维数组名 arr 普通场景退化:类型为int(*)[列数],arr+1会跳过一整行所有元素的总字节&arr代表整个二维数组的地址,&arr+1直接跳过整个二维数组全部字节 多级解引用**arr,逐层抵消指针,最终拿到数组第一个真实数据

习题 7 强转指针计算核心:强制转换只会改变地址的解析方式,不会修改地址本身的数值,结合ptr-1反向寻址取值

9.3 指针数组、多维指针数组计算(习题 8、习题 9)

指针数组内部每个元素都是指针,单个指针大小固定 4 字节 多层数组嵌套,逐层解引用,每一层*都会抵消一层数组 / 指针类型sizeof计算规则:只要被sizeof包裹,数组就不会退化;其余表达式环境数组正常退化&和*配对书写(&arr、&*arr),地址本身不会发生任何变化,只修改数据解析类型

9.4 数组指针综合小练习

int arr[10]; //普通一维int数组 int* brr[10]; //指针数组,数组内存放10个int类型指针,brr退化后类型为int** int(*p)[10]; //数组指针,专门接收&arr完整数组地址
  • p+1:一次性偏移 40 字节(10 个 int 总大小)
  • &arr [0]+1:只偏移 4 字节,单个 int 大小
  • &arr+1:一次性偏移 40 字节,整个数组总大小
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 11:04:51

layui 全屏功能实现:TaoToken 统一 Key 接入与配置验证

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

作者头像 李华
网站建设 2026/9/28 9:40:14

AI模块化架构:多Provider切换、RAG知识库与Agent编排实战

1. 这不是“又一个AI架构教程”&#xff0c;而是一套能落地的模块化设计实践我做AI工程化落地项目三年&#xff0c;从最早用LangChain硬写RAG流水线&#xff0c;到后来给制造业客户搭本地知识库系统&#xff0c;再到最近帮律所构建法律问答Agent&#xff0c;踩过的坑比读过的论…

作者头像 李华
网站建设 2026/9/28 9:39:24

中文医疗问答机器人微调实战:数据清洗、LoRA训练与部署

简介&#xff1a;一款面向医疗场景的大模型微调问答机器人应用&#xff0c;适合对AI大模型应用落地、自然语言处理感兴趣的开发者和研究者&#xff0c;尤其适合希望以完整项目为参照进行二次开发的进阶学习者。项目以中文医疗问答为核心&#xff0c;展示了从模型微调、Prompt设…

作者头像 李华
网站建设 2026/9/28 9:38:48

LSTM+注意力机制预测蛋白质-配体结合亲和力实战指南

简介&#xff1a;本资源是一套基于深度学习的蛋白质-配体结合亲和力预测完整实现方案&#xff0c;面向计算机、人工智能、生物信息学等相关专业的本科生与研究生&#xff0c;适用于毕业设计、课程设计及科研入门实践。项目采用LSTM网络建模序列特征&#xff0c;并融合自定义注意…

作者头像 李华
网站建设 2026/9/28 9:38:36

Python LSTM股市预测实战:从数据到模型完整方案

简介&#xff1a;这份资源面向计算机、金融数据方向的学生与开发者&#xff0c;提供一套基于Python与LSTM模型实现股市预测的完整项目&#xff0c;可作为高分课程设计、期末大作业的参考方案&#xff0c;也适合想入门深度学习时序预测的初学者练手。压缩包共89个文件&#xff0…

作者头像 李华