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;
}