目录
前言
本章我们将学习指针,从初识到进阶完整的向大家剖析指针的原理及应用,文章分为指针——初阶、指针在数组中的妙用、指针类型——进阶、回调函数实际应用四个部分
第一部分包含指针的一些基础知识,为深入理解指针打好基础;第二部分阐释指针在数组中的应用,通过指针更好的运用数组;第三部分向大家介绍几种复杂一些的指针和数组类型及相关的实际应用;第四部分主要介绍回调函数的概念及意义,小伙伴们可以根据自身需求选择对应板块观看
一、指针——初阶
1、内存和地址
我们先讲一个生活中的案例
假设有一栋宿舍楼,把你放在楼里,楼上有100个房间,但是房间没有编号,你的一个朋友来找你玩, 如果想找到你,就得挨个房子去找,这样效率很低,但是我们如果根据楼层和楼层的房间的情况,给每个房间编上号
有了房间号,如果你的朋友得到房间号,就可以快速的找到房间,找到你
如果把上面的例子对照到计算中,又是怎么样呢? 我们知道计算上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回内存中,那我们买电脑的时候,电脑上内存是8GB/16GB/32GB等,那这些内存空间如何高效的管理呢? 其实也是把内存划分为一个个的内存单元,每个内存单元的大小取1个字节
其中,每个内存单元,相当于一个学生宿舍,一个字节空间里面能放8个比特位,就好比同学们住的八人间,每个人是一个比特位
每个内存单元也都有一个编号(这个编号就相当 于宿舍房间的⻔牌号),有了这个内存单元的编 号,CPU就可以快速找到一个内存空间
生活中我们把门牌号也叫地址,在计算机中我们把内存单元的编号也称为地址。C语⾔中给地址起 了新的名字叫:指针
所以我们可以理解为: 内存单元的编号==地址==指针
2、指针变量和地址
(1)取地址操作符
我们了解完地址的概念,下面教大家怎样取出一个元素的地址,假设我们创建了一个整型变量a
int a = 10
创建完成后会向内存空间申请一块内存来储存a的值,由于a是整型变量,在内存空间中会占四个字节,也就是说系统会为它分配四个地址,这四个地址指向的空间是连续存放的,我们为了方便使用,取其中最小编号的一块作为a的地址,这样顺藤摸瓜就能找到其余的三块空间
如何将这个地址取出呢,我们这时就要用到取地址操作符 & ,&a即表示取出a的地址
(2)指针变量
那我们通过取地址操作符(&)拿到的地址是一个数值,比如:0x006FFD70,这个数值有时候也是需要存储起来,方便后期再使用的,那我们把这样的地址值存放在哪里呢?答案是:指针变量中
#include <stdio.h>
int main()
{
int a = 10;
int* pa = &a;
//取出a的地址并存储到指针变量pa中
return 0;
}
指针变量也是⼀种变量,这种变量就是用来存放地址的,存放在指针变量中的值都会理解为地址
32位平台下地址是32个bit位,指针变量大小是4个字节
64位平台下地址是64个bit位,指针变量大小是8个字节
注意指针变量的大小和类型是无关的,只要指针类型的变量,在相同的平台下,大小都是相同的
(3)指针类型
我们看到pa的类型是 int* ,我们该如何理解指针的类型呢?
这⾥pa左边写的是 int* , * 是在说明pa是指针变量,而前面的 int 是在说明pa指向的是整型(int)类型的对象
(4)解引用操作符
我们将地址保存起来,未来是要使用的,那怎么使用呢? 在现实生活中,我们使用地址要找到一个房间,在房间里可以拿取或者存放物品
C语言中其实也是一样的,我们只要拿到了地址(指针),就可以通过地址(指针)找到地址(指针) 指向的对象,这里必须学习一个操作符叫解引用操作符(*)
int main()
{
int a = 100;
int* pa = &a;
*pa = 0;
return 0;
}
上⾯代码中第5行就使用了解引用操作符, *pa 的意思就是通过pa中存放的地址,找到指向的空间, *pa其实就是a变量了;所以*pa=0,这个操作符是把a改成了0
3、指针变量类型的意义
(1)指针的类型决定了,对指针解引用的时候有多大的权限(一次能操作几个字节)。 比如:char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节
(2) char* 类型的指针变量+1跳过1个字节, 这就是指针变量的类型差异带来的变化。 int* 类型的指针变量+1跳过了4个字节。 结论:指针的类型决定了指针向前或者向后走一步有多大(距离)
(3)在指针类型中有一种特殊的类型是 void* 类型的,可以理解为无具体类型的指针(或者叫泛型指针),这种类型的指针可以用来接受任意类型地址。但是也有局限性,行指针的+-整数和解引用的运算
4、const修饰指针变量
我们看下面代码,来分析
#include <stdio.h>
//
代码
1
void test1()
{
int n = 10;
int m = 20;
int *p = &n;
*p = 20;//ok?
p = &m; //ok?
}
void test2()
{
//
代码
2
int n = 10;
int m = 20;
const int* p = &n;
*p = 20;//ok?
p = &m; //ok?
}
void test3()
{
int n = 10;
int m = 20;
int *const p = &n;
*p = 20; //ok?
p = &m; //ok?
}
void test4()
{
int n = 10;
int m = 20;
int const * const p = &n;
*p = 20; //ok?
p = &m; //ok?
}
int main()
{
//
测试⽆
const
修饰的情况
test1();
//
测试
const
放在
*
的左边情况
test2();
//
测试
const
放在
*
的右边情况
test3();
//
测试
*
的左右两边都有
const
test4();
return 0;
}
结论:const修饰指针变量的时候
• const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。 但是指针变量本⾝的内容可变
• const如果放在*的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指 向的内容,可以通过指针改变
5、指针运算
指针的基本运算有三种,分别是:
• 指针+-整数
• 指针-指针
• 指针的关系运算
(1)指针加减整数
因为数组在内存中是连续存放的,只要知道第⼀个元素的地址,顺藤摸⽠就能找到后⾯的所有元素
#include <stdio.h>
//指针+- 整数
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
for(i=0; i<sz; i++)
{
printf("%d ", *(p+i));//p+i 这⾥就是指针+整数
}
return 0;
}
(2)指针-指针
//指针-指针
#include <stdio.h>
int my_strlen(char *s)
{
char *p = s;
while(*p != '\0' )
p++;
return p-s;
}
int main()
{
printf("%d\n", my_strlen("abc"));
return 0;
}
(3)指针的关系运算
//指针的关系运算
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
while(p<arr+sz) //指针的⼤⼩⽐较
{
printf("%d ", *p);
p++;
}
return 0;
}
6、野指针
概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
成因有三点:
(1) 指针未初始化
(2)指针越界访问
(3)指针指向已经被释放的内存空间
7、assert断言
assert.h 头文件定义了宏 assert() ,用于在运行时确保程序符合指定条件,如果不符合,就报错终止运行。这个宏常常被称为“断言”
二、指针在数组中的妙用
1、数组名的理解
数组名就是数组首元素(第一个元素)的地址
两个例外:
(1)sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表示整个数组,计算的是整个数组的大小, 单位是字节
(2)&数组名,这里的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素 的地址是有区别的)
除此之外,任何地方使用数组名,数组名都表示首元素的地址
&arr[0]和&arr[0]+1相差4个字节,arr和arr+1相差4个字节,是因为&arr[0]和arr都是首元素的地址,+1就是跳过一个元素。 但是&arr和&arr+1相差40个字节,这就是因为&arr是数组的地址,+1操作是跳过整个数组的
本质上p[i]是等价于*(p+i),同理arr[i] 应该等价于*(arr+i)
2、一维数组传参的本质
本质上数组传参传递的是数组首元素的地址
一维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式
3、二级指针
上述所说的指针是一级指针,我们要想存储一级指针的地址,这便引出了二级指针的概念,二级指针里存储的便是一级指针的地址
*ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa
**ppa 先通过 *ppa 找到 pa ,然后对pa 进行解引用操作: *pa ,那找到的是 a
4、指针数组
指针数组的每个元素都是用来存放地址(指针)的
指针数组的每个元素是地址,又可以指向一块区域
三、指针类型——进阶
1、数组指针变量
之前我们学习了指针数组,指针数组是⼀种数组,数组中存放的是地址(指针)。 数组指针变量是指针变量?还是数组? 答案是:指针变量
数组指针变量应该是:存放的应该是数组的地址,能够指向数组的指针变量
int (*p)[10];
2、二维数组传参本质
首先我们再次理解一下二维数组,二维数组起始可以看做是每个元素是一维数组的数组,也就是二维数组的每个元素是一个一维数组。那么二维数组的首元素就是第一行,是个一维数组
所以,根据数组名是数组首元素的地址这个规则,二维数组的数组名表示的就是第一行的地址,是一维数组的地址
3、函数指针变量
什么是函数指针变量呢? 根据前⾯学习整型指针,数组指针的时候,我们的类比关系,我们不难得出结论: 函数指针变量应该是用来存放函数地址的,未来通过地址能够调用函数的
int(*pf3)(int, int) = Add;
int(*pf3)(int x, int y) = &Add;
//x和y写上或者省略都是可以的
4、函数指针数组
要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?
int (*parr1[3])();
四、回调函数
回调函数是什么? 回调函数就是一个通过函数指针调用的函数。 如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,被调用的函数就是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应
本章到这里就结束啦,希望各位小伙伴可以点个关注,支持一下阿鹿,大家的鼓励是我前进的第一动力,3Q~