指针详解3
运行环境以Dev-C++、Visual Studio 2022、MacOS的命令行和Xcode为主
1.字符指针变量
// 代码1:将字符变量的地址赋值给字符指针变量
#include <stdio.h>
int main(int argc, const char * argv[]) {
char ch = 'w';
char *pc = &ch;
*pc = 'b';
printf("%c\n", *pc);
return 0;
}
// 代码2:将字符串赋值给字符指针变量,实际上字符串"abcdef"并没有存放在字符指针pstr中
// 本质上是将字符串"abcdef"首字符'a'的地址存到了指针变量pstr中
#include <stdio.h>
int main(int argc, const char * argv[]) {
const char *pstr = "abcdef";
printf("%s\n", pstr);
return 0;
}
// 代码3:对比分析字符串常量和字符串数组。str3和str4指向的是同一个常量字符串,C语言会把常量字符串存储到单独的一个内存区域
// 当几个指针指向同一个字符串时,它们会指向同一块内存。但用相同的字符串常量初始化不同的数组时,会开辟出不同的内存块
#include <stdio.h>
int main(int argc, const char * argv[]) {
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");
}
else {
printf("str1 and str2 are not same\n");
}
if (str3 == str4) {
printf("str3 and str4 are same\n");
}
else {
printf("str3 and str4 are not same\n");
}
return 0;
}

2.数组指针变量
-
2.1 定义和指向原理
-
整型指针变量存放整型变量的地址,指向整型数据,如
int *p_int;;浮点型指针变量存放浮点型变量的地址,指向浮点型数据,如float *pf; -
数组指针变量存放数组的地址,是能够指向数组的指针变量,如
int (*p_arr)[10];-
p_arr先和*结合,说明p_arr是一个指针,指针指向的是一个大小为 10 个整型的数组 -
p_arr是一个指针,指向一个数组,称为数组指针 -
[]的优先级高于*,所以必须加上()来保证p_arr先和*结合
-
-
-
2.2 数组指针的初始化
- 前面的知识中详细分析了数组名、数组首元素的地址、数组的地址间的区别和联系。
&数组名便获得了数组的地址。若要存放该地址,要用到数组指针变量
int arr[10] = {0}; int (*p)[10] = &arr; // 调试后查看&arr和p的类型完全一致 int (*p) [10] = &arr; | | | | | | | | p指向数组的元素个数 | p是数组指针变量名 p指向的数组元素的类型![image]()
- 前面的知识中详细分析了数组名、数组首元素的地址、数组的地址间的区别和联系。
3.二维数组传参的本质
-
3.1 数组形式的形参
-
这是在第 6 章中二维数组作为形参的主要形式
-
实参是二维数组,形参也写成二维数组的形式
#include <stdio.h> void print_arr(int arr[3][5], int r, int c) { // int arr[][5] 也可以 int i = 0; for (i = 0; i < r; i++) { int j = 0; for (j = 0; j < c; j++) { printf("%d ", arr[i][j]); } printf("\n"); } } int main(int argc, const char * argv[]) { int arr[3][5] = {{1, 2, 3, 4, 5}, {2, 3, 4, 5, 6}, {3, 4, 5, 6, 7}}; print_arr(arr, 3, 5); return 0; } -
-
3.2 数组指针形式的形参
-
二维数组是一维数组的集合,是由一维数组组成的数组,它的首元素就是第一行,也是个一维数组
-
根据数组名是数组首元素地址这个规则,二维数组的数组名表示第一行,是一维数组的地址
-
第一行的一维数组的类型为
int [5],所以第一行的地址类型为数组指针类型int (*)[5] -
二维数组传参本质上也是传递了地址,且是第一行这个一维数组的地址
#include <stdio.h> void print_arr(int (*p)[5], int r, int c) { int i = 0, j = 0; for (i = 0; i < r; i++) { for (j = 0; j < c; j++) { printf("%d ", *(*(p + i) + j)); // printf("%d ", p[i][j]); // printf("%d ", (*(p + i))[j]); } } } int main(int argc, const char * argv[]) { int arr[3][5] = {{1, 2, 3, 4, 5}, {2, 3, 4, 5, 6}, {3, 4, 5, 6, 7}}; print_arr(arr, 3, 5); return 0; } -
4.函数指针变量
-
4.1 定义和指向原理
- 数组名表示数组首元素的地址,通过它可以定位到数组元素。类似地,函数名表示函数的地址,也可以通过
&函数名的方式获得函数的地址
#include <stdio.h> void test(void) { printf("test_function\n"); } int main(int argc, const char * argv[]) { printf("test: %p\n", test); printf("&test: %p\n", &test); return 0; }![image]()
- 既然函数名就是函数的地址,就可以用指针变量存储该地址,这样的指针变量称为函数指针,它的写法和数组指针类似
void test(void) { printf("test_function\n"); } void (*pf1)(void) = &test; void (*pf2)(void) = test; int Add(int x, int y) { return x + y; } int (*pf3)(int, int) = Add; int (*pf4)(int x, int y) = &Add; // 加不加x和y都可以 int (*pf3) (int x, int y) = &arr; | | | | | | | | pf3指向函数的参数类型和参数数量 | 函数指针变量名 pf3指向函数的返回类型 - 数组名表示数组首元素的地址,通过它可以定位到数组元素。类似地,函数名表示函数的地址,也可以通过
-
4.2 函数指针的使用
-
通过函数指针调用指针指向的函数
-
由于
&函数名和函数名的打印结果一致,所以通过函数指针调用函数时函数指针旁加不加解引用操作符*均可
#include <stdio.h> int Add(int x, int y) { return x + y; } int main(int argc, const char * argv[]) { int (*pf3)(int, int) = Add; printf("%d\n", (*pf3)(2, 3)); // 打印结果:5 printf("%d\n", pf3(4, 5)); // 打印结果:9 return 0; } -
-
4.3
typedef关键字typedef用于类型重命名,可以将复杂的类型简单化
// 1.普通变量类型重命名 typedef unsigned int uint; // 将unsigned int重命名为uint // 2.指针变量类型重命名 typedef int * ptr_t; // 将int * 重命名为 ptr_t // 3.数组指针重命名 typedef int(*parr_t) [5]; // 将int (*)[5] 重命名为parr_t,新类型名必须在 * 的右边 // 4.函数指针重命名 typedef void (*pf_t)(int); // 将void (*) (int) 重命名为pf_t,新类型名必须在 * 的右边 // 5.分析以下代码 (*(void (*)())0)(); // 是一次函数调用,调用0地址处存放的那个函数。0地址处的函数没有参数,返回类型为void // 6.分析以下代码并用typedef重命名 void (*signal(int, void(*)(int)))(int); // 是一次函数声明,函数名为signal,参数有两个,类型分别是整型int和函数指针void(*)(int) // signal函数的返回值也是一个函数指针,类型为 void (*) (int),它指向的函数参数是,范回类型为void typedef void (*pf_t) (int) pf_t signal(int, pf_t)
5.函数指针数组
-
5.1 定义和指向原理
- 将函数的地址存入一个数组中,该数组称为函数指针数组
int (*parr1[3])(); // parr1先和[]结合,说明parr1是数组,数组的内容是int (*)()类型的函数指针 -
5.2 转移表
- 使用一般的方法编写程序实现一个计算器
#include <stdio.h> 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(void) { printf("**********************\n"); printf("**** 1.Add 2.Sub ****\n"); printf("**** 3.Mul 4.Div ****\n"); printf("******* 0. Exit ******\n"); printf("**********************\n"); } int main(int argc, const char * argv[]) { int x = 0, y = 0; int input = 0; int ret = 0; do { menu(); printf("请输入你的选择:"); scanf("%d", &input); switch (input) { case 1: printf("请输入两个数:"); scanf("%d %d", &x, &y); ret = Add(x, y); printf("ret = %d\n", ret); break; case 2: printf("请输入两个数:"); scanf("%d %d", &x, &y); ret = Sub(x, y); printf("ret = %d\n", ret); break; case 3: printf("请输入两个数:"); scanf("%d %d", &x, &y); ret = Mul(x, y); printf("ret = %d\n", ret); break; case 4: printf("请输入两个数:"); scanf("%d %d", &x, &y); ret = Div(x, y); printf("ret = %d\n", ret); break; case 0: printf("退出...\n"); break; default: printf("输入错误,重新输入!\n"); break; } } while (input); return 0; }![image]()
- 使用函数指针数组编写程序实现一个计算器
// 代码1:通过函数指针数组实现 #include <stdio.h> void menu(void) { 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 argc, const char * argv[]) { int x = 0, y = 0; int input = 0; int ret = 0; int (*pf[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); ret = pf[input](x, y); printf("ret = %d\n", ret); } else if (input == 0) { printf("退出...\n"); } else { printf("输入错误,重新输入!\n"); } } while (input); return 0; } // 代码2:通过函数指针实现 #include <stdio.h> void menu(void) { 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; } void calc(int (*pf)(int, int)) { int x = 0, y = 0; int ret = 0; printf("输入两个整数:"); scanf("%d %d", &x, &y); ret = pf(x, y); printf("ret = %d\n", ret); } int main(int argc, const char * argv[]) { int input = 0; do { menu(); printf("请输入你的选择:"); scanf("%d", &input); 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"); break; default: printf("输入错误,重新输入!\n"); break; } } while (input); return 0; }
6.回调函数
-
6.1 定义和实例对比
-
回调函数就是通过函数指针调用的函数。如果将函数指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,被调用的函数就是回调函数
-
回调函数不是由该函数的实现方法直接调用,而是在特定的事件或条件发生时由另外的一方调用,用于对该事件或条件进行响应
-
在 5.2 节实现计算器的案例中,最开始使用的是普通方法,在
switch结构中输入输出操作重复出现,代码有较多冗余。这些代码只有调用函数的逻辑略有区别,因此将调用函数的地址以实参的形式传递给calc()函数,它的参数是一个函数指针,该指针指向什么函数就调用什么函数。这里使用的就是回调函数的功能
![image]()
-
-
6.2
qsort函数- 1.函数原型
// 对base指向数组的num个元素进行排序,每个元素的大小为bytes字节,排序规则由compar函数决定 // 此函数使用的排序算法通过调用指定的compar函数来比较元素对,调用时会将指向这两个元素的指针作为参数传递给compar函数 // 该函数无返回值,但会修改base所指向数组的内容,按照compar函数定义的规则对元素重新排序 // 对于值相等的元素,它们的最终顺序是未定义的(即不保证稳定排序) void qsort(void* base, // 指向待排序数组的第1个元素的指针 size_t num, // base指向数组中的元素个数 size_t size, // base指向数组中各个元素的大小,单位是字节 int (*compare)(const void *, const void *) // 函数指针,传递函数的地址 );![image]()
- 2.使用
qsort()函数排序整型数据
#include <stdio.h> #include <string.h> //int cmp_int(const void *p1, const void *p2) { // if (*(int *)p1 > *(int *)p2) { // return 1; // } // else if(*(int *)p1 < *(int *)p2) { // return -1; // } // else { // return 0; // } //} int cmp_int(const void *p1, const void *p2) { return *(int *)p1 - *(int *)p2; } void print_arr(int arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); } void test1(void) { int arr[] = {2, 4, 6, 8, 0, 5, 3, 7, 9}; int sz = sizeof(arr) / sizeof(arr[0]); qsort(arr, sz, sizeof(int), cmp_int); print_arr(arr, sz); } int main(int argc, const char * argv[]) { // insert code here... test1(); return 0; }![image]()
- 3.使用
qsort()函数排序结构数据
#include <stdio.h> #include <string.h> struct Stu { char name[20]; int age; }; void print_stu(struct Stu arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%s--%d\n", arr[i].name, arr[i].age); } printf("\n"); } // 按照年龄排序 int cmp_stu_by_age(const void *p1, const void *p2) { return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age; } // 测试qsort函数排序结构体数据,按照名字比较两个结构体数据 // 名字是字符串,字符串比较时使用strcmp函数 int cmp_stu_by_name(const void *p1, const void *p2) { // ->的优先级1高于强制类型转换2,所以要将转换后的指针整体使用-> return strcmp(((struct Stu *)p1)->name, ((struct Stu*)p2)->name); } void test2(void) { // 按姓名排序时需要将结构体数组中的姓名写成英文,写中文的话可能会因为编码导致预期跟实际结果不一致 struct Stu arr[] = {{"zhangsan", 34}, {"lisi", 24}, {"wangwu", 40} }; int sz = sizeof(arr) / sizeof(arr[0]); qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name); print_stu(arr, sz); } void test3(void) { struct Stu arr[] = {{"张三", 34}, {"李四", 24}, {"王五", 40} }; int sz = sizeof(arr) / sizeof(arr[0]); qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age); print_stu(arr, sz); } int main(int argc, const char * argv[]) { // insert code here... printf("sorted by name:\n"); test2(); printf("sorted by age:\n"); test3(); return 0; }![image]()
- 4.使用冒泡排序模拟实现
qsort()函数
#include <stdio.h> #include <string.h> struct Stu { char name[20]; int age; }; void swap(char *buff1, char *buff2, size_t width) { int i = 0; char temp = 0; for (i = 0; i < width; i++) { temp = *buff1; *buff1 = *buff2; *buff2 = temp; buff1++; buff2++; } } void bubble_sort(void *base, size_t sz, size_t width, int (*cmp)(const void *p1, const void *p2)) { int i = 0, j = 0; for (i = 0; i < sz - 1; i++) { 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 cmp_int(const void *p1, const void *p2) { return *(int *)p1 - *(int *)p2; } int cmp_stu_by_name(const void *p1, const void *p2) { return strcmp(((struct Stu *)p1)->name, ((struct Stu*)p2)->name); } int cmp_stu_by_age(const void *p1, const void *p2) { return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age; } void print_arr(int arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); } void print_stu(struct Stu arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%s--%d\n", arr[i].name, arr[i].age); } printf("\n"); } void test1(void) { int arr[] = {2, 4, 6, 8, 0, 5, 3, 7, 9}; int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr, sz, sizeof(int), cmp_int); print_arr(arr, sz); } void test2(void) { struct Stu arr[] = {{"zhangsan", 34}, {"lisi", 24}, {"wangwu", 40} }; int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name); print_stu(arr, sz); } void test3(void) { struct Stu arr[] = {{"张三", 34}, {"李四", 24}, {"王五", 40} }; int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_age); print_stu(arr, sz); } int main(int argc, const char * argv[]) { // insert code here... // test1(); // test2(); test3(); return 0; }







浙公网安备 33010602011771号