指针的综合应用

1.把内存划分为一个个内存单元,这个内存单元的大小是一个字节
2.每个字节都给唯一的编号,这个编号称为地址,地址在c语言中也叫做指针
3.平时所称的指针,通常指的是指针变量,是用来存放内存地址的变量。可以通过&(取地址操作符)取出变量的内存真实地址,把地址可以存放到一个变量中,这个变量即为指针变量,存放在指针变量的值都当做地址来处理
4.int a=10;//在内存中开辟一个空间
int* p=&a;//这里用&取出a的地址,a占用四个字节,这里是将4个字节的第一个字节的的地址存放在p变量中,p就是一个指针变量
5.由于指针内存放的是地址,故指针的大小与指针指向的类型无关,32位机器上指针的大小为4个字节,64位机器上指针的大小为8个字节
在32位的机器上,地址是由32个0或1组成的二进制序列,那地址需要用4个字节来存储
在64位的机器上,地址是由64个0或1组成的二进制序列,那地址需要用8个字节来存储
6.指针的类型决定了,对于指针解引用时可以操作几个字节(有多大的权限)
char* 的指针解引用就只能访问一个字节,而int* 的指针解引用就能访问四个字节

int a=10;
int pa=&a;
char pc=&a;
printf(%p”,pa); 结果为 005EF718
printf(%p”,pa+1); 结果为 005EF71C,即相比pa,pa+1会跳过一个字节的地址
printf(%p”,pc); 003EF718
printf(%p”,pc+1); 003EF719
pa+a  =pa+nsizeof(int)
pc+a  =pa+nsizeof(char)

指针的类型决定了指针向前或者向后走一步有多大的距离
在这里插入图片描述
char* p 由于p的指针类型为char型,所以解引用时只能访问一个字节,p++时每次++也只能向前走(跨越)一个字节,故最后为
1 2 3 4 5 6 7 8 9 10 0 0

#include <stdio.h>
int main()
{
  int arr[] = {1,2,3,4,5};
  short *p = (short*)arr;//由于数组名是数组首元素的地址,即数组名arr为int*类型的指针,故必须强制转换为short*类型的指针
  int i = 0;
  for(i=0; i<4; i++)
  {
    *(p+i) = 0;
  }
   
  for(i=0; i<5; i++)
  {
    printf("%d ", arr[i]);
  }
  return 0;
}

arr数组在内存中的存储格式为:

0x00ECFBF4:  01 00 00 00
0x00ECFBF8:  02 00 00 00
0x00ECFBFC:  03 00 00 00
0x00ECFC00:  04 00 00 00
0x00ECFC04:  05 00 00 00

指针p的类型为short*类型的,因此p每次只能所有两个字节,for循环对数组中内容进行修改时,一次访问的是:
arr[0]的低两个字节,arr[0]的高两个字节,arr[1]的低两个字节,arr[1]的高两个字节,故改变之后,数组中内容如下:

0x00ECFBF4:  00 00 00 00
0x00ECFBF8:  00 00 00 00
0x00ECFBFC:  03 00 00 00
0x00ECFC00:  04 00 00 00
0x00ECFC04:  05 00 00 00

该题中,由于p指针为short类型,故对其解引用时只有修改2个字节的权限,每次加1,步长为2个字节,故最后只修改了8个字节,即2个元素,最后打印的结果为0 0 3 4 5

int a = 0x11223344;

其在内存中的存储
假设,a变量的地址为0x64,则a变量在内存中的模型为:

0x64| 44 |
0x65| 33 |
0x66| 22 |
0x67| 11 |

char类型的指针变量pc指向只能指向字符类型的空间,如果是非char类型的空间,必须要将该空间的地址强转为char类型。
char pc = (char)&a; pc实际指向的是整形变量a的空间,即pc的内容为0x64,即44,
*pc=0,即将44位置中内容改为0,修改完成之后,a中内容为:0x11223300

7.①指针-指针(地址-地址)
两个指针指向同一块空间(指针的类型要是一致的),得到的是两个指针之间元素的个数
②允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但不允许与指向第一个元素之前的那个内存位置的指针进行比较
③arr[i]完全等价于arr+i完全等价于i+arr完全等价于i[arr]

8.二级指针

int a=10;
int* p1=&a;
int **p2=&p1;

int * 代表着p1是指向int 类型的指针,p中存放的是a的地址
int* 代表着p2是指向int 类型的地震,p2中存放的是p1的地址,也就是指针的地址,就可以说p2是一个二级指针,对p1解引用可以得到a,对p2解引用可以得到a的地址,再解引用可以得到a
9.指针数组
用类比的思想来考虑,整形数组是存放整形的数组,字符数组是存放字符的数组,故指针数组也是用来存放指针的数组

int* p[10]={&a,&b,&c}; 这里的p就是用来存放指针的指针数组

我们可以用指针数组来模拟二维数组



int a1={2,3,4,5,6};
int a2={3,4,5,6,7};
int a3={4,5,6,7,8};
int* p[3]={a1,a2,a3};//这里的a1,a2,a3,都是指针,指向的是每个数组首元素,存放的是每个数组首元素的地址
for(int i=0;i<3;i++){
for(j=0;j<5;j++){
printf("%d ",p[i][j]);
}
printf("\n");
}

11.字符指针
char* p=“abcdef”
此时p指向的是常量字符串,是将字符串首元素的地址存放于该指针中,此时该字符串的内容是不可以通过指针来修改的,故也可以写成这种形式 const char* p=“abcdef”,用const在左边来限定指针,左定值
要想对字符串的内容进行修改,就需要写为另外一种形式

char c[]=“abcdef”;
char* p=&c;

此时p指针中存放的是c这个字符串首元素的地址(因为字符串也是数组,int* 时指针存放的是数组首元素的地址,故这里p存放的是字符串首元素的地址),可以通过p对字符串进行修改

char str1[]="hello bit";
char str2[]="hello bit";
char* str3="hello bit";
char* str4="hello bit";
if(str1==str2) printf("str1 and str2 are same");
else printf("str1 and str2 are not same");
if(str3=str4) printf(’str3 and str3 are same");
else printf("str3 and str4 are not same");

最终的结果是,str1 and str2 are not same
str3 and str4 are not same
原因是:str3和str4指向的是一个同一个常量字符串,c/c++会把常量字符串 存储到单独的一个内存区域,当几个指针,指向同一个字符串时,他们实际指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟不同的内存块,所以str1和str2不同,str3和str4相同
12.数组指针
同样是类比的思想,整形指针-指向整形的指针
数组指针-指向数组的指针
而数组指针,就是指向数组的指针

   **int(*p)[10]//注意与int*p[10]这个指针数组相区别
   //数组指针中的p因为有括号首先与*结合,*p说明p是一个指针,而剩下的int [10]代表着p指向的是一个数组,从而说明p是一个数组指针
   //而int*p[10]中,当不存在括号时,p会先与[10]结合,说明p是一个大小为10的数组,而前面的int*代表着数组里面存放的为指针变量**

13.&数组名与数组名的区别
首先,数组名就是首元素的地址数组名就是首元素的地址数组名就是首元素的地址
除了以下两种情况
①.sizeof(数组名)-sizeof内部单独放数组名,数组名表示的时整个数组,计算的是整个数组的大小
②.&数组名,这里的数组名表示整个数组。数组名是首元素的地址,&数组名取出的是数组的地址,从地址的角度来说,数组名代表的首元素的地址与&数组名相同,但意义不同
int arr[10];
当arr+1时,由于arr是首元素的地址,也就是说arr是一个int*类型的指针,它的步长为4对其+1时,它会跳过4个字节
而对&arr+1时,&arr同样也是指针,只不过它是一个数组指针,它的步长为40,对其+1时,它跳过40个字节
14.数组指针的使用

int a[10]={1,2,3,4,5,6,7,8,9,10};
int(*p)[10]=&a;
//当我们要使用数组指针时,注意*p的结果就是a,也就是对数组指针直接解引用,其结果就是数组名,而数组名又是首元素的地址
  *((*p)+i)*(a+i)等价,与a[i]也等价

二维数组的数组名,也是首元素的地址,只不过,二维数组是一维数组的数组,故二维数组首元素的地址为二维数组首行的地址,也就是二维数组首数组的地址,可以说二维数组的数组名就是一个数组指针,它指向的是第一个一维数组,存放的是第一个一维数组的地址

int arr[3][3]

当arr+i时,就找到了第i行也就是第i个数组的地址 (arr+i)实际上就是arr[i],而arr[i]是一个一维数组的数组名,一维数组的数组名是首元素的地址,arr[i]与 &arr[i][0]相同,故arr[i]+j即为第i行第j个元素的地址,再对其解引用可以得到第i行第j列的元素,也就是第i个一维数组中第j个元素
故arr[i][j]= (*(arr+i)+j)
重点就是,当对指针数组解引用时 *(arr+i)就与arr[i[完全相同,完全相同,完全相同

16.数组参数,指针参数(一维数组传参,二维数组传参,一级指针传参,二级指针传参)

假如函数的参数需要传入一维数组时,一维数组传参,传入的(实参)实质上是一个能指向一维数组首元素的指针,二维数组传参时,传入的(实参)实质上时一个指向二维数组首元素的指针,也就是传入一个指向二维数组中第一个一维数组(第一行)的指针。至于接受的形参,则需要根据指向的元素来判断
请添加图片描述
上述情况都是可行的,实参为一维数组时,形参可以是数组(这个数组可以指定大小,也可以不指定大小,因为不会真的创建一个数组),形参也可以是指针,但如果形参是指针需要注意类型。如上述中第三个 形参为int* arr,实参为arr数组名为首元素的地址,而数组内每个元素都是int类型的,所以arr是一个int*类型的指针,相符合。如上述第五个,形参为int** arr,实参为arr2数组名为首元素的地址,因为arr2为指针数组,数组的每个元素为指针,所以arr2(单纯这个数组名)是一个二级指针,所以形参应该用二级指针来接收,相符合。(指针数组传参,传入数组首元素的地址,即指针的地址,接受的类型肯定为二级指针,当然也可以如第四个一样直接用指针数组来接收)

在这里插入图片描述
无论是二维数组还是其他数组,**任何数组传参,实参总是指向该数组首元素的指针,也就是数组首元素的地址,也就是数组名。**而形参则需要根据数组首元素的地址的类型来判断。如对于二维数组,指向二维数组首元素的指针即为数组指针,所以形参必须为二维数组或者数组指针。但二维数组的行可以省略,但列不可以省略。
对于一维指针数组,指向首元素的指针为指针的指针,即指针的地址,即形参必须为指针数组或者二级指针

当一个函数的形参部分为一级指针的时候

int a;
int* p1=&a;
int arr[10];
void p(int *p,int n);

此处传入&a,p1,arr类型都匹配,都是可以的

当一个函数的形参部分为二级指针时

void test(char **p);
char c='b';
char*p=&c;
char**pp=&p;
char*arr[10];//字符指针数组,数组内每个元素存放的是字符指针
test(pp);
test(&p);
test(arr);

pp,&p,arr(arr为数组名为数组首元素的地址,为一个字符指针的地址,即指针的指针即二级指针)
例如void print(int(*arr)[5],int row,int col)//这里的int(*arr)[5]就是一个数组指针,指向的是一个大小为5的整形数组
17.int(parr3[10])[5] parr3首先先与[10]结合,代表着这是一个数组,且数组中有10个元素 剩下的代表数组类型int()[5]代表着一个数组指针,该指针指向一个大小为5的数组
所以in(*parr3[10])[5]是一个存放着10个数组指针,每个数组指针指向5个元素的数组
在这里插入图片描述
18.函数指针
函数指针-指向函数的指针
&函数名与直接函数名相同,都代表函数的地址,没有区别
函数的地址要存放起来就需要存放在函数指针变量中

int Add(int x,int y){
    return x+y;
}
int (*pf)(int ,int )=Add;//等效于 int (*p)(int ,int )=&Add,注意这里的pf也要与数组指针一样先用(*pf)括号括住,不然会与后面的()结合

pf就是一个函数指针变量
要想利用函数指针来调用函数

int ret=(*pf)(3,5);//加星号一定要括起来,不然会首先与后面结合
//不过,这里的*其实无用,可以换为
int ret=pf(3,5);
//与这个是等效的
int ret=Add(3,5);

函数指针类型的写法 int(*)(int ,int ),这里的int根据返回值和参数的不同而不同
将类型放到括号内是强制类型转换
利用函数指针调用函数或者直接调用函数都必须传参,通过以上几点,可以解读下面这行代码

(*(void(*)())0)()
//1.第一步,将0强制转换为void(*)()类型的函数指针
//2.这就意味着0地址处放着一个函数,函数没有参数,返回类型为void
//3.调用0地址处的函数,完全可以将这个*去掉
void(*signal(int ,void(*)(int))(int)

signal(int ,void()(int)只交代了类型,没有具体参数,第一个参数类型是int,第二个参数类型是函数指针 void()(int)
但还缺返回类型。将signal(int ,void()(int)去掉(将一个函数的函数名字,函数参数去掉),剩下的即为返回类型为void()(int)
即返回类型为一个函数指针

上述代码为函数声明,函数的名字是signal,函数的参数第一个是int类型,第二是void(*)(int)类型函数指针,该函数指针指向的函数参数是int,返回类型是void
signal函数的返回类型也是一个函数指针,该函数指针指向的参数是int,返回类型是void

可以进行改进
typedef void(pfun_t)(int t);//将void()(int)重新起个别名叫pfun_t
pfun_t signal(int ,pfun_t)

注意
typedef void(pf)(int t);中的 pt是一种类型,是将 void()(int)这个函数指针类型重命名为pt类型
void(*pf)(int)中的pf是函数指针变量的名字

19.函数指针数组
函数指针数组的每个元素是一个函数指针

int(*pf1)(int ,int )=Add;
int(*pf2)(int ,int)=Sub;
int(*pf3)(int ,int)=Mul;
int(*pf4)(int ,int)=Div;
inf(*pf[4])(int ,int )={Add,Sub.Mul,Div};

只要让pf5后面加上括号[],pf就不会先与*结合而是会与[]结合,成为一个数组
for(i=0;i<4;i++){
int ret=pfi;
printf(“%d”,ret);
}
20.指向函数指针数组的指针
&函数指针数组
int(*p)(int ,int );//函数指针
int(*p2[4])(int ,int )//函数指针数组
&p[2]取出的是函数指针数组的地址
如果把地址放入p3中,那p3就是一个指向函数指针数组的指针

int(*(*p3)[4])(int ,int )=&p2;//分析:首先p3与*结合代表它是一个指针,后面的[4]代表它指向的是一个数组,而这个数组的类型为函数指针类型
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值