C语言 进阶指针学习笔记

文章目录

    • 字符指针
    • 指针数组
    • 数组指针
      • 数组名
      • 数组传参
    • 函数指针
    • 函数指针数组
      • 指向函数指针数组的指针
    • 回调函数
      • Qsort 的使用
      • 通过冒泡排序模拟实现 qsort

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

字符指针

#include <stdio.h>  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 <stdio.h>  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. 存在两个例外
    1. sizeof (数组名),这里的数组名表示整个数组
    2. &数组名,表示的依然是整个数组

类型(对 int arr[5]]):

  • arr -> int*
  • &arr -> int (*)[5]
#include <stdio.h>  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 <stdio.h>  /**  * @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);
// errvoid test1(int (*arr)[5]);
// 参数为一个一维数组void test2(int **arr);
// err,二级指针指向存放一级指针的地址,不能是指向一个一维数组的指针

函数指针

指向函数的指针

#include <stdio.h>  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 <stdio.h>  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 <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()  
{  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 <stdio.h>  
#include <stdlib.h>  /**  * @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 <stdio.h>  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;  
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/71834.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

李沐《动手学深度学习》——14.9. 用于预训练BERT的数据集——wiki数据集问题以及存在的其他问题

问题1&#xff1a;出现"file is not a zip file" 原因是链接已经失效。 解决方法&#xff1a;打开下面链接自行下载&#xff0c;需要魔法。下载完解压到特定位置。 下载链接&#xff1a;项目首页 - Wikitext-2-v1数据包下载:Wikitext-2-v1 数据包下载本仓库提供了一…

【芯片验证】verificationguide上的36道UVM面试题

跟上一篇一样,verificationguide上的36到UVM面试题,通义回答ds判卷。 1. What is uvm_transaction, uvm_seq_item, uvm_object, uvm_component? uvm_transaction、uvm_seq_item、uvm_object、uvm_component是什么? uvm_transaction是UVM中所有事务的基础类,用于表示仿真…

Python 动态规划(DP)套路总结

Python 动态规划&#xff08;DP&#xff09;套路总结 在解决算法问题时&#xff0c;动态规划&#xff08;DP&#xff09; 是一种非常常见的优化技巧&#xff0c;它可以通过保存子问题的结果来避免重复计算&#xff0c;从而减少时间复杂度。Python 提供了非常方便的语法特性&am…

ESP32驱动OV3660摄像头实现yoloV5物体分类(摄像头支持红外夜视、边缘AI计算)

目录 1、传感器特性 2、硬件原理图 3、驱动程序 ESP32-S3 AI智能摄像头模块是一款专为智能家居和物联网应用打造的高性能边缘AI开发模组。它集成了摄像头、麦克风、音频功放、环境光传感器和夜视补光灯,无需依赖云端即可实现本地化AI推理。 凭借TensorFlow Lite、YOLO和O…

RReadWriteLock读写锁应用场景

背景 操作涉及一批数据&#xff0c;如订单&#xff0c;可能存在多个场景下操作&#xff0c;先使用读锁&#xff0c;从redis缓存中获取操作中数据 比如 关闭账单&#xff0c; 发起调账&#xff0c; 线下结算&#xff0c; 合并支付 先判断当前操作的数据&#xff0c;是否在…

网络安全高级软件编程技术 网络安全 软件开发

安全软件开发入门 软件安全问题 有趣的《黑客帝国》终极解释&#xff1a; 《黑客帝国》故事里面的人物关系&#xff0c;就像电脑里面的各种程序的关系一样&#xff1a; 电脑里面的系统程序&#xff1a;Matrix&#xff1b; 病毒程序&#xff1a;以Neo为首的人类&#xff1b; 防病…

苹果商店上架流程,app上架发布流程

苹果商店地址 https://appstoreconnect.apple.com/login 其他地址:开发 - Apple Developer 1.更新代码 将项目的代码更新到最新,更新成功后右下角会给出提示 2.打开模拟器 鼠标右键可以选择设备(Device) 3.测试运行 如下图可以看到已经识别到设备了,点击运行即可,运行到模…

正点原子[第三期]Arm(iMX6U)Linux移植学习笔记-2.1 uboot简介

前言&#xff1a; 本文是根据哔哩哔哩网站上“Arm(iMX6U)Linux系统移植和根文件系统构键篇”视频的学习笔记&#xff0c;在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。 引用&#xff1a; …

Better-SQLite3 参数绑定详解

Better-SQLite3 参数绑定详解 在使用 better-sqlite3 进行数据库操作时&#xff0c;参数绑定是一个非常重要的概念。它不仅提高了代码的可读性和安全性&#xff0c;还能有效防止 SQL 注入攻击。本文将详细介绍如何在 better-sqlite3 中使用匿名参数和命名参数&#xff0c;并展…

C++编程:进阶阶段—4.1封装

C面向对象的三大特性&#xff1a;封装、继承、多态 具有相同性质的对象&#xff0c;抽象为类 4.1 封装 封装的意义&#xff1a;将属性和行为作为一个整体&#xff0c;表现生活中的事物&#xff0c;并将属性和行为加以权限控制。 4.1.1 类的定义及实例化对象 语法&#xff…

运行OpenManus项目(使用Conda)

部署本项目需要具备一定的基础&#xff1a;Linux基础、需要安装好Anaconda/Miniforge&#xff08;Python可以不装好&#xff0c;直接新建虚拟环境的时候装好即可&#xff09;&#xff0c;如果不装Anaconda或者Miniforge&#xff0c;只装过Python&#xff0c;需要确保Python是3.…

spring boot + vue 搭建环境

参考文档&#xff1a;https://blog.csdn.net/weixin_44215249/article/details/117376417?fromshareblogdetail&sharetypeblogdetail&sharerId117376417&sharereferPC&sharesourceqxpapt&sharefromfrom_link. spring boot vue 搭建环境 一、浏览器二、jd…

MPPT与PWM充电原理及区别详解

MPPT&#xff08;最大功率点跟踪&#xff09;和PWM&#xff08;脉宽调制&#xff09;是太阳能充电控制器中常用的两种技术&#xff0c;它们在原理、效率和适用场景上有显著区别。以下是两者的详细对比&#xff1a; 1. 工作原理 PWM&#xff08;脉宽调制&#xff09; 核心机制…

slam学习笔记9---ubuntu2004部署interactive_slam踩坑记录

背景&#xff1a;interactive_slam是一款可用于离线优化点云地图算法。部署安装容易出问题&#xff0c;这里记录一下。 一、安装基本流程 绝大部分跟着readme走&#xff0c;g2o安装使用apt安装 interactive_slam depends on the following libraries:GL3W GLFW Dear ImGui p…

视觉图像处理

在MATLAB中进行视觉图像处理仿真通常涉及图像增强、滤波、分割、特征提取等操作。以下是一个分步指南和示例代码,帮助您快速入门: 1. MATLAB图像处理基础步骤 1.1 读取和显示图像 % 读取图像(替换为实际文件路径) img = imread(lena.jpg); % 显示原图 figure; subplot(2…

用java如何利用jieba进行分词

在Java中使用jieba进行分词&#xff0c;可以借助jieba的Java版本——jieba-analysis。jieba-analysis是一个基于jieba分词算法的Java实现&#xff0c;支持精确模式、全模式和搜索引擎模式等多种分词方式。 以下是使用jieba-analysis进行分词的详细步骤和示例代码&#xff1a; …

【含文档+PPT+源码】Python爬虫人口老龄化大数据分析平台的设计与实现

项目介绍 本课程演示的是一款Python爬虫人口老龄化大数据分析平台的设计与实现&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Python学习者。 1.包含&#xff1a;项目源码、项目文档、数据库脚本、软件工具等所有资料 2.带你从零开始部署运行本…

【A2DP】SBC 编解码器互操作性要求详解

目录 一、SBC编解码器互操作性概述 二、编解码器特定信息元素(Codec Specific Information Elements) 2.1 采样频率(Sampling Frequency) 2.2 声道模式(Channel Mode) 2.3 块长度(Block Length) 2.4 子带数量(Subbands) 2.5 分配方法(Allocation Method) 2…

Android双亲委派

下面是一份 Android 类加载器双亲委派机制的时序图示例&#xff0c;描述了当应用调用 loadClass() 时&#xff0c;各个加载器之间的委派过程。 #mermaid-svg-rBdlhpD2uRjBPiG8 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mer…