深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)(2)

简介: 深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)(2)

函数指针

首先看一段代码:

#include <stdio.h>
void test()
{
    printf("hehe\n");
}
int main()
{
    printf("%p\n",  test); //函数名 就是函数地址
    printf("%p\n", &test); //&函数名 也是函数地址
    return 0;
}

运行结果

image.png

那么如何将test()函数指针保存起来呢?

void test()
{
 printf("hehe\n");
}
//下面pfun1和pfun2哪个有能力存放test函数的地址?
void (*pfun1)();  //函数指针类型
void *pfun2();    //函数,函数的返回值是void*

函数指针类型

指针都是有类型的

整型指针 int*

数组指针 int (*)[]

函数指针 返回值 (*)(参数....)

#include<stdio.h>
int Add(int x, int y)
{
  return x + y;
}
int main()
{
  int (*pf)(int, int) = Add;
  int sum = (*pf)(3, 5);   //对函数指针解引用
  printf("sum = %d", sum);
  sum = pf(3, 5);          //因为 Add和&Add相同,即Add等价于 pf
  printf("sum = %d", sum);
  return 0;
}

image.png

有趣的代码


//代码1
(*(void (*)())0)(); 
//void (*)()为函数指针
//(void (*)())0 将0强制类型抓换成函数指针的地址
//(*(void (*)())0)() *地址,调用0地址处的这个函数
//函数的返回值空,参数为空
//代码2
void (*signal(int , void(*)(int)))(int);
//void(*)(int) 函数指针,返回值void,参数int 
//void (*signal(int , void(*)(int)))(int)
// signal是函数名 
//返回值是void(*)(int)
// 参数int 和函数指针 void(*)(int)
//这是一个函数的声明

当我们看到代码2很难看懂这个代码!

可以简化吗?


void (*signal(int , void(*)(int)))(int);
//既然这个函数的返回值类型是 void(*)(int) 
//那我们可以写成
// void(*)(int) signal(int , void(*)(int));
//但是这样会语法错误 error

image.png

函数指针类型重命名

image.png

简化


typedef void(*ptr_t) (int);   //正确的类型重命名
 ptr_t signal(int, ptr_t);  //简化

image.png

上面的代码出自《C陷阱和缺陷》

有兴趣的伙伴可以尝试阅读!


函数指针数组

数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组

比如:


int* arr[10]; //整型指针数组

那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?


int (*parr1[10]])(); //
int *parr2[10]();
int (*)() parr3[10];

答案是:parr1 ·parr1· 先和[] 结合,说明parr1是数组,数组的内容是什么呢? 是int (*)()类型的函数指针。


函数指针数组的用途:

转移表

例子:(计算器)


#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 (*pf[4])(int, int) = { add,sub,mul,div };
    int sz = sizeof(pf) / sizeof(pf[1]);
    int i = 0;
    for (i = 0; i < 4; i++)
    {
        //实现5和3的加减乘除
        printf("%d\n", pf[i](5,3));
    }
    return 0;
}

image.png

//计算器优化
#include <stdio.h>
void menu()
{
    printf("*************************\n");
    printf("  1:add           2:sub  \n");
    printf("  3:mul           4:div  \n");
    printf("*************************\n");
    printf("请选择:");
}
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;
}
void Calc(int (*pf)(int ,int))
{
    int x, y;
    printf("请输入两个操作数\n");
    scanf("%d%d", &x, &y);
    printf("%d\n", pf(x, y));
}
int main()
{
    int x, y;
    int input = 1;
    int ret = 0;
    int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
    do
    {
        menu();//菜单
        scanf("%d", &input);
        switch (input)
        {
        case 1: Calc(add);
            break;
        case 2: Calc(sub);
            break;
        case 3:Calc(mul);
            break;
        case 4:Calc(div);
            break;
        case 0: printf("退出\n");
            break;
        default: printf("输出有误,请重新输入\n");
            break;
        }
    } while (input);
    return 0;
}

image.png


指向函数指针数组的指针

看到这个指针是不是感觉头都大了,没事我们慢慢分析!

int* arr[3]; //整型指针数组
int*(*parr)[3]=&arr; //整型指针数组的地址放入parr
//parr就是指向整型指针数组的指针
//而我们只需要将整型指针换成函数指针就ok了
int(*pf)(int,int);//函数指针
int(*pfarr[3])(int,int);//函数指针数组
int(*(*ppfarr)[3])(int,int)=&pfarr; //&pfarr
//ppfarr是指针,指针指向一个数组,数组的类型为函数指针
//元素个数为3,函数的返回值为int,参数为int

博主就不带大家套娃了,就学到这里了!


回调函数

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


刚刚的计算器优化后就是回调函数,依赖于函数指针。

我们把加减乘除函数的函数指针,传参的形式,传给另一方调用,回调该加减乘除的函数,所以为回调函数!


qsort函数的使用:

image.png

这是MSDN中对qsort函数的解释!

我给大家介绍一下!

qsort

作用


Performs a quick sort.


qsort是快速排序函数库<stdlib.h> and <search.h>中,我们可以通过qsort函数实现任意类型数据的排序!

函数声明


void qsort( void *base, size_t num, size_t width, int (__cdecl *compare )(const void *elem1, const void *elem2 ) );


返回值


void


参数


void *base

待排序的内容的首地址

size_t num

待排序元素的个数

size_t width

待排序元素的大小

int (__cdecl *compare )(const void *elem1, const void *elem2 )

自己定义一个排序的方式,例如一个结构体数组,结构体元素,按照结构体里的什么类型的数据的类型排序,所以需要自己定义一个campare排序方式!

然后将函数指针传参到qsot中!


//qsort函数使用实例
#include<stdio.h>
struct peo 
{
  char name[10];
  char numb[12];
};
//自己定义一个排序的方式
int compare_char(const void* e1, const void* e2)
{
  return strcmp(((struct peo*)e1)->numb, ((struct peo*)e2)->numb); 
  //按照peo中numb排序
}
int main()
{
  //创建一个结构体数组
  struct peo peo1[3] = { {"张三","15779770910"},{"李四","17370126640"},{"王五","12669781134"} };
  int sz = sizeof(peo1) / sizeof(peo1[1]);
  qsort(peo1, sz, sizeof(peo1[1]), compare_char);
  for (int i = 0; i < sz; i++)
  {
  printf("%s\t%s\n",peo1[i].name,peo1[i].numb);
  }
  return 0;
}

image.png

看完qsort函数使用演示,是不是学会了。


my_qsort函数模拟实现

模仿qsort的功能实现一个通用的冒泡排序!


我们通过qsort函数的使用,我们尝试一下模拟实现qosrt函数。


//模拟实现qsort
//模仿qsort的功能实现一个通用的冒泡排序
//void qsort(void* base, size_t num, size_t width, 
//        int(__cdecl* compare)(const void* elem1, const void* elem2));
#include<stdio.h>
typedef struct student
{
  char name[5];
  int score;
}Student;
int compare_score(const void*e1,const void* e2)
{
  return ((Student*)e1)->score - ((Student*)e2)->score;
}
int compare_name(const void* e1, const void* e2)
{
  return strcmp(((Student*)e1)->name, ((Student*)e2)->name);
}
void swap(void* e1, void* e2, size_t width)
{
  //将元素一个一个字节交换
  int i = 0;
  for (i = 0; i < width; i++)
  {
  char tmp;
  tmp = *((char*)e1+i);
  *((char*)e1+i) = *((char*)e2+i);
  *((char*)e2+i) = tmp;
  }
}
void my_qsort(void* base, size_t num, size_t width, 
  int(*compare)(const void* e1, const void* e2))
{
  int i = 0, j = 0,flag = 1;
  //趟数
  for (i = 0; i < num-1; i++)
  {
  //每趟比较次数
  for (j = 0; j < num - i - 1; j++)
  {
    //比较
    //因为我们无法得知实参的类型,无法得知指针加减的步长
    //所以我们强制转换成char*类型指针,通过width元素大小进行访问 
    if (compare((char*)base + width * j, (char*)base + (j + 1) * width)>0)
    {
    //交换
    swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
    flag = 0;
    }
  }
  if (flag == 1)
    break;
  }
}
int main()
{
  Student student[5] = { {"李四",65},{"张三",78},{"王五",56},{"老王",100},{"小李",98} };
  my_qsort(student, sizeof(student) / sizeof(student[1]),sizeof(student[0]),compare_score);
  int i = 0;
  for (i = 0; i < 5; i++)
  {
  printf("%s %d\n", student[i].name, student[i].score);
  }
  return 0;
}

image.png


指针和数组面试题解

题目


//一维数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a+0));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(a[1]));
printf("%d\n",sizeof(&a));
printf("%d\n",sizeof(*&a));
printf("%d\n",sizeof(&a+1));
printf("%d\n",sizeof(&a[0]));
printf("%d\n",sizeof(&a[0]+1));

解析


//一维数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));// 16
//sizeof数组名就是计算整个数组空间大小
printf("%d\n",sizeof(a+0));// 4/8
//a+0说明数组名a表示首元素地址,加0后还是首元素地址,
//地址就是指针,而指针大小统一为4/8
printf("%d\n",sizeof(*a));//4
//解引用数组名,该处的数组名表示首元素地址,
//*后就是第一个元素 int型所以为4
printf("%d\n",sizeof(a+1));// 4/8
//解析同a+0,a+1代表第二个元素的地址,指针大小4/8
printf("%d\n",sizeof(a[1])); // 4
//a[1]为int 故为4
printf("%d\n",sizeof(&a)); // 4/8
//&数组名,得到了整个数组的地址,而指针大小4/8
printf("%d\n",sizeof(*&a));// 16
//&a得到整个数组地址,而*&a得到整个数组 16
printf("%d\n",sizeof(&a+1)); // 4/8
//&数组名,得到整个数组的地址,&a+1还是数组地址,
//而指针大小4/8
printf("%d\n",sizeof(&a[0]));// 4/8
//&a[0]得到第一个元素的地址,指针4/8
printf("%d\n",sizeof(&a[0]+1)); // 4/8
//&a[0]得到第一个元素的地址,
//&a[0]+1第二个元素地址,而指针大小4/8

运行结果

image.png


题目


//字符数组
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]+1));
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));
char arr[] = "abcdef";
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]+1));
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));
char *p = "abcdef";
printf("%d\n", sizeof(p));
printf("%d\n", sizeof(p+1));
printf("%d\n", sizeof(*p));
printf("%d\n", sizeof(p[0]));
printf("%d\n", sizeof(&p));
printf("%d\n", sizeof(&p+1));
printf("%d\n", sizeof(&p[0]+1));
printf("%d\n", strlen(p));
printf("%d\n", strlen(p+1));
printf("%d\n", strlen(*p));
printf("%d\n", strlen(p[0]));
printf("%d\n", strlen(&p));
printf("%d\n", strlen(&p+1));
printf("%d\n", strlen(&p[0]+1));

解析


//字符数组
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));// 6
//sizeof数组名,就是整个数组 数组大小为6
printf("%d\n", sizeof(arr+0));// 4/8
//arr+0,数组名就是首元素地址,arr+0也是首元素地址
//指针大小4/8
printf("%d\n", sizeof(*arr));// 1
//解引用数组名,此时的数组名代表首元素地址,
//解引用后得到第一个元素,元素大小1
printf("%d\n", sizeof(arr[1]));// 1
//arr[1]表示第一个元素,元素大小1
printf("%d\n", sizeof(&arr));// 4/8
//&数组名,代表数组地址,指针大小4/8
printf("%d\n", sizeof(&arr+1));
//&arr 数组地址,&arr+1也是数组地址
//而指针变量大小4/8
printf("%d\n", sizeof(&arr[0]+1));// 4/8
//&arr[0]第一个元素地址,&arr[0]+1第二个元素地址
printf("%d\n", strlen(arr));//随机值
//stlen函数遇到'\0'停止计数
printf("%d\n", strlen(arr+0));// 随机值
//同上
printf("%d\n", strlen(*arr));// 报错 
// stlen函数参数接收的是指针,*arr是第一个元素
printf("%d\n", strlen(arr[1]));//报错
//同上
printf("%d\n", strlen(&arr));// 随机值
//&arr数组的地址,遇到'\0'停止计数
printf("%d\n", strlen(&arr+1));// 随机值—6
//&arr+1 整个数组的地址加一跳过,数组大小 
//而数组大小为6个字节,所以长度为随机值-6
printf("%d\n", strlen(&arr[0]+1));//随机值-1
//&arr[0]+1得到数组第二个元素地址,从第一个元素计数
//计数结果为随机值,故从第二个为随机值-1

image.png

image.png

char arr[] = "abcdef";
//字符串数组,数组大小要加上'\0'
printf("%d\n", sizeof(arr)); // 7
//sizeof arr arr表示整个数组
printf("%d\n", sizeof(arr+0));// 4/8
//首元素地址加0还是首元素地址,指针大小为4/8
printf("%d\n", sizeof(*arr));// 1
//*arr得到第一个元素,元素大小为 1bit
printf("%d\n", sizeof(arr[1]));// 1
//同上
printf("%d\n", sizeof(&arr)); // 4/8
//&arr整个数组的地址,指针大小4/8
printf("%d\n", sizeof(&arr+1));// 4/8
//&arr+1数组指针,指针大小4/8
printf("%d\n", sizeof(&arr[0]+1));// 4/8
//&arr[0]第一个元素地址,&arr[0]+1第二个元素地址
//而指针大小4/8
printf("%d\n", strlen(arr));// 6
//arr数组存到是字符串,字符串中含有'\0'
printf("%d\n", strlen(arr+0));// 6
// 同上
printf("%d\n", strlen(*arr));// 报错
//*arr得到首元素,而strlen函数参数为指针类型
printf("%d\n", strlen(arr[1]));// 报错
//同上
printf("%d\n", strlen(&arr));// 6
// &arr得到整个数组地址
printf("%d\n", strlen(&arr+1));// 随机值
//&arr+1跳过整个数组
printf("%d\n", strlen(&arr[0]+1));// 5
//&arr[0]+1得到第二个元素地址

image.png



char *p = "abcdef";
//const修饰的字符常量
printf("%d\n", sizeof(p));// 4/8
//p是字符指针,指针大小4/8
printf("%d\n", sizeof(p+1));// 4/8
//p+1字符指针,指针大小4/8
printf("%d\n", sizeof(*p));// 1
//*p得到第一个元素,元素类型为char
printf("%d\n", sizeof(p[0]));// 1
//同上
printf("%d\n", sizeof(&p));// 4/8
//&p为字符指针,指针大小4/8
printf("%d\n", sizeof(&p+1));//4/8
//&p+1为第二个字符地址,字符指针,指针大小4/8
printf("%d\n", sizeof(&p[0]+1));// 4/8
//同上
printf("%d\n", strlen(p));// 随机值
//p指针,所指空间,'\0'位置未知
printf("%d\n", strlen(p+1));// 随机值-1
//p+1第二个字符地址
printf("%d\n", strlen(*p));// 报错
// *p是字符,strlen参数为指针类型
printf("%d\n", strlen(p[0]));// 报错
//同上
printf("%d\n", strlen(&p));// 另一随机值
// &p是二级指针,
printf("%d\n", strlen(&p+1));// 又另一随机值
//&p二级指针 ,&p+1后还是二级指针
printf("%d\n", strlen(&p[0]+1));// 随机值—1
//&p[0]+1第二个字符地址

image.png



//二维数组
  int a[3][4] = { 0 };
  printf("%d\n", sizeof(a));//48
  //sizeof数组名.为整个数组
  printf("%d\n", sizeof(a[0][0]));//4
  //a[0][0]为第一元素
  printf("%d\n", sizeof(a[0]));//16
  //a[0]得到二维数组第一行 一行有4个元素
  printf("%d\n", sizeof(a[0] + 1));//4/8
  //a[0]第一行地址,a[0]+1第二行地址
  //第二行地址,数组地址,指针大小4/8
  printf("%d\n", sizeof(*(a[0] + 1)));//4
  //a[0]首元素地址,即第一行第一个元素地址,
  //加一得到第二个元素地址,解引用得到第二个元素
  printf("%d\n", sizeof(a + 1));//4/8
  //a为第一行地址,a+1第二行地址
  printf("%d\n", sizeof(*(a + 1)));//16
  //a+1第二行地址,解引用后得到第二行
  printf("%d\n", sizeof(&a[0] + 1));//4/8
  //&a[0]+1第二行地址 
  printf("%d\n", sizeof(*(&a[0] + 1)));//16
  //&a[0]+1第二行地址 解引用得到第二行
  printf("%d\n", sizeof(*a));//16
  //a第一行地址,解引用后得到第一行
  printf("%d\n", sizeof(a[3]));//16
  //数组越界,无第四行,但是根据二维数组前面行的大小
    //sizeof认为a[3]相同大小

image.png

总结:

数组名的意义:

1.sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。

2.&数组名,这里的数组名表示整个数组,取出的是整个数组的地址。

3. 除此之外所有的数组名都表示首元素的地址。

目录
相关文章
|
11天前
|
安全 C语言
【C语言】如何规避野指针
【C语言】如何规避野指针
18 0
|
1天前
|
C语言
c语言指针总结
c语言指针总结
7 1
|
6天前
|
存储 程序员 C语言
【C 言专栏】C 语言指针的深度解析
【4月更文挑战第30天】C 语言中的指针是程序设计的关键,它如同一把钥匙,提供直接内存操作的途径。指针是存储其他变量地址的变量,通过声明如`int *ptr`来使用。它们在动态内存分配、函数参数传递及数组操作中发挥重要作用。然而,误用指针可能导致错误,如空指针引用和内存泄漏。理解指针的运算、与数组和函数的关系,以及在结构体中的应用,是成为熟练 C 语言程序员的必经之路。虽然挑战重重,但掌握指针将增强编程效率和灵活性。不断实践和学习,我们将驾驭指针,探索更广阔的编程世界。
|
8天前
|
算法 搜索推荐 程序员
C语言中的函数指针和回调函数
C语言中的函数指针和回调函数
9 2
|
11天前
|
存储 编译器 C语言
【C语言】初步解决指针疑惑
【C语言】初步解决指针疑惑
7 0
|
12天前
|
存储 C语言
指针深入解析(C语言基础)带你走进指针,了解指针
指针深入解析(C语言基础)带你走进指针,了解指针
|
12天前
|
C语言 C++
C语言:指针运算笔试题解析(包括令人费解的指针题目)
C语言:指针运算笔试题解析(包括令人费解的指针题目)
|
12天前
|
C语言
C语言:数组和指针笔试题解析(包括一些容易混淆的指针题目)
C语言:数组和指针笔试题解析(包括一些容易混淆的指针题目)
|
26天前
|
存储 程序员 编译器
爱上C语言:指针很难?来来来,看看这篇(基础篇)
爱上C语言:指针很难?来来来,看看这篇(基础篇)
|
14天前
|
安全 C语言
指针与字符串:C语言中的深入探索
指针与字符串:C语言中的深入探索
15 0