深入理解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. 除此之外所有的数组名都表示首元素的地址。

目录
相关文章
|
26天前
|
C语言
【c语言】指针就该这么学(1)
本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。
45 0
|
24天前
|
C语言
【c语言】指针就该这么学(3)
本文介绍了C语言中的函数指针、typedef关键字及函数指针数组的概念与应用。首先讲解了函数指针的创建与使用,接着通过typedef简化复杂类型定义,最后探讨了函数指针数组及其在转移表中的应用,通过实例展示了如何利用这些特性实现更简洁高效的代码。
15 2
|
25天前
|
C语言
如何避免 C 语言中的野指针问题?
在C语言中,野指针是指向未知内存地址的指针,可能引发程序崩溃或数据损坏。避免野指针的方法包括:初始化指针为NULL、使用完毕后将指针置为NULL、检查指针是否为空以及合理管理动态分配的内存。
|
25天前
|
C语言
C语言:哪些情况下会出现野指针
C语言中,野指针是指指向未知地址的指针,通常由以下情况产生:1) 指针被声明但未初始化;2) 指针指向的内存已被释放或重新分配;3) 指针指向局部变量,而该变量已超出作用域。使用野指针可能导致程序崩溃或不可预测的行为。
|
1月前
|
存储 C语言
C语言32位或64位平台下指针的大小
在32位平台上,C语言中指针的大小通常为4字节;而在64位平台上,指针的大小通常为8字节。这反映了不同平台对内存地址空间的不同处理方式。
|
1月前
|
存储 算法 C语言
C语言:什么是指针数组,它有什么用
指针数组是C语言中一种特殊的数据结构,每个元素都是一个指针。它用于存储多个内存地址,方便对多个变量或数组进行操作,常用于字符串处理、动态内存分配等场景。
|
24天前
|
编译器 C语言
【c语言】指针就该这么学(2)
本文详细介绍了指针与数组的关系,包括指针访问数组、一维数组传参、二级指针、指针数组和数组指针等内容。通过具体代码示例,解释了数组名作为首元素地址的用法,以及如何使用指针数组模拟二维数组和传递二维数组。文章还强调了数组指针与指针数组的区别,并通过调试窗口展示了不同类型指针的差异。最后,总结了指针在数组操作中的重要性和应用场景。
17 0
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
34 3
|
7天前
|
C语言
c语言调用的函数的声明
被调用的函数的声明: 一个函数调用另一个函数需具备的条件: 首先被调用的函数必须是已经存在的函数,即头文件中存在或已经定义过; 如果使用库函数,一般应该在本文件开头用#include命令将调用有关库函数时在所需要用到的信息“包含”到本文件中。.h文件是头文件所用的后缀。 如果使用用户自己定义的函数,而且该函数与使用它的函数在同一个文件中,一般还应该在主调函数中对被调用的函数做声明。 如果被调用的函数定义出现在主调函数之前可以不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必多所调用的函数在做声明
23 6
|
27天前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
34 10