C语言——指针

一、定义

指针也就是内存地址,指针变量是用来存放内存地址的变量。

将内存以一个字节分为一个个内存单元,每个内存单元都进行编号,这个编号就是地址,也就是指针。

	int b = 1;int *pb = &b;//这里的pb变量是一个整型指针变量,用来存放整型变量b的地址

我们可以通过&(取地址操作符)得到一个变量的地址,然后将地址存到一个指针变量中,可以用这个指针变量来访问那个变量。

二、指针的大小

对于32位机器,假设CPU与内存之间有32条地址线,每一根寻址线在工作时会产生高电平(代表1)和低电平(代表0),则这个机器可以产生

00000000 00000000 00000000 00000000

11111111 11111111 11111111 11111111

这么多的地址,一共是2 ^ 32 个地址,一个地址指向的内存单元是一字节,所以这么多地址可以指向大约4GB的内存。

对于64位机器,假设CPU与内存之间有64条地址线,每一根寻址线在工作时会产生高电平(代表1)和低电平(代表0),则这个机器可以产生

00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111

这么多的地址,一共是2 ^ 64 个地址,一个地址指向的内存单元是一字节,所以这么多地址可以指向更大的内存。

这里我们发现,地址的大小是取决于地址线的多少的(或者是系统位数),对于32位机器,地址是32位的,也就是32bit的大小,所以是4字节的大小,所以32位机器的地址大小是4字节;相应的64位机器的地址是8字节。

#include <stdio.h>int main()
{int b = 1;int *pb = &b;printf("%zu\n", sizeof(pb));return 0;
}

64位平台输出结果:

32位平台输出结果:

三、指针的类型

1、指针变量的类型

  • int *:指向整型数据的指针
  • char *:指向字符数据的指针
  • float *:指向浮点数据的指针
  • double *:指向双精度浮点数据的指针
  • void *:通用指针,可以指向任何类型的数据
  • int **:指向int *指针的指针
  • char ***:指向char **指针的指针的指针

等等。

2、指针类型的意义

对于不同类型的指针,只要平台位数是一定的,则指针的大小是一定的。

	int* pa = NULL;double* pb = NULL;char* pc = NULL;printf("%zu\n", sizeof(pa));printf("%zu\n", sizeof(pb));printf("%zu\n", sizeof(pc));

运行结果:

那既然说这样的话,不同类型指针的大小是相同的,为什么不只设定一种指针呢?

(1)指针解引用

这是因为不同类型指针指向的变量类型不同,而不同变量的大小有不同,我们知道一个变量的指针指向的是变量的第一个地址,例如int类型有4个字节,则这4个字节有4个指针,而指向这个int类型变量的指针是这个变量的首地址,如果只有一种指针的话,那我们通过指针就不能正确地访问不同的变量了。

不同类型的指针虽然在内存中占用相同大小的空间(在特定的平台上),但它们指向的数据类型不同,这就要求指针在解引用(dereferencing)时能正确地解释所指向的内存区域的大小和类型。当你解引用指针获取它所指向的值时,指针的类型决定了从指针指向地址开始的内存中读取多少数据,以及如何解释这些数据。例如,整型指针会读取4个字节(在大多数现代平台上),而字符指针只会读取一个字节。

	int a = 0x11223344;int* pa = &a;*pa = 0;//通过解引用经由整型指针变量pa对整型变量a操作

对于变量a,它占4个字节,通过解引用经由整型指针变量pa对整型变量a操作后,直接改变了4个字节的内容。

这里我们强行将整型变量a的地址存在char型指针变量中:

	int a = 0x11223344;char* pc = (char*)&a;//强行将整型变量a的地址存在char型指针变量中*pc = 0;//通过解引用经由整型指针变量pc对整型变量a操作

对于变量a,它占4个字节,这次通过解引用经由整型指针变量pc对整型变量a操作后,只改变了1个字节的内容。

这足以体现指针变量类型的重要性。

(2)指针算数

以及指针在进行指针算数时,指针类型还决定了执行指针算术时的行为,或者说决定了指针的步长。比如说,对于指向一个整数(通常占4个字节)的指针int *p,执行p + 1时,地址会增加sizeof(int)个单位,确保p + 1指向下一个整数的起始位置。而对于指向一个字符(通常占1个字节)的指针char *c,执行c + 1时,地址只会增加sizeof(char)个单位。

	int a = 1;int* pa = &a;char* pc = (char*)&a;printf("%p\n", pa);printf("%p\n", pa + 1);printf("----------------\n");printf("%p\n", pc);printf("%p\n", pc + 1);

运行结果:

对于int*类型指针的步长是4字节,而char*类型指针的步长是1字节。

(3)类型安全

不同类型的指针帮助语言保持类型安全,确保不会将整型数据解释为浮点数,或者反过来。这有助于避免许多类型相关的错误。

	int a = 1;int* pi = &a;*pi = 100;

这里正常将整型指针变量指向整型变量,然后通过解引用经由整型指针变量pi对整型变量a操作后,a中的值是正确的:

	int a = 1;float* pf = (float*)&a;*pf = 100.0f;

这里我们将浮点数指针变量指向整型变量,然后通过解引用经由浮点数指针变量pf对整型变量a操作后,a中的值是错误的:

(4)其他意义

还有一些原因:

  1. 函数指针:函数指针是另一种特别的数据类型,它们的大小也是统一的,但是它们指向的是函数而不是普通的数据类型。它们允许程序动态地调用不同的函数,并且为了安全和正确地执行函数调用,需要对应的类型信息。

  2. 抽象和接口设计:在面向对象的编程中,特别是在使用多态的情况下,不同类型的指针可以指向一个继承体系中的不同对象。这样,同一个函数可以接受不同类型的对象作为参数,根据对象的实际类型来调用相应的方法。

  3. 数据对齐:某些类型的数据需要在内存中特定的对齐方式。类型化指针确保了正确的对齐,这对于硬件访问是很重要的,因为某些硬件架构要求特定类型的数据在内存中的特定对齐。

通过使用不同类型的指针,编程语言提供了一种丰富的方法来操作内存中的数据,同时也确保了访问这些数据的正确性和效率。

这些同样也体现了指针类型的重要意义。

四、野指针

野指针是指那些没有被初始化的指针,或者说它们的值是随机的,因此它们指向的是不确定的内存位置。访问野指针指向的内存同样会导致不可预料的行为或程序崩溃。

(1)未初始化的指针

当一个指针变量被声明但没有被显式初始化时,它将包含一个随机的内存地址,这种指针是野指针。

	int* p;//未初始化的指针,没有明确指向,是野指针

(2)指针操作越界

指针在进行算术运算时超过了其所指向的缓冲区或数组边界,可能会导致指针指向一个非法区域。

	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = arr;//这里的整型指针指向的是数组首元素printf("%d\n", p + 10);//这样操作是访问了数组的第十一个元素,而这个数组只有十个元素,所以是越界访问,p + 10是野指针

(3)其他情况

  1. 指针赋值错误: 由于编程错误,指针可能被赋值为一个意外的非法地址,例如指针类型转换错误等。

使用野指针进行操作是非常危险的,因为它们可能导致程序崩溃、数据损坏或安全漏洞。因此,最佳实践是始终确保指针在使用前已正确初始化,避免野指针的出现。

五、悬空指针

悬空指针(Dangling Pointer),也称为悬挂指针或迷失指针,是指向一块曾经分配过的内存的指针。但由于某些原因,这块内存已经不再有效,指针仍然指向那个地址,此时的指针被称为悬空指针。使用悬空指针访问数据是危险的,因为原来的内存可能已经被重新分配或释放,其内容可能已发生变化或不再属于程序的地址空间。以下是造成悬空指针的一些常见情况:

 (1)指向栈内存的指针在函数返回后

如果一个指针指向了一个函数内的局部变量(栈内存),而该函数返回后该变量的生命周期结束,这时候这个指针也会变成悬空指针。

#include <stdio.h>int* test()
{int a = 0;return &a;
}int main()
{int* p = test();//整型指针变量p接受的值是a的地址,所以整型指针变量p指向a,函数返回后,整型变量a的生命周期结束,然而p依旧指向a原来的地址,p变成野指针return 0;
}

(2)已释放的内存指针

当使用freedelete释放了某个指针指向的内存后,如果没有将该指针置为NULL,它仍然包含释放内存的地址,这种指针也成为悬空指针。

int *ptr = malloc(sizeof(int));
*ptr = 1;
free(ptr); // ptr现在是悬空指针。

最佳的做法是在释放指针指向的内存后,立即将指针设置为NULL,这有助于防止悬空指针的出现,因为NULL指针的解引用是确定性的行为,通常会导致程序的安全终止。

六、如何避免野指针和悬空指针

1、在创建指针变量后初始化

	int a = 0;int* p = &a;//初始化p的值为a的地址

如果没有明确的值去初始化,则初始化为NULL:

	int* p = NULL;//初始化p的值为NULL

因为NULL指针的解引用是确定性的行为,通常会导致程序的安全终止。

2、其他方法

(1)小心数组越界

(2)指针指向的空间释放时,及时置为NULL

(3)避免返回局部变量的地址

(4)使用指针之前检查有效性

七、指针的算数运算

1、指针加法 (ptr + n)

一个指针与一个整数相加时,结果是一个新的指针,它指向相对于原指针向后移动n个元素的位置。这里的n是与指针类型对应的数据类型的大小的倍数。例如,如果你有一个指向int的指针int *p;,并且int占用4个字节,则p + 1会得到一个新的地址,比p的地址高4个字节。

	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = &arr[0];printf("%p\n%p\n", p, p + 1);

地址相差四个字节。

	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = &arr[0];//指向数组首元素的地址if (*(p + 1) == arr[1])//数组第二个元素{printf("相等!\n");}

p + 1直接跳到数组的下一个元素。

2、指针减法 (ptr - n)

一个指针与一个整数相减时,结果是一个新的指针,它指向相对于原指针向前移动n个元素的位置。与指针加法类似,移动的实际字节数取决于指针类型对应的数据类型的大小。

	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = &arr[1];printf("%p\n%p\n", p, p - 1);

地址相差四个字节。

	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = &arr[1];//指向数组第二个元素的地址if (*(p - 1) == arr[0])//数组首元素{printf("相等!\n");}

p - 1直接跳到数组的上一个元素。

3、指针间的减法 (ptr1 - ptr2)

两个类型相同的指针相减时,结果是它们之间相隔的元素个数。如果ptr1ptr2指向同一个数组的不同元素,则ptr1 - ptr2将得到一个整数,指示它们之间的距离。这个结果通常用于确定数组中的位置或计算偏移量。指向同一块内存空间的两个指针相减才有意义。

	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p1 = &arr[0];int* p2 = &arr[9];printf("%d\n", p2 - p1);

第1个元素与第10个元素之间相差9个元素。

4、指针递增和递减

使用++--运算符可以使指针向前或向后移动一个元素。例如,++ptr将指针移向下一个元素,而--ptr将指针移向前一个元素。

	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p;for (p = &arr[0]; p <= &arr[9]; ){*p = 0;p++;}

对数组的每个元素操作,将数组的每个元素赋值为0。

	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p;for (p = &arr[9]; p >= &arr[0]; ){*p = 10;p--;}

对数组的每个元素操作,将数组的每个元素赋值为10。

5、指针比较

指针可以使用关系运算符(>, <, >=, <=, ==, !=)进行比较。这些运算符通常用于比较同一数组或内存块内的指针位置。尝试比较不同数组或内存块的指针是未定义行为。

	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = arr;if (p == &arr[0]){printf("相等!\n");}

八、指针和数组

1、数组名

数组名在大多数情况下是首元素地址,两种情况除外,详见我之前的文章《C语言——数组》。

九、二级指针

二级指针是存放指针变量的指针变量。

在C语言中,二级指针是指向指针的指针,也就是说,它存储的是另一个指针的地址。二级指针通常用于动态多维数组的分配、函数中修改指针本身的值、以及处理指针数组等场景。

#include <stdio.h>int main()
{int var = 10;      //普通的整型变量int* ptr = &var;   //一级指针,指向整型变量var的指针int** pptr = &ptr; //二级指针,指向一级指针ptr的指针printf("var = %d\n", var);printf("*ptr = %d\n", *ptr);printf("**pptr = %d\n", **pptr);//使用二级指针访问var的值return 0;
}

十、指针数组

1、介绍

在C语言中,指针数组是一个数组,其每个元素都是一个指针。换句话说,指针数组是用来存储指针的数组。这种数据结构经常被用来存储字符串数组或者动态分配的结构体数组的指针。

	int a = 0;int b = 1;int c = 2;int* parr[3] = { &a,&b,&c };//指针数组存放指针

2、用指针数组模拟二维数组

因为数组名是首元素地址,所以可以用指针数组存数组的首元素地址实现二维数组的模拟。

#include <stdio.h>int main()
{int arr1[4] = { 1,2,3,4 };int arr2[4] = { 3,4,5,6 };int arr3[4] = { 5,6,7,8 };int* parr[3] = { arr1,arr2,arr3 };for (int i = 0; i < 3; i++){for (int j = 0; j < 4; j++){printf("%d ", *(parr[i] + j));}printf("\n");}return 0;
}

运行结果:

还可以这样:

#include <stdio.h>int main()
{int arr1[4] = { 1,2,3,4 };int arr2[4] = { 3,4,5,6 };int arr3[4] = { 5,6,7,8 };int* parr[3] = { arr1,arr2,arr3 };for (int i = 0; i < 3; i++){for (int j = 0; j < 4; j++){printf("%d ", parr[i][j]);}printf("\n");}return 0;
}

运行结果:

这个版本与上一个是一样的作用。

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

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

相关文章

某音关键词搜索商品接口,某音关键词搜索商品列表接口,宝贝详情页接口,某音商品比价接口接入方案

要接入API接口以采集电商平台上的商品数据&#xff0c;可以按照以下步骤进行&#xff1a; 1、找到可用的API接口&#xff1a;首先&#xff0c;需要找到支持查询商品信息的API接口。这些信息通常可以在电商平台的官方文档或开发者门户网站上找到。 2、注册并获取API密钥&#x…

计算机网络期末复习

计算机网络复习 1.第一章 1.2.2计算机啊网络的分类(认识) 按网络的覆盖范围进行分类&#xff1a; 局域网城域网广域网个人区域网 按网络的使用者进行分类&#xff1a; 公用网专用网 1.3互联网的组成(掌握) 从功能上可以划分为以下两大部分&#xff1a; 边缘部分&#x…

坐标经纬度的基本运算(2个坐标经纬度的距离、中心点坐标经纬度范围内的坐标计算)

现在的应用大都居于LBS服务&#xff0c;用户地理位置的获取&#xff08;经纬度坐标、所属行政区域&#xff09;&#xff0c;提供服务场所的地理位置也有行政区域信息和坐标信息。 用户与服务场所的联系&#xff0c;就近服务原则的设计&#xff0c;服务场所相对于用户的排序。 …

这些流行的K8S工具,你都用上了吗

关注【云原生百宝箱】公众号&#xff0c;获取更多云原生消息 本文介绍了一些流行的 Kubernetes 工具和常见的集群组件。例如 Helm 作为 Kubernetes 应用的包管理器&#xff0c;以及本地开发所需的 Kubernetes 发行版。另外提及了一些常见的集群组件&#xff0c;如集群自动缩放器…

软件测试|一篇文章带你深入理解SQL约束

深入理解SQL约束&#xff1a;保障数据完整性和一致性的重要工具 SQL约束是在关系型数据库中用于保障数据完整性和一致性的重要工具。本文将深入探讨SQL约束的概念、类型以及应用&#xff0c;以帮助读者更好地理解和使用SQL约束来确保数据库中的数据质量。 SQL约束 约束&…

“巴渝工匠杯”2022年重庆市职业院校技能大赛(高职组)云计算样题

“巴渝工匠杯”2022年重庆市职业院校技能大赛&#xff08;高职组&#xff09;云计算样题 需要软件包环境可私信博主 【赛程名称】云计算赛项第一场次-私有云 某企业拟使用OpenStack搭建一个企业云平台&#xff0c;以实现资源池化弹性管理、企业应用集中管理、统一安全认证和授…

2024年【北京市安全员-A证】考试试卷及北京市安全员-A证试题及解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 北京市安全员-A证考试试卷根据新北京市安全员-A证考试大纲要求&#xff0c;安全生产模拟考试一点通将北京市安全员-A证模拟考试试题进行汇编&#xff0c;组成一套北京市安全员-A证全真模拟考试试题&#xff0c;学员可…

python-查漏补缺笔记-更新中

包导入时__init__.py中命令的执行顺序和sys.modules变化 ref: https://edu.csdn.net/skill/practice/python-3-6/164 在有父包和子包的情况下&#xff0c;父包中的“ __ init__.py”语句会在子包的“ __ init__.py”语句之前执行&#xff0c;然后按下列顺序执行导入子包和模块…

计算机毕业设计选题分享-spring boot疾病查询网站01548(赠送源码数据库)JAVA、PHP,node.js,C++、python,大屏数据可视化等

spring boot疾病查询网站 摘 要 随着互联网时代的到来&#xff0c;同时计算机网络技术高速发展&#xff0c;网络管理运用也变得越来越广泛。因此&#xff0c;建立一个B/S结构的疾病查询网站&#xff0c;会使疾病查询工作系统化、规范化&#xff0c;也会提高医院形象&#xff0c…

SpringBoot集成沙箱支付

前言 支付宝沙箱支付&#xff08;Alipay Sandbox Payment&#xff09;是支付宝提供的一个模拟支付环境&#xff0c;用于开发和测试支付宝支付功能的开发者工具。在真实的支付宝环境中进行支付开发和测试可能涉及真实资金和真实用户账户&#xff0c;而沙箱环境则提供了一个安全…

es6中import * as导入方式

es6中import * as导入方式 一、问题和解决方法二、简介import * as三、ES6 模块化语法导入导出1.导入2.导出 一、问题和解决方法 问题报错: export ‘default’ (imported as ‘XLSX’) was not found in ‘xlsx’ (possible exports: CFB, SSF, parse_xlscfb, parse_zip, read…

深入了解Swagger注解:@ApiModel和@ApiModelProperty实用指南

在现代软件开发中&#xff0c;提供清晰全面的 API 文档 至关重要。ApiModel 和 ApiModelProperty 这样的代码注解在此方面表现出色&#xff0c;通过增强模型及其属性的元数据来丰富文档内容。它们的主要功能是为这些元素命名和描述&#xff0c;使生成的 API 文档更加明确。 Api…

STC进阶开发(四)SPI协议、矩阵键盘、EEPROM

前言 这一期我们简单介绍一下SPI协议&#xff0c;然后我们学习一下矩阵键盘&#xff0c;了解EEPROM是干什么用的&#xff0c;话不多说&#xff0c;开整&#xff01; SPI协议 SPI&#xff08;Serial Peripheral Interface&#xff09;是一种同步串行通信协议&#xff0c;用于在…

rtsp解析视频流

这里先说一下 播放rtsp 视频流&#xff0c;尽量让后端转换一下其他格式的流进行播放。因为rtsp的流需要flash支持&#xff0c;现在很多浏览器不支持flash。 先说一下这里我没有用video-player插件&#xff0c;因为它需要用flash ,在一个是我下载flash后&#xff0c;还是无法播放…

Pytorch的GPU版本安装,在安装anaconda的前提下安装pytorch

本文基于conda安装GPU版本的PyTorch 一、CUDA 1.下载CUDA 点击下载 找到对应的版本进行下载 &#xff08;1&#xff09;打开命令提示符查看自己的版本&#xff0c;输入 nvidia-smi 根据自己的版本进行下载 &#xff08;2&#xff09;点击适合自己的版本进行下载 &#…

【MLOps】使用Ray缩放AI

Ray正在人工智能工程领域崭露头角&#xff0c;对扩展LLM和RL至关重要 Spark在数据工程中几乎是必不可少的。Ray正在人工智能工程领域崭露头角。 雷是伦敦大学学院Spark的继任者。Spark和Ray有很多相似之处&#xff0c;例如用于计算的统一引擎。但Spark主要专注于大规模数据分析…

【Python机器学习】k近邻——模型复杂度与泛化能力的关系

以某数据进行研究&#xff0c;先将数据集分为训练集和测试集&#xff0c;然后用不同的邻居数对训练集合测试集的新能进行评估&#xff1a; from sklearn.datasets import load_breast_cancer from sklearn.model_selection import train_test_split from sklearn.neighbors imp…

go执行静态二进制文件和执行动态库文件

目的和需求&#xff1a;部分go的核心文件不开源&#xff0c;例如验证&#xff0c;主程序核心逻辑等等 第一个想法&#xff0c;把子程序代码打包成静态文件&#xff0c;然后主程序执行 子程序 package mainimport ("fmt""github.com/gogf/gf/v2/os/gfile"…

ReCAPTCHA 解决方案的自动识别和解决方法

ReCAPTCHA&#xff0c;作为广泛使用的安全措施&#xff0c;旨在区分人类和自动化机器人。然而&#xff0c;技术的进步导致了自动识别和解决 ReCAPTCHA 挑战的方法的发展。在本文中&#xff0c;我们将探讨自动 ReCAPTCHA 识别和解决技术的概念&#xff0c;以及创新解决方案 Caps…

Yapi部署指南:在 Linux 上 Yapi 教程

YApi YApi 是高效、易用、功能强大的 api 管理平台&#xff0c;旨在为开发、产品、测试人员提供更优雅的接口管理服务。可以帮助开发者轻松创建、发布、维护 API&#xff0c;YApi 还为用户提供了优秀的交互体验&#xff0c;开发人员只需利用平台提供的接口数据写入工具以及简单…