【C语言基础】:深入理解指针(一)
文章目录
- 一、内存和地址
- 1. 内存
- 2. 如何理解编址
- 二、指针变量和地址
- 2.1 取地址操作符(&)
- 2.2 指针变量和解引用操作符(*)
- 2.2.1 指针变量
- 2.2.2 如何拆解指针变量
- 2.2.3 解引用操作符
- 2.3 指针变量的大小
- 三、指针变量类型的意义
- 3.1 指针的解引用
- 3.2 指针 +- 整数
- 3.3 void*指针
- 四、const 修饰指针
- 4.1 const修饰变量
- 4.2 const修饰指针变量
一、内存和地址
1. 内存
内存是计算机中用于存储数据和程序的硬件设备,也被称为随机存取存储器(RAM)。内存具有较快的读写速度,用于临时存储当前正在执行的程序和数据。
在计算机系统中,内存扮演着至关重要的角色,它直接影响着计算机的性能和运行效率。以下是一些关于内存的基本信息:
- 作用:内存用于存储当前正在运行的程序和数据,包括操作系统、应用程序和用户数据等。当计算机启动时,操作系统会将需要的程序和数据加载到内存中,CPU 可以直接从内存中读取和写入数据,而不需要像从硬盘那样慢速地进行输入和输出操作。
- 易失性:内存是一种易失性存储设备,这意味着当计算机断电或重新启动时,内存中的数据将丢失。因此,重要的数据通常需要保存在持久性存储设备(如硬盘、固态硬盘)中。
- 访问速度:内存的访问速度通常比硬盘或固态硬盘快得多,这使得计算机能够更快地读取和写入数据,从而提高系统的性能和响应速度。
- 单位:内存容量通常以字节为单位进行衡量,常见的单位有千兆字节(Gigabyte,GB)和千兆字节(Terabyte,TB)等。
为了更方便理解,我们举一个生活中的小案例:
假设有⼀栋宿舍楼,把你放在楼里,楼上有100个房间,但是房间没有编号,你的⼀个朋友来找你玩,
如果想找到你,就得挨个房子去找,这样效率很低,但是我们如果根据楼层和楼层的房间的情况,给
每个房间编上号,如:
⼀楼:101,102,103...
⼆楼:201,202,203....
...
有了房间号,如果你的朋友得到房间号,就可以快速的找房间,找到你。
这里的房间号就相当于地址。
计算机上CPU( 中央处理器) 在处理数据的时候 ,需要的数据是在内存中读取的 ,处理后的 数据也会放回内存中 ,那我们买电脑的时候 , 电脑上内存是8GB/16GB/32GB等 ,那这些内存空间如何 高效的管理呢?
其实也是把内存划分为一个个的内存单元 ,每个内存单元的大小取1个字节。
补充:计算机中常见的单位
一个比特位可以存储一个二进制位的0或1。
bit (比特位)
1 Byte (字节) = 8 bit
1 KB = 1024 Byte
1 MB = 1024 KB
1 GB = 1024 MB
1 TB = 1024 GB
1 PB = 1024 TB
...
其中,每个内存单元,相当于一个学生宿舍,一个字节空间里面能放8个比特位,就好比同学们住的八人间,每个人是一个比特位。
每个内存单元也都有一个编号(这个编号就相当于宿舍房间的门牌号), 有了这个内存单元的编号,CPU就可以快速找到一个内存空间。
生活中我们把门牌号也叫地址,在计算机中我们把内存单元的编号也称为地址。C语言中给地址起了新的名字叫:指针。
也可以说:内存单元的编号 == 地址 == 指针
2. 如何理解编址
在计算机科学和计算机网络中,“编址”通常指的是为了标识和定位计算机或网络设备而给它们分配一个唯一的地址。这个地址可以用来在网络中准确定位设备,以便进行数据传输、通信或者其他操作。

首先,必须理解,计算机内是有很多的硬件单元,而硬件单元是要互相协同工作的。所谓的协同,至少相互之间要能够进行数据传递。
但是硬件与硬件之间是互相独立的,那么如何通信呢?答案很简单,用"线"连起来。而CPU和内存之间也是有大量的数据交互的,所以,两者必须也用线连起来。
我们可以简单理解,32位机器有32根地址总线,每根线只有两态,表示0,1【电脉冲有无】,那么⼀根线,就能表示2种含义,2根线就能表示4种含义,依次类推。32根地址线,就能表示2^32种含义,每⼀种含义都代表⼀个地址。地址信息被下达给内存,在内存上,就可以找到该地址对应的数据,将数据在通过数据总线传⼊CPU内寄存器。
二、指针变量和地址
2.1 取地址操作符(&)
在C语言中创建变量其实就是向内存申请空间,比如:
int main()
{int a = 10;return 0;
}

在上述代码中,我们创建了一个整型变量a,向内存中申请了4个字节,用来存放整数10,其中每个字节都有一个地址,上面4个字节的地址分别是:
0x0000004B0D2FF984
0x0000004B0D2FF985
0x0000004B0D2FF986
0x0000004B0D2FF987
这里我们用&取地址操作符
int main()
{int a = 10;&a; // 取a的地址printf("%p\n", &a);return 0;
}
按我们所学的知识可以知道,打印的结果是:0x0000004B0D2FF984

虽然整型变量占4个字节,但我们只要知道了第一个字节的地址,就可以顺藤摸瓜访问第4个字节的数据。
2.2 指针变量和解引用操作符(*)
2.2.1 指针变量
我们通过取地址操作符(&)拿到的地址是一个字符,比如:0x0000004B0D2FF984,这个数值我们可以用指针变量把这个数值存储在指针变量中。
int main()
{int a = 10;int* pa = &a; // 取出a的地址并存在指针变量pa中return 0;
}
指针变量也是一种变量,这种变量就是用来存放地址的,存放在指针变量中的值都会理解为地址。
2.2.2 如何拆解指针变量
我们看到pa的类型是int*,我们该如何理解指针的类型呢?
int a = 10;
int* pa = &a; // 取出a的地址并存在指针变量pa中
这里pa左边写的是int*, * 是在说明pa是指针变量,而前面的int是在说明pa指向的是整型(int)类型的对象。

2.2.3 解引用操作符
在C语言中我们只要拿到了地址(指针), 就可以通过地址找到地址指向的对象,这里必须学习一个操作符叫解引用操作符(*)。
#include <stdio.h>
int main()
{int a = 10;int* pa = &a; // 取出a的地址并存在指针变量pa中*pa = 0;printf("%d\n", a);return 0;
}
*pa 的意思就是通过pa中存放的地址,找到指向的空间, pa其实就是a变量了;所以pa = 0 ,这个操作符是把a改成了0。
2.3 指针变量的大小
前面的内容我们了解到,32位机器假设有32根地址总线,每根地址线出来的电信号转换成数字信号后是1或者0 ,那我们把32根地址线产生的2进制序列当做一个地址,那么一个地址就是32个bit位,需要4个字节才能存储。
如果指针变量是用来存放地址的 ,那么指针变的大小就得是4个字节的空间才可以。
同理64位机器,假设有64根地址线,一个地址就是64个二进制位组成的二进制序列,存储起来就需要 8个字节的空间,指针变量的大小就是8个字节。
#include <stdio.h>
int main()
{// 指针变量的大小取决于地址的大小// 32位平台下地址是32个bit位(即4个字节)// 64位平台下地址是64个bit位(即8个字节)printf("%zd\n", sizeof(int*));printf("%zd\n", sizeof(char*));printf("%zd\n", sizeof(short*));printf("%zd\n", sizeof(double*));return 0;
}
x86环境输出结果:

X64环境输出结果:

结论:
- 32位平台下地址是32个bit位,指针变量大小是4个字节
- 64位平台下地址是64个bit位,指针变量大小是8个字节
- 注意指针变量的大小和类型是无关的,只要指针类型的变量,在相同的平台下,大小都是相同的。
三、指针变量类型的意义
3.1 指针的解引用


调试我们可以看到,代码1会将n的4个字节全部改为0,但是代码2只是将n的第一个字节改为0。
结论:指针的类型决定了,对指针解引用的时候有多大的权限(一次能操作几个字节)。
比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。
3.2 指针 ± 整数
通过下面的代码观察地址的变化
#include <stdio.h>
int main()
{int n = 10;char* pa= (char*)&n;int* pi = &n;printf("%p\n", &n);printf("%p\n", pa);printf("%p\n", pa + 1);printf("%p\n", pi);printf("%p\n", pi + 1);return 0;
}

我们可以看出,char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。
这就是指针变量的类型差异带来的变化。指针+1,其实跳过1个指针指向的元素。指针可以+1,那也可以-1。
结论:指针的类型决定了指针向前或者向后走一步有多大(距离)。
3.3 void*指针
在指针类型中有一种特殊的类型是void类型的,可以理解为无具体类型的指针(或者叫泛型指
针),这种类型的指针可以用来接受任意类型地址。但是也有局限性 ,void类型的指针不能直接进
行指针的±整数和解引用的运算。
【举例】
int main()
{int a = 10;int* pa = &a;char* pc = &a;return 0;
}
在上面的代码中,将一个int类型的变量的地址赋值给一个char类型的指针变量。编译器给出了一个警告(如下图), 是因为类型不兼容。而使用void类型就不会有这样的问题。

使用void*类型的指针接收地址:
int main()
{int a = 10;void* pa = &a;void* pc = &a;*pa = 10;*pc = 0;return 0;
}

这里我们可以看到, void* 类型的指针可以接收不同类型的地址,但是无法直接进行指针运算。
一般 void* 类型的指针是使用在函数参数的部分,用来接收不同类型数据的地址,这样的设计可以实现泛型编程的效果。使得一个函数来处理多种类型的数据。
四、const 修饰指针
4.1 const修饰变量
变量是可以修改的,如果把变量的地址交给一个指针变量,通过指针变量的也可以修改这个变量。
但是如果我们希望一个变量加上一些限制,不能被修改,怎么做呢?这就是const的作用。
int main()
{int n = 0;n = 20; //n可以被修改const int m = 10;m = 20; //m不可以被修改return 0;
}
上述代码中m是不能被修改的,其实m本质还是变量,只不过被const修饰后,在语法上加了限制,只要我们在代码中对m就行修改,就不符合语法规则,就报错,致使没法直接修改m。
但是如果我们绕过m,使用m的地址,去修改m就能做到了,虽然这样做是在打破语法规则。
#include <stdio.h>
int main()
{const m = 0;int* p = &m;*p = 20;printf("%d\n", m);return 0;
}

我们可以看到这里一个确实修改了,但是我们还是要思考一下,为什么n要被const修饰呢?就是为了不能被修改,如果p拿到n的地址就能修改n,这样就打破了const的限制,这是不合理的,所以应该让p拿到n的地址也不能修改n,那接下来怎么做呢?
4.2 const修饰指针变量
一般来讲const修饰指针变量 ,可以放在 * 的左边,也可以放在 * 的右边,意义是不一样的。
int * p;//没有const修饰?
int const * p;//const 放在*的左边做修饰
int * const p;//const 放在*的右边做修饰
测试const放在*的左边情况



通过上面几张图可以看到,const在*号左边和在右边是有着不同作用的。
- const如果放在 * 的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。但是指针变量本身的内容可变。
- const如果放在*的右边,修饰的是指针变量本身,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变。
- 如果*号的左右两边都放const,既修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。又修饰的是指针变量本身,保证了指针变量的内容不能修改。
相关文章:
【C语言基础】:深入理解指针(一)
文章目录 一、内存和地址1. 内存2. 如何理解编址 二、指针变量和地址2.1 取地址操作符(&)2.2 指针变量和解引用操作符(*)2.2.1 指针变量2.2.2 如何拆解指针变量2.2.3 解引用操作符 2.3 指针变量的大小 三、指针变量类型的意义3.1 指针的解引用3.2 指针 - 整数3.3 void*指针…...
单点故障解决方案之Smart Link与Monitor Link
-SmartLink技术,创建Smart Link 组。在该组中,加入两个端口。其中1个端口是主端口,也称之为Master端口。另外1个端口是备份端口:也称之为 Slave 端口。 -Monitor Link 组也称之为“监控链路组,由上行端口和下行端口共同组成。下行…...
QT之QSharedMemory共享内存
QSharedMemory是qt提供对共享内存操作的类,主要用来对内存卡写数据和读数据。 常用api: 1、void QSharedMemory::setKey(const QString &key) 为共享内存设置键值。如何当前的内存共享对象已经链接到底层的共享内存段(isAttached)&…...
string 类 经典习题之数字字符相加
题目: 给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和并同样以字符串形式返回。 你不能使用任何內建的用于处理大整数的库(比如 BigInteger), 也不能直接将输入的字符串转换为整数形式。 题目来源࿱…...
通讯录——C语言实现
头文件Contact.h #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<string.h> #include<stdlib.h> #pragma once #define MAX 100 #define MAX_NAME 20 #define MAX_SEX 5 #define MAX_TELE 12 #define MAX_ADDR 30//表示一个人的信息 //struct…...
优思学院|3步骤计算出Cpk|学习Minitab
在生产和质量管理中,准确了解和控制产品特性至关重要。一个关键的工具是Cpk值,它是衡量生产过程能力的重要指标。假设我们有一个产品特性的规格是5.080.02,通过收集和分析过程数据,我们可以计算出Cpk值,进而了解生产过…...
【Java编程进阶之路 06】深入探索:JDK、JRE与JVM的关系与差异
JDK、JRE与JVM:揭开Java运行环境的神秘面纱 在Java开发者的日常工作中,JDK、JRE和JVM这三个概念是不可或缺的。它们构成了Java应用程序的运行环境,但很多初学者可能对这三者的关系和差异感到困惑。本文旨在详细解析JDK、JRE和JVM之间的关系&…...
Linux中的touch命令
在Linux中,touch命令主要用于创建空的文件或者更新已存在文件的时间戳。下面是 touch命令的使用方式和示例说明: 创建空文件 要创建一个空文件,可以使用 touch命令并指定文件名,如下所示: touch new_fi…...
智能驾驶规划控制理论学习-基于采样的规划方法
目录 一、基于采样的规划方法概述 二、概率路图(PRM) 1、核心思想 2、实现流程 3、算法描述 4、节点连接处理 5、总结 三、快速搜索随机树(RRT) 1、核心思想 2、实现流程 3、总结 4、改进RRT算法 ①快速搜索随机图&a…...
二叉树——二叉树所有路径
二叉树所有路径 给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。 叶子节点 是指没有子节点的节点。 示例 1: 输入:root [1,2,3,null,5] 输出:["1->2->5","1-…...
算法训练营day38(补),动态规划6
package main func max(a, b int) int { if a > b { return a } return b } 背包最大重量为4。 物品为: 重量价值物品0115物品1320物品2430 每件商品都有无限个! 问背包能背的物品最大价值是多少? func package03(weight, value []…...
【java、微服务、nacos】nacos服务分级存储模型
Nacos服务分级存储模型 ① 一级是服务,例如userservice ②二级是集群,例如杭州或上海 ③ 三级是实例,例如杭州机房的某台部署了userservice的服务器 配置实例集群属性 改变服务的yml文件 spring:cloud:nacos:discovery:cluster-name: H…...
共享旅游卡:打开0费用旅游新纪元,探索40+精彩线路
随着现代生活节奏的加快,旅游成为了许多人释放压力、寻求乐趣的方式。然而,面对琳琅满目的旅游线路和不断上涨的旅游费用,许多人望而却步。 今天,我们要为您介绍一种颠覆传统旅游方式的创新产品——共享旅游卡。它不仅能让您以0费…...
MQTT协议解析:揭秘固定报头、可变报头与有效载荷的奥秘
MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议)是一种轻量级的通讯协议,常用于远程传感器和控制设备的通讯。MQTT协议基于发布/订阅模式,为大量计算能力有限且工作在低带宽、不可靠网络环境中的设备…...
备战蓝桥杯---树形DP基础3
上一次我们讲了二叉苹果树,现在我们加一点难度,从二叉变成了多叉苹果树。 这样子我们就不可以直接按照上次的方法DP,我们其实可以发现,我们可以用类似背包的思想求解,这就是所谓的树上背包。 我们先加进第一个儿子来…...
IEEE Transactions on Industrial Electronics工业电子TIE修改稿注意事项及提交须知
一、背景 兔年末投了一篇TIE,手稿初次提交的注意事项也整理成了博客IEEE Transactions on Industrial Electronics工业电子TIE论文投稿须知,获得了许多点赞和收藏。最近也收到了审稿结果,给的意见是大修major revision,总之只要不…...
c#委托的三种实现方式
委托是实质一个类,主要目的是将方法当作参数进行传递。 委托是.NET编程的精髓之一,在日常编程中经常用到,在C#中实现委托主要有Func、Action、delegate三种方式,本节主要就这三种委托的用法通过实例展开讲解。 Func用法解析 【F…...
c/c++|红黑树|分析应用|锚点
红黑树是一种自平衡的二叉查找树,它保持着良好的平衡,能够在插入和删除等操作后通过一系列旋转和重新着色操作来保持树的平衡。这种平衡性质使得红黑树在搜索、插入和删除等操作的平均和最坏情况下的时间复杂度都是O(log n)。以下是红黑树的一些关键特性…...
2-29算法习题总结
贪心问题 小A的糖果 题目描述 小 A 有 n n n 个糖果盒,第 i i i 个盒中有 a i a_i ai 颗糖果。 小 A 每次可以从其中一盒糖果中吃掉一颗,他想知道,要让任意两个相邻的盒子中糖的个数之和都不大于 x x x,至少得吃掉几颗糖…...
当Linux 磁盘满了,查看大文件并删除
当你的Linux磁盘空间满了时,可以通过以下步骤查找大文件并删除它们: 检查磁盘空间: 使用以下命令检查磁盘空间的使用情况: df -h这将显示文件系统的使用情况,包括每个文件系统的总大小、已用空间、可用空间和挂载点。 …...
基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销,平衡网络负载,延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...
页面渲染流程与性能优化
页面渲染流程与性能优化详解(完整版) 一、现代浏览器渲染流程(详细说明) 1. 构建DOM树 浏览器接收到HTML文档后,会逐步解析并构建DOM(Document Object Model)树。具体过程如下: (…...
屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!
5月28日,中天合创屋面分布式光伏发电项目顺利并网发电,该项目位于内蒙古自治区鄂尔多斯市乌审旗,项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站,总装机容量为9.96MWp。 项目投运后,每年可节约标煤3670…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...
力扣-35.搜索插入位置
题目描述 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...
Java编程之桥接模式
定义 桥接模式(Bridge Pattern)属于结构型设计模式,它的核心意图是将抽象部分与实现部分分离,使它们可以独立地变化。这种模式通过组合关系来替代继承关系,从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...
iview框架主题色的应用
1.下载 less要使用3.0.0以下的版本 npm install less2.7.3 npm install less-loader4.0.52./src/config/theme.js文件 module.exports {yellow: {theme-color: #FDCE04},blue: {theme-color: #547CE7} }在sass中使用theme配置的颜色主题,无需引入,直接可…...
解决:Android studio 编译后报错\app\src\main\cpp\CMakeLists.txt‘ to exist
现象: android studio报错: [CXX1409] D:\GitLab\xxxxx\app.cxx\Debug\3f3w4y1i\arm64-v8a\android_gradle_build.json : expected buildFiles file ‘D:\GitLab\xxxxx\app\src\main\cpp\CMakeLists.txt’ to exist 解决: 不要动CMakeLists.…...
