C语言入门知识点(11.指针c篇)(超详细)

  • 🌈🌈🌈这里是say-fall分享,感兴趣欢迎三连与评论区留言
  • 🔥🔥🔥专栏:《C语言入门知识点》、《C语言底层》
  • 💪💪💪格言:今天多敲一行代码,明天少吃一份苦头

前言:

本篇继续补充指针篇的知识点,包括指针运算、野指针(包含悬垂指针)、assert断言和指针传参,感兴趣的小伙伴们快做好笔记!!!


文章目录

    • 前言:
    • 正文:
      • 5. 指针运算
        • 5.1 指针 ± 整数
        • 5.2 指针 - 指针
        • 5.3 指针的关系运算
      • 6. 野指针
        • 6.1 初始化指针和野指针
          • 6.1.1 初始化指针
          • 6.1.2 指针越界访问
          • 6.1.2 指针越界访问
        • 6.2 如何规避野指针
          • 6.2.1 初始化野指针时赋值或置于NULL
          • 6.2.2 小心指针越界
          • 6.2.3 指针不再使用时,及时置0,使用前检查指针有效性
          • 6.2.4 避免指针返回局部变量地址
      • 7. assert断言
      • 8. 指针的使用与传址调用
        • 8.1 strlen的模拟实现
        • 8.2 传值调用与传址调用


正文:

5. 指针运算

指针基本运算包括三种:

  • 指针 ± 整数
  • 指针 - 指针
  • 指针的关系运算
5.1 指针 ± 整数

在前文《指针b篇》我们已经了解过指针 ± 整数(了解详情可点击《指针b篇》回顾),我们知道pointer ± n*sizeof(----)----代表的是变量类型,而且变量类型对应了指针会移动几个字节。

那有什么应用呢?

我们不妨想一下,有哪些数据在内存中是连续存放的?很显然,数组就是这样。
也就是说,我们可以通过指针来访问数组的元素,好,详看代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

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

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

	printf("%d\n", arr[2]);
	printf("%d\n", *(p + 2));
  • 可以看得到,打印的两个结果是完全相同的。也就是说,指针通过+整数(移动了4个字节)的方式,访问到了数组的元素。

跑题一下,我们了解一个数组和指针的“小秘密”:
其实数组名就是一个数组第一个元素的指针
int* arr = arr[0]

验证一下:

int main()
{
	int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
	printf("%d ", *arr);
	return 0;
}

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

既然如此的话,我们发现数组其实有两种表示方法:

  • 1.指针表示法
  • 2.数组表示法

指针表示法其实相对来说更底层一点,因为它对内存的应用更明显。

5.2 指针 - 指针

明白了指针与整数的运算,不妨先来猜一下指针 - 指针实际上是在做什么吧

先贴一段代码,想想他的运行结果

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

揭晓答案,运行结果:
在这里插入图片描述

答案是3,也就是说指针 - 指针的结果是指针与指针之间所差变量类型的个数

int main()
{
	int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
	int x = 0;
	x = *(arr + 4) - *(arr + 1);
	printf("%d\n", x);
	int arr_ch[6] = { 'a','b','c','d','e','f' };
	int y = 0;
	y = *(arr_ch + 4) - *(arr_ch + 1);
	printf("%d\n", y)return 0;
}

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

看来确实如此。

有什么应用吗?

我记得有一个函数,能返回字符串的长度。strlen()
我们来试着复刻一下这个函数:

int my_strlen(char* s)
{
	char *x = s;
	while(*x !=  '\0')
		x++;
	return x - s;
}

int main()
{
	printf("%d", my_strlen("abcd"));
	return 0;
}

运行结果:
在这里插入图片描述
yes!我们成功了!

5.3 指针的关系运算

我们这次直接来看代码

//指针的关系运算 
#include <stdio.h>
int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9,10};
 int *p = &arr[0];
 int sz = sizeof(arr)/sizeof(arr[0]);
 while(p<arr+sz) //指针的⼤⼩⽐较 
 {
 printf("%d ", *p);
 p++;
 }
 return 0;
}

这段代码中就用到了指针间的大小比较,通过这串代码输出了数组的元素:
在这里插入图片描述

6. 野指针

6.1 初始化指针和野指针

首先来说一下初始化指针,当我们知道这个指针是指向那里的时候,我们一般直接给指针一个地址,如果不知道的话我们给指针一个NULLNULL和0是等价的,也就是让这个指针指向0。而如果我们什么都不初始化,直接定义一个指针而不赋值,在这个指针就被称为野指针,有的时候指针在运行时跑出作用范围也被称为野指针。

6.1.1 初始化指针
#include <stdio.h> 

int main()
{
	int* p0 = NULL;
	int* p1 = 0;
	int* p2;
	printf("%p\n", p0);
	printf("%p\n", p1);
	printf("%p\n", p2);
	return 0;
}

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

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

6.1.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;
}

这个数组的范围为10,而指针访问时最后到达12才停止,导致报错:在这里插入图片描述

6.1.2 指针越界访问

代码来啦!

#include <stdio.h>
int* test()
{
	int n = 100;
	return &n;
}
int main()
{
	int* p = test();
	printf("%d\n", *p);
	return 0;
}

代码一结果:
在这里插入图片描述

#include <stdio.h>
int* test()
{
	int n = 100;
	return &n;
}
int main()
{
	int* p = test();
	printf("%p\n", p);
	printf("%d\n", *p);
	return 0;
}

代码二结果:
在这里插入图片描述

已知test();返回的是 &n ,但是n是在test()内部的局部变量,如果在test()之外使用&n(p),此时的p是一个悬垂指针,返回的是一个无效的地址,打印的值又可能是100;也有可能是随机值;也可能导致程序崩溃(内存非法访问)。

  • 第2、3种方法都会产生悬垂指针
6.2 如何规避野指针

其实上面的三种野指针成因也就对应了规避野指针的方法

6.2.1 初始化野指针时赋值或置于NULL
#include <stdio.h> 

int main()
{
   int* p0 = NULL;
   int* p1 = 0;
   printf("%p\n", p0);
   printf("%p\n", p1);
   return 0;
}
6.2.2 小心指针越界

在调整指针指向的位置时,不要让指针超出访问的空间,超出空间时,指针就越界访问了

6.2.3 指针不再使用时,及时置0,使用前检查指针有效性

在使用指针后,指针停止使用,再次使用时检查。

#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;
 }
 //此时p已经越界了,可以把p置为NULL 
 p = NULL;
 //下次使⽤的时候,判断p不为NULL的时候再使⽤ 
 //...
 p = &arr[0];//重新让p获得地址 
 if(p != NULL) //判断 
 {
 //...
 }
 return 0;
}
6.2.4 避免指针返回局部变量地址

像是上面的例子,指针不能返回局部变量地址,会产生不可预料的结果。

7. assert断言

我们上文中提到了判断指针p是否置于NULL,如果为真就继续执行,不为真就不执行,这里我们介绍一种用于判断然后决定是否继续运行的宏:assert()
assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报错终⽌运⾏。这个宏常常被称为“断⾔”。

  • PS:宏:是由编译器或解释器预处理的代码替换规则,在程序运行前(预处理阶段)会被自动替换为定义好的代码片段。

我们在这里这样用assert():

#include <assert.h>
assert(p != NULL);

如果想要开启或者关闭assert()函数呢,我们就在#include assert()前定义一个DNEBUG

#define DNEBUG
#include <assert.h>

这样的话,下次运行函数的时候,就会自动屏蔽掉assert(),同理删除后在运行就能开启assert()

assert() 的缺点是,因为引⼊了额外的检查,增加了程序的运⾏时间。

⼀般我们可以在 Debug 中使⽤,在 Release 版本中选择禁⽤ assert 就⾏,在 VS 这样的集成开
发环境中,在 Release 版本中,直接就是优化掉了。这样在debug版本写有利于程序员排查问题,
在 Release 版本不影响⽤⼾使⽤时程序的效率

8. 指针的使用与传址调用

8.1 strlen的模拟实现

前文见过,代码走起!

int my_strlen(char* s)
{
	char *x = s;
	while(*x !=  '\0')
		x++;
	return x - s;
}

int main()
{
	printf("%d", my_strlen("abcd"));
	return 0;
}

运行结果依旧正确:
在这里插入图片描述

8.2 传值调用与传址调用

我们不用指针写一个交换数值的函数:

#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;
}

运行一下:
在这里插入图片描述

  • 欸,为啥没换过来

试试调试:
在这里插入图片描述

  • 可以看得到,在出函数的前一刻,xy的地址和ab的地址是不一样的,出函数以后,xy作为局部变量就会被销毁,也就导致了虽然x和y交换数值了,但是和ab有什么关系呢?

上面我们用的是函数的传值调用,试试用指针呢

#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);
 Swap2(&a, &b);
 printf("交换后:a=%d b=%d\n", a, b);
 return 0;
}

能看的到和上次不同,我们这次调用的是地址,这样的话直接让地址被交换(即指针),就能交换ab的值了。
运行结果:
在这里插入图片描述

很好,通过两个函数的对比,我们就了解了什么时候必须要用指针,这也就大大提高了指针存在的必要性。

  • 本节完…
Delphi 12.3 作为一款面向 Windows 平台的集成开发环境,由 Embarcadero Technologies 负责其持续演进。该环境以 Object Pascal 语言为核心,并依托 Visual Component Library(VCL)框架,广泛应用于各类桌面软件、数据库系统及企业级解决方案的开发。在此生态中,Excel4Delphi 作为一个重要的社区开源项目,致力于搭建 Delphi 与 Microsoft Excel 之间的高效桥梁,使开发者能够在自研程序中直接调用 Excel 的文档处理、工作表管理、单元格操作及宏执行等功能。 该项目以库文件与组件包的形式提供,开发者将其集成至 Delphi 工程后,即可通过封装良好的接口实现对 Excel 的编程控制。具体功能涵盖创建与编辑工作簿、格式化单元格、批量导入导出数据,乃至执行内置公式与宏指令等高级操作。这一机制显著降低了在财务分析、报表自动生成、数据整理等场景中实现 Excel 功能集成的技术门槛,使开发者无需深入掌握 COM 编程或 Excel 底层 API 即可完成复杂任务。 使用 Excel4Delphi 需具备基础的 Delphi 编程知识,并对 Excel 对象模型有一定理解。实践中需注意不同 Excel 版本间的兼容性,并严格遵循项目文档进行环境配置与依赖部署。此外,操作过程中应遵循文件访问的最佳实践,例如确保目标文件未被独占锁定,并实施完整的异常处理机制,以防数据损毁或程序意外中断。 该项目的持续维护依赖于 Delphi 开发者社区的集体贡献,通过定期更新以适配新版开发环境与 Office 套件,并修复已发现的问题。对于需要深度融合 Excel 功能的 Delphi 应用而言,Excel4Delphi 提供了经过充分测试的可靠代码基础,使开发团队能更专注于业务逻辑与用户体验的优化,从而提升整体开发效率与软件质量。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值