嵌入式Day7

嵌入式Day7

一、指针基础概念

  1. 指针与指针变量
    • 指针:某1个字节虚拟内存空间的唯一性标识,即地址。
    • 指针变量:用于存储指针(其它变量地址)的变量。日常说的指针常指指针变量,但两者含义不同。
    • 指针变量特性:本身是变量,有自己的地址;存储的数据是其它变量的地址,通过该地址指向并操作其它变量,实现对内存的操作。32位平台占4个字节,64位平台占8个字节。
  2. 指针变量声明语法
    • 语法格式:数据类型 *指针名。
    • 注意事项:声明中的数据类型代表指针指向对象的数据类型,指针不能乱指;指针名是普通变量名,是运算符,非指针名一部分,建议指针名带“p/ptr”等字眼;声明时要紧跟指针名,避免歧义,建议每行只声明一个变量,除非多个变量类型一致且作用相似。
  3. C语言变量声明建议
    • 尽量每行只声明一个变量,除非多个变量类型一致且作用相似。
    • 将*紧跟指针名,如“int *ptr;”,明确变量为指针类型。
    • 为变量添加有意义的注释,尤其是变量名无法完全描述其用途时。
    • 使用描述性和有意义的变量名,注重代码的清晰性和可读性。

二、指针变量的初始化

  1. 常见初始化方式:用变量名做取地址&运算,然后赋值给指针变量。使用指针访问内存需两次,使用变量名是直接访问内存。
  2. 空指针初始化:使用空指针NULL给指针变量初始化,NULL代表地址值0,是指针类型变量的默认零值。空指针不能解引用操作,是指针不可用的标志。
  3. 野指针:指向随机、未定义内存区域的指针,常见于局部变量指针仅声明未初始化的情况。解引用野指针会带来不可预知的后果,程序中要竭力避免,若不确定指针初始化值,可先初始化为NULL。

三、指针类型作为参数传递

  1. 通过指针传参交换变量值:在C语言函数调用中,传参局部变量的指针可以跨函数修改局部变量取值,符合值传递本质;直接传参基本数据类型不能通过函数调用修改实参数据。
  2. 指针传参时指针指向的修改问题:传参指针变量在函数内部不能修改原始指针的指向,因为函数内部得到的是实参指针变量的副本。若想修改局部变量指针的指向,需要传参指针的指针,即二级指针。
  3. 二级指针的操作:二级指针解引用1次可修改原始一级指针的指向,解引用2次可修改原始一级指针指向的数据。若想跨函数修改二级指针的指向,需要传参二级指针的指针,即三级指针。

四、指针和数组名之间的关系

  1. 数组名的本质与特性:数组名是代表数组在内存中起始地址(基地址、首元素地址)的标识符。多数情况下,数组名可视为指向首元素的指针,但本质并非指针,而是代表整个数组,不能直接用“=”赋值,数组间也不能直接用“=”赋值。
  2. 数组名不代表首元素指针的场景
    • &(数组名):获取的是指向整个数组的指针(数组指针),虽地址值与首元素地址相同,但指针类型不同,前者为数组指针类型,后者为普通元素类型指针(如int*)。
    • sizeof(数组名):得到的是数组所占内存空间总长度。不过,当数组作为参数传递后,函数内部的数组名在sizeof操作中会变成指针。
  3. 数组名可以视为首元素指针的常见场景
    • 初始化指针变量:可直接用数组名初始化指针变量,如int *p = arr;
    • 数组作为函数参数:数组作为参数传给函数时,函数内部得到的数组名会“退化”为首元素指针。
    • 指针算术运算:数组名可直接进行指针算术运算,此时被当作首元素指针使用。

五、数组指针与指针数组

  1. 数组指针:本质是指针,指向整个数组变量。声明方式如int (*ptr)[3];(*)使*运算符先与变量名结合,整体表示一个指向长度为3的int类型数组的指针变量。
  2. 指针数组:本质是数组,数组元素类型为指针类型。声明方式如int *arr[3];[]运算优先级高,先与变量名结合,此数组名为arr,存储元素类型为int*,数组长度为3。在C语言中,字符串数组常以指针数组形式实现。

六、取地址数组名和取地址数组首元素/数组名的区别

  1. 相同点&arr(指向整个数组的数组指针)和&arr[0](指向数组第一个元素的指针,数组名在多数情况也视为首元素指针,故&arr[0]arr意思相近 )存储的地址值相同,均为数组第一个元素的地址值。
  2. 不同点:指针类型不同,&arr得到的指针类型是数组指针类型(如int(*)[3]),&arr[0]得到的指针类型是单纯的int类型指针(int*)。

七、指针类型转换的问题

给指针变量赋值时,使用“=”应尽量保证左右值指针类型一致,避免隐式类型转换。若类型不匹配,可使用强制类型转换,如int *p2 = (int*) &arr;

八、把数组作为参数传递

  1. C语言数组传参特点:C语言数组传参不遵循典型值传递原则,数组名传参给函数后会自动“退化”为首元素指针,函数内部得到的是实参数组名首元素指针的“副本”。
  2. 优点:传参效率高,无需复制整个原始数组,节省空间;利用首元素指针副本可修改原始数组内容;传参类型更灵活,只要类型相同,不同长度数组可一起处理。
  3. 缺点与解决方法:数组传参后,函数内部无法用传参的数组名获取数组长度,使用sizeof(数组名)/sizeof(数组名[0])是错误做法,因为此时数组名是首元素指针,sizeof(数组名)结果为指针变量大小(32位平台4字节,64位平台8字节)。函数内部获取数组长度只能通过外部函数调用传参,任何传参数组的C语言函数都必须额外新增数组长度参数。
  4. 函数内部操作数组元素:数组传参后,在函数内部仍可用“[]”取下标运算符操作数组元素,“arr[i]”中的数组名arr此时已是首元素指针,“[]”本质是指针运算。

九、求数组元素的最值

需求是在main函数获取数组的最大值和最小值,可通过get_max_min函数实现。该函数声明为void get_max_min(int arr[], int len, int *pmax, int *pmin);,其中pmaxpmin分别为main函数中存储最大值和最小值的int变量的指针。函数内部假设第一个元素为最大最小值,遍历数组进行比较和更新。在main函数中,需传参数组、数组长度以及存储最值的变量指针。

十、Constant Pointer vs Pointer to Constant

  1. 概念理解:建议记英文“Constant Pointer”(自身是常量的指针,不可改变指向)和“Pointer to Constant”(不可以通过指针修改指向内容的指针 )。
  2. const修饰指针的形式与辨析
    • const修饰指针类型有const int *p;int const *p;int *const p;三种形式。当const直接紧跟指针名时,修饰指针本身,为“Constant Pointer”,指针不可改变指向;当const不紧跟指针名时,修饰指向的内容,为“Pointer to Constant”,不可通过指针修改指向内容。
    • 普通指针可修改指向和指向内容;const修饰*p时,指针指向内容不可修改,但指针可修改指向;const直接修饰指针名时,指针指向不可修改。实际编程中,const大多修饰“*p”,常见形式为const int *p = &a;

十一、指针的算术运算

指针变量支持的运算包括指针加法、减法、自增自减、两个指针相减以及比较大小,其他运算不合法。

  1. 指针加上一个整数:指针加上整数n,是在原地址基础上增加该指针指向数据类型大小乘以n个字节。
  2. 指针减去一个整数:指针减去整数n,即在原地址基础上减去该指针指向数据类型大小乘以n个字节。
  3. 取下标运算符的原理:取下标[]运算符的底层原理是指针算术运算和解引用运算。arr[i]等价于*(arr + i)[]运算符隐藏了这些底层操作,是一种“语法糖”。由于加法交换律,arr[i]还等价于i[arr],但实际编程中不建议使用i[arr]这种形式。
  4. 指针自增和自减:使用++--对指向数组元素的指针进行操作时,自增使指针指向下一个元素,自减使指针指向前一个元素。自增和自减操作具有副作用,而单纯的加减操作没有副作用。
  5. 两个指针之间做减法:两个指向同一数组(或连续内存块)元素的指针相减,返回的是它们之间的元素数差,而非实际字节差。
  6. 指针判等:通过判断两个指针存储的地址值是否相同,来确定两个指针是否指向同一个对象,常用于判断指针是否为空指针NULL
  7. 指针比较大小:比较的是指针中存储的地址值大小。在数组场景中,如果两个指针都指向同一数组的不同元素,可通过比较大小确定哪个指针指向的元素在数组中的位置更靠前。

十二、指针运算的注意事项

  1. 避免野指针:运算前确保指针已正确初始化,防止出现野指针。
  2. 避免对NULL指针运算:使用指针前检查是否为NULL,存在为NULL的可能时先进行空指针检查。
  3. 确保操作范围合法:在数组内进行指针算术运算时,运算结果的指针必须指向数组内部合法位置,否则可能导致越界访问、产生野指针和未定义行为。
  4. 限制运算类型:指针只支持加法、减法,不支持乘法、除法、取模运算;两个指针不能相加;不同类型的指针不应混合运算。
  5. 指针相减条件:两个指针做减法时,必须指向同一个数组或连续内存块,否则结果未定义。

十三、*p++结构

*p++结合了解引用运算符和自增运算符。后缀++优先级高于解引用运算符*,先返回指针p原本的地址,再让指针p指向下一个元素;*p++整体先返回指针p原本指向元素的取值,同时使p指针指向下一个元素。

十四、遍历数组的方式

  1. 利用指针遍历数组:创建临时指针指向数组首元素,通过指针自增遍历数组。
  2. 利用索引遍历数组:使用数组下标遍历数组,这种方式更推荐。

十五、二维数组与数组指针分析

  1. 二维数组与数组指针关系:对于二维数组int matrix[5][5]matrix可视为首元素指针,其首元素是第一个长度为5的int类型一维数组,matrix是指向该数组的指针,即数组指针,从地址值看,它指向元素1;从指针类型看,是int[5] * 类型,声明格式为int (*matrix)[5]
  2. 复杂表达式计算:以*(*(matrix + 2) +3)为例,逐步分析指针运算和解引用操作。
  3. 二维数组作为参数传递时函数声明
    • void test(int arr[][5], int len);len表示二维数组的长度(行长,即一维数组的个数),5是二维数组的列长(不能省略)。
    • void test2(int (*arr)[5], int len);:直接使用数组指针类型声明参数,与上述形式等价。这条消息已经在编辑器中准备就绪。你想如何调整这篇文档?请随时告诉我。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值