前言:
指针可以说是c语言的灵魂所在了。之所以这样认为,是因为它可以直接操作内存,而当我们写程序时,内存的报错有时候直接导致程序的崩溃,这时就需要我们一步步去检查我们的代码。但同时也为我们提供了很大的便利,比如数组或者结构体变量传参时,就不需要我们为它再开辟一块同样大小的内存块,只需要将它的首地址传过去,就可以访问它所有的数据。那么可以说它是把双刃剑,我们如何使用它就显得尤为重要了,接下来我会详细的介绍它。
目录
字符指针
为什么是这样存呢?
字符指针常量
为什么存在这样的现象呢?
指针数组
数组指针
梨为什么是这个样子呢?
梨数组指针写法的解读
梨数组指针的使用
梨解读
函数指针
⚽函数地址
⚽函数指针写法解读
⚽函数指针调用函数
函数指针数组
⚽函数指针数组写法解读
指向函数指针数组的指针
殺指向函数指针数组的指针写法解读
回调函数
利用回调函数,改版冒泡排序,排序任何类型数据
核心思想
注意
代码实现
小结
字符指针
#include
int main()
{
const char* ptr = "hello word";
printf("%s", ptr);
return 0;
}
我们可以清楚地看到ptr指向的是首元素的地址 。
为什么是这样存呢?
首先ptr是一个char*类型的指针,如果认为是把“hello word”存进去显然也存不下,因为这块内存在申请时就是连续的内存块,所以我们只需要拿到它的首元素地址,就可以访问它整块内存了。
字符指针常量:
int main()
{
char str1[] = "hello word";
char str2[] = "hello word";
const char* ptr1 = "hello word";
const char* prt2 = "hello word";
return 0;
}
首先ptr是一个char*类型的指针,如果认为是把“hello word”存进去显然也存不下,因为这块内存在申请时就是连续的内存块,所以我们只需要拿到它的首元素地址,就可以访问它整块内存了。
int main()
{
char str1[] = "hello word";
char str2[] = "hello word";
const char* ptr1 = "hello word";
const char* prt2 = "hello word";
return 0;
}
我们可以清楚看到str1和str2的首地址是不一样的,而ptr1和ptr2的首地址又是一样的。
为什么存在这样的现象呢?
str1和str2是向内存申请的不同的两个字符数组,那么不同的数组肯定在不同的内存块中存放,这和它的数组内容是没有关系的。
ptr1和ptr2是const修饰的字符指针常量,它们指向的是常量字符串,而c/c++把常量字符串单独的存储在内存的一块区域,当用几个指针指向同一个常量字符串,它们指向的都是同一块内存空间。它们是常量字符串,是不可以修改的,意味着我们只可以读数据,所以我们只需要为它开辟一块内存空间即可。
指针数组
int main()
{
int* arr[10] = { NULL };
char* arr[10] = { NULL };
return 0;
}
str1和str2是向内存申请的不同的两个字符数组,那么不同的数组肯定在不同的内存块中存放,这和它的数组内容是没有关系的。
ptr1和ptr2是const修饰的字符指针常量,它们指向的是常量字符串,而c/c++把常量字符串单独的存储在内存的一块区域,当用几个指针指向同一个常量字符串,它们指向的都是同一块内存空间。它们是常量字符串,是不可以修改的,意味着我们只可以读数据,所以我们只需要为它开辟一块内存空间即可。
int main()
{
int* arr[10] = { NULL };
char* arr[10] = { NULL };
return 0;
}
顾名思义,它强调的是数组,数组中存放的是指针,int *arr[10]存放的就是10个整形指针,char* arr[10]存放的就是10个字符指针。
数组指针
顾名思义,它强调的是一个指针,存放的是整个数组的地址。
int main()
{
int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
int(*pa)[10] = &arr;
return 0;
}
可以清楚看见pa + 1跳过了40个字节,arr + 1跳过了4个字节。
梨为什么是这个样子呢?
首先&arr和arr看上去一样的,但实际指向是不同的,arr指向的是数组的首元素的地址,&arr指向的是整个数组的地址。它只不过是用首元素地址来表示,所以它们的步长是不一致的。pa的步长是40个字节,arr的步长是4个字节。
梨数组指针写法的解读
int(*pa)[10],pa是这个数组指针变量的指针名,它的类型是int(*)[10],指向的是10个整形的数组。
梨数组指针的使用
#include
void print(int(*pa)[3], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf("%d ", pa[i][j]);
}
printf("n");
}
}
int main()
{
int arr[2][3] = {0,1,2,3,4,5};
print(arr, 2, 3);
return 0;
}
梨解读:
二维数组的传参,二维数组的数组名是首元素地址,即整个第一行数组的地址,所以传参时就需要数组指针取接收。
函数指针
#include
void fun()
{
printf("hello wordn");
}
int main()
{
fun();
printf("%pn", fun);
printf("%pn", &fun);
void(*pf)() = fun;
(*pf)();
return 0;
}
⚽函数地址
可以清楚看见,fun和&fun是一样的,即函数的地址可以通过这两种方法得到。函数地址要想存起来,就得使用函数指针void(*pf)()存。
⚽函数指针写法解读
void(*pf)(),pf指向函数的指针名,类型是void(*)(),指向的函数返会空,函数没有参数。
⚽函数指针调用函数:
(*pf)(),对函数指针pf解引用找到fun函数,然后没有参数传递。
函数指针数组
#include
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(*pfun[4])(int x, int y) = { Add,Sub,Mul,Div };
//利用函数指针数组调用Add函数
int sum = (*pfun[0])(2, 3);
printf("%d", sum);
return 0;
}
⚽函数指针数组写法解读
int(*pfun[4])(int x, int y),pfun先和[4]结合说明它是一个数组,int(*)(intx, int y)是pfun数组数据的类型,是一个函数指针,函数的返回类型是int,参数是(int x, int y)。
指向函数指针数组的指针
#include
int sum(int x, int y)
{
return x + y;
}
int main()
{
int ret = sum(2, 3);
int(*pfun_arr[3])(int x, int y) = { sum };
int(*(*ppfun_arr)[3])(int x, int y) = &pfun_arr;
printf("%d", ret);
return 0;
}
首先&arr和arr看上去一样的,但实际指向是不同的,arr指向的是数组的首元素的地址,&arr指向的是整个数组的地址。它只不过是用首元素地址来表示,所以它们的步长是不一致的。pa的步长是40个字节,arr的步长是4个字节。
int(*pa)[10],pa是这个数组指针变量的指针名,它的类型是int(*)[10],指向的是10个整形的数组。
梨数组指针的使用
#include
void print(int(*pa)[3], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf("%d ", pa[i][j]);
}
printf("n");
}
}
int main()
{
int arr[2][3] = {0,1,2,3,4,5};
print(arr, 2, 3);
return 0;
}
梨解读:
二维数组的传参,二维数组的数组名是首元素地址,即整个第一行数组的地址,所以传参时就需要数组指针取接收。
函数指针
#include
void fun()
{
printf("hello wordn");
}
int main()
{
fun();
printf("%pn", fun);
printf("%pn", &fun);
void(*pf)() = fun;
(*pf)();
return 0;
}
⚽函数地址
可以清楚看见,fun和&fun是一样的,即函数的地址可以通过这两种方法得到。函数地址要想存起来,就得使用函数指针void(*pf)()存。
⚽函数指针写法解读
void(*pf)(),pf指向函数的指针名,类型是void(*)(),指向的函数返会空,函数没有参数。
⚽函数指针调用函数:
(*pf)(),对函数指针pf解引用找到fun函数,然后没有参数传递。
函数指针数组
#include
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(*pfun[4])(int x, int y) = { Add,Sub,Mul,Div };
//利用函数指针数组调用Add函数
int sum = (*pfun[0])(2, 3);
printf("%d", sum);
return 0;
}
⚽函数指针数组写法解读
int(*pfun[4])(int x, int y),pfun先和[4]结合说明它是一个数组,int(*)(intx, int y)是pfun数组数据的类型,是一个函数指针,函数的返回类型是int,参数是(int x, int y)。
指向函数指针数组的指针
#include
int sum(int x, int y)
{
return x + y;
}
int main()
{
int ret = sum(2, 3);
int(*pfun_arr[3])(int x, int y) = { sum };
int(*(*ppfun_arr)[3])(int x, int y) = &pfun_arr;
printf("%d", ret);
return 0;
}
二维数组的传参,二维数组的数组名是首元素地址,即整个第一行数组的地址,所以传参时就需要数组指针取接收。
函数指针
#include
void fun()
{
printf("hello wordn");
}
int main()
{
fun();
printf("%pn", fun);
printf("%pn", &fun);
void(*pf)() = fun;
(*pf)();
return 0;
}
⚽函数地址
可以清楚看见,fun和&fun是一样的,即函数的地址可以通过这两种方法得到。函数地址要想存起来,就得使用函数指针void(*pf)()存。
⚽函数指针写法解读
void(*pf)(),pf指向函数的指针名,类型是void(*)(),指向的函数返会空,函数没有参数。
⚽函数指针调用函数:
(*pf)(),对函数指针pf解引用找到fun函数,然后没有参数传递。
函数指针数组
#include
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(*pfun[4])(int x, int y) = { Add,Sub,Mul,Div };
//利用函数指针数组调用Add函数
int sum = (*pfun[0])(2, 3);
printf("%d", sum);
return 0;
}
⚽函数指针数组写法解读
int(*pfun[4])(int x, int y),pfun先和[4]结合说明它是一个数组,int(*)(intx, int y)是pfun数组数据的类型,是一个函数指针,函数的返回类型是int,参数是(int x, int y)。
指向函数指针数组的指针
#include
int sum(int x, int y)
{
return x + y;
}
int main()
{
int ret = sum(2, 3);
int(*pfun_arr[3])(int x, int y) = { sum };
int(*(*ppfun_arr)[3])(int x, int y) = &pfun_arr;
printf("%d", ret);
return 0;
}
⚽函数地址
可以清楚看见,fun和&fun是一样的,即函数的地址可以通过这两种方法得到。函数地址要想存起来,就得使用函数指针void(*pf)()存。
⚽函数指针写法解读
void(*pf)(),pf指向函数的指针名,类型是void(*)(),指向的函数返会空,函数没有参数。
⚽函数指针调用函数:
(*pf)(),对函数指针pf解引用找到fun函数,然后没有参数传递。
函数指针数组
#include
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(*pfun[4])(int x, int y) = { Add,Sub,Mul,Div };
//利用函数指针数组调用Add函数
int sum = (*pfun[0])(2, 3);
printf("%d", sum);
return 0;
}
⚽函数指针数组写法解读
int(*pfun[4])(int x, int y),pfun先和[4]结合说明它是一个数组,int(*)(intx, int y)是pfun数组数据的类型,是一个函数指针,函数的返回类型是int,参数是(int x, int y)。
指向函数指针数组的指针
#include
int sum(int x, int y)
{
return x + y;
}
int main()
{
int ret = sum(2, 3);
int(*pfun_arr[3])(int x, int y) = { sum };
int(*(*ppfun_arr)[3])(int x, int y) = &pfun_arr;
printf("%d", ret);
return 0;
}
可以清楚看见,fun和&fun是一样的,即函数的地址可以通过这两种方法得到。函数地址要想存起来,就得使用函数指针void(*pf)()存。
void(*pf)(),pf指向函数的指针名,类型是void(*)(),指向的函数返会空,函数没有参数。
⚽函数指针调用函数:
(*pf)(),对函数指针pf解引用找到fun函数,然后没有参数传递。
函数指针数组
#include
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(*pfun[4])(int x, int y) = { Add,Sub,Mul,Div };
//利用函数指针数组调用Add函数
int sum = (*pfun[0])(2, 3);
printf("%d", sum);
return 0;
}
⚽函数指针数组写法解读
int(*pfun[4])(int x, int y),pfun先和[4]结合说明它是一个数组,int(*)(intx, int y)是pfun数组数据的类型,是一个函数指针,函数的返回类型是int,参数是(int x, int y)。
指向函数指针数组的指针
#include
int sum(int x, int y)
{
return x + y;
}
int main()
{
int ret = sum(2, 3);
int(*pfun_arr[3])(int x, int y) = { sum };
int(*(*ppfun_arr)[3])(int x, int y) = &pfun_arr;
printf("%d", ret);
return 0;
}
(*pf)(),对函数指针pf解引用找到fun函数,然后没有参数传递。
#includeint 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(*pfun[4])(int x, int y) = { Add,Sub,Mul,Div }; //利用函数指针数组调用Add函数 int sum = (*pfun[0])(2, 3); printf("%d", sum); return 0; }
⚽函数指针数组写法解读
int(*pfun[4])(int x, int y),pfun先和[4]结合说明它是一个数组,int(*)(intx, int y)是pfun数组数据的类型,是一个函数指针,函数的返回类型是int,参数是(int x, int y)。
指向函数指针数组的指针
#include
int sum(int x, int y)
{
return x + y;
}
int main()
{
int ret = sum(2, 3);
int(*pfun_arr[3])(int x, int y) = { sum };
int(*(*ppfun_arr)[3])(int x, int y) = &pfun_arr;
printf("%d", ret);
return 0;
}
int(*pfun[4])(int x, int y),pfun先和[4]结合说明它是一个数组,int(*)(intx, int y)是pfun数组数据的类型,是一个函数指针,函数的返回类型是int,参数是(int x, int y)。
#includeint sum(int x, int y) { return x + y; } int main() { int ret = sum(2, 3); int(*pfun_arr[3])(int x, int y) = { sum }; int(*(*ppfun_arr)[3])(int x, int y) = &pfun_arr; printf("%d", ret); return 0; }
顾名思义,它强调的是一个指针,指向的是函数指针数组。
殺指向函数指针数组的指针写法解读:
这个要对比去理解,比如数组指针:int(*pa)[5],pa先和*结合说明它是一个指针,它的类型是int(*)[5],指向的是5个整形的数组。可以看成就是在数组名前加一个 *,然后让它两先结合,就可以说这个指针是指向这个数组了。
int(*pfun_arr[3])(int x, int y),这个是函数指针数组,数组名是pfun_arr,由于数组名前已经有一个 *,那么我们把(*ppfun_arr)括起来让它先和 * 结合,int(*(*ppfun_arr)[3])(int x, int y)就可以说它是指向函数指针数组的指针。
回调函数
这个要对比去理解,比如数组指针:int(*pa)[5],pa先和*结合说明它是一个指针,它的类型是int(*)[5],指向的是5个整形的数组。可以看成就是在数组名前加一个 *,然后让它两先结合,就可以说这个指针是指向这个数组了。
int(*pfun_arr[3])(int x, int y),这个是函数指针数组,数组名是pfun_arr,由于数组名前已经有一个 *,那么我们把(*ppfun_arr)括起来让它先和 * 结合,int(*(*ppfun_arr)[3])(int x, int y)就可以说它是指向函数指针数组的指针。
回调函数就是一个通过函数指针调用的函数。如果你把函数的地址作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
利用回调函数,改版冒泡排序,排序任何类型数据
核心思想:
核心就是不同的数据类型比较方法不同,那么我们可以把比较那块抽离出来,写成一个函数。由于排序数据的类型我们自己清楚,所以只需要实现这个比较函数即可,然后把这个函数传入冒泡排序中比较那块即可。
注意:
核心就是不同的数据类型比较方法不同,那么我们可以把比较那块抽离出来,写成一个函数。由于排序数据的类型我们自己清楚,所以只需要实现这个比较函数即可,然后把这个函数传入冒泡排序中比较那块即可。
注意:
这里使用了void*指针,它可以强转成任何类型的指针,为的就是不同类型的比较。需要把每个数据的首地址传入比较函数,然后强转成需要比较的数据类型,进行每个数据的比较 。
不同数据的交换,也是不一样的,所以我们把数据以字节为单位进行交换。在传入交换函数时,需要把每个数据的首字节地址传入过去,还需知道每个数据的宽度(字节数),进行每个数据的交换。
代码实现:
#include
typedef struct stu
{
char name[20];
int age;
char six[4];
}stu;
int cmp(const void* e1, const void* e2)
{
return (*(int*)e1 - *(int*)e2);
}
int cmp2(const void* e1, const void* e2)
{
return ((stu*)e1)->age - ((stu*)e2)->age;
}
void swap(char* a, char* b, int width)
{
//元素以字节为单位交换
while (width--)
{
char tmp = *a;
*a = *b;
*b = tmp;
a++;
b++;
}
}
void bubble_sort(void* arr, int sz, int width, int (*cmp)(const void* e1, const void* e2))
{
int i = 0;
for (i = 0; i < sz - 1; i++)
{
int j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
if (cmp((char*)arr + j * width, (char*)arr + (j + 1) * width) > 0)
{
//保证以元素来交换,传过去每个元素的首地址,width控制元素宽度
swap((char*)arr + j * width, (char*)arr + (j + 1) * width, width);
}
}
}
}
int main()
{
int arr[] = { 10,9,8,7,6,5,4,3,2,1 };
int sz = sizeof(arr) / sizeof(arr[0]);
stu arr2[] = { {"aa",20,"男"},{"bb",18,"女"},{"cc",8,"男"} , {"dd",2,"无"} };
int sz2 = sizeof(arr2) / sizeof(arr2[0]);
bubble_sort(arr, sz, sizeof(int), cmp);
bubble_sort(arr2, sz2, sizeof(arr2[0]), cmp2);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
小结:
指针这块理解和使用是相当重要的,我们在学习过程中难免出现内存的错误等其他类型的bug,所以我们需要大量的去写代码,发现bug,并且改正bug,相信坚持下去会有不一样的收获。



