C语言进阶第三课-----------指针的进阶----------后续版

简介: C语言进阶第三课-----------指针的进阶----------后续版

复习

在上一篇博客中我简单的介绍了一些,这里我们来复习一下

字符指针

#include<stdio.h>
int main()
{
  char* pc = "abcdef";
  printf("%p\n", pc);
  printf("%c", *pc);
  return 0;
}

pc存放的是字符a的地址,不是字符串

前面我们还学习到了const char * pa 和char *coust pa

数组指针

简单的讲就是指向数组的指针

#include<stdio.h>
int main()
{
  int arr[10] = { 0 };
  int(*parr)[10] = &arr;
  printf("%p\n", parr);
  printf("%p", parr + 1);
  return 0;
}

可以计算出两个地址相差40,也就是40个字节

这里我们又会引出一个知识点,就是数组名的理解,只有&arr 和sizeof(arr)中的arr是整个数组,不是数组首元素地址,其他的数组名都是首元素地址

如果是二维数组传参的话,传递的是第一行的地址

指针数组

简单的说还是数组,只不过元素的类型是指针类型

#include<stdio.h>
int main()
{
  int a = 0;
  int b = 0;
  int c = 0;
  int* arr[] = { &a, &b,&c };
  printf("%p", arr[0]);
  return 0;
}

数组传参和指针传参

数组传参

1. 传递的是数组首元素的地址

2. 一维数组传参,传递的是第一个元素的地址

3. 二维数组传参,传递的是第一行的地址

4. 数组在传参的时候,形参可以写成数组或者指针

5. 如果是二维数组传参,形参为指针要使用数组指针

函数指针

顾名思义就是指向函数的指针

#include<stdio.h>
int Add(int a, int b)
{
  return a + b;
}
int main()
{
  int a = 4;
  int b = 5;
  int (*pA)(int, int) = Add;
  printf("%d", pA(a, b));
  return 0;
}

在这里&Add和Add都是传递函数的地址过去,还有一点就是(*pA)(a, b)和(pA)(a, b)是一样的,

函数指针数组

数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组,可以理解是存放地址的数组。那函数指针数组就是存放函数指针的数组,类型就是函数指针类型,那我就以上面的为例

#include<stdio.h>
int Add(int a, int b)
{
  return a + b;
}
int main()
{
  int a = 4;
  int b = 5;
  int (*parr[])(int, int) = { Add };
  printf("%d", parr[0](a, b));
  return 0;
}

看看是一样的效果

那我们来写一个计算器

#include<stdio.h>
void menu()
{
  printf("*************************************\n");
  printf("*****     0.exit       1.add   *****\n");
  printf("*****     2.sub        3.mul    ******\n");
  printf("*****           4.div          ******\n");
  printf("*************************************\n");
}
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 catf(int(*pa)(int, int))
{
  printf("输入两个整数:>");
  int num1 = 0;
  int num2 = 0;
  scanf("%d %d", &num1, &num2);
  return pa(num1, num2);
}
int main()
{
  while (1)
  {
    menu();
    int input = 0;
    printf("请选择:>");
    scanf("%d", &input);
    int (*arr[])(int, int) = { NULL,add, sub, mul, div };
    if (!input)
    {
      printf("结束");
      break;
    }
    else if (input > 4)
    {
      printf("输入错误\n");
      continue;
    }
    printf("%d\n", catf(arr[input]));
  }
  return 0;
}

这里我运用了函数指针数组(转移表),可以节约很多的代码量,简单的说就是很好用

指向函数指针数组的指针(可以理解为数组的指针,只是类型变了)

前面我们学过了数组指针,这里的意思也是一样的只是叫法不一样,那这个类型就是函数指针类型

,函数指针类型是这样的 int (* )(), 那指向函数指针数组的指针的写法就是 int (* (*parr)[num])(),感觉很别扭

少说废话,直接演示

#include<stdio.h>
int main()
{
  int a = 5;
  int b = 2;
  int* arr[] = { &a, &b };
  int* (*parr)[2] = &arr;
  printf("%d", *(*parr[0]));
  return 0;
}

这是整形指针数组,指向整形指针数组的指针

同理函数指针数组的指针如下

#include<stdio.h>
int add(int a, int b)
{
  return a + b;
}
int main()
{
  int (*arr[])(int, int) = { add };
  int (*(*parr)[1])(int, int) = &arr;
  printf("%d", (*parr[0])(5, 6));
  return 0;
}

这里我只使用了一个函数,小可爱可以看看思路,和数组指针的写法是一样,只是这个类型不一样而已

回调函数

这个知识点主要涉及到函数指针的,比较重要,我们来学习

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个

函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数

的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进

行响应。

我们可以简单理解为回调函数的地址作为参数传递给另一个函数,也就是说回调函数通过通过传递本身地址给另外一个函数

上面我们写的计算器的代码就是使用到了回调函数

这里我简单的画了一下,回调函数就是add()函数,直接调用就是通过函数名直接调用

如果这里还不明白那就举一个函数qsort(),这是一个排序函数,底层是快速排序

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

可能一些小可爱不懂这个函数,那我就来介绍一下这个函数

void* :我们可以理解为一个垃圾桶,可以接收任何指针类型的数据,比如可以接收 int* 、char* 等类型的数据

base:就是我们要从哪里开始排序的第一个元素地址(待排序数据的起始地址)

size_t: 在一些编译器中 是unsigned int,而在一些编译器中就不是,这里就是unsigned int

num:我们要排序的元素个数

width:我们排序的元素中一个元素的大小,单位是字节

compare:是一个函数地址

我们可以想想前面我们的冒泡排序算法

#include<stdio.h>
#include<stdlib.h>
int main()
{
  int arr[] = { 1,5,4,8,26,8,5,2,15,9 };
  int sz = sizeof arr / sizeof(arr[0]);
  int i = 0;
  for (i = 0; i < sz - 1; i++)
  {
    int j = 0;
    for (j = 0; j < sz - i - 1; j++)
    {
      if (arr[j] > arr[j + 1])
      {
        arr[j] = arr[j] ^ arr[j + 1];
        arr[j + 1] = arr[j] ^ arr[j + 1];
        arr[j] = arr[j] ^ arr[j + 1];
      }
    }
  }
  return 0;
}

主要核心就是两两相邻的元素进行比较,我们要理清排几次,每次怎么比较,冒泡排序有一些缺点,就是有点死板,只能排序某一类型,而qsort()可以排序任何类型,主要是在于compare函数的写法;这个函数是用来比较两个元素的,这两个元素可能是整形、字符、结构体等类型,使用的方法也会不同,但最终要返回 >0 <0 ==0的结果,简单的说就是我们要比较啥类型的这个函数就要写成该类型的比较方法

废话少说,直接演示

int ( *compare )(const void *e1, const void *e2 )

这是qsort函数里 的参数,我来解释一下,

可能有一些小可爱不明白void*,那我来解释一下

平时我们写一个int*类型的指针,我们加1会跳过4个字节,char* 类型加1跳过一个字节,如果我们在写比较两个元素比较的方法,不指明类型就会不懂加1跳过几个字节,所以规定了void *类型 不能解引用,也不能加减操作

#include<stdio.h>
#include<stdlib.h>
struct test
{
  char arr[20];
  int age;
};
int cmp2(const void* e1, const void* e2)
{
  return ((struct test*)e1)->age - ((struct test*)e2)->age;
}
int main()
{
  struct test arr[] = { {"ksdads", 15},{"ksdajk", 18} ,{"ksdadgjhjs", 10} };
  qsort(arr, sizeof arr / sizeof arr[0], sizeof(arr[0]), cmp2);
  return 0;
}

这是结构体比较年龄,如果要比较名字的话

int cmp3(const void* e1, const void* e2)
{
  return strcmp(((struct test*)e1)->arr, ((struct test*)e2)->arr);
}

那我们可不可以写一个以冒泡排序为逻辑的qsort函数呢?

我们来试试看

#include<stdio.h>
#include <stdlib.h>
#include<string.h>
void print(int* arr, int sz)
{
  int i = 0;
  for (i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);
  }
  printf("\n");
}
// 比较两个元素
int cmp(const void* e1, const void* e2)
{
  return *(int*)e1 - *(int*)e2;
}
//比较字符
int cmp1(const void* e1, const void* e2)
{
  return *(char*)e1 - *(char*)e2;
}
//两个元素进行交换
void exchange(char* e1, char* e2, size_t byt)
{
  int i = 0;
  for (i = 0; i < byt; i++)
  {
    char a = *(e1 + i);
    *(e1 + i) = *(e2 + i);
    *(e2 + i) = a;
  }
}
void my_qsort(void* base, size_t sz, size_t byt, int (*conpi)(const void*, const void*))
{
  int i = 0;
  for (i = 0; i < sz - 1; i++)
  {
    int j = 0;
    for (j = 0; j < sz - 1 - i; j++)
    {
      if (conpi((char*)base + j * byt, (char*)base + (j + 1) * byt) > 0)
      {
        exchange((char*)base + j * byt, (char*)base + (j + 1) * byt, byt);
      }
    }
  }
}
void test(void *arr, int sz, size_t by)
{
  print(arr, sz);
  my_qsort(arr, sz, by, cmp);
  print(arr,sz);
}
int main()
{
  int arr[] = { 8,3,1,5,2,3,6,9,7,4,8,9,2,4};
  char arr1[] = "dsfdfgfdfgfdf";
  // 整形
  //test(arr, sizeof arr/ sizeof(arr[0]), sizeof (arr[0]));
  // 字符串
  printf("%s\n", arr1);
  my_qsort(arr1, sizeof arr1 / sizeof(arr1[0]), sizeof(arr1[0]), cmp1);
  printf("%s\n", arr1);
  return 0;
}

b5d881d96b1346148c3a2dcfc5907ce7.png

可能有一些小可爱不明白这里,那我来讲讲,因为,我们要构造一个qsort函数,那我们就该要传入对应的base(起始地址)、unm(长度)、wigth(一个元素的字节长度) 和函数cmp(用于比较两个的函数),

我们不知道要传入啥数据类型,我就用void*进行接收,使用上面这个方法是为了让对于的存储的内存进行交换,转换成char*是为了更好的计算

这幅图是我以两个元素进行交换的情况一个元素的内存可以拆分成内存单元(一个字节),转换成char*,加1,我们访问一个字节,这样就可以解决类型不同,访问的字节也不同的问题了

总结

到这里我就介绍结束了,有不懂的小可爱可以私聊我

相关文章
|
1月前
|
存储 C语言
【C语言篇】深入理解指针3(附转移表源码)
【C语言篇】深入理解指针3(附转移表源码)
36 1
|
24天前
|
C语言
【c语言】指针就该这么学(1)
本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。
45 0
|
23天前
|
C语言
【c语言】指针就该这么学(3)
本文介绍了C语言中的函数指针、typedef关键字及函数指针数组的概念与应用。首先讲解了函数指针的创建与使用,接着通过typedef简化复杂类型定义,最后探讨了函数指针数组及其在转移表中的应用,通过实例展示了如何利用这些特性实现更简洁高效的代码。
15 2
|
24天前
|
C语言
如何避免 C 语言中的野指针问题?
在C语言中,野指针是指向未知内存地址的指针,可能引发程序崩溃或数据损坏。避免野指针的方法包括:初始化指针为NULL、使用完毕后将指针置为NULL、检查指针是否为空以及合理管理动态分配的内存。
|
24天前
|
C语言
C语言:哪些情况下会出现野指针
C语言中,野指针是指指向未知地址的指针,通常由以下情况产生:1) 指针被声明但未初始化;2) 指针指向的内存已被释放或重新分配;3) 指针指向局部变量,而该变量已超出作用域。使用野指针可能导致程序崩溃或不可预测的行为。
|
30天前
|
存储 C语言
C语言32位或64位平台下指针的大小
在32位平台上,C语言中指针的大小通常为4字节;而在64位平台上,指针的大小通常为8字节。这反映了不同平台对内存地址空间的不同处理方式。
|
30天前
|
存储 算法 C语言
C语言:什么是指针数组,它有什么用
指针数组是C语言中一种特殊的数据结构,每个元素都是一个指针。它用于存储多个内存地址,方便对多个变量或数组进行操作,常用于字符串处理、动态内存分配等场景。
|
30天前
|
存储 C语言
C语言指针与指针变量的区别指针
指针是C语言中的重要概念,用于存储内存地址。指针变量是一种特殊的变量,用于存放其他变量的内存地址,通过指针可以间接访问和修改该变量的值。指针与指针变量的主要区别在于:指针是一个泛指的概念,而指针变量是具体的实现形式。
|
1月前
|
C语言
C语言指针(3)
C语言指针(3)
11 1
|
1月前
|
C语言
C语言指针(2)
C语言指针(2)
13 1