C语言深度解析--指针

news2024/9/29 17:25:30

目录

指针

指针的定义:

指针的大小:

指针和指针类型

野指针

指针运算

指针+-整数:

指针-指针:

指针的关系运算:

指针和数组

二级指针

指针数组


理解指针的第一步是在机器级上观察指针表示的内容。大多数现代计算机都将内存分割为字节(byte),每个字节可以存储8位的信息。

每个字节都有唯一的地址,用来和内存中的其他字节相区别。如果内存中有n个字节,那么可以把地址看做0~n-1的数。

可执行程序由代码和数据两部分构成。程序中的每个变量占有一个或多个字节内存,把第一个字节的地址称为是变量的地址。

指针

指针的定义:

指针是个变量,存放内存单元地址(编号)的变量。

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

指针的大小:

对于32位的机器,每一位均由0或1组成,那么就能产生2^32次方的地址。每个地址标识一个字节,那我们就可以给2^32Byte=4GB的空间进行编址。

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

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

int main()
{
	int a = 10;
	int* pa = &a;

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

	printf("%d\n",sizeof(pa));//4
	printf("%d\n",sizeof(pc));//4

	return 0;
}

总结:

  1. 指针是用来存放地址的,地址是唯一标识一块地址空间的
  2. 指针的大小在32位平台是4个字节,在64位平台是8个字节

指针和指针类型

变量有不同的类型,那指针变量有没有类型呢?答案是肯定的。如下所示:

char* pc = NULL;
int* pc = NULL;
short* pc = NULL;
long* pc = NULL;
float* pc = NULL;
double* pc = NULL;

这里可以看到,指针的定义方式是:type+*。其实:char* 类型的指针是为了存放 char 类型变量的地址;short* 类型的指针是为了存放 short 类型变量的地址;int*类型的指针是为了存放 int 类型变量的地址。

那指针类型的意义是什么?

指针+/-整数:

int main()
{
	int a = 10;
	int* pa = &a;
	char* pc = &a;

	printf("%p\n",pa);
	printf("%p\n", pa + 1);

	printf("\n");

	printf("%p\n", pc);
	printf("%p\n",pc+1);

	return 0;
}

运行结果:

总结:

指针的类型决定了指针向前或者向后走一步有多大距离。

int*:+1-->+1*sizeof(int)==+4
char*:+1-->+1*sizeof(char)==+1

指针解引用:

案例一:

int main()
{
	int a = 0x11223344;
	int* pa = &a;
	*pa = 0;//操作了四个字节

	return 0;
}

字节的变换:由0x11223344->0x000000,变换了四个字节

 案例二:

int main()
{
	int a = 0x11223344;

	char* pa = &a;
	*pa = 0;//只能操作一个字节,warning C4133: “初始化”: 从“int *”到“char *”的类型不兼容

	return 0;
}

字节的变换:0x11223344->0x11223300,变换了一个字节

案例三:

int main()
{
	int arr[10] = { 0 };

	//假设是按照一个整型的形式访问
	int* p = arr;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*p = 0x11223344;
		p++;
	}

	return 0;
}

 字节的变换:每次循环,修改四个字节的内容

 案例四:

int main()
{
	int arr[10] = { 0 };

	//假设访问这40个字节的时候,是以字节为单位访问的
	char* p = (char*)arr;
	int i = 0;
	for (i = 0; i < 40; i++)
	{
		*p = 'x';
		p++;
	}

	return 0;
}

字节的变换: 每次循环,修改一个字节的内容

 总结:

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

野指针

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

野指针成因: 

1.指针未初始化

int main()
{
	int* p;//局部变量指针未初始化,默认为随机值
	*p = 20;

	return 0;
}

2.指针越界访问

int main()
{
	int arr[5] = {1,2,3,4,5};
	int i = 0;
	int* p = arr;
	for (i = 0; i < 10; i++)
	{
		printf("%d ",*p);//当指针指向的范围超出数组arr的范围时,p就是野指针
		p++;
	}
	return 0;
}

3.指针指向的空间释放

int* test()
{
	int a = 10;
	printf("%d\n",a);
	return &a;
}

int main()
{
	int* p = test();//变量a一旦离开test()函数,就自动回收

	*p = 100;

	return 0;
}

如何规避野指针:

  1. 指针初始化
  2. 小心指针越界
  3. 指针指向的空间被释放后,及时将指针置为NULL(空指针)
  4. 避免返回局部变量的地址
  5. 指针使用之前检查有效性
int main()
{
	int* p = NULL;
	int a = 10;
	p = &a;
	if (p != NULL)
	{
		*p = 20;
	}

	return 0;
}

指针运算

指针+-整数:

指针p加上整数j产生指向特定元素的指针,这个特定元素是p原先指向的元素后的j个位置。更确切地说,如果p指向数组元素a[i],那么p+j指向a[i+j]。如果p指向数组元素a[i],那么p-j指向a[i-j]

int main()
{
	int arr[4] = { 1,3,4,5 };
	int* p = arr;
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		printf("%d ", *p++);//先解引用再++
	}

	printf("\n");

	return 0;
}

指针-指针:

指针-指针的绝对值=指针和指针之间元素的个数

前提:两个指针必须指向同一个数组时,它们相减才有意义

int main()
{
	int arr[10] = {0};
	printf("%d\n",&arr[9]-&arr[0]);//9

	return 0;
}
//实现strlen函数
int my_strlen(char* str)
{
	char* start = str;
	while (*str)
	{
		str++;
	}
	return str - start;
}

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

	return 0;
}

指针的关系运算:

可以用用关系运算符(<,<=,>和>=)和判等运算符(==和!=)进行指针比较。只有在两个指针指向同一数组时,用关系运算符进行的指针比较才有意义。比较的结果依赖于数组中两个元素的相对位置。

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

指针和数组

数组名是数组首元素的地址

a+i等于&a[i];     *(a+i)=a[i]

两个例外:

  1. sizeof(数组名),表示整个数组,计算的是真整个数组的大小
  2. &数组名,数组名表示整个数组,取出的是整个数组的地址
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("%p\n", arr);
	printf("%p\n", arr + 1);
	printf("\n");

	printf("%p\n",&arr[0]);
	printf("%p\n", &arr[0] + 1);
	printf("\n");

	printf("%p\n", &arr);
	printf("%p\n", &arr+1);
	printf("\n");
	
	printf("%d\n",sizeof(arr));

	return 0;
}

 运行结果:

使用指针遍历数组:

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };

	int* p = arr;//指针存放数组首元素的地址
	int sz = sizeof(arr) / sizeof(arr[0]);

	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ",*(p+i));
	}
	return 0;
}

 惯用法:

for (p = a; p < a + N; p++)
{
	sum += *p;
}

虽然可以把数组名用作指针,但是不能给数组名赋新的值,试图使数组名指向其他地方是错误的:

while (*a != 0)
	a++;

这一限制不会带我们造成什么损失:我们可以把a复制给一个指针变量,然后改变该指针变量:

p = a;
while (*p != 0)
	p++;

二级指针

指针变量是变量,是变量就有地址,那指针变量的地址存放在哪里?答案是存放在二级指针里

int main()
{
	int a = 10;
	int* pa = &a;//pa是个指针变量(一级指针)

	int* * ppa = &pa;//ppa是一个二级指针,*告诉我们ppa是指针,int*说明ppa指向的对象是int*
	//*ppa通过对ppa中的地址进行解引用,这样找到的是pa,*ppa其实访问的就是pa
	return 0;
}

具体分析:

指针数组

指针数组是个数组,存放的是指针

案例一:

int main()
{
	int a = 10;
	int b = 11;
	int c = 12;
	int d = 13;
	int e = 14;

	int* arr[5] = {&a,&b,&c,&d,&e};

	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d ",*(arr[i]));
	}

	printf("\n");

	return 0;
}

运行结果:

案例二:

int main()
{
	int data1[] = {1,2,3,4,5};
	int data2[] = {6,7,8,9,10};
	int data3[] = {11,12,13,14,15};

	int* arr[3] = {data1,data2,data3};

	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);//*(arr[i]+j)
		}
		printf("\n");
	}

	return 0;
}

运行结果:

 

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

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

相关文章

第十六届全国大学生信息安全竞赛创新实践赛初赛部分WP AGCTF战队

持续两天的比赛&#xff0c;打的很累&#xff0c;web没有出太多的题&#xff0c;比赛被pwn师傅带飞了&#xff0c;希望下此加油&#xff0c;下边是此次比赛排名。 文章目录 MISC签到卡被加密的生产流量国粹调查问卷pyshell CRYPTO基于国密SM2算法的密钥密文分发可信度量Sign_i…

Java中的深拷贝和浅拷贝介绍

文章目录 基本类型和引用类型Clone方法浅拷贝深拷贝小结 在讲解什么是深拷贝和浅拷贝之前&#xff0c;我们先来了解一下什么是基本类型和引用类型。 基本类型和引用类型 基本类型也称为值类型&#xff0c;分别是字符类型 char&#xff0c;布尔类型 boolean以及数值类型 byte、…

Vue3 项目相关

vite 项目起步式 npm create vite - 1.命名项目名称- 2. 选择技术框架- 3. 进入项目文件夹 npm i 安装依赖&#xff0c;- 4. npm run dev 运行项目配置 package.json 文件 &#xff0c;使项目运行后自动再浏览器中打开。 在 dev 运行命令后添加一个 --open 即可。 "script…

微信小程序初识

微信小程序 因(ios&#xff0c;android)多平台彼此间并不互通&#xff0c;所以开发需要两个不同平台的开发团推队&#xff0c;所以微信小程序因此诞生。 小程序的优点 快速加载更强大的能力原生的体验易用且安全的微信数据开放高效和简单的开发 首先 根据自己的情况安装微…

弄懂软件测试左移和右移,靠它就行

软件测试技术应当贯穿整个软件开发生命周期、对软件产品&#xff08;包括阶段性产品&#xff09;进行验证和确认的活动过程&#xff0c;其核心目标是尽快尽早地发现软件产品中所存在的各种问题 bug—— 与用户需求、预先定义的不一致性。 传统的软件测试流程是 接到项目后参与…

cubemx stm32 pca9685pw模块 16路PWM 可用于舵机驱动 驱动代码

资料 淘宝链接请点这里 淘宝资料资料&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1Kda-c7QdZdQ03FBMa0zeRA 提取码&#xff1a;1234 pca9685pw介绍 这个模块是 I2C 通信控制 16 路 PWM 的模块。 所有路的 频率 是统一设置的&#xff0c;所以每一路的频率都一样&a…

java单元测试( Hamcrest 断言)

java单元测试( Hamcrest 断言) 单元测试特征: 1 范围狭窄 2 限于单一类或方法 3 体积小 为什么要编写单元测试&#xff1f; 为了防止错误&#xff08;很明显&#xff01;&#xff09; 而且还可以提高开发人员的生产力&#xff0c;因为单元测试&#xff1a; (1) 帮助实施——在…

网工内推 | 经验不限,国企招网工,IE认证优先,五险一金

01 一九零五&#xff08;北京&#xff09;网络科技有限公司 &#x1f537;招聘岗位&#xff1a;网络工程师 &#x1f537;职责描述&#xff1a; 1、负责公司内部现有网络配置及调优&#xff1b; 2、负责IT机房的网络和安全的日常维护工作&#xff1b; 3、负责IT机房的紧急故…

轻松掌握redis缓存穿透、击穿、雪崩问题及解决方案(20230529版)

1、缓存穿透 所谓缓存穿透就是非法传输了一个在数据库中不存在的条件&#xff0c;导致查询redis和数据库中都没有&#xff0c;并且有大量的请求进来&#xff0c;就会导致对数据库产生压力&#xff0c;解决这一问题的方法如下&#xff1a; 1、使用空缓存解决 对查询到值是空的…

【Python开发】FastAPI 02:请求参数—路径参数、查询参数

进行接口请求时&#xff0c;请求参数是重中之重了&#xff01;请求参数指客户端向服务端发送请求时&#xff0c;需要传递给服务端的参数&#xff0c;包括路径参数、查询参数、请求体等。举个例子&#xff0c;如果客户端想要获取某个用户的信息&#xff0c;可以向服务端发送一个…

PHPMySQL基础(一):创建数据库并通过PHP进行连接

PHP同样可以对数据库进行连接&#xff0c;并且实现增删改查、登录注册等功能&#xff0c;这一篇写一下怎么使用PHP去连接MySQL数据库 目录 一、创建数据库 1.1 登录页面 1.2 创建数据库 1.3 创建数据库表 1.4 添加表字段 1.5 插入数据 1.6 导出和导入 二、PHP连接数据…

华为OD机试真题B卷 Java 实现【报文重排序】,附详细解题思路

一、题目描述 对报文进行重传和重排序是常用的可靠性机制&#xff0c;重传缓冲区内有一定数量的子报文&#xff0c;每个子报文在原始报文中的顺序已知&#xff0c;现在需要恢复出原始报文。 二、输入描述 输入第一行为N&#xff0c;表示子报文的个数&#xff0c;0 < N &l…

SpringBoot 配置文件和日志文件

目录 一、SpringBoot配置文件 配置文件的格式 .properties配置文件格式 .yml配置文件格式 .properties 与 .yml的区别 配置文件的读取 .properties 与 .yml的区别 设置不同环境的配置⽂件 二、SpringBoot日志文件 日志打印的步骤 得到日志对象 方法一&#xff1a;使…

vulnhub靶场之RAGNAR LOTHBROK: 1

1.信息收集 探测存活主机&#xff0c;输入&#xff1a;netdiscover -r 192.168.239.0/24 &#xff0c;发现192.168.239.178存活。 对目标主机192.168.239.178进行端口扫描&#xff0c;发现存活21(ftp)、80、443、3306端口。 浏览器访问http://192.168.239.178&#xff0c;发…

设计模式 - 代理模式

基本介绍: 代理模式&#xff1a;为一个对象提供一个替身&#xff0c;以控制对这个对象的访问。即通过代理 对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的 功能操作,即扩展目标对象的功能。被代理的对象可以是远程对象、创建开销大的对象或需要安全控…

chatgpt赋能python:Python三次方根的用途和计算方法

Python三次方根的用途和计算方法 如果您是一位Python编程工程师&#xff0c;您可能会经常需要用到Python的数学计算功能。在这篇文章中&#xff0c;我们将探讨Python三次方根的概念和使用&#xff0c;以及如何在Python中计算三次方根。 什么是三次方根&#xff1f; 三次方根…

SpringBoot 框架

SpringBoot 框架 SpringBoot 简介SpringBoot 开发步骤SpringBoot工程快速启动SpringBoot概述起步依赖自动装配切换web服务器 配置文件配置文件格式yaml格式yaml配置文件数据读取Value注解读取配置文件Environment对象自定义对象多环境配置 SpringBoot 整合 SpringBoot 简介 Sp…

这个 计数排序详解过程 我能吹一辈子!!!

文章目录 计数排序概念计数排序算法思路计数排序算法过程计数排序代码实现计数排序缺陷 计数排序概念 计数排序是一个非基于比较的排序算法&#xff0c;元素从未排序状态变为已排序状态的过程&#xff0c;是由额外空间的辅助和元素本身的值决定的。该算法于1954年由 Harold H.…

流行框架(一)EventBus(组件通信)、ARouter(页面跳转)

文章目录 EventBus基本使用EventBus三要素五种线程模式使用步骤EventBus黏性事件&#xff08;sticky event&#xff09; 工作原理中介者模式源码解读Event Bus中介者对象register() / 注册发布事件 / post Arouter组件化开发组件化开发的优点组件分层方案组件化的gradle工程 AR…

C919商业首航 背后功臣风洞实验室有多牛

5月28日&#xff0c;国产大型客机C919&#xff0c;顺利完成商业首航。 首航背后意味着该机型从研发、制造、取证到投运全面贯通&#xff0c;广大旅客终于有机会坐国产大飞机出行了。 很多人不知道C919其实是依托我国独立自主设计制造的世界级风洞群和风洞实验室反复测试“百炼…