版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
高质量C++编程指南
第一章、高质量软件开发之概念1.1软件质量的基本概念一、词典的定义:①典型的或本质的特征②事物固有的或区别于其他事物的特征或本质③优良或者出色的程度二、软件成熟度模型CMM(CapabilityMaturityModel)定义:①一个系统或者组件或者过程符合特定需求的程度②一个系统或者组件或者过程符合客户或者用户的要求或期望的程度
以上定义很抽象三、从实用角度,10大质量属性:1、第一大类:功能性属性①正确性
第一个重要的软件质量属性。指软件按照要求正确执行任务的能力。软件如果运行不正确,将给用户造成不便甚至损失。②健壮性
指异常情况下,软件能够正常运行的能力。健壮性有2层含义:*容错能力:发生异常时系统不出错的能力。对于航天、武器、金融等领域,容错设计非常重要。容错是非常健壮的意思,UNIX容错能力很强。*恢复能力:指软件发生错误(不论死活)后,重新运行时,能否恢复到没有发生错误前的状态的能力。例如:某人挨了坏蛋一顿拳脚,特别健壮的人一点事没有,表示有容错能力;比较健壮的人,虽然被打倒在地,过一会还能爬起来,除了皮肉之痛外倒也不用去医院,表示恢复能力比较强;而虚弱的人可能短期恢复不过来,得在病床上躺很久。恢复能力是很有价值的。如Microsoft公司早期的操作系统Windows3.x和Windows9x动不动就死机,其容错性比较差,但它的恢复能力不错,机器重新启动后一般都能正常运行,看在这个份上,人们也愿意将就着使用.③可靠性
软件可靠性问题通常是由于设计中没有料到的异常和测试中没有暴露的代码缺陷引起的。可靠性是指在一定的环境下,在一定的时间段内,程序不出现故障的概率,是一个统计量。通常用MTTF(平均无故障时间)衡量。2、第二大类:非功能性属性④性能
性能通常是指软件的时间-空间效率,人们总希望软件的运行速度快些,占用的资源少些。可以通过优化数据结构、算法和代码来提高软件性能。⑤易用性
是指用户使用软件容易的程度。软件的易用性要让用户来评价,如果用户觉得软件很难用,开发人员不要有逆反心理,其实不是用户笨,是你开发的软件太笨了。⑥清晰性
清晰意味着工作成果易读、易理解,这个质量属性表达了人们一种质朴的愿望:让我花钱买它或用它,总得让我看明白它是什么东西。开发人员只有在自己思路清晰的时候才能写出让别人易读、易理解的程序和文档。高水平的人能够把系统设计得很简洁。如果软件系统臃肿不堪,它迟早会出问题。⑦安全性
是指信息安全,英文是Security而不是Safety。安全性是指防止系统被非法入侵的能力。对于大多数软件产品来说,杜绝非法入侵既不可能也没有必要。一般地,如果黑客为非法入侵花费的代价(考虑时间、费用、风险等多种因素)高于得到的好处,那么这样的系统认为是安全的⑧可扩展性可扩展性反映了软件适应变化的能力。如需求、设计变化,算法改进、程序的变化等。如果软件的规模很大,问题很复杂,若软件的可扩展性不好,那么就有很大的局限性。可扩展性是软件设计阶段重点考虑的问题。⑨兼容性是指两个或者两个以上的软件相互交换信息的能力。如两个字处理软件的文件格式兼容。兼容性的商业规则是:弱者兼容强者,否则无容身之地;强者避免被兼容,否则市场被瓜分。⑩可移植性
是指软件不经修改或者稍加修改就可以运行在不同硬件环境(CPU、OS和编译器)的能力,主要体现为代码的可移植性。编程语言越低级,用它写的程序越难移植,反之则越容易。一般地,软件设计时应将“设备相关程序”与“设备无关程序”分开,将“功能模块”与“用户界面”分开,这样可提高可移植性。1.2提高软件质量的基本方法提高质量就是减少软件的缺陷。缺陷越少软件质量越高。Bug是缺陷的形象比喻。错误是严重的缺陷。没有错误的程序世间难求。质量的最高境界是尽善尽美,即零缺陷。郎中治病的故事:在中国古代,有一家三兄弟全是郎中。其中一人是名医,人们问他:“你们兄弟三人谁的医术最高?”他回答说:“我常用猛药给病危者治病,偶尔有些病危者被我救活,于是我的医术远近闻名并成了名医。我二哥通常在人们刚刚生病时马上就治愈他们,临近村庄的人都知道他的医术。我大哥深知人们生病的原因,所以能够预防家里人生病,他的医术只有我们家里人知道。消除软件缺陷有三种基本方式:一、在开发过程中有效地防止工作成果产生缺陷,将高质量内建于开发过程之中。这是预防胜于治疗的道理,也是最佳的方式。学习高质量编程的目的就是要一次性编写出高质量的程序,而不是在程序出错后才去修补。二、当工作成果刚刚产生时马上进行质量检查,及时找出并消除工作成果中的缺陷。这种方式的效果比较好,人们一般都能学会。最常用的是技术评审、测试和质量保证等,已经被企业广泛采用并取得了成效。三、当软件交付使用后,用着用着就出错了,赶快请开发者来补救,这种方式的代价最高。可笑的是:当软件系统在用户那里出故障了,那些现场补救成功的人倒成了英雄,好心用户甚至寄来感谢信。1.3高质量软件开发的基本方法一、建立软件过程规范:若想顺利开发高质量的软件产品,必须有条不紊地组织技术开发活动和项目管理活动,我们把这些活动的组织形式称为过程模型。典型的成果是1970年提出的提出的瀑布模型。其它还有增量模型、快速原型模型、螺旋模型、迭代模型。瀑布模型的精髓是线性顺序地开发软件。线性化是人们最容易掌握并能熟练应用的思想方法。特别适合企业软件开发。二、复用复用就是指“利用现成的东西”。是拿来主义的思想。是对前人的成果的利用和继承。复用是人类智慧的表现。复用有利于提高质量、提高生产率和降低成本。勤劳且聪明的人们应该把把大部分的时间用在小比例的创新工作上,而把小部分的时间用在大比例的成熟工作中,这样才能把工作做得又快又好。软件复用简化了软件开发过程,减少了总的开发工作量与维护代价。据统计世界上已有一千多亿行程序,无数功能被重写了成千上万次,真是浪费啊。三、分而治之是把一个复杂的问题分解成若干个简单问题,然后逐个解决。软件分而治之说起来容易,做起来好难。应该着重考虑:复杂问题分解后,每个问题能否用程序实现?所有程序能否最终集成为一个软件系统并有效解决原始的复杂问题?四、优化与折中优化是指优化软件的各个质量属性。如提高运行速度,提高对内存的利用率,使用户的界面更加友好,使三维图形的真实感更强等。软件的折中是指协调各个质量属性,实现整体质量最优。折中是在保证其它质量属性不差的前提下,使某些重要质量属性变的更好。优化与折中——鱼与熊掌可兼得
假设鱼每公斤10元,熊掌每公斤10000元,有个犟脾气的人只有20元,非得要吃上1公斤美妙的“熊掌烧鱼”,怎么办?解决方法:花9元9角9分钱买999克鱼肉,花10元钱买1克熊掌肉,可以做一道“熊掌戏鱼”菜,剩下的那一分钱还可以建立基金,用于推广优化与折中方法。五、技术评审TR(TechnicalReview):目的是尽早发现工作成果中的缺陷,并帮助开发人员及时消除缺陷,技术评审的主要好处:通过消除工作成果中的缺陷而提高产品质量。越早消除缺陷就越能降低成本。开发人员能及时得到同行专家的帮助和指导。技术评审的2种基本类型:正式技术评审(FTR):FTR比较严格,需要举行评审会议,参加评审会议的人员比较多。非正式技术评审(ITR):ITR的形式比较灵活,通常在同伴之间开展,不必举行会议,评审人员比较少。六、测试:是通过运行测试用例(TestCase)来找出软件中的缺陷。测试是一种动态检查。一个成功的测试在于发现了迄今尚未发现的缺陷。所以怎样设计出有效的测试是非常重要的。常用的测试:测试阶段:单元测试、集成测试、系统测试、验收测试测试方法:白盒测试、黑盒测试测试内容:功能测试、界面测试、安全测试*
测试能提高软件的质量,但提高质量不能依赖测试。*测试只能证明缺陷存在,不能证明缺陷不存在,彻底的测试难以实现。*测试的困难是不知道如何进行有效的测试,也不知道什么时候可以放心的结束测试*2—8原则:80%的缺陷聚集在20%的模块中,经常出错的模块改错后还会经常出错七、质量保证QA(QualityAssurance):目的是提供一种有效的人员组织形式和管理方法。它是一种有计划的、贯穿于整个产品生命周期的质量管理方法。质量保证的基本方法是通过有计划地检查工作过程及工作成果是否符合既定的规范,来监控和改进过程质量和产品质量。建议把技术评审、测试与质量保证三者有机地结合起来,才能提高工作效率和降低成本。八、改错:就是找出根源,对症下药。有人问阿凡提:“我肚子痛,应该用什么药?”阿凡提说“应该用眼药水,因为你眼睛不好,吃了脏东西才肚子痛。”由软件错误的症状找出根源并不是件容易的事,因为:1、症状和根源可能相隔很远。2、症状可能在另一个错误被纠正后暂时性消失。3、症状可能并不是由某个程序错误直接引发的,如累积误差。4、症状可能是由不太容易跟踪的人工错误引起的。5、症状可能时隐时现,如内存泄漏。6、很难重现“错误现场”。7、症状可能分布在多个不同任务中,难以跟踪。寻找错误过程称调试(Debugging)。调试简单办法:插入打印语句。1.4有关编程的思考一、有最好的编程语言吗?cc++visualc++visualbasicdelphijava等哪个最好?能很好地解决问题的语言就是好语言根据实际情况选择自己擅长的语言,才能保证有较好的质量。不要发誓忠于某某语言而自寻烦恼。二、编程时应该多使用技巧吗?技巧的优点在于另辟蹊径地解决问题。缺点是技巧不为人熟知。程序中使用太多的技巧,可能会遗留下错误隐患,别人也难以理解。建议用自然的方式编程,不要滥用技巧。《卖油翁》的故事又告诉我们“熟能生巧”,表明技巧是自然而然产生的,不是卖弄出来的。三、换更快的计算机还是换更快算法?如果开发软件的目的是为了学习或是研究,那么应该设计一种更快的算法。如果该软件已经用于商业,则需谨慎考虑。如果更换一台计算机能解决问题,则是最快的解决方案。类似问题:是买现成的程序?还是彻底有自己开发?四、错误是否应该分等级?微软的一些开发小组把错误分为4个等级:一级严重:导致软件崩溃。二级严重:导致一个特性不能运行,并且没有替代方案。三级严重:导致一个特性不能运行,但有替代方案。四级严重:是表面化的或是微小的。观点:该分类带有较重的技术倾向,并不是普遍适用的。
假设某个财务软件有2个错误:错误A使该软件死掉,错误B导致工资计算错误。按上述分类,错误A属一级错误,错误B属二级错误。例如航空软件操作手册写错了,按上述分类属四级错误。但这样的错误可能导致机毁人亡,难道还算微小吗?编程人员应该意识到:所有的错误都是严重的,不存在微不足道的错误,只有这样才能少犯错误。小结
软件质量属性之间并非完全独立的,而是相互交织、相互影响的。因此要同时兼顾多个软件质量属性,使程序达到整体最优。软件工程的观念、方法和规范都是朴实无华的,平凡之人皆可领会,但只有实实在在地用起来才有价值。第二章、C++文件结构和程序版式2.1程序文件的目录结构
一个正在开发程序不仅包括源代码还包含许多资源文件,数据文件、库文件等等.典型的C++程序工程结构如图所示。图中目录的用途如下:1、include目录存放程序的头文件2、source目录存放程序的源文件3、shared目录存放一些共享文件4、resource目录存放各种资源文件,包括图片、图标、光标、对话框5、debug目录存放应用程序调试版本生成的目标文件、lib文件、dll文件及可执行文件等。6、release目录存放应用程序发行版本生成的目标文件、lib文件、dll文件及可执行文件等。7、bin目录存放程序员自己创建的lib文件。提示:要分清楚编译时相对路径和运行时相对路径的不同。例如:#include“.\include\abc.h”其相对路径是当前工程文件的路径.而下面这行代码:
OpenFile(“..\abc.ini”);其相对路径是在运行时可执行文件的路径.2.2程序文件的结构C++源程序一般有2类文件,一类为保存程序声明的头文件,后缀为.h;另一类为保存程序实现的源文件,后缀为.cpp源文件一般要用#include包含头文件一、头文件的用途和结构1、头文件用途:(1)、通过头文件来调用库功能。在一些场合,源代码不便向用户公开,只向用户提供头文件和二进制即可。编译器会从库中提取相应的代码。(2)、能加强类型安全检查。如果某个接口被实现或被使用时的方式与头文件的声明不一致,编译器会指出错误。(3)、头文件可以提高程序的可读性2、头文件结构:头文件中的元素比较多,其结构一般安排:①、版权、版本信息。②、编译预处理块,如:#ifndef/#define/#endif结构。③、文件包含。有#include<>及””。④、常量定义及初始化。⑤、全局变量的声明。⑥、数据类型定义。typedefstructclass⑦、函数原型。⑧、内联函数定义。二、版权和版本信息版权和版本的声明位于头文件和定义文件的开头(见例1-1),主要内容有:(1)版权信息。(2)文件名称,标识符,摘要。(3)当前版本,作者/修改者,完成日期。(4)版本历史信息。这些信息实际上是注释信息,程序不会执行的【规则1】为了防止头文件被重复引用,应当用ifndef/define/endif结构产生预处理块。【规则2】用#include<filename.h>格式来引用标准库的头文件(编译器将从标准库目录开始搜索)。【规则3】用#include“filename.h”格式来引用非标准库的头文件(编译器将从用户的工作目录开始搜索)。【建议1】头文件中只存放“声明”而不存放“定义”在C++语法中,类的成员函数可以在声明的同时被定义,并且自动成为内联函数。这虽然会带来书写上的方便,但却造成了风格不一致,弊大于利。建议将成员函数的定义与声明分开,不论该函数体有多么小。二、
代码行及行内空格【规则1】一行代码只做一件事情,如只定义一个变量,或只写一条语句。这样的代码容易阅读,并且方便于写注释。【规则2】if、for、while、do等语句自占一行,执行语句不得紧跟其后。不论执行语句有多少都要加{
}。这样可以防止书写失误。示例2(a)为风格良好的代码行,示例2(b)为风格不良的代码行。【建议1】尽可能在定义变量的同时初始化该变量(就近原则)如果变量的引用处和其定义处相隔比较远,变量的初始化很容易被忘记。如果引用了未被初始化的变量,可能会导致程序错误。本建议可以减少隐患。【规则3】关键字之后要留空格。象const、virtual、inline、case等关键字之后至少要留一个空格,否则无法辨析关键字。象if、for、while等关键字之后应留一个空格再跟左括号‘(’,以突出关键字。【规则4】函数名之后不要留空格,紧跟左括号‘(’,以与关键字区别。【规则5】‘(’向后紧跟,‘)’、‘,’、‘;’向前紧跟,紧跟处不留空格。【规则6】‘,’之后要留空格,如Function(x,y,z)。如果‘;’不是一行的结束符号,其后要留空格,如for(initialization;condition;update)。【规则7】赋值操作符、比较操作符、算术操作符、逻辑操作符、位域操作符,如“=”、“+=”“>=”、“<=”、“+”、“*”、“%”、“&&”、“||”、“<<”,“^”等二元操作符的前后应当加空格。【规则8】一元操作符如“!”、“~”、“++”、“--”、“&”(地址运算符)等前后不加空格。【规则9】象“[]”、“.”、“->”这类操作符前后不加空格。【建议2】对于表达式比较长的for语句和if语句,为了紧凑起见可以适当地去掉一些空格,如for(i=0;i<10;i++)和if((a<=b)&&(c<=d))三、对齐与缩进【规则1】程序的分界符‘{’和‘}’应独占一行并且位于同一列,同时与引用它们的语句左对齐。【规则2】{
}之内的代码块在‘{’右边数格处左对齐。示例4(a)为风格良好的对齐,示例4(b)为风格不良的对齐。四、长行拆分:【规则1】代码行最大长度宜控制在70至80个字符以内。代码行不要过长,否则眼睛看不过来,也不便于打印。【规则2】长表达式要在低优先级操作符处拆分成新行,操作符放在新行之首(以便突出操作符)。拆分出的新行要进行适当的缩进,使排版整齐,语句可读。五、修饰符的位置修饰符*和&应该靠近数据类型还是靠近变量名,是个有争议的话题。若将修饰符*靠近数据类型,例如:int*x;从语义上讲此写法比较直观,即x是int类型的指针。上述写法的弊端是容易引起误解,例如:int*x,y;此处y容易被误解为指针变量。虽然将x和y分行定义可以避免误解,但并不是人人都愿意这样做。【建议1】应当将修饰符*和&紧靠变量名或者用typedef做个类型映射。例如:char*name;int*x,y; //此处y不会被误解为指针六、注释的风格
C语言的注释符为“/*…*/”。C++语言中,程序块的注释常采用“/*…*/”,行注释一般采用“//…”。注释通常用于:(1)版本、版权声明;(2)函数接口说明;(3)重要的代码行或段落提示。虽然注释有助于理解代码,但注意不可过多地使用注释。
【规则1】注释是对代码的“提示”,而不是文档。程序中的注释不可喧宾夺主,注释太多了会让人眼花缭乱。注释的花样要少【规则2】如果代码本来就是清楚的,则不必加注释。否则多此一举,令人厌烦。例如:i++; //i加1,多余的注释【规则3】边写代码边注释,修改代码同时修改相应的注释,以保证注释与代码的一致性。不再有用的注释要删除。【规则4】注释应当准确、易懂,防止注释有二义性。错误的注释不但无益反而有害【规则5】尽量避免在注释中使用缩写,特别是不常用缩写。【规则6】注释的位置应与被描述的代码相邻,可以放在代码的上方或右方,不可放在下方。【规则7】当代码比较长,特别是有多重嵌套时,应当在一些段落的结束处加注释,便于阅读。七、类的版式类可以将数据和函数封装在一起,其中函数表示了类的行为(或称服务)。类提供关键字public、protected和private,分别用于声明哪些数据和函数是公有的、受保护的或者是私有的。这样可以达到信息隐藏的目的,即让类仅仅公开必须要让外界知道的内容,而隐藏其它一切内容。我们不可以滥用类的封装功能,不要把它当成火锅,什么东西都往里扔。类的版式主要有两种方式:(1)将private类型的数据写在前面,而将public类型的函数写在后面,如示例6(a)。采用这种版式的程序员主张类的设计“以数据为中心”,重点关注类的内部结构。(2)将public类型的函数写在前面,而将private类型的数据写在后面,如示例6(b)采用这种版式的程序员主张类的设计“以行为为中心”,重点关注的是类应该提供什么样的接口(或服务)。
很多C++教课书受到BiarneStroustrup第一本著作的影响,不知不觉地采用了“以数据为中心”的书写方式,并不见得有多少道理。
建议读者采用“以行为为中心”的书写方式,即首先考虑类应该提供什么样的函数。这是很多人的经验——“这样做不仅让自己在设计类时思路清晰,而且方便别人阅读。因为用户最关心的是接口,谁愿意先看到一堆私有数据成员!
应试实例:
美国某著名计算机嵌入公司面试:
下面程序有2中写法,你青睐哪种?为什么?
A:写法1:if(‘A’==a){
a++;
}
写法2:if(a==‘A’){
a++;
}
B:写法1:for(i=0;i<8;i++){
X=i+Y+J*7;
printf(“%d”,X);
}
写法2:S=Y+J*7;
for(i=0;i<8;i++){
printf(“%d”,i+S);
}第三章、C++程序设计基础3.1C++程序的基本概念一、不可缺少的主函数main():
1、
C++程序的可执行部分都是由函数组成的,main()就是其中必须有且只有一个的函数,叫主函数。
C++标准对main()函数有不同一般函数的限制:*不能重载。*不能内联。*不能定义为静态的。*不能取其地址。*不能由用户直接调用。2、main()的命令行参数和返回值可以为main()定义任何类型和个数的形参,但前2个参数为int和char*[]或与之兼容的类型。main()函数常见的原型为:voidmain(void);intmain(void);intmain(intargc,char*argv[]);intmain(intargc,char*argv[],char*env[]);char*argv[]用于接收命令行参数char*env[]用于接收系统环境变量返回值为int时,不同值有不同含义返回0,表示程序正常结束。返回非0,表示错误或非正常退出。3、内部名称:
C++编译器会按照特定的规则把用户定义的标识符转换为相应的内部名称。structexample1{intcount;charname;};structexample2{intcount;char*name;};C++允许用户定义拥有同名成员的多个构造类型如何区分它们的同名成员?
对语言实现来讲,这两个同名成员代表两块不同的内存,名字是引用这两块内存的别名,如果不加以区分,就会连接时导致二义性。二、类型转换:程序中某个地方所需要的数据类型并非你提供的类型时,通常要进行数据类型转换。本质上说,C++不会直接对两个不同的操作数进行运算,除非进行隐式的类型转换或者进行强制转换,再就是定义运算符重载函数。类型转换并不是改变原来的类型和值,而是生成了新的临时变元,其类型为目标类型。1、隐式转换就是编译器在背后帮程序员做的类型转换,程序员往往察觉不到。这种类型转换必须具有足够的安全性。凡是不安全的类型转换,编译器都能够检查出来并给出错误或警告信息,而不会隐式进行。基本数据类型存在如下的兼容关系:
charis-aintintis-alonglongis-afloatfloatis-adouble一个低级的数据类型对象总是优先转换为能够容纳得下它的值的最小的高级类型对象。例如:有2个重载函数:voidfun(longm);voidfun(doublen);如果存在调用fun(100),会调用其中的哪个函数呢?会调用voidfun(longm)。再有:doubled1=100;inti=100;doubled2=i;
编译器隐式地将100和i提升为double的一个临时变量,然后才分别赋给d1和d2,当编译器认为这些临时变量不再需要时就会适时地把它们销毁。派生类和基类之间存在is-a关系,因此可以直接将派生类对象转换为基类对象。虽然会发生内存截断,但无论从内存访问还是从转换结果来说都是安全的。classbase{private:intm_a,m_b;};classderived:publicbase{intm_c;};derivedobj1;baseobj2=obj1;derived*p1=&obj1;base*p2=p1;2、强制转换*强制转换可能导致安全问题.例如:doubled=1.28e+20;inti=(int)d;按照从浮点数到整数的转换语义,结果应该是截去浮点数的小数部分而保留其整数部分,因为d的整数部分远远超出了一个int所能表示的范围,结果当然不是我们所期望的(实际上为0)。*基本数据类型之间的指针强制转换会改变编译器对指针所指的内存单元的解释方式,结果会导致错误。下面的例子会导致内存访问的截断或扩张。结果显然也不是你所期望的。doubled=1000.25;int*iptr=(int*)&d;inti=1000;double*dptr=(double*)&i;
C++基类和派生类之间的指针强制转换同样存在安全隐患。例如:baseobj;derived*dptr=(derived*)&obj;
通过dptr能够访问的内存扩张了4个字节。如果访问m_c就可能引发运行时错误,因为dptr指向的对象根本就没有成员m_c的空间。提示1:不可以直接把基类对象转换为派生类对象,这是不自然的。对于基本类型的强制转换要区分值的截断和内存的截断的不同。
如果确信需要数据类型转换请尽量使用显式的强制类型转换。提示2:尽量避免做违反编译器类型安全原则和数据保护原则的事情,如在有符号和无符号数之间的转换,或者把const对象的地址指派给非const对象指针等。面试实例:
中国台湾著名CPU生产公司:
下面程序的结果是什么?
charfoo(void)
{
unsignedinta=6;
intb=-20;
charc;
(a+b)>6?(c=1):(c=0);
returnc;
}
答案:1三、表达式表达式就是使用运算符和标识符(操作数)按照语法规则连接起来的算式,任何表达式都是有值的。提示1:不要把数学中的表达式与计算机语言支持的表达式相混淆。如:(a<b<c)是数学表达式不是程序表达式。if(a<b<c)并不表示:if((a<b)&&(b<c))而是令人费解的if((a<b)<c)提示2:在使用运算符&&的表达式中,要尽量把最有可能为false的表达式放在&&的左边;同样在使用运算符||的表达式中,要尽量把最有可能为true的表达式放在||的左边;因为C++对逻辑表达式的判断采取突然死亡法;如果&&的左边子表达式计算结果为0,则整个表达式计算结果就为false,后面的子表达式没有必要再计算。这样可以提高程序的执行效率。提示3:不要编写太复杂的复合表达式。如
i=a>=b&&c+f<=g+h四、基本控制语句任何程序只用3种控制语句就可以实现:
顺序结构、选择结构、循环结构。1、选择(判断)结构C++有3种基本的选择结构:单选结构:if双重选择结构:if…else多重选择结构:switch…case建议1:在if…else结构中,要尽量把为TRUE概率较高的条件判断置于前面,这样可以提高该段程序的性能。(1)布尔变量与零值的比较:设flag为布尔变量的名字,它与零值比较的标准if语句如下:if(flag)//表示flag为真if(!flag)//表示flag为假下面的if语句都属于不良风格:if(flag!=TRUE)//错误的用法if(flag==TRUE)//错误的用法if(flag!=1)//错误的用法if(flag==1)//错误的用法因为假为0,真为非0,不同系统真值没有统一值if(flag!=0)//不良的用法if(flag==0)//不良的用法让人误认为flag为整数。(2)整型变量与零值的比较假设整型变量为value,它与零值比较的标准if语句如下:if(value==0)if(value!=0)不可模仿布尔变量的书写风格:if(value)或if(!value)让人误认为value是布尔变量。(3)浮点变量与零值的比较计算机表示浮点数float或double都有精度限制,超出精度的数,精度之外会截断.如:10.222222225与10.222222229在32位计算机中它们就是相等的。提示1:如果两个浮点数的绝对值小于或等于某个可接受的误差,就认为它们是相等的,否则就是不相等的.因此不要直接用==或!=对两个浮点数进行比较.
假设有两个浮点变量x和y,精度定义为EPS=1e-6;则错误的比较方式为:if(x==y)//隐含错误的比较if(x!=y)//隐含错误的比较正确的比较方式为:if(abs(x-y)<=eps)
//x等于yif(abs(x-y)>eps)
//x不等于y浮点数x与零值比较的正确方式为:if(abs(x)<=eps)
//x等于0if(abs(x)>eps)
//x不等于0(4)指针变量与零值的比较指针变量的零值是空值,记为:NULL即不指向任何对象.尽管NULL的值与0相同,但两者意义不同:#defineNULL((void*)0)假设指针变量的名字为p,它与零值比较的标准if语句如下:if(p==NULL)//p与NULL显式比较if(p!=NULL)强调p是指针变量不可写成:
if(p==0)//让人误认为p是整型变量
if(p!=0)
或者写成:if(p)//让人误认为p是布尔变量
if(!p)补充:有时候会看到if(NULL==p)这样的古怪格式,不是程序写错了,而是程序员为了防止将if(p==NULL)误写成:if(p=NULL),有意这样写的,编译器认为是合法的,类似有if(100==i).提示:switch结构没有自动跳出的功能,每个case子句的结尾不要忘记了加上break,否则导致多个分支重叠,也不要忘了最后的default子句,即使程序真的不需要default处理,也应该保留语句default:break;这样做为了防止别人误以为你忘了default处理.也是出于清晰性和对称性考虑的.2、循环(重复)结构
C++提供了3种循环结构:do…while,while,for结构.即确定循环,不确定循环和无限循环.提示:标准C++允许在for语句中声明变量,并且它的作用范围为该for语句块内,不过VisualC++语言的实现没有遵循这个语意,而是把这样声明的变量挪到了for语句块的外面,因此在for语句块结束后仍然有效.
不要使用浮点数做计数器来控制循环,因为它是不精确的.建议1:如果计数器从0开始计数,建议for语句的循环控制变量取值采用前闭后开区间写法.要防止出现差1错误.如:for(inti=0;i<N;i++){……}前闭后开for(intj=0;j<=N-1;j++){……}闭区间建议2:在多重嵌套的循环中,如有可能应当将最长循环放在最内层,最短循环放在最外层,这样可以减少CPU跨切循环层的次数,从而优化程序的性能.例如:for(row=0;row<100;row++){for(col=0;col<6;col++){sum=sum+array[row][col];}}这段程序效率比下面的要低for(col=0;col<6;col++){for(row=0;row<100;row++){sum=sum+array[row][col];}}建议3:如果循环内存在逻辑判断,且循环次数很大,宜将逻辑判断移到循环体的外面.例如:for(i=0;i<N;i++){if(condition)dosomething();elsedootherthing();}//N很大时效率低应该这样写会好些:if(condition){for(i=0;i<N;i++)dosomething();}else{for(i=0;i<N;i++)dootherthing();}如果N很小,两者效率差别不明显,第一种写法比较好,因为程序更简洁.五、C++常量常量是一种标识符,它的值在运行期间恒定不变。常用常量有字面常量、符号常量、布尔常量和枚举常量等。1、字面常量使用最多的常量。字面常量只能引用,不能修改。所以语言实现一般把它保存在程序的符号表里。2、符号常量
有2种:一个是用#define定义的宏常量,另一个用const定义的常量.提示1:由于#define是预编译伪指令,它定义的宏常量在进入编译阶段前就已经替换为所代表的字面常量,本质上是字面常量.
你可以取一个const符号常量的地址。
对于基本数据类型的const常量,编译器会重新在内存中创建它的一个拷贝,你通过其地址访问的是它的拷贝而非原始符号常量。
对于构造类型的const常量,它成为编译时不允许修改的变量,如果你能绕过编译器的静态类型安全检查机制,就可以在运行时修改其内存单元。例1:constlongm=10;//定义常量long*p=(long*)&m;//取常量地址*p=1000;//迂回修改
cout<<*p<<endl;//1000cout<<m<<endl;//10,原始常量没有变例2:
classsample{public:longm;};constsamples;s.m=10;sample*p=(sample*)&s;p->m=1000;cout<<s.m<<endl;//迂回修改成功
cout<<p->m<<endl;提示2:理论上说,只要你手中握有一个对象指针,你就可以设法绕过编译器随意修改它的内容,除非该内存受到操作系统保护。,也就是说C++没有提供对指针有效性的静态检查。【规则1】需要对外公开的常量放在头文件中,不需要对外公开的常量放在定义文件的头部。为便于管理,可以把不同模块的常量集中存放在一个公共的头文件中。【规则2】如果某一常量与其它常量密切相关,应在定义中包含这种关系,而不应给出一些孤立的值。例如:constfloatRADIUS=100;constfloatDIAMETER=RADIUS*2;3、const与#define的比较
C++语言可以用const来定义常量,也可以用#define来定义常量。但是前者比后者有更多的优点:(1)、const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误(边际效应)。(2)、有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试。【规则1】在C++程序中尽量使用const常量来定义符号常量。【规则2】const是constant的缩写,有恒定不变的意思,被const修饰的变量受到C++语言实现的静态类型安全检查机制的强制保护,可以预防意外修改,提高健壮性4、类中的常量
有时我们希望某些常量只在类中有效。由于#define定义的宏常量是全局的,不能达到目的,于是想当然地觉得应该用const修饰数据成员来实现。const数据成员的确是存在的,但其含义却不是我们所期望的。const数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的,因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。
不能在类声明中初始化const数据成员。以下用法是错误的,因为类的对象未创建时,编译器不知道SIZE的值是什么。classA {…//错误,企图在类声明中constintSIZE=100;//初始化const数据成员intarray[SIZE]; //错误,未知的SIZE };const数据成员的初始化只能在类构造函数的初始化表中进行,例如
classA {… A(intsize); //构造函数
constintSIZE; };
A::A(intsize):SIZE(size) {//构造函数的初始化表
… } Aa(100); //对象a的SIZE值为100 Ab(200); //对象b的SIZE值为200
怎样才能建立在整个类中都恒定的常量呢?别指望const数据成员了,应该用类中的枚举常量来实现。例如classA {…enum{SIZE1=100,SIZE2=200};//枚举常量intarray1[SIZE1]; intarray2[SIZE2]; };
枚举常量不会占用对象的存储空间,它们在编译时被全部求值。枚举常量的缺点是:它的隐含数据类型是整数,其最大值有限,且不能表示浮点数(如PI=3.14159)。3.2
C++编译预处理
编译预处理器对编译伪指令进行处理生成中间文件作为编译器的输入。编译伪指令一般以#打头。它不是C++语句,可以出现在程序的任何地方。
常用的预编译伪指令有文件包含、宏定义、条件编译等。一、文件包含#include伪指令用于包含一个头文件,预编译器扫描到该伪指令后就用对应的文本内容替换它。常用有2种语法形式:#include<头文件名>#include“头文件名”使用该伪指令时,头文件前面可以加路径(相对路径或者绝对路径)。例如:#include“.\include\my.h”#include“d:\proj\test\sour\include\a.h”二、宏定义
可以使用#define(或#undef)伪指令来定义(或取消)宏。有不带参数的宏和带参数的宏。宏在它定义后面的任何地方都可以引用。宏的特点和注意事项如下:1、宏定义不是C++语句,因此不需要使用语句结束符“;”,否则它也被看作宏体的一部分。如:#defineout(word)cout<<#word<<endl;使用:out(verygood!)替换结果:cout<<“verygood!”<<endl;2、宏不可调试,即使替换后出现语法错误,编译器也会将错误定位到源程序中而不是定位到具体的某个宏定义。3、带参数的宏体和各个形参应该分别用括号括起来,以免造成意想不到的错误。#definesquare(x)x*x//不好#definesquare(x)((x)*(x))4、不要在引用宏定义的参数列表中使用增量和减量运算符,否则将导致变量的多次求值。如:inta=5;intx=spuare(a++);其结果不是期望的25而是30。5、带参数的宏定义不是函数,没有调用函数的开销,但其每一次扩展都会生成重复的代码,结果可使执行代码的体积增大。6、inline函数不可能完全取代宏,各有各的好处,用宏构造一些重复的、数据和函数混合的、功能较特殊的代码段的时候,其优点就显示出来了。例:#definePi3.1415926#definecircle(r,L,S,V)L=2*Pi*r;\S=Pi*r*r;V=4.0/3.0*Pi*r*r*r调用:floatr=10.0,l,s,v;circle(r,l,s,v);7、当我们不再使用某一个宏时,可以用#undef来取消其定义。简单地删除宏定义会带来许多编译错误。如#undefPi#undefcircle(r,L,S,V)
建议:不要定义很复杂的宏,应该简单而清晰。不要使用宏定义来定义新的类型名,应该使用typedef,否则容易造成错误。三、条件编译条件编译为程序的移植和调试带来很大的方便。条件编译可以出现在程序代码的任何地方。1、#if、#elif和#else
可以用来暂时放弃编译一段代码。如:#if0……/*……*///希望禁止编译的代码段……/*……*///希望禁止编译的代码段#endif如果要使这段代码生效,只要把0该位1。2、#ifdef和#ifndef#ifdefxyz等价于#ifdefined(xyz)此处xyz称为调试宏。如果前面曾经用#define定义过宏xyz,那么#ifdefxyz条件为真,否则条件为假。如:#define
xyz
//如果不想让……
//Dosomething();语句#ifdefxyz//被编译,那么删除Dosomething();//#define
xyz,或者#endif//在其后用#undefxyz取消该宏。#ifndefxyz预编译伪指令等价于#if!defined(xyz)。为了防止头文件被重复引用,应当用ifndef/define/endif结构产生预处理块,这是C++约定俗成做法。3、#error
编译伪指令#error用于输出与平台、环境等有关的信息。#ifndefwin32#errorERROR:OnlyWin32plattformsurpported!#endif当预处理器发现应用程序中没有定义宏win32时,那么把#error后面的字符序列输出到屏幕后就终止。程序不会进入编译阶段4、#pragma
编译伪指令#pragma用于执行语言实现所定义的动作。VC++中使用比较复杂。#pragma(push,8)
//对象成员对齐8字节#pragmawarning(disable:4069)
//不要产生第4069号编译警告。#pragmacomment(lib,“user32.lib”)//当链接时在当前目录下搜索库文件user32.lib,找不到再在系统目录下找。具体参看语言的帮助文件。5、#和##运算符
(1)构串操作符#只能修饰带参数的宏的形参,它将实参的字符序列(而不是实参代表的值)转换成字符串常量。如定义宏#defineSTRING(x)#x#x#x#defineTEXT(x)“class”#x#”info”那么引用宏:intabc=100;//尽管abc是整型量STRING(abc);
//结果abcabcabcTEXT(abc);
//结果classabcinfo(2)合并操作符##将出现在其左右的字符序列合并成一个标识符。注意不是字符串。如:#definePTR(name)typedefname*P\##name;#defineREF(name)typedefname&R\##name;#defineDEF(name)classname;\PTR(name)\REF(name)有:DEF(TImage)
则展开后为classTImage;typedefTImage*PTImage;typedefTImage&RTImage;注意:使用合并操作符##时,产生的标识符必须预先定义,否则编译器会报“标识符未定义”的编译错误。面试实例:
美国某著名网络开发公司:
1、有2个变量a和b,不用if、?:switch或其他判断语句,找出两个数中比较大的数。
答案:intmax=((a+b)+abs(a-b))/2;
2、如何将变量a和b的值进行交换,并且不使用任何中间变量?
答案:a=a+b;b=a-b;a=a-b;
缺点:a和b比较大时,a+b会超界。
用异或就比较好:
a=a^b;b=a^b;a=a^b;函数原型是为了编译器对函数调用语句执行静态类型安全检查,即检查实参是否与形参的个数、类型、及顺序匹配。函数原型的格式如下:[作用域]返回类型函数名(类型1[形参名1],类型2[形参名2],类型3[形参名3],……);形参:在函数原型或定义及catch语句的参数列表中被声明的对象或指针、宏定义中的参数、模板定义中的类型参数等。实参:函数调用语句中以逗号分隔的参数列表中的表达式、throw语句的操作数等。二、函数的堆栈
一般函数都支持三种调用方式:过程调用、嵌套调用及递归调用。函数堆栈实际上使用的是程序的堆栈段内存空间。虽然程序的堆栈段是系统为程序分配的一种静态数据区,函数堆栈是在调用它的时候才动态分配的。这是因为:无法在编译时确定一个函数运行时所需的堆栈大小。如果在函数调用完后还要为它保留堆栈,则会很浪费内存空间。
堆栈是自动管理的。也就是说局部变量的创建和销毁,堆栈的释放都是函数自动完成的,不需要程序员干涉。
函数堆栈重要有三个用途:进入函数前保存环境变量和返回地址;进入函数时保存实参的拷贝;在函数体内保存局部变量。函数调用规范:
函数调用规范决定了函数调用的实参压入堆栈、退出堆栈及堆栈释放的方式。Windows环境下常用的调用规范有:1、_cdecl:这是C++函数默认的调用规范,参数从右向左依次传递并压入堆栈,由调用函数负责堆栈的清退,因此这种方式有利于传递个数可变的参数给被调用函数。如printf()函数。2、_stdcall:这是WinAPI函数使用的调用规范。参数从右向左依次传递并压入堆栈,
由被调用函数负责堆栈的清退。该规范生成的函数代码比_cdecl更小,但当函数有可变个数的参数时会转为_cdecl规范。在Windows中,宏WINAPI、CALLBACK都定义为_stdcall。3、_thiscall:C++非静态成员函数的默认调用规范,不能使用个数可变的参数。当调用非静态成员函数的时候,this指针直接保存在ECX寄存器中而非压入函数堆栈。其它方面与_stdcall相同。4、_fastcall:该规范所修饰的函数的实参将被直接传递到CPU寄存器而不是内存堆栈中(这就是”快速调用”的含义)。堆栈的清退由被调用函数负责。该规范不能用于成员函数。
一般情况下我们不需要显式地指定函数的调用规范。三、参数的传递规则:函数是C++程序的基本功能单元,其重要性不言而喻。函数设计的细微缺点很容易导致该函数被错用,所以光使函数的功能正确是不够的。这里重点论述函数接口设计和内部实现的规则。函数接口的两个要素是参数和返回值。C++语言中,函数的参数和返回值的传递方式有三种:值传递(passbyvalue)、指针传递(passbypointer)和引用传递(passbyreference)。【规则1】参数的书写要完整,不要贪图省事只写参数的类型而省略参数名字。如果函数没有参数,则用void填充。例如:voidSetValue(intw,inth); //良好的风格
voidSetValue(int,int);//不良的风格floatGetValue(void); //良好的风格floatGetValue(); //不良的风格【规则2】参数命名要恰当,输入参数和输出参数的顺序要合理。例如编写字符串拷贝函数StringCopy,它有两个参数。如果把参数名字起为str1和str2,则voidStringCopy(char*str1,char*str2);我们很难搞清楚究竟是把str1拷贝到str2中,还是刚好倒过来。可以把参数名字起得更有意义,如叫strSource和strDestination。这样从名字上就可以看出应该把strSource拷贝到strDestination。还有一个问题,这两个参数那一个放在前那一个放在后?参数的顺序要遵循程序员的习惯。一般地,应将目的参数放在前面,源参数放在后面。如voidStringCopy(char*strSource,char*strDestination);//这样不好别人在使用时可能会不假思索地写成如下形式:charstr[20];StringCopy(str,“HelloWorld”);//参数顺序颠倒【规则3】如果参数是指针,且仅作输入用,则应在类型前加const,以防止该指针在函数体内被意外修改。例如:voidStringCopy(char*strDestination,constchar*strSource);
如果输入参数以值传递的方式传递对象,则宜改用“const&”方式来传递,这样可以省去临时对象的构造和析构过程,从而提高效率。【建议】避免函数有太多的参数,参数个数尽量控制在5个以内。如果参数太多,在使用时容易将参数类型或顺序搞错。此时可以将这些参数封装为一个对象并尽量采用地址传递或引用传递。
尽量不要使用类型和数目不确定的参数列表。C标准库函数printf是采用不确定参数的典型代表,其原型为:intprintf(constchat*format[,argument]…);这种风格的函数在编译时丧失了严格的静态类型安全检查。四、返回值的规则:【规则1】不要省略返回值的类型。C语言中,凡不加类型说明的函数,一律自动按整型处理。这样做不会有什么好处,却容易被误解为void类型。C++语言有很严格的类型安全检查,不允许上述情况发生。由于C++程序可以调用C函数,为了避免混乱,规定任何C++/C函数都必须有类型。如果函数没有返回值,那么应声明为void类型。【规则2】函数名字与返回值类型在语义上不可冲突。违反这条规则的典型代表是C标准库函数getchar。如:charc;c=getchar();if(c==EOF)…按照getchar名字的意思,将变量c声明为char类型是很自然的事情。但不幸的是getchar的确不是char类型,而是int类型,其原型如下:
intgetchar(void);由于c是char类型,取值范围是[-128,127],如果宏EOF的值在char的取值范围之外,那么if语句将总是失败,这种“危险”人们一般哪里料得到!导致本例错误的责任并不在用户,是函数getchar误导了使用者。【建议1】不要将正常值和错误标志混在一起返回。正常值用输出参数获得,而错误标志用return语句返回。
C标准库函数为什么要将getchar声明为令人迷糊的int类型呢?在正常情况下,getchar的确返回单个字符。但如果getchar碰到文件结束标志或发生读错误,它必须返回一个标志EOF。为了区别于正常的字符,只好将EOF定义为负数(通常为负1)。因此函数getchar就成了int类型。为了避免出现误解,我们应该将正常值和错误标志分开。即:正常值用输出参数获得,而错误标志用return语句返回。函数getchar可以写成BOOLGetChar(char*c);【建议2】有时候函数原本不需要返回值,但为了增加灵活性如支持链式表达,可以附加返回值。例如字符串拷贝函数strcpy的原型:char*strcpy(char*strDest,constchar*strSrc);strcpy函数将strSrc拷贝至输出参数strDest中,同时函数的返回值又是strDest。这样做并非多此一举,可以获得如下灵活性:charstr[20];intlength=strlen(strcpy(str,“HelloWorld”));
【建议3】如果函数的返回值是一个对象,有些场合用“引用传递”替换“值传递”可以提高效率。而有些场合只能用“值传递”而不能用“引用传递”,否则会出错。例如:classString{…String&operate=(constString&other);//赋值函数//相加函数,如果没有friend修饰则只许有一个右侧参数friendStringoperate+(constString&s1,constString&s2);private:char*m_data;}String的赋值函数operate=的实现如下:String&String::operate=(constString&other){ if(this==&other) return*this; deletem_data; m_data=newchar[strlen(other.data)+1]; strcpy(m_data,other.data); return*this; //返回的是*this的引用,无需拷贝过程}
对于赋值函数,应当用“引用传递”的方式返回String对象。如果用“值传递”的方式,虽然功能仍然正确,但由于return语句要把*this拷贝到保存返回值的外部存储单元之中,增加了不必要的开销,降低了赋值函数的效率。
String的相加函数operate+的实现如下:Stringoperate+(constString&s1,constString&s2){ Stringtemp; deletetemp.data; //temp.data是仅含‘\0’的字符串
temp.data=newchar[strlen(s1.data)+strlen(s2.data)+1]; strcpy(temp.data,s1.data); strcat(temp.data,s2.data); returntemp; }
对于相加函数,应当用“值传递”的方式返回String对象。如果改用“引用传递”,那么函数返回值是一个指向局部对象temp的“引用”。由于temp在函数结束时被自动销毁,将导致返回的“引用”无效。五、函数内部实现的规则:不同功能的函数其内部实现各不相同,看起来似乎无法就“内部实现”达成一致的观点。但根据经验,我们可以在函数体的“入口处”和“出口处”从严把关,从而提高函数的质量。【规则1】在函数体的“入口处”,对参数的有效性进行检查。很多程序错误是由非法参数引起的,我们应该充分理解并正确使用“断言”(assert)来防止此类错误。
程序一般分为Debug版本和Release版本,Debug版本用于内部调试,Release版本发行给用户使用。断言assert是仅在Debug版本起作用的宏,它用于检查“不应该”发生的情况。在运行过程中,如果assert的参数为假,那么程序就会中止(一般地还会出现提示对话,说明在什么地方引发了assert)。如果程序在assert处终止了,并不是说含有该assert的函数有错误,而是调用者出了差错,assert可以帮助我们找到发生错误的原因。
void*memcpy(void*pvTo,constvoid*pvFrom,size_tsize){assert((pvTo!=NULL)&&(pvFrom!=NULL)); //使用断言,指针为空退出。byte*pbTo=(byte*)pvTo; //防止改变pvTo的地址byte*pbFrom=(byte*)pvFrom; //防止改变pvFrom的地址
while(size-->0) *pbTo++=*pbFrom++; returnpvTo;}【规则2】在函数体的“出口处”,对return语句的正确性和效率进行检查。 如果函数有返回值,那么函数的“出口处”是return语句。如果return语句写得不好,函数要么出错,要么效率低下。注意事项如下:(1)return语句不可返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。例如char*Func(void) {charstr[]=“helloworld”;//str的内存位于栈上
…returnstr; //将导致错误
}(2)要搞清楚返回的究竟是“值”、“指针”还是“引用”。(3)如果函数返回值是一个对象,要考虑return语句的效率。例如
returnString(s1+s2);这是临时对象的语法,表示“创建一个临时对象并返回它”。不要以为它与“先创建一个局部对象temp并返回它的结果”是等价的,如Stringtemp(s1+s2);returntemp;上述代码将发生三件事。首先temp对象被创建,同时完成初始化;然后拷贝构造函数把temp拷贝到保存返回值的外部存储单元中;最后temp在函数结束时被销毁(调用析构函数)。而“创建一个临时对象并返回它”的过程是不同的,编译器直接把临时对象创建并初始化在外部存储单元中,省去了拷贝和析构的花费,提高了效率。类似地,我们不要将returnint(x+y); //创建一个临时变量并返回它写成inttemp=x+y;returntemp;由于内部数据类型如int,float,double的变量不存在构造函数与析构函数,虽然该“临时变量的语法”不会提高多少效率,但是程序更加简洁易读。五、其他建议:【建议1】函数的功能要单一,不要设计多用途的函数。【建议2】函数体规模要小,尽量控制在50行代码之内。【建议3】尽量避免函数带有“记忆”功能。相同的输入应当产生相同的输出。带有“记忆”功能的函数,其行为可能是不可预测的,因为它的行为可能取决于某种“记忆状态”。这样的函数既不易理解又不利于测试和维护
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 高考总复习英语(人教版)教师用书选修7Unit2Robots
- 工程停工通知单监理表格B01-07
- 工程硕士研究生英语基础教程课后习题参考答案Unit1-16
- 人教版高中化学选修三2-2-1分子的立体结构(第一课时)课时测试1
- 全国新课标名校诊断测试(六)理科综合能力测试生物试题
- 江苏省南通市海安市实验中学2023-2024学年高一上学期期中考试政治试题
- 人教部编八年级语文上册《一着惊海天》教学课件
- VDA6完整版本.3过程审核核查表-机加
- 六年级下册-生命生态安全-教案
- 专利代理居间合同委托书
- 骨盆骨折PPT完整版
- 【企业应收账款管理研究的国内外文献综述】
- 英语颜色词语与心理情绪研究性学习展示
- VMware SRM方案介绍专业知识
- 看花识草辨药材(山东联盟)智慧树知到答案章节测试2023年山东中医药大学
- 统一用户管理平台系统操作手册
- 商务条款响应表
- IT外包服务网络安全管理办法v21
- 二年级上册语文课件-10《日月潭》|人教(部编版) (共19张PPT)
- 2023年最新电大高等数学基础形成性考核手册答案含题目
- 中华文化与传播教材课件
评论
0/150
提交评论