【C语言:深入理解指针一】

文章目录

  • 1.指针存在的意义
  • 2.指针变量和地址
  • 3.指针变量类型的意义
    • 3.1指针解引用
    • 3.2指针+ - 整数
    • 3.3void*
  • 4.关键字const
    • 4.1const修饰变量
    • 4.2 const修饰指针
  • 5.指针运算
    • 5.1指针+ -整数
    • 5.2指针-指针
    • 5.3指针比较大小
  • 6. 野指针
  • 7.assert断言
  • 8. 数组名的理解
  • 9.一维数组传参的本质

在这里插入图片描述

1.指针存在的意义

说到C语言,你是不是最害怕里面的指针呀。看完下面的内容,你或许对指针就没那么胆怯了。
首先,我们要明白C语言中为什么要有指针。

  • 指针是C语言的灵魂,这句话并不夸张。指针是C语言中最基础、最重要的概念之一,它使得C语言成为一门强大的、高效的、灵活的编程语言。
  • 指针的存在使得C语言可以进行复杂的内存操作,能够更好地控制程序的行为,同时也能够实现高效的数据结构和算法。
  • 指针是一个变量,它存储了一个内存地址,而这个内存地址指向的是另一个变量或对象的位置。通过指针,我们可以直接访问或修改这个位置的变量或对象,这为我们提供了很大的灵活性和控制力

在计算机中我们把内存单元的编号也称为地址。C语言中给地址起了新的名字叫:指针。
所以我们可以理解为:内存单元的编号=地址 = 指针

2.指针变量和地址

  1. 在C语言中,创建变量就是向内存申请空间,如下图:

【C语言:深入理解指针一】_第1张图片
上述代码就是创建了一个整型变量a,向内存中申请了4个字节的空间,每个字节都有自己的地址。
&a取出的是a所占4个字节中地址较小的字节的地址。,我们知道了它的地址,就可以顺藤摸瓜访问到4个字节的数据。

  1. 如何拿到变量的地址?

我们通过取地址操作符(&)拿到的地址是⼀个数值,比如:0133FE28,这个数值有时候也是需要存储起来,⽅便后期再使⽤的,那我们把这样的地址值存放在哪⾥呢?答案是:指针变量中。
指针变量的写法:就是在变量前加上一颗 *
【C语言:深入理解指针一】_第2张图片
指针变量也是⼀种变量,这种变量就是用来存放地址的,存放在指针变量中的值都会理解为地址。指针变量也有自己的地址 ,这里pa的地址就是0x00cffdfc。

  1. 如何通过地址获取变量?

我们只要拿到了地址(指针),就可以通过地址(指针)找到地址(指针)指向的对象,这⾥必须学习⼀个操作符叫解引⽤操作符(*)。
【C语言:深入理解指针一】_第3张图片

  1. 指针变量的大小
  • 指针变量的大小取决于地址的大小,与指针变量的类型无关。
  • 32位平台下,地址是32个bit位,即4个字节。
  • 64位平台下,地址是64个bit位,即8个字节。

【C语言:深入理解指针一】_第4张图片

3.指针变量类型的意义

3.1指针解引用

【C语言:深入理解指针一】_第5张图片
【C语言:深入理解指针一】_第6张图片
通过调试我们可以看到,第一个会将a的4个字节全部改为0,第二个只是将a的第⼀个字节改为0。
结论:指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)

3.2指针+ - 整数

【C语言:深入理解指针一】_第7张图片

通过上图我们可以发现 int 类型的指针+1跳过了4个字节,char类型的指针+1跳过1个字节。
结论:指针的类型决定了指针向前或者向后走⼀步有多⼤(距离)

3.3void*

在指针类型中有⼀种特殊的类型是 void* 类型的,可以理解为⽆具体类型的指针(或者叫泛型指针),这种类型的指针可以⽤来接受任意类型地址。但是也有局限性, void* 类型的指针不能直接进行指针的+ -整数和解引⽤的操作。

  • void* pi 可以接受任意类型的地址

【C语言:深入理解指针一】_第8张图片

  • 不能对void 类型的指针进行解引用和加减整数的操作

【C语言:深入理解指针一】_第9张图片

4.关键字const

4.1const修饰变量

如果我们希望⼀个变量加上⼀些限制,不能被修改,怎么做呢?这就是const的作⽤。

  • const修饰变量,变量的值就不能修改了

【C语言:深入理解指针一】_第10张图片

4.2 const修饰指针

  • const 在 * 号的左边

【C语言:深入理解指针一】_第11张图片

  • const 在 * 号的右边

【C语言:深入理解指针一】_第12张图片

  • const在 * 的两边

【C语言:深入理解指针一】_第13张图片

总结::左定值,右定向

5.指针运算

5.1指针+ -整数

【C语言:深入理解指针一】_第14张图片

【C语言:深入理解指针一】_第15张图片

5.2指针-指针

  • 指针-指针的绝对值是两个指针之间的元素个数(两个指针必须指向同一块空间

【C语言:深入理解指针一】_第16张图片

5.3指针比较大小

  • 指针比较大小,就是地址比较大小

【C语言:深入理解指针一】_第17张图片

6. 野指针

概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
野指针是如何造成的呢?

  1. 指针未初始化

局部变量不初始化,默认是随机值。
【C语言:深入理解指针一】_第18张图片

  1. 指针越界访问

【C语言:深入理解指针一】_第19张图片

  1. 指针指向的空间被释放

【C语言:深入理解指针一】_第20张图片

可以打印出来10是因为test函数的栈帧空间还没有被破坏,再次打印的时候,就变成随机值了。

指针虽好,但是不规范使用指针可能会造成意想不到的后果,因此在使用时,应该避免出现野指针。

如何规避野指针呢?

  1. 指针初始化
  2. 不要越界访问
  3. 指针变量不使用时及时置为NULL
  4. 指针使用前检查是否为NULL

7.assert断言

assert.h 头⽂件定义了宏 assert() ,⽤于在运行时确保程序符合指定条件,如果不符合,就报错终⽌运⾏。这个宏常常被称为“断言”。
assert() 宏接受⼀个表达式作为参数。如果该表达式为真(返回值⾮零), assert() 不会产⽣任何作⽤,程序继续运行。如果该表达式为假(返回值为零), assert() 就会报错,在标准错误流 stderr 中写⼊⼀条错误信息,显示没有通过的表达式,以及包含这个表达式的⽂件名和⾏号,如下:

#include
#include
int main()
{
	int* p = NULL;
	assert(p != NULL);
	printf("66666\n");
	return 0;
}

【C语言:深入理解指针一】_第21张图片
使⽤ assert() 有几个好处:它不仅能自动标识⽂件和出问题的行号,还有⼀种⽆需更改代码就能开启或关闭 assert() 的机制。如果已经确认程序没有问题,不需要再做断言,就在 #include 语句的前⾯,定义⼀个宏 NDEBUG 。
【C语言:深入理解指针一】_第22张图片
⼀般我们可以在 Debug 中使⽤,在 Release 版本中选择禁⽤ assert 就行,在 VS 这样的集成开发环境中,在 Release 版本中,直接就是优化掉了。这样在debug版本写有利于程序员排查问题,在 Release 版本不影响⽤⼾使⽤时程序的效率。

8. 数组名的理解

  • 数组名就是数组首元素的地址,但是有2个例外!

【C语言:深入理解指针一】_第23张图片

  • 例外1

sizeof(数组名),sizeof中单独放数组名,数组名表示整个数组的大小,单位是字节。
【C语言:深入理解指针一】_第24张图片

  • 例外2

&数组名,取出的是整个数组的地址(整个数组的地址和数组首元素的地址是有区别的)

【C语言:深入理解指针一】_第25张图片
这里我们发现&arr[0]和&arr[0]+1相差4个字节,arr和arr+1 相差4个字节,是因为&arr[0] 和 arr 都是⾸元素的地址,+1就是跳过⼀个元素
但是&arr 和 &arr+1相差40个字节,这就是因为&arr是数组的地址,+1 操作是跳过整个数组的
到这里大家应该搞清楚数组名的意义了吧。

9.一维数组传参的本质

首先从⼀个问题开始,我们之前都是在函数外部计算数组的元素个数,那我们可以把函数传给⼀个函数后,函数内部求数组的元素个数吗?
【C语言:深入理解指针一】_第26张图片
我们发现结果不是我们想要的,在func函数内部,sizeof(arr) 的大小不是40了,而是4,这是为什么呢?

  • 通过对数组名的学习后我们知道,数组名是数组⾸元素的地址;
  • 那么在数组传参的时候,传递的是数组名,也就是说本质上数组传参本质上传递的是数组⾸元素的地址
  • sizeof一个地址,那它的大小肯定是4/8个字节,所以结果才会是1.
  • 因此我们可以明白:一维数组传参,传递的是数组首元素的地址。当我们接收的参数是数组名的时候,可以写成指针的形式。
void test(int* arr)//参数写成指针形式
{
 	printf("%d\n", sizeof(arr));//计算⼀个指针变量的⼤⼩
}
  • 数组元素的访问在编译器处理的时候,也是转换成⾸元素的地址+偏移量求出元素的地址,然后解引⽤来访问的。

你可能感兴趣的:(C_language,c语言,开发语言)