pointer_to_the_advanced(指针进阶)——重置版

文章目录

回顾指针

1,指针就是个变量,用来存放地址,地址 唯一标识 一块 内存空间。
2.指针的大小是固定的 4 / 8字节(32位平台 / 64位平台)。
3.指针是有类型的 ,指针的类型决定了指针的 +- 整数的步长,指针解引用操作的时候的权限(一次访问几个字节内容)
4.指针的运算

举个例子回忆一下

#include<stdio.h>
void test(int* arr)// 这里的 arr 是 数组首元素地址的一份拷贝
{
    printf("%d\n",sizeof(arr[0]));// 计算的数组的一个元素大小,输出为 4
     printf("%d\n",sizeof(arr));// 计算的是地址的大小(这里不能算是数组的地址,因为 该地址是test传过来的  首元素地址  的一份拷贝),输出为 4
//  32位系统     
     sizeof(arr)在这里是求指针(地址)大小(4 byte)    
     sizeof(arr[0])在这里求的是一个元素的大小,int类型(4字节)
      如果是64位系统。指针大小为 8 字节,输出此时就为 2
}

int  main()
{
    int arr[10] = { 0 };
    test(arr);//传过去的是数组首元素地址,因为不是单独 与 sizeof 和 & 操作符连用
    return 0;
}

1.字符指针

在指针的类型 中 我们知道有一种 指针类型 为 字符指针 char*·

程序一:

#include<stdio.h>
int main()
{
    char ch = 'w';
    char* pc = &ch;
    *pc =='w';
    return 0;
}

程序二:

#include<stdio.h>
int main()
{
    char arr[] = "abcdef";
    char* pc = arr;//这里存入的是首元素 a 的地址
    printf("%s\n",arr);//abcdef
    printf("%s\n",pc);//abcdef   两者都是向后打印,直到遇到'\0'停止
    %s 就是 根据 给的地址位置开始向后打印的,知道遇到'\0'停止
    return 0;
}

程序三:

#include<stdio.h>
int main()
{ 
    char* p = "abcdef";// "abcdef" 双引号引起来的abcdef\0,是一个常量字符串
    // 上表达式的意思是,把 a 的地址 存入指针变量p里面去
    printf("%c\n",*p);// *p == a
    // 输出为 a
    printf("%s\n",p);//从存入p的这个地址(首元素a的地址)开始往后打印知道遇到 '\0'
    // 即输出为 abcdef
    return 0;
}

程序四:

#include<stdio.h>
int main()
{
    const char* p = "abcdef";// 最稳妥写法,就是在 * 前面加上 const
    *p = 'w';// 这时候你想改都改不了,况且 "abcdef" 是一个常量字符串,也改不了
    printf("%s\n", p); //你会发现 没有任何输出,程序崩溃。违规操作
    //还有一个原因 abcdef\0 是一个常量字符串,是不可以被改变的(const:是变量具有常量属性)
    return 0;
}

程序五:

#include<stdio.h>
int main()
{
    char arr1[] = "abcdef";
    char arr2[] = "abcdef";                     
    if (arr1 == arr2)//这里是两个不同数组的数组名(不同的首元素地址),是否相同
         //肯定不同,两个不同数组的起始空间肯定不一样
    {
        printf("hehe\n");
    }
    else
    {
        printf("haha\n");// 所以输出这条语句
    }
    return 0;
}

程序六:

#include<stdio.h>
int main()
{


    建议下面两句表达式在 * 前面防疫 const  ,这样就更保险,无法通过解引用去修改常量字符串的内容
     这样写,虽然意义不大(常量字符串本身并不能被修改),语法更为准确
    const char* p1 = "abcdef";
    const char* p2 = "abcdef";
    这里把常量字符串 abcdef\0 的首元素(a)地址分别存入 2 个指针变量
    是因为abcdef\0是常量字符串,不可改变,因此没有必要创建2个,直接共用一个,即 p1 == p2
    if (p1 == p2)
    {
        printf("hehe\n");// 所以输出这条语句
    }
    else
    {
        printf("haha\n");
    }
    return 0;
}

指针数组 - 本质上是一个数组

pointer文章中,我们也学了指针数组,指针数组 指的是一个 存放指针 的数组

程序一:

#include<stdio.h>
int main()
{
    int arr[10] = { 0 };// 整形数组
    char ch[5] = { 0 };// 字符数组
    int* parr[4];// 这就是一个存放 整形指针 的数组,简称 指针数组 
    char* pch[5];// 这就是一个存放 字符指针 的数组,简称 指针数组 
    return 0;
}

程序二:

#include<stdio.h>
int main()
{

    int a = 10;
    int b = 20;
    int c = 30;
    int d = 40;
                                  
    int* arr[4] = { &a, &b, &c, &d }; 
    等价于     //int* pa = &a;
              //int* pb = &b;
              //int* pc = &c;
              //int* pd = &d;
    int i = 0;
    for (i = 0; i < 4; i++)
    {
        printf("%d ",*arr[i]);// 10 20 30 40
    }
    return 0;   // 指针数组很少怎么用 ,下面程序将告诉你,指针数组怎么使用
}

程序三:

#include<stdio.h>
int main()
{
    int arr1[] = { 1, 2, 3, 4, 5 };
    int arr2[] = { 2, 3, 4, 5, 6 };
    int arr3[] = { 3, 4, 5, 6, 7 };//以上三个表达式,就是在说我有三数组,内容是。。。。
    int* parr[] = { arr1, arr2, arr3 };
    // 把上面 三个数组 的  数组名/数组首元素地址  存入这个 指针数组
    
    int i = 0;
    for (i = 0; i < 3; i++)// 遍历 指针数组 parr 的元素
    {
        int j = 0;
        for (j = 0; j < 5; j++)// 遍历 指针数组 parr 的 元素 所指向的 数组 的 元素
        {
            printf("%d ",*(parr[i] + j));
        }
        printf("\n");
    }
    return 0;  
}

这里我们回顾一下 二级指针

int* arr1[]; //一级整形指针数组.
int** arr[];// 二级整形指针数组
ichar* arr2[]; //一级字符指针数组
char** arr2[]; //二级字符指针数组


数组指针 - 指针

程序一:

#include<stdio.h>
int main()
{
    int* p = NULL;// 整形指针 - 指向 整形 的指针   作用:可以 存放  整形的地址
    char* pc = NULL;//字符指针 - 指向 字符 的指针  作用:可以 存放  字符的地址 


数组指针 - 指向 数组 的指针   作用:可以 存放  数组的地址
    //int arr[10] = { 0 };  整形数组
    // arr - 首元素地址
    // &arr[0] - 首元素的地址 
    // &arr - 数组的地址

    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int(* p)[10] = &arr;// 把数组的地址存起来
    // 为什么不能去掉(),因为 [] 的优先级 比 * 高,去掉(),p就和数组[10]先结合,就成 指针数组(存放指针的数组) 了
    // 加 () 把 * 和 p 先结合,使 p 成为一个指针 (另外请注意 这里不是解引用,只是说明 p 是一个指针)
    // 把 *p 去掉,还剩int [10],意思就是说该指针(p)指向 一个 元素个数为 10 的数组,且数组每个元素的类型为整形
    // 即 上表达式 int(*p)[10] 就是数组指针
    return 0;
}

程序二:

#include<stdio.h>
int main()
{
    char* arr[5];
    //如何把上表达式的数组存入 数组指针?
    如下
    char*(*pa)[5]=&arr;
    // 先用()把 * 和 pa(指针变量名) 结合起来,使 pa 为一个指针,也就是说 * 告诉我们 pa 是个指针
    //再在后面加[5],意思是 指针 指向一个元素个数为5的数组
    // 又因为 指针 指向的数组 的  元素类型 为 char*,所以在前面补上

    int arr2[10] = { 0 };
    int (*pa2)[10] = &arr2;
    return 0;
}


&数组名 VS 数组名

数组名 绝大部分时候 都是为 首元素地址

只有两种情况例外  &数组名  和  sizeof(数组名)
在这两种情况下的数组名,代表是整个数组,取出的是数组的地址(与数组首元素地址相同,但意义不同)

举个例子

&arr+1 - 直接 跳过一整个 数组的字节
比如 int arr[10],他的大小是40个字节,&arr+1 数组地址会加上40
如果是 arr+1 它就只跳过一个元素,意思就是 跳过第一个元素,地址指向第二个元素


数组指针的用法 : 一般用在 二维数组

程序一:

#include<stdio.h>
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10};
    int (*pa)[10] = &arr;
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        //printf("%d ",(*pa)[i]);// *pa 就相当于数组名  *pa  ==  arr
        printf("%d ",* (*pa+i) );//等价于 printf("%d ",*(arr+i))
    }
    return 0; // 但 数组指针 不是这么用的,以上只是让你对它理解更深一点
}

在 明白 数组指针 的真正用途之前,我们需要观察一个程序

#include<stdio.h>
int main()
{
    int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    int i = 0;
    int* p = arr;// 指针变量p 接收的是 数组的首元素地址,即 p == arr
    for (i = 0; i < 10;i++)
    {
        printf("%d ",*(p+i));// 通过数组首元素的地址,来遍历数组元素
    }
    printf("\n");
  上下两个循环表达大额效果是相同的
    for (i = 0; i < 10; i++)
    {
        printf("%d ", *(arr + i));
    }
    printf("\n");



    for (i = 0; i < 10; i++)
    {
        printf("%d ", arr[i]);// arr[i] == *(arr+i) == *(p+i) == p[i]
    }
    printf("\n");
  上下两个循环表达大额效果是相同的
    for (i = 0; i < 10; i++)
    {
        printf("%d ", p[i]);// arr[i] == p[i]
    }

    //以上所有写法都是等价的。
    return 0;
}

程序二(数组指针的用法):

#include<stdio.h>
void print1(int arr[3][5], int x, int y)
{
    int i = 0;
    int j = 0;
    for (i = 0; i < x; i++)// 双重循环遍历二维数组
    {
        for (j = 0; j < y; j++)
        {
            printf("%d ",arr[i][j]);// printf("%d ". *(*(arr+i)+j) );
        }
        printf("\n");
    }
}

void print2(int(*p)[5], int x, int y)//由于 二维数组转过的是 首元素地址(一维数组的地址),需要一个一维数组指针来接收
{
    int i = 0;
    int j = 0;
    for (i = 0; i < x; i++)// t通过双重循环,来遍历二维数组
    {
        
        for (j = 0; j < y; j++)
        {
            //printf("%d ", *(*(p+i)+j));// p 是 一维数组的地址 (一行),*p 就是找到了第一行的数据,也就是一维数组的数组名  
                                       // *( p + i),i=0,还是第一行,i=1 是第二行, i=2 第三行
                                       // *(p+i)+j  就是第几行 第几个元素
            printf("%d ",p[i][j]);
// *(*(p+i)+j)== p[i][j] 先用一个括号将 p 和 i括起来,得到一维数组的首元素地址,在对其解引用得到"数组名",
再用一个括号将其和 j 括起来,得到 i 行 第 j 个元素的地址,在对其 解引用,得到该元素的值,并将其打印(注意! []* 的优先级高)
            //printf("%d ", *(p[i]+j))     
            // *(p+i) == arr[i] == p[i]
            // *(p[i]+j) == 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} };
    print1(arr,3,5);
     这里的 数组名 就是 首元素【{12, 34, 5}】地址
     首先我们要 把二维数组 看作 一维数组,把 第一行{1,2,3,4,5}数据,也就是我们的第一个元素(首元素),看作一个一维数组 int a[5]
    //以此类推 第二行数据 就是 第二个元素(也是一个一维数组), 第三行数据 就是 第三个元素(也是一个一维数组)
    print2(arr,3,5);// 那么 传过去的是数组首元素地址,而且是一个一维数组的地址
    return 0; 
}

小汇总

int arr[10] //arr是一个整形数组,具有10个元素.

int* parr1[10];//parr1是指针数组,首先它是一个数组,具有10个元素,且每个元素的类型是(int*) ,

int(*parr2)[10];    parr2是一个整形数组指针
用括号让 * 与 parr2 先结合
所以parr2是一个指针,该指针指向了一个数组,数组有10个元素,每个元素的类型是int

int( * parr3 [10] ) [5]
因为()的优先级最高,所以先判断()里的内容
又因为 * 和[],[]的优先级更高,所以 parr3先与[]结合,所以parr3是个数组
把 parr3[10]去掉,还剩下int(* )[5] ,就是它的元素类型.

例子: int arr[10];去掉arr[10],乘下的 int,就是它的类型(整形)

那int(* )[5]是个什么类型?
仔细观察一下,你会发现,它和数组指针 int(*p)[10] 一样
.那么、我们可以说 parr3是个数组,元素有10个,每个元素都是一个整形数组指针,这个指针能指向5个元素,每个元素的类型是int.的整形数组。(我们称   int( * )[5]  为 整形数组指针类型,简称 数组指针类型 )
在这里插入图片描述


数组参数、指针参数

在写代码的时候难免要把 [ 数组 ] 或者 [ 指针 ]传给函数,那函数的参数该如何设计?

一维数组传参

#include<stdio.h>
void test(int arr[])//  OK
{}

void test(int arr[10])// OK
{}

void test(int* arr)//  OK
{}





void test2(int* *arr)// OK
{}


void test2(int* arr[20])// OK  这里 20 跟上表达式一样可以省略
{}

int main()
{
    int arr[10] = { 0 };
    int* arr2[20] = { 0 };
    test(arr);
    test2(arr2);
    return 0;
}

二维数组传参

#include<stdio.h>
void test(int arr[3][5]) // OK 
{}
void test(int arr[][5]) // OK 维数组 的 行 是能省略的
{}


void test(int arr[3][]) // NO 二维数组 的 列是不能省略的
{}
void test(int arr[][])  // NO 二维数组 的 列是不能省略的
{}

void test(int* arr)  // NO 这只能用来接收一维数组的元素地址(数组的地址,要用数组指针来接收,而不是数组一个一级指针),不能接收二维数组的传参
{}
void test(int* *arr)  //  二维数组的数组名 是 首元素地址(第一行的地址,可以将其当做一个一维数组的地址】)
{}                    // 一个数组的地址,是不 能放进 二级指针的
                      // 二级指针 是用来 存放 一级指针变量 的地址
                      //所以该写法也是错的 NO


void test(int (*arr)[5]) // ok
 ()使 * 和 arr 先结合,使 arr 成为指针,用它来接收二维数组的地址(首元素地址)
将二维数组的首元素地址 就第一行数据的地址(可以看作  一维数组的地址 ,所以需要一个 数组指针 来接收),
该指针指向一个有五个元素的数组(二维数组的一行数据),且每个元素的类型为 int 
{}


int main()
{
    int  arr[3][5] = { 0 };
    test(arr);// 二维数组 的 数组名 是 首元素地址(第一行的地址数据的地址,将其当做 一个 一维数组的数组名)
}

在这里插入图片描述


一级指针传参

#include<stdio.h>
void print(int* p,int sz)
{
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("%d ",*(p+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);//一级指针p 传给函数
    return 0;
}

思考: 当 一个函数 的 参数部分 为 一级指针的时候,函数能接受什么参数?

#include<stdio.h>
void tset(int* p)// 这里 的一级指针变量 p  是 print 函数 中 一级指针变量 的 一份拷贝
{
    ;
}
void print(int* p,int sz)
{
    int a = 10;
    int* p = &a;
    test(&a);// ok
    test(p);// ok,这里传的是 一级指针变量本身,而不是 一级指针 的 地址,所以不需要 二级指针来接收
    return 0;
}
能接受 一级指针变量本省 和 一个整形数据的地址

二级指针的传参

程序一(传二级指针变量本身和一级指针变量地址):

#include<stdio.h>

void test(int* *ptr)
{
    printf("num = %d\n",**ptr);
}

int main()
{
    int n = 10;
    int* p = &n;
    int* *pp = &p;// 最右边的*,说明pp是一个指针,该指针指向 p;左边 int*是类型,是该指针指向 p 的类型
    test(pp);// ok,这里传的是 二级指针变量本省,所以接收只需要 一个 二级指针,相当于 将其拷贝一份数据
    test(&p);// ok 这里传的是 一级指针变量 的 地址,一个一级指针 的 地址,需要一个二级指针来接收
    return 0;
}

程序二(传 一级指针变量的地址):

#include<stdio.h>
void test(int* * p)
{
    printf("hehe");
}
int main()
{
    int* ptr = 0;
    test(&ptr);// 这里就是典型的,传址(传一级指针变量的地址),需要一个二级指针来接收
    return 0;
}

程序三(传 二级指针变量的本身):

#include<stdio.h>

void test(int* * p)
// 因为 test函数传的是二级指针变量的本身,而不是二级指针变量的地址,所以我们只需用一个相同类型的二级指针来接收test的传参
// 这里 的 二级指针变量 p, 相当于 是 二级指针变量 pp 的一份临时拷贝
{
    printf("hehe");
}

int main()
{
    int* ptr = 0;
    int* *pp = &ptr;
    test(pp);// 这里传的是 二级指针变量本身
    return 0;
}

程序四(传 指针数组 的 数组名):

#include<stdio.h>
void test(int* * p)
{
    printf("hehe");
}

int main()
{
    int* arr[10];
    test(arr);
//指针数组 也可以,这里传的是数组名(首元素地址),因为 指针数组里 存放的元素的类型是 int* ,也就是说 首元素地址 其实是一个 一级指针变量(元素)的地址,一个 一级指针变量的地址,需要一个 二级指针变量 来接收
    return 0;
}

思考:当函数的参数为二进制的时候,可以接收什么参数?

一级 指针变量 的地址

二级指针变量的本身

存放 一级指针数组 的 数组名

函数指针

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

程序一:

#include<stdio.h>

int add(int x, int y)
{
    int z = 0;
    z = x + y;
    return z;
}

int main()
{
    int a = 10;
    int b = 20;
    printf("%d\n", add(a, b));


    printf("%p\n",&add);
    printf("%p\n", add);// 这两句程序,输出的结果是一样的。
    //因为  函数名 和 &函数名 都是函数的地址, 而 函数 只有一个,所以函数的地址是唯一的
    return 0;
}

程序二:

#include<stdio.h>

int add(int x, int y)
{
    int z = 0;
    z = x + y;
    return z;
}

int main()
{
    int arr[10] = { 0 };// 整形数组
    int(*p)[10] = &arr;// 整形数组的地址,需要一个 整形数组指针来接收

    int (*pa)(int,int) = add;
// 函数指针,先用()把 * 和 pa 先结合,是 pa 是一个指针(指针才能存放地址),
// 该指针 指向的函数 有两个整形参数,然后该函数类型是 int
// 只需要告诉函数的参数类型就行,x 和 y 写不写都无所谓   
    printf("%d\n",(*pa)(2, 3));// 5
    (*pa) == add,就是说 add 的地址,存入 函数指针 pa 中,我们再通过 解引用,找到add 函数,并对其调用。
    就 好像一个函数声明,告你说,这样也能调用 add 函数
    return 0;
}

程序二:

#include<stdio.h>

void print(char* str)
{
    printf("%s\n", str);
}

int main()
{
    void(*p)(char*) = print;
    //这里不是解引用,我们用(),先让 * 和 p 结合,使p为一个指针
    //该指针 指向函数的参数的类型是 char*
    //函数返回类型是 void
    //这个时候 p 就是我们的所谓的函数指针

    (*p)("hello word!");
    等价于
    print("hello word!")
    return 0;
}
你可以这么认为 函数指针 就是 函数名

阅读两段有趣的代码

代码1

    void (*)() - 函数指针类型
    
 (  void (*)()0, 最外面的括号 是 强制类型转换符号,就是把 0 进行强制类型转换( int -> 函数指针类型  )
 到那时候 0 就是一个函数的地址

( * ( void ( * ) ( ) ) 0) 
 *解引用,调用 0 地址处的该函数【void (*)(),假设该式子等价于  void(*p)(int,int),这样你们应该能理解】
 就是调用 地址 0 处 的 函数 void(*p)(int,int)

代码2(简化 嵌套函数指针的写法)

void(*signa1( int, void(*)(int))) (int);
signal这是一个函数声明
signal 函数的参数有两个,第一个是 int ,第二个是函数指针,该函数指针指向的函数的参数类型是int,返回类型是void
 signal 函数 的 返回类型 也是一个 函数指针,该函数指针指向的函数的参数类型是int,返回类型是void
 然后我们把上面 三个去掉(函数 signal,和 它的两个参数)
 剩下: void (*)(int) 这又是一个函数指针类型,是函数signa1(int, void(*)(int))的返回类型




typedef  void(* pfun_t)(int); 现在这个时候 pfun_t 就是函数指针类型
// pfun_t   简写(重命名)是不能放后面的,要放在(*这里)

上下两者 对 函数名 重命名的写法是不相同的

typedef unsigned int uint;  // 简写是可以直接写在后面的


 简化      void(*signa1(int, void(*)(int)))(int);

typedef  void(*pfun_t)(int);

pfun_t signa1(int, pfun_t);

函数指针实战

#include<stdio.h>
int add(int x, int y)

{
    int z = 0;
    z = x + y;
    return z;
}

int main()
{
    int arr[10] = { 0 };
    int(*p)[10] = &arr;

    int (*pa)(int,int) = add;// 函数指针,先用()把 * 和 pa 先结合,是 pa 是一个指针(指针才能存放地址),指向函数的两个整形参数,然后该函数类型是 int
    //          只需要告诉函数的参数类型就行,x 和 y 写不写都无所谓   
    
    printf("%d\n",(*pa)(2, 3));// 5
    printf("%d\n", (**pa)(2, 3));// 5
    printf("%d\n", (***pa)(2, 3));// 5
    照理说 第一次 *pa 解引用,通过 pa的存入的地址,找到add 函数,第二次 **pa,以*pa为地址再找,以此类推。
     但由 上三个表达式 的输出结果显示,* 没有起到应有的 作用
     那么我们可不可以 去掉 * 呢?
     
    printf("%d\n", pa(2, 3));// 5 
     结果证明 可以
     原因在程序一中:
      printf("%p\n",&add);
      printf("%p\n", add);// 这两句程序,输出的结果是一样的。
    //因为  函数名 和 &函数名 都是函数的地址, 而 函数 只有一个,所以函数的地址是唯一的
       那么,再加上 pa本来就是等函数的地址(pa == &add == add),所以 在使用函数指针时,可以直接写 函数指针变量

     有的时候会有些人会加上 * 的原因,这样可读性高,让读者 能明白 pa 其实是一个指针
     但 加上了 * ,要注意加上(),要不然会出现问题,
    例 *pa(2,3),这时pa会先于(2,3)结合,形成一次函数调用,然后再解引用,就是说 对 调用结果 5 进行解引用,不是我们想要的结果 
    return 0;
}


函数指针数组

#include<stdio.h>

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* arr[5];//指针数组  arr是一个数组,有5个元素,且每个元素的类型为int*,即每个元素都是一个整形指针、
    
    int(*pa)(int ,int) = add;//  我们有4 个函数要被调用,要是 一个接着一个接着这样会麻烦
    
    这时需要一个数组,这个数组可以存放4个函数的地址 - 函数指针的数组(前提是它们的参数和类型是完全一致的)
    int(*parr[4])(int,int) = {add,sub,mul,div};// 函数指针数组
    将上式拆成 2 部分
    int (*) (int,int)    
    parr[4]
    表达的意思是 parr 先和 [4] 结合,成为一个数组,元素有4个,每个元素 都是一个函数指针

    int i = 0;
    for(i = 0; i < 4; i++)
    {
        printf("%d ",parr[i](2, 3));// 加法 5, 减法 -1  乘法 6 除法 0
    }
    return 0;
}


练习(写一个函数指针 pf,能够指向 my_strcpy)

首先我们要知道 我们的自定义函数的类型: char* my_strcpy(char* dest, const char* src);
然后我们对它进行改造:char* ( * pf)(char * ,const char * ) = my_strcpy;

#include<stdio.h>
#include<assert.h>
char* My_strcopy(char* destination,const char* source)//这里本质上是个指针
{
	assert(destination&&source);
	char* ret = destination;
	while (*destination++ = *source++);
	return ret;
}

int  main()
{
	char arr[30] = { 0 };
	char arr1[] = "abc";
	char arr2[] = "def";
	char arr3[] = "adcde";
	char arr4[] = "abcdef";
	char* arr5[4] = { arr1, arr2, arr3, arr4 };
	char*(*pf)(char*, const char*) = My_strcopy;
	for (int i = 0; i < 4; i++)
	{
		printf("%s\n", pf(arr,arr5[i]));
	}
	return 0;
}

写一个函数指针数组,能存放 4个 my_strcpy 函数的地址
char* ( * pfarr[4])(char*, const char*);

     这个我就不写,就跟 指针数组一样,给个打括号放函数名就行了

接下来我们用函数指针数组 实现计算器

正常方式

#include<stdio.h>
void menu()
{
	printf("**************************\n");
	printf("** 1,add      2.sub ******\n");
	printf("** 3,mul      4,div ******\n");
	printf("*****   0.退出      ******\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;
	    int x = 0;
	    int y = 0;
	    do
	    {
	        menu();
	        printf("请选择");
	        scanf("%d",&input);
	        switch (input)
	        {
	        case 1:
	            printf("请输入两个操作数:");
	            scanf("%d%d", &x, &y);
	            printf("%d\n",add(x,y));
	            break;
	        case 2:
	            printf("请输入两个操作数:");
	            scanf("%d%d", &x, &y);
	            printf("%d\n",sub(x,y));
	            break;
	        case 3:
	            printf("请输入两个操作数:");
	            scanf("%d%d", &x, &y);
	            printf("%d\n",mul(x,y));
	            break;
	        case 4:
	            printf("请输入两个操作数:");
	            scanf("%d%d", &x, &y);
	            printf("%d\n",div(x,y));
	            break;
	        case 0:
	            printf("退出程序");
	            break;
	        default:
	            printf("输入错误,请重新选择:");
	            break;
	         
	        }
	    } while (input);
	    return 0;
	}

函数指针数组方式

#include<stdio.h>
void menu()
{
    printf("**************************\n");
    printf("** 1,add      2.sub ******\n");
    printf("** 3,mul      4,div ******\n");
    printf("*****   0.退出      ******\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;
    int x = 0;
    int y = 0;
    int(*parr[])(int, int) = { 0, add, sub, mul, div };// parr 是一个函数指针数组
    // 函数指针数组的用途 又称 转移表
    do
    {
        menu();
        printf("请选择");
        scanf("%d", &input);
        if (input >= 1)
        {
            printf("请输入操作数:");
            scanf("%d%d", &x, &y);
            printf("%d\n", parr[input](x, y));
        }
        else if (input == 0)
        {
            printf("退出程序\n");
            break;
        }
        else
        {
            printf("选择错误,请重新输入");
        }
    } while (input);
    return 0;
}

指向 函数指针数组 的 指针

int arr[10];
int(*p)[10] = &arr; // p就是一个指向 整形数组 的指针
//这就是一个整形数组指针

int* (*p)[10] = &arr;// p 就是一个指向 整形指针数组 的 指针


int add(int x, int y)
{
    return x + y;
}

int(*pf)(int,int) = add;//pf 是 函数指针
int(*pf[4])(int,int)// 这是一个 函数指针数组
int(*(*pf)[4])(int,int)// pf 就是一个 指向函数指针数组的 指针
()让 pf 和 * 先结合,使其成为一个指针,去掉他们还剩 int(*()[4])(int,int)很明显就是一个 函数指针数组

函数指针数组 与 函数指针数组的指针 (详解)

函数指针数组
/先写个数组pfarr[5]
pfarr[5];

// 再用()把 * 和数组 pfarr[5],结合 -指针数组
(*parr[5]);

// 再写函数参数类型与返回类型
int(*parr[5])(int, int);// pafarr 是一个 函数指针 的 数组


指向  函数指针数组 的 指针
*ppfarr = &pfarr;// 函数指针数组 的 地址,存起来,
//ppfarr就是 函数指针数组 的 指针

//再将其替换入内,记得加括号
即:
int(*(*ppfarr)[5])(int, int) = &pfarr;
// 首先用()把 pparr  先和 * 结合,即pparr是一个指针

//该指针向外一看,指向一个数组 [5],  pparr 指针指向 一个数组

//去掉 数组 和 指针 : int(*)(int,int)  这是一个函数指针类型

//数组每个元素,都是 函数指针类型

// 所以 ppfarr 就是一个指向 函数指针 数组 的 指针


int(*p)(int, int); // 函数指针
int(*p[5])(int, int); // 函数指针数组
int(*(*p)[5])(int, int);// 函数指针数组 的 指针
函数指针数组的指针 - 首先它是指针,其次 它是一个数组,最后是 一个函数指针

回调函数

回调函数:
是一个通过函数指针调用的函数,如果你把函数的指针(地址)作为参数传递给另一个函数
当这个指针 被用来 调用 其所指向的函数时,我们就说这是回调函数,

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

简单来说,就是通过 把 实现功能函数的地址,交给另一哥 函数 ,由它去调用我们的 功能函数,
不能直接调用 功能函数,(就是说,你想玩电脑,得经过父母的同意,不然你玩个屁。)

实例:

#include<stdio.h>
void menu()
{
    printf("**************************\n");
    printf("** 1,add      2.sub ******\n");
    printf("** 3,mul      4,div ******\n");
    printf("*****   0.退出      ******\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;
}

void calc(int(*pf)(int))// 这就是你父母,你父母同意了(函数收到相应地址,使用相应的功能),你才能玩
{
    int x = 0;
    int y = 0;
    printf("请选择两个操作数:");
    scanf("%d%d",&x,&y);
    printf("%d\n", pf(x, y));
}

int main()
{
    int input = 0;
    do
    {
        menu();
        printf("请选择");
        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("退出程序");
            break;
        default:
            printf("输入错误,请重新选择:");
            break;
         
        }
    } while (input);
    return 0;
}


qsort 函数 的使用

先来回顾一下冒泡排序 :只能排整形数据

#include<stdio.h>
void bubble_sort(int*arr, int sz)
{
    int i = 0;
    for(i = 0; i < sz - 1; i++)//  整个数组,只用 排 元素的总个数 - 1,因为最后不需要排
                                 //  就好比 432  1,前面的 比 1 大的数,都排到前面去了,
                                 //  1 它自己就只能 stay here
    {
        //一趟冒泡排序
        int j = 0;
        for (j = 0; j < sz - 1 - i; j++)
        //那 一个数 和 它屁股后面的数 进行比较
         {
            if (arr[j] > arr[j + 1])
            // 如果 它 比 它屁股 大,它就前进一位,然后再跟它目前的屁股,再比一次,直到,他没屁股大,停止,然后屁股再去跟它后面的人去比
            // 第一次要比 九次,
            // 它一次比完之后,一开始 跟它比的 屁股的哪一位,他就要开始比了,
            // 由于 屁股这位,跟它比过了,他就不会去比了,因为结果都知道,还比个锤子哟!
            // 所以 屁股 这位 比的次数 比它 少一次,
            // 而 屁股后面那位,跟 它们都比过了,它就比九次 少2次
            // 以此类推
            {   
                int tmp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = tmp;
            }
        }
    }
}

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


qsort 函数 - 库函数 - 排序 - quick sort (快速排序)

先来了解一下, qsort 的 结构类型

void qsort(void *base,目标数组(需要排序的数组)
           size_t num, 数组元素的个数
           size_t width,元素大小(以字节为单位)
          int(*compare)(const void *e1, const void *e2)//函数指针,该函数是一个 比较函数
          
          );
          


void * 介绍

void *  : 万能指针,能接受任何 类型的数据,但不能调用,如果要调用,只能强制类型转换,才能使用

#include<stdio.h>
#include<stdlib.h>
int compare(const void* e1, const void* e2)//用来比较两个整形值
                                           // void* 无类型指针,
                                           // void* 类型的指针 = 可以 接收 任意类型 的地址(指针),,但不能进行解引用
                                           // 俗称 万能指针
                                           
{
    ;
}

int main()
{
    int a = 10;
    void* p = &a;
    *p = 0;// void* 无法进行 解引用操作
    // 因为 指针的类型,决定它解引用操作时,一次可以访问多少个字节
    // 又因为 void* 是一个无类型指针,所以 在它 解引用操作时,无法确定一次访问多少个字节
    return 0;
}


进入实战

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int compare(const void* e1, const void* e2)
{
    return *(int*)e1 - *(int*)e2;// 如果两个宿相等,    返回  0
                                 // 第一个 小于 第二个  返回  复数
}                                // 第一个 大于 第二个  返回  正数

void test()
{
    int arr[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
    int sz = sizeof(arr) / sizeof (arr[0]);
    qsort(arr, sz, sizeof(arr[0]), compare); // qsort 排序 arr,arr有10个元素,一个元素内存的大小,比较两个元素的大小,返回
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
}





int compare_f(const void*e1, const void*e2)
{
    return (int)(*(float*)e1 - *(float*)e2);

    ////if (*(float*)e1 == *(float*)e2)
    ////{
    ////    return 0;
    ////}
    ////else if (*(float*)e1 > *(float*)e2)
    ////{
    ////    return 1;
    ////}
    ////else
    ////{
    ////    return -1;
    ////}
}

void test2()
{
    float f[] = { 9.0, 8.0, 7.0, 6.0, 5.0, 4.0};
    int sz = sizeof(f) / sizeof(f[0]);
    qsort(f, sz, sizeof(f[0]), compare_f);
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("%.1f ", f[i]);
    }
}




struct stu
{
    char name[20];
    int age;
};

int compare_by_age(const void*e1, const void*e2)
{
    return ((struct stu*) e1)->age - ((struct stu*)e2)->age;
}

void test3()
{
     struct stu s[3] = { { "zhangsan", 20 }, { "lisi", 30 }, { "wangwu", 10 } };
     int sz = sizeof(s) / sizeof(s[0]);
     qsort(s, sz, sizeof(s[0]), compare_by_age);
     int i = 0;
     for (i = 0; i < sz; i++)
     {
         printf("%d ", s[i].age);
     }
}



int compare_by_name(const void*e1, const void*e2)
{
    return strcmp( ((struct stu*) e1)->name,((struct stu*)e2)->name);// 名字比较,就是字符串比较,不能用大于,等于和小于来比较
}          // 这里需要 用到 strcmp 函数
           // 返回值跟 大小等于的返回值是一样的,0 ,正数,负数

void test4()
{
    struct stu s2[3] = { { "zhangsan", 20 },{ "lisi", 30 }, { "wangwu", 10 } };
    int sz = sizeof(s2) / sizeof(s2[0]);
    qsort(s2, sz, sizeof(s2[0]), compare_by_name);
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("%s ", s2[i].name);
    }
    
}


int main()
{
    test();
    printf("\n");
    test2();
    printf("\n");
    test3();
    printf("\n");
    test4();
    return 0;
}

qsort(数组名,该数组元素个数,该数组单个元素内存大小,(函数指针,比较两个元素的 所用函数 函数 的 地址 )函数指针的 两个参数 是:待比较的两个元素的地址

改进冒泡排序(回调函数)

#include<stdio.h>
//实现 bubble_sort函数 的程序员,他 是否知道 未来排序 的 数据类型  -  不知道
//程序员也不知道 待 比较 的 两个元素 的 类型
swap(char* buf1, char*buf2, int width)
{ 我们的 叫换函数,是通过一个字节,一个字节的进行交换
运用了 qsort 参数中的 元素大小,进行实现的
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}

int compare_int(const void*e1, const void*e2)
{
	return *(int*)e1 - *(int*)e2;
}

void bubble_sort(void*base, int sz, int width, int(*compare)(void*e1, void*e2))// 模拟 qsort 函数
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			//两个元素比较,前者比后者大,compare 会返回一个 大于 0 的数字,小于(返回一个差值(负的))相等(返回0)
			大于,返回 一个 正数,条件成立,执行 if 语句,进行交换两个元素的位置,其它情况,则不做改动。
			if (compare((char*)base + j*width, (char*)base + (j + 1)*width)>0)// 这里实在调用int 函数名(const void*e1, const void*e2)
			j 一开始 是 0, base 就是 第一个元素, base+(j+1),就是第二个元素
			把他们交给 compare 函数 进行比较,如果 第一个元素 比 第二个元素 大
			那就交换,否则,不交换,反上去,下回 j 就是 1,base 就第二个元素,base+(j+1)就是第三个元素
			以此类推,
注意!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
((char*)base + j*width,char*  和 width(宽度【一个元素的大小】),这里我使用了 qsort 函数参数中 元素个数 和 元素大小,这两点来实现我们的 元素遍历,j*width 等于 跳过几个元素(一个元素的大小 是 width,那 乘上 j,不就是跳过 j 个元素嘛。


			{
				//交换
				由我们自制的交换函数去做
				swap((char*)base + j*width, (char*)base + (j + 1)*width, width);
			}
		}
	}

}


void test()
{
	int arr[10] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	// 使用 bubble_sort 的程序员 一定要知道自己排序的是什么数据
	//他就应该知道 如何比较待排序数组中的元素
	bubble_sort(arr, sz, sizeof(arr[0]), compare_int);
}


struct stu
{
	char name[20];
	int age;
};

int compare_by_age(const void*e1, const void*e2)
{
	return ((struct stu*) e1)->age - ((struct stu*)e2)->age;// 因为 箭头 的 优先级,强制类型转换
}

void test2()
{
	struct stu s[3] = { { "zhangsan", 20 }, { "lisi", 30 }, { "wangwu", 10 } };
	int sz = sizeof(s) / sizeof(s[0]);
	bubble_sort(s, sz, sizeof(s[0]), compare_by_age);
}

int compare_by_name(const void*e1, const void*e2)
{
	return strcmp(((struct stu*) e1)->name, ((struct stu*)e2)->name);// 名字比较,就是字符串比较,不能用大于,等于和小于来比较
}          // 这里需要 用到 strcmp 函数
//  
void test3()
{
	struct stu s[3] = { { "zhangsan", 20 }, { "lisi", 30 }, { "wangwu", 10 } };
	int sz = sizeof(s) / sizeof(s[0]);
	bubble_sort(s, sz, sizeof(s[0]), compare_by_name);
}

int main()
{
	test();
	printf("\n");
	test2();
	printf("\n");
	test3();
	return 0;
}

本文结束

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Dark And Grey

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

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

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

打赏作者

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

抵扣说明:

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

余额充值