【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个字节,不就是整个数组吗?这是为什么呢?

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

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

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

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

五、使用指针访问数组

话不多说,接着看图

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


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

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


总结

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

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

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

相关推荐
Tandy12356_3 小时前
手写TCP/IP协议——IP层输出处理
c语言·网络·c++·tcp/ip·计算机网络
Y1rong6 小时前
C++ QT之记事本
开发语言·qt
diegoXie10 小时前
Python / R 向量顺序分割与跨步分割
开发语言·python·r语言
程序员小白条10 小时前
0经验如何找实习?
java·开发语言·数据结构·数据库·链表
liulilittle10 小时前
C++ 浮点数封装。
linux·服务器·开发语言·前端·网络·数据库·c++
失散1310 小时前
Python——1 概述
开发语言·python
萧鼎10 小时前
Python 图像哈希库 imagehash——从原理到实践
开发语言·python·哈希算法
小小8程序员11 小时前
STL 库(C++ Standard Template Library)全面介绍
java·开发语言·c++
立志成为大牛的小牛11 小时前
数据结构——五十六、排序的基本概念(王道408)
开发语言·数据结构·程序人生·算法