指针的进阶(2)

简介: 指针的进阶(2)

函数指针

先看一段代码:

#include <stdio.h>
void test()
{
 printf("hehe\n");
}
int main()
{
 printf("%p\n", test);
 printf("%p\n", &test);
 return 0;
}


结果:

0000000000401550

0000000000401550

我们可以看到函数名可以表示成地址,那么对于函数的地址,我们就可以用函数指针来存储。

void test()
{
 printf("hehe\n");
}
void (*pfun1)();

星号先与变量名结合,表示指针,后面加上小括号就表示指针指向的是函数的地址。

当函数里面有参数时,我们也需要跟着写进去。

void test(char* pc, int arr[10])
{
}
int main()
{
  void (*pf)(char *, int [10]) = test;
  return 0;
}


只需要写数据类型即可,可以不跟变量名。

下面看一下函数指针的用例:

int Add(int x, int y)
{
  return x + y;
}
int main()
{
  int (*pf)(int, int) = Add;
  int r = Add(3, 5);
  printf("%d\n", r);
  int m = (*pf)(4, 5);
  printf("%d\n", m);
  return 0;
}


8

9

这里我们可以直接调用函数的地址,对它进行解引用,还需要对其函数进行传参,就能实现一个函数的调用;这里*和pf需要用下括号括起来,**倘若没有下括号,那么pf会先与(4,5)先结合,**那么星号在这里就会变成无效的引用,程序将会报错。

函数指针数组

这是一个存放函数地址的数组;

int (*parr1[10])();

我们可以先写出一个函数指针int(*parr1)(),然后通过优先级将【】与parr1结合,就变成了函数指针数组。


下面我们用计算器这个例子,分别用switch和函数指针数组进行对比;

#include <stdio.h>
int add(int a, int b)
{
  return a + b;
}
int sub(int a, int b)
{
  return a - b;
}
int mul(int a, int b)
{
  return a*b;
}
int div(int a, int b)
{
  return a / b;
}
int main()
{
  int x, y;
  int input = 1;
  int ret = 0;
  do
  {
  printf( "*************************\n" );
  printf( " 1:add 2:sub \n" );
  printf( " 3:mul 4:div \n" );
  printf( "*************************\n" );
   printf( "请选择:" );
   scanf( "%d", &input);
   switch (input)
   {
   case 1:
    printf( "输入操作数:" );
     scanf( "%d %d", &x, &y);
   ret = add(x, y);
     printf( "ret = %d\n", ret);
   break;
   case 2:
    printf( "输入操作数:" );
    scanf( "%d %d", &x, &y);
    ret = sub(x, y);
    printf( "ret = %d\n", ret);
     break;
   case 3:
    printf( "输入操作数:" );
    scanf( "%d %d", &x, &y);
    ret = mul(x, y);
    printf( "ret = %d\n", ret);
     break;
   case 4:
    printf( "输入操作数:" );
    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;
  }


这是用switch写出来的计算器,可以实现加减乘除的功能,但我们会发现,倘若我们要对该程序进行补充功能时,那么就得重复写case中的语句,当然我们可以直接粘贴复制,但我们看起来就会比较冗余,认为重复的地方出现过太多次了;

函数指针数组的方式:

#include <stdio.h>
int add(int a, int b)
{
    return a + b;
}
int sub(int a, int b)
{
    return a - b;
}
int mul(int a, int b)
{
    return a*b;
}
int div(int a, int b)
{
    return a / b;
}
int main()
{
    int x, y;
    int input = 1;
    int ret = 0;
    int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
    while (input)
    {
        printf( "*************************\n" );
        printf( " 1:add 2:sub \n" );
        printf( " 3:mul 4:div \n" );
        printf( "*************************\n" );
        printf( "请选择:" );
        scanf( "%d", &input);
        if ((input <= 4 && input >= 1))
        {
            printf( "输入操作数:" );
            scanf( "%d %d", &x, &y);
            ret = (*p[input])(x, y);
        }
        else
        {
            printf( "输入有误\n" );
            printf( "ret = %d\n", ret);
        }    
    }
 return 0;
}


我们先在主函数创建一个函数指针数组int(*p[5])(int x, int y) = { 0, add, sub, mul, div };调用时,只需要对指针p解引用,输入正确的下标和参数,就能实现不必要的冗余

指向函数指针数组的指针

void test(const char* str)
{
 printf("%s\n", str);
}
int main()
{
 //函数指针pfun
 void (*pfun)(const char*) = test;
 //函数指针的数组pfunArr
 void (*pfunArr[5])(const char* str);
 pfunArr[0] = test;
 //指向函数指针数组pfunArr的指针ppfunArr
 void (*(*ppfunArr)[5])(const char*) = &pfunArr;
 return 0;
}


对于这么长的描述,我们可以从函数指针下手,然后拓展到函数指针数组,最后用指针指向函数指针数组即可,我们在写的时候要注意,各符号的优先级,当有[]和星号时,如果没有小括号将变量名和星号括起来,那么它最终就是一个数组,且星号放在变量名前面,[]放在变量名后面,不能随意乱放。


总的来说,对于数组和指针,我们可以组成很长数组指针数组,对于这种写法,我们重点在于要学会看懂怎么读,懂得各符号的优先级,明白哪个与哪个相结合,拆开分解后就一目了然了。

回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。


qsort函数各类的使用

void qsort (void* base, size_t num, size_t size,

int (compar)(const void,const void*))

这是一个快排函数,我们可以看到,它的参数包含函数指针,当我们调用这个函数时,必须对函数指针指向的函数进行调用完,该函数才得以实现,表明qsort函数是一个回调函数。下面我们来看看qsort函数各种类型数据的使用。


整型类型数据:

#include <stdio.h>
int int_cmp(const void * p1, const void * p2)
{
    return (*( int *)p1 - *(int *) p2);
}
void test1()
{
    int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
    int i = 0;
    qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
    for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
    {
    printf( "%d ", arr[i]);
    }
    printf("\n");
}
int main()
{
    test1();
 return 0;
}


结果:0 1 2 3 4 5 6 7 8 9

在这里,对于qsort函数的最后一个参数,需要我们自己创建一个符合条件的函数,使它对应的返回值能够判断不同元素的大小即可。

浮点类型的:

int float_cmp(const void* p1,const void* p2)
{
    if((*(float*)p1-*(float*)p2)>0.000000)
    {
        return 1;
    }
    else if((*(float*)p1-*(float*)p2)<0.000000)
    {
        return -1;
    }
    else
    {
        return 0;
    }
}
void test2()
{
    float arr[]={3.14,2.56,6,78,5.32,4.02};
    int i=0;
    qsort(arr,sizeof(arr)/sizeof(arr[0]),sizeof(float),float_cmp);
    for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
    {
    printf( "%.2f ", arr[i]);
    }
    printf("\n");
}
int main()
{
   // test1();
    test2();
 return 0;
}

2.56 3.14 4.02 5.32 6.00 78.00

在这里,对于最后一个参数,浮点数由于具有精度,函数类型为整型,所以需要与具有精度的0进行判断,所以需要加上条件判断语句。倘若使用第一种方法的强制换类型,那么将会丢失精度。

字符串:

//字符串的大小
int str_cmp_size(const void* p1,const void* p2)
{
    return strcmp(*(char**)p1,*(char**)p2);
}
//字符串的长度
int str_cmp_len(const void* p1,const void* p2)
{
    return strlen(*(char**)p1)-strlen(*(char**)p2);
}
void test3()
{
    char* arr[]={"abc","VPN","RTx","Rtx","rtxbbb","abbb"};
    int i=0;
    qsort(arr,sizeof(arr)/sizeof(arr[0]),sizeof(arr[0]),str_cmp_len);
    for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
    {
    printf( "%s ", arr[i]);
    }
    printf("\n");
}
int main()
{
   // test1();
    //test2();
    test3();
 return 0;
}


大小:RTx Rtx VPN abbb abc rtxbbb

长度:VPN RTx Rtx abc abbb rtxbbb


在这里,为何不直接调用(char*)p1,而要用*(char**),这是因为如果用(char*)的话,对于形参p1来说,存的是arr数组名的地址,strcmp()会将p地址对应的内容转换为字符串,这就不符合我们的想法,所以应该是将数组中对应的字符串的地址传过去,再对其解引用,这样才能得到真正的字符串。

struct Stu
{
  char name[20];
  int age;
};
int cmp_stu_by_age(const void* p1, const void* p2)
{
  return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
void test4()
{
  struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 50},{"wangwu", 15} };
  int sz = sizeof(arr) / sizeof(arr[0]); 
  qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
    for (int i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
    {
    printf( "%d ", arr[i].age);
    }
    printf("\n");
}
int cmp_stu_by_name(const void* p1, const void* p2)
{
  return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
void test5()
{
  struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 50},{"wangwu",15}};
  int sz = sizeof(arr) / sizeof(arr[0]);
  qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
    for (int i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
    {
    printf( "%s ", arr[i].name);
    }
    printf("\n");
}
int main()
{
   // test1();
    //test2();
    //test3();
    test4();
    test5();
 return 0;
}


15 20 50

lisi wangwu zhangsan

对于结构体的使用,要用结构体指针,指向对应的比较变量,就能得到最终答案。

模拟实现sqort函数

在这里,我们用的是冒泡排序实现sqort函数的思想。

void bubble_sort(int arr[], int sz)
  {
    int i = 0;
    //趟数
    for (i = 0; i < sz - 1; i++)
    {
      //一趟比较
      //两两相邻元素比较
      int j = 0;
      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;
        }
      }
    }
  }


这是一个整型类型的冒泡排序,当我们要用到浮点型,结构体类型时,就无法使用该函数,所以,我们根据sqort函数的参数进行模拟实现。

我们可以对上面函数进行改装,首先不能改变冒泡排序的思想,也就是两层循环不变,而我们要改变的是,对于不同类型的比较方式,还有不同类型是怎么交换的。

对于不同类型的比较,我们用到sqort的第四个参数,我们要解决的是怎么传参?我们要清楚,我们传的是每个元素的地址,不同类型意味着它们每个元素所占字节不一样,那么我们可以利用元素的大小乘上对应的j,就能实现元素地址的传递了。


(cmp((char*)base+jsize, (char)base+(j+1)size)>0)

我们用char强制类型转换,因为char*指针移动时,刚好是一个字节一个字节过去的;


对于转换,我们已经知道每个不同类型元素的地址了,我们只需要交换它们的地址即可,对于char*类型的,我们可以一个字节一个字节循环转换;那么就写一个交换函数来进行实现;


void Swap(char* buf1, char* buf2, int size)//交换arr[j],arr[j+1]这两个元素
{
  int i = 0;
  char tmp = 0;
  for (i = 0; i < size; i++)
  {
    tmp = *buf1;
    *buf1 = *buf2;
    *buf2 = tmp;
    buf1++;
    buf2++;
  }
}

原代码:

void Swap(char* buf1, char* buf2, int size)
{
  int i = 0;
  char tmp = 0;
  for (i = 0; i < size; i++)
  {
    tmp = *buf1;
    *buf1 = *buf2;
    *buf2 = tmp;
    buf1++;
    buf2++;
  }
}
void bubble_sort(void* base, int num, int size, int (*cmp)(const void*, const void*))
{
  int  i = 0;
  for (i = 0; i < num - 1; i++)
  {
    int j = 0;
    for (j = 0; j < num - 1 - i; j++)
        {
      if (cmp((char*)base+j*size, (char*)base+(j+1)*size)>0)
      {
        Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
      }
    }
  }
}


这就是利用冒泡排序实现sqort函数的思想,对于如何使用,使用方法和上面的sqort函数是一模一样的,这里就不在过多展示。

相关文章
|
6月前
|
C语言
指针进阶(C语言终)
指针进阶(C语言终)
|
6月前
|
机器学习/深度学习 搜索推荐 算法
【再识C进阶2(下)】详细介绍指针的进阶——利用冒泡排序算法模拟实现qsort函数,以及一下习题和指针笔试题
【再识C进阶2(下)】详细介绍指针的进阶——利用冒泡排序算法模拟实现qsort函数,以及一下习题和指针笔试题
|
6月前
|
C语言
指针进阶(回调函数)(C语言)
指针进阶(回调函数)(C语言)
|
6月前
|
存储 C语言 C++
指针进阶(函数指针)(C语言)
指针进阶(函数指针)(C语言)
|
6月前
|
编译器 C语言
指针进阶(数组指针 )(C语言)
指针进阶(数组指针 )(C语言)
|
6月前
|
搜索推荐
指针进阶(2)
指针进阶(2)
54 4
|
6月前
指针进阶(3)
指针进阶(3)
47 1
|
6月前
|
C++
指针进阶(1)
指针进阶(1)
47 1
|
6月前
|
存储 安全 编译器
C++进阶之路:何为引用、内联函数、auto与指针空值nullptr关键字
C++进阶之路:何为引用、内联函数、auto与指针空值nullptr关键字
52 2
|
7月前
|
C语言
C语言进阶:进阶指针(下)
C语言进阶:进阶指针(下)
51 2