




已阅读5页,还剩66页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
高质量C编程 声明 1 本资料所涉及到的内容来自互联网和作者工作经验 2 本资料仅作为内部资料教学使用 禁止任何商业用途 作者 武汉工业学院数理科学系陈欣20087 1 目录第一章程序的排版第二章变量的命名规则第三章常量 变量与结构第四章基本语句与表达式第五章指针的使用第六章函数的设计 第一章程序的排版 1 1空行空行起着分隔程序段落的作用 空行得体 不过多也不过少 将使程序的布局更加清晰 空行不会浪费内存 虽然打印含有空行的程序是会多消耗一些纸张 但是值得 所以不要舍不得用空行 建议 在一个函数体内 逻辑密切相关的语句之间不加空行 其它地方应加空行分隔 例如 while condition statement1 空行if condition statement2 else statement3 空行statement4 1 2代码行建议 一行代码只做一件事情 如只定义一个变量 或只写一条语句 这样的代码容易阅读 并且方便于写注释 建议 for while do等语句自占一行 执行语句不得紧跟其后 不论执行语句有多少都要加 这样可以防止书写失误 对比左右两段代码 intwidth 宽度intheight 高度intdepth 深度if width height dosomething intwidth height depth 宽度高度深度if width height dosomething 左边是良好的代码 右边是不好的代码 1 3代码行内的空格建议 关键字之后要留空格 象const virtual inline case等关键字之后至少要留一个空格 否则无法辨析关键字 象if for while等关键字之后应留一个空格再跟左括号 以突出关键字 建议 函数名之后不要留空格 紧跟左括号 以与关键字区别 建议 向后紧跟 向前紧跟 紧跟处不留空格 建议 之后要留空格 如Function x y z 如果 不是一行的结束符号 其后要留空格 如for initialization condition update 建议 赋值操作符 比较操作符 算术操作符 逻辑操作符 位域操作符 如 这类操作符前后不加空格 建议 对于表达式比较长的for语句和if语句 为了紧凑起见可以适当地去掉一些空格 如for i 0 i 10 i 和if a b c d voidFunc1 intx inty intz 良好的风格voidFunc1 intx inty intz 不良的风格if year 2000 良好的风格if year 2000 不良的风格if a b i 不良的风格 1 4对齐建议 程序的分界符 和 应独占一行并且位于同一列 同时与引用它们的语句左对齐 下图 良好的代码不好的代码 voidFunction intx programcode if condition programcode else programcode voidFunction intx programcode if condition programcode else programcode 1 5长行拆分建议 代码行最大长度宜控制在70至80个字符以内 代码行不要过长 否则眼睛看不过来 也不便于打印 建议 长表达式要在低优先级操作符处拆分成新行 操作符放在新行之首 以便突出操作符 拆分出的新行要进行适当的缩进 使排版整齐 语句可读 if very longer variable1 very longer variable12 2 6修饰符的位置修饰符 和 应该靠近数据类型还是该靠近变量名 是个有争议的活题 若将修饰符 靠近数据类型 例如 int x 从语义上讲此写法比较直观 即x是int类型的指针 上述写法的弊端是容易引起误解 例如 int x y 此处y容易被误解为指针变量 虽然将x和y分行定义可以避免误解 但并不是人人都愿意这样做 建议 应当将修饰符 和 紧靠变量名例如 char name int x y 此处y不会被误解为指针2 7注释C语言的注释符为 C 语言中 程序块的注释常采用 行注释一般采用 注释通常用于 1 版本 版权声明 2 函数接口说明 3 重要的代码行或段落提示 虽然注释有助于理解代码 但注意不可过多地使用注释 建议 全局变量要有较详细的注释 包括对其功能 取值范围 哪些函数或过程存取它以及存取时注意事项等的说明 示例 TheErrorCodewhenSCCPtranslate GlobalTitlefailure asfollows 变量作用 含义 0 SUCCESS1 GTTableerror 2 GTerrorOthers nouse 变量取值范围 onlyfunctionSCCPTranslate in thismodualcanmodifyit andother modulecanvisititthroughcall thefunctionGetGTTransErrorCode 使用方法BYTEg GTTranErrorCode 强烈建议 函数头部应进行注释 列出 函数的目的 功能 输入参数 输出参数 返回值 调用关系 函数 表 等 示例 下面这段函数的注释比较标准 当然 并不局限于此格式 但上述信息建议要包含在内 Function 函数名称Description 函数功能 性能等的描述Calls 被本函数调用的函数清单CalledBy 调用本函数的函数清单TableAccessed 被访问的表 此项仅对于牵扯到数据库操作的程序 TableUpdated 被修改的表 此项仅对于牵扯到数据库操作的程序 Input 输入参数说明 包括每个参数的作 用 取值说明及参数间关系 Output 对输出参数的说明 Return 函数返回值的说明Others 其它说明 接下来 我们在附录中看一个例子Dib cpp 第二章变量的命名规则 建议 标识符应当直观且可以拼读 可望文知意 不必进行 解码 标识符最好采用英文单词或其组合 便于记忆和阅读 切忌使用汉语拼音来命名 程序中的英文单词一般不会太复杂 用词应当准确 例如不要把CurrentValue写成NowValue 建议 识符的长度应当符合 min length max information 原则 几十年前老ANSIC规定名字不准超过6个字符 现今的C C不再有此限制 一般来说 长名字能更好地表达含义 所以函数名 变量名 类名长达十几个字符不足为怪 那么名字是否越长越好 例如变量名maxval就比maxValueUntilOverflow好用 单字符的名字也是有用的 常见的如i j k m n x y z等 它们通常可用作函数内的局部变量 建议 命名规则尽量与所采用的操作系统或开发工具的风格保持一致 例如Windows应用程序的标识符通常采用 大小写 混排的方式 如AddChild 而Unix应用程序的标识符通常采用 小写加下划线 的方式 如add child 别把这两类风格混在一起用 建议 程序中不要出现仅靠大小写区分的相似的标识符 例如 intx X 变量x与X容易混淆voidfoo intx 函数foo与FOO容易混淆voidFOO floatx 建议 程序中不要出现标识符完全相同的局部变量和全局变量 尽管两者的作用域不同而不会发生语法错误 但会使人误解 例如 includevoidInputFormat char strUserName intlength strlen strUserName 以下部分省略 voidmain intlength 0 以下部分省略 建议 变量的名字应当使用 名词 或者 形容词 名词 例如 floatvalue floatoldValue floatnewValue 建议 全局函数的名字应当使用 动词 或者 动词 名词 动宾词组 例如 DrawBox 全局函数建议 用正确的反义词组命名具有互斥意义的变量或相反动作的函数等 例如 intminValue intmaxValue intSetValue intGetValue 建议 尽量避免名字中出现数字编号 如Value1 Value2等 除非逻辑上的确需要编号 这是为了防止程序员偷懒 不肯为命名动脑筋而导致产生无意义的名字 因为用数字编号最省事 例如 floatGetAverage floatt1 floatt2 floatt3 t4 t3 t4 0 0 t3 t1 t2 t4 t3 2 0 returnt4 简单的Windows应用程序命名规则作者对 匈牙利 命名规则做了合理的简化 下述的命名规则简单易用 比较适合于Windows应用软件的开发 建议 函数名用大写字母开头的单词组合而成例如 voidDraw void 函数名voidSetValue intvalue 函数名建议 变量和参数用小写字母开头的单词组合而成 例如 BOOLflag intdrawMode 建议 常量全用大写的字母 用下划线分割单词例如 constintMAX 100 constintMAX LENGTH 100 建议 静态变量加前缀s 表示static 例如 voidInit staticints initValue 静态变量 建议 如果不得已需要全局变量 则使全局变量加前缀g 表示global 例如 intg howManyPeople 全局变量intg howMuchMoney 全局变量接下来我们来看一看 VC 6 0中提供的CString类中的部分成员函数的命名和注释情况 classCString 以上省略部分代码 getdatalengthintGetLength const TRUEifzerolengthBOOLIsEmpty const clearcontentstoemptyvoidEmpty returnsinglecharacteratzero basedindexTCHARGetAt intnIndex const returnsinglecharacteratzero basedindexTCHARoperator intnIndex const setasinglecharacteratzero basedindexvoidSetAt intnIndex TCHARch 以下省略部分代码 第三章变量与结构 关于常量 考虑 为什么需要常量 如果不使用常量 直接在程序中填写数字或字符串 将会有什么麻烦 1 程序的可读性 可理解性 变差 程序员自己会忘记那些数字或字符串是什么意思 用户则更加不知它们从何处来 表示什么 2 在程序的很多地方输入同样的数字或字符串 难保不发生书写错误 3 如果要修改数字或字符串 则会在很多地方改动 既麻烦又容易出错 建议 尽量使用含义直观的常量来表示那些将在程序中多次出现的数字或字符串 例如 defineMAX100建议 需要对外公开的常量放在头文件中 不需要对外公开的常量放在定义文件的头部 为便于管理 可以把不同模块的常量集中存放在一个公共的头文件中 建议 如果某一常量与其它常量密切相关 应在定义中包含这种关系 而不应给出一些孤立的值 例如 defineMIN100 defineMAXMIN 2 注意 宏定义中的括号要注意到宏只是简单的替换 并不做任何语法和逻辑上的检查 所以容易发生错误 特别是一些复合语句 所以需要尽可能的用括号将不同的层次隔开 例如 defineSQUARE x x x 错误的宏定义 result SQUARE 3 5 此时得到的结果是 result 3 5 3 5 23应该这样定义宏 defineSQUARE x x x 正确的宏定义 result SQUARE 3 5 此时得到的结果是 result 3 5 3 5 64 注意 定义多行的宏有时不能在一行内将宏定义完 需要转行时 可在行尾放一个反斜杠例如 define toupper c c a 结果 a 6 即只执行了一次增1 关于变量 建议 去掉没必要的公共变量 说明 公共变量是增大模块间耦合的原因之一 故应减少没必要的公共变量以降低模块间的耦合度 建议 仔细定义并明确公共变量的含义 作用 取值范围及公共变量间的关系 说明 在对变量声明的同时 应对其含义 作用及取值范围进行注释说明 同时若有必要还应说明与其它变量的关系 建议 明确公共变量与操作此公共变量的函数或过程的关系 如访问 修改及创建等 建议 当向公共变量传递数据时 要十分小心 防止赋与不合理的值或越界等现象发生 建议 防止局部变量与公共变量同名 说明 若使用了较好的命名规则 那么此问题可自动消除 建议 严禁使用未经初始化的变量作为右值 建议 使用变量时要注意其边界值的情况 关于结构 建议 设计结构时应力争使结构代表一种现实事务的抽象 而不是同时代表多种 结构中的各元素应代表同一事务的不同侧面 而不应把描述没有关系或关系很弱的不同事务的元素放到同一结构中 示例 如下结构不太清晰 合理 typedefstructSTUDENT STRU unsignedcharname 8 student sname unsignedcharage student sage unsignedcharsex student ssex asfollows 0 FEMALE 1 MALE unsignedcharteacher name 8 thestudentteacher sname unisgnedcharteacher sex histeachersex STUDENT 分开成两个结构体 感觉会更好一些 若改为如下 可能更合理些 typedefstructTEACHER STRU unsignedcharname 8 teachername unisgnedcharsex teachersex asfollows 0 FEMALE 1 MALE TEACHER typedefstructSTUDENT STRU unsignedcharname 8 student sname unsignedcharage student sage unsignedcharsex student ssex asfollows 0 FEMALE 1 MALE unsignedintteacher ind histeacherindex STUDENT 建议 若两个结构间关系较复杂 密切 那么应合为一个结构 示例 如下两个结构的构造不合理 typedefstructPERSON ONE STRU unsignedcharname 8 unsignedcharaddr 40 unsignedcharsex unsignedcharcity 15 PERSON ONE typedefstructPERSON TWO STRU unsignedcharname 8 unsignedcharage unsignedchartel PERSON TWO 由于两个结构都是描述同一事物的 那么不如合成一个结构 typedefstructPERSON STRU unsignedcharname 8 unsignedcharage unsignedcharsex unsignedcharaddr 40 unsignedcharcity 15 unsignedchartel PERSON 建议 若结构中元素个数过多可考虑依据某种原则把元素组成不同的子结构 以减少原结构中元素的个数 说明 增加结构的可理解性 可操作性和可维护性 示例 假如认为如上的 PERSON结构元素过多 那么可如下对之划分 typedefstructPERSON BASE INFO STRU unsignedcharname 8 unsignedcharage unsignedcharsex PERSON BASE INFO typedefstructPERSON ADDRESS STRU unsignedcharaddr 40 unsignedcharcity 15 unsignedchartel PERSON ADDRESS typedefstructPERSON STRU PERSON BASE INFOperson base PERSON ADDRESSperson addr PERSON 建议 按类型长度同时补齐结构体应该把结构体的成员按照它们类型的长度排序 并且考虑到计算机cpu读取数据的时候 一般以32bit 即4个字节为单位进行的 所以按照4字节对齐后 方便计算机存取 例如 不好的代码 struct chara 5 5字节longk 4字节doublex 8字节 推荐的代码 struct doublex 8字节longk 4字节chara 5 5字节charpad 7 填充7字节 将整个结构补成24字节 第四章基本语句与表达式 4 1优先级注意运算符的优先级 并用括号明确表达式的操作顺序 避免使用默认优先级 为了防止产生歧义并提高可读性 应当用括号确定表达式的操作顺序 例如 word high b 4 3if语句if语句是C C语言中最简单 最常用的语句 然而很多程序员用隐含错误的方式写if语句 4 3 1布尔变量与零值比较建议 不可将布尔变量直接与TRUE FALSE或者1 0进行比较 根据布尔类型的语义 零值为 假 记为FALSE 任何非零值都是 真 记为TRUE TRUE的值究竟是什么并没有统一的标准 例如VisualC 将TRUE定义为1 而VisualBasic则将TRUE定义为 1 假设布尔变量名字为flag 它与零值比较的标准if语句如下 if flag 表示flag为真if flag 表示flag为假其它的用法都属于不良风格 例如 if flag TRUE if flag 1 if flag FALSE if flag 0 4 3 2整型变量与零值比较建议 应当将整型变量用 或 直接与0比较 假设整型变量的名字为value 它与零值比较的标准if语句如下 if value 0 if value 0 不可模仿布尔变量的风格而写成if value 会让人误解value是布尔变量if value 4 3 3浮点变量与零值比较建议 不可将浮点变量用 或 与任何数字比较 千万要留意 无论是float还是double类型的变量 都有精度限制 所以一定要避免将浮点变量用 或 与数字比较 应该设法转化成 或 EPSINON x EPSINON 其中EPSINON是允许的误差 即精度 4 3 4指针变量与零值比较建议 应当将指针变量用 或 与NULL比较 指针变量的零值是 空 记为NULL 尽管NULL的值与0相同 但是两者意义不同 假设指针变量的名字为p 它与零值比较的标准if语句如下 if p NULL p与NULL显式比较 强调p是指针变量if p NULL 不要写成if p 0 容易让人误解p是整型变量if p 0 或者if p 容易让人误解p是布尔变量if p 4 3 5条件判断的简化boolInputFormat constchar strInput BigNumX 上述函数对输入字符串进行判断 符合要求返回true 否则返回false有的同学可能会写出这样的语句 boolbTemp bTemp InputFormat strInput X if bTemp 事实上可以写成更简单明了的形式 If InputFormat strInput X 建议 避免深层次的嵌套 如果出现下面这种结构的代码 该考虑使用switch case结构 if elesif elseif elseif else 建议 将正常情况放在if后面而不是else后面 并且记住 先集中编写正常情况 再编写异常情况 boolCopyFile constchar strResFileName constchar strDesFileName FILE readFile writeFile if readFile fopen strResFileName r if writeFile fopen strDesFileName w 开始用户操作returntrue else printf 目标文件打开失败 returnfalse else printf 源文件打开失败 returnfalse 4 4循环语句的效率本节重点论述循环体的效率 提高循环体效率的基本办法是降低循环体的复杂性 建议 在多重循环中 如果有可能 应当将最长的循环放在最内层 最短的循环放在最外层 以减少CPU跨切循环层的次数 例如下图所示 a 低效率 长循环在最外层 b 高效率 长循环在最内层 建议 循环体内工作量最小化 说明 应仔细考虑循环体内的语句是否可以放在循环体之外 使循环体内工作量最小 从而提高程序的时间效率 示例 c 的程序比示例 d 多执行了N 1次逻辑判断 并且由于前者老要进行逻辑判断 打断了循环 流水线 作业 使得编译器不能对循环进行优化处理 降低了效率 如果N非常大 最好采用示例 d 的写法 可以提高效率 如果N非常小 两者效率差别并不明显 采用示例 c 的写法比较好 因为程序更加简洁 c 效率低但程序简洁 d 效率高但程序不简洁 示例 如下代码效率不高 for ind 0 ind MAX ADD NUMBER ind sum ind back sum sum backupsum 语句 back sum sum 完全可以放在for语句之后 如下 for ind 0 ind MAX ADD NUMBER ind sum ind back sum sum backupsum 建议 不可在for循环体内修改循环变量 防止for循环失去控制 建议 建议for语句的循环控制变量的取值采用 半开半闭区间 写法 示例 a 中的x值属于半开半闭区间 0 x N 起点到终点的间隔为N 循环次数为N 示例 b 中的x值属于闭区间 0 x N 1 起点到终点的间隔为N 1 循环次数为N 相比之下 示例 a 的写法更加直观 尽管两者的功能是相同的 a 循环变量属于半开半闭区间 b 循环变量属于闭区间 建议 将小循环完全展开 尽可能的使用四路分解要充分利用CPU的指令缓存 就要把小循环完全展开 这样可以提高性能 例如 左边的代码就没有右边的代码效率高 for i 0 i 4 i a 0 fun 0 a 1 fun 1 a i fun i a 2 fun 2 a 3 fun 3 我们再看一组代码 for i 0 i 100 i a i fun i 写成以下代码效率更高 for i 0 i 100 i 4 a i fun i a i 1 fun i 1 a i 2 fun i 2 a i 3 fun i 3 解释 这里初看是做了一次循环展开 但是CPU由于具有分支预测技术 条件跳转已经工作得足够快 处理器在微操作的级别 在不影响逻辑的情下 早已经允许将临近的多条指令同时处理 再看下面的这个范例 doublelist 100 doublesum1 0 sum2 0 for inti 0 i 1O0 i十 2 Sum1 list i Sum2 list i 1 Sum suml sum2 同样是借助了循环展开 并且更重要的是其实 这个问题的关键还在于处理的数据是浮点数而不是整数 这里 浮点的加法需要消耗5个时钟周期 现在大多数的CPU下 如果换成整数 加法指令可以和后面的循环变量的递增以及条件跳转指令并发 而使用浮点数这种优化效果就不再存在 这个例子也从侧面说明了 整数运算的效率比浮点数高 虽然现代处理器处理浮点数已经足够快了 但是 整数运算更快 所以 能用整数运算解决的问题 应避免使用浮点数 建议 用do while循环取代while循环用while循环时有以下两种循环形式 unsignedinti i 0 while i0 在这两种循环中 使用do while循环编译后生成的汇编代码的长度短于while循环 4 5switch语句有了if语句为什么还要switch语句 switch是多分支选择语句 而if语句只有两个分支可供选择 虽然可以用嵌套的if语句来实现多分支选择 但那样的程序冗长难读 这是switch语句存在的理由 switch语句的基本格式是 switch variable casevalue1 break casevalue2 break default break 建议 每个case语句的结尾不要忘了加break 否则将导致多个分支重叠 除非有意使多个分支重叠 建议 不要忘记最后那个default分支 即使程序真的不需要default处理 也应该保留语句default break 这样做并非多此一举 建议 将最有可能执行的分支放在比较靠前的位置 可以加快程序的执行速度 来看一个例子 intnMaxScore 0 nMaxScore GetMaxScore Student 第五章指针的使用 5 1指针的不合法使用C语言的指针处理部分是C语言区别于其他程序设计语言的主要特征之一 它既灵活又简洁 程序员在编程时很喜欢使用 使用指针可以有效地表示复杂的数据结构 例如树 不用全局变量就能修改调用函数参数的值 能更有数地处理数组 能灵活实现在动态分配存储器空间等方面的功能 其效率与功能可与用机器语言实现时相媲美 使程序清晰 简洁并可生成紧凑 有效的代码 然而 正因为它的灵话性 简洁性 如果在编程时驾驭不好 就非常容易出错 这些错误 有些导致程序执行的故障中断 有些 隐蔽性 很强 程序出错时没有规律 执行起来有时对 有时又不对 又由于其潜伏性较强 可能需要很长一段时间才能暴露出来 给错误的定位和修改带来很大的工作量 使用指针时的错误可归为两大类 其一是对指针运算符理解错误 其二是偶然使用了不合法的指针 克服第一类错误要花时间彻底弄懂C语言中指针的概念 对第二类错误刊要求每次编程时仔细检查指针的合理性 主要的检查点就是下面将列出的一些错误 5 1 1没有对指针进行初始化处理例 main int pt pt 999 这是使用指针时最常见和最容易犯的错误 它的错误在于指针变量尚未分配存储空间 初始化 就为指针变量所指向的空间进行了赋值操作 句定义了一个指向整型数的指针变量pt 在 中将p1的指向的单元内容的置赋为999 但是因为系统仅为自动变量pt分配了能存放一个指针的内存空间 pt指向某一内存单元 这一单元在 之前其值还不确定 可能是操作系统区 也可能是其它用户区 所以运行pt 999时 可能某个不属于该程序的内存单元的内容被置为999 导致运行出错 甚至使系统死机 解决的方法是在使用之前进行初始化 在 之前加上一句 pt int mall0c sizeof int 使其指向内存中一个处于free状态的内存单元 5 1 2混淆了指针与指针所指向的空间内容的概念例 main char pt pt chat malloc 100 这是使用C语言指针时的另一个典型的错误 它的错误在于混淆了指针与指针所指向空间内容的概念 没有把malloc 返回的地址赋给Pt 却赋给了Pt所指向的空间 而此空间的位置是未知的 这种错误很容易使程序运行时瘫痪 甚至破坏操作系统 解决的方法是把第二句应改为 pt chat malloc 100 5 1 3没有对分配的内存地址做合法性的检查在上一个例子例 main char pt pt chat malloc 100 还有一个不老到的程序员容易犯的错误 这种错误隐蔽得很深 它的错误在于没有malloc 返回的地址进行合法性检查 如果内存用尽了 malloc将返回空 在C语言中 空永远也成不了合法的指针 这种错误隐患引起的问题很难找到 因为它很少发生 只有在分配请求失败时才可能发生 对付这类错误最好的方法是防患于未然 即在每次分配内存后都增加指针合法性的检查 如在上例增加如下程序段 见右侧 main char pt pt chat malloc 100 if pt NULL printf 内存已用完 n exit 1 5 1 4使用完后 忘记释放内存空间出现这类错误最常见的是在进行指针相互赋值时 有点经验的程序员都会在函数或过程退出之前释放掉局部指针所指的内存空间 但往往忽略了在进行指针赋值之前释放不再使用的内存空间 结果造成这部分内存空间 丢失 它既不能再被该程序访问 也不能被其它程序访问 造成不必要的浪费 例 main int pt1 int pt2 pt1 int malloc sizeof int if pt1 NULL printf 内存已用完 n exit 1 pt2 int malloc sizeof int if pt2 NULL printf 内存已用完 n exit 1 pt1 100 pt2 100 pt1 pt2 句定义了ptl pt2为指向整型数的指针 句为pt1分配存储空间并进行合法性检查 句为pt2分配存储空间并进行合法性检查 句分别对其指向的单元进行赋值 句将pt2指针赋ptl指针 执行后ptl p12两指针便指向同一存储单元 即分配给pt2指针的单元 这样原来pt1指针所指向的存储单元便丢失 了 既不能再被任何程序访问到 也不能利用free 释放它 如果程序中包含此段的函数需经常调用执行 则内存空间就会过早溢出而使程序终止 所以应在 句前加上free pt1 释放pl1指针所持有的内存空间 再进行赋值 5 1 5指针使用的内存已经释放C语言中使用malloe函数动态分配内存时 如果分配成功 则返回一个指向所分配的内存首地址的指针 失败则返回一个NULL指针 所以在使用动态分配的内存前 需要将malloe函数的返回指针与NULL指针进行比较以确定是否分配内存成功 使用指针进行动态内存分配操作时 当指针所指向内存被释放 即free 后 指针变量本身并没有被删除掉 如果没有及时地将指针置为NULL指针 在后面的程序中很有可能错误地将该指针当作合法指针使用 示例程序如右图 在该程序中 第一个if语句可以保证只有在动态分配内存成功的前提下对ip指针进行合法的操作 其后通过free操作释放了ip指针指向的内存 但未及时地将ip指针设置为NULL指针 在第二个if语句处 虽然ip所指向的内存区域已经无效 但是ip指针本身是有值的 其值不为NULL 所以该if判断语句的值为真 但此时ip已经是一个野指针 再对其进行操作将可能导致程序崩溃 也就是此时进行if语句判断其结果是无效的 所以为了避免野指针的出现 在对指针进行释放 即free 操作后 应立刻将指针置为NULL指针 voidTest void int ip int malloe sizeof int if ip NULL free ip if ip NULL 5 1 6指针指向的变量超出作用范围局部自动变量只在其生存期中有效 当变量超出其作用域后操作系统将回收该变量的内存空间 在对局部变量使用指针时 如果在局部变量作用域外使用指针访问局部变量 则该指针将成为野指针 示例程序如下 struetT intx voidTest void T tp intj 3 while j 0 Ti i是局部变量 其值在while循环体中有效 i x 10 tp i dosomethingwithtp j intm tp x tp是一个野指针 5 1 7指针指向的地址转移这种情况常出现的原因是 错误地将指向动态分配内存的指针当成指向一般变量的指针使用 示例程序如下 voidTest void charpChar char malloc sizeof char charchs pChar chs free pCbar 该程序的目的是想将chs内容传递给pChar指针指向的内存 但该程序代码将会使指针pChar指向chs所占有的内存地址 其先前指向的内存空间将成为垃圾地址 因为程序没有办法再访问该内存空间了 这里pChar是一个野指针 程序将会导致内存泄漏 同时 在调用free函数释放指pChar的内存时 将会发生异常错误 因为此时pChar指向的内存已经不是由malloc函数动态分配的 不能使用free函数进行释放 要避免这样的情况发生就应该避免将指向动态分配内存的指针在释放前指向其他变量的内存地址 5 2指针和数组名 5 2 1指针与数组名的区别观点1 数组名不是指针请看下面的范例 includevoidmain charstr 10 char pStr str printf d sizeof str printf n printf d sizeof pStr 分析 我们先来推翻 数组名就是指针 的说法 用反证法 证明 数组名不是指针假设 数组名是指针 则 pStr和str都是指针 因为 在WIN32平台下 指针长度为4 所以 第6行和第8行的输出都应该为4 实际情况是 第6行输出10 第8行输出4 所以 假设不成立 数组名不是指针 观点2 数组名神似指针上面我们已经证明了数组名的确不是指针 但是我们再看看程序的第5行 该行程序将数组名直接赋值给指针 这显得数组名又的确是个指针 我们还可以发现数组名显得像指针的例子 include includevoidmain char str1 hello charstr2 5 strcpy str2 str1 printf s str1 printf n printf s str2 分析 标准C库函数strcpy的函数原形中能接纳的两个参数都为char型指针 char cdeclstrcpy char constchar 而我们在调用中传给它的却是两个数组名 函数输出 hellohello数组名再一次显得像指针 既然数组名不是指针 而为什么到处都把数组名当指针用 于是乎 许多程序员得出这样的结论 数组名 主 是 谓 不是指针的指针 宾 在这里 给出两个结论 1 数组名的内涵在于其指代实体是一种数据结构 这种数据结构就是数组 2 数组名的外延在于其可以转换为指向其指代实体的指针 而且是一个指针常量 1 数组名指代一种数据结构 现在可以解释为什么第1个程序第6行的输出为10的问题 根据结论1 数组名str的内涵为一种数据结构 即一个长度为10的char型数组 所以sizeof str 的结果为这个数据结构占据的内存大小 10字节 2 数组名可作为指针常量根据结论2 数组名可以转换为指向其指代实体的指针 所以程序1中的第5行数组名直接赋值给指针 程序2第7行直接将数组名作为指针形参都可成立 下面的程序成立吗 intintArray 10 intArray 读者可以编译之 发现编译出错 原因在于 虽然数组名可以转换为指向其指代实体的指针 但是它只能被看作一个指针常量 不能被修改 关于指针和数组名的争论似乎可以告一段落 但是以下一个程序解释了一个重大的事实 数组名作为函数形参时 在函数体内 其失去了本身的内涵 仅仅只是一个指针 includeintArrayTest char str returnsizeof str voidmain char str1 hello printf d ArrayTest str1 分析 程序的输出结果为4 不可能吧 一个可怕的数字 前面已经提到其为指针的长度 前面指出 数组名内涵为数组这种数据结构 在arrayTest函数体内 str是数组名 那为什么sizeof的结果却是指针的长度 这是因为 1 数组名作为函数形参时 在函数体内 其失去了本身的内涵 仅仅只是一个指针 2 很遗憾 在失去其内涵的同时 它还失去了其常量特性 可以作自增 自减等操作 可以被修改 所以 数据名作为函数形参时 其全面沦落为一个普通指针 它的贵族身份被剥夺 成了一个地地道道的只拥有4个字节的平民 5 2 2指针与数组下标的效率 因为数组下标在编译时要被转换成指针表示法 所以用指针编写数组下标表达式可节约编译时间 看一个简单的例子代码1intarray 10 i for i 0 i 10 i array i 0 代码2intarray 10 p for p array p array 10 p p 0 代码1 2执行了相同的任务 一个是用下标将数组各元素设置为0 另一个用的是指针 代码2的效率显然更高一些 第六章函数的设计 6 1参数的传递函数是C程序的基本功能单元 其重要性不言而喻 函数参数细微缺点很容易导致该函数被错用 函数接口的两个要素是参数和返回值 C语言中 函数的参数和返回值的传递方式有两种 值传递 passbyvalue 和指针传递 passbypointer 建议 参数的书写要完整 不要贪图省事只写参数的类型而省略参数名字 如果函数没有参数 则用void填充 例如 voidSetValue intwidth intheight 良好的风格voidSetValue int int 不良的风格floatGetValue void 良好的风格floatGetValue 不良的风格建议 参数命名要恰当 顺序要合理 例如 编写字符串拷贝函数StringCopy 它有两个参数 如果把参数名字起为str1和str2 例如voidStringCopy char str1 char str2 那么我们很难搞清楚究竟是把str1拷贝到str2中 还是刚好倒过来 可以把参数名字起得更有意义 如叫strSource和strDestination 建议 如果参数是指针 且仅作输入用 则应在类型前加const 以防止该指针在函数体内被意外修改 这样 当试图改变该参数的值 编译器将报警 例如 voidStringCopy char strDestination constchar strSource 我们来看一个具体的例子 include include includevoidcopy char array des constchar array src constintnSize constchar pt array src for pt array src nSize array des pt voidmain char str1 Iloveyou char str2 char malloc strlen str1 copy str2 str1 strlen str1 printf s str2 建议 防止将函数的参数作为工作变量 说明 将函数的参数作为工作变量 有可能错误地改变参数内容 所以很危险 对必须改变的参数 最好先用局部变量代之 最后再将该局部变量的内容赋给该参数 示例 下函数的实现不太好 voidsum data unsignedintnum int data int sum unsignedintcount sum 0 for count 0 count num count sum data count sum成了工作变量 不太好 若改为如下 则更好些 voidsum data unsignedintnum int data int sum unsignedintcount intsum temp sum temp 0 for count 0 count num count sum temp data count sum sum temp 建议 避免函数有太多的参数 参数个数尽量控制在5个以内 如果参数太多 在使用时容易将参数类型或顺序搞错 6 2返回值的规则建议 不要省略返回值的类型 C语言中 凡不加类型说明的函数 一律自动按整型处理 这样做不会有什么好处 却容易被误解为void类型 C 语言有很严格的类型安全检查 不允许上述情况发生 由于C 程序可以调用C函数 为了避免混乱 规定任何C C函数都必须有类型 如果函数没有返回值 那么应声明为void类型 强调 函数名字与返回值类型在语义上不可冲突 违反这条规则的典型代表是C标准库函数getchar 例如 charc c getchar if c EOF 按照getchar名字的意思 将变量c声明为char类型是很自然的事情 但不幸的是getchar的确不是char类型 而是int类型 其原型如下 intgetchar void 由于c是char类型 取值范围是 128 127 如果宏EOF的值在char的取值范围之外 那么if语句将总是失败 这种 危险 人们一般哪里料得到 导致本例错误的责任并不在用户 是函数getchar误导了使用者 建议 不要将正常值和错误标志混在一起返回 正常值用输出参数获得 而错误标志用return语句返回 回顾上例 C标准库函数的设计者为什么要将getchar声明为令人迷糊的int类型呢 他会那么傻吗 在正常情况下 getchar的确返回单个字符 但如果getchar碰到文件结束标志或发生读错误 它必须返回一个标志EOF 为了区别于正常的字符 只好将EOF定义为负数 通常为负1 因此函数getchar就成了int类型 我们在实际工作中 经常会碰到上述令人为难的问题 为了避免出现误解 我们应该将正常值和错误标志分开 即 正常值用输出参数获得 而错误标志用return语句返回 函数getchar可以改写成BOOLGetChar char c 建议 有时候函数原本不需要返回值 但为了增加灵活性如支持链式表达 可以附加返回值 例如字符串拷贝函数strcpy的原型 char strcpy char strDest constchar strSrc strcpy函数将strSrc拷贝至输出参数strDest中 同时函数的返回值又是strDest 这样做并非多此一举 可以获得如下灵活性 charstr 20 intlength strlen strcpy str HelloWorld 一般的 如果函数有返回值 那么函数的 出口处 是return语句 我们不要轻视return语句 如果return语句写得不好 函数要么出错 要么效率低下 注意事项如下 注意 return语句不可返回指向 栈内存 的 指针 或者 引用 因为该内存在函数体结束时被自动销毁 例如 char Func void charstr helloworld str的内存位于栈上 returnstr 将导致错误 注意 要搞清楚返回的究竟是 值 还是 指针 我们来看一个范例程序 include includeconstchar states 输入不可以为空 输入不足六位 密码中不可以有非数字 输入正常 intInputFormat constchar szInput intlength i 0 length strlen szInput if length return0 else if length 9 return2 return3 voidmain charszPassword 256 printf 请输入密码 n gets szPassword printf s states InputFormat szPassword 6 3传值调用 传址调用6 3 1传值调用函数必须通过调用才能实现功能 在调用函数时 通过参数传递数据 在函数调用时的参数为实际参数 简称实参 在函数定义时的参数是形式参数 简称行参 发生函数调用时 是由实参传递数据 实际上是实参的一份拷贝 给行参 传值的特点 单向传递 即函数中对形参的操作不会影响到调用函数中的实参 例
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 校园用电安全知识培训
- 农药经营考试题及答案
- 人才引进线上面试题及答案
- 放射作业考试题及答案
- 类风湿考试题及答案
- 2025年合肥肥西经济开发区石门路幼儿园招聘考试笔试试题(含答案)
- 济南单招试题及答案
- 2025年馆陶县教育系统招聘教师考试笔试试题(含答案)
- 北京知识产权人才培训课件
- 会计制度设计自学考试试题(附答案)
- 2025年云南省事业单位招聘考试综合类专业能力测试试卷(工程类)难点解析
- 信访业务培训课件
- 2025年秋期人教版2年级上册数学核心素养教案(第6单元)(教学反思有内容+二次备课版)
- 2025内蒙古西部天然气蒙东管道有限公司招聘20人笔试参考题库附带答案详解(10套)
- 2025店铺租赁合同协议书下载
- 2025年国企财务招聘笔试题和答案(基础知识测试题)
- 9型人格培训课件
- 2025年银行安全保卫知识考试题库(含答案)
- 曲靖市商务局招聘公益性岗位人员考试真题2024
- 投资评价管理办法
- 达州水务集团有限公司员工招聘考试真题2024
评论
0/150
提交评论