C语言进阶——指针

这篇博客详细介绍了C语言中的指针概念,包括字符指针、数组指针、指针数组、数组参数和指针参数的使用,深入探讨了一维和二维数组在函数参数中的传递方式,并讲解了函数指针、函数指针数组、回调函数以及一些指针和数组相关的面试题解析。通过实例代码展示了各种指针操作和应用场景。
摘要由CSDN通过智能技术生成

目录

一、字符指针char*

二、数组指针

1、概念

2、&数组名和数组

3、数组指针的使用

三、指针数组

四、数组参数和指针参数

1、一维数组传参

2、二维数组传参

3、一级指针传参

4、二级指针传参

五、函数指针

六、函数指针数组

1、概念

2、定义方法

3、使用方法:以计算器为例

七、指向函数指针数组的指针

八、回调函数

九、指针和数组面试题的解析


之前我们已经知道了指针的概念:

●指针就是个变量,用来存放地址,地址唯一标识一块内存空间

●指针的大小是固定的4/8个字节(32位平台/64位平台)

●指针有类型,指针的类型决定了指针的加减整数的步长,指针解引用操作时的权限

一、字符指针char*

一般用法是指向一个字符:

int main()
{
    char ch='w';
    char* pc=&ch;
    *pc='w';
    return 0;
}

还可以指向一个字符串:

int main()
{
    char* pstr="hello bit.";  //不是把字符串放到了指针里,而是把字符串的首字符h的地址放到了指针里
    printf("%s\n",pstr);
    printf("%c\n",&pstr);  //输出h
    return 0;
}

 例:

#include<stdio.h>
int main()
{
    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\n");
    else
       printf("str1 and str2 are not same\n");   
    if(str3==str4)
       printf("str3 and str4 are same\n");
    else
       printf("str3 and str4 are not same\n");   
    return 0;
}
//最后输出的是str1 and str2 are not same和str3 and str4 are same
//str3和str4是同一块内存空间

二、数组指针

1、概念

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

int *p1[10];  //是指针数组
int (*p2)[10];//是数组指针,因为[]的优先级要高于*号,所以必须加上()来保证p先和*结合

2、&数组名和数组

 例:

//1
int main()
{
    int arr[10]={0};
    printf("%p\n",arr);  //007FF94C
    printf("&p\n",&arr); //007FF94C,表示的是数组的地址,而不是数组首元素的地址
    return 0;
}
//2
int main()
{
    int arr[10]={0};
    int* p1=arr;
    int (*p2)[10]=&arr;
    printf("%p\n",p1);    //00CFFCF8
    printf("%p\n",p1+1);  //00CFFCFC
    printf("&p\n",p2);    //00CFFCF8
    printf("&p\n",p2+1);  //00CFFD20,p2+1比p2多了40,跳过了整个数组的大小
    return 0;
}

3、数组指针的使用

一般不会在一维数组上使用。

例:

//1
#include<stdio.h>
int main()
{
    int arr[10]={1,2,3,4,5,6,7,8,9,0};
    int (*p)[10]=&arr; //把数组arr的地址赋值给数组指针变量p,但是我们一般很少这样写
    return 0;
}
//2
void print(int(*p)[5],int r,int c)
{
     int i=0;
     int j=0;
     for(i=0;i<r;i++)
     {
         for(j=0;j<c;j++)
         { 
             printf("%d ",*(*(p+i)+j));
         }
         printf("\n");
     }    
}
int main()
{
    int arr[3][5]={{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};
    print(arr,3,5);//二维数组的首元素是第一行
    return 0;
}

三、指针数组

指针数组是一个存放指针的数组。

int* arr1[10];  //整型指针的数组
char* arr2[4];  //一级字符指针的数组
char** arr3[5]; //二级字符指针的数组

 例:

int arr[10];  //整型数组
int *parr1[10];  //整型指针的数组
int (*parr2)[10];  //指向数组的指针,其指向的数组有10个元素,每个元素的类型是int
int (*parr3[10])[5];  //存放数组指针的数组。parr3是一个数组,数组里有10个元素,每个元素是一个有5个整型元素的数组指针

四、数组参数和指针参数

1、一维数组传参

void test(int arr[])   //可以
{} 
void test(int arr[10]) //可以
{}
void test(int *arr)    //可以
{}
void test(int *arr[20])//可以
{}
void test(int *arr[])  //可以
{}
void test(int **arr)   //可以
{}
int main()
{
    int arr1[10]={0};
    int *arr2[20]={0};
    test(arr1);
    test(arr2);
    return 0;
}

2、二维数组传参

二维数组传参,函数形参的设计只能忽略第一个[]的数字。

void test(int arr[3][5])//可以
{}
void test(int arr[][])  //不可以
{}
void test(int arr[][5]) //可以
{}

void test(int *arr)     //不可以,第一行是一个一维数组的地址
{}
void test(int *arr[5])  //不可以,这样的参数是一个数组不是指针
{}
void test(int (*arr)[5])//可以
{}
void test(int **arr)    //不可以
{}
int main()
{ 
    int main[3][5]={0};
    test(arr);
    return 0;
}

3、一级指针传参

当一个函数的参数部分为一级指针时,函数能接受传【一级指针】和【&变量】。

void print(int *ptr,int sz)
{
     int i=0;
     for(i=0;i<sz;i++)
     {
         printf("%d\n",*(ptr+i));
     }
}
int main()
{
    int arr[10]={1,2,3,4,5,6,7,8,9};
    int *p=arr;
    int sz=sizeof(arr)/sizeof(arr[0]);
    print(p,sz);
    return 0;
}

4、二级指针传参

当一个函数的参数部分为一级指针时,函数能接受传【二级指针】,【&一级指针】,【一级指针的数组,如:arr】

void test(int** ptr)
{
     printf("num=%d\n",**ptr);
}
int main()
{
    int n=10;
    int* p=&n;
    int** pp=&p;
    test(pp);
    test(&p);
    return 0;
}

五、函数指针

函数指针是指向函数的指针,可以存放函数地址。

语法:返回类型 (*函数指针名)(函数参数类型,...,函数参数类型)=&函数名

例:

int(*pf)(int,int)=&Add;  //pf就是一个函数指针变量
void (*pf)(char*)
{...}

int ret=(*pf)(3,5); //三种情况相同
int ret=pf(3,5);
int ret=Add(3,5);

注意:【数组名】不等于【&数组名】,【函数名】等于【&函数名】

例:

//1
(*(void(*)())0)();
//void(*)()是指函数指针类型,(void(*)())0是将0强制转换为函数指针类型,(*(void(*)())0)是指对地址为0的函数进行解引用操作,后面的()用来传参。
//整句代码的意思是调用0地址处的函数,且该函数无参,返回类型是void。

//2
void (*signal(int,void(*)(int)))(int);
//void(*)(int)是参数为整型的函数指针类型。
//整句代码的意思是一个名为signal函数指针的声明,函数参数为一个整型和一个参数为整型的函数指针,且返回类型是void。

六、函数指针数组

1、概念

函数指针数组是存放函数指针的数组,存放同类型的函数指针(返回类型和参数一致)。

2、定义方法

int Add(int x,int y)
{
    return x+y;
}
int Sub(int x,int y)
{
    return x-y;
}
int main()
{ 
    //int (*pf1)(int,int)=Add;
    //int (*pf2)(int,int)=Sub;
    int (*pfArr[2])(int,int)={Add,Sub}; //pfArr就是函数指针数组
}

3、使用方法:以计算器为例

void menu()
{
     printf("*************************\n");
     printf("******1.add   2.sub******\n");
     printf("******3.mul   4.div******\n");
     printf("******    0.exit   ******\n");
     printf("************************\n");
}
int Add(int x,int y)
{
    return x+y;
}
int Sub(int x,int y)
{
    return x-y;
}
int Mul(int x,int y)
{
    return x*y;
}
int Div(int x,int y)
{
    return x/y;
}
int main()
{
    int input=0;
    do
    {
       menu();
       
       int x=0;
       int y=0;
       int ret=0;
       printf("请选择:>");
       scanf("%d ",&input);
       switch(input)
       { 
           case 1:
                printf("请输入2个操作数:\n");
                scanf("%d %d",&x,&y);
                ret=Add(x,y);
                printf("ret=%d\n",ret);
                break;
           case 2:
                printf("请输入2个操作数:\n");
                scanf("%d %d",&x,&y);
                ret=Sub(x,y);
                printf("ret=%d\n",ret);
                break;
           case 3:
                printf("请输入2个操作数:\n");
                scanf("%d %d",&x,&y);
                ret=Mul(x,y);
                printf("ret=%d\n",ret);
                break;
           case 4:
                printf("请输入2个操作数:\n");
                scanf("%d %d",&x,&y);
                ret=Div(x,y);
                printf("ret=%d\n",ret);               
                break;
           case 0:
                printf("退出程序!\n");
                break;
           default:
                printf("选择错误,请重新选择!\n");
                break;
       }
    }while(input);
    return 0;
}

七、指向函数指针数组的指针

int (&p)(int,int);  //函数指针
int (*p2[4])(int,int);   //函数指针的数组
int (*(*p3)[4])(nt,int); //指向函数指针数组的指针

八、回调函数

回调函数是通过一个函数指针调用的函数。

如果把函数的指针地址作为参数传递给一个函数,当这个指针被用来调用其所指向函数时,这就是回调函数。

回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行相应。

 例1:

//可以将上述计算器代码进行优化,将Add、Sub、Mul、Div函数内置在一个Calc函数里
int Calc(int (*pf)(int,int))
{ 
     int x=0;
     int y=0;
     printf("请输入2个操作数:");
     scanf("%d %d",&x,&y);
     return pf(x,y);
}
//之后在case里可以直接调用calc函数
case 1:
   ret=Calc(Add);
   printf("ret=%d\n",ret); 
   break;

 例2:

//1
//冒泡排序实现整型值排序
void bubble_sort(int arr[],int sz)
{ 
     int i=0;
     int j=0;
     for(i=0;i<=sz-1;i++)
     {
         for(j=0;j<sz-1-i;j++)
         {
             if(arr[j]>arr[j+1])
             {
                int tmp=arr[j];
                arr[j]=arr[j+1];
                arr[j+1]=tmp;
             }
         }
     }
}
void print_arr(int arr[],int sz)
{
     int i=0;
     for(i=0;i<sz;i++)
     { 
         printf("%d ",arr[i]);
     }
     printf("\n");
}
void test1()
{
     int arr[10]={9,8,7,6,5,4,3,2,1,0};
     int sz=sizeof(arr)/sizeof(arr[10]);
     print_arr(arr,sz);
     bubble_sort(arr,sz);
     print_arr(arr,sz);
} 
int main()
{
    test1();
    return 0;
}
//2
//qsort函数排序结构体数据
//void qsort(void* base,//base是待排序数据中第一个对象的地址
             //size_t num,//num是待排序数据元素的个数
             //size_t size,//size是待排序数据中一个元素的大小,单位是字节
             //int (*cmp)(const void* e1,const void* e1)//是用来比较待排序数据中2个元素的函数);
int cmp_int(const void* e1,const void* e1)
{
    return *(int*)e1-*(int*)e2;
}
struct Stu
{
    char name[20];
    int age;
}
int sort_by_age(const void* e1,const void* e2)
{
    return ((struct Stu*)e1)->age-((struct Stu*)e2)->age;
}
int sort_by_name(const void* e1,const void* e2)
{
    return strcmp(((struct Stu*)e1)->name,((struct Stu*)e2)->name);
}
void test2()
{
     struct Stu s[]={{"zhangsan",30},{"lisi",34},{"wangwu",20}};
     //按照年龄排序
     int sz=sizeof(s)/sizeof(s[0]);
     qsort(s,sz,sizeof(s[0]),sort_by_age);
     qsort(s,sz,sizeof(s[0]),sort_by_name);
}
int main()
{
    test2();
    return 0;
}
//3
//模仿qsort实现一个冒泡排序的通用算法
void Swap(char* buf1,char* buf2,int width)
{ 
     int i=0;
     for(i=0;i<width;i++)
     {
         char tmp=*buf1;
         *buf1=*buf2;
         *buf2=tmp;
         buf1++;
         buf2++;
     }
}
void bubble_sort(void* base,int sz,int width,int (*cmp)(const void* e1,const void* e2))
{
     int i=0;
     int j=0;
     for(i=0;i<=sz-1;i++)
     {
         for(j=0;j<sz-1-i;j++)
         {
             if(cmp((char*)base+j*width),(char*)base+(j+1)*width)>0)
             {
                Swap((char*)base+j*width,(char*)base+(j+1)*width,width);
             }
         }
     }
}
void test3()
{
     int arr[10]={9,8,7,6,5,4,3,2,1,0};
     int sz=sizeof(arr)/sizeof(arr[10]);
     bubble_sort(arr,sz,sizof(arr[0]),cmp_int);
     print_arr(arr,sz);
} 
int main()
{
    test3();
    return 0;
}

九、指针和数组面试题的解析

例1:

数组名的意义:

●sizeof(数组名):计算的是整个数组的大小
●&数组名:取出的是整个数组的地址
●除此之外,所有的数组名都是数组首元素的地址

int main()
{
    //一维数组
    int a[]={1,2,3,4};
    printf("%d\n",sizeof(a));      //16
    printf("%d\n",sizeof(a+0));    //4(32位平台)或8(64位平台),a+0是第一个元素的地址
    printf("%d\n",sizeof(*a));     //4,*a是数组的第一个元素
    printf("%d\n",sizeof(a+1));    //4,a+1是第二个元素的地址
    printf("%d\n",sizeof(a[1]));   //4,a[1]是第二个元素
    printf("%d\n",sizeof(&a));     //4/8
    printf("%d\n",sizeof(*&a));    //16,*&可以理解为抵消了
    printf("%d\n",sizeof(&a+1));   //4/8
    printf("%d\n",sizeof(&a[0]));  //4/8
    printf("%d\n",sizeof(&a[0]+1));//4/8

    //字符数组
    char arr[]={'a','b','c','d','e','f'};
    printf("%d\n",sizeof(arr));      //6
    printf("%d\n",sizeof(arr+0));    //4/8,是字符的地址,不是字符所占空间大小
    printf("%d\n",sizeof(*arr));     //1
    printf("%d\n",sizeof(arr[1]));   //1
    printf("%d\n",sizeof(&arr));     //4/8
    printf("%d\n",sizeof(&arr+1));   //4/8
    printf("%d\n",sizeof(&arr[0]+1));//4/8
    printf("%d\n",strlen(arr));      //随机值,因为不知道什么时候遇到\0
    printf("%d\n",strlen(arr+0));    //随机值
    printf("%d\n",strlen(*arr));     //报错,因为传的是97
    printf("%d\n",strlen(arr[1]));   //报错,因为传的是98
    printf("%d\n",strlen(&arr));     //随机值
    printf("%d\n",strlen(&arr+1));   //随机值-6
    printf("%d\n",strlen(&arr[0]+1));//随机值-1

    char arr[]="abcdef";
    printf("%d\n",sizeof(arr));      //7
    printf("%d\n",sizeof(arr+0));    //4/8,是字符的地址,不是字符所占空间大小
    printf("%d\n",sizeof(*arr));     //1
    printf("%d\n",sizeof(arr[1]));   //1
    printf("%d\n",sizeof(&arr));     //4/8
    printf("%d\n",sizeof(&arr+1));   //4/8
    printf("%d\n",sizeof(&arr[0]+1));//4/8
    printf("%d\n",strlen(arr));      //6
    printf("%d\n",strlen(arr+0));    //6
    printf("%d\n",strlen(*arr));     //报错,因为传的是97
    printf("%d\n",strlen(arr[1]));   //报错,因为传的是98
    printf("%d\n",strlen(&arr));     //6
    printf("%d\n",strlen(&arr+1));   //随机值,因为不知道\0后面是什么
    printf("%d\n",strlen(&arr[0]+1));//5
   
    char *p="abcdef";
    printf("%d\n",sizeof(p));      //4/8
    printf("%d\n",sizeof(p+1));    //4/8
    printf("%d\n",sizeof(*p));     //1
    printf("%d\n",sizeof(p[0]));   //1,p[0]等价于*(p+0)
    printf("%d\n",sizeof(&p));     //4/8
    printf("%d\n",sizeof(&p+1));   //4/8
    printf("%d\n",sizeof(&p[0]+1));//4/8
    printf("%d\n",strlen(p));      //6
    printf("%d\n",strlen(p+1));    //5
    printf("%d\n",strlen(*p));     //报错,因为传的是97
    printf("%d\n",strlen(p[0]));   //报错,因为传的是97
    printf("%d\n",strlen(&p));     //随机值
    printf("%d\n",strlen(&p+1));   //随机值(与上面的随机值不同)
    printf("%d\n",strlen(&p[0]+1));//5

    //二维数组
    int a[3][4]={0};
    printf("%d\n",sizeof(a));         //48
    printf("%d\n",sizeof(a[0][0]));   //4,是第一行第一个元素
    printf("%d\n",sizeof(a[0]));      //16,是第一行一整行
    printf("%d\n",sizeof(a[0]+1));    //4,是第一行第二个元素的地址
    printf("%d\n",sizeof(*(a[0]+1))); //4,是第一行第二个元素
    printf("%d\n",sizeof(a+1));       //4,是第二行的地址
    printf("%d\n",sizeof(*(a+1)));    //16,是第二行的大小
    printf("%d\n",strlen(&a[0]+1));   //4,是第二行的地址
    printf("%d\n",strlen(*(&a[0]+1)));//16,是第二行的大小
    printf("%d\n",strlen(*a));        //16,是第一行的大小
    printf("%d\n",strlen(a[3]));      //16,没有真正去访问,换成a[-1]也是16
    return 0;
}

例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;
}

例3:

//假设p的值是0x100000,则输出什么?
struct Test
{
    int num;
    char* pcName;
    short sDate;
    char ch[2];
    short sBa[4];
}*p; //已知这里结构体的大小是20个字节
int main()
{
    printf("%p\n",p+0x1);  //0x100014
    printf("%p\n",(unsigned long)p+0x1);  //0x100001
    printf("%p\n",(unsigned int*)p+0x1);  //0x100004
    return 0;
}

例4:

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); //ptr1[-1]等价于*(ptr1-1),这里是第四个元素,即4
    return 0;
}

例5:

int main()
{
    int a[3][2]={(0,1),(2,3),(4,5)}; //这里是逗号表达式,数组里实际存放的是{1,3,5,0,0,0}
    int* p;
    p=a[0];
    printf("%d",p[0]); //1
    return 0;
}

例6:

int main()
{
    int a[5][5];//一行5个元素
    int(*p)[4]; //一行4个元素
    p=a;
    printf("%p,%d\n",&p[4][2]-&a[4][2],&p[4][2]-&a[4][2]);//FFFFFFFC,-4
    return 0;
}

例7:

int main()
{
    int aa[2][5]={1,2,3,4,5,6,7,8,9,10};
    int* ptr1=(int*)(&aa+1);//指向的是二维数组最后面
    int* ptr2=(int*)(*(aa+1));//指的是第二行第一行元素的地址
    printf("%d,%d",*(ptr1-1),*(ptr2-1)); //10,5
    return 0;
}

例8:

int main()
{
    char* a[]={"work","at","alibaba"};
    char** pa=a;
    pa++;
    printf("%s\n",*pa); //at
    return 0;
}

例9:

int main()
{
    char* c[]={"ENTER","NEW","POINT","FIRST"};
    char** cp[]={c+3,c+2,c+1,c}; //cp[]={"FIRST","POINT","NEW","ENTER"}
    char*** cpp=cp; //cpp={"FIRST","POINT","NEW","ENTER"}
    printf("%d\n",**++cpp); //POINT
    printf("%d\n",*--*++cpp+3); //ER
    printf("%d\n",*cpp[-2]+3);  //ST
    printf("%d\n",cpp[-1][-1]+1); //EW,cpp[-1][-1]等价于*(*(cpp-1)-1)
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

趣多多代言人

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

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

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

打赏作者

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

抵扣说明:

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

余额充值