zoukankan      html  css  js  c++  java
  • 高质量C++C编程指南笔记 标签: c++笔记 2015-11-22 20:59 179人阅读 评论(0) 收藏

    1、  在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外层,以减少 CPU 跨切循环层的次数。

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

    3、  即使程序真的不需要default 处理,也应该保留语句 default : break。

    4、  C 语言用#define 来定义常量(称为宏常量)。 C++ 语言除了 #define 外还可以用 const 来定义常量(称为 const 常量)。

    5、  const 与 #define 的比较

    Ø  const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误(边际效应)。

    Ø  有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试。

    6、  需要对外公开的常量放在头文件中,不需要对外公开的常量放在定义文件的头部。为便于管理,可以把不同模块的常量集中存放在一个公共的头文件中。

    7、  const 数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的,因为类可以创建多个对象,不同的对象其 const 数据成员的值可以不同。

    8、  不能在类声明中初始化const 数据成员。以下用法是错误的,因为类的对象未被创建时,编译器不知道 SIZE 的值是什么。

    class A

    {…

    const intSIZE = 100; // 错误,企图在类声明中初始化 const 数据成员

    intarray[SIZE]; // 错误,未知的 SIZE

    };

    9、  const 数据成员的初始化只能在类构造函数的初始化表中进行。

    10、             枚举常量不会占用对象的存储空间,它们在编译时被全部求值。枚举常量的缺点是:它的隐含数据类型是整数,其最大值有限,且不能表示浮点数(如 PI=3.14159)。

    11、             一般地,应将目的参数放在前面,源参数放在后面。

    12、             有时候函数原本不需要返回值,但为了增加灵活性如支持链式表达,可以附加返回值。

    13、             很多程序错误是由非法参数引起的,我们应该充分理解并正确使用“断言”( assert)来防止此类错误。

    14、             return 语句不可返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。

    15、             断言 assert 是仅在 Debug 版本起作用的宏,它用于检查“不应该”发生的情况。

    16、             引用的一些规则如下:

    Ø  引用被创建的同时必须被初始化(指针则可以在任何时候被初始化)。

    Ø  不能有 NULL 引用,引用必须与合法的存储单元关联(指针则可以是 NULL)。

    Ø  一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象)。

    17、             内存分配方式有三种:

    Ø  从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量, static 变量。

    Ø  在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

    Ø  从堆上分配,亦称动态内存分配。程序在运行的时候用 malloc 或 new 申请任意多少的内存,程序员自己负责在何时用 free 或 delete 释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。

    18、             常见的内存错误及其对策如下:

    Ø  内存分配未成功,却使用了它。

    Ø  内存分配虽然成功,但是尚未初始化就引用它。

    Ø  内存分配成功并且已经初始化,但操作越过了内存的边界。

    Ø  忘记了释放内存,造成内存泄露。

    Ø  释放了内存却继续使用它。(使用 free 或 delete 释放了内存后,没有将指针设置为 NULL。导致产生“野指针”。)

    19、             数组名对应着(而不是指向)一块内存,其地址与容量在生命期内保持不变,只有数组的内容可以改变。

    20、             不能对数组名进行直接复制与比较。

    21、             用运算符 sizeof 可以计算出数组的容量(字节数)。sizeof(p)得到的是一个指针变量的字节数,相当于 sizeof(char*),而不是 p 所指的内存容量。 C++/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。

    22、             注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。

    23、             如果函数的参数是一个指针,不要指望用该指针去申请动态内存。(可以用指针的指针)

    24、             发现指针 p 被 free 以后其地址仍然不变(非 NULL),只是该地址对应的内存是垃圾, p 成了“野指针”。如果此时不把 p 设置为 NULL,会让人误以为 p 是个合法的指针。

    25、             free()之后由于指针所指向的内存已经被释放,所以其它代码有机会改写其中的内容,相当于该指针从此指向了自己无法控制的地方,也称为野指针。

    26、             我们发现指针有一些“似是而非”的特征:

    Ø  指针消亡了,并不表示它所指的内存会被自动释放。

    Ø  内存被释放了,并不表示指针会消亡或者成了 NULL 指针。

    27、             “野指针”的成因主要有两种:

    Ø  指针变量没有被初始化。任何指针变量刚被创建时不会自动成为 NULL 指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为 NULL,要么让它指向合法的内存。

    Ø  指针 p 被 free 或者 delete 之后,没有置为 NULL,让人误以为 p 是个合法的指针。

    Ø  指针操作超越了变量的作用范围。

    28、             malloc 与 free 是 C++/C 语言的标准库函数,new/delete 是 C++的运算符。它们都可用于申请动态内存和释放内存。

    29、             对于非内部数据类型的对象而言,光用 maloc/free 无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free 是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于 malloc/free。因此 C++语言需要一个能完成动态内存分配和初始化工作的运算符 new,以及一个能完成清理与释放内存工作的运算符 delete。注意 new/delete 不是库函数。

    30、             由于内部数据类型的“对象”没有构造与析构的过程,对它们而言 malloc/free 和new/delete 是等价的。

    31、             如果用 free 释放“ new 创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错。如果用 delete 释放“ malloc 申请的动态内存”,理论上讲程序不会出错,但是该程序的可读性很差。所以 new/delete 必须配对使用, malloc/free 也一样。

    32、             malloc/free 的使用我们应当把注意力集中在两个要素上:“类型转换”和“ sizeof”。

    Ø  malloc 返回值的类型是 void *,所以在调用 malloc 时要显式地进行类型转换,将 void * 转换成所需要的指针类型。

    Ø  malloc 函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。

    33、             如果 p 是 NULL 指针,那么 free 对 p 无论操作多少次都不会出问题。如果 p 不是 NULL 指针,那么 free 对 p连续操作两次就会导致程序运行错误。

    34、             如果用 new 创建对象数组,那么只能使用对象的无参数构造函数。例如

    Obj *objects = newObj[100]; // 创建100 个动态对象不能写成

    Obj *objects = newObj[100](1);// 创建100 个动态对象的同时赋初值 1

    在用delete 释放对象数组时,留意不要丢了符号‘ []’。例如

    delete []objects; // 正确的用法

    delete objects; // 错误的用法

    后者相当于delete objects[0],漏掉了另外 99 个对象。

    35、             c++中为什么static成员函数不能声明为const?(注意是成员函数)

    答:这是C++的规则,const修饰符用于表示函数不能修改成员变量的值,该函数必须是含有this指针的类成员函数,函数调用方式为thiscall,而类中的static函数本质上是全局函数,调用规约是__cdecl或__stdcall,不能用const来修饰它。

    36、             重载和内联机制既可用于全局函数也可用于类的成员函数, const 与virtual 机制仅用于类的成员函数。

    37、             只能靠参数而不能靠返回值类型的不同来区分重载函数。

    38、             注意并不是两个函数的名字相同就能构成重载。全局函数和类的成员函数同名不算重载,因为函数的作用域不同。

    39、             当心隐式类型转换导致重载函数产生二义性。

    40、             成员函数被重载的特征:

    Ø  相同的范围(在同一个类中);

    Ø  函数名字相同;

    Ø  参数不同;

    Ø  virtual 关键字可有可无。

    41、             覆盖是指派生类函数覆盖基类函数,特征是:

    Ø  不同的范围(分别位于派生类与基类);

    Ø  函数名字相同;

    Ø  参数相同;

    Ø  基类函数必须有virtual 关键字。

    42、             “隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:

    Ø  如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无 virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。【参数不同一定会被隐藏】

    Ø  如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有 virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。【没有virtual一定会被隐藏】

    43、             参数缺省值只能出现在函数的声明中,而不能出现在定义体中。

    44、             如果函数有多个参数,参数只能从后向前挨个儿缺省,否则将导致函数调用语句怪模怪样。

    45、             在 C++运算符集合中,有一些运算符是不允许被重载的。这种限制是出于安全方面的考虑,可防止错误和混乱。

    Ø  不能改变 C++内部数据类型(如int,float 等)的运算符。

    Ø  不能重载‘ .’,因为‘ .’在类中对任何成员都有意义,已经成为标准用法。

    Ø  不能重载目前 C++运算符集合中没有的符号,如#,@,$等。原因有两点,一是难以理解,二是难以确定优先级。

    Ø  对已经存在的运算符进行重载时,不能改变优先级规则,否则将引起混乱。

    46、             在 C 程序中,可以用宏代码提高执行效率。宏代码本身不是函数,但使用起来象函数。预处理器用复制宏代码的方式代替函数调用,省去了参数压栈、生成汇编语言的 CALL调用、返回参数、执行 return 等过程,从而提高了速度。使用宏代码最大的缺点是容易出错,预处理器在复制宏代码时常常产生意想不到的边际效应。使用宏代码还有另一种缺点:无法操作类的私有数据成员。

    47、             C++ 语言的函数内联机制既具备宏代码的效率,又增加了安全性,而且可以自由操作类的数据成员。所以在 C++ 程序中,应该用内联函数取代所有宏代码。

    48、             关键字 inline 必须与函数定义体放在一起才能使函数成为内联,仅将 inline 放在函数声明前面不起任何作用。

    49、             定义在类声明之中的成员函数将自动地成为内联函数。

    50、             内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。

    51、             以下情况不宜使用内联:

    Ø  如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。

    Ø  如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。

    52、             每个类只有一个析构函数和一个赋值函数,但可以有多个构造函数(包含一个拷贝构造函数,其它的称为普通构造函数)。对于任意一个类 A,如果不想编写上述函数,C++编译器将自动为 A 产生四个缺省的函数,如:

    Ø  A(void);                                          //缺省的无参数构造函数

    Ø  A(const A &a);                             //缺省的拷贝构造函数

    Ø  ~A(void);                                       //缺省的析构函数

    Ø  A & operate =(const A &a);     //缺省的赋值函数

    53、             构造函数与析构函数的另一个特别之处是没有返回值类型,这与返回值类型为 void 的函数不同。

    54、             构造函数有个特殊的初始化方式叫“初始化表达式表”(简称初始化表)。初始化表位于函数参数表之后,却在函数体 {} 之前。这说明该表里的初始化工作发生在函数体内的任何代码被执行之前。

    55、             类的 const 常量只能在初始化表里被初始化,因为它不能在函数体内用赋值的方式来初始化。

    56、             类的数据成员的初始化可以采用初始化表或函数体内赋值两种方式,这两种方的效率不完全相同。

    57、             非内部数据类型的成员对象应当采用第一种方式初始化,以获取更高的效率。

    58、             构造和析构的次序:构造从类层次的最根处开始,在每一层中,首先调用基类的构造函数,然后调用成员对象的构造函数。析构则严格按照与构造相反的次序执行,该次序是唯一的,否则编译器将无法自动执行析构过程。

    59、             成员对象初始化的次序完全不受它们在初始化表中次序的影响,只由成员对象在类中声明的次序决定。这是因为类的声明是唯一的,而类的构造函数可以有多个,因此会有多个不同次序的初始化表。如果成员对象按照初始化表的次序进行构造,这将导致析构函数无法得到唯一的逆序。

    60、             拷贝构造函数是在对象被创建时调用的,而赋值函数只能被已经存在了的对象调用。

    61、             深度复制(针对《c++ primer plus》一书string例题的解释):复制构造函数应当复制字符串并将副本的地址赋给str成员,而不仅仅是复制字符串的地址,这样每个对象都有自己的字符串,而不是引用另一个对象的字符串,调用析构函数时都将释放不同的字符串,而不会试图去释放已经被释放的字符串。

    62、             何时调用复制构造函数:新建一个对象并将其初始化为同类现有对象时,复制构造函数都将被调用。每当程序生成了对象副本时,编译器都将使用复制构造函数。具体的说,当函数按值传递对象或函数返回对象时,都将使用复制构造函数。记住,按值传递意味着创建原始变量的一个副本。编译器生成临时对象时,也将使用复制构造函数。何时生成临时对象随编译器而异,但无论那种编译器,当按值传递和返回对象时,都将调用复制构造函数。

    63、             String类的赋值函数要做的工作:

    Ø  自我检查赋值的情况

    Ø  释放成员指针以前指向的内存

    Ø  复制数据而不仅仅是数据的地址

    Ø  指向一个返回对象的引用(不要将 return *this 错写成 return this)

    64、             基类与派生类的析构函数应该为虚(即加 virtual关键字)。

    #include <iostream.h>                                                                     

    classBase

    {

    public:

    virtual ~Base() { cout<< "~Base" << endl ; }

    };

    classDerived : public Base

    {

    public:

    virtual ~Derived() { cout<< "~Derived" << endl; }

    };

    voidmain(void)

    {

    Base * pB = new Derived; // upcast

    delete pB;

    }

    输出结果为:

    ~Derived

    ~Base

    如果析构函数不为虚,那么输出结果为

    ~Base

    65、             如果使用指向对象的引用或指针来调用虚方法,程序将使用为对象类型定义的方法,而不使用为引用或指针类型定义的方法。这称为动态联编或晚期联编。这种行为非常重要,因为这样基类指针或引用可以指向派生类对象。

    66、              const 修饰函数的参数: 如果参数作输出用,不论它是什么数据类型,也不论它采用“指针传递”还是“引用传递”,都不能加 const 修饰,否则该参数将失去输出功能。const 只能修饰输入参数。(参数做输出用不能加constconst只能修饰输入参数)

    67、             如果输入参数采用“值传递”,由于函数将自动产生临时变量用于复制该参数,该输入参数本来就无需保护,所以不要加 const 修饰。

    68、             const 成员函数的声明看起来怪怪的: const 关键字只能放在函数声明的尾部,大概是因为其它地方都已经被占用了。


    您的资助是我最大的动力!
    金额随意,欢迎来赏!

  • 相关阅读:
    oracle数据库体系架构详解
    数据库迁移之从oracle 到 MySQL
    一个专业DBA应具备的技能
    Oracle 内存参数调优设置
    正向代理与反向代理的区别
    负载均衡的几种常用方式
    Java虚拟机JVM学习07 类的卸载机制
    Java虚拟机JVM学习06 自定义类加载器 父委托机制和命名空间的再讨论
    Git 撤销修改
    Git 分支管理和冲突解决
  • 原文地址:https://www.cnblogs.com/Corphish/p/7144191.html
Copyright © 2011-2022 走看看