C++ primer笔记3

1、复制构造函数是一中特殊构造函数,具有单个形参,该形参(常用const修饰)是对给类类型的引用。

2、析构函数是构造函数的互补:当对象超出作用域或动态分配的对象被删除时,将自动应用析构函数。析构函数可用于释放对象时构造或在对象的生命周期中所获取的资源。

3、复制构造函数、赋值操作符和析构函数总称为复制控制。

4、如果我们没有定义复制构造函数,编译器就会为我们合成一个。与合成的默认构造函数不同,即使我们定义了其他的构造函数,也会合成赋值构造函数。

5、逐个成员初始化最简单的概念就是,将合成复制构造函数看成这样一个构造函数:其中每个数据成员在构造函数初始化列表中进行初始化。(逐个成员是指非static成员)

6、禁止复制:iostream类就不允许复制。为了防止复制,类必须显示声明其复制构造函数为private。然而,类的友元和成员仍可以进行复制。如果想要连友元和成员中的复制也禁止,就可以声明一个(private)复制构造函数但不对其进行定义。

7、大多数类应定义复制构造函数和默认构造函数。不定义复制构造函数和默认构造函数,会严重局限类的使用。不允许复制的类对象只能作为引用传递给函数或从函数返回,它们也不能用作容器的元素。

8、类何时需要定义赋值操作符?

①类中包含指针成员②赋值时需要做一些特定工作

9、如何编写析构函数:析构函数是个成员函数,它的名字是在类名字之前加上一个代字号(~),它没有返回值,没有形参。因为不能指定任何形参,所以不能重载析构函数。

10、赋值操作符不需要分配新对象,他只是必须记得给其指针所指向的对象赋新值,而不是给指针本身赋值。

11、分配内存或其他资源的类几乎总是需要定义和复制控制成员来管理所分配的资源。如果一个类需要析构函数,则它几乎也总是需要定义复制构造函数和赋值操作符。

12、不能重载的操作符有::.*.?:

13、重载操作符必须具有至少一个类类型或枚举类型的操作数。用于内置类型的操作符,其含义不能改变。例如int、double...

14、大多数重载操作符可以定义为普通非成员函数(通常将它们设置成所操作类的友元)或类的成员函数。

15、重载逗号、取地址、逻辑与等操作符通常不是好做法。这些操作符具有有用的内置含义,如果我们定义了自己的版本,就不能再使用这些内置含义。

16、输入输出重载符:

①输出重载符<<的重载

为了与IO标准库一致,操作符应接受ostream作为第一个形参,对类类型const对象的引用作为第二个形参,并返回对形参的引用。

    ostream& operator<<(ostream& os, const ClassType &object){
        //any special logic prepare object
        os << " ... ";//...
        return os;
    }

②输入操作符>>的重载

输入输出操作符有如下区别:输入操作符必须处理错误和文件结束的可能性。

    istream& operator>>(istream& in, Sales_item& s){
        double price;
        in >> s.isbn >> s.units_sold >> price;
        if (in){                      //check that inputs succeeded
            s.revenue = s.units_sold * price;
        }
        else
            s = Sales_item();  //input failed: reset object to default state
        return in;
    }

17、关联容器以及某些算法,使用默认<操作符。一般而言,关系操作符,诸如相等操作符,应该定义为非成员函数。

18、类赋值操作符接受类类型形参,通常,该形参是对类类型的const引用,但也可以是对类类型的非const引用。如果没有定义这个操作符,则编译器将合成它。类赋值操作符必须是类的成员,以便编译器可以知道是否需要合成一个。

19、赋值必须返回对*this的引用

    Sales_item& Sales_item::operator+=(const Sales_item&rhs){
        units_sold += rhs.units_sold;
        revenue += rhs.revenue;
        return *this;
    }

20、成员访问操作符:C++语言允许重载解引用操作符(*)和箭头操作符(->)

箭头操作符必须定义为类的成员函数。解引用操作符不要求定义为成员,但将它作为成员一般也是正确的。

21、自增操作符和自减操作符

    前缀自增自减:
    class CheckPtr{
    public:
        CheckPtr& operator++();
        CheckPtr& operator--();
    };

    后缀自增自减:后缀操作符函数接受一个额外的(无用的)int型形参
    class CheckPtr{
    public:
        CheckPtr& operator++(int);
        CheckPtr& operator--(int);
    };

22、转换操作符是一种特殊的类成员函数。它定义将类类型值转变成其他的类型值的转换。转换函数必须是成员函数,不能指定返回类型,并且形参表必须为空。转换函数一般不应该改变被转换的对象。因此,转换操作符通常应定义为const成员。

operator samllInt() const    { return val%256;}

23、当两个类定义了相互转换时,很可能存在二义性。避免二义性的最好方法是避免编写互相隐式转换的成对的类。

24、面对对象编程基于三个基本的概念:数据抽象、继承和动态绑定。在C++中,用类进行数据抽象,用类派生从一个类继承另一个类:派生类继承基类的成员。动态绑定使编译器能够在运行时决定是使用基类定义中定义的函数还是派生类中定义的函数。

25、在C++中,基类必须指出希望派生类重定义那些函数,定义为virtual的函数是基类期待派生类重新定义的,基类希望派生类继承的不能定义为虚函数。

 26、在C++中,通过基类的引用(或指针)调用虚函数时,发生动态绑定。引用(或指针)既可以指向基类对象也可以指向派生类对象,这一事实是动态绑定的关键。用引用(或指针)调用的虚函数在运行时确定,被调用的函数是引用(或指针)所指对象的实际类型所定义的。

27、保留字virtual只在类内部的成员函数声明中出现,不能用在类定义体外部出现的函数定义上。

28、protected成员可以被派生类对象访问但不能被该类型的普通用户访问。

29、为了定义派生类,使用类派生列表指定基类。类派生列表指定了一个或多个基类,具有如下形式:

class classname: access-label   base-class        //class dog: public animal

30、派生类一般会重定义所继承的虚函数,如果派生类没有重定义某个虚函数,则使用基类中定义的版本。

一旦函数在基类函数中声明为虚函数,它就一直是虚函数,派生类无法改变该函数为虚函数这一事实。派生类重定义虚函数时,可以使用virtual保留字,但不是必须这样做。

31、引用和指针的静态类型与动态类型可以不同,这是C++用以支持多态的基石。只有通过引用和指针调用,虚函数才在运行时确定。只有在这些情况下,直到运行时才知道对象的动态类型。

32、非虚函数总是在编译时根据调用该函数的对象、引用或指针的类型确定。

33、覆盖虚函数机制:只有成员函数中的代码才应该使用作用域操作符覆盖虚函数机制。派生类虚函数调用基类版本时,必须显示使用作用域操作符。如果派生类函数忽略了这样做,则函数调用会在运行时确定并且僵尸一个自身调用,从而导致无穷递归。

34、尽管私有继承在使用class保留字时是默认情况,但这在实践中 相对罕见。因为私有继承是如此罕见,通常显示指定private是比较依赖于默认更好的办法。显示指定可清楚指出私有继承而不是一时疏忽。

35、友元关系不能继承。基类的友元对派生类的成员没有特殊访问权限。如果基类被授予友元关系,则只有基类具有特殊访问权限,该基类的派生类不能访问授予友元关系的类。

36、如果基类定义了static成员,则整个继承层次中只有一个这样的成员。无论从基类派生出多少个派生类,每个static成员只有一个实例。

37、static成员遵循常规访问控制:如果成员在基类中为private,则派生类不能访问它。假定可以访问成员,则既可以通过基类访问static成员,也可以通过派生类访问static成员。

38、派生类到基类的转换:如果有一个派生类型的对象,则可以使用它的地址对基类类型的指针进行赋值或初始化。同样,可以使用派生类型的引用或对象初始化基类类型的引用。严格来讲,对对象没有类似转换。编译器不会自动将派生类型对象转换为基类类型对象。

①引用转换不同于转换对象②用派生类对象对基类对象进行初始化或赋值③派生类到基类转换的可访问性

39、基类到派生类的转换:从基类到派生类的自动转换是不存在的,原因在于基类对象 只能是基类对象,它不能包含派生类类型的成员。需要派生类对象时不能使用基类对象

40、构造函数和复制控制:当构造、复制、赋值和撤销派生类型对象时,也会构造、复制、赋值和撤销这些基类子对象。

派生类的构造函数受继承关系的影响,每个派生类构造函数除了初始化自己的数据成员之外,还要初始化基类。

构造函数初始化列表为类的基类和成员提供初始值,它并不指定初始化的执行次序。首先初始化基类,然后根据声明次序初始化派生类的成员。

41、一个类只能初始化自己的直接基类。直接基类就是在派生列表中指定的类。

构造函数只能初始化其直接基类的原因是每个类都定义了自己的接口。一旦类定义了自己的接口,与该类对象的所有交互都应该通过该接口,即使对象是派生类对象的一部分也不行。

派生类应通过使用基类构造函数尊重基类的初始化意图,而不是在派生类构造函数中队这些成员赋值。

42、派生类复制构造函数:如果派生类定义了自己的复制构造函数,该复制构造函数一般应显示使用基类复制构造函数初始化对象的基类部分。

43、派生类赋值操作符:赋值操作符通常与复制构造函数类似:如果派生类定义了自己的赋值操作符,则该操作符必须对基类部分进行显示赋值。

44、派生类析构函数:析构函数的工作与复制构造函数和赋值操作符不同:派生类析构函数不负责撤销基类对象的成员。编译器总是显示调用派生类对象基类部分的析构函数。每个析构函数只负责清除自己的成员。

45、删除指向动态分配对象的指针时,需要运行析构函数在释放对象的内存之前清除对象。如果删除基类指针,则需要运行基类析构函数并清除基类的成员,如果对象实际是派生类型的,则没有定义该行为。

46、在复制控制成员中,只有析构函数应定义为虚函数,构造函数不能定义为虚函数。构造函数是在对象完全构造之前运行的,在构造函数运行的时候,对象的动态类型还不完整。

47、将赋值操作符设为虚函数可能会令人混淆,因为虚函数必须在基类和派生类中具有同样的形参。

48、构造派生类对象时首先运行基类构造函数初始化对象的基类部分。在执行基类构造函数时,对象的派生类部分是未初始化的。实际上,此时对象还不是一个派生类对象。

49、对象、引用或指针的静态类型决定了对象能够完成的行为。甚至当静态类型和动态类型可能不同的时候,就像使用基类类型的引用或指针时可能会发生,静态类型仍然决定着可以使用什么成员。

50、基类类型的指针(引用或对象)只能访问对象的基类部分。

51、名字与冲突:与基类成员同名的派生类成员将屏蔽对基类成员的直接访问。使用作用域操作符可以访问被屏蔽的基类成员。

52、作用域和成员函数:在基类或派生类中使用同一名字的成员函数,其行为和数据成员一样:在派生类作用域中派生类成员将屏蔽基类成员。即使函数原型不同,基类成员也会被屏蔽。

53、虚函数为什么必须在基类和派生类中拥有统一原型?如果基类成员与派生类成员接受的实参不同,就没有办法通过基类类型的引用或指针调用派生类函数。

通过基类调用访问被屏蔽的虚函数:通过基类类型的引用或指针调用函数时,编译器将在基类中查找该函数而忽略派生类。

54、含有(或继承)一个或多个纯虚函数的类是抽象基类。除了作为抽象基类的派生类的对象的组成成分,不能创建抽象类型的对象。

 

你可能感兴趣的:(C++Primer)