C语言指针详解
目录
指针是什么?
指针和指针类型
指针+-整数
指针的解引用
野指针
野指针成因
如何规避野指针
指针运算
指针+- 整数
指针-指针
指针的关系运算
指针和数组
二级指针
指针数组
指针数组
模拟二维数组
指针是什么?
指针理解的2个要点:
1. 指针是内存中一个最小单元的编号,也就是地址
2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
总结:指针就是地址,口语中说的指针通常指的是指针变量。
那我们就可以这样理解:
内存

指针变量
我们可以通过&(取地址操作符)取出变量的内存其实地址,把地址可以存放到一个变量中,这个 变量就是指针变量
#include <stdio.h>
int main()
{
int a = 10;//在内存中开辟一块空间
int *p = &a;
//这里我们对变量a,取出它的地址,可以使用&操作符。
//a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量 中,p就是一个之指针变量。
return 0;
}
int a = 10; //是向内存中的栈区空间申请4个字节的空间,这四个字节用来存放10这个数值
int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
//a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量中,p就是一个指针变量。
总结:
指针变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)。
那这里的问题是:
一个小的单元到底是多大? (1个字节)
如何编址? 经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。
对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0);
那么32根地址线产生的地址就会是:
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
...
11111111 11111111 11111111 11111111
这里就有2的32次方个地址。
每个地址标识一个字节,那我们就可以给
(2^32Byte == 2^32/1024KB==2^32/1024/1024MB==2^32/1024/1024/1024GB == 4GB)
4G的空闲进行编址。
同样的方法,那64位机器,如果给64根地址线,那能编址多大空间,自己计算。
这里我们就明白:
在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节。
那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。
如图32位系统下 各个类型的指针都是4个字节
如图64位系统下 各个类型的指针都是8个字节
总结:
内存被划分成一个个内存单元,每个内存单元的大小是1个字节
每个字节的内存单元都有一个编号,这个编号就是地址,地址在C语言中是指针
地址要存储的话,存放在指针变量中
每个内存单元都有一个唯一的指针来标识
指针是用来存放地址的,地址是唯一标示一块地址空间的。
指针的大小在32位平台是4个字节,在64位平台是8个字节。
指针和指针类型
这里我们在讨论一下:指针的类型
我们都知道,变量有不同的类型,整形,浮点型等。
那指针有没有类型呢? 准确的说:有的。
当有这样的代码:
int num = 10;
p = #
要将&num(num的地址)保存到p中,我们知道p就是一个指针变量,那它的类型是怎样的呢? 我们给指针变量相应的类型。
char *pc = NULL;
int *pi = NULL;
short *ps = NULL;
long *pl = NULL;
float *pf = NULL;
double *pd = NULL;
这里可以看到,指针的定义方式是: type + *
其实:
char* 类型的指针是为了存放 char 类型变量的地址。
short* 类型的指针是为了存放 short 类型变量的地址。
int* 类型的指针是为了存放 int 类型变量的地址。
那指针类型的意义是什么?
指针+-整数
#include <stdio.h>
//演示实例 int main() {int n = 10;char *pc = (char*)&n;int *pi = &n;printf("%p\n", &n);printf("%p\n", pc);printf("%p\n", pc+1);printf("%p\n", pi);printf("%p\n", pi+1);return 0;
}
总结:指针的类型决定了指针向前和向后走一步有多大
int* 的指针+1,跳过4个字节
char* 的指针+1,跳过1个字节
short* 的指针+1,跳过2个字节
double* 的指针+1,跳过8个字节
指针的解引用
//演示实例
#include <stdio.h>
int main() {
int n = 0x11223344;
char *pc = (char *)&n;
int *pi = &n;
*pc = 0; //重点在调试的过程中观察内存的变化。
*pi = 0; //重点在调试的过程中观察内存的变化。return 0;
}
指针类型决定了指针进行解引用操作的时候,访问几个字节
char 1个字节
short 2个字节
int 4个字节
总结:
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。
野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
野指针成因
指针未初始化
#include <stdio.h>
int main() {
int *p;//局部变量指针未初始化,默认为随机值 *p = 20;
return 0;
}
指针越界访问
#include <stdio.h>
int main()
{int arr[10] = {0};int *p = arr;int i = 0;for(i=0; i<=11; i++){//当指针指向的范围超出数组arr的范围时,p就是野指针*(p++) = -1;}
return 0;
}
指针指向的空间释放

这里放在动态内存开辟的时候详细说
如何规避野指针
指针初始化 ——如果明确指针应该指向哪里,就初始化正确的地址
int a =10;
int*p = &a;
——如果指针不知道初始化什么值,为了安全,初始化NULL
int*p = NULL;
小心指针越界
指针指向空间释放即使置NULL
避免返回局部变量的地址
指针使用之前检查有效性
初始化指针:
在定义指针变量时,立即将其初始化为 NULL 或有效的内存地址,这样可以避免它成为野指针。
避免未初始化的指针:
在使用指针之前,确保为其分配了有效的内存空间。
可以使用动态内存分配函数(如 malloc、calloc 等)或者将指针指向有效的对象或数组。
避免释放后仍然使用指针:
在释放了指针所指向的内存后,不要再对其进行访问或引用。可以在释放后将指针设置为 NULL,以避免意外使用。
合理使用指针:
确保在使用指针之前检查其是否为 NULL。可以使用条件语句(如 if)或者断言来验证指针的有效性。
避免悬挂指针:
当一个指针指向已经释放的内存时,将其设置为 NULL,以避免成为悬挂指针。
谨慎使用指针操作:
在进行指针操作时,要确保操作的对象是有效的,并且不会越界访问。
#include <stdio.h>
int main(){int *p = NULL;//....int a = 10;p = &a;if(p != NULL){
*p = 20; }
return 0;
}
指针运算
指针+- 整数
指针-指针
指针的关系运算
指针+- 整数
//arr --> p
//arr == p
//arr+i == p+i
//*(arr+i) ==*(p+i) == arr[i]
//*(arr+i)== arr[i]
//*(i+arr)== i[arr] ——方块仅仅只是操作符
//p指向的是数组首元素
//p+i是数组中下标为i的元素的地址
//p+i起始时跳过了i*sizeof(int)个字符
#define N_VALUES 5
float values[N_VALUES];
float *vp;
//指针+-整数;指针的关系运算
for (vp = &values[0]; vp < &values[N_VALUES];)
{*vp++ = 0;
}
指针加减整数是指对指针进行偏移操作。当一个指针与一个整数相加或相减时,编译器会根据指针所指向的数据类型来计算偏移量,然后将指针移动到相应位置。
例如,假设有一个指向整型数据的指针ptr,可以使用以下方式进行偏移操作:
ptr + n:将指针ptr向后移动n个单位,每个单位的大小由指针所指向的数据类型决定。
ptr - n:将指针ptr向前移动n个单位。
指针-指针
指针与指针之间的减法运算用于计算两个指针之间的偏移量。如果有两个指针ptr1和ptr2,它们指向同一数组或同一内存块的不同位置,可以使用以下方式计算它们之间的偏移量:
ptr2 - ptr1:计算ptr2相对于ptr1的偏移量,结果将以单位为指针所指向的数据类型的大小表示。指针的关系运算包括大于(>)、小于(<)、大于等于(>=)、小于等于(<=)、等于(==)和不等于(!=)等比较运算符。这些运算符用于比较两个指针的值,比较结果基于指针所指向的内存地址。
//指针减去指针的前提:两个指针指向同一块区域,指针类型相同的
// 指针减去指针差值的绝对值:得到的是指针之间的元素个数

模拟strlen
1.计数器
2.递归
int my_strlen(char *s)
{char *p = s;while(*p != '\0' ) //八进制的0p++;
}
return p-s;
指针的关系运算
for(vp = &values[N_VALUES]; vp > &values[0];)
{*--vp = 0;
}
代码简化, 这将代码修改如下:
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{*vp = 0;
}
实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证 它可行。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与 指向第一个元素之前的那个内存位置的指针进行比较。
指针和数组
指针就是指针 (指针变量,存放地址,指针变量的大小是4/8个字节)
数组就是数组 (存放一组数,数组的大小是取决于元素的类型和个数)
数组的数组名是数组首元素的地址,地址是可以访问指针变量中
我们看一个例子:
#include <stdio.h>
int main() {int arr[10] = {1,2,3,4,5,6,7,8,9,0};printf("%p\n", arr);printf("%p\n", &arr[0]);return 0;
}
运行结果:

可见数组名和数组首元素的地址是一样的。
结论:数组名表示的是数组首元素的地址。(2种情况除外,数组章节讲解了)
sizeof(数组名),数组名单独放在sizeof内部,数组名表示整个数组,计算的是数组的大小,单位是字节
&数组名,数组名表示整个数组,取出的是数组的地址,数组的地址和数组首元素的地址,值是一样的,但是类型和意义是不一样的。
那么这样写代码是可行的
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr;//p存放的是数组首元素的地址

注:前两个(arr//&arr[0])表示是数组首元素 +1 跳过4个字节
&arr表示数组 +1 跳过40个字节
既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问一个就成为可能。
例如:
#include <stdio.h>
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,0}; int *p = arr; //指针存放数组首元素的地址
int sz = sizeof(arr)/sizeof(arr[0]);
for(i=0; i<sz; i++)
{printf("&arr[%d] = %p <====> p+%d = %p\n", i, &arr[i], i, p+i);
return 0;
}
}

所以 p+i 其实计算的是数组 arr 下标为i的地址。
那我们就可以直接通过指针来访问数组。
如下:
int main() {
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
int *p = arr; //指针存放数组首元素的地址
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;for (i = 0; i<sz; i++){printf("%d ", *(p + i));}
return 0; }
二级指针
二级指针是指一个指针变量的值是另一个指针变量的地址,也就是说这个指针变量存储的是另一个指针变量的地址。在C语言中,可以通过使用二级指针来传递指针的地址,以便在函数内部修改指针的值。
具体来说,我们先来看一下一级指针的定义和使用。一级指针是最常见的指针,它存储了一个对象的地址。通过解引用操作符(*),我们可以获取到该地址所对应的对象的值。
而二级指针则更进一步,它存储了一个一级指针变量的地址。通过解引用操作符(**),我们可以获取到该一级指针所指向的对象的值。
为了更好地理解二级指针,我们可以举个例子。
假设我们有一个整型变量x,并且有两个指针p1和p2,其中p1指向x的地址,p2指向p1的地址。这样,p2就成为了一个二级指针。
下面是一个简单的示例代码,演示了如何声明和使用二级指针:
#include <stdio.h>int main() {int x = 10;int* p1 = &x;int** p2 = &p1;printf("x = %d\n", x);printf("*p1 = %d\n", *p1);printf("**p2 = %d\n", **p2);return 0;
}
在上面的代码中,我们首先声明了一个整型变量x,并初始化为10。然后声明了一个一级指针p1,并将其指向x的地址。接着声明了一个二级指针p2,并将其指向p1的地址。
在打印输出部分,我们使用解引用操作符()来获取到指针所指向的对象的值。可以看到,通过p1我们可以获取到x的值,通过**p2我们也可以获取到x的值。
二级指针在某些情况下非常有用,例如在函数中传递指针的地址,以便在函数内部修改指针的值。它也可以用于动态分配二维数组等复杂数据结构。

*ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa .
int b = 20;
*ppa = &b;//等价于 pa = &b;
**ppa先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作: *pa ,那找到的是 a .
**ppa = 30; //等价于*pa = 30; //等价于a = 30;
指针数组
指针数组
指针数组是指针还是数组?
答案:是数组。是存放指针的数组。
数组我们已经知道整形数组,字符数组。
int arr1[5]; //整型数组
char arr2[6]; //字符数组

那指针数组是怎样的?
int* arr3[5];//是什么?
arr3是一个数组,有五个元素,每个元素是一个整形指针。

模拟二维数组

i 遍历指针数组arr // j 遍历数组arr1/2/3
模拟出二维数组的效果
相关文章:
C语言指针详解
目录 指针是什么? 指针和指针类型 指针-整数 指针的解引用 野指针 野指针成因 如何规避野指针 指针运算 指针- 整数 指针-指针 指针的关系运算 指针和数组 二级指针 指针数组 指针数组 模拟二维数组 指针是什么? 指针理解的2个要点: 1. 指针是内存中一个…...
RTPS规范v2.5(中文版)
实时发布订阅协议 DDS互操作性有线协议 (DDSI-RTPS) 技术规范 V2.5 (2022-04-01正式发布) https://www.omg.org/spec/DDSI-RTPS/2.5/PDF 目 录 1 范围 8 2 一致性 8 3 规范性参考文献 8 4 术语和定义 9 5 标识 …...
LeetCode102.Binary-Tree-Level-Order-Traversal<二叉树的层序遍历>
题目: 思路: 写过N叉树的层序遍历,(8条消息) LeetCode429.N-Ary-Tree-Level-Order-Traversal<N 叉树的层序遍历>_Eminste的博客-CSDN博客 使用栈保存每一层的结点。然后每次当前层结束。将这一层的值添加进去res中。…...
yolov8系列[五]-项目实战-yolov8模型无人机检测
yolov8系列[五]-项目实战-yolov8模型无人机检测 项目介绍项目展示功能简介代码结构如何启动 开发者模式1. 安装依赖环境2. 启动程序 源代码下载其他 项目介绍 无人机识别项目,无人机搭载nvidia jetson边缘计算板子,进行实时识别。使用yolov8算法,训练了识别无人机的…...
Redis 笔记,基本数据类型、持久化、主从、集群等等问题
标题 😀😀😀创作不易,各位看官点赞收藏. 文章目录 标题Redis 基础笔记1、安装及环境搭建2、Redis 数据类型2.1、String2.2、List2.3、Hash2.4、Set2.5、Zset2.6、BitMap2.7、HyperLogLog2.8、Geospatial2.9、Stream 3、Redis 持久…...
JDK,JRE,JVM三者的关系
JDK(全称 Java Development Kit),Java开发工具包,能独立创建、编译、运行程序。 JDK JRE java开发工具(javac.exe/java.exe/jar.exe) JRE(全称 Java Runtime Environment),能运行…...
行为型-命令模式(Command Pattern)
说明 命令模式(Command Pattern)是一种行为设计模式,它将请求封装为一个对象,以便在不同的请求者和接收者之间进行解耦、参数化和操作的队列化。命令模式允许你将具体的请求封装为对象,这些对象之间彼此独立ÿ…...
总结942
5:40起床 6:00~7:00单词复习300个,记100个 7:15~8:00早读,《love is as strong as death》第一第二段 8:10~9:10三大计算回顾 9:15~10:06 习题880第一章基础选择纠错 10:10~10:30单词默写 10:30~11:40强化第一讲习题 11:40~12:30继续…...
MFC自定义控件使用
用VS2005新建一个MFC项目,添加一个Custom Control控件在窗体 我们需要为自定义控件添加一个类。项目,添加类,MFC类 设置类名字,基类为CWnd,你也可以选择CDialog作为基类 类创建完成后,在它的构造函数中注册一个新的自定义窗体,取名为"MyWindowClass" WNDCL…...
【学习笔记】「ROI 2018 Day 2」无进位加法
先放一个大佬的博客:「loj - 2850」「ROI 2018 Day 2」无进位加法 用数据结构来优化搜索🤔 神一样的 Kidulthood 考场上就已经意识到了这道题的正解是搜索😅 考虑搜索过程的本质🤔 首先是找到最小的满足 t i i t_ii tii最大…...
分布式I/O,IT和OT融合少不了它
长期以来信息技术IT和操作运营技术OT是相互隔离的,随着大数据分析和边缘计算业务的对现场级实时数据的采集需求,IT和OT有了逐渐融合的趋势。IT与OT融合,它赋予工厂的管理者监控运行和过程的能力大为增强,甚至可以预测到可能发生的…...
主干网络篇 | YOLOv8 更换主干网络之 VanillaNet |《华为方舟实验室最新成果》
论文地址:https://arxiv.org/pdf/2305.12972.pdf 代码地址:https://github.com/huawei-noah/VanillaNet 在基础模型的核心是“多样性即不同”,这一哲学在计算机视觉和自然语言处理方面取得了惊人的成功。然而,优化和Transformer模型固有的复杂性带来了挑战,需要转向简洁性…...
AD20. 如何给元器件设计、添加3D模型
Altium Designer学习笔记 - 00.目录 零. 前言 本文以HF46F继电器为例展示设计、添加元器件3D模型的流程,其他元器件类似。 一. 操作步骤 从下图可以看到此时继电器还没有添加3D模型: 1. 获取元器件尺寸 这里通过查找元器件的数据手册可以…...
C++笔记之vector的底层实现和扩容机制
C笔记之vector的底层实现和扩容机制 1. 先申请内存空间,内存空间容量变成原来的n倍(一般是原来的两倍) 2. 将原本容器中的数据拷贝到新的内存空间中 3. 释放原来的内存空间 4. 将数组指针指向新容器的内存空间 code review! 文章目录 C笔记之vector的底层实现和扩…...
JavaSE - Sting类
目录 一. 字符串的定义 二. String类中的常用方法 1. 比较两个字符串是否相等(返回值是boolean类型) 2. 比较两个字符串的大小(返回值是int类型) 3. 字符串查找 (1)s1.charAt(index) index:下标&…...
zotero+overleaf插入参考文献
zotero导出参考献bib文件 overleaf上传此biib文件 后续添加package,输出参考文献,添加引用参考http://t.csdn.cn/bC245 默认导出的bib文件信息臃肿,使用插件设置,安装过程参考http://t.csdn.cn/4HcBm…...
C语言每天一练----输出水仙花数
题目:请输出所有的"水仙花数" 题解:所谓"水仙花数"是指一个3位数,其各位数字立方和等于该数本身。 例如, 153是水仙花数, 因为153 1 * 1 * 1 5 * 5 * 5 3 * 3 * 3" #define _CRT_SECURE_NO_WARNINGS 1#include <stdio.h&g…...
Linux-Shell
1.什么是Bash shell(壳) Bash Shell是一个命令解释器,它在操作系统的最外层,负责用户程序与内核进行交互操作的一种接口,将用户输入的命令翻译给操作系统,并将处理后的结果输出至屏幕。 通过xshell连接,就是打开了一…...
Python读取csv、Excel文件生成图表
简介 本文章介绍了通过读取 csv 或 Excel 文件内容,将其转换为折线图或柱状图的方法,并写入 html 文件中。 目录 1. 读取CSV文件 1.1. 生成折线图 1.1.1. 简单生成图表 1.1.2. 设置折线图格式 1.2. 生成柱状图 1.2.1. 简单生成图表 1.2.2. 设置柱…...
虚拟机中Linux的IP地址配置详解
目录 第一章、虚拟机中Linux的IP地址配置详解1.1)什么是IP地址1.2)如何查看自己电脑ip地址1.3)虚拟机NAT模式中Linux的IP地址设置有什么要求 第二章、使用Linux中的编辑命令进行网卡信息文件的配置 友情提醒 先看文章目录,大致了…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...
HTML 列表、表格、表单
1 列表标签 作用:布局内容排列整齐的区域 列表分类:无序列表、有序列表、定义列表。 例如: 1.1 无序列表 标签:ul 嵌套 li,ul是无序列表,li是列表条目。 注意事项: ul 标签里面只能包裹 li…...
django filter 统计数量 按属性去重
在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...
GitHub 趋势日报 (2025年06月08日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...
(转)什么是DockerCompose?它有什么作用?
一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器。 Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...
Caliper 配置文件解析:config.yaml
Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...
vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...
Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理
引言 Bitmap(位图)是Android应用内存占用的“头号杀手”。一张1080P(1920x1080)的图片以ARGB_8888格式加载时,内存占用高达8MB(192010804字节)。据统计,超过60%的应用OOM崩溃与Bitm…...
JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案
JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停 1. 安全点(Safepoint)阻塞 现象:JVM暂停但无GC日志,日志显示No GCs detected。原因:JVM等待所有线程进入安全点(如…...
