【C语言】初阶指针(详细版)

news2024/11/24 5:04:28

在这里插入图片描述

👦个人主页:Weraphael
✍🏻作者简介:目前正在回炉重造C语言(2023暑假)
✈️专栏:【C语言航路】
🐋 希望大家多多支持,咱一起进步!😁
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注😍


目录

  • 一、什么是指针
      • 1.1 内存
      • 1.2 内存的管理与使用
      • 1.3 指针变量的使用
      • 1.4 指针的大小
  • 二、指针和指针类型
      • 2.1 指针类型的意义
      • 2.2 指针+ 或 - 整数
      • 2.3 指针解引用
  • 三、野指针
      • 3.1 野指针成因
        • 3.1.1 指针未初始化
        • 3.1.2 指针越界访问
      • 3.1.3 指针指向的空间被释放
      • 3.2 如何规避野指针
  • 四、指针运算
      • 4.1 指针+-整数
      • 4.2 指针 - 指针
        • 4.2.1 指针-指针的运用
      • 4.3 指针的关系运算(比较大小)
  • 五、指针和数组
  • 六、二级指针
      • 6.1 什么是二级指针
      • 6.2 二级指针的使用
  • 七、指针数组
      • 7.1 什么是指针数组
      • 7.2 用一维数组模拟二维数组

一、什么是指针

想要理解什么是指针,必须先了解什么是 内存

1.1 内存

内存是电脑上的存储设备,一般都是4G/8G/16G等,程序运行时会加载到内存中,也会使用内存空间。我们可以看看电脑的任务管理器:

在这里插入图片描述

1.2 内存的管理与使用

我们将内存划分为一个个小格子,每一个格子是一个 内存单元,大小为 一个字节,对每一个内存单元进行 编号,假设未来要找一个内存单元,就可以通过编号(地址)很快的找到,我们把这些 编号叫做地址,而地址在C语言中又叫做指针。

在这里插入图片描述

举一个例子,在下图中,假设定义一个变量int a = 10,一个int类型的变量,需要占4个字节的空间,而每个字节都有地址,&a取出的是4个字节中的哪一个的地址呢?其实取出的是第一个字节的地址(也就是较小的地址),也就是说,&a最终取出来的地址是0x0012ff40。当然,可以把这个地址存到一个变量中,int* pa = &a*表示pa是一个指针,int代表pa所指向的类型是int类型,这个pa也叫做指针变量(它是专门用来存放地址的)。

在这里插入图片描述

总结指针理解的2个要点:

  1. 指针是内存中一个最小的单元编号,也就是地址。
  2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量。

总结:指针就是地址,口语中说的指针通常指的是指针变量。

1.3 指针变量的使用

在这里插入图片描述

通过以上代码就验证了,指针变量p存放的就是a的地址。

接下来,可以使用*解引用操作符来对其使用

在这里插入图片描述

1.4 指针的大小

  • 指针变量,就是用来存放地址的变量。(存放在指针中的值都被当成地址处理)。
  • 那么问题来了:一个内存单元到底是多大?刚刚讲过,就是一个字节。那它又是如何编址的呢?
    经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。 对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0)
    那么32根地址线产生的地址就会是:
    00000000000000000000000000000000
    00000000000000000000000000000001
    .....
    11111111111111111111111111111111
    这里就有232个地址。 一个地址管理一个内存单元,那么232个地址就能管理232个内存单元,也就是232个字节,那2的32次方个字节又是多大空间呢?根据进制转化:
    232 Byte = 232÷1024 KB ÷1024 MB ÷ 1024 GB = 4GB
    同样的方法,64位机器,也可以计算出来。
    264 Byte = 8 GB

这里我们就明白:

  • 在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以 一个指针变量的大小就应该是4个字节。
  • 那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。

总结:

  • 指针变量是用来存放地址的,地址是唯一标示一个内存单元的。
  • 指针的大小在32位平台是4个字节,在64位平台是8个字节。

二、指针和指针类型

2.1 指针类型的意义

假设在32位或64位机器上,指针大小都是4个字节或8个字节,直接搞一个通用指针不就完事了,那为什么要区分int*char*double*这些呢?那它肯定就有特殊的意义。

先看看下面的代码:

在这里插入图片描述

我们可以通过调试来观察变量a内存 中的变化

在这里插入图片描述

为了方便观察,我们把它调成4列

在这里插入图片描述

我们发现,内存中确实存的是44332211,只不过是倒着放的(为什么是在内存中倒着存放会在后期讲解)

接着按F10再往下走

在这里插入图片描述

我们发现,4个字节全部被改为0,这说明a的值确实被修改成0了

假设我把指针变量pa的类型改为char*结果又会是如何呢?

可以继续通过观察其内存变化

在这里插入图片描述

继续按F10

在这里插入图片描述

它只改变了1个字节!!

总结:指针类型的意义

  1. 指针类型决定了,指针在进行解引用操作的时候,一次性访问几个字节
    如果是char*类型的指针,解引用访问内存中的一个字节
    如果是int*类型的指针,解引用访问内存中的四个字节
    float*double*也同样如此…

指针类型还要其它意义,接着往下看:

在这里插入图片描述

我们发现,pa + 1跳过了4个字节,pc + 1跳过了1个字节

总结:指针类型的意义:
2. 指针类型决定指针的步长(指针+1到底跳过几个字节)
字符指针+1,跳过1个字节
整型指针+1,跳过4个字节
其它类型的指针也是如此…

2.2 指针+ 或 - 整数

在这里插入图片描述

注意:指针+一个数,是往高地址走,指针-一个数,是往低地址走

2.3 指针解引用

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

在这里插入图片描述

三、野指针

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

3.1 野指针成因

3.1.1 指针未初始化

在这里插入图片描述

3.1.2 指针越界访问

在这里插入图片描述

3.1.3 指针指向的空间被释放

在这里插入图片描述

首先test函数中的a是一个局部变量,根据局部变量的生命周期,出了它的作用域,生命周期结束。返回的时候a的地址就已经被销毁了,此时的指针变量p就是一个野指针。但是运行的时候结果还是10,这只是侥幸,这是因为之前的内存空间还没有被覆盖。我们加上个输出语句就能破坏掉这个内存空间。

在这里插入图片描述

3.2 如何规避野指针

  • 指针初始化
    在这里插入图片描述
  • 小心指针越界
  • 指针指向空间释放,及时置NULL
  • 避免放回局部变量的地址
  • 指针使用之前要检查有效性

四、指针运算

4.1 指针±整数

#include <stdio.h>
#define N 5 // #define定义的标识符常量
float a[N];
float* p; // 全局变量默认初始化为0,而NULL本质上就是0
int main()
{
	for (p = &a[0]; p < &a[N]; )
	{
		*p++;
	}
	return 0;
}

看下图就很容易理解代码循环了,但这里要主要循环体内的代码,*的优先级是比++操作符要高的,而++是后置的(先使用,后++),所以这代码的意思是把数组内5个元素全部赋值成0

在这里插入图片描述

4.2 指针 - 指针

  • 运算的前提条件:两个指针要指向同一块空间(同个数组)
  • 运算结果是:相减绝对值的结果就是两个指针之间的元素个数

在这里插入图片描述

【解析】

在这里插入图片描述

4.2.1 指针-指针的运用

之前我们求字符串长度是这么写的(不使用库函数strlen的情况下),计数法

#include <stdio.h>
int my_strlen(char* str)
{
	// 计数法
	int count = 0;
	while (*str != '\0')
	{
		count++;
		str++;
	}
	return count;
}

int main()
{
	char a[] = "abcdef";
	int len = my_strlen(a);
	printf("%d\n", len);
	return 0;
}

当然还可以使用递归

#include <stdio.h>
int my_strlen(char* str)
{
	if (*str == '\0')
		return 0;
	else
		return 1 + my_strlen(str + 1);
}

int main()
{
	char a[] = "abcdef";
	int len = my_strlen(a);
	printf("%d\n", len);
	return 0;
}

除了以上两种方法,还可以使用 指针 - 指针

#include <stdio.h>

int my_strlen(char* str)
{
	// 记录起始地址
	char* start = str;
	// 记录'\0'的位置
	while (*str != '\0')
	{
		str++;
	}
	return str - start;
}

int main()
{
	char a[] = "abcdef";
	int len = my_strlen(a);
	printf("%d\n", len);
	return 0;
}

【解析】

在这里插入图片描述

注意:++千万不要放到循环表达式中,因为不管是前置还是后置++,它都会有副作用,会导致return的结果有误差。

4.3 指针的关系运算(比较大小)

#include <stdio.h>
#define N 5 // #define定义的标识符常量
float a[N];
float* p; // 全局变量默认初始化为0,而NULL本质上就是0
int main()
{
	for (p = &a[N]; p > &a[0]; )
	{
		*--p;
	}
	return 0;
}

【详解】
在这里插入图片描述

上面的代码的写法有点难以理解,如果写成以下这样,结果还会是一样吗?

#include <stdio.h>
#define N 5 // #define定义的标识符常量
float a[N];
float* p; // 全局变量默认初始化为0,而NULL本质上就是0
int main()
{
	for (p = &a[N - 1]; p >= &a[0];p-- )
	{
		*p = 0;
	}
	return 0;
}

【解析】

在这里插入图片描述

其实,简化过的代码实际在绝大部分的编译器上是可以顺利完成任务的,然而我们应该避免这样书写,因为标准规定并不保证它可行

标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。

五、指针和数组

  1. 指针和数组是不同的对象
  • 指针是一种变量,存放地址的,大小4/8字节的
  • 数组是一组相同类型元素的集合,是可以放多个元素的,大小是取决于元素个数和元素类型
  1. 数组的数组名是数组首元素的地址,地址是可以放在指针变量中,可以通过指针访问数组

如何用指针来访问数组呢?

在这里插入图片描述

或者还能这么写

在这里插入图片描述

【总结】

int arr[10];
int* p = arr;//首元素地址
这里有一层等价关系
arr[i] == *(arr + i) == *(i + arr) == i [arr]
i[arr]是可以正常使用的,因为[]只是一个操作符,i和arr是[ ]的操作数而已
(就像a + b同样也能写成b + a)。在编译的过程中,arr[i]也会被翻译成*(arr+i)

六、二级指针

6.1 什么是二级指针

在这里插入图片描述

变量a的地址存放在指针变量pa中,我们称pa是一级指针。指针变量也是变量,是变量就得有地址,pa的的地址存放在ppa中,所以我们称ppa二级指针

这里再解释一下,pa前面一颗的*是告诉我们pa是指针变量,而pa指向的aint类型,所以pa的类型就是int*;同样的,ppa前一颗的*告诉我们ppa是指针变量,而ppa指向的paint*类型的,所以ppa的类型就是int**

apappa关系如下:

在这里插入图片描述

形象点就是如下所示:

在这里插入图片描述

6.2 二级指针的使用

在这里插入图片描述

七、指针数组

7.1 什么是指针数组

指针数组是指针还是数组? 
--- 是数组(存放指针的数组)

可以类比整型数组和字符数组
整型数组是存放整型的数组
字符数组是存放字符的数组
那么指针数组就是存放指针(地址)的数组

举一个简单的例子:

在这里插入图片描述

7.2 用一维数组模拟二维数组

假设要模拟三行四列的数组

思路:开辟一个数组用来存放3个数组首元素的地址,因为数组在内存中是连续存放的,所以知道首元素的地址,后面也自然而然也就跟着知道了。

在这里插入图片描述

【代码实现】

#include <stdio.h>
int main()
{
	int a[] = { 1,2,3,4 };
	int b[] = { 5,6,7,8 };
	int c[] = { 8,10,11,12 };
	int* arr[] = { a,b,c };
	for (int i = 0; i < 3; i++)
	{
		// 偏移量
		for (int j = 0; j < 4; j++)
		{
			printf("%d ", *(arr[i] + j));
		}
		printf("\n");
	}
	return 0;
}

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

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

相关文章

RSA原理

RSA的历史 RSA加密算法是一种非对称加密算法&#xff0c;在公开密钥加密和电子商业中被广泛使用。RSA是由罗纳德李维斯特&#xff08;Ron Rivest&#xff09;、阿迪萨莫尔&#xff08;Adi Shamir&#xff09;和伦纳德阿德曼&#xff08;Leonard Adleman&#xff09;在1977年一…

本地推理,单机运行,MacM1芯片系统基于大语言模型C++版本LLaMA部署“本地版”的ChatGPT

OpenAI公司基于GPT模型的ChatGPT风光无两&#xff0c;眼看它起朱楼&#xff0c;眼看它宴宾客&#xff0c;FaceBook终于坐不住了&#xff0c;发布了同样基于LLM的人工智能大语言模型LLaMA&#xff0c;号称包含70亿、130亿、330亿和650亿这4种参数规模的模型&#xff0c;参数是指…

Lightening Network for Low-Light Image Enhancement 论文阅读笔记

这是2022年TIP期刊的一篇有监督暗图增强的文章 网络结构如图所示&#xff1a; LBP的网络结构如下&#xff1a; 有点绕&#xff0c;其基于的理论如下。就是说&#xff0c;普通的暗图增强就只是走下图的L1红箭头&#xff0c;从暗图估计一个亮图。但是其实这个亮图和真实的亮图…

54 # 可写流基本用法

内部也是基于 events 模块&#xff0c;fs.open、fs.write&#xff0c;如果文件不存在就会创建文件&#xff0c;默认会清空文件并写入 注意点&#xff1a;可写流的 highWaterMark 表示预期占用的内存&#xff08;达到或者超过预期后返回的值就是false&#xff09;&#xff0c;默…

确认应答机制与超时重发机制【TCP原理(笔记一)】

文章目录 通过序列号与确认应答提高可靠性正常的数据传输数据包丢失的情况确认应答丢失的情况发送的数据 重发超时如何确定 通过序列号与确认应答提高可靠性 在TCP中&#xff0c;当发送端的数据到达接收主机时&#xff0c;接收端主机会返回一个已收到消息的通知。这个消息叫做…

TCP的三次握手以及以段为单位发送数据【TCP原理(笔记二)】

文章目录 连接管理TCP以段为单位发送数据 连接管理 TCP提供面向有连接的通信传输。面向有连接是指在数据通信开始之前先做好通信两端之间的准备工作。 UDP是一种面向无连接的通信协议&#xff0c;因此不检查对端是否可以通信&#xff0c;直接将UDP包发送出去。TCP与此相反&am…

2023-07-16:讲一讲Kafka与RocketMQ中零拷贝技术的运用?

2023-07-16&#xff1a;讲一讲Kafka与RocketMQ中零拷贝技术的运用&#xff1f; 答案2023-07-16&#xff1a; 什么是零拷贝? 零拷贝(英语: Zero-copy) 技术是指计算机执行操作时&#xff0c;CPU不需要先将数据从某处内存复制到另一个特定区域。这种技术通常用于通过网络传输…

layui的基本使用-Helloworld 三把斧的一把斧头的熟练起来

该笔记记录如何使用layui的模块化方法。 访问layui官网 Layui - 极简模块化前端 UI 组件库下载官网的layui压缩包文件&#xff0c;解压到本地文件夹&#xff0c;文件结构如下&#xff1a; vscode创建项目&#xff1b; 位置 测试写了三个文件夹&#xff0c;才测试出来bug 坑所在…

【深度学习笔记】偏差与方差

本专栏是网易云课堂人工智能课程《神经网络与深度学习》的学习笔记&#xff0c;视频由网易云课堂与 deeplearning.ai 联合出品&#xff0c;主讲人是吴恩达 Andrew Ng 教授。感兴趣的网友可以观看网易云课堂的视频进行深入学习&#xff0c;视频的链接如下&#xff1a; 神经网络和…

JAVASE-Java概述与环境搭建(一)

文章目录 一.内容摘要二.引言2.1.何为编程&#xff1f;2.2.什么是计算机编程语言&#xff1f;2.3.编程语言发展史2.3.1.打孔机2.3.2.汇编语言2.3.3.高级语言2.3.3.1.C语言2.3.3.2.C语言2.3.3.3.PHP语言2.3.3.4. .NET语言2.3.3.5. Ruby2.3.3.6. python2.3.3.7. Java 2.3.4.编程语…

代码随想录算法训练营之JAVA|第四天| 24. 两两交换链表中的节点

今天是第 天刷leetcode&#xff0c;立个flag&#xff0c;打卡60天。 算法挑战链接 力扣http://24. 两两交换链表中的节点 第一想法 看到题目的第一想法是交换节点&#xff0c;于是赶紧拿出草稿本画了出来。这不简简单单。 1 -> 2 ->3 ->..... 已有的条件&#x…

WAF相关知识及安全狗的部署和绕过

文章目录 一&#xff1a;WAF基础知识&#xff08;一&#xff09; WAF简介&#xff08;二&#xff09; WAF工作原理1&#xff09; 流量识别2&#xff09; 攻击检测3&#xff09; 攻击防御4&#xff09; 记录日志 &#xff08;三&#xff09; WAF分类&#xff08;四&#xff09; …

云原生|kubernetes|kubernetes集群部署神器kubekey的初步使用(centos7下的kubekey使用)

前言: kubernetes集群的安装部署是学习kubernetes所需要面对的第一个难关&#xff0c;确实是非常不好部署的&#xff0c;尤其是二进制方式&#xff0c;虽然有minikube&#xff0c;kubeadm大大的简化了kubernetes的部署难度&#xff0c;那么&#xff0c;针对我们的学习环境或者…

[Linux笔记]常见命令(持续施工)

常见命令 文件与目录命令 pwd 打印当前所在路径。 建议每次登录后&#xff0c;或长时间未操作时&#xff0c;进行操作前都先执行pwd以确认当前位置。 cd 进入指定目录(change direct) .当前路径 ..上级路径 windows下&#xff0c;\为路径分隔符 Linux下&#xff0c;/为路径…

4.6.tensorRT基础(1)-实际模型上onnx文件的各种操作

目录 前言1. onnx1.1 读取节点1.2 修改节点1.3 替换节点1.4 删除节点1.5 修改input和output1.6 预处理的接入 总结 前言 杜老师推出的 tensorRT从零起步高性能部署 课程&#xff0c;之前有看过一遍&#xff0c;但是没有做笔记&#xff0c;很多东西也忘了。这次重新撸一遍&#…

在if/else中进行函数声明

console.log("第一次输出a: ", a) //输出 本地a if (true) {// 这里js隐式的把function a的定义放到这里来了&#xff0c;此刻这里有一个 块aa 1 // 将 块a的值 由函数修改为1console.log("第二次输出a: ",a) // 此时输出的是 块a的值function a() {} // …

【Kubernetes运维篇】RBAC之准入控制器详解

文章目录 一、ResourceQuota准入控制器1、ResourceQuota是什么&#xff1f;2、限制CPU、内存、Pod数量、Deployment数量3、限制存储空间大小 二、LimitRanger准入控制器1、LimitRanger是什么&#xff1f;2、LimitRanger限制案例 一、ResourceQuota准入控制器 中文官方参考文档…

前端Vue仿美团右侧侧边栏弹框筛选框popup alert

随着技术的发展&#xff0c;开发的复杂度也越来越高&#xff0c;传统开发方式将一个系统做成了整块应用&#xff0c;经常出现的情况就是一个小小的改动或者一个小功能的增加可能会引起整体逻辑的修改&#xff0c;造成牵一发而动全身。 通过组件化开发&#xff0c;可以有效实现…

Linux dpkg和dpkg-deb常用参数使用说明

名词解释 “dpkg ”是“ Debian Packager ”的简写。为“Debian” 专门开发的套件管理系统&#xff0c;方便软件的安装、更新及移除。所有源自“Debian”的“Linux ”发行版都会使用 “dpkg”&#xff0c;例如 “ Ubuntu ”、“Knoppix ”等。 dpkg-deb和dpkg的区别 dpkg-de…

golang使用bcrypt包对密码进行加密

bcrypt bcrypt是一个由美国计算机科学家尼尔斯普罗沃斯&#xff08;Niels Provos&#xff09;以及大卫马齐耶&#xff08;David Mazires&#xff09;根据Blowfish加密算法所设计的密码散列函数&#xff0c;于1999年在USENIX中展示。实现中bcrypt会使用一个加盐的流程以防御彩虹…