1.字符指针
字符指针(Character Pointer)char*
是指向字符数据的指针变量。
使用实例:
int main() {char ch = 'w';// 声明一个字符变量ch并赋值为 'w'char *pc = &ch; // 声明一个字符指针pc并将其指向ch的地址*pc = 'w';// 通过指针pc修改ch的值为 'w'char arr[] = "abcdef";// 声明一个字符数组arr并初始化为字符串 "abcdef"char* p = arr; // 声明一个字符指针p,将其指向数组arr的首地址return 0;}
还有一种使用方式如下:
#include int main() {const char *pstr = "hello world."; // 声明一个指向常量字符的指针pstr,并将其指向字符串常量"hello wrold."printf("%s\n", pstr);// 输出字符串"hello world."*pstr = 'w';// 尝试修改常量字符串,会导致编译错误,因为常量字符串不可修改printf("%c\n", *pstr);// 尝试访问常量字符串的第一个字符,不会发生修改,输出仍为原始字符'h'return 0;}
通过指针修改常量字符串的操作是非法的,会导致编译错误。常量字符串应该被视为只读数据。如果需要修改字符串内容,应该使用字符数组而不是指向常量字符的指针。
代码 const char* pstr = "hello world.";
特别容易让同学以为是把字符串 hello world
放到字符指针 pstr
里了,但是/本质是把字符串 hello world.
首字符的地址放到了pstr
中。
下面的结果是什么?
#include int main() {char str1[] = "hello world.";char str2[] = "hello world.";const char *str3 = "hello world.";const char *str4 = "hello world.";if (str1 == str2)printf("str1 and str2 are same\n");elseprintf("str1 and str2 are not same\n");if (str3 == str4)printf("str3 and str4 are same\n");elseprintf("str3 and str4 are not same\n");return 0;}//str1 and str2 are not same//str3 and str4 are same
str1
和str2
是字符数组,它们包含了相同的字符串 “hello world.”。str3
和str4
是指向常量字符的指针,它们也指向相同的字符串 “hello world.”。
然后,程序使用条件语句比较了这四个变量的指针值,并输出比较结果:
- 通过
str1 == str2
比较,它们是两个不同的字符数组,所以输出 “str1 and str2 are not same”。 - 通过
str3 == str4
比较,它们指向相同的常量字符,所以输出 “str3 and str4 are same”。
尽管 str1
和 str2
的内容相同,但它们是两个不同的数组,因此它们在内存中的地址也不同。而 str3
和 str4
是指向相同字符串常量的指针,它们指向的地址相同。
如果想比较字符串的内容而不是地址,应该使用字符串比较函数
strcmp()
,例如strcmp(str1, str2)
。
2.指针数组
在C语言中,指针数组是指一个数组,其元素都是指针类型的变量。指针数组可以存储指向不同类型对象的指针。
那么指针数组是指针还是数组?
是数组。是存放指针的数组。
例如:
int main() {//整型数组-存放整型的数组int arr[10];//字符数组-存放字符的数组char arr2[5];//指针数组-存放指针的数组int *arr3[5]; //存放整型指针的数组char *arr4[6];//存放字符指针的数组return 0;}
例如:
#include int main() {int a = 10;int b = 20;int c = 30;int d = 40;int e = 50;int *arr3[5] = {&a, &b, &c, &d, &e};//存放整型指针的数组int i = 0;for (i = 0; i < 5; i++) {printf("%d ", *(arr3[i]));}return 0;}
指针数组使用场景:用一个一维数组模拟二维数组
#includeint main() {//用一维数组模拟一个二维数组int arr1[] = {1, 2, 3, 4, 5};int arr2[] = {2, 3, 4, 5, 6};int arr3[] = {3, 4, 5, 6, 7};int arr4[] = {4, 5, 6, 7, 8};int *arr[4] = {arr1, arr2, arr3, arr4};int i = 0;for (i = 0; i < 4; i++) {int j = 0;for (j = 0; j < 5; j++) {printf("%d ", *(*(arr + i) + j));}printf("\n");}//int i = 0;//for (i = 0; i < 4; i++)//{//int j = 0;//for (j = 0; j < 5; j++)//{//printf("%d ", arr[i][j]);//}//printf("\n");//}return 0;}
输出结果:
1 2 3 4 5 2 3 4 5 6 3 4 5 6 7 4 5 6 7 8
3.数组指针
3.1 数组指针的定义
数组指针是指针?还是数组?
答案是:指针。
我们已经熟悉: 整型指针: int * pint
; 能够指向整型数据的指针。
浮点型指针: float * pf
; 能够指向浮点型数据的指针。 那数组指针应该是:能够指向数组的指针。
下面代码哪个是数组指针?
int *p1[10];int (*p2)[10];//p1, p2分别是什么?
解释:
int (*p)[10]
;
p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。
这里要注意:[]的优先级要高于号的,所以必须加上()来保证p先和结合。
int main(){int arr[10] = {1,2,3,4,5};int (* pa)[10] = &arr;//取出的是数组的地址存放到pa中,pa是数组指针变量char arr[5];char (*p1)[5] = &arr;}
3.2 &数组名VS数组名
对于下面的数组:
int arr[10];
arr
和 &arr
分别是啥?
我们知道arr是数组名,数组名表示数组首元素的地址。
那&arr数组名到底是啥? 我们看一段代码:
#include int main() {int arr[10] = {0};printf("%p\n", arr);printf("%p\n", &arr);return 0;}
运行结果如下:
000000000061FDF0000000000061FDF0
可见数组名和&数组名打印的地址是一样的。
难道两个是一样的吗?
我们再看一段代码:
#include int main() {int arr[10] = {0};printf("arr = %p\n", arr);printf("&arr= %p\n", &arr);printf("arr+1 = %p\n", arr + 1);printf("&arr+1= %p\n", &arr + 1);return 0;}
输出结果如下:
arr = 000000000061FDF0&arr= 000000000061FDF0arr+1 = 000000000061FDF4&arr+1= 000000000061FE18
根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义应该不一样的。
实际上: &arr
表示的是数组的地址,而不是数组首元素的地址。(细细体会一下)
本例中&arr
的类型是: int(*)[10]
,是一种数组指针类型数组的地址+1,跳过整个数组的大小,所以&arr+1
相对于&arr
的差值是40.
3.3 数组指针的使用
那数组指针是怎么使用的呢?
既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。
实例:
#include int main() {int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};int(*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p//但是我们一般很少这样写代码return 0;}
一个数组指针的使用:
#include void print1(int *arr, int sz) {int i = 0;for (i = 0; i < sz; i++) {//*(arr + i) 表示指针arr偏移i个元素后所指向的值。printf("%d ", *(arr + i));}printf("\n");}void print2(int (*p)[10], int sz) {int i = 0;for (i = 0; i < sz; i++) {//int (*p)[10]是数组指针,(*p)[i] 表示指针p所指向的数组的第i个元素的值。printf("%d ", (*p)[i]); //*p = arr;}printf("\n");}int main() {int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};int sz = sizeof(arr) / sizeof(arr[0]);print1(arr, sz);//1 2 3 4 5 6 7 8 9 10print2(&arr, sz);//1 2 3 4 5 6 7 8 9 10return 0;}
print1
函数的参数是一个整型指针,可以传递任意大小的整型数组给它进行打印。而 print2
函数的参数是一个指向包含10个整数的数组的指针,因此只能传递大小为10的整型数组给它进行打印。
这两种写法都可以用于打印整型数组的元素,选择使用哪种方式取决于你的需求和所操作的数组的类型和大小。
继续:
void print3(int arr[3][5], int r, int c) {int i = 0;for (i = 0; i < r; i++) {int j = 0;for (j = 0; j < c; j++) {//arr[i][j] 表示二维数组arr的第i行、第j列的元素的值。printf("%d ", arr[i][j]);}printf("\n");}}void print4(int (*p)[5], int r, int c) {//函数接受一个指向包含5个整数的数组指针pint i = 0;for (i = 0; i < r; i++) {int j = 0;for (j = 0; j < c; j++) {//*(*(p + i) + j) 表示指针p所指向的二维数组的第i行、第j列的元素的值。//p为指针 p+i表示一维数组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}};print3(arr, 3, 5);print4(arr, 3, 5);return 0;}
解析 *(*(p + i) + j)
:
(p + i)
:首先,p
是一个指向包含5个整数的数组的指针。通过p + i
运算,指针p
偏移了i
个整数大小的字节,即指向了二维数组的第i
行。*(p + i)
: 在上一步中,p
偏移了i
行,所以*(p + i)
表示指针p
所指向的二维数组的第i
行的首个元素。这是一个指向整数的指针。(*(p + i) + j)
: 在上一步的基础上,j
是用于偏移列的索引。*(p + i) + j
表示指针*(p + i)
所指向的一维数组中,偏移了j
个整数大小的字节,即指向了二维数组中第i
行、第j
列的元素。*(*(p + i) + j)
: 最后,*(*(p + i) + j)
使用指针解引用操作符*
,获取指针(*(p + i) + j)
所指向的整数值。这就是二维数组中第i
行、第j
列的元素值。
学了指针数组和数组指针我们来一起回顾并看看下面代码的意思:
int arr[5];int *parr1[10];int (*parr2)[10];int (*parr3[10])[5];
int arr[5];
这是一个包含5个整数的数组,名为arr
。int *parr1[10];
这是一个包含10个指向整数的指针的数组,名为parr1
。它可以存储10个整数的地址。int (*parr2)[10];
这是一个指向包含10个整数的数组的指针,名为parr2
。它指向一个具有10个整数的数组。int (*parr3[10])[5];
这是一个包含10个指向包含5个整数数组的指针的数组,名为parr3
。它可以存储10个指向具有5个整数的数组的指针。
让我们分解int (*parr3[10])[5]
并解释其含义:
- 从变量名
parr3
开始,我们知道这是一个数组。 - 继续向右,我们看到
[10]
,表示parr3
是一个包含10个元素的数组。 - 继续向右,我们看到
*
,表示数组的元素是指针。 - 继续向右,我们看到
[5]
,表示指针指向的是包含5个整数的数组。 - 最后,我们有
int
,表示数组中的整数类型。
因此,int (*parr3[10])[5]
可以解读为:parr3
是一个包含10个元素的数组,每个元素都是指向包含5个整数的数组的指针。
4.数组参数、指针参数
在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?
4.1 一维数组传参
#include void test(int arr[])//ok?{}void test(int arr[10])//ok?{}void test(int *arr)//ok?{}void test2(int *arr[20])//ok?{}void test2(int **arr)//ok?{}int main() {int arr[10] = {0};int *arr2[20] = {0};test(arr);test2(arr2);}
上述代码中都OK。arr[10]
等价于*arr
,[]中的值可以省略。 *arr[20]
等价于**arr
4.2 二维数组传参
void test(int arr[3][5])//ok?{}void test(int arr[][])//ok?err{}void test(int arr[][5])//ok?{}int main() {int arr[3][5] = {0};test(arr);}
总结:
二维数组传参,函数形参的设计只能省略第一个[]的数字。因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。这样才方便运算。
void test(int *arr)//ok?{}void test(int *arr[5])//ok?err{}void test(int (*arr)[5])//ok?{}void test(int **arr)//ok? err{}int main() {int arr[3][5] = {0};test(arr);}
函数 test(int *arr[5])
是错误的,因为它声明了一个包含 5 个指向整数的指针的数组,而不是一个指向整数的指针。函数 test(int **arr)
也是错误的,因为它声明了一个指向指针的指针,而不是一个指向整数或整数数组的指针。在主函数中,参数 int *arr
的正确函数声明应该是 test(int (*arr)[5])
,因为它是一个包含 5 个整数的数组的指针 。
4.3 一级指针传参
#include void print(int *p, int sz) {int i = 0;for (i = 0; i < sz; i++) {printf("%d ", *(p + i));//1 2 3 4 5 6 7 8 9 10}}int main() {int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};int *p = arr;int sz = sizeof(arr) / sizeof(arr[0]);//一级指针p,传给函数print(p, sz);return 0;}
思考:
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
比如:
void test1(int *p){}//test1函数能接收什么参数?int型一维数组,和int型变量的地址void test2(char* p){}//test2函数能接收什么参数?char型一维数组,和char型变量的地址
4.4 二级指针传参
#include void test(int **ptr) {printf("num = %d\n", **ptr);}int main() {int n = 10;int *p = &n;int **pp = &p;test(pp);//num = 10test(&p);//num = 10return 0;}
思考:
当函数的参数为二级指针的时候,可以接收什么参数?
void test(char **p) {}int main() {char c = 'b';char *pc = &c;char **ppc = &pc;char *arr[10];test(&pc);//pc是一个一级指针,&pc即二级指针test(ppc);//ppc是一个二级指针test(arr);//Ok?return 0;}
数组名本身也可以被解释为指向数组首元素的指针。对于一个char*
类型的数组arr[10]
,arr
可以被解释为指向arr[0]
的指针,而arr[0]
又是一个char*
类型的指针。
因此,当调用test(arr)
时,实际上将指向arr[0]
的指针传递给了test
函数。在test
函数内部,参数p
的类型是char**
,它可以接收指向char*
类型的指针。因此,test(arr)
是合法的。
5.函数指针
首先看一段代码:
#include void test() {printf("hehe\n");}int main() {printf("%p\n", test);printf("%p\n", &test);return 0;}
输出的结果:
00000000004015500000000000401550
输出的是两个地址,这两个地址是 test 函数的地址。
那我们的函数的地址要想保存起来,怎么保存?
下面我们看代码:
void test() {printf("hehe\n");}//下面pfun1和pfun2哪个有能力存放test函数的地址?void (*pfun1)();void *pfun2();
pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。
什么是函数指针?
函数指针是指向函数的指针变量。它可以用于在程序运行时动态地选择调用不同的函数,或者将函数作为参数传递给其他函数。
函数指针的声明:
return_type (*pointer_name)(parameter_list);
其中,return_type
是函数的返回类型,pointer_name
是指针变量的名称,parameter_list
是函数的参数列表。例如,以下是一个指向返回整数类型、接受两个整数参数的函数指针的声明:
int (*sum_ptr)(int, int);
函数指针的赋值:
函数指针可以通过将函数的名称赋值给指针变量来进行初始化。例如,假设有以下函数定义:
int sum(int a, int b) {return a + b;}
可以将该函数赋值给函数指针:
sum_ptr = sum;
使用函数指针调用函数: 使用函数指针调用函数与直接调用函数的语法相似,只需将指针变量后面加上参数列表即可。例如:
int result = sum_ptr(3, 4);
实例1:
#include int Add(int x, int y) {return x + y;}int main() {//pf就是函数指针变量int (*pf)(int x, int y) = Add;//Add和&Add都是函数的地址,没有区别,类似于数组名和&数组名int sum = (*pf)(3, 5);//等价于int sum = Add(3, 5);printf("%d\n", sum);//8return 0;}
实例2:
#include int test(const char *str, double d) {}int main() {int (*pt1)(const char *, double) = &test;int (*pt2)(const char *str, double d) = &test;return 0;}
阅读两段有趣的代码:
//代码1(*(void (*)())0)();//代码2void (*signal(int , void(*)(int)))(int);
(*(void (*)())0)();
把0直接转换成一个void (*)()的函数指针,然后去调用0地址处的函数,函数的参数为无参
void (*signal(int , void(*)(int)))(int);
是一次函数声明
声明的函数叫:signal
signal函数的第一个参数是int类型的
signal函数的第二个参数是一个函数指针类型,该函数指针指向的函数参数是int,返回类型是void
signal函数的返回类型也是一个函数指针类型,该函数指针指向的函数参数是int,返回类型是void
代码2太复杂,如何简化:
typedef void(*pfun_t)(int);//pfun_t 是一个函数指针,参数是int,返回类型是voidpfun_t signal(int, pfun_t);
6.函数指针数组
数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组
比如:
int *arr[10];//数组的每个元素是int*
函数指针数组中的每个元素都是一个函数指针。函数指针是指向函数的指针变量,可以用来调用该函数。
那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?
int (*parr1[10])(); //函数指针数组,指向一个无参函数,返回值为intint *parr2[10](); int (*)() parr3[10];
答案是:parr1
parr1
先和 []
结合,说明 parr1
是数组,数组的内容是什么呢?
是 int (*)()
类型的函数指针。
函数指针数组的用途:转移表
例子:(计算器)
#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;}void menu() {printf("***************************\n");printf("***** 1.add2. sub****\n");printf("***** 3.mul4. div****\n");printf("***** 0.exit ****\n");printf("***************************\n");}int main() {int input = 0;int x = 0;int y = 0;int ret = 0;do {menu();printf("请选择:>");scanf("%d", &input);switch (input) {case 1:printf("请输入2个操作数:>");scanf("%d %d", &x, &y);ret = Add(x, y);printf("%d\n", ret);break;case 2:printf("请输入2个操作数:>");scanf("%d %d", &x, &y);ret = Sub(x, y);printf("%d\n", ret);break;case 3:printf("请输入2个操作数:>");scanf("%d %d", &x, &y);ret = Mul(x, y);printf("%d\n", ret);break;case 4:printf("请输入2个操作数:>");scanf("%d %d", &x, &y);ret = Div(x, y);printf("%d\n", ret);break;case 0:printf("退出计算器\n");break;default:printf("选择错误\n");break;}} while (input);}
使用函数指针数组简化代码:
#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;}void menu() {printf("***************************\n");printf("***** 1.add2. sub****\n");printf("***** 3.mul4. div****\n");printf("***** 0.exit ****\n");printf("***************************\n");}int main() {int input = 0;int x = 0;int y = 0;int ret = 0;//函数指针数组 - 转移表int (*pfArr[])(int, int) = {0, Add, Sub, Mul, Div};do {menu();printf("请选择:>");scanf("%d", &input);if (input == 0) {printf("退出计算器\n");break;}if (input >= 1 && input <= 4) {printf("请输入2个操作数:>");scanf("%d %d", &x, &y);ret = pfArr[input](x, y);printf("%d\n", ret);} else {printf("选择错误\n");}} while (input);}
7.指向函数指针数组的指针
指向函数指针数组的指针是一个指针,它指向函数指针数组的起始地址。可以使用这个指针来访问函数指针数组中的元素,以及通过函数指针调用相应的函数。
下面是一个简单的示例,展示了如何声明和使用指向函数指针数组的指针:
#include void test(const char* str){printf("%s\n", str);}int main(){ //函数指针pfunvoid (*pfun)(const char*) = test; //函数指针的数组pfunArrvoid (*pfunArr[5])(const char* str); pfunArr[0] = test; //指向函数指针数组pfunArr的指针ppfunArr void (*(*ppfunArr)[5])(const char*) = &pfunArr; return 0;}// 函数指针的声明typedef void (*FuncPtr)(int);// 函数定义void func1(int num) {printf("func1: %d\n", num);}void func2(int num) {printf("func2: %d\n", num);}void func3(int num) {printf("func3: %d\n", num);}int main() {// 函数指针数组的定义和初始化FuncPtr funcs[] = { func1, func2, func3 };// 指向函数指针数组的指针FuncPtr (*ptrToFuncArray)[3] = &funcs;// 通过指针访问函数指针数组的元素,并调用相应的函数(*ptrToFuncArray)[0](10);// 调用 func1(*ptrToFuncArray)[1](20);// 调用 func2(*ptrToFuncArray)[2](30);// 调用 func3return 0;}
8.回调函数
回调函数是一种函数指针的使用方式,它允许将一个函数作为参数传递给另一个函数,并在需要的时候调用该函数。回调函数提供了一种灵活的机制,使得代码可以根据特定的条件或事件来执行不同的行为。
#include // 回调函数类型的定义typedef void (*CallbackFunc)(int);// 执行回调函数的函数void performCallback(CallbackFunc callback, int value) {// 调用传递进来的回调函数callback(value);}// 回调函数1void callback1(int value) {printf("Callback 1: %d\n", value);}// 回调函数2void callback2(int value) {printf("Callback 2: %d\n", value);}int main() {int data = 10;// 使用回调函数1进行操作performCallback(callback1, data);// 使用回调函数2进行操作performCallback(callback2, data);return 0;}
输出结果如下:
Callback 1: 10Callback 2: 10
我们首先定义了一个回调函数类型 CallbackFunc
,它是一个函数指针类型,可以指向一个带有一个 int
参数和 void
返回类型的函数。
然后,我们定义了一个名为 performCallback
的函数,它接受一个回调函数作为参数,并在需要的时候调用该函数,传递给它一个值。
接下来,我们定义了两个回调函数 callback1
和 callback2
,它们都符合 CallbackFunc
类型的函数签名。
在 main
函数中,我们定义了一个整数变量 data
,然后使用 performCallback
函数两次,分别传递不同的回调函数和 data
值。这样就实现了在不同情况下执行不同回调函数的功能。
当程序运行时,会依次调用 performCallback
函数,并根据传递的回调函数打印相应的信息。
qsort函数
C语言中qsort函数也是通过函数指针回调函数实现的
#include //qosrt函数的使用者得实现一个比较函数int int_cmp(const void *p1, const void *p2) {return (*(int *) p1 - *(int *) p2);}int main() {int arr[] = {1, 3, 5, 7, 9, 2, 4, 6, 8, 0};int i = 0;qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {printf("%d ", arr[i]);}printf("\n");return 0;}
用冒泡排序模拟实现下回调函数方式:
#include int cmp_int(const void *e1, const void *e2) {return (*(int *) e1 - *(int *) e2);}//char类型1字节,适用于任何类型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++;}}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 j = 0;for (j = 0; j < sz - 1 - i; j++) {if (cmp((char *) base + j * width, (char *) base + (j + 1) * width) > 0) {//交换Swap((char *) base + j * width, (char *) base + (j + 1) * width, width);}}}}int main() {int arr[] = {1, 3, 5, 7, 9, 2, 4, 6, 8, 0};//char *arr[] = {"aaaa","dddd","cccc","bbbb"};int i = 0;bubble_sort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), cmp_int);for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {printf("%d ", arr[i]);}printf("\n");return 0;}