【C语言】初阶指针详解
大家好,我是苏貝,本篇博客带大家了解C语言中令人头疼的指针,如果大家觉得我写的不错的话,可以给我一个赞👍吗,感谢❤️
使用的是VS2019编译器,默认为32位平台
文章目录
- ①指针是什么
- ②指针定义与&和*操作符
- 1.指针定义
- 2.&和*操作符
- ③指针类型
- ④ 野指针
- 1.野指针成因
- 2.如何规避野指针
- ⑤指针运算
- 1.指针 + - 整数 = 指针
- 2.指针 - 指针
- 3.指针的关系运算
- ⑥二级指针
- ⑦指针和数组
- ⑧指针数组
①指针是什么
指针理解的2个要点:
- 指针是内存中一个最小单元的编号,也就是地址
单元编号 == 地址 ==C语言中也叫: 指针 - 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
内存:
指针变量:
我们可以通过&(取地址操作符)取出变量的内存起始地址,把地址可以存放到一个变量中,这个变量就是指针变量
要知道
1.内存被划分为一个个的内存单元,每个内存单元的大小是一个字节
2.每个字节的内存单元都有一个编号,这个编号就是地址,地址在C语言中被称为指针
3.每个内存单元都有唯一的地址来标识
4.地址要存储的话,存放在指针变量中
5.在32位机器上,地址的大小为4个字节,所以指针变量的大小也为4个字节
在64位机器上,地址的大小为8个字节,所以指针变量的大小也为8个字节
对第5点进行解释
对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0);
那么32根地址线产生的地址就会是:
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
…
11111111 11111111 11111111 11111111
每个地址有32个比特位,即32/8=4个字节
同理:对于64位的机器,有8个字节
那32和64位机器能编址多大空间呢?
由上方解释可知,32位机器就有2的32次方个地址。每个地址标识一个字节,那我们就可以给 (2^ 32Byte = 2^ 32/1024KB =2^ 32/1024/1024MB=2^32/1024/1024/1024GB = 4GB) 4G的空闲进行编址。
同理,64位机器就可以给 2^ 64Byte 的空闲进行编址。
②指针定义与&和*操作符
1.指针定义
这里我们在讨论一下:指针的类型
我们都知道,变量有不同的类型,整形,浮点型等。那指针有没有类型呢?
准确的说:有的。当有这样的代码:
(1) int* p;
(2) char* p;
(3) int** p;
(1)* 代表p是指针变量,int* 是指针变量p的类型,int 是p所指向的类型,即p所指向的是int类型的变量
(2)* 代表p是指针变量,char* 是指针变量p的类型,int 是p所指向的类型
(3)离p最近的 * 代表p是指针变量,int* * 是指针变量p的类型,int* 是p所指向的类型
2.&和*操作符
这里的&是取地址操作符,*是间接访问操作符
int a=10;
int* p=&a;
p=&a的意思是:用&操作符取出a的地址,放在指针变量p中,指针变量的类型是 a的类型 + 一个 * 号,例如:a是int类型的,p的类型为int * ; a是char类型的,p的类型为char * ……
int a=10;
int *p=&a;
*p=0;
*p=0;意思是通过 *间接访问操作符解引用找到p指向的那个变量并将之赋值为0(即将0赋值给a)*p==a
int main()
{int a = 10;//在内存中开辟一块空间存储aint* p = &a;//用&操作符取出a的地址,放在指针变量p中*p = 20;//p通过*解引用找到a并将之赋值为20,*p==aprintf("a=%d\n", a);return 0;
}
//a=20
③指针类型
先看下面代码的结果,正如我们上面所说的,在32位平台下地址的大小为4个字节,那既然大家字节数都一样,为什么还要区分char* ,int* 等类型呢?直接用一个笼统的类型如all*类型不可以吗?
答案是不可以。为什么呢?
因为指针类型是有意义的,指针类型决定了指针进行解引用操作时访问几个字节
示例1:
因为下图的p是整型指针,指向的是一个int类型的变量,所以解引用时访问4个字节,所以在进行*p=0操作后,0x11223344全部变成0
示例2:
将上图的int* p=&a;改为char* p=&a;
因为下图的p是 char* 指针,指向的是一个char类型的变量,所以解引用时访问1个字节,所以在进行*p=0操作后,0x11223344全部变成0x11223300
示例3:
指针类型决定了指针 +1 / -1跳过几个字节(与指针类型进行解引用操作时访问的字节数相同)
总结
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
比如: char* 的指针解引用就只能访问1个字节,而 int* 的指针的解引用就能访问4个字节,double* 的指针解引用就只能访问8个字节……
④ 野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
1.野指针成因
(1). 指针未初始化:
指针未初始化,默认为随机值
int main()
{int* p;//局部变量指针未初始化,默认为随机值*p = 20;return 0;
}
(2).指针越界访问:
数组arr只有10个元素,但却循环了11次,循环第11次时,针指向的范围超出数组arr的范围时,p成为野指针
解释*p++ = i ;
++(后缀自增操作符)的优先级高于*(间接访问操作符),所以p先后置++,再与 * 结合,但由于后置++是先使用再自增,所以执行*p=i操作后再自增,所以数组首元素被赋值为0后p自增1,使得p指向数组的第二个元素(跳了4个字节,原因在上面指针类型)
int main()
{int arr[10] = { 0 };int* p = arr;//p指向arr的首元素int i = 0;for (i = 0; i <= 10; i++){//当指针指向的范围超出数组arr的范围时,p就是野指针*p++ = i;}return 0;
}
(3).指针指向的空间释放:
a是函数内的局部变量,在函数调用结束后,a所开辟的内存空间会被释放,即使在释放前将a的地址传了出去,但p接收地址的时候a所开辟的内存空间已被释放,此时再用* 对p进行解引用,即使找到了该地址也属于非法访问
int* text()
{int a = 10;return &a;
}int main()
{int* p = text();//p就是野指针printf("*p=%d", *p);return 0;
}
2.如何规避野指针
- 指针初始化(若一开始不知道指针该指向哪,就先指向NULL)
NULL就是0,当0作为地址时,用户程序是不能访问的
- 小心指针越界
- 指针指向的空间释放,那就使之指向NULL
- 避免返回局部变量的地址
- 指针使用之前检查有效性
int main()
{int* p = NULL;//1//....int a = 10;p = &a;if (p != NULL)//5{*p = 20;}return 0;
}
⑤指针运算
1.指针 + - 整数 = 指针
int main()
{int arr[9] = { 1,2,3,4,5,6,7,8,9 };//使用指针打印数组的内容int* p = arr;//arr是首元素地址,所以p指向数组首元素int i = 0;for (i = 0; i < 9; i++){printf("%d ", *(p + i));//p指向数组首元素,即下标为0的元素//p+i指向数组下标为i的元素//*(p+i)是通过*解引用找到下标为i的元素//p+i 与p相比,跳过了i*sizeof(int)个字节}return 0;
}
//1 2 3 4 5 6 7 8 9
上面arr是首元素地址,指针变量p里面存的也是首元素地址,所以arr==p
因此arr+i == p+i, arr[i] == p[i](下标为i的元素的地址)
*(arr+i) == *(p+i) = arr[i]
下面表达式中,左边数组的这种形式在编译器处理时会转化为右边的表达式,又因为[ ] 和+ 一样只是一个操作符,所以i写在[ ]里面或外面都可以
arr[ i ] == *(arr+i) //当然,更建议写成 arr[ i ] 这种形式
i [ arr] == *(i+arr)
拓展:
数组名是数组首元素地址,除了以下2种情况:
(1)sizeof(数组名),此时的数组名代表整个数组,所以计算结果是整个数组的大小
(2)&数组名,此时的数组名也代表整个数组,取出的是整个数组的地址,返回的是数组首元素的地址,但绝不代表&数组名==arr(首元素地址),如下:
arr和&arr[0]等价,都是首元素地址,类型是int*,所以+1跳过4个字节
&arr是取出了整个数组的地址,但返回首元素地址,所以+1跳过整个数组即10* 4=40个字节
sizeof(arr)计算结果是整个数组的大小即10* 4=40个字节
2.指针 - 指针
指针-指针的前提:两个指针指向同一块区域,指针类型相同
指针-指针差值的绝对值是:指针和指针之间的元素个数
了解了这些之后,我们是不是又多了一种计算字符个数的功能的自定义函数my_strlen()实现的方法
点击链接了解:自定义实现strlen函数的3种方法
3.指针的关系运算
请看下面代码:
int main()
{int arr[5];int* vp = NULL;for (vp = &arr[5]; vp > &arr[0];){*--vp = 0;}return 0;
}
若不想一开始就越界,有人可能会修改成这样的代码:
int main()
{int arr[5];int* vp = NULL;for (vp = &arr[4]; vp >= &arr[0];vp--){*vp = 0;}return 0;
}
那这两种方法哪个更好呢?答案是第一种,第二种在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较
⑥二级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?
存放在 二级指针 中
*pp 通过对pp中的地址进行解引用,这样找到的是 p , *pp 其实访问的就是 p
**pp 先通过 *pp 找到 p ,然后对 p 进行解引用操作: *p ,那找到的是 a
int main()
{int a = 10;int* p = NULL;int** pp = NULL;*pp = &a;//等价于 p = &b;**pp = 30;//等价于*p = 30;//等价于a = 30;return 0;
}
⑦指针和数组
指针就是指针,指针变量就是一个变量,存放的是地址,指针变量的大小取决与32位平台还是64位平台
数组就是数组,可以存放一组相同类型的数,数组的大小取决于元素的类型和个数
数组的数组名是首元素地址,通过指针可以访问一个数组的每一个元素
int main()
{int arr[5] = { 1,2,3,4,5 };int* p = arr;//p=arr//arr[i]=p[i]=*(arr+i)=*(p+i)都是下标为i的元素int i = 0;for (i = 0; i < 5; i++){printf("%d ", p[i]);//arr[i],*(arr+i),*(p+i)}return 0;
}
//1 2 3 4 5
⑧指针数组
指针数组是指针还是数组?我们知道,整型数组是数组,存放的是int 类型的元素;字符数组是数组,存放的是char类型的元素……
指针数组是数组,是存放指针的数组
例如:int* arr[5];
arr是数组名,5代表数组有5个元素,int* 代表每个元素都是一个整型指针
好了,那么本篇博客就到此结束了,如果你觉得本篇博客对你有些帮助,可以给个大大的赞👍吗,感谢看到这里,我们下篇博客见❤️
相关文章:

【C语言】初阶指针详解
大家好,我是苏貝,本篇博客带大家了解C语言中令人头疼的指针,如果大家觉得我写的不错的话,可以给我一个赞👍吗,感谢❤️ 使用的是VS2019编译器,默认为32位平台 文章目录 ①指针是什么②指针定义与…...

ElasticSearch:项目实战(1)
es环境搭建参考:ElasticSearch:环境搭建步骤_Success___的博客-CSDN博客 需求: 用户输入关键可搜索文章列表 关键词高亮显示 文章列表展示与home展示一样,当用户点击某一篇文章,可查看文章详情 思路: …...
React 实现文件分片上传和下载
React 实现文件分片上传和下载 在开发中,文件的上传和下载是常见的需求。然而,当面对大型文件时,直接的上传和下载方式可能会遇到一些问题,比如网络传输不稳定、文件过大导致传输时间过长等等。为了解决这些问题,我们…...
2023.8.13
atcoder_abc\AtCoder Beginner Contest 310\E_NAND_repeatedly //题意:给定一个n长度的01串,计算f(l,r)(l<r,l在1~n,r在1~n)的和,f的计算(ai,a(i1))运算,有0就为1,11为0 //若f(l,r)1,则f(l,r-1)为0或sr为0,即只取决于上一位的情况和当前位ÿ…...
kvm not all arguments converted during string
kylin virt-manager 远程镜像制作问题记录(not all arguments ) 项目场景: 服务器端安装的OS版本:Kylin-Server-10-SP1-Release-Build20-20210518-arm64-2021-05-18 客户端安装的OS版本:Kylin-Server-10-SP1-Release-Build20-20210518-x86_…...

JVM 基础
巩固基础,砥砺前行 。 只有不断重复,才能做到超越自己。 能坚持把简单的事情做到极致,也是不容易的。 JVM 类加载机制 JVM 类加载机制分为五个部分:加载,验证,准备,解析,初始化&am…...

智谷星图赵俊:让人才和区块链产业“双向奔赴”丨对话MVP
区块链产业需要什么样的人才?赵俊很有发言权。 赵俊是北京智谷星图科技有限公司的技术总监,也是FISCO BCOS官方认证讲师。他2017年接触区块链,随后选择人才培育领域深耕。“为区块链行业引进更多人才这件事很有价值,跟我的职业理…...

C# Equals()方法报错:NullReferenceException was unhandled
下面是一个C# Equals()方法的例子,执行时报错了 static void Main(string[] args) {string name "sandeep";string myName null;Console.WriteLine(" operator result is {0}", name myName);Console.WriteLine("Equals method result…...

Linux下C语言调用libcurl库获取天气预报信息
一、概述 当前文章介绍如何在Linux(Ubuntu)下使用C语言调用libcurl库获取天气预报的方法。通过HTTP GET请求访问百度天气API,并解析返回的JSON数据,可以获取指定城市未来7天的天气预报信息。 二、设计思路 【1】使用libcurl库进…...
“深入解析JVM:Java虚拟机原理和内部结构“
标题:深入解析JVM:Java虚拟机原理和内部结构 摘要:本文将深入解析JVM(Java虚拟机)的原理和内部结构。我们将从JVM的基础概念开始,逐步介绍其组成部分,包括类加载器、运行时数据区、字节码解释器…...
Arrays.asList() 返回的list不能add,remove
一.Arrays.asList() 返回的list不能add,remove Arrays.asList()返回的是List,而且是一个定长的List,所以不能转换为ArrayList,只能转换为AbstractList 原因在于asList()方法返回的是某个数组的列表形式,返回的列表只是数组的另一个视图,而数组本身并没…...

命令执行漏洞
1、命令执行漏洞 1.1、简介 Django是用Python开发的一个免费开源的Web结构,几乎包括了Web使用方方面面,能够用于快速建立高性能、文雅的网站,Diango提供了许多网站后台开发常常用到的模块,使开发者可以专注于业务部分。 1.2、漏…...
Hive 中 sort by 和 order by 的区别
文章目录 数据量大小区别作用范围 在 Hive 中, SORT BY 和 ORDER BY 都用于对查询结果进行排序,但它们在实现方式和适用场景上有一些区别。 数据量大小区别 SORT BY: SORT BY 用于在 Hive 中对查询结果进行排序,它的主要特点是在…...
网络资源利用最大化:爬虫带宽优化解决方案
大家好,作为一名专业的爬虫程序员,我们都知道在爬取大量数据的过程中,网络带宽是一个十分宝贵的资源。如果我们不合理地利用网络带宽,可能会导致爬虫任务的效率低下或者不稳定。今天,我将和大家分享一些优化爬虫带宽利…...

STDF - 基于 Svelte 和 Tailwind CSS 打造的移动 web UI 组件库,Svelte 生态里不可多得的优秀项目
Svelte 是一个新兴的前端框架,组件库不多,今天介绍一款 Svelte 移动端的组件库。 关于 STDF STDF 是一个移动端的 UI 组件库,主要用来开发移动端 web 应用。和我之前介绍的很多 Vue 组件库不一样,STDF 是基于近来新晋 js 框架 S…...

C语言一些有趣的冷门知识
文章目录 概要1.访问数组元素的方法运行结果 2.中括号的特殊用法运行结果 3.大括号的特殊用法运行结果 4.sizeof的用法运行结果 5.渐进运算符运行结果 小结 概要 本文章只是介绍一些有趣的C语言知识,纯属娱乐。这里所有的演示代码我是使用的编译器是Visual Studio …...
Oracle数据库审计
1.什么是审计 审计是用来监控和记录用户的数据库操作的 2.审计级别 语句审计权限审计对象审计 3.查看审计功能是否开启: show parameter audit;相关参数: audit_file_destOS中审计信息存放位置audit_sys_operations默认值为FALSE,即不审…...

Node.js新手在哪儿找小项目练手?
前言 可以参考一下下面的nodejs相关的项目,希望对你的学习有所帮助,废话少说,让我们直接进入正题>> 1、 NodeBB Star: 13.3k 一个基于Node.js的现代化社区论坛软件,具有快速、可扩展、易于使用和灵活的特点。它支持多种数…...

全国各城市-货物进出口总额和利用外资-外商直接投资额实际使用额(1999-2020年)
最新数据显示,全国各城市外商直接投资额实际使用额在过去一年中呈现了稳步增长的趋势。这一数据为研究者提供了对中国外商投资活动的全面了解,并对未来投资趋势和政策制定提供了重要参考。 首先,这一数据反映了中国各城市作为外商投资的热门目…...
CentOS 7查看磁盘空间
CentOS如何查看硬盘大小 CentOS是一种基于Linux的操作系统,主要用于服务器端应用。在服务器管理中,硬盘大小是一个非常重要的指标,查看硬盘大小可以帮助系统管理员有效地管理硬盘空间和避免硬盘满了的情况。 方法一:使用df命令 …...

边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果,可同时显示主类&#x…...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...

【7色560页】职场可视化逻辑图高级数据分析PPT模版
7种色调职场工作汇报PPT,橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版:职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...

【C++特殊工具与技术】优化内存分配(一):C++中的内存分配
目录 一、C 内存的基本概念 1.1 内存的物理与逻辑结构 1.2 C 程序的内存区域划分 二、栈内存分配 2.1 栈内存的特点 2.2 栈内存分配示例 三、堆内存分配 3.1 new和delete操作符 4.2 内存泄漏与悬空指针问题 4.3 new和delete的重载 四、智能指针…...

认识CMake并使用CMake构建自己的第一个项目
1.CMake的作用和优势 跨平台支持:CMake支持多种操作系统和编译器,使用同一份构建配置可以在不同的环境中使用 简化配置:通过CMakeLists.txt文件,用户可以定义项目结构、依赖项、编译选项等,无需手动编写复杂的构建脚本…...
DAY 26 函数专题1
函数定义与参数知识点回顾:1. 函数的定义2. 变量作用域:局部变量和全局变量3. 函数的参数类型:位置参数、默认参数、不定参数4. 传递参数的手段:关键词参数5 题目1:计算圆的面积 任务: 编写一…...