U7 函数指针、排序算法、二维数组知识点

本文详细介绍了函数指针的概念、应用,包括回调函数的作用,以及冒泡排序、插入排序、选择排序和快速排序等常见排序算法的实现。同时讨论了递归和二维数组,以及二级指针的使用实例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.函数指针

void ( *p ) ( int,int ) 指向这样一类函数,返回值是void,形参是两个int

p是函数指针,*p是定义,指这是一个指针,指针的名字是p

可以这样理解:void ( * ) ( int,int ) p=add;

void ( *p ) ( int,int ) 这是定义格式,真正用的时候

p(int a,int b) p就是一个模版,可以填入想要调用的函数,a,b就是该函数的参数。

作用: 把放在外面的函数接口放在结构体中,方便调用; 不再需要头文件中声明; 可以避免重名函数; 可以由结构体成员直接调用方法(像变量一样); 最大的作用是回调函数 callback; 函数指针也可以作为参数传递,即可以访问函数指针指向的那串代码

缺点:极大增加了结构体的内存损耗

//函数指针代码示例
#include<stdio.h>
    int add(int a,int b)
{
    return a+b;
}

    int sub(int a,int b)
{
    return a-b;
}
int main ()
{
    int (*p)(int,int)=NULL;    //固定写法
    p = add;                  //p=&add;也是可以的 函数指针p指向add函数
    printf("%d\n",p(1,2));    //p(1,2)是给指向的函数赋值   结果:3
    return 0;
}

mystring.h

#ifndef _MYSTRING_H_
#define _MYSTRING_H_
#define Max 1024

typedef struct string mystring;
          // 本来初始化过程:mystring obj1;Initialize(&obj1,"hello") ;  
#define Init_Mystring(obj,str)  mystring  obj; Initialize(&obj,str); //简化初始化 
//现在初始化过程:   Init_Mystring(obj1,"hello") ;注意这里的obj1是结构体的名字,不是指针!!!                      


struct string
{
    char string[Max];   
    int size;     //记录字符串长度,避免每次调用strlen一个一个去数      
    void (*print)(mystring *obj);   //定义函数指针
    //也就是定义这样一个函数,它的变量是一个结构体类型的指针obj,返回值是void
    int (*isEqual)(mystring*obj1,mystring*obj2);
    int (*iscontains)(mystring *dest,mystring *src);
    int (*stringsize)(mystring *dest,mystring *src);
    void (*removstring)(mystring *dest,const char *str);
    void (*insertstring)(mystring *dest,const char *str,int index);
    //new>
};


void  Initialize(mystring *obj,const char* str); //初始化函数不能设函数指针

#endif                                         //因为不能自己调用自己,给自己赋值初始化

mystring.c

#include "mystring.h"
#include<string.h>
#include<stdio.h>
//  < new
void Print(mystring *obj);       //这些叫做前置声明
int IsEqual(mystring*obj1,mystring*obj2);
                     //无函数指针不可前置声明,因为在main.c中展开头文件找不到函数接口
int IsContains(mystring *dest,mystring *src);//接口从头文件移过来是为了方便只有结构体可以调用函数
int Size(c);
void RemoveString(mystring *dest,const char *str);
void InsertString(mystring *dest,const char *str,int index);
//new>

void Initialize(mystring *obj, const char *str)  
{
    strcpy(obj->string,str);   
    obj->size=strlen(str);
    //<new        有函数指针的初始化     
    obj->print = Print;       //Print是这个模版的具体实现  
    //前面的print只是一个模版名,有这么个函数叫print某某,它有一个mystring *obj变量,返回值是void
    obj->iscontains= IsContains;
    obj->isEqual= IsEqual;
    obj->stringsize= Size;
    obj->insertstring= InsertString;
    obj->removstring= RemoveString;  
    //new>   
}//下面与之前代码一样

main.c

#include<stdio.h>
#include<string.h>
#include"mystring.h"

int main()
{
    //<new
    Init_Mystring(obj,"helloworld");//宏替换

    obj.print(&obj);          //是.还是->取决于前面,此处obj是变量,不是指针
    obj.insertstring(&obj,"ell",1);
    obj.print(&obj);          //print的形参是指针,所以需要传地址
    //new>
    return 0;           
}      

回调函数作用:

降低函数与被调函数之间的关联性;

把部分函数功能交由别的函数去完成; (比如传参)

使函数与部分功能取消强绑定

//回调函数callback示例1:
#include<stdio.h>

int add(int a,int b)
{
    return a+b;
}

int sub(int a,int b)
{
    return a-b;
}

void Func(int (*ptr)(int,int))    //这就是一个函数调用模版,可以调任何需要的函数
{      //函数指针也可以作为参数传递,即可以访问函数指针指向的那串代码
    printf("%d\n",ptr(1,2));    //加不加*都行,加*要(*ptr)
}
int main()
{
    Func(add);                //加不加&都可以
    return 0;
}  
//回调函数callback示例2:
#include<stdio.h>
-
//开发者1
void Write_to_Client1(const char* str)
{
    printf("writre to 1 info :%s",str);
}
void Write_to_Client2(const char* str)
{
    printf("writre to 2info :%s",str);
}
//开发者2
void Func(void (*ptr)(const char* ))    // 只知道有函数需要hello字符串,并不知道用来干什么
{
    const char* s="hello!";      //初始化函数指针指向的函数的参数
    ptr(s);          //加不加*都行,加*要(*ptr)     ptr是一个待定函数模版(s)传参
}
int main()    
{
    Func(Write_to_Client1);  //可以理解为Func是函数的函数,也就是函数指针做变量使用    
    return 0;      //输出writre to 1 info :hello!
}

2. 辨析:指针函数

void* p(int,int) 返回值是指针的函数叫指针函数

返回值需要指针就用指针函数

指向一个函数的指针叫做函数指针

3. 排序

1)冒泡排序

第一趟排序最大的数被两两交换到最边上,第二趟第二大的到倒数第二个以此类推

2)插入排序

后插入的数与已经插好的比较,拿要插入的数在已经排好序的数中从前往后或从后往前挨个比较,直到找到合适位置插入

3)选择排序

找到一个数列最小的数,与第一个数交换,然后找剩下最小的和第二个交换,以此类推

4)快速排序

设第一个数是基准值,左边指针往右走,右边指针往左走,比基准值大的扔基准值右边,小的扔基准值左边

#include<stdio.h>
#define ArrayLen(a)  sizeof(a)/sizeof(a[0])

int IsSmaller(int a,int b)//从大到小
{
    if(a<b)
    return 1;   //需要交换
    else 
    return 0;
}
int IsBigger(int a,int b)  //从小到大
{
    if(a<b)
    return 0;
    else 
    return 1;   //需要交换
}


void BubbleSort(int* a,int len,int (*Rule)(int,int))//函数指针的回调,从小到大还是从大到小
{
    for(int i=0;i<len-1;i++)//外循环次数,最大要循环len-1轮(所有数都不在自己位置)
    {                       //内循环是一轮循环排序次数
         int flag=0;         //flag在外循环里,每次结束要归零
        for(int j=0;j<len-i-1;j++)//减掉i是因为要减掉已经排好序的数
        {
           
            if(Rule(a[j],a[j+1]))//什么样的交换规则由用户需求决定,这就是回调
            {
                int temp=a[j];
                a[j]=a[j+1];
                a[j+1]=temp;
                flag=1;
            }
        }
        if(flag==0)  // 标志位,单轮排序完却没有发生交换,说明已经排序好,就退出排序
        {
            break;
        }
    }
}

void InsertSort(int *a,int len)
{
    for(int i=1;i<len;i++)//每个要插入的数要跟前面的数比较,除了a[0]
    {                     
        int temp=a[i];    //temp初始化为要插入的当前值
        int j=i;          
        for(;j>0;j--)  //temp与前面已经排序好的数比较
            {
                if(a[j-1]>temp)  //如果前面的数比temp要大,依次后移一位
                {
                    a[j]=a[j-1];  
                }
                else
                {
                    break; //如果没有break,j就要执行j--
                }
            }
            a[j]=temp;
    }
}

void ChooseSort(int *a,int len)
{
    for(int i=0;i<len;i++)      //一共len-1轮排序 
    {
        int min=i;              //把i设为当前最小数的下标 
        int Minum=a[min];       
        for(int j=i+1;j<len;j++) 
        {
            if(a[j]<Minum)       //如果发现比a[i]更小的数,就把这个数设为当前最小,j是其下标
            {
                Minum=a[j];      //在循环过程中a[min]不一定就是最小值(Minum)
                min=j;            
            }
        }                            
        int temp=a[i];             //记录循环开始时a[i]的值(刚刚只是改变j值,i始终没变)
        a[i]=Minum;                //把刚刚找的最小数与a[i]  交换  位置
        a[min]=temp;               
    }
}
void ChooseSort2(int* a,int len )
{
    int left=0;
    int right=len-1;
    while(left<right)
    {
        int min=left;
        int max=right;
        for(int i=left;i<=right;i++)
        {
            if(a[i]<a[min])
            min=i;
            if(a[i]>a[max])
            max=i;
        }
        int temp =a[left];
        a[left]=a[min];
        a[min]=temp;
   if(max==left )              //避免最大的本身在最左边,最小的在最右边
    {                           //那交换两次后等于没变
        max=min;
    }
    else{
         temp=a[right];
        a[right]=a[max];
        a[max]=temp;
       }

        left++;
        right--;
    }
}
void FastSort(int *a,int start,int end)//首尾下标
{
    int temp=a[start];          //temp基准值
    int left=start;
    int right=end;
    while(left<right)       //左边找小的,右边找大的,一旦left和right相遇,退出循环
    {
        while(left<right && temp<a[right]) //比较temp右边的数,找出比基准值更小的
        {                  //left和right不可见面,不然死循环
            right--;       //找不到就继续往右
        }
        if(left<right)   //找到后把右边那个比基准值小的数赋值给left所在位置
        {
        a[left]=a[right];
        left++;
        }
        while(left<right && temp>a[left])
        {
            left++;
        }
        if(left<right)
        {
        a[right]=a[left];
        right--;
        }
        a[left]=temp;                  //一次循环结束时顺序:2 1 3  5  9 8     left和right都指向5
        FastSort(a,start,left-1);      //递归来排序213
        FastSort(a,right+1,end);      //递归来排序98
    }
}

int main()
{
    int a[]={5,8,3,1,9,2};
    //BubbleSort(a,ArrayLen(a),IsSmaller);  //IsSmaller赋值给了Rule
    //InsertSort(a,ArrayLen(a));            //IsSmaller是调用函数的名字
    //ChooseSort(a,ArrayLen(a));            //给地址给名字都可以
    //ChooseSort2(a,ArrayLen(a));
    FastSort(a,0,ArrayLen(a)-1);
    for(int i=0;i<ArrayLen(a);i++)
    {
        printf("%d ",a[i]);
    }printf("\n");
    return 0;
}

4.递归

递归:也就是自己调用自己

注意点: 1.终止条件 2.循环体

//例题:猴子一天吃一半多一个,十天还剩一个
#include<stdio.h>
int EatPeach(int day)
{
    if(day==10)
    return 1;+
    else
    return ((EatPeach(day+1)+1)*2);//返回值是桃子数
}                       //EatPeach(day+1)后面一天的桃子数

int factorial(int n)    //n!=n*n-1*n-2......1
{
    if(n==1||n==0)
    return 1;
    else
    return n*factorial(n-1);
    
}
int mystrlen(char* s)    //计算字符串长度
{
    if(*s=='\0')
    return 0;
    else
    return  mystrlen(s+1)+1; //s+1指针往后走一格
}


int main()
{
    printf("%d\n",EatPeach(1));//结果: 1534

    char *s="adc";
    printf("%d",mystrlen(s));

    return 0;   
}      //left和right不可见面,不然死循环

5.二维数组

*(*(a+i)+j)=a[ i ] [ j ]

#include<stdio.h>

int main()
{
    int a[2][3]={{1,2,3},{4,5,6}};//两行三列
    int b[2][3]={1,2,3,4,5,6};
    for(int i=0;i<2;i++)
    {
        for(int j=0;j<3;j++)
        {
            printf("%x ",&a[i][j]);

        }
        printf("\n");
    }
    printf("%x,%x,%x,%x",a,&a,&a[0],&a[0][0]);
    printf("%x,%x,%x,%x",a+1,&a+1,&a[0]+1,&a[0][0]+1);
    return 0;    //a:首行地址           a+1跨12个字节,跨一行 *a首元素地址,**a首元素的值
                //&a:整个数组的首地址    &a+1跨整个数组
                //&a[0]:首行地址      &a[0]+1跨12个字节,跨一行
           //&a[0][0]:数组首元素地址  &a[0][0]+1 跨一个元素大小   
}

int ( *p )[ 3 ]: 数组指针(二级):一个这样的指针,它指向一个带有三个int数据的数组的地址 控制列数 p+1跨一行数组,12字节

int* p[n]:指针数组(二级),这个数组 有n个一级指针,每个指针控制一个一维数组,控制行数 p+1 跨一个指针,8字节

#include<stdio.h>

int main()
{
    int a[2][3]={{1,20,3},{4,5,6}};//两行三列
    int b[2][3]={1,2,3,4,5,6};
    
    printf("%d\n",*(*a+1));   //a[0][1]  20
    printf("%d\n",*((*(a+1))+1));//a[1][1]   5
                            //a是二级指针,*a是变成一行的一维数组

    int c[2][3]={1,2,3,4,5,6};
    int* p=c;  //此时c是首行地址,等于是把c从二维数组降维,c换成&c结果一样
    printf("%d\n",*p);  //输出:1

                            
    int (*p)[3]=a;      //p是二级指针,此时p=a  p++跨一行,三个数
    printf("%d\n",*p); //*p就是取第一行数组的首元素的地址 
     return 0;        //**p是取值第一行数组的首元素的值
                     //*(*p+1)是20     *(*(p+1)+1)第二行第二个元素取值
                     
    int* ptr[2]={a[0],a[1]};  //ptr指向这个数组的地址,p[0]=a[0];
    printf("%d\n",*ptr[1])); //输出:4,二维数组第二行首元素                
}    //*ptr[0]+1是第一行第一个元素值加一,*(ptr[0]+1)是第一行第二个元素
#include<stdio.h>

int main()
{
   int a[5][5];
   int (*p)[4];   //p++一步跨16字节  也就是这是指向一个一行四个元素的int数组
   p=a;            //一般a[4][2]第五行第三个元素,第23个,p=a说明p是五行四列
   printf("%d %d\n",&p[4][2],&a[4][2]);//p[4][2]是五行第三个元素,第19个 
    return 0;        //结果:2054741992 2054742008 所以地址差16字节
}                        //如果输出&p[4][2]-&a[4][2]却得-4        
                        //是地址减地址(无符号数)减出-4   
    int a[2][3]={1,2,3,4,5,6};
    int* p[3];        //指针数组
    *(p+1)=a[1];     //把第二行数组给到指针数组的第二个数组*(p+1)
                        //此时p[1]==a[1](这俩单独看是一维)
                     //p+1是第二个数组的地址,*(p+1)取出这个数组
    printf("%d",*p[1]);         输出:4      
#include<stdio.h>
main()
{
    char* s[10];
    s[0]="suqian";
    s[1]="jiaoyu";
    printf("%c\n",*s[0]);      //s
    printf("%c\n",*(s[0]+1));  //u
    printf("%c\n",*s[0]+1);    //t   s+1(ascll码)
    printf("%c\n",*(s[0]+6));  //\0
    printf("%c\n",*(s[0]+7));  //j  数组的地址是连续的
    printf("%c\n",**(&s[0]+1));//j  
          //&s[0]就是一个指向s[0]的指针(二级指针)&s[0]+1就是第二个数组

}

6.二级指针

#include<stdio.h>
int main()
{
    int a=10;
    int* ptr=&a;
    int** ptr2=&ptr;      //二级指针
    printf("%d ",**ptr2);
    
    return 0;

}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值