C语言程序设计教程第8章-1_第1页
C语言程序设计教程第8章-1_第2页
C语言程序设计教程第8章-1_第3页
C语言程序设计教程第8章-1_第4页
C语言程序设计教程第8章-1_第5页
已阅读5页,还剩29页未读 继续免费阅读

下载本文档

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

文档简介

C语言程序设计教程第8章指针(3)—指针与函数、指针数组、二级指针与字符串处理Contents本次课程目录C语言程序设计教程·第8章指针(3)——深入理解指针在函数、数组与字符串中的高级应用。01指针与函数参数传递02返回指针值的函数03指针数组与命令行参数04二级指针的概念与应用05指针与字符串高级处理CHAPTER01指针与函数参数传递从值传递到地址传递,掌握指针作为函数参数的核心机制CHAPTER08·POINTER值传递的局限性回顾C语言函数默认采用值传递机制,实参的值被复制到形参的独立内存空间中,函数内部对形参的任何修改都不会影响主调函数中的原始变量。01拷贝副本机制调用swap(a,b)时,系统为形参x和y各分配独立内存,将a和b的值复制进去。函数结束后这些临时内存空间被释放,原始变量保持不变。02经典反例验证swap函数内部完成x与y交换后,主函数中a和b的值不变。这是因为被操作的是副本而非原始变量,充分暴露了值传递无法修改外部数据的局限性。03值传递适用场景仅需读取数据而不需修改原始变量时,如计算最大值、求和、比较大小等场景,值传递安全且简洁,避免了意外修改数据的风险。04突破局限:地址传递将变量的地址作为参数传递给函数,通过指针间接访问并修改原始内存单元,实现真正的数据修改。这是指针解决值传递局限性的核心机制。FUNCTIONPARAMETER指针作为函数参数:地址传递机制将指针作为函数参数时,传递的是变量的地址值。虽然地址本身仍是"值传递"(拷贝了一份地址),但函数内部通过解引用操作符*可以直接访问和修改主调函数中原始变量所在的内存单元,从而实现跨函数的数据修改。01·声明与调用int*p&a*p函数声明形参为指针类型(如int*p),调用时传入变量的地址(&a),函数通过*p读写原始内存int*p·&a02·swap实现int*p1int*p2swap函数正确实现:形参int*p1和int*p2接收a和b的地址,通过临时变量temp完成*p1与*p2的值交换*p1↔*p203·本质理解地址传递的"传递"过程仍是值传递(地址值被复制),但效果上实现了对原始数据的间接修改值传递→间接修改04·编程注意*pp=NULL函数内必须通过解引用*p操作目标内存,若直接修改指针本身(如p=NULL)则不影响原始变量*p≠pChapter08·Pointers指针参数实现多值返回C语言函数只能通过return返回一个值,但利用指针参数可以让函数将多个计算结果直接写入调用者提供的内存地址中,等效实现"多值返回"。01典型场景编写findMaxMin函数,同时求出数组的最大值和最小值,通过int*max和int*min两个指针参数返回findMaxMin02实现思路函数内部遍历数组,比较后将最大值赋给*max、最小值赋给*min,主函数通过传入的变量地址自动获得结果*max·*min03对比方案优劣相比使用全局变量返回多值,指针参数更安全可靠,不会造成命名污染和副作用扩散零副作用04扩展应用标准库中scanf('%d',&x)利用地址参数将用户输入写入变量,本质上也是多值返回的思路scanf()POINTER·ARRAY·FUNCTION数组作为函数参数的指针本质当数组名作为函数实参时,实际传递的是指向首元素的指针值。形参intarr[]自动退化为int*arr,两者完全等价。01数组名退化为指针函数形参intarr[]在编译时被自动转换为int*arr,sizeof(arr)返回指针大小而非数组总字节数int*arr02等价访问方式arr[i]与*(arr+i)在函数内部完全等价,编译器生成相同的机器指令,前者是后者的语法糖arr[i]≡*(arr+i)03天然地址传递数组名本身就是地址常量,不需要&取地址,函数内修改arr[i]直接改变主调函数的原始数组No&needed04长度信息丢失数组退化为指针后丢失长度信息,函数通常需要额外接收一个intn参数来指定数组元素个数intnrequired综合实例综合实例:排序与统计一体化函数通过一个同时完成数组排序和统计计算的综合函数,可以验证指针参数的两大核心应用——数组参数天然实现原地修改,指针参数实现额外的计算结果返回。函数签名设计voidsortAndAvg(int*arr,intn,double*avg),数组指针用于排序,avg指针用于返回平均值int*arr,intn,double*avg排序实现函数内部用冒泡或选择排序对arr指向的原始数组进行原地排序,主调函数的数组自动变为有序原地修改平均值计算遍历数组求和后除以n,将结果通过*avg=sum/n写回主调函数提供的double变量*avg=sum/n调用示例inta[5]={3,1,4,1,5};doublemean;调用sortAndAvg(a,5,&mean)后a变为有序且mean=2.8mean=2.8Chapter02返回指针值的函数掌握指针函数的定义、典型应用与局部变量返回的常见陷阱Syntax·CLanguage指针函数的定义与语法辨析指针函数是指返回值类型为指针的函数,其声明格式为"类型名*函数名(参数表)"。需要与"函数指针"严格区分:前者是函数返回地址值,后者是指针变量指向函数入口。01int*findMax(int*arr,intn)声明格式:int*findMax(int*arr,intn)表示findMax函数返回一个指向int的指针。02int*p()int(*p)()语法辨析:int*p()中*p是函数名(指针函数),int(*p)()中(*p)是指针变量(函数指针),括号改变优先级。03返回值含义:指针函数返回一个内存地址,调用者可用同类型指针变量接收该地址并访问指向的数据。04应用场景:数组查找返回元素地址、动态内存分配返回堆区地址、字符串处理返回结果串地址。CHAPTER08·指针函数指针函数实例:数组元素查找在数组中查找目标元素并返回其地址是指针函数最经典的应用之一。通过返回地址而非下标,调用者既可以用NULL判断查找是否成功,又可以通过指针差运算获取下标,还能直接用返回的指针修改元素值,信息量和灵活性远超返回整型下标。01函数实现int*search(int*arr,intn,inttarget)遍历数组,匹配时返回&arr[i],未找到返回NULL02调用与判断int*result=search(a,10,42);通过if(result!=NULL)判断查找成功,*result可直接访问目标值03下标计算技巧若已知数组首地址为a,则result-a即为目标元素的下标值,这是指针减法的经典应用04标准库对照C标准库的strchr(s,c)返回字符首次出现的地址,strstr(s1,s2)返回子串首地址,思路一致WARNING·指针安全核心陷阱:禁止返回局部变量地址⚠函数返回后,栈帧被回收,局部变量地址即刻失效函数中的局部变量存储在栈区,函数返回后栈帧被回收,局部变量的内存空间失效。若返回局部变量的地址,调用者得到的是"悬空指针"(danglingpointer),访问该地址属于未定义行为,可能导致程序崩溃或数据错误。错误示例int*badFunc(){intx=10;return&x;}返回时x的栈内存已被回收,调用者获得的地址指向无效区域DANGLINGPOINTER危害访问悬空指针属于未定义行为,可能输出垃圾值、覆盖其他数据、或触发段错误(SegmentationFault),导致程序崩溃或产生难以排查的数据错误。Segfault修复方案一static

intx=10;将局部变量声明为static,静态变量存储在数据段,函数结束后不会被销毁static修复方案二int*p=malloc(sizeof(int));使用malloc在堆区动态分配内存,堆区内存需手动free释放mallocCHAPTER8·POINTERS动态内存分配与指针函数结合通过malloc/calloc在堆区动态分配的内存不受函数作用域限制,函数返回后堆区内存仍然有效,是C语言实现动态数据结构的基础。01函数实现int*createArray(intn)内部调用malloc(n*sizeof(int))分配堆区内存,初始化后返回首地址malloc(n*sizeof(int))02堆区特性堆区内存由操作系统管理但生命周期由程序员控制,函数返回后分配的内存不会自动释放Lifetime:Programmer-controlled03主函数使用int*arr=createArray(100)接收返回指针后,可用arr[i]或*(arr+i)方式访问各元素arr[i]/*(arr+i)04内存释放责任使用完毕后必须调用free(arr)释放堆区内存,释放后应将arr置为NULL防止悬空指针误用free(arr);arr=NULL;CHAPTER03指针数组与命令行参数掌握指针数组的定义、字符串数组的高效管理以及main函数的参数机制CHAPTER8·POINTERS指针数组的定义与语法辨析指针数组是元素类型为指针的数组,声明格式为'类型名*数组名[大小]'。运算符优先级决定了语义差异——[]优先级高于*,这一规则是辨析的关键。内存布局对比64位系统下·单位:字节01定义格式:int*p[5]声明含5个元素的数组,每个元素是int*类型指针,可分别指向不同的int变量或数组02优先级辨析:*p[5]中[]优先级高于*,p先与[5]结合成为数组,再与*结合说明元素是指针类型03对比数组指针:int(*p)[5]中括号改变优先级,p先与*结合成为指针,指向含5个int元素的一维数组04内存布局:指针数组本身占用连续存储空间(64位系统下5个指针占40字节),各指针指向的目标地址可以不连续CHAPTER08·POINTERS指针数组管理字符串集合用字符指针数组管理多个字符串是C语言的标准做法,交换字符串只需交换指针值,排序和查找效率优势显著。定义示例char*days[7]={'Sunday','Monday',...},每个元素指向一个字符串常量的首地址,数组名本身也是指向指针的指针char*arr[]空间效率二维数组chardays[7][10]需70字节且短字符串浪费空间,指针数组仅需7个指针加各字符串实际长度,内存利用率大幅提升70Bvs7ptr高效排序对字符串排序时只需交换指针值(swap指针),无需调用strcpy复制字符串内容,时间复杂度从O(n²)降至O(1)swapptr访问方式days[i]直接得到第i个字符串,days[i][j]得到第i个字符串的第j个字符,与二维数组语法一致,学习成本低days[i][j]Chapter08·Pointermain函数的命令行参数机制C语言的main函数可接收操作系统传递的命令行参数:argc记录参数个数,argv是字符指针数组存储各参数字符串。01完整签名intmain(intargc,char*argv[])或等价写法intmain(intargc,char**argv),两者编译器均认可。这是C语言标准规定的函数原型,允许程序从命令行接收外部输入。02argc含义参数总数,包含程序名本身。如命令行输入./calc3+5,argc为4(程序名+3个用户参数)。该值由操作系统自动计算并传入。03argv含义字符指针数组,argv[0]固定为程序路径名,argv[1]到argv[argc-1]依次存储用户输入的各参数字符串。最后一个元素argv[argc]保证为NULL指针。04典型应用文件处理程序通过argv获取输入文件名,编译器通过命令行参数指定优化选项,工具类程序通过参数控制行为模式。这种机制使程序具备灵活的外部配置能力。命令行参数实例:命令行参数实现简易计算器通过命令行参数实现简易计算器,完整展示了argc/argv的使用流程:参数数量校验、字符串到数值的类型转换、运算符判断与分支处理。01参数校验if(argc!=4)则输出用法提示'Usage:calcnum1opnum2'并return1退出,防止参数不足导致越界访问02类型转换使用stdlib.h中的atoi函数将argv[1]和argv[3]从字符串转换为int类型,如atoi('123')返回整数12303运算符判断通过argv[2][0]获取运算符字符串的首字符,用switch语句匹配'+''-''*''/'执行对应运算04编译运行示例gcccalc.c-ocalc后执行./calc15+8,程序输出'15+8=23',展示完整的参数传递与处理流程CHAPTER04二级指针的概念与应用理解指向指针的指针,掌握二级指针在函数参数和动态数据结构中的关键作用Chapter8·Pointers二级指针的基本概念与访问机制二级指针是"指向指针的指针",其建立过程遵循严格的逻辑链:变量有地址→指针存储变量地址→指针自身也有地址→二级指针存储指针的地址。通过两次解引用**pp可以访问到最终的数据值。01逻辑链条inta=10;int*p=&a;int**pp=&p;—三级关系中pp指向p,p指向a,a存储值10。a=10p=&app=&p02声明格式int**pp中**表示pp是二级指针,它指向int*类型的变量,即指向一个指向int的指针。int**pp→int*→int03访问规则pp的值是p的地址,*pp等价于p(得到a的地址),**pp等价于*p等价于a(得到值10)。**pp≡*p≡a=1004内存视角每个变量(包括指针)都占用独立的内存空间,二级指针本质上就是存储了另一个指针变量所在内存的地址。pp→p→a→10CHAPTER8·POINTERS二级指针的核心应用:在函数中修改指针当需要在函数内部修改指针变量本身(如让它指向新分配的内存)时,必须将指针的地址传入函数,即使用二级指针参数。一级指针参数只能修改指针指向的数据,而二级指针参数能修改指针本身存储的地址值,这是两者在功能上的本质区别。问题场景函数需要为调用者的指针变量分配内存,如voidallocate(int**pp,intn)内部调用malloc分配n个intint**pp实现原理调用时传入&p(指针p的地址),函数内通过*pp=malloc(n*sizeof(int))将堆区地址写回主函数的p*pp=malloc一级指针局限若参数为int*p,函数内p=malloc(...)只修改了形参的拷贝,主函数的指针仍然指向NULL或旧地址形参拷贝标准库应用C标准库的strtol函数通过char**endptr参数返回解析停止位置的地址,正是二级指针的经典应用char**endptrCLANGUAGE·CHAPTER08·POINTERS二级指针与指针数组的内在关联指针数组的数组名在表达式中退化为指向首元素的指针。由于首元素本身就是指针类型(如char*),退化后的指针类型为char**——即二级指针。这一关系使得二级指针可以自然地遍历和操作指针数组,也是main函数argv参数两种写法等价的底层原因。退化规则:char*arr[5]的数组名arr在表达式中退化为char**类型,指向arr[0]这个指针元素遍历方式:char**p=arr;然后通过p++依次指向arr[1]、arr[2]…,*p得到当前指针,**p得到字符值argv两种写法:char*argv[]和char**argv完全等价,前者强调"指针数组",后者强调"二级指针",编译器处理一致函数参数传递:将指针数组传给函数时,形参用char**arr接收,函数内可用arr[i]或*arr方式访问各字符串CHAPTER05指针与字符串高级处理用指针高效操作字符串,手动实现标准库函数,深入理解字符串的底层处理机制CHAPTER08·POINTER字符指针与字符串的两种绑定方式字符指针指向字符串有两种方式:指向字符数组中的字符串(可修改)和指向字符串常量(只读)。前者存储在栈区或可写区域,后者存储在只读数据段。混淆两者的可修改性差异是初学者最常见的错误之一,也是段错误的常见来源。方式一:字符数组charstr[]="Hello";str存储在栈区可写内存中,str[0]='h'合法,可修改字符串的任意字符。数组名代表首地址,内容可被安全修改。可写·STACK·数组初始化方式二:指针指向常量char*p="Hello";p指向只读数据段中的字符串常量,p[0]='h'是非法操作,运行时触发段错误(SegmentationFault)。只读·RODATA·指针赋值本质区别str[]是数组初始化,内容被复制到可写内存;p=是指针赋值,指针指向只读常量区的固定字符串。两者内存位置和可修改性完全不同。初始化VS赋值·内存布局差异最佳实践如需修改字符串内容,务必使用字符数组方式;若仅读取字符串,使用constchar*p明确表示只读意图,让编译器帮助检查非法写操作。CONST推荐·编译期保护CHAPTER08·POINTER指针遍历字符串的经典范式利用字符串以'\0'结尾的特性,指针遍历字符串可以用while(*p)作为循环条件,无需预知字符串长度。循环条件:while(*p)利用'\0'的ASCII值为0(逻辑false)自动终止循环,无需调用strlen预计算字符串长度while(*p)遍历模板:while(*p){处理*p;p++;}每次迭代访问当前字符后将指针前移一位,直到遇到终止符p++统计字符示例:while(*p){if(*p>='a'&&*p<='z')count++;p++;}用指针遍历统计小写字母个数count++代码简洁性:对比for(i=0;i<strlen(s);i++)的下标方式,指针遍历避免了重复调用strlen的性能浪费vsstrlenChapter8·Pointers手动实现strcpy字符串复制函数手动实现strcpy函数是理解字符串底层操作的最佳练习。核心逻辑是逐字符复制直到遇到终止符'\0',一行while循环即可完成赋值、判断和前移三个操作。但必须确保目标缓冲区空间足够,否则造成缓冲区溢出——这是C语言中最常见的安全漏洞之一。01函数签名char*myStrcpy(char*dest,constchar*src)char*myStrcpy(char*dest,constchar*src)返回值指向目标串首地址,src声明为const保证不修改源串02核心实现while((*dest++=*src++)!='\0');while((*dest++=*src++)!='\0');赋值表达式的值即为赋入的值,复制到'\0'时条件为false退出循环03安全性警告strlen(src)+1目标数组必须预留足够空间(至少strlen(src)+1字节),否则超出部分覆盖相邻内存,引发缓冲区溢出04改进版本strncpy(dest,src,n)strlcpystrncpy(dest,src,n)限制最多复制n个字符,可防止溢出;实际开发中推荐使用更安全的strlcpySTRINGCOMPARISON手动实现strcmp字符串比较函数strcmp逐字符比较ASCII码值,返回正数、负数或0;用==比较的是指针地址而非内容,必须调用strcmp。01函数实现:循环比较直到字符不同或到达末尾,返回ASCII差值*s1-*s2*s1-*s202返回值语义:0表示完全相同,正值表示s1首个不同字符ASCII码大于s2,负值则相反+/−/003常见错误:s1==s2比较的是两个指针的地址值而非字符串内容,地址不同则判断为不等s1==s2✗04正确使用:strcmp(s1,s2)==0判断内容相同,注意必须与0比较而非直接判断返回值==0✓FUNCTIONIMPLEMENTATION手动实现strcat字符串拼接函数strcat的实现分两步:先将目标指针移动到字符串末尾的'\0'位置,再从该位置开始逐字符复制源串。关键约束是目标缓冲区必须有足够的剩余空间容纳追加内容。01while(*dest)dest++while((*dest++=*src++));先while(*dest)dest++定位到'\0'位置,再while((*dest++=*src++));从末尾开始逐字符追加复制。02第一步跳过目标串已有内容到达末尾,第二步复用strcpy的逻辑将源串(含'\0')追加到末尾,两步共同完成拼接。03strlen(src)+1目标数组剩余空间必须≥strlen(src)+1,否则追加内容溢出到相邻内存,可能破坏其他变量或程序栈。04strcat(strcat(buf,s1),s2)strcat返回dest的初始值(目标串首地址),支持链式调用如strcat(strcat(buf,s1),s2)连续拼接。Comparison三大字符串函数对比总结strcpy、strcat、strcmp是C语言最核心的三个字符串处理函数,它们共享指针遍历和'\0'终止的底层模式,但在操作目的、参数要求和返回值语义上各有差异。三者均存在缓冲区溢出的安全隐患,实际工程中应优先使用带长度限制的安全替代版本。strcpy字符串复制功能'\0'将源串完整复制到目标缓冲区,含终止符'\0'返回值char*目标串首地址char*,支持嵌套调用安全替代strncpystrncpy限制最多复制n个字符strncpystrcat字符串拼接功能'\0'将源串追加到目标串末尾,覆盖原'\0'后写入返回值char*目标串首地址char*,支持链式拼接安全替代strncatstrncat限制最多追加n个字符strncatstrcmp字符串比较功能逐字符比较ASCII码,遇不同字符或末尾时停止返回值intint型差值:0相等,正/负表示大小安全替代strncmpstrncmp限制最多比较前n个字符strncmpChapter8·Pointers指针使用五大常见错误汇总指针的强大伴随着安全风险。未初始化指针、访问已释放内存、忘记释放动态内存、写入超出分配范围以及修改字符串常量是最常见的五类错误。野指针int*p未初始化就使用,指向随机内存地址,可能破坏系统数据导致不可预测行为WildPointer悬空指针free(p)后未将p置NULL,后续访问已回收的堆区内存,可能被其他数据覆盖DanglingPointer内存泄漏malloc分配内存后忘记free,程序长期运行将耗尽可用内存,最终导致系统崩溃MemoryLeak缓冲区溢出strcpy向10字节数组复制20字节字符串,超出部分覆盖相邻内存,是最常见的安全漏洞BufferOverflow修改常量试图修改只读数据段的字符串常量,如p[0]='H'修改"hello",运行时触发段错误SegfaultCHAPTER08·POINTERBESTPRACTICES指针编程最佳实践指南防御性编程是规避指针错误的核心策略。通过初始化置NULL、释放后置NULL、malloc/free成对管理、优先使用安全函数、以及const保护只读数据这五条最佳实践,可以从源头上消除绝大多数指针相关的Bug,是每一位C语言程序员必须养成的工程习惯。立即初始化所有指针定义时初始化为NULL或有效地址,杜绝野指针的产生=NULL释放后置空释放内存后将指针置NULL,后续误用会立即触发可捕获的错

温馨提示

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

评论

0/150

提交评论