指针深入理解

初始指针

指针的理解
  • 计算机的硬件单元之间需要互相协同,满足数据的传递。但是硬件之间相互独立,彼此之间通过“线”联系在一起。
  • 程序的执行是先把代码加载进入内存中,然后通过CPU的读写来进行数据的相互交互,这种情况也是通过"线”连接而来。CPU要访问内存中的某个字节空间,需要知道这个字节空间所在的地址
    ​​​​在这里插入图片描述
  • 32位机器有32根地址总线,每根线只有两态,表示0,1【电脉冲有无】,那么一根线,就能表示2种含义。
  • 地址信息被下达给内存,在内存内部,就可以找到该地址对应的数据,将数据在通过数据总线传入CPU内寄存器,实现数据的交互。因此,内存当中的编址并不需要开辟空间为其存储,而是通过硬件电路的方式对内存进行编址的,我们内存的那些地址值,高地址到低地址的排序等这些都是硬件中规定好的,我们只需要遵守它的规定,正确使用规定就好。而我们所说的地址,又称之为"指针"。
  • 指针可以理解为地址,或者是具有指向性的数字。地址的字节数是4个字节。内存单元的字节大小是一个字节,内存单元的编址就是地址。
  • 变量表示空间时,一般格式表示为在赋值符号“=”的左边,称左值; 作为数据属性时,表示内容,格式一般表示为在赋值符号“=”的右边,称右值
  • 每个地址标识一个字节(内存单元),那么我们就可以给出(2^32 Byte <–> 2^32 / 1024 KB <–>2^32 / 1024 / 1024 MB <–> 2^32 / 1024 / 1024 / 1024 GB <–> 4GB)4G的空间进行编址。在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节。那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。
  • 对变量取地址,不用考虑该变量的类型,该地址是变量在内存中开辟众多字节数的最低地址。指针永远指向变量在内存中开辟众多字节数的最低地址。
    在这里插入图片描述
  • 对指针解引用,代表指针所指向的目标。即对指针变量p解引用,使用的是指针变量p里面的内容,即右值
    在这里插入图片描述
    第一行:定义一个变量a并初始化,值为10;
    第二行:定义一个指针变量p,其指向的内存里面保存的是int类型的数据;在定义变量p的同时把p的值设置为a的地址;
    第三行:给*p赋值为NULL,即给p指向的内存赋值为NULL;而p本身的值,即保存变量a的地址并没有改变。NULL是一个宏,被宏定义为0。
    在这里插入图片描述
    图中的&p是得到二级指针,将二级指针强制转为一级指针类型,保存进指针变量p中,其不再指向NULL,而是指向&p;换句话说,就是定义一个指针,自己指向自己。
指针的运算
  • 指针±整数
    指针变量的类型取决于的指针解引用指向的目标类型
    我们对指针变量行+1操作时,其本质就是将指针变量保存的指针往高地址进行移动,移动的距离(步长)取决于该指针变量是什么类型
    指针变量的类型决定了指针变量(做为右值,取其内容)±n(整数)时,向低地址或者向高地址走n步有多大(距离)。
  • 指针-指针
    存在两个指针指向同一数组,两指针进行相减操作,结果为两指针包含的数组元素的个数。(总字节数除以每个数组元素所占的字节数)。
    在这里插入图片描述
数组与指针
  • 任何一个c语言当中的变量取地址时,取出来的地址值一定是此变量所开辟的众多字节当中的最小地址
    ​​在这里插入图片描述

  • &a[0]:数组名a分别和运算符&和运算符[]结合,我们知道,运算符[]的运算优先级比运算符&高,所以数组名a先和运算符[]结合,此时表示首元素,在与运算符&结合表示首元素地址,就是数组内第一个元素的地址
    &a:数组名与运算符&结合表示数组的地址,就是数两者在数字层面上的值是一样的,主要差别是体现在类型上。我们知道两者的数组整体都是地址,而地址就是指针,对此进行指针+1,即&a[0]+1和&a+1时;我们发现指针移动的步长不一样,对&a[0]+1,指针移动的步长为该元素保存的数据的类型大小;对&a+1,指针移动的步长为该数组中元素保存的数据类型大小乘上元素个数。

  • 当一个数组名表示整个数组只有俩种情况:
    &a或者sizeof(a)

  • 多维数组的内存存储

在这里插入图片描述

  • 数组中元素的类型,就是去掉数组名和第一个方括号[ ]及其里面的常量表达式,剩余的就是数组中元素类型;数组中第一个方括号代表的是该数组包含的元素的个数,里面的常量表达式可以省略不写,如果不写,数组的元素个数由初始化决定。
    char x[2][3][4] = {0};是一个三维数组,由于第一个方括号中常量表达式为2,所以该数组有2个元素,每个元素的类型就是去掉x[2],剩余的char [3][4]就是元素的类型;
  • 在一维数组中,由于一维数组内部包含的元素都是基本数据类型,所以首元素其实就是数组的第一个元素;但是到了多维,我们以二维数组为例,二维数组的首元素是一个一维数组,而二维数组的第一个元素是二维数组的首元素中的首元素。
误区总结
  • 当使用scanf(“%d”,a)时由于32位机的整数和地址传进去一样大,故程序不会报错。
  • 数组变量本身是指针,本身表达地址,无需&,但数组单元要用&。
  • 不同类型的指针不可以相互赋值。
  • void指不知道指向什么东西,计算时与char相同。
  • &函数名和函数名都是指的是某元素的地址。

指针进阶

数组指针

由于[]的优先级高于*,所以先和[]结合,但是加上()之后可以改变优先级的结合顺序,形成数组指针。

int main()	
{
    int arr[10]={1,2,3,4,5,6,7,8,9,10};
    int (*p)[10]=&arr;
	int i=0;
	for(i=0;i<10;i++)
	 {
	 	printf("%d",(*p)[i]);
	 	printf("%d",*(p+i));//上下俩种形式的结果一致
	 }

     char* arr[5];
	 char* (*pa)[5]=&arr; 
	 
	 return 0;	
}
字符指针
 int main()
 {
    char arr[]="abcdef";
  	char* pc=arr;//arr为首元素地址,字符指针存放数组名,也就是把首元素a的地址放到pc里 

 	char*p="abcdef" (""为常量字符串,实际上是把a的地址赋给p)
 	printf("%c",*p); //得到a
 	printf("%s",p);//从p存的地址处出发开始打印一个字符串
}

面试题讲解:

int main()
{
  char a1[]="abcdef";
  char a2[]="acbdef";//a1和a2是俩块不同的内存单元
      // (a1!=a2)         {a1和a2分别为数组首元素地址,自然不同}
  char*p1="abcdef" 
  char*p2="abcdef"//俩个字符串一模一样,常量字符串不可修改,在内存中不能存俩份,所以相同 (a1==a2)
} 

指针数组
int main()
{
  int a=10; int b=20; int c=30; int d=40;
  int* arr[4]={&a,&b,&c,&d};// arr[i]是每个元素的地址 

    int a1={1,2,3,4,5};
    int a2={2,3,4,5,6};
    int a3={3,4,5,6,7};
    int* prr[]={a1,a2,a3};存入首元素地址
     for(i=0;i<3;i++{int j=0;
         for(j=0;j<5;j++)
         {
           *(prr[i]+j);
         } 
}

实战演练1:

void print1(int arr[3][5],int x,int y)
{
	int i=0;
	int j=0;
	for(i=0;i<x;i++)
	{
	  for(j=0;j<y;j++)
	  {printf("%d",arr[i][j]); }
	   printf("\n");}/*把二维数组想像为一维数组再去讨论首元素地址,每行为一个元素,则arr有三个元素。即arr代表第一行的地址 */
}

void print2(int(*p)[5],int x,int y)//参数为数组指针
{ 
	int i=0;
	for(i=0;i<x;i++)
	 {
	 	int j=0;
		for(j=0;j<y;j++)
		{
	 	*(p+i);//找到这一行 
	 	*(*(p+i)+j)//找到这一行第j个元素 
	 	}
	 }

 } 
  /*a[i]==*(a+i)==*(p+i) ==p[i] p与arr可以互换 
  [j]==*(a+j)*/

数组传参

一维数组传参:

int arr1[10];    
    void canshu(int a[]);
    void canshu(int a[10]);    
    void canshu(int*arr);     
int* arr2[10];
    void canshu(int*a[10]);
    void canshu(int**a);//一级指针的地址放在二级指针中 

二维数组传参:

int a[10][20];
void test(int arr[10][20]);
void test(int arr[][20]);
void test(int *arr)//err 传入的首元素地址是一维数组的地址,不能用一级指针和二级指针接受
void test(int (*arr)[5]);//指向某一行有5个元素

函数指针

函数指针是一个指针,是指向函数的指针,是存放函数地址的指针。

int (*pa)(int,int)=add; 
printf("%d".(*pa) (2,3))printf("%d".(**pa) (2,3))printf("%d".(pa) (2,3))//结果都一致。*不产生影响。

实战演练:

 (* (void(*)()) 0) (); //0强制转换为void(*)()后解引用调用
  
void ( *signal( int , void(*)(int)) ) (int);
 //函数名 signal 参数有2个,一个是int,一个是函数指针.函数返回类型void(*) (int)
 
 typedef void(*mama)(int);给函数返回类型重新取个名字 
 则上面的可以写成 mama signal(int,mama);
函数指针数组
//有一个数组可以存放多个函数的地址---存放函数指针的数组 
      int(*pa[4]) (int,int)={Add,sub,mul,dlv};
//pa先与[]结合说明是数组,类型为int(*)(int,int) ;
       for(i=0;i<4;i++)
        {
        	pa[i](2,3);
		}

面试例题:
例:charstrcpy(chara,const char*b);
写一个函数指针pf,指向strcpy

 char* (*pf)(char*a,const char*b);
  写一个函数指针数组
   char* (*pf[4]) (char*a,const char*b); 
指向函数指针数组的指针
int main()
{
	int arr={0};
	int (*p)[10]=&arr;
	
	int (*parr[4])(int,int);// parr是数组,函数指针的数组
	int(*(*pp)[4]) (int,int)=&parr; //pp是一个数组指针,指向一个数组
	//每个元素的类型是函数指针,元素类型int(*)(int,int) 
}
回调函数

通过一个函数指针调用的函数 ,把函数的地址作为参数传递给另一个函数,当这个指针被用来指向所调用的函数时称为回调。

void text(void(*p)(char*))
 {
 	printf("text\n");
 	p("bit");
 }
 void print(char*str)
 {
 	printf("hehe%s",str);
  } 
 
 int main()
 {
   text(print);
 }

指针面试题必刷

1.百练printf

char arr[]="abcdef";`//存入abcdef\0
printf("%d",sizeof(arr));//7
printf("%d",sizeof(arr+0));//4或者8 计算出来的是首元素的地址
printf("%d",sizeof(*arr));//1 计算首元素的大小
printf("%d",sizeof(arr[1]));//1 计算的是第二个元素的大小
printf("%d",sizeof(&arr));//4或者8 计算数组地址的大小
printf("%d",sizeof(&arr+1));//4或者8 跳过整个数组后面的地址
printf("%d",sizeof(&arr[0]+1));//4或者8 第二个元素的地址

char a[]={'a'.'b'.c','d','e','f'};
printf("%d",sizeof(arr));//6
printf("%d",sizeof(arr+0));//6
printf("%d",sizeof(*arr));//非法访问内存 传入的是97
printf("%d",sizeof(arr[1]));//err
printf("%d",sizeof(&arr));//6 数组的地址——>数组指针char(*p)[7]=&arr;
printf("%d",sizeof(&arr+1));//随机值
printf("%d",sizeof(&arr[0]+1));//45

char *p="abcdef";//常量字符串abcdef\0
printf("%d",sizeof(p));//4或者8 计算指针变量的大小
printf("%d",sizeof(p+1));//4 8  p+1得到字符b的地址
printf("%d",sizeof(*p));//1 计算首元素的大小
printf("%d",sizeof(p[0]));//1 p[0]=*(p+0)
printf("%d",sizeof(&p));//4或者8 计算数组地址的大小
printf("%d",sizeof(&p+1));//4或者8 跳过整个数组后面的地址
printf("%d",sizeof(&p[0]+1));//4或者8 第二个元素的地址

char *p="abcdef";
printf("%d",strlen(p));//6
printf("%d",strlen(p+1));//5
printf("%d",strlen(*p));//err 传入a的97 报错
printf("%d",strlen(p[0]));//err 
printf("%d",strlen(&p));//随机值 从地址的起始值开始往后面数 后面不可知
printf("%d",strlen(&p+1));//随机值
printf("%d",strlen(&p[0]+1));//5

int a[3][4]={0};
printf("%d",sizeof(a));//3*4*4=48
printf("%d",sizeof(a[0][0]));//4
printf("%d",sizeof(a[0]));//16
printf("%d",sizeof(a[0]+1));//4 第一行的第一个元素 a[0]是第一行的数组名,数组名表示第一行第一个元素的地址  
printf("%d",sizeof(*(a[0]+1)));//4
printf("%d",sizeof(a+1));//16 第二行一维数组的地址
//a是二维数组的数组名,没有sizeof(数组名),也没有&(数组名),所以a是首元素的地址。二维数组的首元素是第一行,则a是第一行的地址
printf("%d",sizeof(*(a+1)));//16 计算第二行的大小 
printf("%d",sizeof(&a[0]+1));//4 第二行的地址
printf("%d",sizeof(*(&a[0]+1));//16
printf("%d",sizeof(*a));//16 a是首元素地址 *a就是第一行
printf("%d",sizeof(a[3]));//16

2.编程运算题

int main()
{
  int a[5]={1.2.3.4.5};
  int *ptr=(int*)(&a+1);
  printf("%d,%d",*(a+1),*(ptr-1));//2,5
  return 0;
}
struct test{
  int Num;
  char *pc;
  short sd;
  char cha[2];
  short sb[4];
  }*p;
  //假设p的值是0x100000 则表达式的值为多少
  int main()
  {//可知结构体的大小为20个字节
    printf("%d",p+0x1);//0x100000+20=0x100014
     printf("%d",(unsigned long)p+0x1);//转换为十进制整数+1==0x100001
      printf("%d",(unsigned int*)p+0x1);//0x100004
  }
  
int main()
{
  int a[4]={1,2,3,4};
  int *ptr1=(int*)(&a+1);
  int *ptr2=(int*)((int)a+1);
  printf("%x%x",ptr1[-1],*ptr2);//4 2000000
 }
int main()
{
  int a[3][2]={(0,1),(2,3),(4,5)};
  int *p;
  p=a[0];
  printf("%d",p[0]);//*(p+0)=1
}
int main()
{
 int a[5][5];
 int(*p)[4];
 p=a;//类型:int (*)[4]-----int (*)[5]
 printf("%p,%d",&p[4][2]-&a[4][2],&p[4][2]-&a[4][2]);0xfffffff04 -4
 return 0;
 }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

太一TT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值