指针的进阶
首先回顾一下基本概念:
1.指针是一个变量,用来存放地址,地址唯一标识一块内存空间。
2.指针的大小是固定的4/8个字节(32位平台/64平台)。
3.指针具有不同的类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。
4.指针的运算
字符指针
一般用法:
int main()
{
char ch = 'w';
char* pc = &ch;
return 0;
}
int main()
{
char arr[] = "abcdef";
char* pc = arr;
printf("%s\n", arr);
printf("%s\n", pc);
return 0;
}
int main()
{
const char* p = "abcdef";//"abcdef"是一个常量字符串
printf("%c\n", *p);
printf("%s\n", p);
return 0;
}
下段代码的输出结果是什么:
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcdef";
const char* p1 = "abcdef";
const char* p2 = "abcdef";
if (arr1 == arr2)
{
printf("hehe\n");
}
else
{
printf("haha\n");
}
return 0;
}
答案:haha
arr1 与arr2储存的地址不相同
这段代码的输出值又是什么呢?
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcdef";
const char* p1 = "abcdef";
const char* p2 = "abcdef";
if (p1 == p2)
{
printf("hehe\n");
}
else
{
printf("haha\n");
}
return 0;
}
答案:hehe
常量字符串不可以被修改,p1 p2都指向同一空间(字符串一样,存同样的地址。)
当几个指针指向同一个字符串的时候,它们实际会指向同一块内存,但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。
指针数组
指针数组是数组,用来存放指针
int main()
{
int arr[10] = { 0 };//整型数组
char ch[5] = { 0 };//字符数组
int* parr[4];//存放整型指针的数组 - 指针数组
char* pch[5];//存放字符指针的数组 - 指针数组
return 0;
}
应用:
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++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("%d", *(parr[i] + j));
}
printf("\n");
}
return 0;
}
数组指针
int main()
{
int* p = NULL;//p是整型指针 - 指向整型的指针 - 可以存放整型的地址
char* pc = NULL;//pc是字符指针 - 指向字符的指针 - 可以存放字符的地址
//数组指针 - 指向数组的指针 - 存放数组的地址
int arr[10] = { 0 };
//arr - 首元素地址
//&arr[0] - 首元素地址
//&arr - 数组的地址
return 0;
}
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int (*p)[10] = &arr;//[]的优先级大于*
//上面的p就是数组指针
练习:补出pa的类型
int main()
{
char* arr[5];
pa =&arr
return 0;
}
char*(*pa)[5] = &arr;
pa指向的数组是5个元素
*说明pa是指针
pa指向的数组的元素类型是char*
数组指针的用法
//参数是数组的形式
void print1(int arr[3][5], int x, int y)
{
int i = 0;
int j = 0;
for (i = 0; j < y; j++)
{
for (j = 0; j < y; j++)
{
printf("%d", arr[i][j]);
}
printf("\n");
}
}
//参数是指针的形式
void printf(int(*p)[5], int x, int y)
{
int i = 0;
for (i = 0; i < x; i++)
{
int j = 0;
for (j = 0; j < y; j++)
{
printf("%d", *(*(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);
print2(arr, 3, 5);
return 0;
}
int arr[5]; arr是一个5个元素的整型数组
int* parr1[10]; parr1是一个数组,数组有10个元素,每个元素的类型是int*,parr1是指针数 组
int(*parr2)[10]; parr2是一个指针,该指针指向了一个数组,数组有10个元素,每个元素的 类型是int - parr2是数组指针
int(*parr3[10])[5]; parr3是一个数组,parr3是数组名,该数组有10个元素,每个元素是一个数 组指针,该数组指针指向的数组有5个元素,每个元素是int
总结:
int main()
{
//字符指针
char ch = 'w';
char* p = &ch;
const char* p2 = "abcdef";//指针数组 - 数组 - 存放指针的数组
int* arr[10];
char* ch[5];
//数组指针 - 指向数组
//int *p3;//整型指针 - 指向整形的指针
//char* p4;//字符指针 - 指向字符的
int arr2[5];//数组
int(*pa)[5] = &arr2;//取出数组的地址,pa就是一个数组指针
return 0;
}
数组参数,指针参数
数组传参
void test(int arr[3][5])
{}
void test1(int[][5])
{}
void test2(int arr[3][])//err
int main()
{
int arr[3][5] = { 0 };
test(arr);//二维数组传参
test1(arr);
test2(arr);
return 0;
}
二维数组传参,函数形参的设计只能省略第一个[ ]的数字,因为对于一个二维数组,可以不知道有多少行,但是必须知道一行多少元素,这样才更加方便运算。
二维数组首元素地址是数组第一行的地址。
一级指针传参
void print(int* p, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%dn", *(p + i));
}
}
int main()
{
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; int* p = arr;
int* p = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
//一级指针p,传给函数
print(p, sz);
return 0;
}
但是当一个函数的参数部分为一级指针的时候,函数能接受什么参数?
void test1(int* p)
{}
int main()
{
int a = 10;
test1(&a);
test1(p1);
return 0;
}
二级指针传参
void test1(int** ptr)
{
printf("num = %d\n", **ptr);
}
int main()
{
int n = 10;
int* p = &n;
int** pp = &p;
test(pp);
test(&p);
return 0;
}
void test(int**p)
{}
int main()
{
int *ptr;
int** pp = &ptr;
test(&ptr);
test(pp);
int* arr[10];
test(arr);//指针数组也可以
return 0;
}
可以传:一级指针的地址,二级指针变量本身,存放一级指针的数组的数组名
函数指针
数组指针 - 指向数组的指针
函数指针 - 指向函数的指针- 存放函数地址的一个指针
int Add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 10;
int b = 20;
int arr[10] = { 0 };
printf("%p\n", &Add);
printf("%p\n", Add);
//&函数名 和 函数名 都是函数的地址
return 0;
}
int Add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 10;
int b = 20;
//int arr[10] = { 0 };
//int(*p)[10] = &arr;
int(*pa)(int, int) = Add;
printf("%d\n", (*pa)(2, 3));//5
return 0;
}
看下面两个代码:
代码1:
(*(void)(*)()0)();
( void(*)() )0 ----> (*( void(*)() )0) ();
把0强制类型转换成:void(*)()函数指针类型 - 0就是一个函数的地址,调用0地址该处的函数
代码2:
void(*signal(int, void(*)(int)))(int);
void(*)(int) ---->函数指针类型
void(* )(int) ----->
类比:int Add(int,int)
例:
//signal是一个函数声明
//signal函数的参数有2个,第一个是int类型,第二个是函数指针,该函数指针指向的函数的参数是int,返回类型是void
//signal函数的返回类型也是一个函数指针,该函数指针指向的函数的参数是int,返回类型是void
void (* signal(int, void(*)(int)) )(int);
//简化
typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);
typedef unsigned int uint;
函数指针数组
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];
//需要一个数组,这个数组可以存放四个函数的地址 - 函数指针的数组
int(*Pa)(int, int) = Add;//Sub/Mul/Div
int(*parr[4])(int, int)={ Add,Sub,Mul,Div};//函数指针的数组
int i = 0;
for (i = 0; i < 4; i++)
{
printf("%d\n", parr[i](2, 3));//5 -1 6 0
}
return 0;
}
例:
my_strcpy(char* dest, const char* src);
//写一个函数指针pf,能够指向my_strcpy
//写一个函数指针数组pfarr,能够存放4个my_strcpy函数的地址
char* (*pf)(char*, const char*);
char* (*pfarr[4])(char*, const char*);
函数指针数组的用途:转移表
例:计算器:
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 = 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("退出\n");
default:
printf("选择错误\n");
break;
}
}while(input);
return 0;
}
改造:运用函数指针数组
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 = 0;
int x = 0;
int y = 0;
int (*pfArr[5])(int, int) = { 0,Add,Sub,Mul,Div };
do
{
menu();
printf("请选择:>");
scanf("%d",&input);
if (input >= 1 && input <= 4)
{
printf("请输入两个操作数:>");
scanf("%d%d", &x, &y);
int ret = pfArr[input](x, y);
printf("%d\n", ret);
}
else if (input == 0)
{
printf("退出\n");
}
else
{
printf("选择错误\n");
}
}while(input);
return 0;
}
pfArr 是一个函数指针数组
指向函数指针数组的指针
int main()
{
int arr[10] = { 0 };
int(*P)[10] = &arr;//取出数组的地址
int(*pf)(int,int);//函数指针
int(*pfArr[4])(int, int);//pfArr是一个数组 - 函数指针的数组
//ppfArr是一个指向[函数指针数组]的指针
int(*(*ppfArr)[4])(int, int) = &pfArr;
//ppfArr是一个数组指针,指针指向的数组有4个元素
//指向的数组的每个元素的类型是一个函数指针int(*)(int,int)
return 0;
}
总结:
int Add(int x, int y)
{
return x + y;
}
int main()
{
//指针数组
//int* arr[10];
//数组指针
//int*(*pa)[10] = &arr;
//函数指针
int (*pAdd)(int, int) = Add;//&Add
//int sum =(*pAdd)(1,2);
//int sum =*pAdd(1,2);
//printf("sum = %d\n",sum);
//函数指针的数组
int(*pArr[5])(int, int);
//指向函数指针数组的指针
int(* (*pArr)[5])(int, int) = &pArr;
return 0;
}
回调函数
回调函数是一个通过函数指针调用的函数。如果把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就称其为回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或发生的条件时由另外一方调用的,用于对该事件或条件进行响应。
举例:
void print(char *str)
{
printf("hehe:%s", str);
}
void test(void(*p)(char*))
{
printf("test\n");
p("bit");
}
int main()
{
test(print);
return 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("退出\n");
default:
printf("选择错误\n");
break;
}
这段代码中
printf("请输入两个操作数:>");
scanf("%d%d", &x, &y);
出现了多次,如何运用回调函数简化代码呢?
void Calc(int(*pf)(int,int))
{
int x = 0;
int y = 0;
printf("请输入两个操作数:>");
scanf("%d%d", &x, &y);
printf("%d\n", pf(x, y));
}
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("退出\n");
default:
printf("选择错误\n");
break;
补充:
int main()
{
int a = 10;
int* pa = &a;
void* p = &a;//void* 类型的指针,可以接受任意类型的地址
// void* 类型的指针不可以进行解引用操作
//void*类型的指针不可以进行+—整数的操作
return 0;
}
qsort - 库函数 - 排序
快速排序
第一个参数:待排序数组的首元素的地址
第二个参数:待排序数组的元素个数
第三个参数: 待排序的数组的每个元素的大小 - 单位是字节
第四个参数:是函数指针,比较两个函数所用函数的地址 - 这个函数使用者自己实现
函数指针的两个参数是:待比较的两个元素的地址