字符指针
指向字符的指针称为字符指针,用char*表示。一般使用如下表示方法
int main() { char ch = 'w'; char* pc = &ch; *pc = 'w'; return 0; }
这里的pc就是一个指向ch的字符指针,还有另一种表示方法:
int main() { const char* pstr = "hello world!"; printf("%s\n", pstr); return 0; }
注意:这里的pstr的类型是const char*, 字符指针是不能指向字符串的,这里的pstr指向的是常量字符串hello world!的第一个字符h。
例:
#include<stdio.h> int main() { const char* p1 = "abcdef"; const char* p2 = "abcdef"; char str1[] = "abcdef"; char str2[] = "abcdef"; if(p1 == p2) printf("p1==p2\n"); else printf("p1!=p2\n"); if(p3 == p4) printf("str1==str2\n"); else printf("str1!=str2\n"); return 0; }
问最终输出的结果是什么?
由于p1,p2指向的是常量字符串abcdef的首字符地址,因此p1,p2的内存放的地址是相同的,所以第一个输出p1==p2,str1和str2是在栈区分别开辟两块空间存放两个相同的字符串,因此str1和str2是两个不同的地址,所以输出str1!=str2。
指针数组
存放字符的数组叫做字符数组,存放整型的数组叫做整型数组,以此类推,那么指针数组就存放指针的数组,所以指针数组是数组,这和后面的数组指针要区分,因为数组的创建公式是:
数组类型 变量名[元素个数] = { 初始化内容 };
因此创建指针数组只要把数组类型改一下就可以。例:
char* p1[5] = {0}; int* p2[5] = {0}; float* p2[5] = {0};
数组指针
整型有整形指针,字符型有字符指针,那么指向数组的指针也是必要存在的,因此,C语言引入了数组指针的概念。
int main() { int arr[10] = {0}; int (*p)[10] = &arr; return 0; }
用()让p先与*结合,表示这是一个指针,[10]表示这是一个数组,int表示这个指针指向的数组内的元素是int类型。
我们知道数组名表示的是首元素地址,那么就会有一个问题,上面的代码中&arr取到的地址不就应该是地址的地址,也就是二级指针了,但是事实不是这样
除以下两种情况外,数组名均表示首元素地址:
1.sizeof(数组名),此时表示的是整个数组,计算的结果是整个数组的大小,单位是字节
2.&数组名,此时表示的是整个数组,取出的是整个数组的地址
数组指针的使用
现在我们引入了数组指针的概念,那么数组指针要怎么用呢?下面是一个例子:
#include <stdio.h> void print_arr1(int arr[3][5], int row, int col) { int i = 0; for(i=0; i<row; i++) { for(j=0; j<col; j++) { printf("%d ", arr[i][j]); } printf("\n"); } } void print_arr2(int (*arr)[5], int row, int col) { int i = 0; for(i=0; i<row; i++) { for(j=0; j<col; j++) { printf("%d ", arr[i][j]); } printf("\n"); } int main() { int arr[3][5] = {1,2,3,4,5,6,7,8,9,10}; print_arr1(arr, 3, 5); print_arr2(arr, 3, 5); return 0 }
如果我们要对二维数组进行传参,就有上面两种方式,print_arr1的方式就是用一个二维数组接收,这种方式显然是中规中矩的,没有问题。但是,第二种方式,我们传过去的参数是二维数组的数组名,这里的数组名表示首元素地址,二维数组的首元素是一个一维数组的地址,所以,我们在设计print_arr2的参数时,我们应该用一个数组指针来接收,这就是数组指针的一种使用方式。
数组传参、指针传参
上面我们说到了二维数组传参的一种方式,那么现在,我们来总结以下数组和指针应该怎么传参,有多少种传参方式。
一维数组传参
void test1(int arr[])//用一维数组接收 {} void test2(int arr[10])//用一维数组接收 {} void test3(int* arr)//用一级指针接收 {} int main() { int arr[10] = { 0 }; test1(arr); test2(arr); test3(arr); return 0; }
二维数组传参
void test1(int arr[][10])//用二维数组接收 {} //多维数组传参的时候,只能省略第一个[]的内容 void test2(int arr[10][10])//用二维数组接收 {} void test3(int (*arr)[10])//用数组指针接收 {} int main() { int arr[10][10] = {0}; test1(arr); test2(arr); test3(arr); return 0; }
一级指针传参
如果我们要传一个一级指针,我们应该如何设计函数的参数?显而易见,应该用相同类型的一级指针,那么反过来想,如果函数参数是一个一级指针,那么我们可以传什么参数
void test(int* p) {} int main() { int a = 0; test(&a);//传变量的地址 int* ptr = &a; test(ptr);//传一级指针 int arr[10] = { 0 }; test(arr);//传一维数组的数组名 return 0; }
二级指针传参
如果函数的参数是一个二级指针,那么我们可以传什么参数
void test(int** p) {} int main() { int a = 0; int* pa = &a; test(&pa);//传一级指针的地址 int** ppa = &pa; test(ppa);//传二级指针 int* arr[10] = { 0 }; test(arr);//传指针数组的数组名 return 0; }
综上所述,传参的时候,只要传过去的参数与函数的参数类型一致就可以。
函数指针
函数指针的表示
上文中,我们讲到了数组指针,那么函数有没有指针呢?答案是有的。类比于数组指针,指向函数的指针就是函数指针。
从上面这个例子中我们可以看出来函数名和取地址函数名的地址是一样的,那么取出来的地址也就只能用函数指针存放了。一个函数,要有函数的返回值,参数,和函数名三个要素,因此,我们对于函数指针,要表示出函数的参数和函数名,所以函数指针的表示也就显而易见了:
int (*pf)(int, int) = &test;
函数指针的使用
函数指针的调用和函数的调用方法一致,可以理解成用函数指针代替函数,但是值得一提的是,函数指针可以当作参数传递给另一个函数。那问题来了,我们可以直接调用函数,为什么要绕这么一大圈,这么麻烦的调用呢?因为函数指针要和函数指针数组一起使用。
函数指针数组
存放函数指针的数组叫做函数指针数组,函数指针数组的定义方式是怎样的呢?
函数指针数组是一个数组,所以数组名首先要跟[]结合,所以假设数组名为parr,那么首先就有parr1[],这个数组存放的类型是函数指针,假设这个函数指针指向的是一个返回值为int,参数为两个int的函数,数组有十个元素,那么函数指针数组的定义就是int (*parr[10] ) (int ,int)。
函数指针的用途:转移表
这里有一个小例子,用函数指针数组实现一个计算器,由于内容过多,放在下一篇博客里单独成一份。
指向函数指针数组的指针
指向函数指针数组的指针是一个指针,指针指向的是一个数组,数组内存放的是函数指针。
所以定义方式如下:
后面就是无限的“套娃”了,就不过多赘述。
回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。