C语言-指针讲解(2)

news2025/7/19 13:40:01


文章目录

  • 1.野指针
    • 1.1 什么是野指针
    • 1.2 造成野指针的原因有哪些呢
      • 1.2.1造成野指针具体代码实例:
    • 1.3 如何避免野指针呢?
      • 1.3.1如何对指针进行初始化?
      • 1.3.2如何才能小心指针越界?
      • 1.3.3 指针变量不再使用时,如何及时置NULL,在指针使用之前检查有效性?
  • 2.assert断言
    • 2.1 什么是assert断言
    • 2.2 如何使用assert断言呢?
    • 2.3 使用assert有什么好处呢?
  • 3.指针的使用和传址调用
    • 3.1 学习指针的目的是什么?
    • 3.2 什么是传址调用?
    • 3.3 怎么进行传址调用?
  • 4.数组名的理解
    • 4.1 arr和&arr的区别
  • 5.二级指针
    • 5.1 什么是二级指针
  • 5.2 指针变量的地址存放在哪里呢?
    • 5.3 对于二级指针的运算是怎么样的呢?
  • 6.指针数组
    • 6.1 什么是指针数组呢?
  • 7.指针数组模拟二维数组

通过前面的介绍
C语言指针详解(一)超详细~
相信大家对指针的基本概念及用法有了初步的了解。

我们来回顾一下上次那个博客讲了什么吧~
1.指针就是变量,用于存放地址的,地址唯一标识的一块内存空间。
2.指针的大小分别是4/8个字节(32位平台/64位平台)
3.指针是有类型的,指针的类型决定了指针±整数的步长,以及指针解引用的权限有多大。
4.指针的运算。
那么这次博主给大家继续深入理解指针的其他高级用法吧
这是本次我们要讲解的知识点:


1.野指针

1.1 什么是野指针

野指针,顾名思义,就是指针指向的位置是不可知的。就好比如没有主人的流浪狗一样。

1.2 造成野指针的原因有哪些呢

1.指针未被初始化
2.指针越界访问
3.指针指向的空间释放
前面两个造成野指针原因都比较容易理解,所以我们一会重点讲一下第三个

1.2.1造成野指针具体代码实例:

1.指针未被初始化

#include <stdio.h>
int main()
{
	int *p;//局部变量指针未初始化,默认为随机值
	*p = 20;
	return 0;
}

2.指针越界访问

#include <stdio.h>
int main()
{
	int arr[10] = {0};
	int *p = &arr[0];
	int i = 0;
	for(i=0; i<=11; i++)
	{
		//当指针指向的范围超出数组arr的范围时,p就是野指针
		*(p++) = i;
	}
	return 0;
}

3.指针指向的空间释放

int* test()//由于返回的是n的地址,因此函数返回的是int*类型
{
	int n = 100;//在test函数中创建了局部变量n,
	return &n;//当我们在中间的函数做了一些事情后,我们就返回n,把n的地址返回到指针变量p来接收
	
}
int main()
{
	int* p = test();//由于返回的是地址,所以我们拿指针变量p来接收
	printf("%d\n", *p);
	return 0;
}

从上面这个代码中,当test()函数中变量n申请了一块空间,而出这个test()函数的时候,这个n的地址就会被销毁,并还给操作系统。然后回到main函数,但是指针变量p仍然记住n的地址,如果到时通过对p进行解引用操作,来改变它所指向对象的值。这就属于是非法访问了,足矣说明p是个野指针。

1.3 如何避免野指针呢?

1.指针初始化
2.小心指针越界
3.指针变量不再使用时,及时置NULL,指针使用之前检查有效性。

1.3.1如何对指针进行初始化?

如果不知道指针指向哪里,可以先给指针复制NULL,NULL是C语言中定义的一个标识符常量,值是0,但是这个地址是无法直接使用的。

初始化如下:

include <stdio.h>
int main()
{
	int num = 10;
	int*p1 = &num;
	int*p2 = NULL;
	return 0;
}

1.3.2如何才能小心指针越界?

通常来说,一个程序向内存申请了哪些空间,通过指针也只能访问哪些空间,不能超出范围访问,超出了就是越界访问。

1.3.3 指针变量不再使用时,如何及时置NULL,在指针使用之前检查有效性?

  • 当指针变量指向一块区域的时候,我们可以通过指针访问该区域,如果我们后期不再使用这个指针访问空间的时候,我们可以先把该指针置为NULL。
  • 然后到下次使用该指针变量之前,我们要先判断它是否为NULL,如果是就不能指直接使用,不是的话我们才能使用。

2.assert断言

2.1 什么是assert断言

assert.h头文件定义了assert(),它是用于在运行时确保程序符合指定条件,如果不符合,就报错终止运行,这个宏尝尝被称为“断言”。

2.2 如何使用assert断言呢?

在这里插入图片描述

assert(p != NULL);

当上面代码在程序运行到这一行程序时,验证变量p是否等于NULL。如果确实不等于NULL,程序会继续运行,否则就会终止运行,并且给出报错信息提示。

2.3 使用assert有什么好处呢?

  • 它不仅能自动标识文件和出问题的行号,还有一种无需更改代码就能开启或关闭assert()的机制。
  • 如果已经确认程序没有问题,不需要再做断言,就在#include<assert.h>语句的前面,定义一个NDEBUG。
    具体代码如下:
#define NDEBUG
#include <assert.h>

需要注意的是,assert()也是有缺点的。由于引入了额外的检查,会增加程序的运行时间。


3.指针的使用和传址调用

3.1 学习指针的目的是什么?

学习指针的目的是使用指针解决问题,那什么问题,非指针不可呢?

比方说,我们要写一个函数,来交换两个整数变量的值。

经过一番思考后,我们可能会写出这个代码出来~

#include <stdio.h>
void Swap1(int x, int y)
{
	int tmp = x;
	x = y;
	y = tmp;
}

int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	printf("交换前:a=%d b=%d\n", a, b);
	Swap1(a, b);
	printf("交换后:a=%d b=%d\n", a, b);
	return 0;
}

当我们运行此代码,结果如下:

我们会发现这两个变量没有产生交换的效果,这是为什么呢?我们不妨调试一下~

在这里插入图片描述

从图中,我们可以看出在main函数中,我们调用了swap函数。并把变量a和b作为传过去,形参用x和y来接收。但我们发现,这里的变量a和变量x的地址不相同,变量b和变量y的地址也不相同。这也说明形参x和y是一个独立的空间。当swap函数调用结束后返回main函数,a和b的变量依然无法交换,swap在使用的时候,本质上就是把变量本身传递给函数,这也就是我们常说的传值调用

因此我们得出以下结论:

实参传递给行参的时候,形参会单独创建一份临时空间来接收实参,对形参的修改不影响实参,所以swap是失败的。

那我们怎么解决呢?
在这里插入图片描述
我们得借助函数间传址调用来解决。

3.2 什么是传址调用?

传址调用,顾名思义就是将main函数中的变量地址传到所调用的函数中,然后在被调函数中,通过地址间的操作即可实现两个数的交换。

3.3 怎么进行传址调用?

那回到刚刚那种情景,我们只需把变量a和b的地址分别传给swap函数,然后swap函数内部中,通过地址间的操作即可实现main函数中a和b两个数的交换。

代码实现如下:

#include <stdio.h>
void Swap2(int*px, int*py)
{
	int tmp = 0;
	tmp = *px;
	*px = *py;
	*py = tmp;
}
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	printf("交换前:a=%d b=%d\n", a, b);
	Swap1(&a, &b);
	printf("交换后:a=%d b=%d\n", a, b);
	return 0;
}

运行结果如下:
在这里插入图片描述

4.数组名的理解

在上一次博客C语言指针详解(一)超详细~
我们曾写过两行代码:

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

这里我们是使用&arr[0]的方式拿到了数组第一个元素的地址。但是数组名本来就是地址,不信我们可以拿VS编译器来测试一下。
在这里插入图片描述

从上图,我们发现数组名和数组首元素的地址打印出的结果是一模一样的。

因此我们可以得出这个结论:数组名是数组首元素(第一个元素)的地址
但是呢,有同学会有疑问,如果数组名是数组首元素的地址,那这个代码该怎么理解?

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("%d\n", sizeof(arr));
	return 0;
}

从下图,我们发现输出结果是40。
在这里插入图片描述
为什么不是4/8呢?如果数组是首元素的地址,按理说输出的应该是4/8才对。

其实数组名就是数组首元素(第一个元素)的地址是对的,但是有两个例外:

  • sizeof(数组名),sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小。
  • &数组名表示整个数组,取出的是整个数组的地址,(整个数组的地址和数组的首元素的地址是有区别的)。

除此之外,其他地方使用数组名,数组名都表示首元素的地址。


这时,或许还会同学不理解,他们也许会再测试一下这个代码:

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("&arr[0] = %p\n", &arr[0]);
	printf("arr = %p\n", arr);
	printf("&arr = %p\n", &arr);
	return 0;
}

发现这三个打印的结果都一样,会再次出现疑惑?
在这里插入图片描述
那接下来我来介绍他们之间的区别。

4.1 arr和&arr的区别

我们直接上代码分析~

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("&arr[0] = %p\n", &arr[0]);
	printf("&arr[0]+1 = %p\n", &arr[0]+1);
	printf("arr = %p\n", arr);
	printf("arr+1 = %p\n", arr+1);
	printf("&arr = %p\n", &arr);
	printf("&arr+1 = %p\n", &arr+1);
	return 0;
}

运行结果:
在这里插入图片描述

  • 从上图,我们发现&arr[0]和&arr[0]+1相差4个字节,arr和arr+1相差4个字节,是因为&arr[0]和arr都是首字符地址,+1就是跳过一个元素。
  • 但是&arr和&arr+1是相差40个字节,这就是因为&arr是数组的地址,因此这里+1就是跳过整个数组。

相信到这里大家应该搞清楚数组名的意义了吧。
除了有两个例外,其他的数组名都是数组首元素的地址。


5.二级指针

5.1 什么是二级指针

二级指针指向的是一级指针的指针,也就是说一个指针指向的是另外的指针,同时二级指针也是存放一级指针的地址,则称之为二级指针。

5.2 指针变量的地址存放在哪里呢?

这个我们可以先画个图来分析一下~

在这里插入图片描述

比方说,我们从上图可以得知,我们可以得知指针变量pa存放的是a的地址,而指针变量ppa存放的是指针变量pa的地址,你们由这个规律,我们就能推导出指针变量pppa存放的是指针变量ppa的地址。

另外,这里有个小细节需要大家注意的是,由于pa中的p左边的*代表pa是个指针变量,而前面的int代表pa是个int类型的指针变量。那同理,ppa中的p左边的 *代表ppa是个指针变量,而旁边还有一个 *。代表的是ppa是一个int *类型的指针变量。

5.3 对于二级指针的运算是怎么样的呢?

我们先来看下面代码,然后再逐一进行分析。

#include <stdio.h>
int main() {

	int a = 10;
	int* p = &a;//p是一级指针

	int** pp = &p;//pp是二级指针
	printf("%d\n", **pp);


	return 0;
}

从上图可以得知,首先,** pp先通过*pp找到p,然后我们再对p进行解引用操作: *p,那找到的就是a,那么最终输出的结果就是10。

VS运行结果如下所示:
在这里插入图片描述



6.指针数组

6.1 什么是指针数组呢?

俗话说,存放整型的数组是整形数组。
存放字符的数组是字符数组。
在这里插入图片描述
那么同理,存放指针的数组则是指针数组。

并且指针数组的每个元素都是用来存放地址(指针)的。
如下图所示:
在这里插入图片描述

我们会发现指针数组每个元素都是存放地址的,又可以指向一块区域。



7.指针数组模拟二维数组

#include <stdio.h>
int main()
{
	int arr1[] = {1,2,3,4,5};
	int arr2[] = {2,3,4,5,6};
	int arr3[] = {3,4,5,6,7};
	//数组名是数组首元素的地址,类型是int*的,就可以存放在parr数组中
	int* parr[3] = {arr1, arr2, arr3};
	int i = 0;
	int j = 0;
	for(i=0; i<3; i++)
	{
		for(j=0; j<5; j++)
		{
			printf("%d ", parr[i][j]);
			//parr[i][j]==*(*(parr+i)+j))
		}
		printf("\n");
	}
	return 0;
}

在这里插入图片描述

从图中,我们可以看出parr[i]是访问parr数组的元素,parr[i]找到的数组元素指向了整型一维数组,parr[i][j]就是整型一维数组中的元素。
需要注意的是,上面的代码模拟出的二维数组的效果,实际上并未完全是二维数组,以为每一行并非是连续的。


** 好啦!今天博主就分享到这里**
在这里插入图片描述
** 如果觉得博主讲得不错的话。欢迎大家一键三连支持一下**

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

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

相关文章

2024年天津高职升本科考试将于11月开始报名

2024年天津高职升本科考试文化课网上报名及其现场确认将于11月下旬开始 2023年11月1日&#xff0c;天津招考资讯官方网站发布了本月&#xff08;11月&#xff09;报名事项安排&#xff0c;将进行下列考试项目网上报名工作&#xff0c;2024年备考天津专升本的考生可以看到2024年…

django如何连接sqlite数据库?

目录 一、SQLite数据库简介 二、Django连接SQLite数据库 1、配置数据库 2、创建数据库表 三、使用Django ORM操作SQLite数据库 1、定义模型 2、创建对象 3、查询对象 总结 本文将深入探讨如何在Django框架中连接和使用SQLite数据库。我们将介绍SQLite数据库的特点&…

Java——Spring常见的基础知识

1、Spring 答&#xff1a;Spring 总共大约有 20 个模块&#xff0c; 由 1300 多个不同的文件构成。 而这些组件被分别整合在核心容器(Core Container) 、 AOP(Aspect Oriented Programming)和设备支持(Instrmentation) 、数据访问与集成(Data Access/Integeration) 、Web、 消…

列式数据库ClickHouse,大宽表聚合、报表一下全搞定

一、前言 现在数据库的种类也是特别的多&#xff0c;大致的类别包括&#xff1a; 关系型数据库&#xff08; MySQL、Oracle、PostgreSQL&#xff09;非关系型数据库&#xff08;Redis、MongoDB、Cassandra、Neo4j&#xff09;全文搜索引擎和分布式文档存储系统&#xff08;El…

阿里云绝地反击:老用户购买云服务器99元一年

2023阿里云服务器优惠活动来了&#xff0c;以前一直是腾讯云比阿里云优惠&#xff0c;阿里云绝地反击&#xff0c;放开老用户购买资格&#xff0c;99元服务器老用户可以买&#xff0c;并且享受99元续费&#xff0c;阿腾云亲测可行&#xff0c;大家抓紧吧&#xff0c;数量不多&a…

【LeetCode:80. 删除有序数组中的重复项 II | 双指针】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

【排序算法】 计数排序(非比较排序)详解!了解哈希思想!

&#x1f3a5; 屿小夏 &#xff1a; 个人主页 &#x1f525;个人专栏 &#xff1a; 算法—排序篇 &#x1f304; 莫道桑榆晚&#xff0c;为霞尚满天&#xff01; 文章目录 &#x1f4d1;前言&#x1f324;️计数排序的概念☁️什么是计数排序&#xff1f;☁️计数排序思想⭐绝对…

免费低代码软件:最佳选型推荐

低代码是什么&#xff1f;他是鉴于0代码和高代码之间的概念&#xff0c;主要强调用户无需专业的代码知识即可完成一个成熟的应用程序的搭建。而市场上众多的低代码软件&#xff0c;如何选择一个合适自身企业的产品呢&#xff1f;小编建议不妨先试试免费低代码软件Zoho Creator。…

栅格及波段融合工具

支持大部分栅格数据的波段融合&#xff0c;可以将不同栅格数据的任意波段融合到一个栅格文件里&#xff0c;可以选择融合的波段。 下载地址&#xff1a; https://download.csdn.net/download/qq_35582643/88490703?spm1001.2014.3001.5503 运行方式&#xff1a; 输入…

万达商业携手蜂鸟视图实现CAD图纸一键转换三维地图

室内三维电子地图在很多行业已经形成了广泛应用&#xff0c;特别是商业地产领域的需求持续增长。然而&#xff0c;应用方一直面临挑战的难题是传统的地图绘制工作的繁重和难以满足时效性。为应对这一难题&#xff0c;在2022年蜂鸟视图推出CAD快速转换工具&#xff0c;能够将主流…

Python和urllib库下载网页内容

首先我们需要导入urllib库&#xff0c;然后使用urllib库的urlopen()函数来打开网页。urlopen()函数的第一个参数是需要下载的网页的URL&#xff0c;第二个参数是服务器的URL和端口。 import urllib.request ​ # 打开网页 url proxy_url response urllib.request.urlopen…

记一次 .NET 某工厂无人车调度系统 线程爆高分析

一&#xff1a;背景 1. 讲故事 前些天有位朋友找到我&#xff0c;说他程序中的线程数爆高&#xff0c;让我帮忙看下怎么回事&#xff0c;这种线程数爆高的情况找问题相对比较容易&#xff0c;就让朋友丢一个dump给我&#xff0c;看看便知。 二&#xff1a;为什么会爆高 1. …

【java学习—十一】泛型(1)

文章目录 1. 为什么要有泛型Generic2. 泛型怎么用2.1. 泛型类2.2. 泛型接口2.3. 泛型方法 3. 泛型通配符3.1. 通配符3.2. 有限制的通配符 1. 为什么要有泛型Generic 泛型&#xff0c;JDK1.5新加入的&#xff0c;解决数据类型的安全性问题&#xff0c;其主要原理是在类声明时通过…

精选10款Python可视化工具,请查收

今天我们会介绍一下10个适用于多个学科的Python数据可视化库&#xff0c;其中有名气很大的也有鲜为人知的。 1、matplotlib matplotlib 是Python可视化程序库的泰斗。经过十几年它仍然是Python使用者最常用的画图库。它的设计和在1980年代被设计的商业化程序语言MATLAB非常接近…

的修工单管理系统好用吗?工单系统应该怎么选?

在当今的数字化时代&#xff0c;企业运营效率的高低往往取决于其内部管理工具的先进性和实用性。工单管理系统作为企业运营中的重要工具&#xff0c;其作用日益凸显。市场上存在许多工单管理系统&#xff0c;但“的修”以其独特的产品差异化和优势&#xff0c;在竞争中独树一帜…

Linux C语言进阶-D3~D4字符串处理函数

求字符串长度函数strlen、字符串拷贝strcpy、字符串连接strcat、字符串比较strcmp 头文件<string.h> 求字符串长度strlen函数 1、计算字符串长度&#xff0c;并且遇到\0结束&#xff0c;返回字符串长度 2、 计算字符串长度&#xff0c;遇到转义字符&#xff0c;\不算在内…

基于ASP.NET MVC + Bootstrap的仓库管理系统

基于ASP.NET MVC Bootstrap的仓库管理系统。源码亲测可用&#xff0c;含有简单的说明文档。 适合单仓库&#xff0c;基本的仓库入库管理&#xff0c;出库管理&#xff0c;盘点&#xff0c;报损&#xff0c;移库&#xff0c;库位等管理&#xff0c;有着可视化图表。 系统采用Bo…

消息的订阅与发布机制

消息的订阅与发布机制 功能&#xff1a;可完成任意组件之间数据的传递&#xff08;同全局事件总线功能一样&#xff09;区别&#xff1a;与全局事件总线相比&#xff0c;消息的订阅和发布机制需要使用第三方库。我用的是pubsub-js库&#xff0c;其他的第三方库也可以使用&#…

如何在崩坏3rd游戏中使用万安单机单窗口软件进行游戏道具收购?

如何在崩坏3rd游戏中使用窗口软件进行游戏道具收购&#xff1f; 首先&#xff0c;定义在崩坏3rd游戏中&#xff0c;使用窗口软件进行游戏道具收购涉及到账户绑定、软件下载、游戏内购买等步骤。 我在玩崩坏3rd游戏时&#xff0c;使用了窗口软件成功完成了游戏道具的收购。 步骤…

【产品体验】OA办公系统

一、演示地址 http://admin.dianshixinxi.com:90/index 二、办公管理功能 1.我的待办&#xff1a;当前登录用户&#xff0c;办理任务 2.通知公告&#xff1a;通知与公告&#xff0c;已发布通知公告会显示在首页 3.自定义表单&#xff1a;自定义表单&#xff0c;托拉拽的形式…