c++/c语言(高质量程序设计指南林锐建议总结)

6 篇文章 0 订阅

第四章C++/C程序设计入门

  1. 在使用运算符&&的表达式中,要尽量把最可能为false中的子表达式放在
    &&的左边;同样在使用运算符“||”的表达式中,要尽量把最可能为true的表达式放在“||”的左边。因为c++/c对逻辑表达式的判断采取“突然死亡法”(猝死法);

  2. 在if/else中,要尽量把为true的概率较高的条件的条件判断置于前面,这样可以提高段程序的性能。

  3. 如果计数器从0开始计数,则建议for语句的循环控制变量的取值采用“前闭后开”写法。要防止出现差1错误。

  4. 对于多维数组来说,正确的遍历方法要看语言以什么顺序来安排数组元素的存储空间。比如fortran是以“先列后行”的顺序在内存中连续存放数组元素,而c++/c则是以“先行后列”的顺序来连续存储数组元素。因此。以“先行后列”存储的数组就应该以“先行后列”的顺序遍历。

  5. 如果循环体内存在逻辑判断,并且循环次数很大,宜将逻辑判断移到循环体外面。

第五章 C++/C 常量

  1. 尽量使用含义直观的符号变量来表示那些将在程序中多次出现的数字或字符串。

第六章C++/C函数设计基础

  1. (1)应避免函数有太多的参数,参数的个数尽量控制在5个以内。如果参数太多,在使用时容易将参数类型和顺序搞错,此时,可以将这些参数封装为一个对象并采用地址传递或传递方式。
  2. (2)尽量不要使用类型和数目不确定的参数列表。c标准库函数prinf时采用不确定参数列表的典型列表,其原型为:
int printf(const char *format[,argument]...)
  1. 这种风格的函数在编译时丧失了严格的静态类型安全检查。
  2. 不要将正常值错误标志混在一起返回。建议正常值用输出参数获得,而错误标志用return语句返回。
  3. 函数的功能要单一,即一个函数只完成一件事情,不要设计多用途 的函 数。函数体的规模要小,尽量控制在50行代码之内。
  4. 不仅要检查输入参数的有效性,还要检查通过其他途径进入函数体内的变量的有效性,如全局变量、文件句柄。
  5. 用于出错处理的返回值一定要清楚,让使用者不容易忽视或误解错误情况。
  6. 尽量避免函数带有记忆功能,相同的输入应产生相同的输出。带有记忆功能的函数,其行为可能是不可预测的,因为他的行为可能取决于某种
    “记忆状态”
    。这样的函数既不易于理解又不利于测试和维护,在c++/c语言中,函数static局部变量是函数的记忆存储器。建议少用static局部变量,除非必需,例如static
    局部变量可以实现全局常量的封装。
inlie const char * GetWindowsPath(void)
{
  sttic const char *p="C:\\Windows\\";
  return p;

}
  1. 尽管语法允许,但是请不要在内层程序块中定义会遮蔽外层程序块中的同名标识符,否则会损害程序的可理解性。

  2. 不要使用间接递归,即一个函数通过调用另一个函数来调用自己,因为它会损害程序的清晰性。

  3. 在函数的入口处,建议使用断言来检查参数的有效性(合法性)。

  4. 请给asseert语句加注释,告诉人们assert语句究竟要干什么。

  5. 如果输入参数采用“指针传递”,那么加const修饰可以防止意外的改动该指针指向的内存单元,起到保护的作用。

第七章 c++/c指针、数组和字符串

第八章 c++/c高级数据类型

  1. 为了不使程序产生混乱和妨碍理解,建议还使用struct定义简单的数据集合:而定义一些具有行为的ADT时最好采用class,如果采用struct似乎感觉不到面向对象的味道。

  2. 在设计位域的时候,最好不要让一个位域成员跨越一个不完整的字节来存放,因为这样会增加计算机运算的开销,例如,前面的DateTime的设计就不好,比较好的设计应该是:

struct DateTime
{
    unsigned int year;
    unsigned int month  :8;
    unsigned int day    :8;
    unsigned int hour   :8;
    unsigned int minute :8;
    unsigned int second :8;

]
  1. 请使用sizeof()运算来计算位域的大小而不是自己估算,因为你很容易估算错误。
  2. 使用匿名枚举来定义程序中出现的相关常量集合是一个好办法,它可以取代宏常量和const符号常量。例如:
enum
{
   OBJECT_CREATION  =0X10,
   OBJECT_DELETION  =0X11,
   STATE_CHANGE     =0X12,
   ATTR_VALUE_CHANGE =0X13,
   NEW_ALARM        =0X20,

ALARM_CLEARED   =0X21,
ALRAM_CHANGED    0X22
};

第九章 c++/c编译预处理

  1. 虽然宏定义很灵活,并且通过彼此结合可以产生 许多变形用法,但是c++/c程序员不要定义很复杂的宏,宏定义应该简单而清晰。
  2. 宏名采用大写字符组成的单词或其缩写序列,并在各单词之间使用"_"分隔。
  3. 如果需要公布某个宏,那么该宏定义应当放置在头文件中,否则放置在实现文件(.cpp)的顶部。
  4. 不要使用宏来定义新类型名,应该使用typedf,否则容易造成错误。
  5. 给宏添加注释时请使用块注释(/* */),而不要使用行注释。因为有些编译器可能会会把宏后面的行注释理解为宏体的一部分。
  6. 尽量使用const取代宏来定义符号变量。
  7. 对于较长的使用频率较高的重复代码片段,建议使用函数或模板而不要使用带参数的宏定义;而对于较短的重复代码片段,可以使用带参数的宏定义,这不仅是出于类型安全的考虑。而且也优化与折衷的体现。
  8. 尽量避免在局部范围内(如函数内、类型定义内等)定义宏,除非它只是在该局部范围内使用,否则会损害程序的清晰性。

第十章 c++/c文件结构和程序版式

  1. 哪些地方使用空行呢、。一般约定如下:
    1)ADT/UDT定义之间要留有空行。ADT内部的各个访问段(public、private等)之间要留空行;每一段内按照相关性分组的,建议在各组之间留空行,没有明显相关性的不需要分组。
    2)函数定义之间要留空行。在函数体内,完整的控制结构及单独的语句块之间要分别留出空行;他们与其他段落之间也要留出空行以示区分;逻辑上密切相关的语句序列之间不要留有空行(例如,初始化数据成员的一系列语句);最后一条return
    语句前要留有空行,除非该函数只有织一条语句;控制结构、语句块、条件编译块等遵循同样的规则。
    3)注释行或注释块与被它们注释的程序元素之间不要留空行。
  2. 1)不要书写复杂的语句行,一行代码只做一件事情,如只定义一个变量或只写一条语句,这样书写出来的代码容易阅读,并且便于写注释。
    2)if,elseif、for、do、while的语句各自占一行,其他语句不得紧跟其后;不论该语句中有多少行语句都要用{}括起来,这样可以防止书写失误,当你准备开始书写语句块时,首先写下一对{}然后在里面书写语句,这样可以避免不宜察觉的逻辑错误。
    3)局部变量在定义的同时应该初始化,而且要在同一行内初始化。这是因为,对于局部变量,系统不会自动初始化它们,在运行时它们的内存单元将保留上次使用以来留下的脏值。再者,如果变量的引用除距其定义处较远,变量的初始化就容易被遗忘,如果引用了未初始化的变量,很有可能导致运行错误。
  3. 1)关键字之后要留有空格。像const、virtual、inline、case等关键字之后至少要留一个空格,否则无法辨析关键字。像if、else、for、while、switch等关键字之后应留一个空格再跟(以突出关键字。
    2)函数名之后不留空格,无论是在原型、定义还是在调用中
    3)([向后紧跟],;、)向前紧跟,紧跟处不留空格,之后要留空格,如f(x,y,z);如果;不是一行的结束符,则后面也要留空格,如for(int; y; z;),
    4)预编译指令中#和保留字之间不要留空格;文件包含伪指令中文件名与两端的<>或“”之间不留空格。
    5)二元运算符(如 + += >= <= + * % && || << ^ 等)的前后应当加空格。
    6)一元运算符,如! ~ ++ – - &(取地址运算符) (反引用)等,与所作用的操作数之间不加空格。
    7). -> .
    ->* ::这类运算符前后不加空格;?:前后要加空格。
  4. 长表达式要在低优先运算符处拆分为对行,运算符放在新行之首(以示突出)。拆分出的新行要进行适当的缩进,使排版整齐,语句可读。这些运算符常见的有二元逻辑运算符。输入输出运算符等。
  5. 程序的分界符{应独占一行}并且位于同一列,,同时与引用他们的语句对齐。{}之内的代码块在{右边数格处左对齐,建议采用一个‘\t’字符或4个空格。各层嵌套请使用统一的缩进,在另一些情况下,为了不使语句块和{}看起来有被架空的感觉,可将{}和其内部的语句块对齐,并且都向右缩进4空格。
  6. 建议将修饰符*和&紧靠变量名,例如:char name 或者使用typedef做一个类型映射,例如
    typedef int
    p;
    p p1 p2;
  7. 1)注释是对代码的提示而不是文档。程序中的注释不可喧宾夺主,注释太多了会让人眼花缭乱。注释的花样要少,不要用注释拼图案。
    2)如果代码本来就是清楚的,则不必加注释,否则多此一举,令人厌恶
    3)边写代码边加注释,修改代码的同时修改相应的注释,以保证注释与代码的一致性。
    4)不在有用的注释要删除,注释应当准确、易懂、防止出现二义性。错误的注释不但无害而且有害。
    5)主食的额文职应与被描述的代码相邻,苦役放在代码的上方或右方,不可放在下方。当代码较长,特别是有多重嵌套时,应当在一些段落的结束处加注释便于阅读。例如:
    /******************************************
    *
    *
    *
    ********************************************/
  8. 建议采用“以行为为中心”的方式来编写类,即首先考虑类应该提供什么样的接口(即函数)。这是很多人的经验——“这样做不仅让自己在设计类时思路清晰,而且方便别人阅读。因为用户最关心的是接口,谁愿意看到一堆私有数据成员!”

第十一章 C++/C应用程序名规则

**1.共性规则**

1)标识符的名字应当直观可以拼读,可望文知义,不必进行“解码”。
2)标识符的长度应当符合“min-length & max-information”原则。
3)程序中不要出现紧靠大小写来区分的相似标识符,虽然C++/C是大小写相关的。
4)不要使程序中出现局部变量和全局变量同名的现象,尽管由于两者的作用域不同而不会发生语法错误,让人误解。
5) 变量应当使用名词或者形容词+名词的格式来命名。
6)全局函数的名字应当使用动词或者动词+名词(动宾词组)。类的成员函数应当使用“动词”,被省略掉的名词就是对象本身。例如:DrawBox()全局函数
7)用正确的反义词组命名具有相反意义的变量或相反动作的函数等

2.建议

1)尽量避免名字中出现数字编号,如value1、value2等,除非逻辑上的确需要如此。这是为了防止程序员偷懒,不肯动脑子而用的无意义的名字(因为数字编号最省事),就像
一般没有人会给子女起名叫张三或李四一样。
2)类型名和函数名均以大写字母开头的单词组合而成。
3)变量名和参数名采用第一单词首字母小写而后面的单词字母大写的单词组合,
4)符号常量和宏名用全大写的单词组合而成,并在单词之间用单下画线分隔,注意首尾不要使用下画线。
5)给静态变量加前缀s_(表示static)
6)如果不得已使用全局变量,这时全局变量加前缀g_(表示global)
7)类的数据成员加前缀m_(表示member),这样可以避免数据成员与成员函数的参数同名。
8)为了防止某一软件库中的一些标识符和其他软件库中的冲突,可以统一为各种标识符加上能反映软件性质的前缀。例如:三维图形OpenGL的所有库函数以gl开头,所有的常量(或者宏定义)均以GL开头,还有更好的办法使用名字空间。

第十二章 C++面向对象程序设计方法概述

规则

  1. 1)如果类A类B毫不相关,不可以为了使B的功能多一点就让B继承A的功能和属性。
    类的组合特性具体表现为两种:聚合(has-a)和关联(holds-a)。

C++支持多态的方法总结如下:

  1. 进过隐含的转型操作,令一个public多态基类的指针或者引用指向它的一个派生类的对象
  2. 通过这个指针或引用调用基类的虚函数,包括通过指针的反引用调用虚函数,因为反引用一个指针将返回所指对象的引用
  3. 使用dynamic_cast<>和typeid运算符。

C++的虚函数和多态有如下突出优点:

  1. 应用程序不必为每一个派生类编写功能调用,只需要对基类的虚函数进行改写或者扩展即可。可以大大提高程序的可以服用性和可扩展性。
  2. 派生类的功能可以被基类指针引用,这叫向后兼容。

第十三章 对象的初始化、拷贝和析构

  1. 不要在构造函数内做与初始化对象无关的工作,不要在析构函数内做与销毁一个对象无关的工作。也就是说,构造函数和析构函数应该做能够满足正确初始化和销毁一个对象的最少工作量,否则会降低效率,甚至可能让人误解。
  2. 初始化就是在对象创建的同时使用初值直接填充对象的内存单元,因此不会有数据结构转换等中间过程,也不会产生临时对象;而赋值则是在对象创建好后任何时候都可以调用的而且可以多次调用的函数,由于它调用的是“=”运算符,因此可能需要进行类型的转换。即会产生临时对象。
  3. 最好为每个类显式地定义构造函数和析构函数,即使他们暂时空着,尤其是当类含有指针成员或引用成员函数的时候。
  4. 构造函数的另一重要用途就是给一些可能存在的隐含成员创建一个初始化的机会,否则虚拟机制将不能保证实现。
  5. 构造函数分为三类,默认构造函数、拷贝构造函数和其他带参数的构造函数。
  6. 不能同时定义一个无参数的构造函数和一个参数全部有默认值的构造参数,否则会造成二义性。
  7. 如果没有显式地定义默认构造函数却定义带参数的构造函数,那么后者的存在就会组织编译器生成前者,于是类就没有默认构造函数,此时如果定义对象就会导致编译错误。
  8. 一般来说,重载的构造函数的行为都差不多,因此必然存在重复代码片段。当我们为类定义多个构造函数时,设法把其中相同任务的代码片段抽取出来并定义为一个非public的成员函数然后在每一个构造函数中适当的地方调用它。
  9. 如果不主动编写拷贝构造函数和拷贝赋值函数,编译器将以按成员拷贝的方式自动生成相应的默认函数。倘若类中含有指针成员或引用成员,那么两个默认的函数就可能隐含错误。
  10. 拷贝构造函数和拷贝赋值函数非常容易混淆,常导致错写、错用。拷贝构造函数实在对象呗创建并用另一个已经存在的对象来初始化它时调用的,而赋值函数只能把一个对象赋值给另一个已经存在的对象,使得那个已经存在的对象具有和源对象相同的状态。

第十四章 C++函数的高级特性

  1. 只能靠参数列表而不能仅靠返回值类型的不同来区分重载函数。编译器根据参数列表为每个重载函数产生不同的内部标识符。
  2. 并不是两个函数的名字相同就能构成重载。全局函数和类的成员函数同名不算重载,因为他门的作用域不同。

成员函数被重载的特征是:

 具有相同的作用域
 函数名字相同
 参数类型、顺序或数目不同(包括const或者非const参数)
 virtual关键字可有可无。

覆盖是指派生类重新实现(或者改写)了基类的成员函数,其特征是;

 不同的作用域(分别位于派生类和基类中)
 函数名称相同
 参数列表相同
 基类函数必须是虚函数。

虚函数的重载方式有两种,完全重写和扩展。 扩展是指派生类虚函数首先调用基类相应的虚函数,然后在增加新的 功能。
令人迷惑的隐藏规则!
具体规则如下:

派生类的函数与基类的函数同名,但是参数列表有所差异,此时无论有无virtual关键字,基类的函数在派生类中
将被隐藏(注意不能与重载混淆)
派生类的函数与基类的函数同名,参数列表页相同,但是基类函数没有virtual关键字,此时基类的函数在派生类
中将被隐藏(注意别与覆盖混淆)
  1. 参数默认值的使用规则是把参数默认值放在函数的声明中,而不要放在定义体中。
  2. 如果函数有多个参数,那么参数只能从后向前依次默认,否则将导致函数调用语句怪模怪样
  3. C++语言支持函数重载,所以才能将运算符当成函数来用,c语言就不行了,
    1)不要过分担心自己不会用,他的本质仍是程序员熟悉的函数。
    2)不要过分使用,如果他不能使代码变得更加易读易写,那就别用。
  4. 对任何内联函数,编译器在符号表里放入函数的声明,包括名字、参数类型、返回值类型(符号表是编译器用来收集和保存字面常量和某些符号常量的地方)。如果编译器没有发现内联函数存在的错误,那么该函数的代码也被放入符号表里。在调用一个内联函数时,编译器首先检查调用是否正确,如果正确,内联函数的代码就会直接代替函数调用语句,于是省去函数调用的开销。而对于预处理来说,预处理不能进行来兴安全检查和自动类型转换,假如内联函数时成员函数,对象地址(this)会被放在合适的地方,这也是预处理办不到的。
  5. 关键字inline必须与函数定义体放在一起才能使用函数真正的内联,仅把inline放在函数声明的前面不起任何作用。
  6. 内联不是万灵丹,它以代码膨胀(拷贝)为代价,仅仅省去了函数调用的开销,从而提高程序的执行效率。如果执行函数体内代码的时间比函数调用的开销大得多,那么inline的效率收益会很小,另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码凉增大,消耗更多的内存空间。
  7. 不少人误认为让类放入构造函数和析构函数成为内联函数更有效,殊不知,构造函数和析构函数会隐藏一些行为,如偷偷地调用基类或成员对象的构造函数和析构函数。所以不要轻易让构造函数和析构函数成为内联函数
    8.类型转换的本质是创建新的目标对象,并以源对象的值来初始化,所以源对象没有丝毫改变。不要把类型转换理解为将源对象的类型转换为目标类型。
    9.类型转换运算符只能定义费静态成员函数。
  8. 在C++程序中尽量不要在使用C风格的类型转换,除非源对象和目标类型都是基本类型的对象或指针,否则很不安全。C++的类型转换运算符在需要的时候会进行指针调整,因此结果比较安全。
  9. 不要混淆const成员函数和成员函数返回const类型,实际上,这两者之间没有必然联系。也就是说,const成员函数并非一定要返回const类型,非const成员函数也并非必须返回const类型。

第十五章 C++异常处理和RTTI

健壮性是指软件在异常环境下仍然能够进行正常运行的能力,健壮性是非常重要的软件质量属性。提高C++应用软件健壮性的基本手段之一就是使用异常处理技术。
C++的异常处理技术是一种面向对象的运行时错误处理机制,其思路完全不同C语言旧式的返回值检查策略。

  1. 在真正导致错误的语句即将执行之前,并且异常发生的条件已经具备时,使用我们自定义的软件异常(异常对象)来替代它,从而阻止它,因此,当异常抛出时真正的错误实际上并未发生。
  2. 虽然异常对象看上去像局部对象,但是它并非创建在函数堆栈上,而是在专用的异常堆栈上,因此它才可以跨接多个函数而传递到上层,否则在堆栈清退的过程中就会被销毁。不要企图把局部对象的地址作为异常对象抛出,因为局部对象会在异常抛出后函数堆栈清退的过程中被销毁。
    3.函数原型中的异常要与实现中的异常说明保持一致,否则容易引起异常冲突。
  3. 全局对象在程序运行之前构造,因此如果它们的构造函数中有异常抛出的话,将永远不会被捕获,全局对象的析构函数也是一样,因为它们在程序结束后才会被调用,这些异常只有操作系统才能捕获,应用程序无能为力。
    5.一般情况下不要把异常处理机制当做正常的程序空中流程来使用,如果不使用异常处理机制能够安全而高效地消除错误,那么就不要使用异常处理,但是由于不同编译器在实现异常处理机制时采用的技术一般不同,而且异常处理确实也是一种流程控制的手段,所以可能会应用在某些安全领域(比如反跟踪)
  4. catch块的参数应当采用引用传递而不是值传递,其中一个原因是异常对象可能在调用链中上溯好几个层次才能遇到匹配的处理块,显然引用产地比值传递的效率高得多,另一个原因是这样可以利用异常对象的多态性,因为异常类型可能是多态性,你也可以抛出一个异常对象的地址,那么catch块中的参数就应该是异常类型的指针。
  5. 在异常组合中,要合理安排异常处理的层次:一定要把派生类异常捕获放在基类异常捕获的前面,否则派生类异常匹配永远也不会执行到。
  6. 在异常抛出后,当找到第一个类型匹配的catch子句时,编译器就认为该异常已经被处理(识别)了。至于在catch块中怎么编写异常处理代码,就是自己的问题了。
  7. 如果实在无法判断到底会有什么异常抛出,那就使用“一网打尽”的策略;catch(void*)和catch(…)而且一定要放在异常组合的最后面,catch(void*)应该放在catch(…)的前面。
  8. 当你编写异常时,要确保派生类的成员函数的异常说明和基类成员函数的异常说明一直,即派生类改写的虚函数的异常说明至少要和对应的积累函数的异常说明相同,甚至更加严格。更特殊。
  9. 如果标准异常类型能够满足你的需要,就直接使用他们。这不仅减少了工作量,而且增强了代码的可移植性。

第十六章 内存管理

内存分配方式有以下三种:

1)从静态存储区域分配。内存在程序编译的时候就已经分配好了(即已经编址),这些内存在程序的整个运行期间都存在,如全局变量、static变量等。
2)在堆栈上分配。在函数执行期间,函数内局部变量(包括形参)的存储单元都创建在堆栈上,函数结束时这些存储单元自动释放(堆栈清退)。堆栈内存分配运算内置于处理器的指令集中,效率很高,并且一般不存在失败的危险,但是分配的内容容量有限,可能出现堆栈溢出。
3)从堆或自由空间上分配,亦称动态内存分配,程序在运行期间用malloc()或new申请任意数量的内存,程序员自己掌握释放内存的恰当时机(使用free()或者delete)。动态内存的生存期由程序员决定,使用非常灵活,但也最容易产生问题。
  1. 一般的原则,如果使用堆栈存储和静态存储就能满足应用要求,就不要使用动态存储。 这是因为在堆上分配内存需要很可观的额外开销。
  2. 用malloc或new申请内存之后,应该立即检查指针值是否为null或者进行异常处理,以防止使用值为null的指针。
  3. 不要忘记初始化指针、数组和动态内存,防止将未初始化的内存作为右值使用。
  4. 避免数组或指针下标越界,特别要当心发生“多1”或者少1 操作。
    动态内存的申请和释放必须配对,防止内存泄漏。
  5. 用free或delete释放了内存之后,立即将指针设置为NULL,防止产生“野指针”。
  6. new/delete并非库函数,而是语言实现直接支持的运算符,正如sizeof()和typeid()及C++新增的4个类型转换运算符也不是库函数一样。

问题:既然new/delete的功能完全覆盖了malloc/free,为什么C++不把malloc()/free()淘汰出局呢?

1)C++程序经常要调用C函数,而且有些C++实现可能使用malloc()/free()来实现new/delete,而C程序只能用malloc()/free()来管理动态内存。
2)使用new/delete更加安全,因为new可以自动计算他要构造的对象的字节数量(包括成员边界调整而增加的填补字节和隐含成员),而malloc()却不能;new直接返回目标类型的指针。不需要显式地转换类型,而malloc则返回void*,必须首先显式地转换成目标类型才能使用。
3)我们可以成为自定义类重载new/delete更高的效率,因此某些STL实现版本的内存分配器会采用malloc()/free()来进行存储管理。
4)在某些情况下,malloc()/free()可以提供new/delete更高的效率,因此某些STL实现版本的内存分配器会采用malloc()/free()来进行存储管理。

  1. 在未标准化时,c++中的new运算符总是返回NULL表示分配失败,就像malloc()那样,因此程序员不得不总检查其返回值。标准C++修订了new的语义,plain new 在失败后会抛出标准异常std::bad_alloc而不是返回NULL。
  2. nothrow new 就是不抛出异常的运算符new的形式,nothrow new在失败时返回NULL。所以使用它时就不需要设置异常处理器,而像过去一样检查返回值是否为NULL即可。
  3. placement new的主要用途就是:反复使用一块较大的动态分配成功的内存来构造不同类型的对象或者他们的数组。使用placement new构造起来的对象或其数组,要显式地调用他们的析构函数来销毁(析构函数并不释放对象的内存),千万不要使用delete,这是因为,placement new构造起来的对象或其他数组的大小并不一定等于原来分配内存大小,因此使用delete会造成内存泄漏,或者在之后释放呢村时出现运行时错误。
    总结下new/delete的用法
    在这里插入图片描述
  4. 无论是何种类型,new/delete和new[]/delete[]总是应该正确配对使用,因此,没有任何一种数据类型,对于其动态创建的数组来说delete
    p和delete [] p是等价的。
  5. 多次使用delete一个不等于NULL的指针会导致运行时错误,但是多次delete一个NULL指针没有任何危险,因为delete运算符会首先检查这种情况,如果指针为NULL,则直接返回。

第十七章学习和使用STL

  1. 由于关联容器在概念上是无序的,所以它只能通过元素的值来定位其中的元素对象,这是关联式容器具有find()函数的原因,也是调用这种容器insert()函数和erase()函数时可以不指定插入位置和删除位置而仅指定元素值或者索引值的原因(关联式容器会自动地为新插入的元素安排一个合适的位置)。
  2. 由于顺序容器本来就有序,所以他是通过元素对象在容器中的位置来标识一个元素的,而不是通过元素的值(因为它可以存储值相等的多个元素对象,而且它们的位置不一定相邻)。这也是顺序容器的insert()函数和erase()函数时必须指定插入位置和删除位置而不能仅指定元素值的原因。当然,关联式容器也能存储值相等的元素,比如multimap和multiset等,但是它们在容器中的位置肯定是相邻的。
  3. 显然,顺序容器的实现不会像关联式容器那样在增加和删除元素时对其元素进行自动排序,它的概念决定了自动排序对查找(定位)其中任一元素的平均效率没有任何贡献(否则反而会影响插入和删除的效率)
  4. map应该提供key值来定位value对象的接口,这就是map具有operator[]运算符的原因。
  5. 关联式容器 的find()和operator[]函数反映了它们在应用层面的“随机访问”方式。但从底层实现上看,它们采用二叉树存储结构决定 了访问方式仍然是顺序访问,真正随机访问是时间恒定的。如vector和deque的operator[],而关联式容器的这种“模拟随机”访问的效率则是O(log2^N),随着N的增大而按对数级增大。在创建关联式容器对象的时候,可以指定二叉树结点的排序方式(递增递减)。这就是关联式容器具有“谓词”,而顺序容器没有“谓词”的原因(优先级队列除外)
  6. 如果元素查找是经常做的操作,插入和删除反而是很少做的操作,那么就不适合使用顺序容器么应该使用关联式容器,反之就是顺序容器。
  7. 如果你在创建一个容器时能够预先估计出他可能存放的最大数目,那么你就可以给他预先分配足够数量的存储空间,从而可以避免频繁的存储空间重分配操作,某些STL容器带有这种功能的构造函数及reserve()方法。
  8. 尽量在容器的尾部执行插入操作没因为这里的插入操作效率最高。对于顺序容器来说,它们本身并不要求元素排序,因此你完全没有必要刻意的在开头或中间插入元素;对于关联式容器,它们在实现时就是有序的,调用insert()的时候会自动进行重新排序(伴随着树的平衡、旋转等操作),因此你也没有必要刻意的在开头或中间插入操作。
    为什么引用不能作为STL容器的元素类型?
    1)引用在创建时必须初始化一个具体的对象,而STL容器不能满足这一要求
    2)引用没有构造函数和析构函数,更没有负值语义。这就是说,STL容器只支持对象语义,而不支持引用语义,
  9. 对于关联式容器,元素类型的要求可能更苛刻一些,关联式容器在实现上默认按“<”对其元素进行排序,从而确定它们在二叉树上的位置次序,同时在定位元素对象时也要调用元素类型的operator<运算符。这就要求元素类型必须至少定义“<”运算符重载函数。
  10. 迭代器的通用是一种概念上的通用,所有的泛型容器和泛型算法都使用“迭代器”来指示元素对象,所有的迭代器都具有相同或相似的访问接口,但是每一种容器都有自己的迭代器类型,毕竟每一种容器的底层存储方式不尽相同,所以迭代器的实现方式就会不同。千万不要以为存在一种“通用的迭代器”——他可以适用于任何类型的容器。
  11. 指针代表真正的内存地址,即对象在内存中的存储位置;迭代器则代表元素在容器中的相对位置(当遍历容器的时候,关联式容器的元素也就具有了相对位置)
  12. 为什么要对迭代器进行分类,主要是泛型算法可以根据不同类别的迭代器所具有的不同能力来实现不同性能的版本,使得能力大的迭代器用于这些算法时具有更高的效率,
  13. 尽量使用迭代器类型,而不是显式地使用指针,
  14. 只是用迭代器提供的标准操作,不要使用任何非标准操作,以避免STL版本更新的时候出现不兼容问题。
  15. 当不会改动容器元素值的时候,请使用const迭代器(const_iterator)。
  16. 顺序容器vector和string都可用reserver()和resize()来预留空间或调整他们的大小:reserve()用来保留(扩充)容量,它并不改变容器的有效元素个数;resize()则调整容器的大小(size,有效元素的个数),而且有时候也会增大容器的容量。当把这两个函数与assign()、insert()、push_back()、replace()及泛型算法搭配起来使用。
  17. 尽量不要在遍历容器的过程中对容器进行插入元素、删除元素等修改操作,这和不要在for循环中修改计数器是一个道理,特别是连续存储的容器中。因为这些操作会使一些迭代器失效,特别是当前的迭代器,这在效果上等价于修改了循环计数器。
  18. 修改容器和修改容器中的元素对象的值是两回事,前者可能引起容器底层存储的变动,因此可能使迭代器失效,而后置则则不会。顺序容器允许直接修改其中元素对象的值;关联容器则不允许修改其元素的Key值,
  19. 窄化(narrow)这个词常用在类型转换(cast)中,专指类型安全的向下转型。这里的意思应该去掉基础结构中不使用与适配器的接口,也就是适配器在概念上不应该支持的接口,而保留或增加适配器特有的接口和属性。
  20. 泛型算法并不接受容器对象作为参数,因此它不了解与传入的迭代器关联容器的具体情况,当然他也不能对容器做任何操作(无法调用容器的方法),除非使用迭代器适配器。
  21. 在应用编程时要选用最合适的算法,找出效率最高的算法。
  22. STL提供了最常用的算法,尽管它们很有用,但不可能解决所有的应用问题,所以在编写自己的算法时,尽量做到与STL框架无缝结合,这样会提高算法的可扩展性。
  23. 使用vector时,用reserve()成员函数预先分配需要的内存空间,它既可以保护迭代器使之不会失效,又可以提高运行效率。
  24. 尽量不要使用slist的insert、erase、previous等操作。因为这些操作需要向前遍历,但是slist不能直接向前遍历,所以他会从头开始向后搜索,所需时间与位于当前之前的元素成正比。slist专门提供了insert_after、erase_after等函数进行优化,但若经常需要向前遍历,建议选用list
  25. 当需要stack、queue或priority_queue这样的数据结构时,直接使用对应的容器类,不要使用deque去做他们类似的工作。
  26. 当元素的有序比搜索速度更重要时,应选用set、multiset、map或multimap。否则,选用hash_set、hash_multiset、hash_map、hash_mulimap.
  27. 王容器中插入元素时,若元素在容器中的顺序无关紧要,请尽量加载最后面。若经常需要在序列容器的开头或中间增加与删除元素时应选用list
  28. 对关联式容器而言,尽量不要使用C语言的字符串(即字符指针)作为键值。如果非用不可,应显式地定义字符串比较运算符,operator<、operator==、operator<=等
  29. 当容器作为参数被传递时,请采用引用传递方式。否则将调用容器的拷贝构造函数,其开销是难以想象的。
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风赤

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值