热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

C语言指针形参(指向指针的指针形参)

一、通过指针形参在子函数改变常量大家都知道,C语言子函数的形参,是可以为普通数据类型,也可以为指针的。最初遇到这问题,是在学习STM32的库函数的使用。当初刚接触库函数,对于函数初

一、通过指针形参在子函数改变常量

大家都知道,C语言子函数的形参,是可以为普通数据类型,也可以为指针的。最初遇到这问题,是在学习STM32的库函数的使用。当初刚接触库函数,对于函数初始化接口,如:

GPIO_Init(GPIOA, &GPIO_InitStructure);
为什么要取初始化结构体变量的地址传递进库函数(&GPIO_InitStructure),而不是直接将结构体变量本身(GPIO_InitStructure)传递进去,不甚了解。直到后来,程序中指针用得多了才有所理解,在这里做个记录。先上代码:

#include
void AddNum1(int data)
{
  data++;
}
void AddNum2(int *data)
{
  (*data)++; //自增运算符“++”优先级高于取值符“*”
  printf("AddNum2()运行结果:\r\n\r\n*data = %d\r\n", *data);
  printf(" data = 0x%08X\r\n", data);
  printf("&data = 0x%08X\r\n\r\n", &data);
}
int main(void)
{
  int Num1 = 1;
  //测试AddNum1
  AddNum1(Num1);
  printf("AddNum1()运行结果:\r\n\r\nNum1 = %d\r\n", Num1);
  Num1 = 1;
  printf("\r\n\r\n");
  //测试AddNum2
  AddNum2(&Num1);
  printf(" Num1 = %d\r\n", Num1);
  printf("&Num1 = 0x%08X\r\n", &Num1);
  getchar();
}

运行结果如图:
技术分享图片

 

编译环境为VS2015。

可知,AddNum1没有改变Num1的值,而AddNum2将Num1的值自增了1。分析:

(1)对于子函数形参的理解:

        主函数中的代码“AddNum1(Num1);”。实质上,它将Num1的值赋值给了子函数的形参“data”。

        可将“AddNum1(Num1);”代码理解为运行了以下代码:

void AddNum1()
{
  int data = Num1;
  data++;
}

通俗的解释就是,子函数声明了一个整型常量“data”,用“data”缓存“Num1”的值。函数中的其他代码,是针对“data”进行运算的,而“Num1” 除了把它自身的值传递给“data”外没有其他任何操作。所以,“AddNum1();” 这个函数并有没改变“Num1”的值。

(2)指针形参的作用:

        我们在对常量,或者是指针进行操作的时候,实质上是对其对应的内存进行操作。对“AddNum2(&Num1);”运行结果以 内存分布图诠释如下:

技术分享图片

1、可知,Num1的地址是0x00600FFA0C,”AddNum2(int *data)“;声明了一个指针data,并且将Num1的地址赋值给了指  针 data,相当于执行了”data = 0x00600FFA0C;“,此时”*data“ 等同于”Num1“。

2、接下来的”(*data)++;“,操作的是指针data指向的内存”0x00600FFA0C“,这行代码使这个内存块上存储的常量自    增了1,所以”*data = 2“。由1可知,”*data“ 等同于 ”Num1“,所以“*data = 2 = Num1”。

 总结:通过将变量地址传递进子函数,在子函数内操作该地址的内存上存储的数据可达到改变变量的目的。

二、通过指向指针的指针在子函数改变指针的值

这种情况我用得比较少。不过在调用内存管理函数的时候可能会用到。如下代码:

主函数声明了一个指向0x00000001地址的char型指针pMemory,并通过子函数申请内存,将申请得到的地址赋值给pMemory

#include
#include
void GetMemory1(char *pAddr)
{
  pAddr = (char *)malloc(sizeof(char) * 100);
}
void GetMemory2(char **pAddr)
{
  *pAddr = (char *)malloc(sizeof(char) * 100);
  printf(" *pAddr = 0x%08x\r\n", *pAddr);
  printf(" pAddr = 0x%08x\r\n", pAddr);
  printf(" &pAddr = 0x%08x\r\n\r\n", &pAddr);
}
int main(void)
{
  char *pMemory = 0x00000001;
  printf(" pMemory = 0x%08x\r\n", pMemory);
  printf("&pMemory = 0x%08x\r\n\r\n", &pMemory);
  GetMemory1(pMemory);

  printf("/*******GetMemory1();********/\r\n\r\n");
  printf(" pMemory = 0x%08x\r\n", pMemory);
  printf("&pMemory = 0x%08x\r\n\r\n", &pMemory);
  printf("/*******GetMemory2();********/\r\n\r\n");
  GetMemory2(&pMemory);
  printf(" pMemory = 0x%08x\r\n", pMemory);
  printf("&pMemory = 0x%08x\r\n", &pMemory);


  getchar();
}
运行结果:

 技术分享图片

如上,GetMemory1();并不能将地址赋值给pMemory,而GetMemory2();成功将申请得到的地址赋值给pMemory。分析:

(1)GetMemory1();

        类似于“AddNum1();”,声明了一个指针pAddr,然后将pMemory的值赋值给pAddr。后续代码改变的是pAddr的数据 而没有改变pMemory。所以没有成功地将申请得到的地址赋值给pMemory。

(2)GetMemory2();

        先上图:

 技术分享图片

  进入函数后,pMemory的地址0x008FF718赋值给了pAddr。malloc();申请得到的内存空间的地址0x02BB4D80赋值给了pAddr所指向的内存“*pAddr”(0x008FF718)。又因为*pAddr = pMemory; ,所以申请得到的内存地址成功赋值给了pMemory。

  综上所述,当数据被传递进子函数,如需通过子函数改变数据的值,需将它的地址作为形参传递进函数(无论常量亦或是指针)。


推荐阅读
  • 单链表的高效遍历及性能优化策略
    本文探讨了单链表的高效遍历方法及其性能优化策略。在单链表的数据结构中,插入操作的时间复杂度为O(n),而遍历操作的时间复杂度为O(n^2)。通过在 `LinkList.h` 和 `main.cpp` 文件中对单链表进行封装,我们实现了创建和销毁功能的优化,提高了单链表的使用效率。此外,文章还介绍了几种常见的优化技术,如缓存节点指针和批量处理,以进一步提升遍历性能。 ... [详细]
  • iOS 设备唯一标识获取的高效解决方案与实践
    在iOS 7中,苹果公司再次禁止了对MAC地址的访问,使得开发者无法直接获取设备的物理地址。为了在开发过程中实现设备的唯一标识,苹果推荐使用Keychain服务来存储和管理唯一的标识符。此外,还可以结合其他技术手段,如UUID和广告标识符(IDFA),以确保设备的唯一性和安全性。这些方法不仅能够满足应用的需求,还能保护用户的隐私。 ... [详细]
  • 本文探讨了如何有效地构建和优化微信公众平台账号,涵盖了用户信息管理、内容创作与发布、互动策略及数据分析等方面。通过合理设置用户信息字段,如用户名、昵称、密码、真实姓名和性别等,确保账号的安全性和用户体验。同时,文章还介绍了如何利用微信公众平台的各项功能,提升用户参与度和品牌影响力。 ... [详细]
  • 将解压缩版Tomcat集成至系统服务
    将解压缩版Tomcat集成至系统服务的方法如下:首先,在命令行中导航至Tomcat的`bin`目录,运行`service.bat install`命令以安装服务。需要注意的是,服务名称和显示名称已在`service.bat`脚本中预设,默认情况下会随不同版本有所变化。此外,建议检查并配置相关参数,确保服务能够稳定运行。 ... [详细]
  • 资源管理器的基础架构包括三个核心组件:1)资源池,用于将CPU和内存等资源分配给不同的容器;2)负载组,负责承载任务并将其分配到相应的资源池;3)分类函数,用于将不同的会话映射到合适的负载组。该系统提供了两种主要的资源管理策略。 ... [详细]
  • 本文深入解析了Java面向对象编程的核心概念及其应用,重点探讨了面向对象的三大特性:封装、继承和多态。封装确保了数据的安全性和代码的可维护性;继承支持代码的重用和扩展;多态则增强了程序的灵活性和可扩展性。通过具体示例,文章详细阐述了这些特性在实际开发中的应用和优势。 ... [详细]
  • 初探性能优化:入门指南与实践技巧
    在编程领域,常有“尚未精通编码便急于优化”的声音。为了从性能优化的角度提升代码质量,本文将带领读者初步探索性能优化的基本概念与实践技巧。即使程序看似运行良好,数据处理效率仍有待提高,通过系统学习性能优化,能够帮助开发者编写更加高效、稳定的代码。文章不仅介绍了性能优化的基础知识,还提供了实用的调优方法和工具,帮助读者在实际项目中应用这些技术。 ... [详细]
  • 作为软件工程专业的学生,我深知课堂上教师讲解速度之快,很多时候需要课后自行消化和巩固。因此,撰写这篇Java Web开发入门教程,旨在帮助初学者更好地理解和掌握基础知识。通过详细记录学习过程,希望能为更多像我一样在基础方面还有待提升的学员提供有益的参考。 ... [详细]
  • 在众多市场调研公司中,如何选择一家值得信赖的合作伙伴至关重要。基于我在市场调查行业近二十年的经验,我将推荐几家国内知名的市场调研机构,供您参考:1. 开元研究——专注于零售报刊发行研究、媒体广告价值评估及网络营销分析等领域,以其专业性和准确性赢得了广泛认可。 ... [详细]
  • 具备括号和分数功能的高级四则运算计算器
    本研究基于C语言开发了一款支持括号和分数运算的高级四则运算计算器。该计算器通过模拟手算过程,对每个运算符进行优先级标记,并按优先级从高到低依次执行计算。其中,加减运算的优先级最低,为0。此外,该计算器还支持复杂的分数运算,能够处理包含括号的表达式,提高了计算的准确性和灵活性。 ... [详细]
  • C# .NET 4.1 版本大型信息化系统集成平台中的主从表事务处理标准示例
    在C# .NET 4.1版本的大型信息化系统集成平台中,本文详细介绍了主从表事务处理的标准示例。通过确保所有操作要么全部成功,要么全部失败,实现主表和关联子表的同步插入。主表插入时会返回当前生成的主键,该主键随后用于子表插入时的关联。以下是一个示例代码片段,展示了如何在一个数据库事务中同时添加角色和相关用户。 ... [详细]
  • DRF框架中Serializer反序列化验证机制详解:深入探讨Validators的应用与优化
    在DRF框架的反序列化验证机制中,除了基本的字段类型和长度校验外,还常常需要进行更为复杂的条件限制校验。通过引入`validators`模块,可以实现自定义校验逻辑,如唯一字段校验等。本文将详细探讨`validators`的使用方法及其优化策略,帮助开发者更好地理解和应用这一重要功能。 ... [详细]
  • 2012年9月12日优酷土豆校园招聘笔试题目解析与备考指南
    2012年9月12日,优酷土豆校园招聘笔试题目解析与备考指南。在选择题部分,有一道题目涉及中国人的血型分布情况,具体为A型30%、B型20%、O型40%、AB型10%。若需确保在随机选取的样本中,至少有一人为B型血的概率不低于90%,则需要选取的最少人数是多少?该问题不仅考察了概率统计的基本知识,还要求考生具备一定的逻辑推理能力。 ... [详细]
  • 并发编程入门:初探多任务处理技术
    并发编程入门:探索多任务处理技术并发编程是指在单个处理器上高效地管理多个任务的执行过程。其核心在于通过合理分配和协调任务,提高系统的整体性能。主要应用场景包括:1) 将复杂任务分解为多个子任务,并分配给不同的线程,实现并行处理;2) 通过同步机制确保线程间协调一致,避免资源竞争和数据不一致问题。此外,理解并发编程还涉及锁机制、线程池和异步编程等关键技术。 ... [详细]
  • 男性健康问题常常被忽视,许多人对疾病持轻视态度,即使出现症状也往往置之不理,认为会自行好转。然而,现代男性在健康管理方面应当重视医生的专业建议。以下是十个关键点,包括但不限于:胸口疼痛应及时就医、定期进行体检、保持合理饮食和适量运动等,以维护整体健康。 ... [详细]
author-avatar
剡亚军_191
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有