对于指针的理解

目录:

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强制类型转换

上述代码是一次函数调用:

  1. 把0整个整数值,强制转化为一个函数的地址,这个函数没有参数,返回类型是void
  2. 去调用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]进行解引用操作,找到函数,再对函数进行传参

  • 22
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值