下载

3下载券

加入VIP
  • 专属下载特权
  • 现金文档折扣购买
  • VIP免费专区
  • 千万文档免费下载

上传资料

关闭

关闭

关闭

封号提示

内容

首页 高质量C++_C编程指南高质量C++_C编程指南

高质量C++_C编程指南高质量C++_C编程指南.doc

高质量C++_C编程指南高质量C++_C编程指南

liumarx
2018-09-08 0人阅读 举报 0 0 暂无简介

简介:本文档为《高质量C++_C编程指南高质量C++_C编程指南doc》,可适用于IT/计算机领域

高质量CC编程指南v高质量CC编程指南文件状态草稿文件√正式文件更改正式文件文件标识:当前版本:作者:林锐博士完成日期:年月日版本历史版本状态作者参与者起止日期备注V草稿文件林锐至林锐起草V正式文件林锐至朱洪海审查V林锐修正草稿中的错误目录前言第章文件结构版权和版本的声明头文件的结构定义文件的结构头文件的作用目录结构第章程序的版式空行代码行代码行内的空格对齐长行拆分修饰符的位置注释类的版式第章命名规则共性规则简单的Windows应用程序命名规则简单的Unix应用程序命名规则第章表达式和基本语句运算符的优先级复合表达式if语句循环语句的效率for语句的循环控制变量switch语句goto语句第章常量为什么需要常量const与#define的比较常量定义规则类中的常量第章函数设计参数的规则返回值的规则函数内部实现的规则其它建议使用断言引用与指针的比较第章内存管理内存分配方式常见的内存错误及其对策指针与数组的对比指针参数是如何传递内存的?free和delete把指针怎么啦?动态内存会被自动释放吗?杜绝“野指针”有了mallocfree为什么还要newdelete?内存耗尽怎么办?mallocfree的使用要点newdelete的使用要点一些心得体会第章C函数的高级特性函数重载的概念成员函数的重载、覆盖与隐藏参数的缺省值运算符重载函数内联一些心得体会第章类的构造函数、析构函数与赋值函数构造函数与析构函数的起源构造函数的初始化表构造和析构的次序示例:类String的构造函数与析构函数不要轻视拷贝构造函数与赋值函数示例:类String的拷贝构造函数与赋值函数偷懒的办法处理拷贝构造函数与赋值函数如何在派生类中实现类的基本函数一些心得体会第章类的继承与组合继承组合第章其它编程经验使用const提高函数的健壮性提高程序的效率一些有益的建议参考文献附录A:CC代码审查表附录B:CC试题附录C:CC试题的答案与评分标准前言软件质量是被大多数程序员挂在嘴上而不是放在心上的东西!除了完全外行和真正的编程高手外初读本书你最先的感受将是惊慌:“哇!我以前捏造的CC程序怎么会有那么多的毛病?”别难过作者只不过比你早几年、多几次惊慌而已。请花一两个小时认真阅读这本百页经书你将会获益匪浅这是前面N个读者的建议。一、编程老手与高手的误区自从计算机问世以来程序设计就成了令人羡慕的职业程序员在受人宠爱之后没有必要、也不应该知道函数是否需要内联。定义在类声明之中的成员函数将自动地成为内联函数例如classA{public:voidFoo(intx,inty){…}自动地成为内联函数}将成员函数的定义体放在类声明之中虽然能带来书写上的方便但不是一种良好的编程风格上例应该改成:头文件classA{public:voidFoo(intx,inty)}定义文件inlinevoidA::Foo(intx,inty){…}慎用内联内联能提高函数的执行效率为什么不把所有的函数都定义成内联函数?如果所有的函数都是内联函数还用得着“内联”这个关键字吗?内联是以代码膨胀(复制)为代价仅仅省去了函数调用的开销从而提高函数的执行效率。如果执行函数体内代码的时间相比于函数调用的开销较大那么效率的收获会很少。另一方面每一处内联函数的调用都要复制代码将使程序的总代码量增大消耗更多的内存空间。以下情况不宜使用内联:()如果函数体内的代码比较长使用内联将导致内存消耗代价较高。()如果函数体内出现循环那么执行函数体内代码的时间要比函数调用的开销大。类的构造函数和析构函数容易让人误解成使用内联更有效。要当心构造函数和析构函数可能会隐藏一些行为如“偷偷地”执行了基类或成员对象的构造函数和析构函数。所以不要随便地将构造函数和析构函数的定义体放在类声明中。一个好的编译器将会根据函数的定义体自动地取消不值得的内联(这进一步说明了inline不应该出现在函数的声明中)。一些心得体会C语言中的重载、内联、缺省参数、隐式转换等机制展现了很多优点但是这些优点的背后都隐藏着一些隐患。正如人们的饮食少食和暴食都不可取应当恰到好处。我们要辨证地看待C的新机制应该恰如其分地使用它们。虽然这会使我们编程时多费一些心思少了一些痛快但这才是编程的艺术。第章类的构造函数、析构函数与赋值函数构造函数、析构函数与赋值函数是每个类最基本的函数。它们太普通以致让人容易麻痹大意其实这些貌似简单的函数就象没有顶盖的下水道那样危险。每个类只有一个析构函数和一个赋值函数但可以有多个构造函数(包含一个拷贝构造函数其它的称为普通构造函数)。对于任意一个类A如果不想编写上述函数C编译器将自动为A产生四个缺省的函数如A(void)缺省的无参数构造函数A(constAa)缺省的拷贝构造函数~A(void)缺省的析构函数Aoperate=(constAa)缺省的赋值函数这不禁让人疑惑既然能自动生成函数为什么还要程序员编写?原因如下:()如果使用“缺省的无参数构造函数”和“缺省的析构函数”等于放弃了自主“初始化”和“清除”的机会C发明人Stroustrup的好心好意白费了。()“缺省的拷贝构造函数”和“缺省的赋值函数”均采用“位拷贝”而非“值拷贝”的方式来实现倘若类中含有指针变量这两个函数注定将出错。对于那些没有吃够苦头的C程序员如果他说编写构造函数、析构函数与赋值函数很容易可以不用动脑筋表明他的认识还比较肤浅水平有待于提高。本章以类String的设计与实现为例深入阐述被很多教科书忽视了的道理。String的结构如下:classString{public:String(constchar*str=)普通构造函数String(constStringother)拷贝构造函数~String(void)析构函数Stringoperate=(constStringother)赋值函数private:char*mdata用于保存字符串}构造函数与析构函数的起源作为比C更先进的语言C提供了更好的机制来增强程序的安全性。C编译器具有严格的类型安全检查功能它几乎能找出程序中所有的语法问题这的确帮了程序员的大忙。但是程序通过了编译检查并不表示错误已经不存在了在“错误”的大家庭里“语法错误”的地位只能算是小弟弟。级别高的错误通常隐藏得很深就象狡猾的罪犯想逮住他可不容易。根据经验不少难以察觉的程序错误是由于变量没有被正确初始化或清除造成的而初始化和清除工作很容易被人遗忘。Stroustrup在设计C语言时充分考虑了这个问题并很好地予以解决:把对象的初始化工作放在构造函数中把清除工作放在析构函数中。当对象被创建时构造函数被自动执行。当对象消亡时析构函数被自动执行。这下就不用担心忘了对象的初始化和清除工作。构造函数与析构函数的名字不能随便起必须让编译器认得出才可以被自动执行。Stroustrup的命名方法既简单又合理:让构造函数、析构函数与类同名由于析构函数的目的与构造函数的相反就加前缀‘~’以示区别。除了名字外构造函数与析构函数的另一个特别之处是没有返回值类型这与返回值类型为void的函数不同。构造函数与析构函数的使命非常明确就象出生与死亡光溜溜地来光溜溜地去。如果它们有返回值类型那么编译器将不知所措。为了防止节外生枝干脆规定没有返回值类型。(以上典故参考了文献Eekel,pp)构造函数的初始化表构造函数有个特殊的初始化方式叫“初始化表达式表”(简称初始化表)。初始化表位于函数参数表之后却在函数体{}之前。这说明该表里的初始化工作发生在函数体内的任何代码被执行之前。构造函数初始化表的使用规则:·如果类存在继承关系派生类必须在其初始化表里调用基类的构造函数。例如classA{…A(intx)A的构造函数}classB:publicA{…B(intx,inty)B的构造函数}B::B(intx,inty):A(x)在初始化表里调用A的构造函数{…}·类的const常量只能在初始化表里被初始化因为它不能在函数体内用赋值的方式来初始化(参见节)。·类的数据成员的初始化可以采用初始化表或函数体内赋值两种方式这两种方式的效率不完全相同。非内部数据类型的成员对象应当采用第一种方式初始化以获取更高的效率。例如classA{…A(void)无参数构造函数A(constAother)拷贝构造函数Aoperate=(constAother)赋值函数}classB{public:B(constAa)B的构造函数private:Ama成员对象}示例(a)中类B的构造函数在其初始化表里调用了类A的拷贝构造函数从而将成员对象ma初始化。示例(b)中类B的构造函数在函数体内用赋值的方式将成员对象ma初始化。我们看到的只是一条赋值语句但实际上B的构造函数干了两件事:先暗地里创建ma对象(调用了A的无参数构造函数)再调用类A的赋值函数将参数a赋给ma。B::B(constAa):ma(a){…}B::B(constAa){ma=a…}示例(a)成员对象在初始化表中被初始化示例(b)成员对象在函数体内被初始化对于内部数据类型的数据成员而言两种初始化方式的效率几乎没有区别但后者的程序版式似乎更清晰些。若类F的声明如下:classF{public:F(intx,inty)构造函数private:intmx,myintmi,mj}示例(c)中F的构造函数采用了第一种初始化方式示例(d)中F的构造函数采用了第二种初始化方式。F::F(intx,inty):mx(x),my(y){mi=mj=}F::F(intx,inty){mx=xmy=ymi=mj=}示例(c)数据成员在初始化表中被初始化示例(d)数据成员在函数体内被初始化构造和析构的次序构造从类层次的最根处开始在每一层中首先调用基类的构造函数然后调用成员对象的构造函数。析构则严格按照与构造相反的次序执行该次序是唯一的否则编译器将无法自动执行析构过程。一个有趣的现象是成员对象初始化的次序完全不受它们在初始化表中次序的影响只由成员对象在类中声明的次序决定。这是因为类的声明是唯一的而类的构造函数可以有多个因此会有多个不同次序的初始化表。如果成员对象按照初始化表的次序进行构造这将导致析构函数无法得到唯一的逆序。Eckel,p示例:类String的构造函数与析构函数String的普通构造函数String::String(constchar*str){if(str==){mdata=newchar*mdata=‘’}else{intlength=strlen(str)mdata=newcharlengthstrcpy(mdata,str)}}String的析构函数String::~String(void){deletemdata由于mdata是内部数据类型也可以写成deletemdata}不要轻视拷贝构造函数与赋值函数由于并非所有的对象都会使用拷贝构造函数和赋值函数程序员可能对这两个函数有些轻视。请先记住以下的警告在阅读正文时就会多心:·本章开头讲过如果不主动编写拷贝构造函数和赋值函数编译器将以“位拷贝”的方式自动生成缺省的函数。倘若类中含有指针变量那么这两个缺省的函数就隐含了错误。以类String的两个对象a,b为例假设amdata的内容为“hello”bmdata的内容为“world”。现将a赋给b缺省赋值函数的“位拷贝”意味着执行bmdata=amdata。这将造成三个错误:一是bmdata原有的内存没被释放造成内存泄露二是bmdata和amdata指向同一块内存a或b任何一方变动都会影响另一方三是在对象被析构时mdata被释放了两次。·拷贝构造函数和赋值函数非常容易混淆常导致错写、错用。拷贝构造函数是在对象被创建时调用的而赋值函数只能被已经存在了的对象调用。以下程序中第三个语句和第四个语句很相似你分得清楚哪个调用了拷贝构造函数哪个调用了赋值函数吗?Stringa(“hello”)Stringb(“world”)Stringc=a调用了拷贝构造函数最好写成c(a)c=b调用了赋值函数本例中第三个语句的风格较差宜改写成Stringc(a)以区别于第四个语句。示例:类String的拷贝构造函数与赋值函数拷贝构造函数String::String(constStringother){允许操作other的私有成员mdataintlength=strlen(othermdata)mdata=newcharlengthstrcpy(mdata,othermdata)}赋值函数StringString::operate=(constStringother){()检查自赋值if(this==other)return*this()释放原有的内存资源deletemdata()分配新的内存资源并复制内容intlength=strlen(othermdata)mdata=newcharlengthstrcpy(mdata,othermdata)()返回本对象的引用return*this}类String拷贝构造函数与普通构造函数(参见节)的区别是:在函数入口处无需与进行比较这是因为“引用”不可能是而“指针”可以为。类String的赋值函数比构造函数复杂得多分四步实现:()第一步检查自赋值。你可能会认为多此一举难道有人会愚蠢到写出a=a这样的自赋值语句!的确不会。但是间接的自赋值仍有可能出现例如内容自赋值b=a…c=b…a=c地址自赋值b=a…a=*b也许有人会说:“即使出现自赋值我也可以不理睬大不了化点时间让对象复制自己而已反正不会出错!”他真的说错了。看看第二步的delete自杀后还能复制自己吗?所以如果发现自赋值应该马上终止函数。注意不要将检查自赋值的if语句if(this==other)错写成为if(*this==other)()第二步用delete释放原有的内存资源。如果现在不释放以后就没机会了将造成内存泄露。()第三步分配新的内存资源并复制字符串。注意函数strlen返回的是有效字符串长度不包含结束符‘’。函数strcpy则连‘’一起复制。()第四步返回本对象的引用目的是为了实现象a=b=c这样的链式表达。注意不要将return*this错写成returnthis。那么能否写成returnother呢?效果不是一样吗?不可以!因为我们不知道参数other的生命期。有可能other是个临时对象在赋值结束后它马上消失那么returnother返回的将是垃圾。偷懒的办法处理拷贝构造函数与赋值函数如果我们实在不想编写拷贝构造函数和赋值函数又不允许别人使用编译器生成的缺省函数怎么办?偷懒的办法是:只需将拷贝构造函数和赋值函数声明为私有函数不用编写代码。例如:classA{…private:A(constAa)私有的拷贝构造函数Aoperate=(constAa)私有的赋值函数}如果有人试图编写如下程序:Ab(a)调用了私有的拷贝构造函数b=a调用了私有的赋值函数编译器将指出错误因为外界不可以操作A的私有函数。如何在派生类中实现类的基本函数基类的构造函数、析构函数、赋值函数都不能被派生类继承。如果类之间存在继承关系在编写上述基本函数时应注意以下事项:·派生类的构造函数应在其初始化表里调用基类的构造函数。·基类与派生类的析构函数应该为虚(即加virtual关键字)。例如#include<iostreamh>classBase{public:virtual~Base(){cout<<"~Base"<<endl}}classDerived:publicBase{public:virtual~Derived(){cout<<"~Derived"<<endl}}voidmain(void){Base*pB=newDerivedupcastdeletepB}输出结果为:~Derived~Base如果析构函数不为虚那么输出结果为~Base·在编写派生类的赋值函数时注意不要忘记对基类的数据成员重新赋值。例如:classBase{public:…Baseoperate=(constBaseother)类Base的赋值函数private:intmi,mj,mk}classDerived:publicBase{public:…Derivedoperate=(constDerivedother)类Derived的赋值函数private:intmx,my,mz}DerivedDerived::operate=(constDerivedother){()检查自赋值if(this==other)return*this()对基类的数据成员重新赋值Base::operate=(other)因为不能直接操作私有数据成员()对派生类的数据成员赋值mx=othermxmy=othermymz=othermz()返回本对象的引用return*this}一些心得体会有些C程序设计书籍称构造函数、析构函数和赋值函数是类的“BigThree”它们的确是任何类最重要的函数不容轻视。也许你认为本章的内容已经够多了学会了就能平安无事我不能作这个保证。如果你希望吃透“BigThree”请好好阅读参考文献ClineMeyersMurry。第章类的继承与组合对象(Object)是类(Class)的一个实例(Instance)。如果将对象比作房子那么类就是房子的设计图纸。所以面向对象设计的重点是类的设计而不是对象的设计。对于C程序而言设计孤立的类是比较容易的难的是正确设计基类及其派生类。本章仅仅论述“继承”(Inheritance)和“组合”(Composition)的概念。注意当前面向对象技术的应用热点是COM和CORBA这些内容超出了C教材的范畴请阅读COM和CORBA相关论著。继承如果A是基类B是A的派生类那么B将继承A的数据和函数。例如:classA{public:voidFunc(void)voidFunc(void)}classB:publicA{public:voidFunc(void)voidFunc(void)}main(){BbbFunc()B从A继承了函数FuncbFunc()B从A继承了函数FuncbFunc()bFunc()}这个简单的示例程序说明了一个事实:C的“继承”特性可以提高程序的可复用性。正因为“继承”太有用、太容易用才要防止乱用“继承”。我们应当给“继承”立一些使用规则。·【规则】如果类A和类B毫不相关不可以为了使B的功能更多些而让B继承A的功能和属性。不要觉得“白吃白不吃”让一个好端端的健壮青年无缘无故地吃人参补身体。·【规则】若在逻辑上B是A的“一种”(akindof)则允许B继承A的功能和属性。例如男人(Man)是人(Human)的一种男孩(Boy)是男人的一种。那么类Man可以从类Human派生类Boy可以从类Man派生。classHuman{…}classMan:publicHuman{…}classBoy:publicMan{…}·注意事项【规则】看起来很简单但是实际应用时可能会有意外继承的概念在程序世界与现实世界并不完全相同。例如从生物学角度讲鸵鸟(Ostrich)是鸟(Bird)的一种按理说类Ostrich应该可以从类Bird派生。但是鸵鸟不能飞那么Ostrich::Fly是什么东西?classBird{public:virtualvoidFly(void)…}classOstrich:publicBird{…}例如从数学角度讲圆(Circle)是一种特殊的椭圆(Ellipse)按理说类Circle应该可以从类Ellipse派生。但是椭圆有长轴和短轴如果圆继承了椭圆的长轴和短轴岂非画蛇添足?所以更加严格的继承规则应当是:若在逻辑上B是A的“一种”并且A的所有功能和属性对B而言都有意义则允许B继承A的功能和属性。组合·【规则】若在逻辑上A是B的“一部分”(apartof)则不允许B从A派生而是要用A和其它东西组合出B。例如眼(Eye)、鼻(Nose)、口(Mouth)、耳(Ear)是头(Head)的一部分所以类Head应该由类Eye、Nose、Mouth、Ear组合而成不是派生而成。如示例所示。classEye{public:voidLook(void)}classNose{public:voidSmell(void)}classMouth{public:voidEat(void)}classEar{public:voidListen(void)}正确的设计虽然代码冗长。classHead{public:voidLook(void){meyeLook()}voidSmell(void){mnoseSmell()}voidEat(void){mmouthEat()}voidListen(void){mearListen()}private:EyemeyeNosemnoseMouthmmouthEarmear}示例Head由Eye、Nose、Mouth、Ear组合而成如果允许Head从Eye、Nose、Mouth、Ear派生而成那么Head将自动具有Look、Smell、Eat、Listen这些功能。示例十分简短并且运行正确但是这种设计方法却是不对的。功能正确并且代码简洁但是设计方法不对。classHead:publicEye,publicNose,publicMouth,publicEar{}示例Head从Eye、Nose、Mouth、Ear派生而成一只公鸡使劲地追打一只刚下了蛋的母鸡你知道为什么吗?因为母鸡下了鸭蛋。很多程序员经不起“继承”的诱惑而犯下设计错误。“运行正确”的程序不见得是高质量的程序此处就是一个例证。第章其它编程经验使用const提高函数的健壮性看到const关键字C程序员首先想到的可能是const常量。这可不是良好的条件反射。如果只知道用const定义常量那么相当于把火药仅用于制作鞭炮。const更大的魅力是它可以修饰函数的参数、返回值甚至函数的定义体。const是constant的缩写“恒定不变”的意思。被const修饰的东西都受到强制保护可以预防意外的变动能提高程序的健壮性。所以很多C程序设计书籍建议:“Useconstwheneveryouneed”。用const修饰函数的参数如果参数作输出用不论它是什么数据类型也不论它采用“指针传递”还是“引用传递”都不能加const修饰否则该参数将失去输出功能。const只能修饰输入参数:·如果输入参数采用“指针传递”那么加const修饰可以防止意外地改动该指针起到保护作用。例如StringCopy函数:voidStringCopy(char*strDestination,constchar*strSource)其中strSource是输入参数strDestination是输出参数。给strSource加上const修饰后如果函数体内的语句试图改动strSource的内容编译器将指出错误。·如果输入参数采用“值传递”由于函数将自动产生临时变量用于复制该参数该输入参数本来就无需保护所以不要加const修饰。例如不要将函数voidFunc(intx)写成voidFunc(constintx)。同理不要将函数voidFunc(Aa)写成voidFunc(constAa)。其中A为用户自定义的数据类型。·对于非内部数据类型的参数而言象voidFunc(Aa)这样声明的函数注定效率比较底。因为函数体内将产生A类型的临时对象用于复制参数a而临时对象的构造、复制、析构过程都将消耗时间。为了提高效率可以将函数声明改为voidFunc(Aa)因为“引用传递”仅借用一下参数的别名而已不需要产生临时对象。但是函数voidFunc(Aa)存在一个缺点:“引用传递”有可能改变参数a这是我们不期望的。解决这个问题很容易加const修饰即可因此函数最终成为voidFunc(constAa)。以此类推是否应将voidFunc(intx)改写为voidFunc(constintx)以便提高效率?完全没有必要因为内部数据类型的参数不存在构造、析构的过程而复制也非常快“值传递”和“引用传递”的效率几乎相当。问题是如此的缠绵我只好将“const”修饰输入参数的用法总结一下如表所示。对于非内部数据类型的输入参数应该将“值传递”的方式改为“const引用传递”目的是提高效率。例如将voidFunc(Aa)改为voidFunc(constAa)。对于内部数据类型的输入参数不要将“值传递”的方式改为“const引用传递”。否则既达不到提高效率的目的又降低了函数的可理解性。例如voidFunc(intx)不应该改为voidFunc(constintx)。表“const”修饰输入参数的规则用const修饰函数的返回值·如果给以“指针传递”方式的函数返回值加const修饰那么函数返回值(即指针)的内容不能被修改该返回值只能被赋给加const修饰的同类型指针。例如函数constchar*GetString(void)如下语句将出现编译错误:char*str=GetString()正确的用法是constchar*str=GetString()·如果函数返回值采用“值传递方式”由于函数会把返回值复制到外部临时的存储单元中加const修饰没有任何价值。例如不要把函数intGetInt(void)写成constintGetInt(void)。同理不要把函数AGetA(void)写成constAGetA(void)其中A为用户自定义的数据类型。如果返回值不是内部数据类型将函数AGetA(void)改写为constAGetA(void)的确能提高效率。但此时千万千万要小心一定要搞清楚函数究竟是想返回一个对象的“拷贝”还是仅返回“别名”就可以了否则程序会出错。见节“返回值的规则”。·函数返回值采用“引用传递”的场合并不多这种方式一般只出现在类的赋值函数中目的是为了实现链式表达。例如classA{…Aoperate=(constAother)赋值函数}Aa,b,ca,b,c为A的对象…a=b=c正常的链式赋值(a=b)=c不正常的链式赋值但合法如果将赋值函数的返回值加const修饰那么该返回值的内容不允许被改动。上例中语句a=b=c仍然正确但是语句(a=b)=c则是非法的。const成员函数任何不会修改数据成员的函数都应该声明为const类型。如果在编写const成员函数时不慎修改了数据成员或者调用了其它非const成员函数编译器将指出错误这无疑会提高程序的健壮性。以下程序中类stack的成员函数GetCount仅用于计数从逻辑上讲GetCount应当为const函数。编译器将指出GetCount函数中的错误。classStack{public:voidPush(intelem)intPop(void)intGetCount(void)constconst成员函数private:intmnumintmdata}intStack::GetCount(void)const{mnum编译错误企图修改数据成员mnumPop()编译错误企图调用非const函数returnmnum}const成员函数的声明看起来怪怪的:const关键字只能放在函数声明的尾部大概是因为其它地方都已经被占用了。提高程序的效率程序的时间效率是指运行速度空间效率是指程序占用内存或者外存的状况。全局效率是指站在整个系统的角度上考虑的效率局部效率是指站在模块或函数角度上考虑的效率。·【规则】不要一味地追求程序的效率应当在满足正确性、可靠性、健壮性、可读性等质量因素的前提下设法提高程序的效率。·【规则】以提高程序的全局效率为主提高局部效率为辅。·【规则】在优化程序的效率时应当先找出限制效率的“瓶颈”不要在无关紧要之处优化。·【规则】先优化数据结构和算法再优化执行代码。·【规则】有时候时间效率和空间效率可能对立此时应当分析那个更重要作出适当的折衷。例如多花费一些内存来提高性能。·【规则】不要追求紧凑的代码因为紧凑的代码并不能产生高效的机器码。一些有益的建议·【建议】当心那些视觉上不易分辨的操作符发生书写错误。我们经常会把“==”误写成“=”象“||”、“”、“<=”、“>=”这类符号也很容易发生“丢”失误。然而编译器却不一定能自动指出这类错误。·【建议】变量(指针、数组)被创建之后应当及时把它们初始化以防止把未被初始化的变量当成右值使用。·【建议】当心变量的初值、缺省值错误或者精度不够。·【建议】当心数据类型转换发生错误。尽量使用显式的数据类型转换(让人们知道发生了什么事)避免让编译器轻悄悄地进行隐式的数据类型转换。·【建议】当心变量发生上溢或下溢数组的下标越界。·【建议】当心忘记编写错误处理程序当心错误处理程序本身有误。·【建议】当心文件IO有错误。·【建议】避免编写技巧性很高代码。·【建议】不要设计面面俱到、非常灵活的数据结构。·【建议】如果原有的代码质量比较好尽量复用它。但是不要修补很差劲的代码应当重新编写。·【建议】尽量使用标准库函数不要“发明”已经存在的库函数。·【建议】尽量不要使用与具体硬件或软件环境关系密切的变量。·【建议】把编译器的选择项设置为最严格状态。·【建议】如果可能的话使用PCLint、LogiScope等工具进行代码审查。参考文献ClineMarshallPClineandGregALomow,CFAQs,AddisonWesley,EckelBruceEckel,ThinkinginC(C编程思想刘宗田等译)机械工业出版社MaguireSteveMaguire,WritingCleanCode(编程精粹姜静波等译)电子工业出版社MeyersScottMeyers,EffectiveC,AddisonWesley,MurryRobertBMurry,CStrategiesandTactics,AddisonWesley,SummitSteveSummit,CProgrammingFAQs,AddisonWesley,附录A:CC代码审查表文件结构重要性审查项结论头文件和定义文件的名称是否合理?头文件和定义文件的目录结构是否合理?版权和版本声明是否完整?重要头文件是否使用了ifndefdefineendif预处理块头文件中是否只存放“声明”而不存放“定义”……程序的版式重要性审查项结论空行是否得体?代码行内的空格是否得体?长行拆分是否得体?“{”和“}”是否各占一行并且对齐于同一列?重要一行代码是否只做一件事?如只定义一个变量只写一条语句。重要If、for、while、do等语句自占一行不论执行语句多少都要加“{}”。重要在定义变量(或参数)时是否将修饰符*和&紧靠变量名?注释是否清晰并且必要?重要注释是否有错误或者可能导致误解?重要类结构的public,protected,private顺序是否在所有的程序中保持一致?……命名规则重要性审查项结论重要命名规则是否与所采用的操作系统或开发工具的风格保持一致?标识符是否直观且可以拼读?标识符的长度应当符合“minlengthmaxinformation”原则?重要程序中是否出现相同的局部变量和全部变量?类名、函数名、变量和参数、常量的书写格式是否遵循一定的规则?静态变量、全局变量、类的成员变量是否加前缀?……表达式与基本语句重要性审查项结论重要如果代码行中的运算符比较多是否已经用括号清楚地确定表达式的操作顺序?是否编写太复杂或者多用途的复合表达式?重要是否将复合表达式与“真正的数学表达式”混淆?重要是否用隐含错误的方式写if语句例如()将布尔变量直接与TRUE、FALSE或者、进行比较。()将浮点变量用“==”或“!=”与任何数字比较。()将指针变量用“==”或“!=”与比较。如果循环体内存在逻辑判断并且循环次数很大是否已经将逻辑判断移到循环体的外面?重要Case语句的结尾是否忘了加break?重要是否忘记写switch的default分支?重要使用goto语句时是否留下隐患例如跳过了某些对象的构造、变量的初始化、重要的计算等。……常量重要性审查项结论是否使用含义直观的常量来表示那些将在程序中多次出现的数字或字符串?在C程序中是否用const常量取代宏常量?重要如果某一常量与其它常量密切相关是否在定义中包含了这种关系?是否误解了类中的const数据成员?因为const数据成员只在某个对象生存期内是常量而对于整个类而言却是可变的。……函数设计重要性审查项结论参数的书写是否完整?不要贪图省事只写参数的类型而省略参数名字。参数命名、顺序是否合理?参数的个数是否太多?是否使用类型和数目不确定的参数?是否省略了函数返回值的类型?函数名字与返回值类型在语义上是否冲突?重要是否将正常值和错误标志混在一起返回?正常值应当用输出参数获得而错误标志用return语句返回。重要在函数体的“入口处”是否用assert对参数的有效性进行检查?重要使用滥用了assert?例如混淆非法情况与错误情况后者是必然存在的并且是一定要作出处理的。重要return语句是否返回指向“栈内存”的“指针”或者“引用”?是否使用const提高函数的健壮性?const可以强制保护函数的参数、返回值甚至函数的定义体。“Useconstwheneveryouneed”……内存管理重要性审查项结论重要用malloc或new申请内存之后是否立即检查指针值是否为?(防止使用指针值为的内存)重要是否忘记为数组和动态内存赋初值?(防止将未被初始化的内存作为右值使用)重要数组或指针的下标是否越界?重要动态内存的申请与释放是否配对?(防止内存泄漏)重要是否有效地处理了“内存耗尽”问题?重要是否修改“指向常量的指针”的内容?重要是否出现野指针?例如()指针变量没有被初始化。()用free或delete释放了内存之后忘记将指针设置为。重要是否将mallocfree和newdelete混淆使用?重要malloc语句是否正确无误?例如字节数是否正确?类型转换是否正确?重要在创建与释放动态对象数组时newdelete的语句是否正确无误?……C函数的高级特性重要性审查项结论重载函数是否有二义性?重要是否混淆了成员函数的重载、覆盖与隐藏?运算符的重载是否符合制定的编程规范?是否滥用内联函数?例如函数体内的代码比较长函数体内出现循环。重要是否用内联函数取代了宏代码?……类的构造函数、析构函数和赋值函数重要性审查项结论重要是否违背编程规范而让C编译器自动为类产生四个缺省的函数:()缺省的无参数构造函数()缺省的拷贝构造函数()缺省的析构函数()缺省的赋值函数。重要构造函数中是否遗漏了某些初始化工作?重要是否正确地使用构造函数的初始化表?重要析构函数中是否遗漏了某些清除工作?是否错写、错用了拷贝构造函数和赋值函数?重要赋值函数一般分四个步骤:()检查自赋值()释放原有内存资源()分配新的内存资源并复制内容()返回*this。是否遗漏了重要步骤?重要是否正确地编写了派生类的构造函数、析构函数、赋值函数?注意事项:()派生类不可能继承基类的构造函数、析构函数、赋值函数。()派生类的构造函数应在其初始化表里调用基类的构造函数。()基类与派生类的析构函数应该为虚(即加virtual关键字)。()在编写派生类的赋值函数时注意不要忘记对基类的数据成员重新赋值。……类的高级特性重要性审查项结论重要是否违背了继承和组合的规则?()若在逻辑上B是A的“一种”并且A的所有功能和属性对B而言都有意义则允许B继承A的功能和属性。()若在逻辑上A是B的“一部分”(apartof)则不允许B从A派生而是要用A和其它东西组合出B。……其它常见问题重要性审查项结论重要数据类型问题:(1)变量的数据类型有错误吗?(2)存在不同数据类型的赋值吗?(3)存在不同数据类型的比较吗?重要变量值问题:(1)变量的初始化或缺省值有错误吗?(2)变量发生上溢或下溢吗?(3)变量的精度够吗?重要逻辑判断问题:(1)由于精度原因导致比较无效吗?(2)表达式中的优先级有误吗?(3)逻辑判断结果颠倒吗?重要循环问题:(1)循环终止条件不正确吗?(2)无法正常终止(死循环)吗?(3)错误地修改循环变量吗?(4)存在误差累积吗?重要错误处理问题:(1)忘记进行错误处理吗?(2)错误处理程序块一直没有机会被运行?(3)错误处理程序块本身就有毛病吗?如报告的错误与实际错误不一致处理方式不正确等等。(4)错误处理程序块是“马后炮”吗?如在被它被调用之前软件已经出错。重要文件IO问题:(1)对不存在的或者错误的文件进行操作吗?(2)文件以不正确的方式打开吗?(3)文件结束判断不正确吗?(4)没有正确地关闭文件吗?附录B:CC试题本试题仅用于考查CC程序员的基本编程技能。内容限于CC常用语法不涉及数据结构、算法以及深奥的语法。考试成绩能反映出考生的编程质量以及对CC的理解程度但不能反映考生的智力和软件开发能力。笔试时间分钟。请考生认真答题切勿轻视。一、请填写BOOL,float,指针变量与“零值”比较的if语句。(分)提示:这里“零值”可以是,,FALSE或者“空指针”。例如int变量n与“零值”比较的if语句为:if(n==)if(n!=)以此类推。请写出BOOLflag与“零值”比较的if语句:请写出floatx与“零值”比较的if语句:请写出char*p与“零值”比较的if语句:二、以下为WindowsNT下的位C程序请计算sizeof的值(分)charstr=“Hello”char*p=strintn=请计算sizeof(str)=sizeof(p)=sizeof(n)=voidFunc(charstr){请计算sizeof(str)=}void*p=malloc()请计算sizeof(p)=三、简答题(分)、头文件中的ifndefdefineendif干什么用?、#include<filenameh>和#include“filenameh”有什么区别?、const有什么用途?(请至少说明两种)、在C程序中调用被C编译器编译后的函数为什么要加extern“C”声明?、请简述以下两个for循环的优缺点第一个for(i=i<Ni){if(condition)DoSomething()elseDoOtherthing()}第二个if(condition){for(i=i<Ni)DoSomething()}else{for(i=i<Ni)DoOtherthing()}优点:缺点:优点:缺点:四、有关内存的思考题(分)voidGetMemory(char*p){p=(char*)malloc()}voidTest(void){char*str=GetMemory(str)strcpy(str,"helloworld")printf(str)}请问运行Test函数会有什么样的结果?答:char*GetMemory(void){charp="helloworld"returnp}voidTest(void){char*str=str=GetMemory()printf(str)}请问运行Test函数会有什么样的结果?答:VoidGetMemory(char**p,intnum){*p=(char*)malloc(num)}voidTest(void){char*str=GetMemory(str,)strcpy(str,"hello")printf(str)}请问运行Test函数会有什么样的结果?答:voidTest(void){char*str=(char*)malloc()strcpy(str,“hello”)free(str)if(str!=){strcpy(str,“world”)printf(str)}}请问运行Test函数会有什么样的结果?答:五、编写strcpy函数(分)已知strcpy函数的原型是char*strcpy(char*strDest,constchar*strSrc)其中strDest是目的字符串strSrc是源字符串。()不调用CC的字符串库函数请编写函数strcpy()strcpy能把strSrc的内容复制到strDest为什么还要char*类型的返回值?六、编写类String的构造函数、析构函数和赋值函数(分)已知类String的原型为:classString{public:String(constchar*str=)普通构造函数String(constStringother)拷贝构造函数~String(void)析构函数Stringoperate=(constStringother)赋值函数private:char*mdata用于保存字符串}请编写String的上述个函数。附录C:CC试题的答案与评分标准一、请填写BOOL,float,指针变量与“零值”比较的if语句。(分)请写出BOOLflag与“零值”比较的if语句。(分)标准答案:if(flag)if(!flag)如下写法均属不良风格不得分。if(flag==TRUE)if(flag==)if(flag==FALSE)if(flag==)请写出floatx与“零值”比较的if语句。(分)标准答案示例:constfloatEPSINON=if((x>=EPSINON)(x<=EPSINON)不可将浮点变量用“==”或“!=”与数字比较应该设法转化成“>=”或“<=”此类形式。如下是错误的写法不得分。if(x==)if(x!=)请写出char*p与“零值”比较的if语句。(分)标准答案:if(p==)if(p!=)如下写法均属不良风格不得分。if(p==)if(p!=)if(p)if(!)二、以下为WindowsNT下的位C程序请计算sizeof的值(分)charstr=“Hello”char*p=strintn=请计算sizeof(str)=(分)sizeof(p)=(分)sizeof(n)=(分)voidFunc(charstr){请计算sizeof(str)=(分)}void*p=malloc()请计算sizeof(p)=(分)三、简答题(分)、头文件中的ifndefdefineendif干什么用?(分)答:防止该头文件被重复引用。、#include<filenameh>和#include“filenameh”有什么区别?(分)答:对于#include<filenameh>编译器从标准库路径开始搜索filenameh对于#include“filenameh”编译器从用户的工作路径开始搜索filenameh、const有什么用途?(请至少说明两种)(分)答:()可以定义const常量()const可以修饰函数的参数、返回值甚至函数的定义体。被const修饰的东西都受到强制保护可以预防意外的变动能提高程序的健壮性。、在C程序中调用被C编译器编译后的函数为什么要加extern“C”?(分)答:C语言支持函数重载C语言不支持函数重载。函数被C编译后在库中的名字与C语言的不同。假设某个函数的原型为:voidfoo(intx,inty)该函数被C编译器编译后在库中的名字为foo而C编译器则会产生像foointint之类的名字。C提供了C连接交换指定符号extern“C”来解决名字匹配问题。、请简述以下两个for循环的优缺点(分)for(i=i<Ni){if(condition)DoSomething()elseDoOtherthing()}if(condition){for(i=i<Ni)DoSomething()}else{for(i=i<Ni)DoOtherthing()}优点:程序简洁缺点:多执行了N次逻辑判断并且打断了循环“流水线”作业使得编译器不能对循环进行优化处理降低了效率。优点:循环的效率高缺点:程序不简洁四、有关内存的思考题(每小题分共分)voidGetMemory(char*p){p=(char*)malloc()}voidTest(void){char*str=GetMemory(str)strcpy(str,"helloworld")printf(str)}请问运行Test函数会有什么样的结果?答:程序崩溃。因为GetMemory并不能传递动态内存Test函数中的str一直都是。strcpy(str,"helloworld")将使程序崩溃。char*GetMemory(void){charp="helloworld"returnp}voidTest(void){char*str=str=GetMemory()printf(str)}请问运行Test函数会有什么样的结果?答:可能是乱码。因为GetMemory返回的是指向“栈内存”的指针该指针的地址不是但其原现的内容已经被清除新内容不可知。voidGetMemory(char**p,intnum){*p=(char*)malloc(num)}voidTest(void){char*str=GetMemory(str,)strcpy(str,"hello")printf(str)}请问运行Test函数会有什么样的结果?答:()能够输出hello()内存泄漏voidTest(void){char*str=(char*)malloc()strcpy(str,“hello”)free(str)if(str!=){strcpy(str,“world”)printf(str)}}请问运行Test函数会有什么样的结果?答:篡改动态内存区的内容后果难以预料非常危险。因为free(str)之后str成为野指针if(str!=)语句不起作用。五、编写strcpy函数(分)已知strcpy函数的原型是char*strcpy(char*strDest,constchar*strSrc)其中strDest是目的字符串strSrc是源字符串。()不调用CC的字符串库函数请编写函数strcpychar*strcpy(char*strDest,constchar*strSrc){assert((strDest!=)(strSrc!=))分char*address=strDest分while((*strDest=*strSrc)!=‘’)分returnaddress分}()strcpy能把strSrc的内容复制到strDest为什么还要char*类型的返回值?答:为了实现链式表达式。分例如intlength=strlen(strcpy(strDest,“helloworld”))六、编写类String的构造函数、析构函数和赋值函数(分)已知类String的原型为:classString{public:String(constchar*str=)普通构造函数String(constStringother)拷贝构造函数~String(void)析构函数Stringoperate=(constStringother)赋值函数private:char*mdata用于保存字符串}请编写String的上述个函数。标准答案:String的析构函数String::~String(void)分{deletemdata由于mdata是内部数据类型也可以写成deletemdata}String的普通构造函数String::String(constchar*str)分{if(str==){mdata=newchar若能加判断则更好*mdata=‘’}else{intlength=strlen(str)mdata=newcharlength若能加判断则更好strcpy(mdata,str)}}拷贝构造函数String::String(constStringother)分{intlength=strlen(othermdata)mdata=newcharlength若能加判断则更好strcpy(mdata,othermdata)}赋值函数StringString::operate=(constStringother)分{()检查自赋值分if(this==other)return*this()释放原有的内存资源分deletemdata()分配新的内存资源并复制内容分intlength=strlen(othermdata)mdata=newcharlength若能加判断则更好strcpy(mdata,othermdata)()返回本对象的引用分return*this}Pageo

用户评价(0)

关闭

新课改视野下建构高中语文教学实验成果报告(32KB)

抱歉,积分不足下载失败,请稍后再试!

提示

试读已结束,如需要继续阅读或者下载,敬请购买!

文档小程序码

使用微信“扫一扫”扫码寻找文档

1

打开微信

2

扫描小程序码

3

发布寻找信息

4

等待寻找结果

我知道了
评分:

/101

高质量C++_C编程指南高质量C++_C编程指南

VIP

在线
客服

免费
邮箱

爱问共享资料服务号

扫描关注领取更多福利