C语言---深入指针(4)

2024-09-23 20:32:59 浏览数 (2)

回调函数

代码语言:javascript复制
//回调函数就是通过函数指针调用的函数
//这个在之前的转移表-计算器里面很明显,通过函数指针数组内的函数指针进行函数的调用
//
// 
// 将这四段代码分装成一个函数,一个代码将这4个问题都解决
int Add(int x, int y)
{
    return x   y;
}
int Sub(int x, int y)
{
    return x - y;
}
int Mull(int x, int y)
{
    return x * y;
}
int Div(int x, int y)
{
    return x / y;
}
void menu()
{
    printf("**************************************n");
    printf("**********   1.add    2.sub***********n");
    printf("*********    3.mull   4.div***********n");
    printf("*********    0.exit      *************n");
    printf("**************************************n");
}
void Calc(int (*pf)(int, int))//因为传过来的是函数的地址,那么就要用函数指针进行接收
{
    int x = 0, y = 0,ret=0;
    printf("请输入两个操作数");
    scanf("%d %d", &x, &y);
    ret = pf(x, y);//当pf接收到的是加法函数的地址的时候,我们直接用pf来调用
    //对于函数名就是函数的地址,用指针变量存起来的就是函数地址,那么指针变量的名字就是函数变量的地址
    //那么可以直接用指针变量的名字进行调用
    printf("%dn", ret);
}
int main()
{
    int ret = 0;
    int input = 0;
    int x = 0, y = 0;
    do
    {

        menu();//菜单
        printf("请选择:");
        scanf("%d", &input);
        switch (input)
        {
        case 1:
            Calc(Add);    //将加法函数的地址传过去--函数名是地址       
            break;
        case 2:
            Calc(Sub);            
            break;
        case 3:
            Calc(Mull);            
            break;
        case 4:
            Calc(Div);

            break;
        case 0:
            printf("退出计算器n");
            break;
        default:
            printf("选择错误,重新选择n");
            break;
        }
    } while (input);
    return 0;
}
 //这4个代码的差异就是运算的方式不同
//
// Calc是中间商,Add这些计算的函数是回调函数
// 
// 通过函数调用另一个函数进行调用
//
//当你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其
//所指向的函数时,被调用的函数就是回调函数
//这个代码里面回调函数就是Add,Sub,Mull,Div

回忆冒泡函数

代码语言:javascript复制
//回忆冒泡排序---对一组整数进行排序
//void bubble_sort(int arr[], int sz)//接受一个整型数组
//{
//    //趟数
//    for (int i = 0; i < sz-1; i  )
//    {
//        //一趟内部的两两比较
//        for (int 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;
//            }
//        }
//    }
//}
//print(int arr[], int sz)
//{
//    for (int i = 0; i < sz; i  )
//    {
//        printf("%d ", arr[i]);
//    }
//}
//int main()
//{
//    int arr[] = { 3,1,7,9,4,2,6,8,0 };
//    //进行排序--升序
//    int sz = sizeof(arr) / sizeof(arr[0]);
//
//    //冒泡排序核心思想,两两数字进行比较,不满足我们要求的顺序的话我们可以对其进行交换
//    //满足我们要求的顺序的话我们就换下一对数字进行比较
//    
//    //冒泡排序进行排序
//    bubble_sort(arr,sz);
//    print(arr,sz);
//    return 0;
//}
//对于冒泡排序的话,只能排序整型数组
//对于其他类型的数组无法排序
//字符串数组、字符数组、结构体数组、浮点数数组

strcmp----字符串的大小比较

代码语言:javascript复制
/字符串的比较
//应该使用strcmp进行比较
//int strcmp(const char* str1, const char* str2);
//char str1[]="Hello";
//char str2[]="hello";
//strcmp(str1, str2));
// 如果 strcmp 返回值大于 0,
// 则表示第一个字符串(str1)在比较中大于第二个字符串(str2)。
// 

a,b,c,d,c,e,f

a,b,c,q

因为d的ASCII小于q的,所以第一行的字符串就小于第二行的字符串

strcmp比较的不是长度,比较的是对应位置的字符ASCII大小的

qsort的结构

代码语言:javascript复制
而接下来介绍的qsort能适用于任意类型的数据的排序
void qsort(void* base,//指向待排序数组第一个元素的指针
            size_t num, //base指向的数组中的元素个数
            size_t size, //base指向的数组中每个元素的大小
            int(*com)(const void*, const void*)//函数指针--传递函数的地址
            //
            );

利用qsprt进行整型数组的排序

代码语言:javascript复制
//对于冒泡排序比较不同的类型的数据时,两个循环没有什么差异,但是对于不同的数据进行比较就有着差异
//对于整数是进行比较大小,但是并不是所有的数据能能用比'>''<'进行比较

//既然不同数据的元素比较是有差异的,那么就直接把两个元素比较的代码抽离出来 
//谁要调用qsort来进行比较排序,那就让谁提供比较的函数


//下面的函数比较的是p1指向的元素和p2指向的元素
//对函数
//p1指向的元素比p2指向的元素小的时候,返回的是小于0的数
//p1指向的元素等于p2指向的元素的时候,返回的是0
//p1指向的元素比p2指向的元素打的时候,返回的是大于0的数

//咱么为了使用qsort函数,我们必须提供一个比较2个整型的比较函数
//格式int cmp_int(const void*p1, const void*p2),那么这个函数的名字就是qsort函数的第四个条件
//int cmp_int(const void*p1, const void*p2)
//{
//    //p1就指向了这个数组里面的3,p2指向的就是1
//    /*if(*p1>*p2)*///不能这么写,因为在函数的参数里面,p1的类型是void*,p2是void*
//    //void *指针不能进行解引用操作
//    //将p1的类型强制转换成int*,再进行解引用就行了     *(int*)p1
//    //比较的三种情况
//    if (*(int*)p1 > *(int*)p2)
//    {
//        return 1;
//    }
//    else if (*(int*)p1 < *(int*)p2)
//    {
//        return -1;
//    }
//    else
//    {
//        return 0;
//    }
//
//}
//void print(int arr[], int sz)
//{
//    for (int i = 0; i < sz; i  )
//    {
//        printf("%d ", arr[i]);
//    }
//}
test这个函数测试qsort来排序整型数据
//void test1()
//{
//    int arr[] = { 3,1,7,9,4,2,6,8,0 };
//    int sz = sizeof(arr) / sizeof(arr[0]);
//    qsort(arr, sz, sizeof(arr[0]), cmp_int);
//    //(数组首元素地址,数组长度,每个元素字节大小,进行比较大小的函数)
//    print(arr, sz);//打印排序完后的数组
//}
//int main()
//{
//    test1();
//    return 0;
//}

//提供想要排序的数组,计算数组元素个数
//再利用qsort进行排序
//再利用qsort进行排序的时候,要提供数组首元素的地址、数组长度、数组每个元素的字节大小、
//最后再提供一个比较的函数的函数名,这个函数有固定的写法
//int cmp_int(const void*p1, const void*p2)
//因为p1和p2的类型都是void*,不能对其进行解引用进行比较,
//所以我们必须将其强制类型转换,(int*)p1
//再对其进行解引用*(int*)p1
//再将*(int*)p1与*(int*)p2进行比较
//前者大于后者就返回大于0的数
//前者小于后者就返回小于0的数
//前者等于后者就返回0

//对于qsort函数来说,我们只需要额外构建一个比较函数就能利用qsort进行快速排列

对于qsort函数来说,我们只需要额外构建一个比较函数就能利用qsort进行快速排列

代码语言:javascript复制
//对下面代码进行简化
// test1
int cmp_int(const void* p1, const void* p2)
{

    return *(int*)p1 - *(int*)p2;
}
//因为这里比较的是整数,那么我们直接作差返回
void print(int arr[], int sz)
{
    for (int i = 0; i < sz; i  )
    {
        printf("%d ", arr[i]);
    }
}
//test这个函数测试qsort来排序整型数据
void test1()
{
    int arr[] = { 3,1,7,9,4,2,6,8,0 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    qsort(arr, sz, sizeof(arr[0]), cmp_int);
    //(数组首元素地址,数组长度,每个元素字节大小,进行比较大小的函数)
    print(arr, sz);//打印排序完后的数组
}
int main()
{
    test1();
    return 0;
}

利用qsprt进行结构体的排序

代码语言:javascript复制
/*void qsort(void* base,//指向待排序数组第一个元素的指针
            size_t num, //base指向的数组中的元素个数
            size_t size, //base指向的数组中每个元素的大小
            int(*com)(const void*, const void*)//函数指针--传递函数的地址
            //
            );*/
//利用qsort函数对结构体进行排序
struct Stu//创建一个结构体
{
    char name[20];
    int age;
};

//按照名字比较两个结构体的数据
int cmp_stu_by_name(const void* p1, const void* p2)//p1和p2分别指向数组中的结构体
{
    //先将p1强制类型转换
    //(struct Stu*)p1---结构体指针-----不能直接指向成员  错误写法:(struct Stu*)p1->
    //要先括起来,将强制类型转换后的结构阔起来,才能找到成员

    //((struct Stu*)p1)->name, ((struct Stu*)p2)->name
    // 

     //两个名字是字符串,字符串比较是采用strcmp函数的
    return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);

    //因为strcmp函数的返回值和我们期望的qsort的第四个比较函数的返回值一样的,
    //所以我们直接将strcmp的值返回
    //strcmp要包含头文件的include <string.h>
}
void print(struct Stu arr[], int sz)
{
    for (int i = 0; i < sz; i  )
    {
        printf("%s ", arr[i].name);
    }
}
void test2()
{
    struct Stu arr[] = { {"zhangsan",20},{"lisi",35},{"wangwu",18}};//给了三个人的信息 
    //利用qsort对数组内的信息进行排序
    int sz = sizeof(arr) / sizeof(arr[0]);
    qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);//传递的函数是进行比较两个结构体对象
        //但是我们现在是按照名字比较还是年龄比较呢?
    print(arr, sz);
}

int main()
{
    test2();
    return 0;
}
代码语言:javascript复制
//按照年龄排序
struct Stu//创建一个结构体
{
    char name[20];
    int age;
};
void print(struct Stu arr[], int sz)
{
    for (int i = 0; i < sz; i  )
    {
        printf("%d ", arr[i].age);
    }
}//打印结果:18 20 35
void cmp_stu_by_age(const void* p1, const void* p2)
{
    //return strcmp(((struct Stu*)p1)->age, ((struct Stu*)p2)->age);
    //因为age是两个整数,所以不需要用strcmp了
    return ((struct Stu*)p1)->age-((struct Stu*)p2)->age;
    //将他们的值进行大小比较,直接返回
    //前者大于后者直接返回大于0的数
    //前者小于后者直接返回小于0的数
    //前者等于后者直接返回0



}
void test3()
{
    struct Stu arr[] = { {"zhangsan",20},{"lisi",35},{"wangwu",18} };
    int sz = sizeof(arr) / sizeof(arr[0]);
    qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
    print(arr, sz);

}
int main()
{
    test3();
    return 0;
}

关于结构体的补充知识点

利用结构体指针来访问结构体里面的成员对象

代码语言:javascript复制
//结构体知识点补充,利用结构体指针来找到结构体里面的数据
struct Stu//创建一个结构体
{
    char name[20];
    int age;
};
int main()
{
    struct Stu s = { "zhangsan",20 };
    //第一种写法:
    printf("%s %dn", s.name, s.age);
    //这里的知识点就是利用结构体名字加点再加结构体对象,就可以调用出对应的数

    struct Stu* ps=&s;//将变量s的地址取出存放到结构体指针内
    //struct Stu*就是这个结构体指针ps的类型
    //ps里面存放的是s的地址,ps就指向了s

    //第二种写法:

    //那么我们利用ps来找到这个结构体数组里面的数据怎么找呢?
    printf("%s %dn", (*ps).name, (*ps).age);
//ps是里面存放的是这个结构体的地址,对ps进行解引用得到的就是这个结构体s了


    //第三种写法:
    printf("%s %dn", ps->name, ps->age);//ps指向的对象的成员
    return 0;
}
//结构体成员访问操作符
// .       结构体变量.成员名
//->       结构体指针->成员名

qsort函数的总结

对于qsort函数来说

主要的就是括号内的第四个元素,也就是进行元素比较的函数的名字

对于qsort来说,这个进行比较的函数只需要返回三种值

大于0,小于0,或者等于0,只要传递回来就能直接快速排列

对于qsort函数来说:

第一个元素是要排列的数组的首元素的地址,就是数组名

第二个元素就是这个数组的元素个数sz

第三个元素就是每个元素的字节大小sizeof([0])

第四个元素就是这个比较的函数的函数名

对于这个比较函数就有说法了

这个就是固定格式

返回值是数字,就是大于0的数,0,小于0的数,所以函数前面是int

int cmp_int(const void* p1, const void* p2)

p1和p2的类型都是void*

p1和p2都指向的数组内的要进行比较的元素

如果要进行比较的话就需要对这个指针进行强制类型转换

假设:

int*强制类型转换,

(int*)p1

再对转换后的结果进行解引用就是p1指向的那个数

(int)p1

判断的是整型数组的话:

那么我们直接写:return* (int*)p1 - *(int*)p2;

两个数进行相减,返回值三种情况:大于0的数、0、小于0的数

假如说要判断结构体成员的年龄的话

return ((struct Stu)p1)->age-((struct Stu)p2)->age;

返回的就是年龄相减的值

先强制类型转换p1

(struct Stu*)p1

因为p1指向的是这个结构体数组,那么我们直接通过箭头操作符直接访问数据

(struct Stu*)p1→age

假如说要判断结构体成员的姓名的话

对于这个结构体数组来说,这个姓名就是字符串

对于字符串的比较我们要用到strcmp函数

因为我们想要的这个比较函数的返回值和这个strcmp的返回值一样的,那么我们直接返回strcm函数的值

return strcmp(((struct Stu)p1)->name, ((struct Stu)p2)->name);

strcmp(((struct Stu)p1)->name, ((struct Stu)p2)->name)

在strcmp函数中进行比较的是((struct Stu)p1)->name和((struct Stu)p2)->name

qsort实现升序和降序的原理

因为qsort默认实现的是升序

对于数组的快排,如果我们想实现数组的降序,

因为qsort是固定死的

但是qsort里面的一个元素,第四个元素,比较函数

我们只能通过这个比较函数来实现降序

对于这个代码

return ((struct Stu)p1)->age-((struct Stu)p2)->age;

如果前者大于后者就返回大于0的数,如果是升序的话,那么就要将这个较大的数和这个较小的数进行交换,因为我们要实现升序的效果,就是从小到大

如果想实现降序的话,我们将逻辑反过来:

return ((struct Stu)p2)->age-((struct Stu)p1)->age;

后面的大于前面的,返回值是小于0的数,因为要实现降序,那么我们就将较大的数与较小的数进行交换

咱们仔细想想,

对于qsort函数来讲,我们交换数组元素的条件是啥呢?

就是这个返回的值

如果我们写的是前面的值减去后面的值大于0的话就进行交换,就是前面的数大于后面的数,把大的换后面去,所以这种写法代表的排序顺序就是升序

假如后面的值大一些呢,就是前面值减去后面的值小于0,就是说后面的大,前面的小,就将这两个数进行交换,那么大的值都在前面,小的值在后面,那么我们就实现了降序了

就是对于qsort来说,我们这个快速排列需要交换满足条件的值,那么这个比较函数里面的条件就是这个满足交换条件的值交换的条件,就是return 后面的条件,如果这个返回值大于0的话就将这两个数进行对调了

p1-p2>0我们就对调这两个数----大的换后面去,---升序

p2-p1>0我们就对调这两个数----大的换前面去----降序

qsort函数的模拟实现

代码语言:javascript复制
//我们是否能将bubble_sort函数改造成通用的算法,可以排序任意类型的数据
//模仿qsort
struct stu
{
    char name[20];
    int age;
};
int cmp_stu_by_name(const void* p1, const void* p2)
{
    return strcmp(((struct stu*)p1)->name, ((struct stu*)p2)->name);
}

int cmp_int(const void* p1, const void* p2)
{
    return *(int*)p1 - *(int*)p2;
}
int cmp_stu_by_age(const void* p1, const void* p2)
{
    return ((struct stu*)p1)->age - ((struct stu*)p2)->age;
}





void Swap(char* buf1, char* buf2, size_t width)//传过来的是hcar*指针,那么我们就用char*接受
{
    char tmp = 0;
    for (int i = 0; i < width; i  )//因为不知道两个元素之间有多少个字节,那么我们就把每个字节进行交换
    {
        tmp = *buf1;
        *buf1 = *buf2;
        *buf2 = tmp;
    //这里仅仅只是交换一对字节,因为char类型是1个字节

    //交换完这一对字节,那我们就换下一对字节进行交换,因为buf1和buf2都是指针,那么我们利用指针的加减来实现字节间的加加
        buf1  ;
        buf2  ;
    //这个循环总共进行width次,每次一对字节交换
    }
}



//size_t是无符号整型
void bubble_sort(void *base,size_t sz,size_t width,int(*cmp)(const void*p1, const void* p2))//将函数的形参改成void*,因为我们不知道传过来的是什么类型的数据
//而恰好void*可以接收任意类型的地址,那么bubble_sort这个函数就能处理任意类型的数据了

//这里的base指向的是数组的首元素的地址,sz是数组元素个数
//仅仅知道这两个消息是仅仅不够的,因为我们不知道每个元素的字节大小是多少,因为不同类型的元素字节大小不同

//解释一下这个函数,第一个接收的是数组首元素的地址,第二个是数组元素个数,第三个是元素之间的宽度,就是字节大小
//第四个就是比较函数
{
    //趟数
    for (int i = 0; i < sz-1; i  )
    {
        //一趟内部的两两比较
        for (int j = 0; j < sz - 1 - i; j  )
        {
            //唯一要改变的就是下面的比较的代码了
            //1.比较两个元素
            if (cmp((char*)base   j * width, (char*)base   (j   1) * width) > 0)//如果比较函数的返回值大于0,就进行交换
                //如果传过去的元素满足条件大于0满足交换的条件,那么就进入条件语句进行交换


                //那么我们需要将arr[j]的地址和arr[j 1]的地址传到比较函数进行大小比较
            //元素之间的字符大小根据j的变化来表示,j*width
            //将base强制转换成char*指针,因为对于char*来说,加几就是跳过几个字节
                //(char)base width
            {

                //2.交换两个元素
                Swap((char*)base   j * width, (char*)base   (j   1) * width,width);

                //将起始地址传过去,但是并不知道交换多宽的数据,我们就一定得将宽度传过去

                //将两个交换的元素传过去,还有宽度也传过去
//交换的是(char*)base   j * width和  (char*)base   (j   1) * width这两个地址指向的元素

            }






        }
    }
}
print(int arr[], int sz)
{
    for (int i = 0; i < sz; i  )
    {
        printf("%d ", arr[i]);
    }
}



void test1()
{
    int arr[] = { 3,1,7,9,4,2,6,8,0 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);//cmp_int就是最前面的比较函数
    print(arr, sz);
}

void test2()
{
    struct stu arr[] = { {"zhangsan", 20},{"lisi",35},{"wangwu", 18} };
    int sz = sizeof(arr) / sizeof(arr[0]);
    bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);

}
void test3()
{
    struct stu arr[] = { {"zhangsan", 20},{"lisi",35},{"wangwu", 18} };
    int sz = sizeof(arr) / sizeof(arr[0]);
    bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
}

int main()
{
    test1();//对于整型数组
    //test2();//对于结构体--姓名
    //test3();//对于结构体--年龄
    return 0;
}

//p1和p2指向的是我要比较的两个元素
//因为排序我们得知道他们的大小才能进行,那么这个bubble_sort的第四个形参的那个比较函数的返回值就体现了他们的大小
//
// 
// 
// 
// 对于不同的类型的数据间的比较,我们要将内循环里面的比较条件语句进行改变
//
// 
// 整理一下:
// 拿这个整数数组排列来说
// 
// 首先我们先创建了一个test函数,test函数里面有数组的创建和数组长度的计算,还有一个冒泡函数
// 
// 对于冒泡函数,
// 我们传过去了一个首元素的地址--就是数组名
// 还传过去数组的长度
// 还有数组元素之间的宽度,因为我们不知道不同元素间的宽度
// 最后还传了一个比较数组函数的函数名字
// 
// 
// 
// 在经典的冒泡函数中,我们利用两层循环对数组进行排序
// 
// 而面对不同的元素的时候,这个比较的条件一定是要进行更换的,但是两层循环可以不用动
// 
// 
// 在我们创建冒泡函数接受传来的实参的时候,有这么几个元素
// 
// 我们用void*base来接受传来的数组名,因为我们并不知道传来的是什么类型的数据,恰好void*类型就可以接待任何类型的数据了
// 
// 在后面,我们创建了size_t sz来接受数组长度,size_t就是无符号整型的意思
// 
// 在后面我们又创建了一个size_t width,用来接收传来的数组中元素之间的字节宽度
// 
// 最后,因为下面传来的实参是比较数组中两个相邻元素的函数,那么我们就用一个函数指针进行接收,一定是比较函数哦
// 在这个函数指针创建的时候,可是有说法哦
// int(*cmp)(const void *p1,const void *p2)
//返回类型是整数的函数指针,返回类型有三种,大于0,等于0,小于0的整数
// 利用返回的值进行判断,判断是否交换这两个相邻的元素的位置
// 
// 而里面的const void *p1就是来接受任何类型的地址
// 
// 说完冒泡排序的外面,再说里面吧
// 
// 出了基本的两层循环,改变的就是里面的条件语句
// if (cmp((char*)base   j * width, (char*)base   (j   1) * width) > 0)
// 利用传来的比较函数,我们比较了数组中相邻的两个元素的大小,如果返回值>0,我们就让这两个相邻的元素进行交换
// 将base的指针类型强制转换成char*类型的指针
// (char*)base   j * width这个就是这个比较函数的第一个元素,也是起始点
// (char*)base   (j   1) * width这个是第二个元素,结束点
// 
// 将两个元素传到比较函数里面,返回的值就是return *(int*)p1-*(int*)p2
// 
// 可能你们又忘了为什么这么写:
// p1的类型在上面是void*类型,我们就需要将其强制转换到整型元素,再对其进行解引用操作,返回的值就是这两个元素之间的差值,反应两个元素的大小
// 
// 
// 为什么后面还有j*width呢?
// 随着j的变化,从0到sz-1-i,因为char类型的数据是1个字节的,如果我们有了width了,我们就知道我们该从哪里比较,到哪里停止了
// 
// 
// 
// 那么就进入这个if条件语句里面的Swap交换函数了
// 
// 这个交换函数可有意思
// 传过来的元素地址:(char*)base   j * width, (char*)base   (j   1) * width,width
// 
// 因为传过来的是地址,所以我们就用指针进行接收,我们还接受了元素的宽度
// 在这个函数里面,我们创建了一个临时变量tmp
// 还创建了一个循环 ,i从0开始,到width结束,就是循环width次,有多少个字节就交换几次,下面的就是很常见的交换的步骤了
// 但是是指针之间的交换,这些指针都是1个字节,因为传过来的是char*类型的指针
// 
// 再后面,就进行指针  ,换另一个字节,直到两个元素之间的字节都交换完成,那么这两个元素就交换完成了
// 交换四对字节
// 
//这些代码没有具体类型的要求,你只要传一个数据,我们都能接受
// 因为我们使用void*类型的指针进行存储的

0 人点赞