指针深入理解

指针深入理解,第1张

指针深入理解

文章目录

初始指针

指针的理解指针的运算数组与指针误区总结 指针进阶

数组指针字符指针指针数组数组传参函数指针函数指针数组指向函数指针数组的指针回调函数 指针面试题必刷

初始指针 指针的理解

计算机的硬件单元之间需要互相协同,满足数据的传递。但是硬件之间相互独立,彼此之间通过“线”联系在一起。程序的执行是先把代码加载进入内存中,然后通过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 
数组传参 

一维数组传参:

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("textn");
 	p("bit");
 }
 void print(char*str)
 {
 	printf("hehe%s",str);
  } 
 
 int main()
 {
   text(print);
 }

指针面试题必刷

1.百练printf

char arr[]="abcdef";`//存入abcdef
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
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;
 }

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5703552.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存