目录:
Part1:深入理解指针(一)
Part2:深入理解指针(二)
Part3:深入理解指针(三)
Part1:深入理解指针(一)
1. 内存和地址
(1)内存
内存相当于一个大的空间,把内存划分为一个个内存单元,每一个内存单元的大小为一个字节,每个内存单元就相当于一个学生宿舍,一个字节空间里能放8个比特,就好比同学们住八人间,每个人就是一个比特位。每一个内存单元都有一个编号,有了内存单元的编号,CPU就能快速找到一个内存空间。
内存单元(字节)的编号==地址==指针
(2)编址
计算机编址,并不是把每个字节的地址记录下来,而是通过硬件设计完成的,硬件和硬件之前通过地址总线连接起来,32位机器有32根地址总线,每根线可以表示为0/1,所以32条地址总线就有2^32种组合,就对应2^32个不同的地址。所以内存中就有2^32个字节,2的32次方个内存单元,2的32次方×8个比特。
2. 指针变量和地址
(1)取地址操作符(&)
以int a = 10;为例子:
创建一个变量a并赋值为10,在内存上申请4个字节的空间存放10,&a按照16进制输出,&a取出的是a所占4个字节中地址较小的字节的地址,知道第一个字节的地址就可以推出剩下3个字节的地址。
(2)指针变量和解引用操作符
&操作符拿到的地址是一个数值,需要用一个指针变量来存放地址(指针)
Int a= 10; Int * pa = &a; *pa = 0;
Int *中的*说明pa为一个指针变量,int *中的int说明pa最后返回的是int类型。
拿到变量的地址(指针)就可以通过地址(指针)找到地址(指针)所指向的对象,从而通过改变地址(指针)来改变变量,这里就需要运用解引用操作符*,*pa的意思就是通过pa中存放的地址,找到地址所指向的空间,从而去修改变量。
(3)指针变量的大小
指针变量专门用来存放地址,指针变量的大小取决于地址的存放需要多大空间。
对于32位机器,有32根地址总线,如果将一个32根地址总线产生的二进制序列当作一个地址,那么1个地址就是32个比特位,需要4个字节用来存储。
对于64位机器,有64根地址总线,如果将一个64根地址总线产生的二进制序列当成一个地址,那么1个地址就对应64个比特位,需要8个字节来存储。
3. 指针变量类型的意义
(1)指针的解引用
指针类型决定了,对于指针解引用的时候有多大的权限(一次能操作几个字节)
char*只能访问一个字节
short*只能访问2个字节
int*只能访问4个字节
double*只能访问8个字节
%p打印地址
(2)指针+-整数
char*类型的指针变量+1跳过1个字节,int*类型的指针变量+1跳过了4个字节。
指针的类型决定了指针向前或者向后⾛⼀步有多⼤(距离)。
(3)void*指针(泛型指针)
void*类型指针可以接受任意类型的指针,但void*类型指针的局限性在于不能直接进行指针的+-和解引用操作。
4. const修饰指针
(1)const修饰变量
const int a = 0;
a不能被修改,但a的本质还是变量,只不过被const修饰后,在语法上加了限制,只要我
们在代码中对n就⾏修改,就不符合语法规则,就报错,致使没法直接修改n。此时a为常变量。
但可以通过修改a的地址来修改a。
int * pa = &a;
*pa = 10;
(2)const修饰指针变量
const int * pa = &a;
*pa = 0;(×)
pa = 0;(√)
这里的const修饰*pa使得不能通过指针变量p来修改p所指向的内容
Int * const pa = &a;
这里const修饰pa,意思是pa不能被修改
*pa = 0;(√)
pa = 0;(×)
const int * const pa = &a;
*pa = 0;(×)
pa = 0;(×)
5.指针运算
(1)指针+-整数
Arr[i]→*(p+i) p+i是i×sizeof(类型)
(2)指针-指针
指针-指针得到的绝对值是指针与指针之间元素的个数,不同类型字节不同,相差相同字节前提下,元素个数也可能不同。
前提条件:两个指针指向同一块空间
(3)指针的关系运算
arr是首元素地址 arr+sz指数组最后一个元素的地址 p中存放&arr[0]==arr为首元素地址 p<arr+sz指首元素到最后一个元素 p++操作由于p指向int类型,所以一次跳过4个字节,一个数组元素
6. 野指针
7. assert断言
#include <assert.h>头文件定义了宏assert()
宏assert()接收了一个表达式作为参数,当表达式成立(返回值非0)assert()不会产生任何作用,当表达式不成立(返回值为0),assert()就会报错
8. 指针的使用和传址调用
(1)strlen的模拟实现
Char * str 作为指针变量,接受字符串 str内存放字符串第一个字符的地址,const限制*str(a)使得字符串本身不能被改变 Str++根据str返回类型是char类型,str++跳过一个字节
(2)传值调用与传址调用
传值调用时,函数的实参传递给形参时,形参时实参的一份临时拷贝,对形参的修改不会改变实参,因为形参又开辟了一块空间,对于形参空间的改变不会影响实参空间的变化。
想要真正改变实参时,可以在函数接受实参时,形参创建为指针变量,接受实参的地址,这样可以通过形参间接改变实参,通过pa中存放的地址,找到地址所指向的空间,从而去修改变量。
传址调⽤,可以让函数和主调函数之间建⽴真正的联系,在函数内部可以修改主调函数中的变量;所以未来函数中只是需要主调函数中的变量值来实现计算,就可以采⽤传值调⽤。如果函数内部要修改主调函数中的变量的值,就需要传址调⽤。
深入理解指针(二)
1. 数组名的理解
一般情况下,数组名就表示首元素地址,但是有两个例外:
sizeof(数组名)计算整个数组的大小,单位是字节
&数组名,这里数组名表示整个数组,取出的是整个数组的地址
2.使用指针访问数组
arr表示首元素地址 arr<--->p int * p = arr;-->将数组首元素地址存在p中 p+i表示地址加i×sizeof(类型)-->跳过的元素arr[i]==p[i]==*(p+i)==*(arr+i)
3. 一维数组传参的本质
本质上数组传参本质上传递的是数组⾸元素的地址。
所以函数形参的部分理论上应该使⽤指针变量来接收⾸元素的地址。那么在函数内部我们写sizeof(arr)计算的是⼀个地址的⼤⼩(单位字节)⽽不是数组的⼤⼩(单位字节)。正是因为函数的参数部分是本质是指针,所以在函数内部是没办法求的数组元素个数的。
⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。
4. 二级指针
一级指针char * pc int * a
二级指针char **ppc int ** pa
变量有地址 一级指针内存放变量的地址 二级指针内存放一级指针的地址同时二级指针也有地址
*ppa对于ppa中的地址进行解引用,这样就能找到pa,pa内存放的是a的地址
*ppa==pa==&a
**ppa就是对ppa中的地址进行解引用,这样就能找到pa,再对pa进行解引用,就能通过pa内存放的a的地址找到a,从而对a进行改变。
5. 指针数组
整型数组是数组,存放整数;字符数组是数组,存放字符;指针数组是数组,存放指针
指针数组的每个元素都是用来存放指针(地址)的。
指针数组的每一个元素都是地址,又可以指向一个元素。
6. 指针数组模拟二维数组
数组名是数组首元素地址,可以将三个数组提取出首元素放在新的指针数组中 parr[i][j]==*(parr+i)[j]==*(arr i[j])==arr i j
深入理解指针(三)
1. 字符指针变量
一般使用:char * i = ‘w’;
另一种使用方法:char * j = “abcdef”;
此时是将字符串”abcdef”的首元素地址放到了j中 j[number]==*(j+number)
此时可以将字符串想象成一个数组,
arr[i]==*(arr+i)(数组)<---->j[number]==*(j+number)(字符串)
不同点在于数组可以改动,字符串不能改动
2. 数组指针变量
(1)数组指针变量是什么
整形指针变量是指针变量,存放整型变量的地址 int * pint int*为指针类型
字符指针变量是指针变量,存放字符变量的地址 char * pf char *为指针类型
数组指针变量是指针变量,存放数组变量的地址 int (*p2)[10] int(*)[10]为指针类型
(2)数组指针变量怎么初始化
获取数组的地址&arr
使用数组指针变量存放获取的数组地址int (*p)[10] = &arr; p==&arr *p==*&arr==arr
3. 二维数组传参的本质
数组初始化:Int arr[3][5] = {{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};
函数形参接收数组(接受数组第一行-->一个一维数组)int (*p)[5]
数组输出:*((*(p+i)+j))
二维数组传参时形参的接受可以使用指针也可以使用数组
二维数组其实是元素为一维数组的数组
首元素其实就是第一行,首元素地址就是第一行的地址
4. 函数指针变量
(1)函数指针变量的创建
整形指针变量存放整形类型的地址;字符指针变量存放字符类型的地址
数组指针变量存放数组的地址;函数指针变量存放函数的地址
数组名------数组首元素地址
&数组名------整个元素的地址
函数名------函数的地址
&函数名------函数的地址
Int (*p)(int,int)------类型int(*)(int,int)
(2)函数指针变量的使用
(3)typedef关键字
代码1:(*(void(*)())0)(); Void(*)()--函数指针类型 (void(*)())0--将0强制类型转换
上述代码是一次函数调用:
- 把0整个整数值,强制转化为一个函数的地址,这个函数没有参数,返回类型是void
- 去调用0地址的函数
代码2:void(*signal(int,void(*)(int)))(int); signal是一个函数
signal函数的参数有两个,第一个是int类型,第二个是函数指针类型,该指针指向的函数参数是Int,返回类型是void
signal函数的返回类型是void(*)(int)函数指针,该指针指向的函数参数是int,返回类型是void
一般类型:typedef int* ptr_t
数组指针和函数指针:typedet (*parr_t)[5]
5. 函数指针数组
函数指针数组在函数指针条件下加工
函数指针 int (*p)(int) 函数指针数组 int (*p[10])(int)
函数指针数组内元素类型是int (*)(int)
6. 转移表
替代switch+函数调用:
Int (*p[5])(int x,int y)={0,add,sub,mul,div};函数声明
(*p[number])(x,y);函数调用
p先与[number]结合表示函数指针数组对应的指针,再对p[number]进行解引用操作,找到函数,再对函数进行传参