logo资料库

c语言难点分析整理,C语言.doc

第1页 / 共318页
第2页 / 共318页
第3页 / 共318页
第4页 / 共318页
第5页 / 共318页
第6页 / 共318页
第7页 / 共318页
第8页 / 共318页
资料共318页,剩余部分请下载后查看
1.C 语言中的指针和内存泄漏
2.内存覆盖
39.回车和换行的区别
5 58 C 语言中的指针和内存泄漏 C 语言难点分析整理 10 C 语言难点 18 C/C++实现冒泡排序算法 32 C++中指针和引用的区别 35 const char*, char const*, char*const 的区别 36 C 中可变参数函数实现 38 C 程序内存中组成部分 41 C 编程拾粹 42 目录 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. C 语言中实现数组的动态增长 44 11. C 语言中的位运算 46 12. 浮点数的存储格式: 50 13. 位域 14. C 语言函数二维数组传递方法 64 15. C 语言复杂表达式的执行步骤 66 16. C 语言字符串函数大全 68 17. C 语言宏定义技巧 89 18. C 语言实现动态数组 100 19. C 语言笔试-运算符和表达式 104 20. C 语言编程准则之稳定篇 107 21. C 语言编程常见问题分析 108 22. C 语言编程易犯毛病集合 112 23. C 语言缺陷与陷阱(笔记) 119 24. C 语言防止缓冲区溢出方法 126 25. C 语言高效编程秘籍 128 26. C 运算符优先级口诀 133 27. do/while(0)的妙用 134 28. exit()和 return()的区别 140 29. exit 子程序终止函数与 return 的差别 141 30. extern 与 static 存储空间矛盾 145 31. PC-Lint 与 C\C++代码质量 147 32. spirntf 函数使用大全 158 33. 二叉树的数据结构 167 34. 位运算应用口诀和实例 35. 内存对齐与 ANSI C 中 struct 内存布局 173 36. 冒泡和选择排序实现 180 37. 函数指针数组与返回数组指针的函数 38. 右左法则- 复杂指针解析 189 39. 回车和换行的区别 40. 堆和堆栈的区别 194 41. 堆和堆栈的区别 198 42. 如何写出专业的 C 头文件 202 43. 打造最快的 Hash 表 207 186 170 192
224 391 395 265 269 228 236 44. 指针与数组学习笔记 222 45. 数组不是指针 46. 标准 C 中字符串分割的方法 47. 汉诺塔源码 231 48. 洗牌算法 234 49. 深入理解 C 语言指针的奥秘 50. 游戏外挂的编写原理 254 51. 程序实例分析-为什么会陷入死循环 258 52. 空指针究竟指向了内存的哪个地方 260 53. 算术表达式的计算 54. 结构体对齐的具体含义 55. 连连看 AI 算法 274 56. 连连看寻路算法的思路 283 57. 重新认识:指向函数的指针 288 58. 链表的源码 291 59. 高质量的子程序 295 60. 高级 C 语言程序员测试必过的十六道最佳题目+答案详解 297 61. C 语言常见错误 320 62. 超强的指针学习笔记 325 63. 程序员之路──关于代码风格 343 64. 指针、结构体、联合体的安全规范 346 65. C 指针讲解 352 66. 关于指向指针的指针 368 67. C/C++ 误区一:void main() 373 68. C/C++ 误区二:fflush(stdin) 376 69. C/C++ 误区三:强制转换 malloc() 的返回值 70. C/C++ 误区四:char c = getchar(); 381 71. C/C++ 误区五:检查 new 的返回值 383 72. C 是 C++ 的子集吗? 384 73. C 和 C++的区别是什么? 387 74. 无条件循环 388 75. 产生随机数的方法 76. 顺序表及其操作 390 77. 单链表的实现及其操作 78. 双向链表 79. 程序员数据结构笔记 399 80. Hashtable 和 HashMap 的区别 408 81. hash 表学习笔记 410 82. C 程序设计常用算法源代码 412 83. C 语言有头结点链表的经典实现 419 84. C 语言惠通面试题 428 85. C 语言常用宏定义 450 380 389
1. C 语言中的指针和内存泄漏 在使用 C 语言时,您是否对花时间调试指针和内存泄漏问题感到厌倦?如果是这样,那么 本文就适合您。您将了解可能导致内存破坏的指针操作类型,您还将研究一些场景,了解要 在使用动态内存分配时考虑什么问题。 引言 对于任何使用 C 语言的人,如果问他们 C 语言的最大烦恼是什么,其中许多人可能会回答 说是指针和内存泄漏。这些的确是消耗了开发人员大多数调试时间的事项。指针和内存泄漏 对某些开发人员来说似乎令人畏惧,但是一旦您了解了指针及其关联内存操作的基础,它们 就是您在 C 语言中拥有的最强大工具。 本文将与您分享开发人员在开始使用指针来编程前应该知道的秘密。本文内容包括: 1. 导致内存破坏的指针操作类型 2. 在使用动态内存分配时必须考虑的检查点 3. 导致内存泄漏的场景 如果您预先知道什么地方可能出错,那么您就能够小心避免陷阱,并消除大多数与指针和内 存相关的问题。 ? 什么地方可能出错? 有几种问题场景可能会出现,从而可能在完成生成后导致问题。在处理指针时,您可以使用 本文中的信息来避免许多问题。 1. 未初始化的内存 在本例中,p 已被分配了 10 个字节。这 10 个字节可能包含垃圾数据,如图 1 所示。 char *p = malloc ( 10 ); 图 1. 垃圾数据 如果在对这个 p 赋值前,某个代码段尝试访问它,则可能会获得垃圾值,您的程序可能具 有不可预测的行为。p 可能具有您的程序从未曾预料到的值。 良好的实践是始终结合使用 memset 和 malloc,或者使用 calloc。 char *p = malloc (10);memset(p,’\0’,10); 现在,即使同一个代码段尝试在对 p 赋值前访问它,该代码段也能正确处理 Null 值(在 理想情况下应具有的值),然后将具有正确的行为。 2. 内存覆盖 由于 p 已被分配了 10 个字节,如果某个代码片段尝试向 p 写入一个 11 字节的值,则该 操作将在不告诉您的情况下自动从其他某个位置“吃掉”一个字节。让我们假设指针 q 表 示该内存。 图 2. 原始 q 内容 图 3. 覆盖后的 q 内容 结果,指针 q 将具有从未预料到的内容。即使您的模块编码得足够好,也可能由于某个共 存模块执行某些内存操作而具有不正确的行为。下面的示例代码片段也可以说明这种场景。 Char *name = (char *) malloc(11); // Assign some value to namememcpy ( p,name,11); // Problem begins here 在本例中,memcpy 操作尝试将 11 个字节写到 p,而后者仅被分配了 10 个字节。 作为良好的实践,每当向指针写入值时,都要确保对可用字节数和所写入的字节数进行交叉
核对。一般情况下,memcpy 函数将是用于此目的的检查点。 内存读取越界 内存读取越界 (overread) 是指所读取的字节数多于它们应有的字节数。这个问题并不太严 重,在此就不再详述了。下面的代码提供了一个示例。 char *ptr = (char *)malloc(10);char name[20] ;memcpy ( name,ptr,20); // Problem begins here 在本例中,memcpy 操作尝试从 ptr 读取 20 个字节,但是后者仅被分配了 10 个字节。 这还会导致不希望的输出。 内存泄漏 内存泄漏可能真正令人讨厌。下面的列表描述了一些导致内存泄漏的场景。 重新赋值 我将使用一个示例来说明重新赋值问题。 char *memoryArea = malloc(10);char *newArea = malloc(10); 这向如下面的图 4 所示的内存位置赋值。 图 4. 内存位置 memoryArea 和 newArea 分别被分配了 10 个字节,它们各自的内容如图 4 所示。如果某 人执行如下所示的语句(指针重新赋值)…… memoryArea = newArea; 则它肯定会在该模块开发的后续阶段给您带来麻烦。 在 上 面 的 代 码 语 句 中 , 开 发 人 员 将 memoryArea 指 针 赋 值 给 newArea 指 针 。 结 果 , memoryArea 以前所指向的内存位置变成了孤立的,如下面的图 5 所示。它无法释放,因为 没有指向该位置的引用。这会导致 10 个字节的内存泄漏。 图 5. 内存泄漏 在对指针赋值前,请确保内存位置不会变为孤立的。 首先释放父块 假设有一个指针 memoryArea,它指向一个 10 字节的内存位置。该内存位置的第三个字节 又指向某个动态分配的 10 字节的内存位置,如图 6 所示。 图 6. 动态分配的内存 free(memoryArea) 如果通过调用 free 来释放了 memoryArea,则 newArea 指针也会因此而变得无效。newArea 以前所指向的内存位置无法释放,因为已经没有指向该位置的指针。换句话说,newArea 所 指向的内存位置变为了孤立的,从而导致了内存泄漏。 每当释放结构化的元素,而该元素又包含指向动态分配的内存位置的指针时,应首先遍历子 内存位置(在此例中为 newArea),并从那里开始释放,然后再遍历回父节点。 这里的正确实现应该为: free( memoryArea->newArea);free(memoryArea); 返回值的不正确处理 有时,某些函数会返回对动态分配的内存的引用。跟踪该内存位置并正确地处理它就成为了 calling 函数的职责。 char *func ( ){ return malloc(20); // make sure to memset this location to ‘\0’…}
func ( ); // Problem lies here} void callingFunc ( ){ 在上面的示例中,callingFunc() 函数中对 func() 函数的调用未处理该内存位置的返回地 址。结果,func() 函数所分配的 20 个字节的块就丢失了,并导致了内存泄漏。 归还您所获得的 在开发组件时,可能存在大量的动态内存分配。您可能会忘了跟踪所有指针(指向这些内存 位置),并且某些内存段没有释放,还保持分配给该程序。 始终要跟踪所有内存分配,并在任何适当的时候释放它们。事实上,可以开发某种机制来跟 踪这些分配,比如在链表节点本身中保留一个计数器(但您还必须考虑该机制的额外开销)。 访问空指针 访问空指针是非常危险的,因为它可能使您的程序崩溃。始终要确保您不是在访问空指针。 总结 本文讨论了几种在使用动态内存分配时可以避免的陷阱。要避免内存相关的问题,良好的实 践是: 始终结合使用 memset 和 malloc,或始终使用 calloc。 每当向指针写入值时,都要确保对可用字节数和所写入的字节数进行交叉核对。 在对指针赋值前,要确保没有内存位置会变为孤立的。 每当释放结构化的元素(而该元素又包含指向动态分配的内存位置的指针)时,都应首先遍 历子内存位置并从那里开始释放,然后再遍历回父节点。 始终正确处理返回动态分配的内存引用的函数返回值。 每个 malloc 都要有一个对应的 free。 确保您不是在访问空指针。 C 语言难点分析整理 2. 这篇文章主要是介绍一些在复习 C 语言的过程中笔者个人认为比较重点的地方,较好的掌握 这些重点会使对 C 的运用更加得心应手。此外会包括一些细节、易错的地方。涉及的主要内 容包括:变量的作用域和存储类别、函数、数组、字符串、指针、文件、链表等。一些最基 本的概念在此就不多作解释了,仅希望能有只言片语给同是 C 语言初学者的学习和上机过程 提供一点点的帮助。 变量作用域和存储类别: 了解了基本的变量类型后,我们要进一步了解它的存储类别和变量作用域问题。 变量类别 局部变量 子类别 静态变量(离开函数,变量值仍保留) 自动变量 寄存器变量 全局变量 静态变量(只能在本文件中用) 非静态变量(允许其他文件使用) 换一个角度 变量类别 静态存储变量 子类别 静态局部变量(函数) 静态全局变量(本文件) 非静态全局/外部变量(其他文件引用) 动态存储变量 自动变量 寄存器变量 形式参数
extern 型的存储变量在处理多文件问题时常能用到,在一个文件中定义 extern 型的变量即 说明这个变量用的是其他文件的。顺便说一下,笔者在做课设时遇到 out of memory 的错误, 于是改成做多文件,再把它 include 进来(注意自己写的*.h 要用“”不用<>),能起到一 定的效用。static 型的在读程序写结果的试题中是个考点。多数时候整个程序会出现多个 定义的变量在不同的函数中,考查在不同位置同一变量的值是多少。主要是遵循一个原则, 只要本函数内没有定义的变量就用全局变量(而不是 main 里的),全局变量和局部变量重名 时局部变量起作用,当然还要注意静态与自动变量的区别。 函数: 对于函数最基本的理解是从那个叫 main 的单词开始的,一开始总会觉得把语句一并写在 main 里不是挺好的么,为什么偏择出去。其实这是因为对函数还不够熟练,否则函数的运 用会给我们编程带来极大的便利。我们要知道函数的返回值类型,参数的类型,以及调用函 数时的形式。事先的函数说明也能起到一个提醒的好作用。所谓形参和实参,即在调用函数 时写在括号里的就是实参,函数本身用的就是形参,在画流程图时用平行四边形表示传参。 函数的另一个应用例子就是递归了,笔者开始比较头疼的问题,反应总是比较迟钝,按照老 师的方法,把递归的过程耐心准确的逐级画出来,学习的效果还是比较好的,会觉得这种递 归的运用是挺巧的,事实上,著名的八皇后、汉诺塔等问题都用到了递归。 例子: longfun(intn) { longs; if(n==1||n==2) s=2; elses=n-fun(n-1); returns; } main() { printf("%ld",fun(4)); } 数组: 分为一维数组和多维数组,其存储方式画为表格的话就会一目了然,其实就是把相同类型的 变量有序的放在一起。因此,在处理比较多的数据时(这也是大多数的情况)数组的应用范 围是非常广的。 具体的实际应用不便举例,而且绝大多数是与指针相结合的,笔者个人认为学习数组在更大 程度上是为学习指针做一个铺垫。作为基础的基础要明白几种基本操作:即数组赋值、打印、 排序(冒泡排序法和选择排序法)、查找。这些都不可避免的用到循环,如果觉得反应不过 来,可以先一点点的把循环展开,就会越来越熟悉,以后自己编写一个功能的时候就会先找 出内在规律,较好的运用了。另外数组做参数时,一维的[]里可以是空的,二维的第一个[] 里可以是空的但是第二个[]中必须规定大小。 冒泡法排序函数: voidbubble(int a[],int n) { inti,j,k; for(i=1,i
for(j=0;ja[j+1]) { k=a[j]; a[j]=a[j+1]; a[j+1]=k; } } 选择法排序函数: voidsort(int a[],int n) { inti,j,k,t; for(i=0,i
等等。 字符串: 字符串其实就是一个数组(指针),在 scanf 的输入列中是不需要在前面加“&”符号的,因 为字符数组名本身即代表地址。值得注意的是字符串末尾的‘’,如果没有的话,字符串很 有可能会不正常的打印。另外就是字符串的定义和赋值问题了,笔者有一次的比较综合的上 机作业就是字符串打印老是乱码,上上下下找了一圈问题,最后发现是因为 char*name; 而不是 charname[10]; 前者没有说明指向哪儿,更没有确定大小,导致了乱码的错误,印象挺深刻的。 另外,字符串的赋值也是需要注意的,如果是用字符指针的话,既可以定义的时候赋初值, 即 char*a="Abcdefg"; 也可以在赋值语句中赋值,即 char*a; a="Abcdefg"; 但如果是用字符数组的话,就只能在定义时整体赋初值,即 char a[5]={"abcd"};而不能在 赋值语句中整体赋值。 常用字符串函数列表如下,要会自己实现: 函数作用 字符串拷贝函数 strcpy(char*,char *) 字符串追加函数 strcat(char*,char *) 要足够大 字符串比较函数 strcmp(char*,char *) 负值。注意,不是比较长度,是比较字符 ASCII 码的大小,可用于按姓名字母排序等。 字符串长度 strlen(char *) 返回字符串的长度,不包括''.转义字符算一个字符。 字符串型->整型 atoi(char *) 整型->字符串型 itoa(int,char *,int) 后者拷贝到前者 后者追加到前者后,返回前者,因此前者空间 前者等于、小于、大于后者时,返回 0、正值、 函数调用形式 备注 做课设时挺有用的 sprintf(char *,格式化输入) 赋给字符串,而不打印出来。课设时用也比较方便 注:对字符串是不允许做==或!=的运算的,只能用字符串比较函数 指针: 指针可以说是 C 语言中最关键的地方了,其实这个“指针”的名字对于这个概念的理解是十 分形象的。首先要知道,指针变量的值(即指针变量中存放的值)是指针(即地址)。指针 变量定义形式中:基本类型 *指针变量名 中的“*”代表的是这是一个指向该基本类型的指 针变量,而不是内容的意思。在以后使用的时候,如*ptr=a 时,“*”才表示 ptr 所指向的 地址里放的内容是 a。 指针比较典型又简单的一应用例子是两数互换,看下面的程序, swap(intc,intd) { intt; t=c; c=d; d=t; }
分享到:
收藏