目录

C语言-进阶指针学习笔记

C语言 进阶指针学习笔记

大部分的内容都写在代码注释中 指针有类型,指针的类型决定了指针的±整数的步长,指针解引用操作的时候的权限

字符指针

#include int main(void) { const char *str1 = “Hello, World!”; const char *str2 = “Hello, World!”; // 在这个写法中,字符串放在表达式中(“hello world” 视为一个表达式) // 该表达式的值是一个指向字符串的指针 // *str2 = “Hello, C!”; // const 在 * 前面,不能修改指针指向的内容 char str3[] = “Hello, World!”; char str4[] = “Hello, World!”; // 在这个写法中,字符串放在数组中,数组是一个变量,可以修改 char *str5 = “Hello, World!”; // 在这个写法中,指针未被修饰,且其中存的为常量字符串 // *str5 = “Hello, C!”; // 会报错,因为指针指向的是常量字符串,不能被修改 // 在前面加上 const 修饰符,即可在编译阶段报错,避免运行时错误 // const char *str5 = “Hello, World!”; printf(“str1: %s, address: %p\n”, str1, str1); printf(“str2: %s, address: %p\n”, str2, str2); printf(“str5: %s, address: %p\n”, str5, str5); // str1、str2 和 str5 的地址是相同的,因为它们指向的是常量字符串,在内存中只有一份 printf(“str3: %s, address: %p\n”, str3, str3); printf(“str4: %s, address: %p\n”, str4, str4); // str3 和 str4 的地址是不同的,因为它们是数组,每个数组都有自己的内存空间 return 0; } C/C++会把常量字符串存储到单独的一个内存区域,当几个常量指针指向同一个字符串时,他们实际会指向同一块内存(全局区) 但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块,因此 str1 和 str2 不同,str3 和 str4 不同

指针数组

指针数组是一个存放指针的数组 #include int main() { int arr1[] = {1, 2, 3, 4, 5}; // 整形数组 // 指针数组 int parr1[] = {arr1, arr1 + 1, arr1 + 2, arr1 + 3, arr1 + 4}; // parr1 是一个数组,数组中的每个元素都是一个 int,指向 arr1 中的元素 for (int i = 0; i < 5; i++) { printf(“arr1[%d]: %d, address: %p\n”, i, arr1[i], &arr1[i]); printf(“parr1[%d]: %d, address: %p\n”, i, *parr1[i], parr1[i]); // 这里的 address 实际上就是 arr2[i] 的值,即 arr1 中元素的地址 } // —————————- int arr2[] = {1, 2, 3, 4, 5}; int arr3[] = {2, 3, 4, 5, 6}; int *parr2[] = {arr2, arr3}; // 利用指针数组,将多个独立数组组合成类二维数组 for(int i = 0; i < 2; i++) { for(int j = 0; j < 5; j++) { printf(“parr2[%d][%d]: %d, address: %p\n”, i, j, parr2[i][j], &parr2[i][j]); // parr2[i][j] 等价于 *(parr2[i] + j),即 arr2[i] 中的第 j 个元素 } } return 0; }

数组指针

数组指针指指向数组的指针 int main() { int arr1[5]; // 指针数组 // arr1 先是一个数组(先和方块结合),数组中的每个元素都是一个 int,指向 int 类型的变量 int (*arr2)[5]; // 数组指针 // arr2 是一个指针,指向一个 int 类型的数组 return 0; }

数组名

  1. 通常,数组名表示的都是首元素地址
  2. 存在两个例外
  3. sizeof (数组名),这里的数组名表示整个数组
  4. &数组名,表示的依然是整个数组 类型(对 int arr[5]]):
  • arr -> int*
  • &arr -> int (*)[5] #include int main() { int arr1[] = {1, 2, 3, 4, 5}; printf("%p\n", arr1); printf("%p\n", &arr1); // 在这里,&arr1 表示整个数组的地址,即 arr1 的地址 printf("—————-\n"); int sz = sizeof(arr1); printf("%d\n", sz); // sizeof(arr1) 是整个数组的大小,单位是字节 // 在这里,数组名表示整个数组 printf("—————-\n"); printf("%p\n", arr1 + 1); printf("%p\n", &arr1 + 1); // arr1 + 1 表示数组中第二个元素的地址 // &arr1 + 1 表示整个数组后面的地址 printf("—————-\n"); int (parr)[5] = &arr1 // parr 是一个指针,指向一个 int 类型的数组 // 需要写清楚有几个元素 // 此时,parr 的类型为 int()[5] int p = &arr1 // warning:‘int()[5]’ 类型的表达式被隐式转换为不兼容的指针类型 return 0; }

数组传参

#include /**

  • @brief 打印数组内容,参数直接传数组
  • @param arr 传入数组
  • @param line 数组行数
  • @param length 数组列数 /void print1(int arr[3][5], int line, int length) { for (int i = 0; i < line; i++) { for (int j = 0; j < length; j++) { printf("%d “, arr[i][j]); } printf("\n”); } } /*
  • @brief 打印数组内容,传参传数组指针
    • @param p
  • @param line
  • @param length */ void print2(int (p)[5], int line, int length) { for(int i = 0; i < line; i++) { for(int j = 0; j < length; j++) { printf("%d “, ((p + i) + j)); // printf("%d “, ((p + i))[j]); // 等价于上面的写法 // 如果写成 *(p + i)[j],输出会报错,因为 [] 的优先级高于 * printf("%d “, p[i][j]); // 也可以这样写 } printf("\n”); } } int main() { int arr[3][5] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; // 二维数组的首元素是第一行,在这里为含有 5 个元素的一维数组 print1(arr, 3, 5); print2(arr, 3, 5); // 将二维数组的首元素传入函数 return 0; } int *parr1[10]; // 指针数组 int(*parr)[10]; // 数组指针 int(*parr[5])[10]; // 存放数组指针的数组 一维指针数组传参 指针数组元素为 指针 指针数组的数组名相当于一个 指针 指针指向存放一个指针的位置 -> 二级指针 因此指针数组传参可以为二级指针 void test(int **parr); void test1(int *parr[20]); 二维数组通过指针传参 二维数组的首元素是一维数组,不能用一级/二级指针接收 形参应该是一个一维数组的指针 void test(int *arr); // err void test1(int (*arr)[5]); // 参数为一个一维数组 void test2(int **arr); // err,二级指针指向存放一级指针的地址,不能是指向一个一维数组的指针

函数指针

指向函数的指针 #include int func1(int x, int y) { return x + y; } int main() { printf("%p\n”, &func1); // 取地址符得到函数的地址 printf("%p\n”, func1); // 对函数来说,&func1 和 func1 是一样的 int (p)(int, int) = func1; // 定义一个函数指针 // 返回值类型 (指针变量名)(参数类型1, 参数类型2, …); int ret = (p)(1, 2); // 通过函数指针调用函数 int ret2 = p(1, 2); // p 和 p 是一样的,如果要写 p,必须加括号(如果不加括号,p 先和后面结合, 会被当成解引用操作符对 p(1, 2) 解引用) // 等价于 int ret = func1(1, 2); printf("%d\n", ret); printf("%d\n", ret2); return 0; } 案例 ((void ()())0)(); // void (p)() -> p 是函数指针 // void ()() -> 函数指针类型 // (void ()())0 -> 把 0(0x00000000) 强制转换为函数指针类型(此时把 0 看作一个地址) // ((void (*)())0)() -> 调用 0 地址对应的函数 // 即这行代码为一次函数调用,调用 0 地址的函数 void (signal(int, void()(int)))(int); // 一次函数声明 // 声明的 signal 函数参数 1 类型为 int,参数 2 类型为函数指针,该函数指针指向的函数参数是 int 类型,返回类型为 void // signal 函数的返回类型为函数指针,该函数指针参数是 int 类型,返回类型为 void // ———- typedef void (pf_t)(int); // 把 void()(int) 类型重命名为 pf_t int main() { void (signal(int, void ()(int)))(int); pf_t signal(int, pf_t); // 使用 pf_t 重命名后的类型 return 0; } 函数指针的用途 #include void menu() { printf(“Please select:\n”); printf(“1. Add\n”); printf(“2. Sub\n”); printf(“3. Mul\n”); printf(“4. Div\n”); printf(“0. Exit\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; } // 方法 2:将输入代码块放在一个函数中,通过函数指针传递给 Calculate 函数(回调函数) /** /**

  • @brief 接收一个函数指针,通过函数指针调用函数
    • @param pf 函数指针 */void Calculate(int (*pf)(int, int)) { printf(“Please input two numbers: “); int num1 = 0; int num2 = 0; scanf("%d %d”, &num1, &num2); int ret = pf(num1, num2); printf(“Result: %d\n”, ret); } int main() { int choice = 0; do { menu(); printf(“Please select: “); scanf("%d”, &choice); // printf(“Please input two numbers: “); // int num1 = 0; // int num2 = 0; // scanf("%d %d”, &num1, &num2); // int ret = 0; // 输入代码块放在外面,无论选择什么都要输入两个数,不符合逻辑 switch (choice) { case 1: // printf(“Please input two numbers: “); // int num1 = 0; // int num2 = 0; // scanf("%d %d”, &num1, &num2); // int ret = 0; // 方法 1:将每一个输入涉及的代码块放在一个 case 语句中,造成代码冗余 // ret = Add(num1, num2); Calculate(Add); break; case 2: // ret = Sub(num1, num2); Calculate(Sub); break; case 3: // ret = Mul(num1, num2); Calculate(Mul); break; case 4: // ret = Div(num1, num2); Calculate(Div); break; case 0: printf(“Exit!\n”); break; default: printf(“Error input!\n”); break; } // printf(“Result: %d\n”, ret); } while (choice); return 0; }

函数指针数组

把函数指针放在数组中 #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(“Please select:\n”); printf(“1. Add\n”); printf(“2. Sub\n”); printf(“3. Mul\n”); printf(“4. Div\n”); printf(“0. Exit\n”); } int main() { int choice = 0; // 实现函数跳转(转移表) int(*pfArr[4])(int, int) = {Add, Sub, Mul, Div}; do { menu(); scanf("%d”, &choice); if(choice < 0 || choice > 4) { printf(“Error input!\n”); continue; } else if (choice == 0) { break; } else { printf(“Please input two numbers: “); int num1 = 0; int num2 = 0; scanf("%d %d”, &num1, &num2); printf(“Result: %d\n”, pfArr[choice - 1](num1, num2)); } } while (choice); printf(“Exit!\n”); return 0; }

指向函数指针数组的指针

int (pfarr[])(int, int) = {add, sub, mul, div}; // 指向函数指针数组的指针 int ((*pfarr)[5])(int, int)

回调函数

通过函数指针调用的函数(把 A 函数的指针作为参数传递给 B 函数,当特定的事件或条件满足时,由 B 函数调用 A 函数,此时 A 为回调函数)

Qsort 的使用

/**

  • @brief 快速排序(库函数),可以对任意类型的数据进行排序 ** @param base 排序的数据起始位置
  • @param nmemb 待排序元素个数
  • @param size 待排序元素大小(字节)
  • @param compar 函数指针,指向比较函数,e1、e2分别指向待比较的两个元素,当e1 < e2时,返回负数;当e1 = e2时,返回0;当e1 > e2时,返回正数 */ void qsort(void *base, size_t nmemb, size_t size, int (compar)(const void * e1, const void e2)); 回调函数的使用 #include #include /
  • @brief 整形比较函数 ** @param e1 比较的元素1
  • @param e2 比较的元素2
  • @return int 当e1 < e2时,返回负数;当e1 = e2时,返回0;当e1 > e2时,返回正数 */int compareInt(const void *e1, const void *e2) { return *(int *)e1 - *(int *)e2; // void *可以接收任意类型的数据,不能解引用,也不能进行运算,需要转换为具体类型 // 也可以写成降序排序,return *(int *)e2 - *(int *)e1; } void testFunc() { int arr[] = {9, 8, 7, 6, 5, 4, 3, 2, 1}; int sz = sizeof(arr) / sizeof(arr[0]); qsort(arr, sz, sizeof(arr[0]), compareInt); for(int i = 0; i < sz; i++) { printf("%d “, arr[i]); } } struct Stu { char name[20]; int age; }; int compareStu(const void *e1, const void *e2) { return ((struct Stu *)e1)->age - ((struct Stu *)e2)->age; } void testFunc2() { struct Stu s[3] = {{“zhangsan”, 20}, {“lisi”, 30}, {“wangwu”, 10}}; int sz = sizeof(s) / sizeof(s[0]); qsort(s, sz, sizeof(s[0]), compareStu); for(int i = 0; i < sz; i++) { printf("%s %d\n”, s[i].name, s[i].age); } } int main() { testFunc(); // int 类型排序 printf("\n”); testFunc2(); // 结构体类型排序 return 0; }

通过冒泡排序模拟实现 qsort

#include struct Stu { char name[20]; int age; }; int compareStu(const void *e1, const void *e2) { return ((struct Stu *)e1)->age - ((struct Stu *)e2)->age; } int compareInt(const void *e1, const void *e2) { return *(int *)e1 - *(int *)e2; } void swapMem(void *e1, void *e2, size_t width) { char tmp; for(int i = 0; i < width; i++) { tmp = *((char *)e1 + i); *((char *)e1 + i) = *((char *)e2 + i); ((char )e2 + i) = tmp; } } /

  • @brief 模拟实现 qsort 函数 ** @param arr 待排序数组
  • @param sz 待排序元素个数
  • @param width 待排序元素大小(字节),由于传入的是 void * 类型,无法得知具体类型,需要传入元素大小
  • @param compare 比较函数 */void bubbleSort(void *arr, size_t sz, size_t width, int (*compare)(const void *e1, const void *e2)) { sz = (int)sz; width = (int)width; for(int i = 0; i < sz - 1; i++) { for(int j = 0; j < sz - 1 - i; j++) { if(compare((char *)arr + j * width, (char *)arr + (j + 1) * width) > 0) // 通过起始位置 + 偏移量来访问元素 { swapMem((char *)arr + j * width, (char *)arr + (j + 1) * width, width); // 交换两个元素 } } } } int main() { int arr[] = {9, 8, 7, 6, 5, 4, 3, 2, 1}; int sz = sizeof(arr) / sizeof(arr[0]); bubbleSort(arr, sz, sizeof(arr[0]), compareInt); for(int i = 0; i < sz; i++) { printf("%d “, arr[i]); } printf("\n”); struct Stu s[3] = {{“zhangsan”, 20}, {“lisi”, 30}, {“wangwu”, 10}}; sz = sizeof(s) / sizeof(s[0]); bubbleSort(s, sz, sizeof(s[0]), compareStu); for(int i = 0; i < sz; i++) { printf("%s %d\n”, s[i].name, s[i].age); } return 0; }