【初阶C语言】指针的妙用

news2024/12/23 13:14:19

前言:在C语言中,有一个非常重要的知识点,叫做指针,指针也是数据类型中的一种。在本节内容中,我们就一起来学习指针。


 学习一个新知识的时候,我们需要从这几个方面:指针是什么,指针是怎么访问数据(指针是怎么工作的),也就是弄清楚指针类型的作用,怎么运用指针,还有注意使用指针时常犯的错误,做好上面,你对指针的学习就已经非常棒了。

1. 指针是什么
2. 指针和指针类型
3. 野指针
4. 指针运算
5. 指针和数组
6. 二级指针
7. 指针数组

本节重点,介绍指针类型的意义


一、介绍指针

讲到指针,就应该要想到跟他关联的东西--地址。内存会被分成一个个小的单元,每个单元的单位是字节,所以每个内存都会有一个编号,该编号就是内存的地址,通过地址就可以找到该内存。

指针的概念:指针就是地址,口头说的指针就是指针变量,用来存放地址。存放的地址其实就是内存的编号。

指针变量==地址==单元编号

注意:存放在指针变量中的数据,会当成地址来处理。

代码对指针的解释:

#include<stdio.h>
int main()
{
	int a = 10;//定义一个变量a
	int* p = &a;//取出变量a的地址存放到变量p中
	return 0;//因为变量p存放的是地址,所以称为指针变量
}

 代码解读:int说明指针变量所指的对象是int型,*在说明该变量是指针类型。

总结:

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

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

由上面可知,指针的内存大小都是4/8个字节,为什么指针还有指针类型区分呢?

二、指针和指针类型

前言:我们要学习指针类型,目的就是要知道指针类型存在的意义和怎么使用。

1.指针类型的分类

字符型指针charchar* p1
短整形指针shortshort* p2
整形指针intint* p3
长整形指针longlomg* p4
单精度浮点型指针floatfloat* p5
双精度浮点型指针doubledouble* p6

既然指针有这么多类型,我们可以对它们的内存大小一探究竟。

可以通过代码的方式一探究竟:

#include<stdio.h>
int main()
{
	printf("%d\n", sizeof(char*));
	printf("%d\n", sizeof(short*));
	printf("%d\n", sizeof(int*));
	printf("%d\n", sizeof(long*));
	printf("%d\n", sizeof(float*));
	printf("%d\n",sizeof(double*));
	return 0;
}

两种平台下运行的结果: 

总结:

  无论是哪一种平台下计算的结果,每种指针类型的内存大小都一样,都是4或8个字节。

2.指针类型的访问权限

    指针的访问是什么意思呢?就是通过解引用操作符(*)找到地址所指向的对象,再对其操作的过程,该操作一般是修改数据。

我们先看代码:

正常修改:

#include<stdio.h>
int main()
{
	int a = 0x11223344;//这里表示十六进制的数字
	int* p = &a;
	printf("%d\n", a);

	*p = 0;//解引用操作把a修改成0
	printf("%d\n",a);
}

运行结果:

 输入的0x11223344是十六进制,打印的结果287454020是十进制。

 我们看一下调试的结果图:

 ox11223344解读:0x的意思是表示十六进制的数字,这里是为了方便观察。整形int的内存大小是4个字节,也就是32个比特位(二进制);因为四个二进制数字可以转化成一个十六进制数字,所以为了方便,系统会自动把二进制转化成十六进制(8位十六进制)。

 

总结: 以上是为了说明数据存储在内存中是以十六进制存储。两个十六进制数字=一个字节,这个知识点很重要,下面会用到。所以我们对变量a赋值0x11223344,因为已经是十六进制数字,存在内存中就是11223344的形式,方便我们观察而已。

 上面的内容是引入,下面开始重点:

 

我们正常输入数据并修改:

 不正常修改的代码:

#include<stdio.h>
int main()
{
	int a =0x5201314;
	char* p = &a;//注意这里是用char类型的指针
	printf("%d\n", a);

	*p = 0;//解引用操作把a修改成0
	printf("%d\n", a);
}

 这里与上面不一样的区别:指针类型是char*,它一样可以存放变量a的地址。

运行结果:

 输入的数据是十六进制的0x5201314,第一次打印的结果是十进制的85988116,所以在所以观察会不明显,我们看在内存中的存储。

内存存储图:

 这里为什么不是像上面一样是把所有的数据全部修改成0呢?因为char指针类型每次只能访问一个字节的大小,所以每次修改的权限只有一个字节。

我们可以看short类型:

 修改后:

 上面那么多的例子目的是说明不同的指针类型有不同的访问权限,像char每次只能访问一个字节,short访问两个字节,int访问四个字节,取决于类型的内存大小,但是指针的大小是一样的。

总结1:不同的指针类型,可以访问数据的权限不一样

3.不同指针类型的运加减性质

这里要介绍的内容就是不同的指针类型+1或-1的结果。

例题:

未修改的代码:

#include<stdio.h>
int main()
{
	int arr[7] = { 5,2,0,1,3,1,4 };
	int* p1 = &arr;
	int i = 0;
	for (i = 0; i < 7; i++)
		printf("%d ",*(p1+i));
	printf("\n");
	return 0;
}

运行结果1:

 这里跟一般的访问数组不一样,这里是通过指针来访问每个数组。

把数组赋值成0后打印:

#include<stdio.h>
int main()
{
	int arr[7] = { 5,2,0,1,3,1,4 };
	int* p1 = &arr;
	int i = 0;
	for (i = 0; i < 7; i++)
		printf("%d ", *(p1 + i));//第一次打印
	printf("\n");
	i = 0;
	for (i = 0; i < 7; i++)
		*(p1 + i) = 0;//全部赋值成0
	i = 0;
	for (i = 0; i < 7; i++)
		printf("%d ", *(p1 + i));//第二次打印
	return 0;
}

运行结果2;

 用其他类型指针来修改数据:

#include<stdio.h>
int main()
{
	int arr[7] = { 5,2,1,1,3,1,4 };
	char* p1 = &arr;
	int i = 0;
	for (i = 0; i < 7; i++)
		printf("%d ", arr[i]);//第一次打印
	printf("\n");
	i = 0;
	for (i = 0; i < 7; i++)
		*(p1 + i) = 0;//全部赋值成0
	i = 0;
	for (i = 0; i < 7; i++)
		printf("%d ", arr[i]);//第二次打印
	return 0;
}

运行结果2:

这里的结果又跟上面的不一样了,没有将数据全部修改成0。由上面我们知道,char指针每次只能访问一个字节,而数组为整形,每个数据占用四个字节。

当char指针+1的时候,是跳过一个字节,所以+1一次是没有办法访问到数组的第二个元素,也就没有办法修改。

总结2:指针类型的意义在于,决定指针+1或者-1跳过多少个字节。

指针类型大总结1.指针类型决定了指针解引用操作的时候,访问几个字节

                             2.指针类型决定了指针+1或-1操作跳过几个字节

三、野指针

野指针,其实就是非法指针,并不是要学习使用野指针,而是为了避免野指针的出现而去认识

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

出现野指针大概有这些情况:

1.指针未初始化

2.指针越界访问

3.返回局部变量的地址

4.释放的指针未及时置空

1.指针未初始化

#include<stdio.h>
int main()
{
	int* p;//指针未初始化,为野指针
	return 0;
}

像这样,创建指针的时候没有让他指向某块空间,也没有置空,这样就是野指针。

正确做法:

#include<stdio.h>
int main()
{
	int* p = NULL; // 指针置空赋值NULL;
	return 0;
}

一般默认指针变量置空赋值NULL,普通变量赋值为0。另外,NULL默认值为0。

2.指针越界访问数组

下面的是错误代码:

#include<stdio.h>
int main()
{
	int arr[5] = {0};
	int* p = arr;
	int i = 0;
	for (i=0;i<=5;i++)//这是错误代码
	{
		*p = 520;
		p++;
	}
	return 0;
}

因为数组的大小为5,循环会走6次,从而越界访问,我们看一下这种情况下的运行结果;

3.返回局部变量的地址

#include<stdio.h>
int* test()
{
	int a = 0;//局部变量a出了test函数就会被销毁
	return &a;
}
int main()
{
	int* p = test();
	printf("%d\n",*p);
	return 0;
}

因为出了test函数,局部变量a就已经被销毁了,本来属于局部变量a的地址,现在却已经不是他的了。此时这块地址的指向是不确定的

4.关于置空的问题

当指针指向的空间释放,要及时将指针置空,也就是赋值为NULL。

所以当我们去使用指针的时候,就要去检查指针的有效性,指针是否为空。

p=NULL;//p为指针,指向空间被释放时要及时置空

//使用前,检查指针的有效性
if(p==NULL)
    return;//指针为空直接返回

四、指针运算

指针的运算包括指针+-整数,如p++;和指针+-指针

1.指针加减整数

#include<stdio.h>
int main()
{
	int arr[5] = {1,2,3,4,5};//创建并初始化数组
	//利用指针加减整数打印数组
	int* p = arr;//定义和初始化指针
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d ",*(p+i));
	}
    //*(p+i)可以写成*p++,但是后者的p会发生变化
	return 0;
}

运行结果:

 

 第一次循环:i=0,所以p+i=p,然后解引用p找的是arr[0],并打印。

p指向的地址是数组首元素的地址;p+i是数组下标为i的元素的地址;(p+i)跳过i*sizeof(int)个字节。

总结:指针加减整数,不是跳过整数个地址,而是跳过整数*sizeof(指针所指向的数据类型)个地址。

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

2.指针-指针

两个指针相减就是地址之间的运算,一般在数组中出现,因为;

指针减指针的前提条件:两个指针必须指向同一块区域,且指针类型相同。

举例:

#include<stdio.h>
int main()
{
	int arr[10] = {0};
	printf("%d\n",&arr[9]-&arr[0]);
    printf("%d\n",&arr[0]-&arr[9]);
    //最后一个元素的地址-首元素地址
	return 0;
}

运行结果 

 总结:指针-指针插值的绝对值,是指针和指针之间的元素个数。

 

五、指针与数组的运用

     因为数组名是首元素的地址,所以可以通过首元素地址访问该数组,也就可以将地址赋值给指针变量,再用指针去访问数组,这就是他们的关系。

知识点:数组名就是表示数组首元素的地址,但是有两个例外。

1.sizeof(数组名),数组名单独放在括号内部,数组名表示整个数组,计算的是整个数组的大小,单位是字节。

2.&数组名,数组名表示整个数组,取出的是数组的地址,数组的地址和数组首元素的地址在数值上是一样的,但是类型和意义不一样的,体现在地址+1上面。

(该知识点已经在数组的章节介绍过,属于数组的知识点)

例子说明:

#include <stdio.h>
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,0 };
	int* p = arr; //指针存放数组首元素的地址
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (i = 0; i < sz; i++)
	{
		printf("&arr[%d] = %p <====> p+%d = %p\n", i, &arr[i], i, p + i);
	}
	return 0;
}

运行结果:

 通过指针来访问数组是可行的,因为指针所指向的地址是跟数组匹配的。

总结:因为数组名是地址的原因,所以可以和指针很好的配合。

六、二级指针

二级指针也是指针,不过二级指针里面存放的地址是一级指针的地址,依次类推,三级指针存放二级指针的地址……

#include <stdio.h>
int main()
{
	int a = 10;
	int* p = &a;
	//*说明p是指针,int说明p指向的类型是int
	//p是一级指针,存放变量a的地址
	int* * pp = &p;
	//第一个*说明pp是指针,int*说明pp指向的p变量类型是int*
	//pp是二级指针
	return 0;
}

里面的文字解析很重要

七、指针数组

指针数组本质是数组,只不过里面存放的类型是指针类型。就像字符数组里面存放的是字符一样。

如:

char* arr1[5];//存放字符指针的数组
int* arr2[6];

举例:

#include<stdio.h>
int main()
{
	int arr1[3] = {1,2,3};
	int arr2[3] = {4,5,6};
	int arr3[3] = { 7,8,9 };
	//arr就是指针数组
	int* arr[3] = { arr1,arr2,arr3 };
	//可以模拟二维数组
	int i = 0;
	for (i=0;i<3;i++)
	{
		int j = 0;
		for (j=0;j<3;j++)
		{
			printf("%d ",arr[i][j]);
			//i=0,访问arr1
		}
		printf("\n");
	}
	return 0;
}

运行结果:

 指针数组在初级C语言不会详细介绍,在进阶C语言我们会进一步探讨。


本章完……以上内容如有错误,欢迎各位大佬的补充,后续会持续更新指针的内容

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

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

相关文章

前端JavaScript入门-day08-正则表达式

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 介绍 语法 元字符 边界符 量词 字符类&#xff1a; 修饰符 介绍 正则表达式&#xff08;Regular …

【开源项目--稻草】Day04

【开源项目--稻草】Day04 1. 续 VUE1.1 完善VUEAJAX完成注册功能 Spring验证框架什么是Spring验证框架使用Spring-Validation 稻草问答-学生首页显示首页制作首页的流程开发标签列表标签列表显示原理 从业务逻辑层开始编写控制层代码开发问题列表开发业务逻辑层开发页面和JS代码…

Vue系列第八篇:echarts绘制柱状图和折线图

本篇将使用echarts框架进行柱状图和折线图绘制。 目录 1.绘制效果 2.安装echarts 3.前端代码 4.后端代码 1.绘制效果 2.安装echarts // 安装echarts版本4 npm i -D echarts4 3.前端代码 src/api/api.js //业务服务调用接口封装import service from ../service.js //npm …

Go Windows下开发环境配置(图文)

Go Windows下开发环境配置 下载 安装 点击下载的安装包进行安装。安装路径可以选择到自己的目录。 环境变量配置 GOROOT&#xff1a;&#xff08;指定到安装目录下&#xff09; GOPATH&#xff1a;&#xff08;是工作空间&#xff09; path&#xff1a;在安装时已经添加了…

XML基础知识讲解

文章目录 1. xml简介2. xml快速入门3. xml的元素(标签)定义4. xml标签的命名规范5. xml的属性定义和注释6. 转义字符7. CDATA区8. xml的处理指令9. xml的约束 1. xml简介 XML&#xff08;eXtensible Markup Language&#xff09;是一种用于描述数据的标记语。 它以纯文本的方…

并查集练习 — 岛屿问题(解法二)

题目如岛屿问题解法一文章所介绍&#xff0c;这里不过多赘述&#xff0c;直接讲解第二种解法。 并查集解法 并查集解法的整体思路是&#xff0c;将二维数组中为‘1’的部分提取出来作为样本&#xff0c;再进行判断&#xff0c;如果左上方向有同样为‘1’的&#xff0c;则进行…

架构训练营学习笔记:6-1 微服务

序 这部分是了解的。传统企业使用soa较多。很多企业银行、电信对于Oracle 依赖大&#xff0c;强调稳定性。各个项目侧重外包&#xff0c;技术栈不统一。 soa 历史 这个之前电信的BOSS系统就是这种架构&#xff0c;不知道现在呢&#xff0c;核心计费系统billing是运行在tuxduo…

整理mongodb文档:collation

文章连接 整理mongodb文档:collation 看前提示 对于mongodb的collation。个人主要用的范围是在createcollection&#xff0c;以及find的时候用&#xff0c;所以本片介绍的时候也是这两个地方入手&#xff0c;对新手个人觉得理解概念就好。不要求强制性掌握&#xff0c;但是要…

jmeter工具测试和压测websocket协议【杭州多测师_王sir】

一、安装JDK配置好环境变量&#xff0c;安装好jmeter 二、下载WebSocketSampler发送请求用的&#xff0c;地址&#xff1a;https://bitbucket.org/pjtr/jmeter-websocket-samplers/downloads/?spma2c4g.11186623.2.15.363f211bH03KeI 下载解压后的jar包放到D:\JMeter\apache-j…

MyCat配置rule.xml、server.xml讲解

1. rule.xml分片规则配置文件 rule.xml中配置的主要就是拆分表的规则&#xff0c;rule.xml中主要包含两类标签 tableRule 和Function。 tableRule标签里面主要配置我们的分片规则&#xff0c;Function里面涉及的是分片规则里面所涉及的java类&#xff0c;都是在function中配置…

python中的装饰器的真正含义和用法

闭包&#xff1a; 闭包是python中的一个很实用的写法&#xff0c;可以使得用户在函数中调用该函数外的函数的变量&#xff0c;使得该变量常驻于内存中。 闭包函数&#xff1a; 输入是函数&#xff0c;输出也是一个函数。 装饰器的写法是python闭包的语法糖。 面试中经常面…

[C++] KY79 浮点数加法 北京大学复试上机题

题目链接 KY79 浮点数加法https://www.nowcoder.com/questionTerminal/ddec753f446e4ba4944e35378ba635c8 描述 求2个浮点数相加的和 题目中输入输出中出现浮点数都有如下的形式&#xff1a; P1P2...Pi.Q1Q2...Qj 对于整数部分&#xff0c;P1P2...Pi是一个非负整数 对于小数…

【前端】html

HTML标签&#xff08;上&#xff09; 目标&#xff1a; -能够说出标签的书写注意规范 -能够写出HTML骨架标签 -能够写出超链接标签 -能够写出图片标签并说出alt和title的区别 -能够说出相对路径的三种形式 目录&#xff1a; HTML语法规范HTML基本结构标签开发工具HTML常用标…

C语言案例 99乘法口诀-04

难度2复杂度2 题目&#xff1a;打印99乘法口诀 步骤一&#xff1a;定义程序目标 编写一个C程序&#xff0c;打印99乘法口诀。 步骤二&#xff1a;程序设计 整个程序分别为两个部分&#xff0c;第一部分是使用for循环打印的行数&#xff0c;第二部分是使用for循环控制打印的列…

基于Azure OpenAI Service 的知识库搭建实验⼿册

1.概要 介绍如何使⽤Azure OpenAI Service 的嵌⼊技术&#xff0c;创建知识库&#xff1b;以及创建必要的资源组和资源&#xff0c;包括 Form Recognizer 资源和 Azure 翻译器资源。在创建问答机器⼈服务时&#xff0c;需要使⽤已部署模型的 Azure OpenAI 资源、已存在的…

一键翻译,实现文件名简体中文转日语的便捷改名!

您是否曾经遇到过想要将文件名从简体中文翻译成日语&#xff0c;却苦于没有合适的工具&#xff1f;现在&#xff0c;让我们为您介绍一款便捷的解决方案——文件名简体中文转日语翻译工具&#xff01; 首先&#xff0c;第一步&#xff0c;我们需要打开文件批量改名&#xff0c;…

无涯教程-Perl - binmode函数

描述 此函数设置在区分两者的操作系统上以二进制形式读取和写入FILEHANDLE的格式。非二进制文件的CR LF序列在输入时转换为LF,在LF时在输出时转换为CR LF。这对于使用两个字符分隔文本文件中的行的操作系统(MS-DOS)至关重要,但对使用单个字符的操作系统(Unix,Mac OS,QNX)没有影…

付款方式谈得好,订单跑不了

在与客户商讨付款方式时出现分歧&#xff0c;其实是非常常见的情况。 在这种情况下&#xff0c;我们需要紧握谈判的节奏&#xff0c;不可忽视任何细节&#xff0c;更不能让自己陷入混乱之中。 先看一个案例~ 客户发来邮件&#xff1a; Dear... Please find our attached f…

远程访问桌面软件 OpenText Exceed TurboX(ETX)如何提高企业生产力

远程访问桌面软件 OpenText Exceed TurboX&#xff08;ETX&#xff09;如何提高企业生产力 几乎所有规模和行业的企业&#xff0c;员工的工作方式、时间和地点方面发生重大变化&#xff0c;这主要得益于新技术和全球商业与协作。业务领导者正在推动其 IT 部门提出解决方案&…

使用yarn启动项目报错

使用yarn启动项目报错 解决方法&#xff1a; 1.点击“开始”菜单搜索找到 Windows PowerShell ISE并以管理员身份运行(注&#xff1a;不是以管理员的身份直接运行cmd) 2. 输入 set-ExecutionPolicy RemoteSigned 回车 3.输入&#xff08;选择全是&#xff09; 4.再输入get-Exe…