C语言超详细指针知识(一)

通过前面一段时间C语言的学习,我们了解了数组,函数,操作符等的相关知识,今天我们将要开始进行指针的学习,这是C语言中较难掌握的一个部分,一定要认真学习!!!


1.内存与地址

在正式学习指针前,我们首先要了解两个概念,内存与地址。该如何理解它们呢?

举个例子,一栋宿舍楼有不同房间,每间房门前都有属于他自己的门牌号,让我们可以正确的寻找到对应的宿舍。一栋宿舍楼对应一块内存,每间宿舍对应一小块内存,地址就是门牌号,方便我们快速找到。相对应的我们可以把内存划分为一个个的内存单元,每个内存单元的大小是一个字节。字节是是计算机中最基本的存储单位。以下是一些常见的计算机内存单位:

bit    --比特位

Byte  --字节            1Byte = 8bit

KB                          1KB = 1024 Byte

MB                          1MB = 1024 KB

GB                           1GB = 1024 MB

TB                           1TB = 1024 GB

其中,每个内存单元,相当于一个学生宿舍,一个字节空间能放8个比特位,就好比一个八人寝。每个内存单元也都有一个编号(相当于门牌号),有了这个编号(就是地址),CPU就能快速找到一个内存空间。C语言中给地址起了新的名字:指针。所以:

内存单元的编号==地址==指针


2.指针变量和地址

2.1取地址操作符(&)

理解了内存和地址的关系,我们再回到C语言,在C语言中创建变量其实就是向内存申请空间,比如:

上述代码创建了整型变量a,向内存申请了四个字节的空间,用于存放整数10,每个字节都有自己的地址,整数10会优先存放在四个字节中地址较小的字节。

那么我们要如何得到a的地址呢?这就需要用到一个取地址操作符&,还是上面这个代码,我们用&a来得到了a的地址,存放到p中,打印出来:

 虽然整型变量占⽤4个字节,我们只要知道了第⼀个字节地址,顺藤摸瓜访问到4个字节的数据也是可行的。


2.2指针变量和解引用操作符

上面代码我们打印了&a的地址004FF818,这是一个十六进制的数值,这个数值是可以存储起来的,如整数存放在整型变量中,指针当然也要存放在指针变量中,为了存放&a的地址我们用了p来充当指针变量,它的类型是int*,int*中的*是在说明p是指针变量,与整形变量做区分,而为什么是int呢?因为p地址对应的内存空间存放的是整型变量。

那如果是char类型的变量,要存放它的地址,指针变量该是什么呢?当然是char*,*做区分,char表明存放的是字符类型,如:

char ch = 'a';
char* pc = &ch;

我们知道变量利用取地址操作符得到了地址,那么知道地址有没有对应的操作符得到变量呢?答案是肯定的,这时候就要拿出解引用操作符*了,没错,他跟上文的*一模一样,但意义截然不同,我们看代码:

 我们先创建了一个整型变量num,然后把该变量地址命名为pz,将pz解引用,赋值32,num里的值也产生了相应变化,说明我们通过*pz找到了对应23的内存空间,改变了它。

有同学肯定在想,这⾥如果目的就是把num改成32的话,写成 num  = 32; 不就完了,为啥⾮要使⽤指针呢?其实这⾥是把num的修改交给了pz来操作,这样对num的修改,就多了⼀种的途径,写代码就会更加灵活,后期慢慢就能理解了。

2.3指针变量的大小

指针变量的大小取决于所处环境(x86环境和x64环境)

int main()
{//在x86环境下,指针变量的大小是四个字节printf("%zd\n", sizeof(char*));//4printf("%zd\n", sizeof(short*));//4printf("%zd\n", sizeof(int*));//4printf("%zd\n", sizeof(long*));//4//在x64环境下,指针变量的大小是四个字节printf("%zd\n", sizeof(char*));//8printf("%zd\n", sizeof(short*));//8printf("%zd\n", sizeof(int*));//8printf("%zd\n", sizeof(long*));//8return 0;
}
32位平台下地址是32个bit位,指针变量大小是4个字节
64位平台下地址是64个bit位,指针变量大小是8个字节
从代码里,我们可以明白指针变量的大小和类型是无关的,只要在相同的环境下,大小都是相同的。

指针变量的大小既然和类型无关,为什么会有各种各样的指针类型呢?我们继续接下来的学习。


我们来看一个代码:

void test1()
{int n = 0x11223344;int* pi = &n;*pi = 0;printf("%0x\n", n);//0
}void test2()
{int n = 0x11223344;char* pc = (char*)&n;*pc = 0;printf("%0x\n", n);//11223300
}int main()
{test1();test2();return 0;
}

 观察结果,我们会发现test1会将n的四个字节对应的内存空间全部改为0,但是test2只将n的第一个字节对应的内存空间改为0。

指针的类型决定了,对指针解引用的时候有多大的权限(⼀次能操作几个字节)。
比如: char* 的指针解引用就只能访问⼀个字节,而 int* 的指针的解引用就能访问四个字节。

接下来我们再来看一个代码:

我们可以看出, char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。
这就是指针变量的类型差异带来的变化。
指针的类型决定了指针向前或者向后走一步有多⼤(距离)。

 2.4 void*指针

在指针类型中有⼀种特殊的类型是 void* 类型的,可以理解为无具体类型的指针(或者叫泛型指
针),这种类型的指针可以用来接受任意类型地址。但是也有局限性, void* 类型的指针不能直接进⾏指针的+-整数和解引用的运算。 
看如下代码:

 我们看到,void*类型的指针并不能进行相应的计算,那么void*指针到底有什么用呢?它一般使用在函数参数的部分,用来接收不同类型数据的地址,可以达到范式编程的效果。我们在后面的学习中会慢慢接触到。


3.const修饰

变量是可以修改的,如果把变量的地址交给⼀个指针变量,通过指针变量的也可以修改这个变量。
但是如果我们希望⼀个变量加上⼀些限制,不能被修改,怎么做呢?这就是const的作用。看如下代码:
上述代码中n是不能被修改的,其实n本质是变量,只不过被const修饰后,在语法上加了限制,只要我们在代码中对n进行修改,就不符合语法规则,就报错。
但是不要忘记我们是学习过指针知识,可以通过地址解引用来改变数值,这种方法可不可以绕过const修饰呢?看下面结果:

可以看到,错误变成了警告,代码得以正常运行 ,打破了const的限制。但我们的目的是让变量无法被修改,有没有其他的限制办法呢?当然有,我们让const修饰指针就行了,看下面代码:

代码马上又报错了,指针指向的内存空间将无法被修改,但是我们马上又有了另一个发现 :

 将const放置在指针变量类型之后,代码又可以正常运行了,这是为什么呢?

int main()
{int m = 10;m = 12;const n = 12;const int* p1 = &n;*p1 = 10;//errorp1 = &m;//rightreturn 0;
}int main()
{int m = 10;m = 12;const n = 12;int* const p1 = &n;p1 = &m;//error*p1 = 13;//rightreturn 0;
}

 在上述代码中,我们发现:

• const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。但是指针变量本身的内容可变。
• const如果放在*的右边,修饰的是指针变量本身,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变。

 4.指针运算

指针的基本运算有三种:

指针+-整数
指针-指针
指针的关系运算

4.1指针+-整数 

因为数组在内存中是连续存放的,只要知道第⼀个元素的地址,顺藤摸瓜就能找到后⾯的所有元素。

                                                             数组元素和下标 

int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p1 = arr;int sz = sizeof(arr) / sizeof(arr[0]);for (int i = 0; i < sz; i++){printf("%d ", *(p1 + i));}return 0;
}

 4.2指针减指针

int my_strlen(char* ch)
{char* p = ch;while (*p != '\0'){p++;}return p - ch;
}int main()
{printf("%d\n", my_strlen("abcdef"));return 0;
}

4.3指针的运算关系

int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int sz = sizeof(arr) / sizeof(arr[0]);int* p = arr;while (p < arr + sz){printf("%d ", *p);p++;}return 0;
}

5.野指针

概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

5.1野指针成因 

5.1.1指针未初始化

5.1.2指针越界访问 

 5.1.3指针指向的空间释放

int* test()
{int num = 100;return &num;
}int main()
{int* p = test();printf("%d\n", *p);return 0;
}

5.2如何规避野指针

5.2.1指针初始化

如果明确知道指针指向哪⾥就直接赋值地址,如果不知道指针应该指向哪⾥,可以给指针赋值 NULL 。NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是⽆法使⽤的,读写该地址会报错。
int main()
{int num1 = 12;int* p1 = &num1;int* p2 = NULL;return 0;
}

5.2.2小心指针越界访问

⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问。

5.2.3指针变量不再使用时,及时置NULL,指针使用之前检查有效性 

当指针变量指向⼀块区域的时候,我们可以通过指针访问该区域,后期不再使⽤这个指针访问空间的时候,我们可以把该指针置为NULL。因为约定俗成的⼀个规则就是:只要是NULL指针就不去访问,同时使⽤指针之前可以判断指针是否为NULL。

5.2.4避免返回局部变量的地址

如造成野指针的第3个例⼦,不要返回局部变量的地址。

6.assert断言

assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就
报错终⽌运⾏。这个宏常常被称为“断⾔”。
基本语法:
#include <assert.h>
assert(expression);

这里的 expression 是一个布尔表达式,assert 会对其进行求值。如果 expression 的值为真(非零),程序会继续正常执行;如果 expression 的值为假(零),assert 会触发一个断言失败,程序会调用 abort() 函数终止执行,并输出错误信息。 

它不仅能自动标识⽂件和出问题的⾏号,还有⼀种⽆需更改代码就能开启或关闭 assert() 的机制。如果已经确认程序没有问题,不需要再做断⾔,就在 #include <assert.h> 语句的前⾯,定义⼀个宏 NDEBUG

#define NDEBUG
#include<assert.h>

然后,重新编译程序,编译器就会禁用文件中所有的 assert() 语句。如果程序又出现问题,可以移 除这条 #define NDBUG 指令(或者把它注释掉),再次编译,这样就重新启用了 assert() 语句。

assert() 的缺点是,因为引入了额外的检查,增加了程序的运行时间。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/76539.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

程序化广告行业(70/89):ABTester系统助力落地页优化实践

程序化广告行业&#xff08;70/89&#xff09;&#xff1a;ABTester系统助力落地页优化实践 在程序化广告领域摸爬滚打多年&#xff0c;深知持续学习和知识共享的重要性。写这篇博客&#xff0c;就是希望能和大家一起深入探索程序化广告行业&#xff0c;共同学习、共同进步。今…

项目管理(高软56)

系列文章目录 项目管理 文章目录 系列文章目录前言一、进度管理二、配置管理三、质量四、风险管理五、真题总结 前言 本节主要讲项目管理知识&#xff0c;这些知识听的有点意思啊。对于技术人想创业&#xff0c;单干的都很有必要听听。 一、进度管理 二、配置管理 三、质量 四…

常见的后缀名

.exe .exe&#xff08;“executable”&#xff08;可执行的&#xff09;&#xff09;是 Windows 操作系统中最常见的可执行文件扩展名。此类文件包含了计算机能够直接运行的机器码指令。当用户双击 .exe 文件时&#xff0c;操作系统会读取其中的指令并执行相应的程序或任务。…

XILINX DDR3专题---(1)IP核时钟框架介绍

1.什么是Reference Clock&#xff0c;这个时钟一定是200MHz吗&#xff1f; 2.为什么APP_DATA是128bit&#xff0c;怎么算出来的&#xff1f; 3.APP &#xff1a;MEM的比值一定是1:4吗&#xff1f; 4.NO BUFFER是什么意思&#xff1f; 5.什么情况下Reference Clock的时钟源可…

Doris 安装部署、实际应用及优化实践:对比 ClickHouse 的深度解析

在实时分析、报表系统以及高并发 OLAP 查询等场景中&#xff0c;列式存储数据库因其卓越的查询性能逐渐成为主流。Doris 和 ClickHouse 是近年来最受欢迎的两款开源 OLAP 引擎&#xff0c;本文将系统介绍 Doris 的安装部署、应用场景及优化实践&#xff0c;并与 ClickHouse 做一…

OracleLinuxR5U5系统重启后启动数据库oracle23ai

1、切换到oracle用户 [rootOracleLinux-R9-U5 ~]# su oracle2、查看oracle是否配置了ORACLE_SID [oracleOracleLinux-R9-U5 root]$ cd ~ [oracleOracleLinux-R9-U5 ~]$ cat .bash_profile3、输出内容如下&#xff1a; [oracleOracleLinux-R9-U5 ~]$ cat .bash_profile # .ba…

【正点原子】STM32MP257 同构多核架构下的 ADC 电压采集与处理应用开发实战

在嵌入式系统中&#xff0c;ADC模拟电压的读取是常见的需求。如何高效、并发、且可控地完成数据采集与处理&#xff1f;本篇文章通过双线程分别绑定在 Linux 系统的不同 CPU 核心上&#xff0c;采集 /sys/bus/iio 接口的 ADC 原始值与缩放系数 scale&#xff0c;并在另一个核上…

电商用户购物行为分析:基于K-Means聚类与分类验证的完整流程

随着电商行业的快速发展,用户行为分析成为企业优化营销策略、提升用户体验的重要手段。通过分析用户的购物行为数据,企业可以挖掘出用户群体的消费特征和行为模式,从而制定更加精准的营销策略。本文将详细介绍一个基于Python实现的电商用户购物行为分析系统,涵盖数据预处理…

AMGCL库的Backends及使用示例

AMGCL库的Backends及使用示例 AMGCL是一个用于解决大型稀疏线性方程组的C库&#xff0c;它提供了多种后端(backends)实现&#xff0c;允许用户根据不同的硬件和性能需求选择合适的计算后端。 AMGCL支持的主要Backends 内置Backends: builtin - 默认的纯C实现block - 支持块状…

Express中间件(Middleware)详解:从零开始掌握(3)

实用中间件模式25例 1. 基础增强模式 请求属性扩展 function extendRequest() {return (req, res, next) > {req.getClientLanguage () > {return req.headers[accept-language]?.split(,)[0] || en;};next();}; } 响应时间头 function responseTime() {return (r…

05--MQTT物联网协议

一、MQTT的概念 MQTT 协议快速入门 2025&#xff1a;基础知识和实用教程 | EMQ 1.MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;是一种轻量级、基于发布-订阅模式的消息传输协议&#xff0c;适用于资源受限的设备和低带宽、高延迟或不稳定的网络环境。它…

数据结构与算法——链表OJ题详解(2)

文章目录 一、前言二、OJ续享2.1相交链表2.2环形链表12.2环形链表2 三、总结 一、前言 哦了兄弟们&#xff0c;咱们上次在详解链表OJ题的时候&#xff0c;有一部分OJ题呢up并没有整理完&#xff0c;这一个星期呢&#xff0c;up也是在不断的学习并且沉淀着&#xff0c;也是终于…

SQL Server AlwaysOn (SQL 查询数据详解及监控用途)

修正后的完整查询 SELECT ar.replica_server_name AS [副本名称],ar.availability_mode_desc AS [同步模式],DB_NAME(dbr.database_id) AS [数据库名称],dbr.database_state_desc AS [数据库状态],dbr.synchronization_state_desc AS [同步状态],dbr.synchronization_health_d…

力扣热题100刷题day63|49.字母异位词分组

目录 一、哈希表相关理论 二、思路 核心思路 三、相关题目 四、总结 一、哈希表相关理论 代码随想录刷题day15|&#xff08;哈希表篇&#xff09;242.有效的字母异位词、383.赎金信-CSDN博客 二、思路 首先&#xff0c;创建一个map集合&#xff0c;遍历字符串数组&…

爱普生可编程晶振SG8201CJ和SG8200CJ在胃镜机器人发挥重要作用

在医疗机器人技术高速发展的今天&#xff0c;胃镜机器人作为胃肠道疾病诊断与治疗的创新设备&#xff0c;正逐渐改变传统诊疗模式。其复杂精密的系统需要精准的时间同步与稳定的信号输出&#xff0c;胃镜机器人是一种先进的医疗设备&#xff0c;用于无创性地检查胃部疾病。与传…

Ubuntu22环境下,Docker部署阿里FunASR的gpu版本

番外: 随着deepseek的爆火,人工智能相关的开发变得异常火爆,相关的大模型开发很常见的agent智能体需要ASR语音识别的功能,阿里开源的FunASR几乎是把一个商业的项目放给我们使用了。那么我们项目中的生产环境怎么部署gpu版本的语音识别服务呢?经过跟deepseek的一上午的极限…

图解Java设计模式

1、设计模式面试题 2、设计模式的重要性 3、7大设计原则介绍 3.1、单一职责原则

transformers的 pipeline是什么:将模型加载、数据预处理、推理等步骤进行了封装

transformers的 pipeline是什么:将模型加载、数据预处理、推理等步骤进行了封装 pipe = pipeline("text-generation", model=model, tokenizer=tokenizer, max_new_tokens=50 )pipeline :这是 transformers 库中一个非常实用的工具函数。它可以基于预训练模型快速构…

jmeter插件安装

1、下载 下载地址&#xff1a; Documentation :: JMeter-Plugins.org 然后复制到D:\apache-jmeter-5.6.3\lib\ext 复制后 2、重启jmeter 在菜单【选项】找到“Plugins Manager” 在 Plugins Manager 界面上&#xff0c;点击“Available Plugins”标签页&#xff0c;可以浏览所…

VSCode CMake调试CPP程序

文章目录 1 安装C与CMake插件2 配置CMakeLists.txt3 使用CMake编译调试3.1 编译3.2 调试 4 自定义构建调试参考 1 安装C与CMake插件 C插件 CMake插件 2 配置CMakeLists.txt 编写测试程序 #include<iostream>int main(int argc, char const *argv[]) {int a 1, b 2;i…