【C语言指针二】从入门到通透:核心知识点全梳理(野指针,assert断言,指针的使用和传址调用,数组名的理解和使用指针反访问数组)

🎬 个人主页秦苒&
专栏传送门 :《C语言

🍀指尖燃热血,代码铸锋芒;以信仰破局,向顶峰生长


🎬秦苒&的简介:


前言:前面在【C语言指针一】从入门到通透:核心知识点全梳理(内存、变量、运算、const修饰)中我们介绍了内存、变量、运算、const修饰
接下来我们继续学习野指针,assert断言,指针的使用和传址调用,数组名的理解和使用指针反访问数组。


文章目录


提示:以下是本篇文章正文内容,下面案例可供参考

一、野指针

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

1.1指针未初始化

我们直接看图学习:

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

当然啦,解决方法就是使其初始化,那么如何使他初始话呢?
对于明确知道指针应该指向哪里我们直接赋值;对于不知道应该指向哪里的指针,我们可以给指针赋值为NULL。(NULL 是C语言中定义的一个标识符常量,值是0,0也是地址,但是这个地址是无法使用的,读写该地址会报错。)

如图所示:

c 复制代码
#include <stdio.h>
int main()
{
   int num = 10;
   int*p1 = &num;
   int*p2 = NULL;
   return 0;
}

因此,指针变量不再使用时,及时置NULL,指针使用之前检查有效性

当指针变量指向⼀块区域的时候,我们可以通过指针访问该区域,后期不再使用这个指针访问空间的时候,我们可以把该指针置为NULL。因为约定俗成的⼀个规则就是:只要是NULL指针就不去访问,同时使用指针之前可以判断指针是否为NULL。

1.2小心指针越界

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

例如

c 复制代码
#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;
}

1.3 指针指向的空间释放

如图所示,这里我们需要避免返回局部变量的地址。

二、assert 断言

首先,assert()在使用使需要包含头文件assert.h 。assert用于在运行时确保程序符合指定条件,如果不符合,就报错终止运行。这个宏常常被称为"断言"。

c 复制代码
assert(p != NULL);

当程序运行到这行语句时会判断表达式的真假,当表达式为真(非零)时,程序正常运行;当表达式为假(零)时,程序会报错,如下图所示:

assert对程序员是很友好的。它不仅能自动标识文件和出问题的行号,还有⼀种无需更改代码就能开启或关闭assert() 的机制。

如果我们在确定程序没有问题时,我们只需要在#include <assert.h> 语句的前面定义一个宏NDEBUG 。如下图所示:

c 复制代码
#define NDEBUG
#include <assert.h>

接着,重新编译程序,编译器就会禁用文件中所有的除这条assert() 语句。

如果程序又出现问题,我们只需要移除或者注释掉#define NDEBUG

或许有人要说,if语句也可以起判断作用,但是当我们在确认代码都正确后,我们想删去if语句时,需要找到对应的代码块接着注释或者删除掉,显然是没有assert断言方便的!assert极大程度上便利了程序员。

有利就有弊了。assert() 的弊端是,因为引入了额外的检查,增加了程序的运行时间。

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

VS在这里可以切换开发环境中(相信煮包的读者中还是有小白的):

三、 指针的使用和传址调用

3.1 strlen的模拟实现

库函数strlen的功能是求字符串长度,长度不可能是负数,返回 size_t。统计字符串中\0 之前的字符的个数。

3.2 传值调用和传址调用

要求 :写一个函数,交换两个整形的变量的值

在不考虑指针的情况下我们可能会这么写

c 复制代码
#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;
}

运行结果如下:

运行结果和我们预想的完全不一样,这是为什么呢?原因如下:
实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实参。

这时我们就主要借助指针来帮我们解决问题啦!

c 复制代码
#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);
  Swap2(&a, &b);//将变量的地址传递给了函数(传址调用)
  printf("交换前:a=%d b=%d\n", a, b);
  printf("交换后:a=%d b=%d\n", a, b);
return 0;
}

传址调用,可以让函数和主调函数之间建立 真正的联系,在函数内部可以修改主调函数中的变量;
所以未来函数中只是需要主调函数中的变量值来实现计算,就可以采用传值调用。
如果函数内部要修改主调函数中的变量的值,就需要传址调用

四、数组名的理解

在上一节【C语言指针一】从入门到通透:核心知识点全梳理(内存、变量、运算、const修饰)中,有这样一段代码

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

我们用 &arr[0]拿到啦数组第一个元素的地址,但是其实数组名本来就是地址,而且是数组首元素的地址

我们发现数组名和数组首元素的地址打印出的结果⼀模⼀样,数组名就是数组首元素(第⼀个元素)的地址。

但是凡事总是有例外的,看图展示:

c 复制代码
#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;
}

运行结果:

这是为什么呢?
sizeof(数组名),sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节

再看:

我们来仔细研究一下输出结果:

?---->40(具体详情如图所示)

40个字节,不就是整个数组吗?这是为什么呢?

这里我们发现&arr0和&arr0+1相差4个字节,arr和arr+1相差4个字节,是因为&arr0和arr都是首元素的地址,+1就是跳过⼀个元素。

但是&arr和&arr+1相差40个字节,这就是因为&arr是数组的地址,+1操作是跳过整个数组的。

结论:&数组名,这里的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素的地址是有区别的)

除上述两种情况外,任何地方使用数组名,数组名都表示首元素的地址

五、使用指针访问数组

话不多说,接着看图

数组名arr是数组首元素的地址,可以赋值给p,其实数组名 arr 和 p 在这里是等价的。那我们可以使用arr[i]可以访问数组的元素,将 * (p+i)换成pi也是能够正常打印的,所以本质上pi是等价于*(p+i)。同理arri 应该等价于 * (arr+i)


那么数组和指针又有怎样的关系呢?

这篇关于C语言指针的分享就到这里啦~


总结

从野指针的"坑"到数组与指针的联动,这些都是C语言里既基础又关键的知识点------毕竟指针用好了是"利器",用不好就是"雷区"。 如果你在实际写代码时遇到了指针相关的问题,或者对今天讲的某部分内容有疑问,欢迎在评论区一起交流~ # 结尾 勇敢的寻宝者啊,这次旅途你挖掘到多少宝藏呢,苒苒很期待下次与您相遇!

结语:希望对寻找C语言相关内容的寻宝者有所帮助,不要忘记给博主"一键三连"哦!你的每一次鼓励都为我提供 了前行的动力!

小喵很期待与你再次寻宝奥 ᰔᩚ/•᷅•᷄\୭

相关推荐
Scott9999HH2 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社3 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海3 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖4 小时前
JDK 26 新特性详解
java·开发语言
马优晨5 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区7 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大7 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai7 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜7 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa8 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio