C语言程序设计重庆大学课件第05章指针与函数_第1页
C语言程序设计重庆大学课件第05章指针与函数_第2页
C语言程序设计重庆大学课件第05章指针与函数_第3页
C语言程序设计重庆大学课件第05章指针与函数_第4页
C语言程序设计重庆大学课件第05章指针与函数_第5页
已阅读5页,还剩27页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Chapter05C语言程序设计第05章指针与函数重庆大学计算机学院·程序设计基础课程Contents本章目录C语言程序设计·第05章指针与函数01指针基础与核心概念02指针与数组的深层关系03函数指针与回调机制04指针作为函数参数的高级应用05指针数组与多级指针06总结回顾与学习展望Chapter01指针基础与核心概念从内存地址到指针变量,建立对指针本质的深层理解Chapter05·Pointers指针的基本概念与用途指针是C语言中存储内存地址的特殊变量,通过指针可以实现对数据的间接访问与直接内存操作,这是C语言高效性和灵活性的核心基础,也是理解后续数组、函数、动态内存等高级特性的关键前提。01地址存储机制指针变量的本质是存储另一个变量的内存地址,而非直接存储数据值本身,这使得程序可以通过地址间接访问和修改目标变量02内存地址模型内存可以类比为带编号的储物柜阵列,每个字节都有唯一地址,指针记录目标数据的起始地址,编译器根据指针类型确定读取范围03核心应用场景指针的核心用途包括:实现函数间的数据共享与修改、高效遍历数组与字符串、动态内存分配与管理、构建链表等复杂数据结构04底层控制能力C语言通过指针提供对硬件层的直接操作能力,这也是C语言在操作系统、嵌入式系统等底层开发领域不可替代的根本原因Chapter05·指针与函数指针变量的声明与初始化指针变量的声明需指定目标数据类型并使用星号标识,初始化时应赋予有效地址或NULL,未初始化的'野指针'是C语言程序中最常见且最难排查的错误来源之一。01声明语法为'数据类型*指针名',如int*p表示p指向int型数据,编译器根据类型决定解引用时读取的字节数(int通常4字节,char为1字节)4/1Byte02星号与变量名绑定而非类型绑定:int*p1,p2中仅p1是指针,p2是普通int变量;若要同时声明两个指针需写为int*p1,*p2*绑定变量03初始化方式包括:赋值为已有变量地址(p=&a)、赋值为NULL表示空指针、赋值为同类型指针的值;未初始化的指针包含随机地址值&a·NULL·ptr04野指针危害:未初始化或已释放后未置NULL的指针指向不确定内存区域,对其进行解引用操作可能导致段错误、数据损坏或难以复现的隐蔽bug野指针CHAPTER05·POINTERS指针运算符:取地址与解引用取地址运算符(&)获取变量的内存地址,解引用运算符(*)通过地址访问目标数据,二者构成指针操作的核心闭环——前者从"数据找地址",后者从"地址找数据",是理解所有指针应用的语法基础。取地址运算符&作用于普通变量,返回该变量在内存中的起始地址。如&a返回整型变量a的地址,返回值类型为int*(指向int的指针)。&a→int*解引用运算符*作用于指针变量,访问指针所指向地址中存储的实际数据值。如*p读取p指向的int值;*p=100则将100写入目标内存。*p↔value互为逆操作*&a等价于a本身,&*p等价于p本身(当p指向有效变量时)。理解这种对称性是掌握指针思维的关键。*&a≡a常见错误对未初始化指针或NULL指针解引用会导致程序崩溃(段错误);对void*指针直接解引用在C语言中不合法,需先进行类型转换。SEGFAULTMemoryModel指针的内存模型与存储布局指针变量本身也占用内存空间(32位系统4字节,64位系统8字节),它存储的值是目标变量的地址而非数据本身,理解'指针也有地址、指针的值是地址、地址指向数据'这三层关系是建立正确内存模型的关键。内存布局示意(inta=42;int*p=&a;)变量名存储地址(示例)存储的值占用字节说明a0x7fff5000424普通int变量,存储实际数据p0x7fff50080x7fff50008(64位)指针变量,存储a的地址&a—0x7fff5000—取地址运算,得到a的地址*p—42—解引用运算,读取p指向的值&p—0x7fff5008—指针自身也有地址指针变量p存储在地址0x7fff5008处,其值为变量a的地址0x7fff5000,通过*p可读取a的值42CHAPTER05·POINTERARITHMETIC指针的算术运算与移动规则指针的算术运算以所指数据类型的大小为单位进行地址偏移,而非简单的地址数值加减——int*指针加1实际偏移4字节,char*指针加1偏移1字节,这一设计使指针能自然地按元素粒度遍历连续内存空间。01地址偏移公式p+n的实际地址计算为p+n×sizeof(指针所指类型):int*p在地址1000处,p+1的实际地址为1004而非1001,确保每次移动恰好跳过一个完整元素。p+n×sizeof(T)02自增与自减p++使指针移向下一个同类型元素,p--移向上一个元素;注意*p++与(*p)++的区别——前者移动指针,后者修改所指值。*p++vs(*p)++03指针相减两个指向同一数组的指针相减得到它们之间的元素个数(而非字节数),如p2−p1=3表示中间相隔3个元素,常用于计算数组长度。p2−p1=n04非法运算指针之间不能相加、不能乘以常数、不能进行位运算;不同类型指针之间不能直接相减,必须确保指向同一连续内存区域才有意义。p1+p2×p×n×p&q×CHAPTER02指针与数组的深层关系从数组名到指针常量,揭示数组访问的底层机制Chapter05·Pointers&Arrays指针与一维数组:数组名的退化机制数组名在表达式中自动退化为指向首元素的指针常量,数组下标运算a[i]的本质是*(a+i)——编译器将所有数组访问统一转化为指针偏移加解引用操作,这是C语言"一切皆地址"设计哲学的核心体现。01数组名退化规则inta[10]中,a在表达式中等价于&a[0](类型为int*),但sizeof(a)和&a是例外——前者返回整个数组大小,后者返回指向数组的指针。02下标运算本质a[i]被编译器翻译为*(a+i),因此a[i]与i[a]在语法上完全等价(虽然后者极少使用),都是先偏移i个元素再解引用。03指针遍历数组可用for循环配合指针移动遍历整个数组,如for(int*p=a;p<a+10;p++),比下标方式更高效,直接操作地址避免了重复计算偏移。04数组名是指针常量a的值(首元素地址)不可修改,a++是非法的;但可以声明指针p=a后对p进行移动操作,实现灵活的数组遍历。Chapter05·Pointers&Arrays指针与二维数组:行优先存储与行指针二维数组在内存中按行优先顺序线性排列,数组名a的类型是'指向含N个元素的一维数组的指针'(int(*)[N]),a+i指向第i行的起始位置,访问a[i][j]的本质是*(*(a+i)+j)的两级偏移解引用。01行优先存储:inta[3][4]在内存中占据12个连续int空间,排列顺序为a[0][0]、a[0][1]…a[0][3]、a[1][0]…a[2][3],逻辑上的二维结构在物理上是一维线性排列inta[3][4]02行指针类型:a的类型为int(*)[4](指向含4个int的数组的指针),a+1偏移一整行(4个int=16字节),而非一个int;注意int*p与int(*)[4]是完全不同的类型int(*)[4]03元素访问链:a[i][j]的底层实现为*(*(a+i)+j)—第一步a+i定位第i行,第二步*(a+i)得行首地址,第三步+j偏移列,第四步解引用取值*(*(a+i)+j)04常见混淆:int*p=a是类型不匹配的(应为int(*p)[4]=a);若要逐元素遍历可用int*p=&a[0][0]将二维数组视为一维连续空间&a[0][0]CHAPTER05·POINTERS&FUNCTIONS指针与字符串:字符指针与字符串常量C语言字符串是以'\0'结尾的字符序列,通过字符指针(char*)进行操作。字符数组与字符指针的核心区别在于:数组在栈上创建可修改的副本,指针指向只读常量区——混淆二者是初学者最常见的运行时错误来源。01字符指针声明char*str="Hello"在只读数据区创建字符串常量,str存储其首地址,编译器自动追加'\0'作为终止标志。str[0]='H'可读,但修改str[0]将触发段错误(SegmentationFault)。SegmentationFault02字符数组对比charstr[]="Hello"在栈上分配6字节并复制内容,str[0]='h'合法。char*p="Hello"中p指向常量区,p[0]='h'非法——二者本质区别在于内存区域。Stackvs.rodata03字符串遍历while(*p){putchar(*p);p++;}利用指针移动与'\0'零值特性实现遍历。这是strlen、strcpy等C标准库函数的底层实现原理。while(*p)04字符串函数本质strcpy(dest,src)通过双指针同步移动逐字符复制直到'\0'。strcmp逐字符比较ASCII值判断字典序——所有字符串操作最终都是指针操作。strcpy/strcmpCHAPTER05·POINTERS&ARRAYS指针与数组等价表达式对照数组下标访问与指针偏移访问在编译器层面完全等价,理解各种表达形式的对应关系不仅能加深对底层机制的认识,还能帮助开发者在不同场景下选择最简洁高效的写法。数组a[5]={1,2,3,4,5}与指针p=a的等价表达式表达式等价形式值/含义说明a[2]*(a+2)/p[2]/*(p+2)3访问第3个元素,四种写法结果完全相同&a[0]a/p/&*p首元素地址数组名在表达式中退化为指针a+ip+i/&a[i]/&p[i]第i+1个元素地址地址偏移,偏移量为i×sizeof(int)*(a+i)a[i]/p[i]/*(p+i)第i+1个元素的值下标运算符的本质就是偏移后解引用sizeof(a)—(不等价于sizeof(p))20字节(5×4)sizeof(a)返回整个数组大小,sizeof(p)仅返回指针大小摘要:数组下标运算与指针偏移在编译器层面完全等价,但sizeof和取地址(&)操作是例外。CHAPTER03函数指针与回调机制将函数作为数据处理,实现运行时的动态调用与策略切换Chapter05·Pointers&Functions函数指针的定义与基本用法函数在内存中有确定的入口地址,函数指针是存储该地址的变量,通过它可以在运行时动态选择并调用不同的函数——这种"将函数作为数据处理"的能力是C语言实现多态、回调和插件架构的核心机制。01声明语法int(*pf)(int,int)声明pf为指向"接收两个int参数、返回int"类型函数的指针。括号中*pf表明pf是指针,后面(int,int)表明参数列表。int(*pf)(int,int)02赋值方式pf=add或pf=&add两种写法等价,因为函数名本身代表函数的入口地址,编译器自动处理函数名到地址的转换。pf=add≡pf=&add03调用方式pf(3,5)和(*pf)(3,5)均可调用目标函数,前者更简洁常用;调用时参数类型和数量必须与函数指针声明完全匹配。pf(3,5)≡(*pf)(3,5)04类型安全函数指针有严格的类型约束——参数类型、数量和返回值类型必须全部匹配,否则编译器报错。int(*pf)(int,int)≠void(double)Chapter05·Pointer&Function函数指针实战:策略切换与通用排序通过将比较函数作为函数指针参数传入通用排序算法,可以在不修改排序逻辑的前提下实现升序、降序或自定义排序策略的灵活切换——这正是C标准库qsort函数的核心设计思想。01通用排序函数接收函数指针参数voidsort(intarr[],intn,int(*cmp)(int,int)),排序框架调用cmp(arr[i],arr[j])判断交换顺序,实现算法与策略解耦void(*cmp)(int,int)02升序与降序比较函数灵活切换升序compareAsc当a>b时返回1表示需交换;降序compareDesc当a<b时返回1;调用sort(arr,10,compareAsc)即可切换排序方向compareAsc/compareDesc03qsort函数原型与泛型设计voidqsort(void*base,size_tnmemb,size_tsize,int(*compar)(constvoid*,constvoid*)),通过函数指针支持任意类型数据的自定义排序stdlib.h04设计模式启示:C语言策略模式函数指针实现了"策略模式"的C语言版本——算法骨架固定,将可变部分(比较逻辑)抽象为函数指针参数,由调用者在运行时注入具体实现StrategyPatternChapter05·Pointers&Functions回调函数的原理与典型应用场景回调函数是将函数地址通过函数指针传递给框架,由框架在特定时机回头调用的机制——实现控制反转,是事件驱动与插件系统的核心范式。回调机制本质调用方将函数地址传递给框架,框架在条件满足时通过函数指针调用,形成「注册-触发-执行」的控制反转模式控制反转信号处理回调signal(SIGINT,handler)注册Ctrl+C处理回调,操作系统在信号触发时回调handler执行自定义清理逻辑SIGINT遍历回调模式foreach对数组每个元素调用callback,用户可注入打印、累加、过滤等不同处理逻辑,实现通用遍历foreach大型项目应用GUI事件处理、网络编程数据回调、插件系统初始化函数,均依赖回调机制实现模块间松耦合通信松耦合ApplicationPatterns函数指针的两大应用范式函数指针在实际编程中主要呈现两种应用范式:算法策略切换(同一框架注入不同算法逻辑)和事件驱动回调(框架在特定时机触发用户自定义处理),二者共同实现了"框架固定、行为可变"的设计目标。算法策略切换qsort排序:通过比较函数指针支持任意排序策略,调用者定义比较逻辑,排序框架不关心具体排序依据状态机实现:用函数指针数组存储各状态处理函数,状态转移时通过索引切换处理函数,避免冗长的switch-case数学计算框架:数值积分函数接收被积函数指针,可对任意数学函数执行梯形法或辛普森法积分事件驱动回调信号处理:signal()注册信号回调函数,进程收到特定信号时由操作系统调度执行用户自定义的清理或恢复逻辑异步I/O:注册读写完成回调函数,I/O操作完成后系统自动回调通知,避免阻塞等待,提升并发处理能力插件架构:主程序加载动态库时调用其init函数指针完成初始化,实现功能模块的热插拔与松耦合扩展CHAPTER04指针作为函数参数的高级应用值传递与地址传递的本质区别,以及返回指针的安全实践CHAPTER05·POINTER&FUNCTION值传递与地址传递的本质区别C语言函数参数默认采用值传递(复制实参副本),函数内修改形参不影响原变量;通过指针参数实现地址传递,函数可直接操作实参所在的内存地址——这是C语言实现'多返回值'和'原地修改'的唯一途径。值传递局限swap(inta,intb)中a和b是实参的副本,函数内交换a、b的值后原变量不变,因为操作的是独立的栈上副本而非原始数据。栈副本地址传递原理swap(int*a,int*b)接收实参的地址,通过*a和*b直接读写原变量内存位置,函数返回后原变量已完成交换。指针解引用多返回值实现C语言函数只能return一个值,但通过多个指针参数可以"带回"多个结果,如divmod同时返回商和余数。商+余数性能考量传递大型结构体时,值传递需要复制整个结构体(可能几百字节),而指针传递只需复制8字节地址,显著提升效率并减少栈空间消耗。8字节第05章·指针与函数函数返回指针的有效性与安全性函数返回指针时必须确保指向的内存在函数返回后仍然有效——返回局部变量地址是最常见的致命错误(悬空指针),合法方式包括返回全局变量、static变量或动态分配内存的地址,每种方式都有特定的生命周期管理要求。01悬空指针陷阱:返回局部变量地址(如return&local)是严重错误——函数返回后栈帧被回收,该地址指向的内存已不属于程序,访问导致未定义行为。undefinedbehavior02合法方式一(static变量):staticintval=42;return&val;静态变量存储在数据段而非栈上,函数返回后仍然有效,但多次调用共享同一变量。datasegment03合法方式二(动态分配):int*p=malloc(sizeof(int));*p=42;returnp;堆内存在free之前始终有效,但调用者必须负责释放,否则造成内存泄漏。heap/free04合法方式三(传入缓冲区):函数接收调用者提供的指针参数并填充数据后返回该指针,如char*myStrcpy(char*dest,constchar*src),避免内存管理责任转移。caller-ownedPOINTER&CONSTconst与指针的结合:数据保护机制const修饰指针时位置不同含义截然不同:const在*左侧表示"指向常量的指针"(值不可改),const在*右侧表示"常量指针"(指向不可改)——在函数参数中合理使用const能防止数据被意外修改,是C语言工程规范的重要组成部分。01指向常量的指针constint*p(等价于intconst*p),不能通过*p修改所指值,但p可重新指向其他变量;常用于函数形参保护输入数据。constint*p02常量指针int*constp=&a;p本身的指向不可改变,但可通过*p修改所指值;适用于函数内部确保指针不偏移的场景。int*constp03双重constconstint*constp=&a;既不能修改指向也不能通过p修改值,提供最强数据保护;常用于指向只读配置数据的函数参数。constint*constp04工程实践strlen(constchar*s)、printf(constchar*fmt,...)等标准库函数均用const保护输入参数;项目规范要求不修改的指针参数必须加const。strlen·printfCHAPTER05指针数组与多级指针从指针数组到二级指针,掌握多层间接访问的编程技巧CHAPTER05·指针与函数指针数组的概念与应用指针数组是元素为指针类型的数组(如char*arr[]),每个元素可独立指向不同位置的数据——它比二维数组更灵活(各行可不等长),是处理命令行参数、字符串表和动态数据结构的基础工具。声明与理解int*arr[5]声明含5个int指针的数组,arr[i]是一个int*类型的指针;注意int*arr[5](指针数组)与int(*arr)[5](数组指针)的区别。int*arr[5]字符串表应用char*weekdays[]每行字符串长度不同但共享统一数组索引,比charweekdays[][10]节省内存,无需按最长串分配空间。char*[]命令行参数main(intargc,char*argv[])中argv就是指针数组,argc记录参数个数,argv[i]指向第i个参数字符串,是程序与操作系统交互的标准接口。argc/argv动态二维结构通过指针数组配合malloc可构建不规则二维数组,每行长度独立控制,如arr[0]分配2个元素、arr[1]分配5个元素。mallocChapter05·Pointers二级指针:指向指针的指针二级指针存储一级指针的地址,需两次解引用——核心价值在于让函数修改指针变量本身,是动态内存分配与链表操作的核心参数类型。声明与访问int**ppint**pp**ppint**pp声明二级指针,pp存储int*类型指针的地址;*pp得到一级指针,**pp得到最终int值。访问链为pp→地址→一级指针→地址→int数据。pp→*pp→**pp函数中修改指针voidalloc(int**pp)*pp=malloc(sizeof(int))alloc(&p)int*pvoidalloc(int**pp)内部执行*pp=malloc(sizeof(int)),调用alloc(&p)后p指向新分配内存;若仅传int*p则无法修改p本身的指向。*pp=malloc()链表插入操作voidinsert(Node**head,intval)*head=newNodevoidinsert(Node**head,intval)通过二级指针直接修改头指针,无需特殊处理空链表情况;*head=newNode让调用者的头指针指向新节点。Node**head与指针数组关系char*argv[]char**char**argvchar*argv[]的数组名在表达式中退化为char**类型,因此main函数的参数也可写为char**argv,两种写法完全等价。argv[]≡**argvCOMPARISON易混淆概念对比:指针数组vs数组指针指针数组本质是数组,元素为指针;数组指针本质是指针,指向整个数组。区分关键在于运算符优先级:[]优先于*,括号可改变绑定顺序。int*arr[5]指针数组·本质是数组arr含5个int*元素,sizeof为40字节(5×8)40B存储字符串地址、不规则二维结构、命令行参数argv表达式中退化为int**(指向指针的指针)int**int(*ptr)[5]数组指针·本质是指针ptr存数组首地址,sizeof为8字节,ptr+1偏移20字节8B指向二维数组的行,常作函数参数接收二维数组只能指向恰好含5个int的数组,[5]与[10]类型不匹配CHAPTER05·指针与函数综合实例:命令行参数解析程序命令行参数解析是指针数组最经典的应用场景——argv作为char*指针数组承载操作系统传入的参数,通过指针遍历和字符串比较实现选项识别,综合体现了指针数组、字符指针和指针运算的协同工作。参数结构intmain(intargc,char*argv[])中argc记录参数总数,argv[i]指向第i个参数字符串;运行./app-vinput.txt时argc=3,argv[1]='-v'argc=3选项遍历for(inti=1;i<argc;i++)逐个检查argv[i],用strcmp(argv[i],'-v')==0判断是否为verbose选项,匹配后设置标志变量verbose=1strcmp参数关联-o选项需读取下一个参数作为输出文件名,需检查i+1<argc防止越界,然后filename=argv[++i]使指针跳到参数值位置argv[++i]错误处理未知选项打印usage信息并exit(1);缺少必要参数时通过fprintf(stderr,...)输出错误提示,返回值非零通知shell程序异常退出exit(1)CHAPTER06总结回顾与学习展望梳理全章知识脉络,明确后续学习方向与实践路径CHAPTERREVIEW全章知识脉络与核心要点回顾本章以'地址'为核心线索,从指针基础→指针与数组→函数指针→指针参数→多级指针层层递进,构建了完整的指针知识体系——掌握这套体系是理解C语言内存模型、编写高效底层代码的必要前提。基础层:指针本质与运算指针存储内存地址,通过&取地址、*解引用实现数据间接访问,指针算术以类型大小为单位偏移。野指针(未初始化)和悬空指针(指向已释放内存)是最常见的指针错误,初始化为NULL是

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论