目录
字符指针
指针数组
数组指针
数组指针的使用
数组参数和指针参数
一维数组传参
二维数组传参
函数指针
函数地址的存储
函数指针的应用编辑
代码分析
函数指针的用途
函数指针数组
指向函数指针数组的指针
回调函数
用回调函数实现冒泡排序
void*指针
自己设计qsort函数实现冒泡排序
这里abcdef\0占7个字节,而char*类型只有4个字节,p里面存的是首元素地址,而不是所有元素,通过首元素地址就能找到后面字符的地址然后进行打印,遇到\0停止打印,千万不要理解为这里p存的是整个字符串
在有些编译器上上面的程序会正常运行,但是会报警告,这是因为"abcdef"是常量字符串,常量字符串意思是本身不能被修改
当我们对p里面的内容进行修改,然后进行调试,我们发现这里会出现错误,这是因为"abcdef"是常量字符串,不能被修改
为防止我们写代码时出现错误,我们用const修饰就行
当我们用char类型去存储字符串,然后打印也会报错,这是因为这里的char时字符类型,而后面是字符串,所以会出错,若想打印用char[]数组就行
p1=p2原因:
p1里面放的是a的地址,p2里面放的也是a的地址,p1=p2说明p1和p2指向同一个地址,由于abcdef\0是常量字符串,它只能读不能写(也就是不能被改),由于它只能读不能写的特性,所以在电脑内存里只存了一份"abcdef",所以p1和p2指向了同一个地方
arr1!=arr2原因:
这里创建了俩个独立的数组arr1和arr2,里面分别放了"abcdef",也就是说内存里面有俩个"abcdef",一个存在arr1里面,另一个存在arr2里面,arr1和arr2都是首元素地址,因为是俩个不同的空间,所以arr1和arr2不相等
指针数组:里面存放的全是其它数组首元素的地址
p数组里面元素类型都是int *
p[i]=*(p+i)
用解引用和二维数组的方式去打印,此时会报错
这是因为*p是int *类型,而此时里面的数字是int类型,所以不能用解引用的方式打印二维数组 *(p+i)=p[i]
数组指针:存放整个数组地址的指针
int *p1[10]; //指针数组,p1和[]先结合
int (*p2)[10];//数组指针,p2和*先结合,p2指向[10],int说明数组类型是intp2是数组指针,p2可以指向一个数组,该数组有10个元素,每个元素的类型是int类型
数组名通常表示首元素地址,但是有俩个例外,
sizeof(数组名):计算的是整个数组的大小,注意括号是只能是数组名,不能出现其它任何的字符
&数组名:这里的数组名是整个数组,所以&数组名取出的是整个数组的地址
&数组名+1表示跳过整个数组大小
a[0]+1表示跳过一个元素所占字节大小
总结:数组以各种方式在+1时候表示跳过其所占类型的大小
数组指针就是用来存放一个数组的地址的 ,这颗*就是告诉这里是指针而不是解引用,p2的类型是int (*)[10],数组指针跟二级指针没任何关系,二级指针是用来存放一级指针的地址的,数组指针[]里面的大小不能忽略不写
注意看,下面有警告
这里p指向整个数组,*p就是数组名,也就是说*p就是首元素地址,如果要打印里面的值,对其解引用就行 ,数组指针一般不在一维数组使用,这里只不过是便于让人更加理解指针数组
二维数组的数组名即首元素的地址表示第一行的地址,所以这里用数组指针进行接收
*(p+i)表示拿到了第n行数组名,j表示某行第j个元素的地址,再解引用就行
*(p+i)可以写成p[i],*(*(p+i)+j)=*(p[i]+j)=p[i][j]
p的类型int(*)[5],*(p+i)一次性跳过类型所占大小,所以一次跳过一行,一次跳过5个int字节
总结:二维数组传参,传的首元素地址是第一行地址,第一行有一堆数组,所以数组指针进行接收,一维数组传参,传的是第一个元素地址,第一个元素只有一个,所以用int *p接收即可,即传过来一堆数组地址的时候用数组指针可以接收,传过来一个就用其它方式(但数组指针仍然可以接收)
传一堆数组不能用int *p来接收,因为这里会出警告,实参 形参类型不同
parr3先和[]结合,再和*结合,把parr 3[10]拿出来剩int(*)[5],这是一个数组指针类型,parr3是一个存放数组指针的数组就是parr3[10]的类型是int(*)[5]
数组指针数组:数组里的每一个元素是其余数组的所有地址,也就是一个数组里面全是数组指针
这里的二级指针也可以,因为arr2是指针数组,指针数组里面村的全是数组首元素地址,都是int *类型,而int *是一级指针,一级指针的地址存放在二级指针中
最后一个是二级指针,而我们穿的二维数组的首元素也就是一行,也就是一维数组,一维数组不能放到二级指针中去,二级指针只是来存放一级指针地址的指针
#include
void print(int *p, int sz) { int i = 0; for(i=0; i
一级指针传参,用一级指针接收
二级指针传参,用二级指针接收,
指向函数的指针
函数名和&函数名打印出来的结果一模一样 ,二者拿到的都是函数的地址,没什么不一样的
设置一个指针指向函数,前面是函数的返回值类型,括号里是参数类型 ,()里面参数的名字写不写都无所谓,说清楚类型就可以
通过指针找到函数,就可以进行使用了
也可以不写&因为&函数和函数名都是函数的地址,没什么区别
//代码1
( *( void (*)() ) 0 )();
分析:void(*)()这是一个函数指针类型
( void(*)())0 这是对0进行强制类型转换,把0由int转换为函数指针类型,此时0不再是平常所理解的0,而0变为了一个地址,此时0地址中放了一个函数,只不过函数没有参数,返回类型为void
(*( void(*)())0)()
这里相当于(*(函数指针))(),对函数指针解引用,也就是调用这个函数,先解引用找到这个函数,由于0地址的函是没有参数的,所以在调用的时候没有传参
//代码2
void (*signal( int , void (*)(int) ) ) (int);
简化上面的代码
这里写一个计算器,实现加减乘除
#include
#include
void menu()
{
printf("*****************************\n");
printf("**** 1. add 2. sub *****\n");
printf("**** 3. mul 4. div *****\n");
printf("**** 0. exit *****\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;
int x = 0;
int y = 0;
int ret = 0;
do
{
menu();
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入俩个数:");
scanf("%d %d", &x, &y);
ret = Add(x, y);
printf("%d\n", ret);
break;
case 2:
printf("请输入俩个数:");
scanf("%d %d", &x, &y);
ret =Sub(x, y);
printf("%d\n", ret);
break;
case 3:
printf("请输入俩个数:");
scanf("%d %d", &x, &y);
ret = Mul(x, y);
printf("%d\n", ret);
break;
case 4:
printf("请输入俩个数:");
scanf("%d %d", &x, &y);
ret = Div(x, y);
printf("%d\n", ret);
break;
case 0:
printf("退出\n");
break;
default:printf("重新输入");
}
} while (input);
return 0;
}
这里很多地方的代码有些相似
我们用函数指针来对该程序进行优化,
把函数指针放在数组中,一个数组中全是函数指针
#include
#include
void menu()
{
printf("*****************************\n");
printf("**** 1. add 2. sub *****\n");
printf("**** 3. mul 4. div *****\n");
printf("**** 0. exit *****\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;
do{
menu();
printf("请选择:");
scanf("%d", &input);
int x = 0;
int y = 0;
int (*pfArr[5])(int, int) = { 0,Add,Sub,Mul,Div };
if (input == 0)
printf("退出\n");
else if (input >= 1 && input <= 4)
{
printf("请输入俩个数字:");
scanf("%d %d", &x, &y);
int ret = pfArr[input](x,y);
printf("%d\n", ret);
}
else
printf("请重新输入");
} while (input);
return 0;
}
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个 函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数 的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。前面那个计算器就是回调函数
不同类型在排序的时候,俩元素的比较方法不一样,我们可用qsort函数进行排序,qsort可以排序任意类型的数据
#include
#include
int cmp_int(const void* e1, const void* e2)//
{
return (*(int*)e1 - *(int*)e2);//强制类型转换之后,在进行解引用
}
int main()
{
int arr[] = {9,8,7,6,5,4,3,2,1,0};
//0 1 2 3 4 5 6 7 8 9
//把数组排成升序
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
若要排降序,只需将cmp_int函数里面的e1和e2互换,qsort默认排升序
#include
#include
void Swap(char* buf1, char* buf2, int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
int cmp_int(const void*e1,const void *e2)
{
return (*(int*)e1 - *(int*)e2);
}
void bubble_sort(void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2))
{
int i = 0;
//趟数
for (i = 0; i < sz - 1; i++)
{
int flag = 1;//假设数组是排好序
//一趟冒泡排序的过程
int j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
if (cmp((char*)base + width * j, (char*)base + width * (j + 1))>0)
{
Swap((char*)base + width * j, (char*)base + width * (j + 1),width);
flag = 0;
}
}
if (flag == 1)
{
break;
}
}
}
void test1()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0};
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]),cmp_int );
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
int main()
{
test1();
return 0;
}